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 index 2296f5b5e8..90ae2c08b9 100644 --- a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.examples.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.examples.test.ts @@ -47,6 +47,14 @@ describe('bitbucket:pipelines:run', () => { 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); @@ -71,7 +79,7 @@ describe('bitbucket:pipelines:run', () => { return res( ctx.status(201), ctx.set('Content-Type', 'application/json'), - ctx.json({}), + ctx.json(responseJson), ); }, ), @@ -103,7 +111,7 @@ describe('bitbucket:pipelines:run', () => { return res( ctx.status(201), ctx.set('Content-Type', 'application/json'), - ctx.json({}), + ctx.json(responseJson), ); }, ), @@ -137,7 +145,7 @@ describe('bitbucket:pipelines:run', () => { return res( ctx.status(201), ctx.set('Content-Type', 'application/json'), - ctx.json({}), + ctx.json(responseJson), ); }, ), @@ -173,7 +181,7 @@ describe('bitbucket:pipelines:run', () => { return res( ctx.status(201), ctx.set('Content-Type', 'application/json'), - ctx.json({}), + ctx.json(responseJson), ); }, ), @@ -212,7 +220,7 @@ describe('bitbucket:pipelines:run', () => { return res( ctx.status(201), ctx.set('Content-Type', 'application/json'), - ctx.json({}), + ctx.json(responseJson), ); }, ), @@ -253,7 +261,7 @@ describe('bitbucket:pipelines:run', () => { return res( ctx.status(201), ctx.set('Content-Type', 'application/json'), - ctx.json({}), + ctx.json(responseJson), ); }, ), diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.test.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.test.ts index 97cdc05d2f..dc76a79898 100644 --- a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.test.ts @@ -45,6 +45,16 @@ describe('bitbucket:pipelines:run', () => { 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); @@ -52,10 +62,27 @@ describe('bitbucket:pipelines:run', () => { jest.clearAllMocks(); }); - it('should call the bitbucket api for running a pipeline', async () => { + 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); - const workspace = 'test-workspace'; - const repo_slug = 'test-repo-slug'; worker.use( rest.post( `https://api.bitbucket.org/2.0/repositories/${workspace}/${repo_slug}/pipelines`, @@ -64,7 +91,7 @@ describe('bitbucket:pipelines:run', () => { return res( ctx.status(201), ctx.set('Content-Type', 'application/json'), - ctx.json({}), + ctx.json(responseJson), ); }, ), @@ -75,4 +102,55 @@ describe('bitbucket:pipelines:run', () => { }); 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 index a8ad93ce9b..594da76971 100644 --- a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.ts +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucketCloudPipelinesRun.ts @@ -16,10 +16,9 @@ import { examples } from './bitbucketCloudPipelinesRun.examples'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; -import fetch from 'node-fetch'; +import fetch, { Response } from 'node-fetch'; import * as inputProps from './inputProperties'; import { ScmIntegrationRegistry } from '@backstage/integration'; -import { InputError } from '@backstage/errors'; import { getAuthorizationHeader } from './helpers'; const id = 'bitbucket:pipelines:run'; @@ -44,7 +43,7 @@ export const createBitbucketPipelinesRunAction = (options: { schema: { input: { type: 'object', - required: ['workspace', 'repo_url'], + required: ['workspace', 'repo_slug'], properties: { workspace: inputProps.workspace, repo_slug: inputProps.repo_slug, @@ -52,24 +51,36 @@ export const createBitbucketPipelinesRunAction = (options: { 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); - if (!integrationConfig) { - throw new InputError( - `No matching integration configuration for host ${host}, please check your integrations config`, - ); - } const authorization = getAuthorizationHeader( - token ? { token } : integrationConfig.config, + token ? { token } : integrationConfig!.config, ); - + let response: Response; try { - const response = await fetch( + response = await fetch( `https://api.bitbucket.org/2.0/repositories/${workspace}/${repo_slug}/pipelines`, { method: 'POST', @@ -81,10 +92,26 @@ export const createBitbucketPipelinesRunAction = (options: { body: JSON.stringify(body) ?? {}, }, ); - const data = await response.json(); } 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/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[];