feat: add publish:gitlab branches configuration support
Signed-off-by: Ilya Katlinski <ilya.katlinsky@gmail.com>
This commit is contained in:
@@ -251,3 +251,7 @@ export function getGitCommitMessage(
|
||||
export function entityRefToName(name: string): string {
|
||||
return name.replace(/^.*[:/]/g, '');
|
||||
}
|
||||
|
||||
export function printGitlabError(error: any): string {
|
||||
return JSON.stringify({ code: error.code, message: error.description });
|
||||
}
|
||||
|
||||
@@ -89,4 +89,31 @@ export const examples: TemplateExample[] = [
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
description: 'Initializes a git repository with branches settings',
|
||||
example: yaml.stringify({
|
||||
steps: [
|
||||
{
|
||||
id: 'publish',
|
||||
action: 'publish:gitlab',
|
||||
name: 'Publish to GitLab',
|
||||
input: {
|
||||
repoUrl: 'gitlab.com?repo=project_name&owner=group_name',
|
||||
branches: [
|
||||
{
|
||||
name: 'dev',
|
||||
create: true,
|
||||
protected: true,
|
||||
ref: 'master',
|
||||
},
|
||||
{
|
||||
name: 'master',
|
||||
protected: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -45,6 +45,12 @@ const mockGitlabClient = {
|
||||
ProjectMembers: {
|
||||
add: jest.fn(),
|
||||
},
|
||||
Branches: {
|
||||
create: jest.fn(),
|
||||
},
|
||||
ProtectedBranches: {
|
||||
protect: jest.fn(),
|
||||
},
|
||||
};
|
||||
jest.mock('@gitbeaker/node', () => ({
|
||||
Gitlab: class {
|
||||
@@ -87,7 +93,7 @@ describe('publish:gitlab', () => {
|
||||
output: jest.fn(),
|
||||
createTemporaryDirectory: jest.fn(),
|
||||
};
|
||||
const mockContextWithOptions = {
|
||||
const mockContextWithSettings = {
|
||||
input: {
|
||||
repoUrl: 'gitlab.com?repo=repo&owner=owner',
|
||||
repoVisibility: 'private' as const,
|
||||
@@ -104,6 +110,33 @@ describe('publish:gitlab', () => {
|
||||
output: jest.fn(),
|
||||
createTemporaryDirectory: jest.fn(),
|
||||
};
|
||||
const mockContextWithBranches = {
|
||||
input: {
|
||||
repoUrl: 'gitlab.com?repo=repo&owner=owner',
|
||||
repoVisibility: 'private' as const,
|
||||
branches: [
|
||||
{
|
||||
name: 'dev',
|
||||
create: true,
|
||||
ref: 'master',
|
||||
protect: true,
|
||||
},
|
||||
{
|
||||
name: 'stage',
|
||||
create: true,
|
||||
},
|
||||
{
|
||||
name: 'perf',
|
||||
protect: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
workspacePath: 'lol',
|
||||
logger: getVoidLogger(),
|
||||
logStream: new PassThrough(),
|
||||
output: jest.fn(),
|
||||
createTemporaryDirectory: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
@@ -166,6 +199,8 @@ describe('publish:gitlab', () => {
|
||||
name: 'bob',
|
||||
visibility: 'private',
|
||||
});
|
||||
expect(mockGitlabClient.Branches.create).not.toHaveBeenCalled();
|
||||
expect(mockGitlabClient.ProtectedBranches.protect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call the correct Gitlab APIs when the owner is an organization', async () => {
|
||||
@@ -184,6 +219,8 @@ describe('publish:gitlab', () => {
|
||||
visibility: 'private',
|
||||
ci_config_path: '.gitlab-ci.yml',
|
||||
});
|
||||
expect(mockGitlabClient.Branches.create).not.toHaveBeenCalled();
|
||||
expect(mockGitlabClient.ProtectedBranches.protect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call the correct Gitlab APIs when the owner is not an organization', async () => {
|
||||
@@ -202,16 +239,18 @@ describe('publish:gitlab', () => {
|
||||
visibility: 'private',
|
||||
ci_config_path: '.gitlab-ci.yml',
|
||||
});
|
||||
expect(mockGitlabClient.Branches.create).not.toHaveBeenCalled();
|
||||
expect(mockGitlabClient.ProtectedBranches.protect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call the correct Gitlab APIs when using project options with override of visibility and topics', async () => {
|
||||
it('should call the correct Gitlab APIs when using project settings with override of visibility and topics', async () => {
|
||||
mockGitlabClient.Users.current.mockResolvedValue({ id: 12345 });
|
||||
mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
|
||||
mockGitlabClient.Projects.create.mockResolvedValue({
|
||||
http_url_to_repo: 'http://mockurl.git',
|
||||
});
|
||||
|
||||
await action.handler(mockContextWithOptions);
|
||||
await action.handler(mockContextWithSettings);
|
||||
|
||||
expect(mockGitlabClient.Namespaces.show).toHaveBeenCalledWith('owner');
|
||||
expect(mockGitlabClient.Projects.create).toHaveBeenCalledWith({
|
||||
@@ -221,6 +260,48 @@ describe('publish:gitlab', () => {
|
||||
topics: ['topic1', 'topic2'],
|
||||
ci_config_path: '.gitlab-ci.yml',
|
||||
});
|
||||
expect(mockGitlabClient.Branches.create).not.toHaveBeenCalled();
|
||||
expect(mockGitlabClient.ProtectedBranches.protect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call the correct Gitlab APIs for branches and protectd branches when branch settings provided', async () => {
|
||||
mockGitlabClient.Users.current.mockResolvedValue({ id: 12345 });
|
||||
mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
|
||||
mockGitlabClient.Projects.create.mockResolvedValue({
|
||||
id: 123456,
|
||||
http_url_to_repo: 'http://mockurl.git',
|
||||
});
|
||||
|
||||
await action.handler(mockContextWithBranches);
|
||||
|
||||
expect(mockGitlabClient.Namespaces.show).toHaveBeenCalledWith('owner');
|
||||
expect(mockGitlabClient.Projects.create).toHaveBeenCalledWith({
|
||||
namespace_id: 1234,
|
||||
name: 'repo',
|
||||
visibility: 'private',
|
||||
});
|
||||
expect(mockGitlabClient.Branches.create).toHaveBeenCalledTimes(2);
|
||||
expect(mockGitlabClient.ProtectedBranches.protect).toHaveBeenCalledTimes(2);
|
||||
|
||||
expect(mockGitlabClient.Branches.create).toHaveBeenCalledWith(
|
||||
123456,
|
||||
'dev',
|
||||
'master',
|
||||
);
|
||||
expect(mockGitlabClient.Branches.create).toHaveBeenCalledWith(
|
||||
123456,
|
||||
'stage',
|
||||
'master',
|
||||
);
|
||||
|
||||
expect(mockGitlabClient.ProtectedBranches.protect).toHaveBeenCalledWith(
|
||||
123456,
|
||||
'dev',
|
||||
);
|
||||
expect(mockGitlabClient.ProtectedBranches.protect).toHaveBeenCalledWith(
|
||||
123456,
|
||||
'perf',
|
||||
);
|
||||
});
|
||||
|
||||
it('should call initRepoAndPush with the correct values', async () => {
|
||||
|
||||
@@ -18,7 +18,7 @@ import { InputError } from '@backstage/errors';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
import { Gitlab } from '@gitbeaker/node';
|
||||
import { initRepoAndPush } from '../helpers';
|
||||
import { initRepoAndPush, printGitlabError } from '../helpers';
|
||||
import { getRepoSourceDirectory, parseRepoUrl } from './util';
|
||||
import { Config } from '@backstage/config';
|
||||
import { examples } from './gitlab.examples';
|
||||
@@ -49,6 +49,12 @@ export function createPublishGitlabAction(options: {
|
||||
/** @deprecated in favour of settings field */
|
||||
topics?: string[];
|
||||
settings?: Record<string, any>;
|
||||
branches?: Array<{
|
||||
name: string;
|
||||
protect?: boolean;
|
||||
create?: boolean;
|
||||
ref?: string;
|
||||
}>;
|
||||
}>({
|
||||
id: 'publish:gitlab',
|
||||
description:
|
||||
@@ -122,6 +128,35 @@ export function createPublishGitlabAction(options: {
|
||||
'Additional project settings, based on https://docs.gitlab.com/ee/api/projects.html#create-project attributes',
|
||||
type: 'object',
|
||||
},
|
||||
branches: {
|
||||
title: 'Project branches settings',
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
required: ['name'],
|
||||
properties: {
|
||||
name: {
|
||||
title: 'Branch name',
|
||||
type: 'string',
|
||||
},
|
||||
protect: {
|
||||
title: 'Should branch be protected',
|
||||
description: `Will mark branch as protected. The default value is 'false'`,
|
||||
type: 'boolean',
|
||||
},
|
||||
create: {
|
||||
title: 'Should branch be created',
|
||||
description: `If branch does not exist, it will be created from provided ref. The default value is 'false'`,
|
||||
type: 'boolean',
|
||||
},
|
||||
ref: {
|
||||
title: 'Branch reference',
|
||||
description: `Branch reference to create branch from. The default value is 'master'`,
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
output: {
|
||||
@@ -157,6 +192,7 @@ export function createPublishGitlabAction(options: {
|
||||
setUserAsOwner = false,
|
||||
topics = [],
|
||||
settings = {},
|
||||
branches = [],
|
||||
} = ctx.input;
|
||||
const { owner, repo, host } = parseRepoUrl(repoUrl, integrations);
|
||||
|
||||
@@ -247,6 +283,41 @@ export function createPublishGitlabAction(options: {
|
||||
gitAuthorInfo,
|
||||
});
|
||||
|
||||
if (branches) {
|
||||
for (const branch of branches) {
|
||||
const {
|
||||
name,
|
||||
protect = false,
|
||||
create = false,
|
||||
ref = 'master',
|
||||
} = branch;
|
||||
|
||||
if (create) {
|
||||
try {
|
||||
await client.Branches.create(projectId, name, ref);
|
||||
} catch (e) {
|
||||
throw new InputError(
|
||||
`Branch creation failed for ${name}. ${printGitlabError(e)}`,
|
||||
);
|
||||
}
|
||||
ctx.logger.info(
|
||||
`Branch ${name} created for ${projectId} with ref ${ref}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (protect) {
|
||||
try {
|
||||
await client.ProtectedBranches.protect(projectId, name);
|
||||
} catch (e) {
|
||||
throw new InputError(
|
||||
`Branch protection failed for ${name}. ${printGitlabError(e)}`,
|
||||
);
|
||||
}
|
||||
ctx.logger.info(`Branch ${name} protected for ${projectId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx.output('commitHash', commitResult?.commitHash);
|
||||
ctx.output('remoteUrl', remoteUrl);
|
||||
ctx.output('repoContentsUrl', repoContentsUrl);
|
||||
|
||||
Reference in New Issue
Block a user