feat: Add 'gitlab:repo:push' scaffolder action
Signed-off-by: Arthur Gavlyukovskiy <agavlyukovskiy@gmail.com>
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend-module-gitlab': minor
|
||||
'@backstage/plugin-scaffolder-backend': minor
|
||||
---
|
||||
|
||||
Add 'gitlab:repo:push' scaffolder action to push files to arbitrary branch without creating a Merge Request
|
||||
@@ -76,6 +76,22 @@ export const createGitlabProjectVariableAction: (options: {
|
||||
JsonObject
|
||||
>;
|
||||
|
||||
// @public
|
||||
export const createGitlabRepoPushAction: (options: {
|
||||
integrations: ScmIntegrationRegistry;
|
||||
}) => TemplateAction<
|
||||
{
|
||||
repoUrl: string;
|
||||
branchName: string;
|
||||
commitMessage: string;
|
||||
sourcePath?: string | undefined;
|
||||
targetPath?: string | undefined;
|
||||
token?: string | undefined;
|
||||
commitAction?: 'update' | 'delete' | 'create' | undefined;
|
||||
},
|
||||
JsonObject
|
||||
>;
|
||||
|
||||
// @public
|
||||
export function createPublishGitlabAction(options: {
|
||||
integrations: ScmIntegrationRegistry;
|
||||
|
||||
@@ -19,12 +19,12 @@ import {
|
||||
parseRepoUrl,
|
||||
serializeDirectoryContents,
|
||||
} from '@backstage/plugin-scaffolder-node';
|
||||
import { Gitlab } from '@gitbeaker/node';
|
||||
import { Types } from '@gitbeaker/core';
|
||||
import path from 'path';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { InputError } from '@backstage/errors';
|
||||
import { resolveSafeChildPath } from '@backstage/backend-common';
|
||||
import { createGitlabApi } from './helpers';
|
||||
|
||||
/**
|
||||
* Create a new action that creates a gitlab merge request.
|
||||
@@ -158,33 +158,16 @@ export const createPublishGitlabMergeRequestAction = (options: {
|
||||
targetPath,
|
||||
sourcePath,
|
||||
title,
|
||||
token: providedToken,
|
||||
token,
|
||||
} = ctx.input;
|
||||
|
||||
const { host, owner, repo, project } = parseRepoUrl(
|
||||
repoUrl,
|
||||
integrations,
|
||||
);
|
||||
const { owner, repo, project } = parseRepoUrl(repoUrl, integrations);
|
||||
const repoID = project ? project : `${owner}/${repo}`;
|
||||
|
||||
const integrationConfig = integrations.gitlab.byHost(host);
|
||||
|
||||
if (!integrationConfig) {
|
||||
throw new InputError(
|
||||
`No matching integration configuration for host ${host}, please check your integrations config`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!integrationConfig.config.token && !providedToken) {
|
||||
throw new InputError(`No token available for host ${host}`);
|
||||
}
|
||||
|
||||
const token = providedToken ?? integrationConfig.config.token!;
|
||||
const tokenType = providedToken ? 'oauthToken' : 'token';
|
||||
|
||||
const api = new Gitlab({
|
||||
host: integrationConfig.config.baseUrl,
|
||||
[tokenType]: token,
|
||||
const api = createGitlabApi({
|
||||
integrations,
|
||||
token,
|
||||
repoUrl,
|
||||
});
|
||||
|
||||
let assigneeId = undefined;
|
||||
|
||||
@@ -0,0 +1,436 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { createRootLogger, getRootLogger } from '@backstage/backend-common';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { ScmIntegrations } from '@backstage/integration';
|
||||
import { TemplateAction } from '@backstage/plugin-scaffolder-node';
|
||||
import { Writable } from 'stream';
|
||||
import { createMockDirectory } from '@backstage/backend-test-utils';
|
||||
import { createGitlabRepoPushAction } from './gitlabRepoPush';
|
||||
|
||||
// Make sure root logger is initialized ahead of FS mock
|
||||
createRootLogger();
|
||||
|
||||
const mockGitlabClient = {
|
||||
Projects: {
|
||||
create: jest.fn(),
|
||||
show: jest.fn(async (_: any) => {
|
||||
return {
|
||||
default_branch: 'main',
|
||||
};
|
||||
}),
|
||||
},
|
||||
Branches: {
|
||||
create: jest.fn(),
|
||||
show: jest.fn(),
|
||||
},
|
||||
Commits: {
|
||||
create: jest.fn(async (_: any) => ({
|
||||
id: 'bb6bce457ed069a38ef8d16ef38602972c7735c5',
|
||||
})),
|
||||
},
|
||||
};
|
||||
|
||||
jest.mock('@gitbeaker/node', () => ({
|
||||
Gitlab: class {
|
||||
constructor() {
|
||||
return mockGitlabClient;
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
describe('createGitLabCommit', () => {
|
||||
let instance: TemplateAction<any>;
|
||||
|
||||
const mockDir = createMockDirectory();
|
||||
const workspacePath = mockDir.resolve('workspace');
|
||||
|
||||
beforeEach(() => {
|
||||
mockDir.clear();
|
||||
|
||||
const config = new ConfigReader({
|
||||
integrations: {
|
||||
gitlab: [
|
||||
{
|
||||
host: 'gitlab.com',
|
||||
token: 'token',
|
||||
apiBaseUrl: 'https://api.gitlab.com',
|
||||
},
|
||||
{
|
||||
host: 'hosted.gitlab.com',
|
||||
apiBaseUrl: 'https://api.hosted.gitlab.com',
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
instance = createGitlabRepoPushAction({ integrations });
|
||||
});
|
||||
|
||||
describe('createGitLabCommitWithoutCommitAction', () => {
|
||||
it('default commitAction is create', async () => {
|
||||
const input = {
|
||||
repoUrl: 'gitlab.com?repo=repo&owner=owner',
|
||||
commitMessage: 'Create my new commit',
|
||||
branchName: 'some-branch',
|
||||
};
|
||||
mockDir.setContent({
|
||||
[workspacePath]: {
|
||||
'foo.txt': 'Hello there!',
|
||||
},
|
||||
});
|
||||
const ctx = {
|
||||
createTemporaryDirectory: jest.fn(),
|
||||
output: jest.fn(),
|
||||
logger: getRootLogger(),
|
||||
logStream: new Writable(),
|
||||
input,
|
||||
workspacePath,
|
||||
};
|
||||
await instance.handler(ctx);
|
||||
|
||||
expect(mockGitlabClient.Branches.create).toHaveBeenCalledTimes(0);
|
||||
expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith(
|
||||
'owner/repo',
|
||||
'some-branch',
|
||||
'Create my new commit',
|
||||
[
|
||||
{
|
||||
action: 'create',
|
||||
filePath: 'foo.txt',
|
||||
content: 'SGVsbG8gdGhlcmUh',
|
||||
encoding: 'base64',
|
||||
execute_filemode: false,
|
||||
},
|
||||
],
|
||||
);
|
||||
expect(ctx.output).toHaveBeenCalledWith(
|
||||
'commitHash',
|
||||
'bb6bce457ed069a38ef8d16ef38602972c7735c5',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createGitLabCommitWithCommitAction', () => {
|
||||
it('commitAction is create when create is passed in options', async () => {
|
||||
const input = {
|
||||
repoUrl: 'gitlab.com?repo=repo&owner=owner',
|
||||
commitMessage: 'Create my new commit',
|
||||
branchName: 'some-branch',
|
||||
commitAction: 'create',
|
||||
};
|
||||
mockDir.setContent({
|
||||
[workspacePath]: {
|
||||
'foo.txt': 'Hello there!',
|
||||
},
|
||||
});
|
||||
|
||||
const ctx = {
|
||||
createTemporaryDirectory: jest.fn(),
|
||||
output: jest.fn(),
|
||||
logger: getRootLogger(),
|
||||
logStream: new Writable(),
|
||||
input,
|
||||
workspacePath,
|
||||
};
|
||||
await instance.handler(ctx);
|
||||
|
||||
expect(mockGitlabClient.Branches.create).toHaveBeenCalledTimes(0);
|
||||
expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith(
|
||||
'owner/repo',
|
||||
'some-branch',
|
||||
'Create my new commit',
|
||||
[
|
||||
{
|
||||
action: 'create',
|
||||
filePath: 'foo.txt',
|
||||
content: 'SGVsbG8gdGhlcmUh',
|
||||
encoding: 'base64',
|
||||
execute_filemode: false,
|
||||
},
|
||||
],
|
||||
);
|
||||
expect(ctx.output).toHaveBeenCalledWith(
|
||||
'commitHash',
|
||||
'bb6bce457ed069a38ef8d16ef38602972c7735c5',
|
||||
);
|
||||
});
|
||||
|
||||
it('commitAction is update when update is passed in options', async () => {
|
||||
const input = {
|
||||
repoUrl: 'gitlab.com?repo=repo&owner=owner',
|
||||
commitMessage: 'Create my new commit',
|
||||
branchName: 'some-branch',
|
||||
commitAction: 'update',
|
||||
};
|
||||
mockDir.setContent({
|
||||
[workspacePath]: {
|
||||
'foo.txt': 'Hello there!',
|
||||
},
|
||||
});
|
||||
|
||||
const ctx = {
|
||||
createTemporaryDirectory: jest.fn(),
|
||||
output: jest.fn(),
|
||||
logger: getRootLogger(),
|
||||
logStream: new Writable(),
|
||||
input,
|
||||
workspacePath,
|
||||
};
|
||||
await instance.handler(ctx);
|
||||
|
||||
expect(mockGitlabClient.Branches.create).toHaveBeenCalledTimes(0);
|
||||
expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith(
|
||||
'owner/repo',
|
||||
'some-branch',
|
||||
'Create my new commit',
|
||||
[
|
||||
{
|
||||
action: 'update',
|
||||
filePath: 'foo.txt',
|
||||
content: 'SGVsbG8gdGhlcmUh',
|
||||
encoding: 'base64',
|
||||
execute_filemode: false,
|
||||
},
|
||||
],
|
||||
);
|
||||
expect(ctx.output).toHaveBeenCalledWith(
|
||||
'commitHash',
|
||||
'bb6bce457ed069a38ef8d16ef38602972c7735c5',
|
||||
);
|
||||
});
|
||||
|
||||
it('commitAction is delete when delete is passed in options', async () => {
|
||||
const input = {
|
||||
repoUrl: 'gitlab.com?repo=repo&owner=owner',
|
||||
commitMessage: 'Create my new commit',
|
||||
branchName: 'some-branch',
|
||||
commitAction: 'delete',
|
||||
};
|
||||
mockDir.setContent({
|
||||
[workspacePath]: {
|
||||
'foo.txt': 'Hello there!',
|
||||
},
|
||||
});
|
||||
|
||||
const ctx = {
|
||||
createTemporaryDirectory: jest.fn(),
|
||||
output: jest.fn(),
|
||||
logger: getRootLogger(),
|
||||
logStream: new Writable(),
|
||||
input,
|
||||
workspacePath,
|
||||
};
|
||||
await instance.handler(ctx);
|
||||
|
||||
expect(mockGitlabClient.Branches.create).toHaveBeenCalledTimes(0);
|
||||
expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith(
|
||||
'owner/repo',
|
||||
'some-branch',
|
||||
'Create my new commit',
|
||||
[
|
||||
{
|
||||
action: 'delete',
|
||||
filePath: 'foo.txt',
|
||||
content: 'SGVsbG8gdGhlcmUh',
|
||||
encoding: 'base64',
|
||||
execute_filemode: false,
|
||||
},
|
||||
],
|
||||
);
|
||||
expect(ctx.output).toHaveBeenCalledWith(
|
||||
'commitHash',
|
||||
'bb6bce457ed069a38ef8d16ef38602972c7735c5',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with sourcePath', () => {
|
||||
it('creates a commit with only relevant files', async () => {
|
||||
const input = {
|
||||
repoUrl: 'gitlab.com?repo=repo&owner=owner',
|
||||
commitMessage: 'Create my new commit',
|
||||
branchName: 'some-branch',
|
||||
sourcePath: 'source',
|
||||
commitAction: 'create',
|
||||
};
|
||||
|
||||
mockDir.setContent({
|
||||
[workspacePath]: {
|
||||
source: { 'foo.txt': 'Hello there!' },
|
||||
irrelevant: { 'bar.txt': 'Nothing to see here' },
|
||||
},
|
||||
});
|
||||
|
||||
const ctx = {
|
||||
createTemporaryDirectory: jest.fn(),
|
||||
output: jest.fn(),
|
||||
logger: getRootLogger(),
|
||||
logStream: new Writable(),
|
||||
input,
|
||||
workspacePath,
|
||||
};
|
||||
|
||||
await instance.handler(ctx);
|
||||
|
||||
expect(mockGitlabClient.Branches.create).toHaveBeenCalledTimes(0);
|
||||
expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith(
|
||||
'owner/repo',
|
||||
'some-branch',
|
||||
'Create my new commit',
|
||||
[
|
||||
{
|
||||
action: 'create',
|
||||
filePath: 'foo.txt',
|
||||
content: 'SGVsbG8gdGhlcmUh',
|
||||
encoding: 'base64',
|
||||
execute_filemode: false,
|
||||
},
|
||||
],
|
||||
);
|
||||
expect(ctx.output).toHaveBeenCalledWith(
|
||||
'commitHash',
|
||||
'bb6bce457ed069a38ef8d16ef38602972c7735c5',
|
||||
);
|
||||
});
|
||||
|
||||
it('creates a commit with only relevant files placed under different targetPath', async () => {
|
||||
const input = {
|
||||
repoUrl: 'gitlab.com?repo=repo&owner=owner',
|
||||
commitMessage: 'Create my new commit',
|
||||
branchName: 'some-branch',
|
||||
sourcePath: 'source',
|
||||
targetPath: 'target',
|
||||
commitAction: 'create',
|
||||
};
|
||||
|
||||
mockDir.setContent({
|
||||
[workspacePath]: {
|
||||
source: { 'foo.txt': 'Hello there!' },
|
||||
irrelevant: { 'bar.txt': 'Nothing to see here' },
|
||||
},
|
||||
});
|
||||
|
||||
const ctx = {
|
||||
createTemporaryDirectory: jest.fn(),
|
||||
output: jest.fn(),
|
||||
logger: getRootLogger(),
|
||||
logStream: new Writable(),
|
||||
input,
|
||||
workspacePath,
|
||||
};
|
||||
|
||||
await instance.handler(ctx);
|
||||
|
||||
expect(mockGitlabClient.Branches.create).toHaveBeenCalledTimes(0);
|
||||
expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith(
|
||||
'owner/repo',
|
||||
'some-branch',
|
||||
'Create my new commit',
|
||||
[
|
||||
{
|
||||
action: 'create',
|
||||
filePath: 'target/foo.txt',
|
||||
content: 'SGVsbG8gdGhlcmUh',
|
||||
encoding: 'base64',
|
||||
execute_filemode: false,
|
||||
},
|
||||
],
|
||||
);
|
||||
expect(ctx.output).toHaveBeenCalledWith(
|
||||
'commitHash',
|
||||
'bb6bce457ed069a38ef8d16ef38602972c7735c5',
|
||||
);
|
||||
});
|
||||
|
||||
it('should not allow to use files outside of the workspace', async () => {
|
||||
const input = {
|
||||
repoUrl: 'gitlab.com?repo=repo&owner=owner',
|
||||
commitMessage: 'Create my new commit',
|
||||
branchName: 'some-branch',
|
||||
sourcePath: '../../test',
|
||||
commitAction: 'create',
|
||||
};
|
||||
|
||||
const ctx = {
|
||||
createTemporaryDirectory: jest.fn(),
|
||||
output: jest.fn(),
|
||||
logger: getRootLogger(),
|
||||
logStream: new Writable(),
|
||||
input,
|
||||
workspacePath,
|
||||
};
|
||||
|
||||
await expect(instance.handler(ctx)).rejects.toThrow(
|
||||
'Relative path is not allowed to refer to a directory outside its parent',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createCommitToBranchThatDoesNotExist', () => {
|
||||
it('should create a new branch', async () => {
|
||||
mockGitlabClient.Branches.show.mockRejectedValue({
|
||||
response: {
|
||||
statusCode: 404,
|
||||
},
|
||||
});
|
||||
const input = {
|
||||
repoUrl: 'gitlab.com?repo=repo&owner=owner',
|
||||
commitMessage: 'Create my new commit',
|
||||
branchName: 'some-branch',
|
||||
};
|
||||
mockDir.setContent({
|
||||
[workspacePath]: {
|
||||
'foo.txt': 'Hello there!',
|
||||
},
|
||||
});
|
||||
const ctx = {
|
||||
createTemporaryDirectory: jest.fn(),
|
||||
output: jest.fn(),
|
||||
logger: getRootLogger(),
|
||||
logStream: new Writable(),
|
||||
input,
|
||||
workspacePath,
|
||||
};
|
||||
await instance.handler(ctx);
|
||||
|
||||
expect(mockGitlabClient.Branches.create).toHaveBeenCalledWith(
|
||||
'owner/repo',
|
||||
'some-branch',
|
||||
'main',
|
||||
);
|
||||
expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith(
|
||||
'owner/repo',
|
||||
'some-branch',
|
||||
'Create my new commit',
|
||||
[
|
||||
{
|
||||
action: 'create',
|
||||
filePath: 'foo.txt',
|
||||
content: 'SGVsbG8gdGhlcmUh',
|
||||
encoding: 'base64',
|
||||
execute_filemode: false,
|
||||
},
|
||||
],
|
||||
);
|
||||
expect(ctx.output).toHaveBeenCalledWith(
|
||||
'commitHash',
|
||||
'bb6bce457ed069a38ef8d16ef38602972c7735c5',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
createTemplateAction,
|
||||
parseRepoUrl,
|
||||
serializeDirectoryContents,
|
||||
} from '@backstage/plugin-scaffolder-node';
|
||||
import { Types } from '@gitbeaker/core';
|
||||
import path from 'path';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { InputError } from '@backstage/errors';
|
||||
import { resolveSafeChildPath } from '@backstage/backend-common';
|
||||
import { createGitlabApi } from './helpers';
|
||||
|
||||
/**
|
||||
* Create a new action that commits into a gitlab repository.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const createGitlabRepoPushAction = (options: {
|
||||
integrations: ScmIntegrationRegistry;
|
||||
}) => {
|
||||
const { integrations } = options;
|
||||
|
||||
return createTemplateAction<{
|
||||
repoUrl: string;
|
||||
branchName: string;
|
||||
commitMessage: string;
|
||||
sourcePath?: string;
|
||||
targetPath?: string;
|
||||
token?: string;
|
||||
commitAction?: 'create' | 'delete' | 'update';
|
||||
}>({
|
||||
id: 'gitlab:repo:push',
|
||||
schema: {
|
||||
input: {
|
||||
required: ['repoUrl', 'branchName', 'commitMessage'],
|
||||
type: 'object',
|
||||
properties: {
|
||||
repoUrl: {
|
||||
type: 'string',
|
||||
title: 'Repository Location',
|
||||
description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username`,
|
||||
},
|
||||
branchName: {
|
||||
type: 'string',
|
||||
title: 'Source Branch Name',
|
||||
description: 'The branch name for the commit',
|
||||
},
|
||||
commitMessage: {
|
||||
type: 'string',
|
||||
title: 'Commit Message',
|
||||
description: `The commit message`,
|
||||
},
|
||||
sourcePath: {
|
||||
type: 'string',
|
||||
title: 'Working Subdirectory',
|
||||
description:
|
||||
'Subdirectory of working directory to copy changes from',
|
||||
},
|
||||
targetPath: {
|
||||
type: 'string',
|
||||
title: 'Repository Subdirectory',
|
||||
description: 'Subdirectory of repository to apply changes to',
|
||||
},
|
||||
token: {
|
||||
title: 'Authentication Token',
|
||||
type: 'string',
|
||||
description: 'The token to use for authorization to GitLab',
|
||||
},
|
||||
commitAction: {
|
||||
title: 'Commit action',
|
||||
type: 'string',
|
||||
enum: ['create', 'update', 'delete'],
|
||||
description:
|
||||
'The action to be used for git commit. Defaults to create.',
|
||||
},
|
||||
},
|
||||
},
|
||||
output: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
projectid: {
|
||||
title: 'Gitlab Project id/Name(slug)',
|
||||
type: 'string',
|
||||
},
|
||||
projectPath: {
|
||||
title: 'Gitlab Project path',
|
||||
type: 'string',
|
||||
},
|
||||
commitHash: {
|
||||
title: 'The git commit hash of the commit',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
async handler(ctx) {
|
||||
const {
|
||||
branchName,
|
||||
repoUrl,
|
||||
targetPath,
|
||||
sourcePath,
|
||||
token,
|
||||
commitAction,
|
||||
} = ctx.input;
|
||||
|
||||
const { owner, repo, project } = parseRepoUrl(repoUrl, integrations);
|
||||
const repoID = project ? project : `${owner}/${repo}`;
|
||||
|
||||
const api = createGitlabApi({
|
||||
integrations,
|
||||
token,
|
||||
repoUrl,
|
||||
});
|
||||
|
||||
let fileRoot: string;
|
||||
if (sourcePath) {
|
||||
fileRoot = resolveSafeChildPath(ctx.workspacePath, sourcePath);
|
||||
} else {
|
||||
fileRoot = ctx.workspacePath;
|
||||
}
|
||||
|
||||
const fileContents = await serializeDirectoryContents(fileRoot, {
|
||||
gitignore: true,
|
||||
});
|
||||
|
||||
const actions: Types.CommitAction[] = fileContents.map(file => ({
|
||||
action: commitAction ?? 'create',
|
||||
filePath: targetPath
|
||||
? path.posix.join(targetPath, file.path)
|
||||
: file.path,
|
||||
encoding: 'base64',
|
||||
content: file.content.toString('base64'),
|
||||
execute_filemode: file.executable,
|
||||
}));
|
||||
|
||||
try {
|
||||
await api.Branches.show(repoID, branchName);
|
||||
} catch (e) {
|
||||
if (e.response?.statusCode !== 404) {
|
||||
throw new InputError(
|
||||
`Failed to check status of branch '${branchName}'. Please make sure that branch already exists or Backstage has permissions to create one. ${e}`,
|
||||
);
|
||||
}
|
||||
// create a branch using the default branch as ref
|
||||
try {
|
||||
const projects = await api.Projects.show(repoID);
|
||||
const { default_branch: defaultBranch } = projects;
|
||||
await api.Branches.create(repoID, branchName, String(defaultBranch));
|
||||
} catch (e2) {
|
||||
throw new InputError(
|
||||
`The branch '${branchName}' was not found and creation failed with error. Please make sure that branch already exists or Backstage has permissions to create one. ${e2}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const commit = await api.Commits.create(
|
||||
repoID,
|
||||
branchName,
|
||||
ctx.input.commitMessage,
|
||||
actions,
|
||||
);
|
||||
ctx.output('projectid', repoID);
|
||||
ctx.output('projectPath', repoID);
|
||||
ctx.output('commitHash', commit.id);
|
||||
} catch (e) {
|
||||
throw new InputError(
|
||||
`Committing the changes to ${branchName} failed. Please check that none of the files created by the template already exists. ${e}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { parseRepoUrl } from '@backstage/plugin-scaffolder-node';
|
||||
import { InputError } from '@backstage/errors';
|
||||
import { Gitlab } from '@gitbeaker/node';
|
||||
import { ScmIntegrationRegistry } from '@backstage/integration';
|
||||
import { Resources } from '@gitbeaker/core';
|
||||
|
||||
export function createGitlabApi(options: {
|
||||
integrations: ScmIntegrationRegistry;
|
||||
token?: string;
|
||||
repoUrl: string;
|
||||
}): Resources.Gitlab {
|
||||
const { integrations, token: providedToken, repoUrl } = options;
|
||||
|
||||
const { host } = parseRepoUrl(repoUrl, integrations);
|
||||
|
||||
const integrationConfig = integrations.gitlab.byHost(host);
|
||||
|
||||
if (!integrationConfig) {
|
||||
throw new InputError(
|
||||
`No matching integration configuration for host ${host}, please check your integrations config`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!integrationConfig.config.token && !providedToken) {
|
||||
throw new InputError(`No token available for host ${host}`);
|
||||
}
|
||||
|
||||
const token = providedToken ?? integrationConfig.config.token!;
|
||||
const tokenType = providedToken ? 'oauthToken' : 'token';
|
||||
|
||||
return new Gitlab({
|
||||
host: integrationConfig.config.baseUrl,
|
||||
[tokenType]: token,
|
||||
});
|
||||
}
|
||||
@@ -19,3 +19,4 @@ export * from './createGitlabProjectAccessTokenAction';
|
||||
export * from './createGitlabProjectVariableAction';
|
||||
export * from './gitlab';
|
||||
export * from './gitlabMergeRequest';
|
||||
export * from './gitlabRepoPush';
|
||||
|
||||
@@ -69,6 +69,7 @@ import {
|
||||
|
||||
import {
|
||||
createPublishGitlabAction,
|
||||
createGitlabRepoPushAction,
|
||||
createPublishGitlabMergeRequestAction,
|
||||
} from '@backstage/plugin-scaffolder-backend-module-gitlab';
|
||||
|
||||
@@ -165,6 +166,9 @@ export const createBuiltinActions = (
|
||||
createPublishGitlabMergeRequestAction({
|
||||
integrations,
|
||||
}),
|
||||
createGitlabRepoPushAction({
|
||||
integrations,
|
||||
}),
|
||||
createPublishBitbucketAction({
|
||||
integrations,
|
||||
config,
|
||||
|
||||
Reference in New Issue
Block a user