Merge pull request #21959 from mattprinold/feat/21365-scaffolder-custom-action-bitbucket-run-pipeline
Feat/21365 scaffolder custom action bitbucket run pipeline
This commit is contained in:
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
+274
@@ -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);
|
||||
});
|
||||
});
|
||||
+202
@@ -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: '**',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
];
|
||||
+156
@@ -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',
|
||||
);
|
||||
});
|
||||
});
|
||||
+117
@@ -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`,
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -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`,
|
||||
);
|
||||
};
|
||||
@@ -17,3 +17,4 @@ export * from './bitbucket';
|
||||
export * from './bitbucketCloud';
|
||||
export * from './bitbucketServer';
|
||||
export * from './bitbucketServerPullRequest';
|
||||
export { createBitbucketPipelinesRunAction } from './bitbucketCloudPipelinesRun';
|
||||
|
||||
@@ -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 };
|
||||
@@ -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[];
|
||||
|
||||
Reference in New Issue
Block a user