diff --git a/.changeset/green-dolphins-cover.md b/.changeset/green-dolphins-cover.md new file mode 100644 index 0000000000..77c90e72f5 --- /dev/null +++ b/.changeset/green-dolphins-cover.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder-backend-module-bitbucket': patch +'@backstage/plugin-scaffolder-backend': minor +--- + +The Scaffolder builtin actions now contains an action for running pipelines from Bitbucket Cloud Rest API diff --git a/plugins/scaffolder-backend-module-bitbucket/api-report.md b/plugins/scaffolder-backend-module-bitbucket/api-report.md index 8e58c5fe7e..0f5f09b5b8 100644 --- a/plugins/scaffolder-backend-module-bitbucket/api-report.md +++ b/plugins/scaffolder-backend-module-bitbucket/api-report.md @@ -8,6 +8,19 @@ import { JsonObject } from '@backstage/types'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; +// @public +export const createBitbucketPipelinesRunAction: (options: { + integrations: ScmIntegrationRegistry; +}) => TemplateAction< + { + workspace: string; + repo_slug: string; + body?: object | undefined; + token?: string | undefined; + }, + JsonObject +>; + // @public @deprecated export function createPublishBitbucketAction(options: { integrations: ScmIntegrationRegistry; diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloud.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloud.ts index c69ae0b147..e9d815f5e8 100644 --- a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloud.ts +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloud.ts @@ -25,6 +25,7 @@ import { import fetch, { Response, RequestInit } from 'node-fetch'; import { Config } from '@backstage/config'; +import { getAuthorizationHeader } from './helpers'; const createRepository = async (opts: { workspace: string; @@ -93,29 +94,6 @@ const createRepository = async (opts: { return { remoteUrl, repoContentsUrl }; }; -const getAuthorizationHeader = (config: { - username?: string; - appPassword?: string; - token?: string; -}) => { - if (config.username && config.appPassword) { - const buffer = Buffer.from( - `${config.username}:${config.appPassword}`, - 'utf8', - ); - - return `Basic ${buffer.toString('base64')}`; - } - - if (config.token) { - return `Bearer ${config.token}`; - } - - throw new Error( - `Authorization has not been provided for Bitbucket Cloud. Please add either username + appPassword to the Integrations config or a user login auth token`, - ); -}; - /** * Creates a new action that initializes a git repository of the content in the workspace * and publishes it to Bitbucket Cloud. diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.examples.test.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.examples.test.ts new file mode 100644 index 0000000000..90ae2c08b9 --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.examples.test.ts @@ -0,0 +1,274 @@ +/* + * 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 { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { PassThrough } from 'stream'; +import { createBitbucketPipelinesRunAction } from './bitbucketCloudPipelinesRun'; +import yaml from 'yaml'; +import { examples } from './bitbucketCloudPipelinesRun.examples'; +import { ConfigReader } from '@backstage/config'; +import { ScmIntegrations } from '@backstage/integration'; +import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; + +describe('bitbucket:pipelines:run', () => { + const config = new ConfigReader({ + integrations: { + bitbucketCloud: [ + { + username: 'u', + appPassword: 'p', + }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + const action = createBitbucketPipelinesRunAction({ integrations }); + const mockContext = { + input: {}, + workspacePath: 'wsp', + logger: getVoidLogger(), + logStream: new PassThrough(), + output: jest.fn(), + createTemporaryDirectory: jest.fn(), + }; + const responseJson = { + repository: { + links: { + html: { href: 'https://bitbucket.org/test-workspace/test-repo-slug' }, + }, + }, + build_number: 1, + }; + const worker = setupServer(); + setupRequestMockHandlers(worker); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('should trigger a pipeline for a branch', async () => { + expect.assertions(2); + worker.use( + rest.post( + 'https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo-slug/pipelines', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Basic dTpw'); + expect(req.body).toEqual({ + target: { + ref_type: 'branch', + type: 'pipeline_ref_target', + ref_name: 'master', + }, + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseJson), + ); + }, + ), + ); + const ctx = Object.assign({}, mockContext, { + input: yaml.parse(examples[0].example).steps[0].input, + }); + await action.handler(ctx); + }); + + it('should trigger a pipeline for a commit on a branch', async () => { + expect.assertions(2); + worker.use( + rest.post( + 'https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo-slug/pipelines', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Basic dTpw'); + expect(req.body).toEqual({ + target: { + commit: { + type: 'commit', + hash: 'ce5b7431602f7cbba007062eeb55225c6e18e956', + }, + ref_type: 'branch', + type: 'pipeline_ref_target', + ref_name: 'master', + }, + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseJson), + ); + }, + ), + ); + const ctx = Object.assign({}, mockContext, { + input: yaml.parse(examples[1].example).steps[0].input, + }); + await action.handler(ctx); + }); + + it('should trigger a specific pipeline definition for a commit', async () => { + expect.assertions(2); + worker.use( + rest.post( + 'https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo-slug/pipelines', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Basic dTpw'); + expect(req.body).toEqual({ + target: { + commit: { + type: 'commit', + hash: 'a3c4e02c9a3755eccdc3764e6ea13facdf30f923', + }, + selector: { + type: 'custom', + pattern: 'Deploy to production', + }, + type: 'pipeline_commit_target', + }, + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseJson), + ); + }, + ), + ); + const ctx = Object.assign({}, mockContext, { + input: yaml.parse(examples[2].example).steps[0].input, + }); + await action.handler(ctx); + }); + + it('should trigger a specific pipeline definition for a commit on a branch or tag', async () => { + expect.assertions(2); + worker.use( + rest.post( + 'https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo-slug/pipelines', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Basic dTpw'); + expect(req.body).toEqual({ + target: { + commit: { + type: 'commit', + hash: 'a3c4e02c9a3755eccdc3764e6ea13facdf30f923', + }, + selector: { + type: 'custom', + pattern: 'Deploy to production', + }, + type: 'pipeline_ref_target', + ref_name: 'master', + ref_type: 'branch', + }, + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseJson), + ); + }, + ), + ); + const ctx = Object.assign({}, mockContext, { + input: yaml.parse(examples[3].example).steps[0].input, + }); + await action.handler(ctx); + }); + + it('should trigger a custom pipeline with variables', async () => { + expect.assertions(2); + worker.use( + rest.post( + 'https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo-slug/pipelines', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Basic dTpw'); + expect(req.body).toEqual({ + target: { + type: 'pipeline_ref_target', + ref_name: 'master', + ref_type: 'branch', + selector: { + type: 'custom', + pattern: 'Deploy to production', + }, + }, + variables: [ + { key: 'var1key', value: 'var1value', secured: true }, + { + key: 'var2key', + value: 'var2value', + }, + ], + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseJson), + ); + }, + ), + ); + const ctx = Object.assign({}, mockContext, { + input: yaml.parse(examples[4].example).steps[0].input, + }); + await action.handler(ctx); + }); + + it('should trigger a pull request pipeline', async () => { + expect.assertions(2); + worker.use( + rest.post( + 'https://api.bitbucket.org/2.0/repositories/test-workspace/test-repo-slug/pipelines', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Basic dTpw'); + expect(req.body).toEqual({ + target: { + type: 'pipeline_pullrequest_target', + source: 'pull-request-branch', + destination: 'master', + destination_commit: { + hash: '9f848b7', + }, + commit: { + hash: '1a372fc', + }, + pull_request: { + id: '3', + }, + selector: { + type: 'pull-requests', + pattern: '**', + }, + }, + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseJson), + ); + }, + ), + ); + const ctx = Object.assign({}, mockContext, { + input: yaml.parse(examples[5].example).steps[0].input, + }); + await action.handler(ctx); + }); +}); diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.examples.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.examples.ts new file mode 100644 index 0000000000..d6255526af --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.examples.ts @@ -0,0 +1,202 @@ +/* + * 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: 'Trigger a pipeline for a branch', + 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', + body: { + target: { + ref_type: 'branch', + type: 'pipeline_ref_target', + ref_name: 'master', + }, + }, + }, + }, + ], + }), + }, + { + description: 'Trigger a pipeline for a commit on a branch', + 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', + body: { + target: { + commit: { + type: 'commit', + hash: 'ce5b7431602f7cbba007062eeb55225c6e18e956', + }, + ref_type: 'branch', + type: 'pipeline_ref_target', + ref_name: 'master', + }, + }, + }, + }, + ], + }), + }, + { + description: 'Trigger a specific pipeline definition for a commit', + 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', + body: { + target: { + commit: { + type: 'commit', + hash: 'a3c4e02c9a3755eccdc3764e6ea13facdf30f923', + }, + selector: { + type: 'custom', + pattern: 'Deploy to production', + }, + type: 'pipeline_commit_target', + }, + }, + }, + }, + ], + }), + }, + { + description: + 'Trigger a specific pipeline definition for a commit on a branch or tag', + 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', + body: { + target: { + commit: { + type: 'commit', + hash: 'a3c4e02c9a3755eccdc3764e6ea13facdf30f923', + }, + selector: { + type: 'custom', + pattern: 'Deploy to production', + }, + type: 'pipeline_ref_target', + ref_name: 'master', + ref_type: 'branch', + }, + }, + }, + }, + ], + }), + }, + { + description: 'Trigger a custom pipeline with variables', + 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', + body: { + target: { + type: 'pipeline_ref_target', + ref_name: 'master', + ref_type: 'branch', + selector: { + type: 'custom', + pattern: 'Deploy to production', + }, + }, + variables: [ + { key: 'var1key', value: 'var1value', secured: true }, + { + key: 'var2key', + value: 'var2value', + }, + ], + }, + }, + }, + ], + }), + }, + { + description: 'Trigger a pull request pipeline', + 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', + body: { + target: { + type: 'pipeline_pullrequest_target', + source: 'pull-request-branch', + destination: 'master', + destination_commit: { + hash: '9f848b7', + }, + commit: { + hash: '1a372fc', + }, + pull_request: { + id: '3', + }, + selector: { + type: 'pull-requests', + pattern: '**', + }, + }, + }, + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.test.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.test.ts new file mode 100644 index 0000000000..dc76a79898 --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.test.ts @@ -0,0 +1,156 @@ +/* + * 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 { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { PassThrough } from 'stream'; +import { createBitbucketPipelinesRunAction } from './bitbucketCloudPipelinesRun'; +import { ConfigReader } from '@backstage/config'; +import { ScmIntegrations } from '@backstage/integration'; + +describe('bitbucket:pipelines:run', () => { + const config = new ConfigReader({ + integrations: { + bitbucketCloud: [ + { + username: 'u', + appPassword: 'p', + }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + const action = createBitbucketPipelinesRunAction({ integrations }); + const mockContext = { + input: {}, + workspacePath: 'wsp', + logger: getVoidLogger(), + logStream: new PassThrough(), + output: jest.fn(), + createTemporaryDirectory: jest.fn(), + }; + const workspace = 'test-workspace'; + const repo_slug = 'test-repo-slug'; + const responseJson = { + repository: { + links: { + html: { href: 'https://bitbucket.org/test-workspace/test-repo-slug' }, + }, + }, + build_number: 1, + }; + const worker = setupServer(); + setupRequestMockHandlers(worker); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should throw if there is no integration credentials or token provided', async () => { + const configNoCreds = new ConfigReader({ + integrations: { + bitbucketCloud: [], + }, + }); + + const integrationsNoCreds = ScmIntegrations.fromConfig(configNoCreds); + const actionNoCreds = createBitbucketPipelinesRunAction({ + integrations: integrationsNoCreds, + }); + const testContext = Object.assign({}, mockContext, { + input: { workspace, repo_slug }, + }); + await expect(actionNoCreds.handler(testContext)).rejects.toThrow( + /Authorization has not been provided for Bitbucket Cloud/, + ); + }); + + it('should call the bitbucket api for running a pipeline with integration credentials', async () => { + expect.assertions(1); + worker.use( + rest.post( + `https://api.bitbucket.org/2.0/repositories/${workspace}/${repo_slug}/pipelines`, + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Basic dTpw'); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseJson), + ); + }, + ), + ); + + const testContext = Object.assign({}, mockContext, { + input: { workspace, repo_slug }, + }); + await action.handler(testContext); + }); + + it('should call the bitbucket api for running a pipeline with input token', async () => { + expect.assertions(1); + worker.use( + rest.post( + `https://api.bitbucket.org/2.0/repositories/${workspace}/${repo_slug}/pipelines`, + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe('Bearer abc'); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseJson), + ); + }, + ), + ); + + const testContext = Object.assign({}, mockContext, { + input: { workspace, repo_slug, token: 'abc' }, + }); + await action.handler(testContext); + }); + + it('should call outputs with the correct data', async () => { + worker.use( + rest.post( + `https://api.bitbucket.org/2.0/repositories/${workspace}/${repo_slug}/pipelines`, + (_, res, ctx) => { + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json(responseJson), + ); + }, + ), + ); + + const testContext = Object.assign({}, mockContext, { + input: { workspace, repo_slug, token: 'abc' }, + }); + await action.handler(testContext); + expect(testContext.output).toHaveBeenCalledWith('buildNumber', 1); + expect(testContext.output).toHaveBeenCalledWith( + 'repoUrl', + 'https://bitbucket.org/test-workspace/test-repo-slug', + ); + expect(testContext.output).toHaveBeenCalledWith( + 'pipelinesUrl', + 'https://bitbucket.org/test-workspace/test-repo-slug/pipelines', + ); + }); +}); diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.ts new file mode 100644 index 0000000000..c44fdcc824 --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.ts @@ -0,0 +1,117 @@ +/* + * 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, { Response } from 'node-fetch'; +import * as inputProps from './inputProperties'; +import { ScmIntegrationRegistry } from '@backstage/integration'; +import { getAuthorizationHeader } from './helpers'; + +const id = 'bitbucket:pipelines:run'; +/** + * Creates a new action that triggers a run of a bitbucket pipeline + * + * @public + */ +export const createBitbucketPipelinesRunAction = (options: { + integrations: ScmIntegrationRegistry; +}) => { + const { integrations } = options; + return createTemplateAction<{ + workspace: string; + repo_slug: string; + body?: object; + token?: string; + }>({ + id, + description: 'Run a bitbucket cloud pipeline', + examples, + schema: { + input: { + type: 'object', + required: ['workspace', 'repo_slug'], + properties: { + workspace: inputProps.workspace, + repo_slug: inputProps.repo_slug, + body: inputProps.pipelinesRunBody, + token: inputProps.token, + }, + }, + output: { + type: 'object', + properties: { + buildNumber: { + title: 'Build number', + type: 'number', + }, + repoUrl: { + title: 'A URL to the pipeline repositry', + type: 'string', + }, + repoContentsUrl: { + title: 'A URL to the pipeline', + type: 'string', + }, + }, + }, + }, + supportsDryRun: false, + async handler(ctx) { + const { workspace, repo_slug, body, token } = ctx.input; + const host = 'bitbucket.org'; + const integrationConfig = integrations.bitbucketCloud.byHost(host); + + const authorization = getAuthorizationHeader( + token ? { token } : integrationConfig!.config, + ); + let response: Response; + try { + response = await fetch( + `https://api.bitbucket.org/2.0/repositories/${workspace}/${repo_slug}/pipelines`, + { + method: 'POST', + headers: { + Authorization: authorization, + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body) ?? {}, + }, + ); + } catch (e) { + throw new Error(`Unable to run pipeline, ${e}`); + } + + if (response.status !== 201) { + throw new Error( + `Unable to run pipeline, ${response.status} ${ + response.statusText + }, ${await response.text()}`, + ); + } + + const responseObject = await response.json(); + + ctx.output('buildNumber', responseObject.build_number); + ctx.output('repoUrl', responseObject.repository.links.html.href); + ctx.output( + 'pipelinesUrl', + `${responseObject.repository.links.html.href}/pipelines`, + ); + }, + }); +}; diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/helpers.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/helpers.ts new file mode 100644 index 0000000000..efa6fd64f4 --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/helpers.ts @@ -0,0 +1,38 @@ +/* + * 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. + */ + +export const getAuthorizationHeader = (config: { + username?: string; + appPassword?: string; + token?: string; +}) => { + if (config.username && config.appPassword) { + const buffer = Buffer.from( + `${config.username}:${config.appPassword}`, + 'utf8', + ); + + return `Basic ${buffer.toString('base64')}`; + } + + if (config.token) { + return `Bearer ${config.token}`; + } + + throw new Error( + `Authorization has not been provided for Bitbucket Cloud. Please add either username + appPassword to the Integrations config or a user login auth token`, + ); +}; diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/index.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/index.ts index 1093b07785..9b009255fd 100644 --- a/plugins/scaffolder-backend-module-bitbucket/src/actions/index.ts +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/index.ts @@ -17,3 +17,4 @@ export * from './bitbucket'; export * from './bitbucketCloud'; export * from './bitbucketServer'; export * from './bitbucketServerPullRequest'; +export { createBitbucketPipelinesRunAction } from './bitbucketCloudPipelinesRun'; diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/inputProperties.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/inputProperties.ts new file mode 100644 index 0000000000..d37426e982 --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/inputProperties.ts @@ -0,0 +1,155 @@ +/* + * 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. + */ + +const workspace = { + title: 'Workspace', + description: `The workspace name`, + type: 'string', +}; + +const repo_slug = { + title: 'Repository name', + description: 'The repository name', + type: 'string', +}; + +const ref_type = { + title: 'ref_type', + type: 'string', +}; + +const type = { + title: 'type', + type: 'string', +}; + +const ref_name = { + title: 'ref_name', + type: 'string', +}; +const source = { + title: 'source', + type: 'string', +}; +const destination = { + title: 'destination', + type: 'string', +}; +const hash = { + title: 'hash', + type: 'string', +}; + +const pattern = { + title: 'pattern', + type: 'string', +}; + +const id = { + title: 'id', + type: 'string', +}; + +const key = { + title: 'key', + type: 'string', +}; +const value = { + title: 'value', + type: 'string', +}; +const secured = { + title: 'secured', + type: 'boolean', +}; + +const token = { + title: 'Authentication Token', + type: 'string', + description: 'The token to use for authorization to BitBucket Cloud', +}; + +const destination_commit = { + title: 'destination_commit', + type: 'object', + properties: { + hash, + }, +}; + +const commit = { + title: 'commit', + type: 'object', + properties: { + type, + hash, + }, +}; + +const selector = { + title: 'selector', + type: 'object', + properties: { + type, + pattern, + }, +}; + +const pull_request = { + title: 'pull_request', + type: 'object', + properties: { + id, + }, +}; + +const pipelinesRunBody = { + title: 'Request Body', + description: + 'Request body properties: see Bitbucket Cloud Rest API documentation for more details', + type: 'object', + properties: { + target: { + title: 'target', + type: 'object', + properties: { + ref_type, + type, + ref_name, + source, + destination, + destination_commit, + commit, + selector, + pull_request, + }, + }, + variables: { + title: 'variables', + type: 'array', + items: { + type: 'object', + properties: { + key, + value, + secured, + }, + }, + }, + }, +}; + +export { workspace, repo_slug, pipelinesRunBody, token }; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts index dfef2d3c92..a767b3cd17 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts @@ -60,6 +60,7 @@ import { createPublishBitbucketCloudAction, createPublishBitbucketServerAction, createPublishBitbucketServerPullRequestAction, + createBitbucketPipelinesRunAction, } from '@backstage/plugin-scaffolder-backend-module-bitbucket'; import { @@ -223,6 +224,9 @@ export const createBuiltinActions = ( integrations, githubCredentialsProvider, }), + createBitbucketPipelinesRunAction({ + integrations, + }), ]; return actions as TemplateAction[];