feat(bitbucketCloudPipelinesRun): adds new TemplateAction for running a bitbucket pipeline

Signed-off-by: Matthew Prinold <matthewprinold@gmail.com>
This commit is contained in:
Matthew Prinold
2023-12-06 11:20:46 +00:00
parent 7896e7daf9
commit 24e5c11a75
4 changed files with 397 additions and 0 deletions
@@ -0,0 +1,130 @@
/*
* Copyright 2021 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 { getVoidLogger } from '@backstage/backend-common';
import { createMockDirectory } from '@backstage/backend-test-utils';
import { Writable } from 'stream';
import { TemplateAction } from '@backstage/plugin-scaffolder-node';
import { createBitbucketPipelinesRunAction } from './bitbucketCloudPipelinesRun';
import fetch from 'node-fetch';
import yaml from 'yaml';
import { examples } from './bitbucketCloudPipelinesRun.examples';
import { json } from 'stream/consumers';
import { object } from 'zod';
jest.mock('node-fetch');
const { Response } = jest.requireActual('node-fetch');
const createdResponse = {
type: '<string>',
uuid: '<string>',
build_number: 33,
creator: {
type: '<string>',
},
repository: {
type: '<string>',
},
target: {
type: '<string>',
},
trigger: {
type: '<string>',
},
state: {
type: '<string>',
},
created_on: '<string>',
completed_on: '<string>',
build_seconds_used: 50,
};
describe('bitbucket:pipelines:run', () => {
const logStream = {
write: jest.fn(),
} as jest.Mocked<Partial<Writable>> as jest.Mocked<Writable>;
const mockDir = createMockDirectory();
const workspacePath = mockDir.resolve('workspace');
let action: TemplateAction<any>;
const mockContext = {
input: {},
baseUrl: 'somebase',
workspacePath,
logger: getVoidLogger(),
logStream,
output: jest.fn(),
createTemporaryDirectory: jest.fn(),
};
const body = {
type: '<string>',
uuid: '<string>',
build_number: 33,
creator: {
type: '<string>',
},
repository: {
type: '<string>',
},
target: {
type: '<string>',
},
trigger: {
type: '<string>',
},
state: {
type: '<string>',
},
created_on: '<string>',
completed_on: '<string>',
build_seconds_used: 50,
};
beforeEach(() => {
jest.resetAllMocks();
action = createBitbucketPipelinesRunAction();
});
it('should call the bitbucket api for running a pipeline', async () => {
const ctx = Object.assign({}, mockContext, {
input: yaml.parse(examples[0].example).steps[0].input,
});
(fetch as jest.MockedFunction<typeof fetch>).mockResolvedValue(
new Response(JSON.stringify(createdResponse), {
url: 'url',
status: 201,
statusText: 'Created',
}),
);
await action.handler(ctx);
expect(fetch).toHaveBeenCalledWith(
'https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo-slug/pipelines',
{
body: JSON.stringify(body),
headers: {
Accept: 'application/json',
Authorization: 'Bearer testToken',
'Content-Type': 'application/json',
},
method: 'POST',
},
);
expect(logStream.write).toHaveBeenCalledTimes(2);
expect(logStream.write).toHaveBeenNthCalledWith(1, 'Response: 201 Created');
expect(logStream.write).toHaveBeenNthCalledWith(2, createdResponse);
});
});
@@ -0,0 +1,37 @@
/*
* Copyright 2021 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 { TemplateExample } from '@backstage/plugin-scaffolder-node';
import yaml from 'yaml';
export const examples: TemplateExample[] = [
{
description: '',
example: yaml.stringify({
steps: [
{
action: 'bitbucket:pipelines:run',
id: 'run-bitbucket-pipeline',
name: 'Run an example bitbucket pipeline',
input: {
workspace: 'test-workspace',
repo_slug: 'test-repo-slug',
},
},
],
}),
},
];
@@ -0,0 +1,128 @@
/*
* Copyright 2021 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 { getVoidLogger } from '@backstage/backend-common';
import { createMockDirectory } from '@backstage/backend-test-utils';
import { Writable } from 'stream';
import { TemplateAction } from '@backstage/plugin-scaffolder-node';
import { createBitbucketPipelinesRunAction } from './bitbucketCloudPipelinesRun';
import fetch from 'node-fetch';
jest.mock('node-fetch');
const { Response } = jest.requireActual('node-fetch');
const createdResponse = {
type: '<string>',
uuid: '<string>',
build_number: 33,
creator: {
type: '<string>',
},
repository: {
type: '<string>',
},
target: {
type: '<string>',
},
trigger: {
type: '<string>',
},
state: {
type: '<string>',
},
created_on: '<string>',
completed_on: '<string>',
build_seconds_used: 50,
};
describe('bitbucket:pipelines:run', () => {
const logStream = {
write: jest.fn(),
} as jest.Mocked<Partial<Writable>> as jest.Mocked<Writable>;
const mockDir = createMockDirectory();
const workspacePath = mockDir.resolve('workspace');
let action: TemplateAction<any>;
const mockContext = {
input: {},
baseUrl: 'somebase',
workspacePath,
logger: getVoidLogger(),
logStream,
output: jest.fn(),
createTemporaryDirectory: jest.fn(),
};
const body = {
type: '<string>',
uuid: '<string>',
build_number: 33,
creator: {
type: '<string>',
},
repository: {
type: '<string>',
},
target: {
type: '<string>',
},
trigger: {
type: '<string>',
},
state: {
type: '<string>',
},
created_on: '<string>',
completed_on: '<string>',
build_seconds_used: 50,
};
beforeEach(() => {
jest.resetAllMocks();
action = createBitbucketPipelinesRunAction();
});
it('should call the bitbucket api for running a pipeline', async () => {
const workspace = 'test-workspace';
const repo_slug = 'test-repo-slug';
const ctx = Object.assign({}, mockContext, {
input: { workspace, repo_slug },
});
(fetch as jest.MockedFunction<typeof fetch>).mockResolvedValue(
new Response(JSON.stringify(createdResponse), {
url: 'url',
status: 201,
statusText: 'Created',
}),
);
await action.handler(ctx);
expect(fetch).toHaveBeenCalledWith(
'https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo-slug/pipelines',
{
body: JSON.stringify(body),
headers: {
Accept: 'application/json',
Authorization: 'Bearer testToken',
'Content-Type': 'application/json',
},
method: 'POST',
},
);
expect(logStream.write).toHaveBeenCalledTimes(2);
expect(logStream.write).toHaveBeenNthCalledWith(1, 'Response: 201 Created');
expect(logStream.write).toHaveBeenNthCalledWith(2, createdResponse);
});
});
@@ -0,0 +1,102 @@
/*
* Copyright 2021 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 { examples } from './bitbucketCloudPipelinesRun.examples';
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
import fetch from 'node-fetch';
const id = 'bitbucket:pipelines:run';
/**
* Creates a new action that triggers a run of a bitbucket pipeline
*
* @public
*/
export const createBitbucketPipelinesRunAction = () => {
return createTemplateAction<{
workspace: string;
repo_slug: string;
}>({
id,
description: '',
examples,
schema: {
input: {
type: 'object',
required: ['workspace', 'repo_url'],
properties: {
workspace: {
title: 'Workspace',
description: `This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example {workspace UUID}.`,
type: 'string',
},
repo_slug: {
title: 'The repository',
description: 'The repository name',
type: 'string',
},
},
},
},
supportsDryRun: false,
async handler(ctx) {
const { workspace, repo_slug } = ctx.input;
const bodyData = {
type: '<string>',
uuid: '<string>',
build_number: 33,
creator: {
type: '<string>',
},
repository: {
type: '<string>',
},
target: {
type: '<string>',
},
trigger: {
type: '<string>',
},
state: {
type: '<string>',
},
created_on: '<string>',
completed_on: '<string>',
build_seconds_used: 50,
};
try {
const response = await fetch(
`https://api.bitbucket.org/2.0/repositories/${workspace}/${repo_slug}/pipelines`,
{
method: 'POST',
headers: {
Authorization: 'Bearer testToken',
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(bodyData),
},
);
ctx.logStream.write(
`Response: ${response.status} ${response.statusText}`,
);
const data = await response.json();
ctx.logStream.write(data);
} catch (err) {
ctx.logStream.write(err);
}
},
});
};