diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/bitbucketCloud/bitbucketCloudPipelinesRun.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/bitbucketCloud/bitbucketCloudPipelinesRun.examples.test.ts new file mode 100644 index 0000000000..6205f04400 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/bitbucketCloud/bitbucketCloudPipelinesRun.examples.test.ts @@ -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: '', + uuid: '', + build_number: 33, + creator: { + type: '', + }, + repository: { + type: '', + }, + target: { + type: '', + }, + trigger: { + type: '', + }, + state: { + type: '', + }, + created_on: '', + completed_on: '', + build_seconds_used: 50, +}; + +describe('bitbucket:pipelines:run', () => { + const logStream = { + write: jest.fn(), + } as jest.Mocked> as jest.Mocked; + const mockDir = createMockDirectory(); + const workspacePath = mockDir.resolve('workspace'); + let action: TemplateAction; + + const mockContext = { + input: {}, + baseUrl: 'somebase', + workspacePath, + logger: getVoidLogger(), + logStream, + output: jest.fn(), + createTemporaryDirectory: jest.fn(), + }; + + const body = { + type: '', + uuid: '', + build_number: 33, + creator: { + type: '', + }, + repository: { + type: '', + }, + target: { + type: '', + }, + trigger: { + type: '', + }, + state: { + type: '', + }, + created_on: '', + completed_on: '', + 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).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); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/bitbucketCloud/bitbucketCloudPipelinesRun.examples.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/bitbucketCloud/bitbucketCloudPipelinesRun.examples.ts new file mode 100644 index 0000000000..81b8603c35 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/bitbucketCloud/bitbucketCloudPipelinesRun.examples.ts @@ -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', + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/bitbucketCloud/bitbucketCloudPipelinesRun.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/bitbucketCloud/bitbucketCloudPipelinesRun.test.ts new file mode 100644 index 0000000000..741ae02e92 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/bitbucketCloud/bitbucketCloudPipelinesRun.test.ts @@ -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: '', + uuid: '', + build_number: 33, + creator: { + type: '', + }, + repository: { + type: '', + }, + target: { + type: '', + }, + trigger: { + type: '', + }, + state: { + type: '', + }, + created_on: '', + completed_on: '', + build_seconds_used: 50, +}; + +describe('bitbucket:pipelines:run', () => { + const logStream = { + write: jest.fn(), + } as jest.Mocked> as jest.Mocked; + const mockDir = createMockDirectory(); + const workspacePath = mockDir.resolve('workspace'); + let action: TemplateAction; + + const mockContext = { + input: {}, + baseUrl: 'somebase', + workspacePath, + logger: getVoidLogger(), + logStream, + output: jest.fn(), + createTemporaryDirectory: jest.fn(), + }; + + const body = { + type: '', + uuid: '', + build_number: 33, + creator: { + type: '', + }, + repository: { + type: '', + }, + target: { + type: '', + }, + trigger: { + type: '', + }, + state: { + type: '', + }, + created_on: '', + completed_on: '', + 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).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); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/bitbucketCloud/bitbucketCloudPipelinesRun.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/bitbucketCloud/bitbucketCloudPipelinesRun.ts new file mode 100644 index 0000000000..b732ce2689 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/bitbucketCloud/bitbucketCloudPipelinesRun.ts @@ -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: '', + uuid: '', + build_number: 33, + creator: { + type: '', + }, + repository: { + type: '', + }, + target: { + type: '', + }, + trigger: { + type: '', + }, + state: { + type: '', + }, + created_on: '', + completed_on: '', + 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); + } + }, + }); +};