Scaffolder: Refactor publish and prepare
Co-authored-by: blam<ben@blam.sh>
This commit is contained in:
@@ -30,10 +30,7 @@ describe('Helpers', () => {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Template',
|
||||
metadata: {
|
||||
annotations: {
|
||||
// [LOCATION_ANNOTATION]:
|
||||
// 'github:https://github.com/benjdlambert/backstage-graphql-template/blob/master/template.yaml',
|
||||
},
|
||||
annotations: {},
|
||||
name: 'graphql-starter',
|
||||
title: 'GraphQL Service',
|
||||
description:
|
||||
@@ -275,11 +272,11 @@ describe('Helpers', () => {
|
||||
expect(detector('http://derp.org:80/wat')).toBe('gitlab');
|
||||
expect(detector('https://foo.org/wat')).toBe('gitlab');
|
||||
expect(detector('http://not.derp.net')).toBe(undefined);
|
||||
expect(detector('http://derp.net')).toBe('azure/api');
|
||||
expect(detector('http://derp.net:8080/wat')).toBe('azure/api');
|
||||
expect(detector('http://derp.net')).toBe('azure');
|
||||
expect(detector('http://derp.net:8080/wat')).toBe('azure');
|
||||
expect(detector('http://github.com')).toBe('github');
|
||||
expect(detector('http://gitlab.com')).toBe('gitlab');
|
||||
expect(detector('http://dev.azure.com')).toBe('azure/api');
|
||||
expect(detector('http://dev.azure.com')).toBe('azure');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -74,7 +74,8 @@ export function makeDeprecatedLocationTypeDetector(
|
||||
// These are installed by default by the integrations
|
||||
hostMap.set('github.com', 'github');
|
||||
hostMap.set('gitlab.com', 'gitlab');
|
||||
hostMap.set('dev.azure.com', 'azure/api');
|
||||
hostMap.set('dev.azure.com', 'azure');
|
||||
hostMap.set('bitbucket.org', 'bitbucket');
|
||||
|
||||
config.getOptionalConfigArray('integrations.github')?.forEach(sub => {
|
||||
hostMap.set(sub.getString('host'), 'github');
|
||||
@@ -83,7 +84,7 @@ export function makeDeprecatedLocationTypeDetector(
|
||||
hostMap.set(sub.getString('host'), 'gitlab');
|
||||
});
|
||||
config.getOptionalConfigArray('integrations.azure')?.forEach(sub => {
|
||||
hostMap.set(sub.getString('host'), 'azure/api');
|
||||
hostMap.set(sub.getString('host'), 'azure');
|
||||
});
|
||||
config.getOptionalConfigArray('integrations.bitbucket')?.forEach(sub => {
|
||||
hostMap.set(sub.getString('host'), 'bitbucket');
|
||||
|
||||
@@ -46,7 +46,7 @@ describe('AzurePreparer', () => {
|
||||
metadata: {
|
||||
annotations: {
|
||||
[LOCATION_ANNOTATION]:
|
||||
'azure/api:https://dev.azure.com/backstage-org/backstage-project/_git/template-repo?path=%2Ftemplate.yaml',
|
||||
'url:https://dev.azure.com/backstage-org/backstage-project/_git/template-repo?path=%2Ftemplate.yaml',
|
||||
},
|
||||
name: 'graphql-starter',
|
||||
title: 'GraphQL Service',
|
||||
@@ -95,7 +95,7 @@ describe('AzurePreparer', () => {
|
||||
{ logger },
|
||||
);
|
||||
|
||||
await preparer.prepare(mockEntity);
|
||||
await preparer.prepare(mockEntity, { logger });
|
||||
|
||||
expect(Git.fromAuth).toHaveBeenCalledWith({
|
||||
logger,
|
||||
@@ -116,7 +116,7 @@ describe('AzurePreparer', () => {
|
||||
{ logger },
|
||||
);
|
||||
|
||||
await preparer.prepare(mockEntity);
|
||||
await preparer.prepare(mockEntity, { logger });
|
||||
|
||||
expect(Git.fromAuth).toHaveBeenCalledWith({
|
||||
logger,
|
||||
@@ -128,7 +128,7 @@ describe('AzurePreparer', () => {
|
||||
it('calls the clone command with the correct arguments for a repository', async () => {
|
||||
const preparer = new AzurePreparer(new ConfigReader({}), { logger });
|
||||
|
||||
await preparer.prepare(mockEntity);
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
|
||||
expect(mockGitClient.clone).toHaveBeenCalledWith({
|
||||
url:
|
||||
@@ -141,7 +141,7 @@ describe('AzurePreparer', () => {
|
||||
const preparer = new AzurePreparer(new ConfigReader({}), { logger });
|
||||
delete mockEntity.spec.path;
|
||||
|
||||
await preparer.prepare(mockEntity);
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
|
||||
expect(mockGitClient.clone).toHaveBeenCalledWith({
|
||||
url:
|
||||
@@ -154,7 +154,9 @@ describe('AzurePreparer', () => {
|
||||
const preparer = new AzurePreparer(new ConfigReader({}), { logger });
|
||||
mockEntity.spec.path = './template/test/1/2/3';
|
||||
|
||||
const response = await preparer.prepare(mockEntity);
|
||||
const response = await preparer.prepare(mockEntity, {
|
||||
logger: getVoidLogger(),
|
||||
});
|
||||
|
||||
expect(response.split('\\').join('/')).toMatch(
|
||||
/\/template\/test\/1\/2\/3$/,
|
||||
@@ -167,6 +169,7 @@ describe('AzurePreparer', () => {
|
||||
|
||||
const response = await preparer.prepare(mockEntity, {
|
||||
workingDirectory: '/workDir',
|
||||
logger: getVoidLogger(),
|
||||
});
|
||||
|
||||
expect(response.split('\\').join('/')).toMatch(
|
||||
|
||||
@@ -62,7 +62,7 @@ export class AzurePreparer implements PreparerBase {
|
||||
const workingDirectory = opts.workingDirectory ?? os.tmpdir();
|
||||
const logger = opts.logger;
|
||||
|
||||
if (!['azure/api', 'url'].includes(protocol)) {
|
||||
if (!['azure', 'url'].includes(protocol)) {
|
||||
throw new InputError(
|
||||
`Wrong location protocol: ${protocol}, should be 'url'`,
|
||||
);
|
||||
|
||||
@@ -81,7 +81,7 @@ describe('BitbucketPreparer', () => {
|
||||
|
||||
it('calls the clone command with the correct arguments for a repository', async () => {
|
||||
const preparer = new BitbucketPreparer(new ConfigReader({}), { logger });
|
||||
await preparer.prepare(mockEntity);
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
expect(mockGitClient.clone).toHaveBeenCalledWith({
|
||||
url: 'https://bitbucket.org/backstage-project/backstage-repo',
|
||||
dir: expect.any(String),
|
||||
@@ -104,7 +104,7 @@ describe('BitbucketPreparer', () => {
|
||||
{ logger },
|
||||
);
|
||||
|
||||
await preparer.prepare(mockEntity);
|
||||
await preparer.prepare(mockEntity, { logger });
|
||||
|
||||
expect(Git.fromAuth).toHaveBeenCalledWith({
|
||||
logger,
|
||||
@@ -116,7 +116,7 @@ describe('BitbucketPreparer', () => {
|
||||
it('calls the clone command with the correct arguments for a repository when no path is provided', async () => {
|
||||
const preparer = new BitbucketPreparer(new ConfigReader({}), { logger });
|
||||
delete mockEntity.spec.path;
|
||||
await preparer.prepare(mockEntity);
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
expect(mockGitClient.clone).toHaveBeenCalledWith({
|
||||
url: 'https://bitbucket.org/backstage-project/backstage-repo',
|
||||
dir: expect.any(String),
|
||||
@@ -126,7 +126,9 @@ describe('BitbucketPreparer', () => {
|
||||
it('return the temp directory with the path to the folder if it is specified', async () => {
|
||||
const preparer = new BitbucketPreparer(new ConfigReader({}), { logger });
|
||||
mockEntity.spec.path = './template/test/1/2/3';
|
||||
const response = await preparer.prepare(mockEntity);
|
||||
const response = await preparer.prepare(mockEntity, {
|
||||
logger: getVoidLogger(),
|
||||
});
|
||||
|
||||
expect(response.split('\\').join('/')).toMatch(
|
||||
/\/template\/test\/1\/2\/3$/,
|
||||
@@ -148,7 +150,7 @@ describe('BitbucketPreparer', () => {
|
||||
{ logger },
|
||||
);
|
||||
|
||||
await preparer.prepare(mockEntity);
|
||||
await preparer.prepare(mockEntity, { logger });
|
||||
|
||||
expect(Git.fromAuth).toHaveBeenCalledWith({
|
||||
logger,
|
||||
@@ -173,7 +175,7 @@ describe('BitbucketPreparer', () => {
|
||||
{ logger },
|
||||
);
|
||||
|
||||
await preparer.prepare(mockEntity);
|
||||
await preparer.prepare(mockEntity, { logger });
|
||||
|
||||
expect(Git.fromAuth).toHaveBeenCalledWith({
|
||||
logger,
|
||||
@@ -198,7 +200,7 @@ describe('BitbucketPreparer', () => {
|
||||
{ logger },
|
||||
);
|
||||
|
||||
await preparer.prepare(mockEntity);
|
||||
await preparer.prepare(mockEntity, { logger });
|
||||
|
||||
expect(Git.fromAuth).toHaveBeenCalledWith({
|
||||
logger,
|
||||
@@ -212,6 +214,7 @@ describe('BitbucketPreparer', () => {
|
||||
mockEntity.spec.path = './template/test/1/2/3';
|
||||
const response = await preparer.prepare(mockEntity, {
|
||||
workingDirectory: '/workDir',
|
||||
logger: getVoidLogger(),
|
||||
});
|
||||
|
||||
expect(response.split('\\').join('/')).toMatch(
|
||||
|
||||
@@ -90,7 +90,7 @@ describe('GitHubPreparer', () => {
|
||||
{ logger },
|
||||
);
|
||||
|
||||
await preparer.prepare(mockEntity);
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
|
||||
expect(mockGitClient.clone).toHaveBeenCalledWith({
|
||||
url: 'https://github.com/benjdlambert/backstage-graphql-template',
|
||||
@@ -101,7 +101,7 @@ describe('GitHubPreparer', () => {
|
||||
const preparer = new GithubPreparer(new ConfigReader({}), { logger });
|
||||
delete mockEntity.spec.path;
|
||||
|
||||
await preparer.prepare(mockEntity);
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
|
||||
expect(mockGitClient.clone).toHaveBeenCalledWith({
|
||||
url: 'https://github.com/benjdlambert/backstage-graphql-template',
|
||||
@@ -112,7 +112,9 @@ describe('GitHubPreparer', () => {
|
||||
it('return the temp directory with the path to the folder if it is specified', async () => {
|
||||
const preparer = new GithubPreparer(new ConfigReader({}), { logger });
|
||||
mockEntity.spec.path = './template/test/1/2/3';
|
||||
const response = await preparer.prepare(mockEntity);
|
||||
const response = await preparer.prepare(mockEntity, {
|
||||
logger: getVoidLogger(),
|
||||
});
|
||||
expect(response.split('\\').join('/')).toMatch(
|
||||
/\/template\/test\/1\/2\/3$/,
|
||||
);
|
||||
@@ -123,6 +125,7 @@ describe('GitHubPreparer', () => {
|
||||
mockEntity.spec.path = './template/test/1/2/3';
|
||||
const response = await preparer.prepare(mockEntity, {
|
||||
workingDirectory: '/workDir',
|
||||
logger: getVoidLogger(),
|
||||
});
|
||||
|
||||
expect(response.split('\\').join('/')).toMatch(
|
||||
@@ -142,7 +145,7 @@ describe('GitHubPreparer', () => {
|
||||
{ logger },
|
||||
);
|
||||
|
||||
await preparer.prepare(mockEntity);
|
||||
await preparer.prepare(mockEntity, { logger });
|
||||
|
||||
expect(Git.fromAuth).toHaveBeenCalledWith({
|
||||
logger,
|
||||
@@ -161,7 +164,7 @@ describe('GitHubPreparer', () => {
|
||||
{ logger },
|
||||
);
|
||||
|
||||
await preparer.prepare(mockEntity);
|
||||
await preparer.prepare(mockEntity, { logger });
|
||||
|
||||
expect(Git.fromAuth).toHaveBeenCalledWith({
|
||||
logger,
|
||||
|
||||
@@ -78,12 +78,12 @@ describe('GitLabPreparer', () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
['gitlab', 'gitlab/api'].forEach(protocol => {
|
||||
['gitlab'].forEach(protocol => {
|
||||
it(`calls the clone command with the correct arguments for a repository using the ${protocol} protocol`, async () => {
|
||||
const preparer = new GitlabPreparer(new ConfigReader({}), { logger });
|
||||
mockEntity = mockEntityWithProtocol(protocol);
|
||||
|
||||
await preparer.prepare(mockEntity);
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
|
||||
expect(mockGitClient.clone).toHaveBeenCalledWith({
|
||||
url: 'https://gitlab.com/benjdlambert/backstage-graphql-template',
|
||||
@@ -107,7 +107,7 @@ describe('GitLabPreparer', () => {
|
||||
);
|
||||
mockEntity = mockEntityWithProtocol(protocol);
|
||||
|
||||
await preparer.prepare(mockEntity);
|
||||
await preparer.prepare(mockEntity, { logger });
|
||||
|
||||
expect(Git.fromAuth).toHaveBeenCalledWith({
|
||||
logger,
|
||||
@@ -127,7 +127,7 @@ describe('GitLabPreparer', () => {
|
||||
);
|
||||
mockEntity = mockEntityWithProtocol(protocol);
|
||||
|
||||
await preparer.prepare(mockEntity);
|
||||
await preparer.prepare(mockEntity, { logger });
|
||||
|
||||
expect(Git.fromAuth).toHaveBeenCalledWith({
|
||||
logger,
|
||||
@@ -141,7 +141,7 @@ describe('GitLabPreparer', () => {
|
||||
mockEntity = mockEntityWithProtocol(protocol);
|
||||
delete mockEntity.spec.path;
|
||||
|
||||
await preparer.prepare(mockEntity);
|
||||
await preparer.prepare(mockEntity, { logger: getVoidLogger() });
|
||||
|
||||
expect(mockGitClient.clone).toHaveBeenCalledWith({
|
||||
url: 'https://gitlab.com/benjdlambert/backstage-graphql-template',
|
||||
@@ -153,7 +153,9 @@ describe('GitLabPreparer', () => {
|
||||
const preparer = new GitlabPreparer(new ConfigReader({}), { logger });
|
||||
mockEntity = mockEntityWithProtocol(protocol);
|
||||
mockEntity.spec.path = './template/test/1/2/3';
|
||||
const response = await preparer.prepare(mockEntity);
|
||||
const response = await preparer.prepare(mockEntity, {
|
||||
logger: getVoidLogger(),
|
||||
});
|
||||
expect(response.split('\\').join('/')).toMatch(
|
||||
/\/template\/test\/1\/2\/3$/,
|
||||
);
|
||||
@@ -164,6 +166,7 @@ describe('GitLabPreparer', () => {
|
||||
mockEntity.spec.path = './template/test/1/2/3';
|
||||
const response = await preparer.prepare(mockEntity, {
|
||||
workingDirectory: '/workDir',
|
||||
logger: getVoidLogger(),
|
||||
});
|
||||
|
||||
expect(response.split('\\').join('/')).toMatch(
|
||||
|
||||
@@ -85,8 +85,7 @@ export class Preparers implements PreparerBuilder {
|
||||
|
||||
preparers.register('file', filePreparer);
|
||||
preparers.register('gitlab', gitlabPreparer);
|
||||
preparers.register('gitlab/api', gitlabPreparer);
|
||||
preparers.register('azure/api', azurePreparer);
|
||||
preparers.register('azure', azurePreparer);
|
||||
preparers.register('github', githubPreparer);
|
||||
preparers.register('bitbucket', bitbucketPreparer);
|
||||
|
||||
|
||||
@@ -15,28 +15,45 @@
|
||||
*/
|
||||
jest.mock('./helpers');
|
||||
|
||||
jest.mock('azure-devops-node-api', () => ({
|
||||
WebApi: jest.fn(),
|
||||
getPersonalAccessTokenHandler: jest.fn(),
|
||||
}));
|
||||
|
||||
import { AzurePublisher } from './azure';
|
||||
import { GitApi } from 'azure-devops-node-api/GitApi';
|
||||
import { WebApi } from 'azure-devops-node-api';
|
||||
import * as helpers from './helpers';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
|
||||
const { mockGitApi } = require('azure-devops-node-api/GitApi') as {
|
||||
mockGitApi: {
|
||||
createRepository: jest.MockedFunction<GitApi['createRepository']>;
|
||||
};
|
||||
};
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
|
||||
describe('Azure Publisher', () => {
|
||||
const publisher = new AzurePublisher(new GitApi('', []), 'fake-token');
|
||||
const logger = getVoidLogger();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('publish: createRemoteInAzure', () => {
|
||||
it('should use azure-devops-node-api to create a repo in the given project', async () => {
|
||||
mockGitApi.createRepository.mockResolvedValue({
|
||||
const mockGitClient = {
|
||||
createRepository: jest.fn(),
|
||||
};
|
||||
const mockGitApi = {
|
||||
getGitApi: jest.fn().mockReturnValue(mockGitClient),
|
||||
};
|
||||
|
||||
((WebApi as unknown) as jest.Mock).mockImplementation(() => mockGitApi);
|
||||
|
||||
const publisher = new AzurePublisher(
|
||||
new ConfigReader({
|
||||
scaffolder: {
|
||||
azure: {
|
||||
api: {
|
||||
baseUrl: 'https://dev.azure.com/myorg',
|
||||
token: 'fake-azure-token',
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ logger },
|
||||
);
|
||||
mockGitClient.createRepository.mockResolvedValue({
|
||||
remoteUrl: 'https://dev.azure.com/organization/project/_git/repo',
|
||||
} as { remoteUrl: string });
|
||||
|
||||
@@ -54,7 +71,7 @@ describe('Azure Publisher', () => {
|
||||
catalogInfoUrl:
|
||||
'https://dev.azure.com/organization/project/_git/repo?path=%2Fcatalog-info.yaml',
|
||||
});
|
||||
expect(mockGitApi.createRepository).toHaveBeenCalledWith(
|
||||
expect(mockGitClient.createRepository).toHaveBeenCalledWith(
|
||||
{
|
||||
name: 'repo',
|
||||
},
|
||||
@@ -63,7 +80,62 @@ describe('Azure Publisher', () => {
|
||||
expect(helpers.initRepoAndPush).toHaveBeenCalledWith({
|
||||
dir: '/tmp/test',
|
||||
remoteUrl: 'https://dev.azure.com/organization/project/_git/repo',
|
||||
auth: { username: 'notempty', password: 'fake-token' },
|
||||
auth: { username: 'notempty', password: 'fake-azure-token' },
|
||||
logger,
|
||||
});
|
||||
});
|
||||
|
||||
it('should use azure-devops-node-api with integrations config', async () => {
|
||||
const mockGitClient = {
|
||||
createRepository: jest.fn(),
|
||||
};
|
||||
const mockGitApi = {
|
||||
getGitApi: jest.fn().mockReturnValue(mockGitClient),
|
||||
};
|
||||
|
||||
((WebApi as unknown) as jest.Mock).mockImplementation(() => mockGitApi);
|
||||
|
||||
const publisher = new AzurePublisher(
|
||||
new ConfigReader({
|
||||
integrations: {
|
||||
azure: [
|
||||
{
|
||||
host: 'dev.azure.com',
|
||||
token: 'fake-azure-token',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
{ logger },
|
||||
);
|
||||
mockGitClient.createRepository.mockResolvedValue({
|
||||
remoteUrl: 'https://dev.azure.com/organization/project/_git/repo',
|
||||
} as { remoteUrl: string });
|
||||
|
||||
const result = await publisher.publish({
|
||||
values: {
|
||||
storePath: 'https://dev.azure.com/organization/project/_git/repo',
|
||||
owner: 'bob',
|
||||
},
|
||||
directory: '/tmp/test',
|
||||
logger,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
remoteUrl: 'https://dev.azure.com/organization/project/_git/repo',
|
||||
catalogInfoUrl:
|
||||
'https://dev.azure.com/organization/project/_git/repo?path=%2Fcatalog-info.yaml',
|
||||
});
|
||||
expect(mockGitClient.createRepository).toHaveBeenCalledWith(
|
||||
{
|
||||
name: 'repo',
|
||||
},
|
||||
'project',
|
||||
);
|
||||
expect(helpers.initRepoAndPush).toHaveBeenCalledWith({
|
||||
dir: '/tmp/test',
|
||||
remoteUrl: 'https://dev.azure.com/organization/project/_git/repo',
|
||||
auth: { username: 'notempty', password: 'fake-azure-token' },
|
||||
logger,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -83,6 +83,7 @@ export class AzurePublisher implements PublisherBase {
|
||||
project: owner,
|
||||
name,
|
||||
});
|
||||
|
||||
const catalogInfoUrl = `${remoteUrl}?path=%2Fcatalog-info.yaml`;
|
||||
|
||||
await initRepoAndPush({
|
||||
|
||||
@@ -22,6 +22,7 @@ import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { msw } from '@backstage/test-utils';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
|
||||
describe('Bitbucket Publisher', () => {
|
||||
const logger = getVoidLogger();
|
||||
@@ -59,14 +60,25 @@ describe('Bitbucket Publisher', () => {
|
||||
);
|
||||
|
||||
const publisher = new BitbucketPublisher(
|
||||
'https://bitbucket.org',
|
||||
'fake-user',
|
||||
'fake-token',
|
||||
new ConfigReader({
|
||||
integrations: {
|
||||
bitbucket: [
|
||||
{
|
||||
host: 'bitbucket.org',
|
||||
username: 'fake-user',
|
||||
appPassword: 'fake-token',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
{
|
||||
logger: getVoidLogger(),
|
||||
},
|
||||
);
|
||||
|
||||
const result = await publisher.publish({
|
||||
values: {
|
||||
storePath: 'project/repo',
|
||||
storePath: 'https://bitbucket.org/project/repo',
|
||||
owner: 'bob',
|
||||
},
|
||||
directory: '/tmp/test',
|
||||
@@ -87,6 +99,7 @@ describe('Bitbucket Publisher', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('publish: createRemoteInBitbucketServer', () => {
|
||||
it('should create repo in bitbucket server', async () => {
|
||||
server.use(
|
||||
@@ -117,14 +130,24 @@ describe('Bitbucket Publisher', () => {
|
||||
);
|
||||
|
||||
const publisher = new BitbucketPublisher(
|
||||
'https://bitbucket.mycompany.com',
|
||||
'fake-user',
|
||||
'fake-token',
|
||||
new ConfigReader({
|
||||
integrations: {
|
||||
bitbucket: [
|
||||
{
|
||||
host: 'bitbucket.mycompany.com',
|
||||
token: 'fake-token',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
{
|
||||
logger: getVoidLogger(),
|
||||
},
|
||||
);
|
||||
|
||||
const result = await publisher.publish({
|
||||
values: {
|
||||
storePath: 'project/repo',
|
||||
storePath: 'https://bitbucket.mycompany.com/project/repo',
|
||||
owner: 'bob',
|
||||
},
|
||||
directory: '/tmp/test',
|
||||
@@ -140,7 +163,7 @@ describe('Bitbucket Publisher', () => {
|
||||
expect(initRepoAndPush).toHaveBeenCalledWith({
|
||||
dir: '/tmp/test',
|
||||
remoteUrl: 'https://bitbucket.mycompany.com/scm/project/repo',
|
||||
auth: { username: 'fake-user', password: 'fake-token' },
|
||||
auth: { username: 'x-token-auth', password: 'fake-token' },
|
||||
logger: logger,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,10 +25,14 @@ import {
|
||||
import { Logger } from 'winston';
|
||||
import gitUrlParse from 'git-url-parse';
|
||||
|
||||
// TODO(blam): We should probably start to use a bitbucket client here that we can change
|
||||
// the baseURL to point at on-prem or public bitbucket versions like we do for
|
||||
// github and ghe
|
||||
export class BitbucketPublisher implements PublisherBase {
|
||||
private readonly host?: string;
|
||||
private readonly username?: string;
|
||||
private readonly token?: string;
|
||||
private readonly appPassword?: string;
|
||||
private readonly integrations: BitbucketIntegrationConfig[];
|
||||
|
||||
constructor(config: Config, { logger }: { logger: Logger }) {
|
||||
@@ -64,6 +68,15 @@ export class BitbucketPublisher implements PublisherBase {
|
||||
"DEPRECATION: Using the apiBaseUrl format under 'scaffolder.bitbucket.api.username' will not be respected in future releases. Please consider using integrations config instead",
|
||||
);
|
||||
}
|
||||
|
||||
this.appPassword = config.getOptionalString(
|
||||
'scaffolder.bitbucket.api.appPassword',
|
||||
);
|
||||
if (this.appPassword) {
|
||||
logger.warn(
|
||||
"DEPRECATION: Using the appPassword format under 'scaffolder.bitbucket.api.appassword' will not be respected in future releases. Please consider using integrations config instead",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async publish({
|
||||
@@ -71,20 +84,20 @@ export class BitbucketPublisher implements PublisherBase {
|
||||
directory,
|
||||
logger,
|
||||
}: PublisherOptions): Promise<PublisherResult> {
|
||||
const { resource: host, owner: project, name } = gitUrlParse(
|
||||
const { resource: hostname, owner: project, name } = gitUrlParse(
|
||||
values.storePath,
|
||||
);
|
||||
|
||||
const token = this.getToken(host);
|
||||
if (!token) {
|
||||
throw new Error('No token provided to create the remote repository');
|
||||
const token = this.getToken(hostname);
|
||||
const appPassword = this.getAppPassword(hostname);
|
||||
const username = this.getUsername(hostname);
|
||||
|
||||
if (!username && !appPassword && !token) {
|
||||
throw new Error('Cannot create repository without bitbucket credentials');
|
||||
}
|
||||
const username = this.getUsername(host);
|
||||
if (!username) {
|
||||
throw new Error('No username provided to create the remote repository');
|
||||
}
|
||||
const apiUrl = this.getHost(host);
|
||||
if (!apiUrl) {
|
||||
const host = this.getHost(hostname);
|
||||
|
||||
if (!host) {
|
||||
throw new Error('No host provided to create the remote repository');
|
||||
}
|
||||
|
||||
@@ -94,14 +107,17 @@ export class BitbucketPublisher implements PublisherBase {
|
||||
name,
|
||||
description,
|
||||
host,
|
||||
username,
|
||||
token,
|
||||
appPassword,
|
||||
});
|
||||
|
||||
await initRepoAndPush({
|
||||
dir: directory,
|
||||
remoteUrl: result.remoteUrl,
|
||||
auth: {
|
||||
username: username,
|
||||
password: token,
|
||||
username: username ? username : 'x-token-auth',
|
||||
password: appPassword ? appPassword : token ?? '',
|
||||
},
|
||||
logger,
|
||||
});
|
||||
@@ -109,12 +125,15 @@ export class BitbucketPublisher implements PublisherBase {
|
||||
}
|
||||
|
||||
private async createRemote(opts: {
|
||||
username?: string;
|
||||
token?: string;
|
||||
appPassword?: string;
|
||||
project: string;
|
||||
name: string;
|
||||
description: string;
|
||||
host: string;
|
||||
}): Promise<PublisherResult> {
|
||||
if (opts.host === 'https://bitbucket.org') {
|
||||
if (opts.host === 'bitbucket.org') {
|
||||
return this.createBitbucketCloudRepository(opts);
|
||||
}
|
||||
return this.createBitbucketServerRepository(opts);
|
||||
@@ -124,11 +143,22 @@ export class BitbucketPublisher implements PublisherBase {
|
||||
project: string;
|
||||
name: string;
|
||||
description: string;
|
||||
username?: string;
|
||||
appPassword?: string;
|
||||
}): Promise<PublisherResult> {
|
||||
const { project, name, description } = opts;
|
||||
const { project, name, description, username, appPassword } = opts;
|
||||
if (!appPassword) {
|
||||
throw new Error(
|
||||
'appPassword is required to create the remote repository',
|
||||
);
|
||||
}
|
||||
|
||||
if (!username) {
|
||||
throw new Error('username is required to create the remote repository');
|
||||
}
|
||||
|
||||
let response: Response;
|
||||
const buffer = Buffer.from(`${this.username}:${this.token}`, 'utf8');
|
||||
const buffer = Buffer.from(`${username}:${appPassword}`, 'utf8');
|
||||
|
||||
const options: RequestInit = {
|
||||
method: 'POST',
|
||||
@@ -169,8 +199,13 @@ export class BitbucketPublisher implements PublisherBase {
|
||||
project: string;
|
||||
name: string;
|
||||
description: string;
|
||||
token?: string;
|
||||
host: string;
|
||||
}): Promise<PublisherResult> {
|
||||
const { project, name, description } = opts;
|
||||
const { project, name, description, token, host } = opts;
|
||||
if (!token) {
|
||||
throw new Error('No token provided to create the remote repository');
|
||||
}
|
||||
|
||||
let response: Response;
|
||||
const options: RequestInit = {
|
||||
@@ -180,13 +215,13 @@ export class BitbucketPublisher implements PublisherBase {
|
||||
description: description,
|
||||
}),
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.token}`,
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
try {
|
||||
response = await fetch(
|
||||
`${this.host}/rest/api/1.0/projects/${project}/repos`,
|
||||
`https://${host}/rest/api/1.0/projects/${project}/repos`,
|
||||
options,
|
||||
);
|
||||
} catch (e) {
|
||||
@@ -216,6 +251,13 @@ export class BitbucketPublisher implements PublisherBase {
|
||||
);
|
||||
}
|
||||
|
||||
private getAppPassword(host: string): string | undefined {
|
||||
return (
|
||||
this.appPassword ||
|
||||
this.integrations.find(c => c.host === host)?.appPassword
|
||||
);
|
||||
}
|
||||
|
||||
private getHost(host: string): string | undefined {
|
||||
return this.host || this.integrations.find(c => c.host === host)?.host;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
import { GithubPublisher } from './github';
|
||||
import { initRepoAndPush } from './helpers';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
|
||||
const { mockGithubClient } = require('@octokit/rest') as {
|
||||
mockGithubClient: {
|
||||
@@ -42,11 +43,21 @@ describe('GitHub Publisher', () => {
|
||||
});
|
||||
|
||||
describe('with public repo visibility', () => {
|
||||
const publisher = new GithubPublisher({
|
||||
client: new Octokit(),
|
||||
token: 'abc',
|
||||
repoVisibility: 'public',
|
||||
});
|
||||
const publisher = new GithubPublisher(
|
||||
new ConfigReader({
|
||||
integrations: {
|
||||
github: [
|
||||
{
|
||||
token: 'fake-token',
|
||||
host: 'github.com',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
{
|
||||
logger,
|
||||
},
|
||||
);
|
||||
|
||||
describe('publish: createRemoteInGithub', () => {
|
||||
it('should use octokit to create a repo in an organisation if the organisation property is set', async () => {
|
||||
@@ -63,7 +74,7 @@ describe('GitHub Publisher', () => {
|
||||
|
||||
const result = await publisher.publish({
|
||||
values: {
|
||||
storePath: 'blam/test',
|
||||
storePath: 'https://github.com/blam/test',
|
||||
owner: 'bob',
|
||||
access: 'blam/team',
|
||||
},
|
||||
@@ -94,7 +105,7 @@ describe('GitHub Publisher', () => {
|
||||
expect(initRepoAndPush).toHaveBeenCalledWith({
|
||||
dir: '/tmp/test',
|
||||
remoteUrl: 'https://github.com/backstage/backstage.git',
|
||||
auth: { username: 'abc', password: 'x-oauth-basic' },
|
||||
auth: { username: 'fake-token', password: 'x-oauth-basic' },
|
||||
logger,
|
||||
});
|
||||
});
|
||||
@@ -113,7 +124,7 @@ describe('GitHub Publisher', () => {
|
||||
|
||||
const result = await publisher.publish({
|
||||
values: {
|
||||
storePath: 'blam/test',
|
||||
storePath: 'https://github.com/blam/test',
|
||||
owner: 'bob',
|
||||
access: 'blam',
|
||||
},
|
||||
@@ -137,7 +148,7 @@ describe('GitHub Publisher', () => {
|
||||
expect(initRepoAndPush).toHaveBeenCalledWith({
|
||||
dir: '/tmp/test',
|
||||
remoteUrl: 'https://github.com/backstage/backstage.git',
|
||||
auth: { username: 'abc', password: 'x-oauth-basic' },
|
||||
auth: { username: 'fake-token', password: 'x-oauth-basic' },
|
||||
logger,
|
||||
});
|
||||
});
|
||||
@@ -157,7 +168,7 @@ describe('GitHub Publisher', () => {
|
||||
|
||||
const result = await publisher.publish({
|
||||
values: {
|
||||
storePath: 'blam/test',
|
||||
storePath: 'https://github.com/blam/test',
|
||||
owner: 'bob',
|
||||
access: 'bob',
|
||||
description: 'description',
|
||||
@@ -187,18 +198,26 @@ describe('GitHub Publisher', () => {
|
||||
expect(initRepoAndPush).toHaveBeenCalledWith({
|
||||
dir: '/tmp/test',
|
||||
remoteUrl: 'https://github.com/backstage/backstage.git',
|
||||
auth: { username: 'abc', password: 'x-oauth-basic' },
|
||||
auth: { username: 'fake-token', password: 'x-oauth-basic' },
|
||||
logger,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with internal repo visibility', () => {
|
||||
const publisher = new GithubPublisher({
|
||||
client: new Octokit(),
|
||||
token: 'abc',
|
||||
repoVisibility: 'internal',
|
||||
});
|
||||
const publisher = new GithubPublisher(
|
||||
new ConfigReader({
|
||||
integrations: {
|
||||
github: [{ host: 'github.com', token: 'fake-token' }],
|
||||
},
|
||||
scaffolder: {
|
||||
github: {
|
||||
visibility: 'internal',
|
||||
},
|
||||
},
|
||||
}),
|
||||
{ logger },
|
||||
);
|
||||
|
||||
it('creates a private repository in the organization with visibility set to internal', async () => {
|
||||
mockGithubClient.repos.createInOrg.mockResolvedValue({
|
||||
@@ -215,7 +234,7 @@ describe('GitHub Publisher', () => {
|
||||
const result = await publisher.publish({
|
||||
values: {
|
||||
isOrg: true,
|
||||
storePath: 'blam/test',
|
||||
storePath: 'https://github.com/blam/test',
|
||||
owner: 'bob',
|
||||
},
|
||||
directory: '/tmp/test',
|
||||
@@ -236,18 +255,33 @@ describe('GitHub Publisher', () => {
|
||||
expect(initRepoAndPush).toHaveBeenCalledWith({
|
||||
dir: '/tmp/test',
|
||||
remoteUrl: 'https://github.com/backstage/backstage.git',
|
||||
auth: { username: 'abc', password: 'x-oauth-basic' },
|
||||
auth: { username: 'fake-token', password: 'x-oauth-basic' },
|
||||
logger,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('private visibility in a user account', () => {
|
||||
const publisher = new GithubPublisher({
|
||||
client: new Octokit(),
|
||||
token: 'abc',
|
||||
repoVisibility: 'private',
|
||||
});
|
||||
const publisher = new GithubPublisher(
|
||||
new ConfigReader({
|
||||
integrations: {
|
||||
github: [
|
||||
{
|
||||
token: 'fake-token',
|
||||
host: 'github.com',
|
||||
},
|
||||
],
|
||||
},
|
||||
scaffolder: {
|
||||
github: {
|
||||
visibility: 'private',
|
||||
},
|
||||
},
|
||||
}),
|
||||
{
|
||||
logger,
|
||||
},
|
||||
);
|
||||
|
||||
it('creates a private repository', async () => {
|
||||
mockGithubClient.repos.createForAuthenticatedUser.mockResolvedValue({
|
||||
@@ -263,7 +297,7 @@ describe('GitHub Publisher', () => {
|
||||
|
||||
const result = await publisher.publish({
|
||||
values: {
|
||||
storePath: 'blam/test',
|
||||
storePath: 'https://github.com/blam/test',
|
||||
owner: 'bob',
|
||||
},
|
||||
directory: '/tmp/test',
|
||||
@@ -284,7 +318,7 @@ describe('GitHub Publisher', () => {
|
||||
expect(initRepoAndPush).toHaveBeenCalledWith({
|
||||
dir: '/tmp/test',
|
||||
remoteUrl: 'https://github.com/backstage/backstage.git',
|
||||
auth: { username: 'abc', password: 'x-oauth-basic' },
|
||||
auth: { username: 'fake-token', password: 'x-oauth-basic' },
|
||||
logger,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,33 +14,56 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
jest.mock('@gitbeaker/node');
|
||||
jest.mock('@gitbeaker/node', () => ({
|
||||
Gitlab: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('./helpers');
|
||||
|
||||
import { GitlabPublisher } from './gitlab';
|
||||
import { Gitlab as GitlabAPI } from '@gitbeaker/core';
|
||||
import { Gitlab } from '@gitbeaker/node';
|
||||
import { initRepoAndPush } from './helpers';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
|
||||
const { mockGitlabClient } = require('@gitbeaker/node') as {
|
||||
mockGitlabClient: {
|
||||
Namespaces: jest.Mocked<GitlabAPI['Namespaces']>;
|
||||
Projects: jest.Mocked<GitlabAPI['Projects']>;
|
||||
Users: jest.Mocked<GitlabAPI['Users']>;
|
||||
};
|
||||
};
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
|
||||
describe('GitLab Publisher', () => {
|
||||
const logger = getVoidLogger();
|
||||
const publisher = new GitlabPublisher(new Gitlab({}), 'fake-token');
|
||||
const mockGitlabClient = {
|
||||
Namespaces: {
|
||||
show: jest.fn(),
|
||||
},
|
||||
Projects: {
|
||||
create: jest.fn(),
|
||||
},
|
||||
Users: {
|
||||
current: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
((Gitlab as unknown) as jest.Mock).mockImplementation(
|
||||
() => mockGitlabClient,
|
||||
);
|
||||
});
|
||||
|
||||
describe('publish: createRemoteInGitLab', () => {
|
||||
it('should use gitbeaker to create a repo in a namespace if the namespace property is set', async () => {
|
||||
const publisher = new GitlabPublisher(
|
||||
new ConfigReader({
|
||||
integrations: {
|
||||
gitlab: [
|
||||
{
|
||||
host: 'gitlab.com',
|
||||
token: 'fake-token',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
{ logger },
|
||||
);
|
||||
|
||||
mockGitlabClient.Namespaces.show.mockResolvedValue({
|
||||
id: 42,
|
||||
} as { id: number });
|
||||
@@ -51,14 +74,17 @@ describe('GitLab Publisher', () => {
|
||||
const result = await publisher.publish({
|
||||
values: {
|
||||
isOrg: true,
|
||||
storePath: 'bloum/blam/test',
|
||||
storePath: 'https://gitlab.com/blam/test',
|
||||
owner: 'bob',
|
||||
},
|
||||
directory: '/tmp/test',
|
||||
logger,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ remoteUrl: 'mockclone' });
|
||||
expect(result).toEqual({
|
||||
remoteUrl: 'mockclone',
|
||||
catalogInfoUrl: 'mockclone',
|
||||
});
|
||||
expect(mockGitlabClient.Projects.create).toHaveBeenCalledWith({
|
||||
namespace_id: 42,
|
||||
name: 'test',
|
||||
@@ -72,6 +98,20 @@ describe('GitLab Publisher', () => {
|
||||
});
|
||||
|
||||
it('should use gitbeaker to create a repo in the authed user if the namespace property is not set', async () => {
|
||||
const publisher = new GitlabPublisher(
|
||||
new ConfigReader({
|
||||
integrations: {
|
||||
gitlab: [
|
||||
{
|
||||
host: 'gitlab.com',
|
||||
token: 'fake-token',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
{ logger },
|
||||
);
|
||||
|
||||
mockGitlabClient.Namespaces.show.mockResolvedValue({});
|
||||
mockGitlabClient.Users.current.mockResolvedValue({
|
||||
id: 21,
|
||||
@@ -82,14 +122,17 @@ describe('GitLab Publisher', () => {
|
||||
|
||||
const result = await publisher.publish({
|
||||
values: {
|
||||
storePath: 'bloum/blam/test',
|
||||
storePath: 'https://gitlab.com/blam/test',
|
||||
owner: 'bob',
|
||||
},
|
||||
directory: '/tmp/test',
|
||||
logger,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ remoteUrl: 'mockclone' });
|
||||
expect(result).toEqual({
|
||||
remoteUrl: 'mockclone',
|
||||
catalogInfoUrl: 'mockclone',
|
||||
});
|
||||
expect(mockGitlabClient.Users.current).toHaveBeenCalled();
|
||||
expect(mockGitlabClient.Projects.create).toHaveBeenCalledWith({
|
||||
namespace_id: 21,
|
||||
|
||||
@@ -14,120 +14,97 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Publishers } from './publishers';
|
||||
import {
|
||||
LOCATION_ANNOTATION,
|
||||
TemplateEntityV1alpha1,
|
||||
} from '@backstage/catalog-model';
|
||||
import { GithubPublisher } from './github';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import { getVoidLogger } from '@backstage/backend-common';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { AzurePublisher } from './azure';
|
||||
import { GitlabPublisher } from './gitlab';
|
||||
import { BitbucketPublisher } from './bitbucket';
|
||||
|
||||
jest.mock('@octokit/rest');
|
||||
|
||||
describe('Publishers', () => {
|
||||
const mockTemplate: TemplateEntityV1alpha1 = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Template',
|
||||
metadata: {
|
||||
annotations: {
|
||||
[LOCATION_ANNOTATION]:
|
||||
'github:https://github.com/benjdlambert/backstage-graphql-template/blob/master/template.yaml',
|
||||
},
|
||||
name: 'graphql-starter',
|
||||
title: 'GraphQL Service',
|
||||
description:
|
||||
'A GraphQL starter template for backstage to get you up and running\nthe best pracices with GraphQL\n',
|
||||
uid: '9cf16bad-16e0-4213-b314-c4eec773c50b',
|
||||
etag: 'ZTkxMjUxMjUtYWY3Yi00MjU2LWFkYWMtZTZjNjU5ZjJhOWM2',
|
||||
generation: 1,
|
||||
},
|
||||
spec: {
|
||||
type: 'website',
|
||||
templater: 'cookiecutter',
|
||||
path: './template',
|
||||
schema: {
|
||||
$schema: 'http://json-schema.org/draft-07/schema#',
|
||||
required: ['storePath', 'owner'],
|
||||
properties: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
title: 'Owner',
|
||||
description: 'Who is going to own this component',
|
||||
},
|
||||
storePath: {
|
||||
type: 'string',
|
||||
title: 'Store path',
|
||||
description: 'GitHub store path in org/repo format',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
it('should throw an error when the publisher for the source location is not registered', () => {
|
||||
const publishers = new Publishers();
|
||||
|
||||
expect(() => publishers.get(mockTemplate)).toThrow(
|
||||
expect(() =>
|
||||
publishers.get('https://github.com/org/repo', {
|
||||
logger: getVoidLogger(),
|
||||
}),
|
||||
).toThrow(
|
||||
expect.objectContaining({
|
||||
message: 'No publisher registered for type: "github"',
|
||||
message:
|
||||
'No matching publisher detected for "https://github.com/org/repo". Please make sure this host is registered in the integration config',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should return the correct preparer when the source matches', () => {
|
||||
const publishers = new Publishers();
|
||||
const publisher = new GithubPublisher({
|
||||
client: new Octokit(),
|
||||
token: 'fake',
|
||||
repoVisibility: 'public',
|
||||
it('should return the correct preparer when the source matches for github', async () => {
|
||||
const publishers = await Publishers.fromConfig(new ConfigReader({}), {
|
||||
logger: getVoidLogger(),
|
||||
});
|
||||
publishers.register('github', publisher);
|
||||
|
||||
expect(publishers.get(mockTemplate)).toBe(publisher);
|
||||
expect(
|
||||
publishers.get('https://github.com/org/repo', {
|
||||
logger: getVoidLogger(),
|
||||
}),
|
||||
).toBeInstanceOf(GithubPublisher);
|
||||
});
|
||||
|
||||
it('should throw an error if the metadata tag does not exist in the entity', () => {
|
||||
const brokenTemplate: TemplateEntityV1alpha1 = {
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Template',
|
||||
metadata: {
|
||||
annotations: {},
|
||||
name: 'react-ssr-template',
|
||||
title: 'React SSR Template',
|
||||
description:
|
||||
'Next.js application skeleton for creating isomorphic web applications.',
|
||||
uid: '7357f4c5-aa58-4a1e-9670-18931eef771f',
|
||||
etag: 'YWUxZWQyY2EtZDkxMC00MDM0LWI0ODAtMDgwMWY0YzdlMWIw',
|
||||
generation: 1,
|
||||
},
|
||||
spec: {
|
||||
type: 'website',
|
||||
templater: 'cookiecutter',
|
||||
path: '.',
|
||||
schema: {
|
||||
$schema: 'http://json-schema.org/draft-07/schema#',
|
||||
required: ['storePath', 'owner'],
|
||||
properties: {
|
||||
owner: {
|
||||
type: 'string',
|
||||
title: 'Owner',
|
||||
description: 'Who is going to own this component',
|
||||
},
|
||||
storePath: {
|
||||
type: 'string',
|
||||
title: 'Store path',
|
||||
description: 'GitHub store path in org/repo format',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
it('should return the correct preparer when the source matches for azure', async () => {
|
||||
const publishers = await Publishers.fromConfig(new ConfigReader({}), {
|
||||
logger: getVoidLogger(),
|
||||
});
|
||||
|
||||
const publishers = new Publishers();
|
||||
|
||||
expect(() => publishers.get(brokenTemplate)).toThrow(
|
||||
expect.objectContaining({
|
||||
message: expect.stringContaining('No location annotation provided'),
|
||||
expect(
|
||||
publishers.get('https://dev.azure.com/org/project/_git/repo', {
|
||||
logger: getVoidLogger(),
|
||||
}),
|
||||
).toBeInstanceOf(AzurePublisher);
|
||||
});
|
||||
|
||||
it('should return the correct preparer when the source matches for bitbucket', async () => {
|
||||
const publishers = await Publishers.fromConfig(new ConfigReader({}), {
|
||||
logger: getVoidLogger(),
|
||||
});
|
||||
|
||||
expect(
|
||||
publishers.get('https://bitbucket.org/owner/repo', {
|
||||
logger: getVoidLogger(),
|
||||
}),
|
||||
).toBeInstanceOf(BitbucketPublisher);
|
||||
});
|
||||
|
||||
it('should return the correct preparer when the source matches for gitlab', async () => {
|
||||
const publishers = await Publishers.fromConfig(new ConfigReader({}), {
|
||||
logger: getVoidLogger(),
|
||||
});
|
||||
|
||||
expect(
|
||||
publishers.get('https://gitlab.com/owner/repo', {
|
||||
logger: getVoidLogger(),
|
||||
}),
|
||||
).toBeInstanceOf(GitlabPublisher);
|
||||
});
|
||||
|
||||
it('should respect registrations for custom URLs for providers using the integrations config', async () => {
|
||||
const publishers = await Publishers.fromConfig(
|
||||
new ConfigReader({
|
||||
integrations: {
|
||||
github: [
|
||||
{ host: 'my.special.github.enterprise.thing', token: 'lolghe' },
|
||||
],
|
||||
},
|
||||
}),
|
||||
{
|
||||
logger: getVoidLogger(),
|
||||
},
|
||||
);
|
||||
|
||||
expect(
|
||||
publishers.get('https://my.special.github.enterprise.thing/org/repo', {
|
||||
logger: getVoidLogger(),
|
||||
}),
|
||||
).toBeInstanceOf(GithubPublisher);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -52,7 +52,7 @@ export class Publishers implements PublisherBuilder {
|
||||
const publisher = this.publisherMap.get(protocol as RemoteProtocol);
|
||||
if (!publisher) {
|
||||
throw new Error(
|
||||
`Failed to detect publisher type. Unable to determine integration type for location "${location}". ` +
|
||||
`Failed to detect publisher type. Unable to determine integration type for location "${protocol}". ` +
|
||||
"Please add appropriate configuration to the 'integrations' configuration section",
|
||||
);
|
||||
}
|
||||
@@ -69,85 +69,19 @@ export class Publishers implements PublisherBuilder {
|
||||
const typeDetector = makeDeprecatedLocationTypeDetector(config);
|
||||
const publishers = new Publishers(typeDetector);
|
||||
|
||||
const githubConfig = config.getOptionalConfig('scaffolder.github');
|
||||
if (githubConfig) {
|
||||
try {
|
||||
const githubPublisher = new GithubPublisher(config, { logger });
|
||||
publishers.register('file', githubPublisher);
|
||||
publishers.register('github', githubPublisher);
|
||||
} catch (e) {
|
||||
const providerName = 'github';
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
throw new Error(
|
||||
`Failed to initialize ${providerName} scaffolding provider, ${e.message}`,
|
||||
);
|
||||
}
|
||||
const githubPublisher = new GithubPublisher(config, { logger });
|
||||
publishers.register('file', githubPublisher);
|
||||
publishers.register('github', githubPublisher);
|
||||
|
||||
logger.warn(
|
||||
`Skipping ${providerName} scaffolding provider, ${e.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
const gitLabPublisher = new GitlabPublisher(config, { logger });
|
||||
publishers.register('gitlab', gitLabPublisher);
|
||||
|
||||
const gitLabConfig = config.getOptionalConfig('scaffolder.gitlab');
|
||||
if (gitLabConfig) {
|
||||
try {
|
||||
const gitLabPublisher = new GitlabPublisher(config, { logger });
|
||||
publishers.register('gitlab', gitLabPublisher);
|
||||
publishers.register('gitlab/api', gitLabPublisher);
|
||||
} catch (e) {
|
||||
const providerName = 'gitlab';
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
throw new Error(
|
||||
`Failed to initialize ${providerName} scaffolding provider, ${e.message}`,
|
||||
);
|
||||
}
|
||||
const azurePublisher = new AzurePublisher(config, { logger });
|
||||
publishers.register('azure', azurePublisher);
|
||||
|
||||
logger.warn(
|
||||
`Skipping ${providerName} scaffolding provider, ${e.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
const bitbucketPublisher = new BitbucketPublisher(config, { logger });
|
||||
publishers.register('bitbucket', bitbucketPublisher);
|
||||
|
||||
const azureConfig = config.getOptionalConfig('scaffolder.azure');
|
||||
if (azureConfig) {
|
||||
try {
|
||||
const azurePublisher = new AzurePublisher(config, { logger });
|
||||
publishers.register('azure/api', azurePublisher);
|
||||
} catch (e) {
|
||||
const providerName = 'azure';
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
throw new Error(
|
||||
`Failed to initialize ${providerName} scaffolding provider, ${e.message}`,
|
||||
);
|
||||
}
|
||||
|
||||
logger.warn(
|
||||
`Skipping ${providerName} scaffolding provider, ${e.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const bitbucketConfig = config.getOptionalConfig(
|
||||
'scaffolder.bitbucket.api',
|
||||
);
|
||||
if (bitbucketConfig) {
|
||||
try {
|
||||
const bitbucketPublisher = new BitbucketPublisher(config, { logger });
|
||||
publishers.register('bitbucket', bitbucketPublisher);
|
||||
} catch (e) {
|
||||
const providerName = 'bitbucket';
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
throw new Error(
|
||||
`Failed to initialize ${providerName} scaffolding provider, ${e.message}`,
|
||||
);
|
||||
}
|
||||
|
||||
logger.warn(
|
||||
`Skipping ${providerName} scaffolding provider, ${e.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
return publishers;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,5 @@ export type RemoteProtocol =
|
||||
| 'file'
|
||||
| 'github'
|
||||
| 'gitlab'
|
||||
| 'gitlab/api'
|
||||
| 'azure/api'
|
||||
| 'azure'
|
||||
| 'bitbucket';
|
||||
|
||||
Reference in New Issue
Block a user