From c56a279b5011f964ae5eef11b5769e2593c1a835 Mon Sep 17 00:00:00 2001 From: "liad.shachoach" Date: Fri, 10 Jan 2025 16:20:40 +0200 Subject: [PATCH 01/11] Adding scaffolder action bitbucketCloud:branchRestriction:create to allow users to create bitbucket cloud branch restrictions via Template resources Signed-off-by: liad.shachoach --- .changeset/chilled-rocks-rule.md | 5 + ...ketCloudBranchRestriction.examples.test.ts | 162 ++++++++++++++ ...itbucketCloudBranchRestriction.examples.ts | 115 ++++++++++ .../bitbucketCloudBranchRestriction.test.ts | 100 +++++++++ .../bitbucketCloudBranchRestriction.ts | 209 ++++++++++++++++++ .../src/actions/index.ts | 1 + .../src/actions/inputProperties.ts | 97 ++++++++ .../src/module.ts | 4 + 8 files changed, 693 insertions(+) create mode 100644 .changeset/chilled-rocks-rule.md create mode 100644 plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.examples.test.ts create mode 100644 plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.examples.ts create mode 100644 plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.test.ts create mode 100644 plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts diff --git a/.changeset/chilled-rocks-rule.md b/.changeset/chilled-rocks-rule.md new file mode 100644 index 0000000000..bb2aad2a47 --- /dev/null +++ b/.changeset/chilled-rocks-rule.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch +--- + +Added bitbucketCloud:branchRestriction:create to allow users to create bitbucket cloud branch restrictions in templates diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.examples.test.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.examples.test.ts new file mode 100644 index 0000000000..4897d6acbf --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.examples.test.ts @@ -0,0 +1,162 @@ +/* + * Copyright 2023 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 { createBitbucketCloudBranchRestrictionAction } from './bitbucketCloudBranchRestriction'; +import { ScmIntegrations } from '@backstage/integration'; +import { ConfigReader } from '@backstage/config'; +import yaml from 'yaml'; +import { examples } from './bitbucketCloudBranchRestriction.examples'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; + +describe('bitbucketCloud:branchRestriction:create', () => { + const config = new ConfigReader({ + integrations: { + bitbucketCloud: [ + { + username: 'x-token-auth', + appPassword: 'your-default-auth-token', + }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + const action = createBitbucketCloudBranchRestrictionAction({ integrations }); + const mockContext = createMockActionContext({ + input: { + repoUrl: + 'bitbucket.org?workspace=workspace&project=project&repo=repo&project=project', + }, + }); + + beforeEach(() => { + global.fetch = jest.fn().mockImplementation(() => + Promise.resolve({ + status: 201, + json: () => + Promise.resolve({ + status: 201, + }), + }), + ); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should restrict push to the main branch, except for the defined uuids, and the admins group', async () => { + const input = yaml.parse(examples[0].example).steps[0].input; + const ctx = Object.assign({}, mockContext, { input }); + await action.handler(ctx); + + expect(global.fetch).toHaveBeenCalledWith( + 'https://api.bitbucket.org/2.0/repositories/workspace/repo/branch-restrictions', + { + body: JSON.stringify({ + branch_match_kind: 'branching_model', + users: [{ uuid: '{a-b-c-d}' }, { uuid: '{e-f-g-h}' }], + groups: [{ slug: 'admins' }], + kind: 'push', + branch_type: 'development', + }), + method: 'POST', + headers: { + Accept: 'application/json', + Authorization: + 'Basic eC10b2tlbi1hdXRoOnlvdXItZGVmYXVsdC1hdXRoLXRva2Vu', + 'Content-Type': 'application/json', + }, + }, + ); + }); + + it('should restrict push to the main branch, except for the admins group', async () => { + const input = yaml.parse(examples[1].example).steps[0].input; + const ctx = Object.assign({}, mockContext, { input }); + await action.handler(ctx); + + expect(global.fetch).toHaveBeenCalledWith( + 'https://api.bitbucket.org/2.0/repositories/workspace/repo/branch-restrictions', + { + body: JSON.stringify({ + branch_match_kind: 'branching_model', + users: [], + groups: [{ slug: 'admins' }], + kind: 'push', + branch_type: 'development', + }), + method: 'POST', + headers: { + Accept: 'application/json', + Authorization: + 'Basic eC10b2tlbi1hdXRoOnlvdXItZGVmYXVsdC1hdXRoLXRva2Vu', + 'Content-Type': 'application/json', + }, + }, + ); + }); + + it('should require passing builds to merge to branches matching a pattern test-feature/*', async () => { + const input = yaml.parse(examples[2].example).steps[0].input; + const ctx = Object.assign({}, mockContext, { input }); + await action.handler(ctx); + + expect(global.fetch).toHaveBeenCalledWith( + 'https://api.bitbucket.org/2.0/repositories/workspace/repo/branch-restrictions', + { + body: JSON.stringify({ + branch_match_kind: 'glob', + kind: 'require_passing_builds_to_merge', + value: 1, + pattern: 'test-feature/*', + }), + method: 'POST', + headers: { + Accept: 'application/json', + Authorization: + 'Basic eC10b2tlbi1hdXRoOnlvdXItZGVmYXVsdC1hdXRoLXRva2Vu', + 'Content-Type': 'application/json', + }, + }, + ); + }); + + it('should require approvals to merge to branches matching a pattern test-feature/*', async () => { + const input = yaml.parse(examples[3].example).steps[0].input; + const ctx = Object.assign({}, mockContext, { input }); + await action.handler(ctx); + + expect(global.fetch).toHaveBeenCalledWith( + 'https://api.bitbucket.org/2.0/repositories/workspace/repo/branch-restrictions', + { + body: JSON.stringify({ + branch_match_kind: 'glob', + kind: 'require_approvals_to_merge', + value: 1, + pattern: 'test-feature/*', + }), + method: 'POST', + headers: { + Accept: 'application/json', + Authorization: + 'Basic eC10b2tlbi1hdXRoOnlvdXItZGVmYXVsdC1hdXRoLXRva2Vu', + 'Content-Type': 'application/json', + }, + }, + ); + }); +}); diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.examples.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.examples.ts new file mode 100644 index 0000000000..bfe24d1e70 --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.examples.ts @@ -0,0 +1,115 @@ +/* + * Copyright 2023 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: + 'restrict push to the main branch, except for alice, bob, and the admins group', + example: yaml.stringify({ + steps: [ + { + id: 'createBranchRestriction', + action: 'bitbucketCloud:branchRestriction:create', + name: 'Create Bitbucket Cloud branch restriction', + input: { + repoUrl: + 'bitbucket.org?repo=repo&workspace=workspace&project=project', + kind: 'push', + users: [ + { + uuid: '{a-b-c-d}', + }, + { + uuid: '{e-f-g-h}', + }, + ], + groups: [ + { + slug: 'admins', + }, + ], + }, + }, + ], + }), + }, + { + description: + 'restrict push to the main branch, except for the admins group', + example: yaml.stringify({ + steps: [ + { + id: 'restrictPushToFeatureBranches', + action: 'bitbucketCloud:branchRestriction:create', + name: 'Create Bitbucket Cloud branch restriction by branch type', + input: { + repoUrl: + 'bitbucket.org?repo=repo&workspace=workspace&project=project', + kind: 'push', + groups: [ + { + slug: 'admins', + }, + ], + }, + }, + ], + }), + }, + { + description: + 'require passing builds to merge to branches matching a pattern test-feature/*', + example: yaml.stringify({ + steps: [ + { + id: 'requirePassingBuildsToMergeByPattern', + action: 'bitbucketCloud:branchRestriction:create', + name: 'Create Bitbucket Cloud require passing build to merge to branches matching a pattern', + input: { + repoUrl: + 'bitbucket.org?repo=repo&workspace=workspace&project=project', + kind: 'require_passing_builds_to_merge', + branchMatchKind: 'glob', + pattern: 'test-feature/*', + }, + }, + ], + }), + }, + { + description: + 'require approvals to merge to branches matching a pattern test-feature/*', + example: yaml.stringify({ + steps: [ + { + id: 'requireApprovalsToMergeByPattern', + action: 'bitbucketCloud:branchRestriction:create', + name: 'Create Bitbucket Cloud require approvals to merge branch restriction', + input: { + repoUrl: + 'bitbucket.org?repo=repo&workspace=workspace&project=project', + kind: 'require_approvals_to_merge', + branchMatchKind: 'glob', + pattern: 'test-feature/*', + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.test.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.test.ts new file mode 100644 index 0000000000..f16a7f6f70 --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.test.ts @@ -0,0 +1,100 @@ +/* + * 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 { createBitbucketCloudBranchRestrictionAction } from './bitbucketCloudBranchRestriction'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; +import { ScmIntegrations } from '@backstage/integration'; +import { ConfigReader } from '@backstage/config'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; + +describe('bitbucketCloud:branchRestriction:create', () => { + const config = new ConfigReader({ + integrations: { + bitbucketCloud: [ + { + username: 'u', + appPassword: 'p', + }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + const action = createBitbucketCloudBranchRestrictionAction({ integrations }); + const mockContext = createMockActionContext({ + input: { + repoUrl: 'bitbucket.org?workspace=workspace&project=project&repo=repo', + kind: 'push', + }, + }); + const server = setupServer(); + registerMswTestHooks(server); + + it('should work if the token is provided through ctx.input', async () => { + expect.assertions(2); + const token = 'user-token'; + server.use( + rest.post( + 'https://api.bitbucket.org/2.0/repositories/workspace/repo/branch-restrictions', + (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe(`Bearer ${token}`); + req.json().then(data => { + expect(data).toEqual({ + branch_match_kind: 'branching_model', + branch_type: 'development', + users: [], + groups: [], + kind: 'push', + }); + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }, + ), + ); + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + token: token, + }, + }); + }); + + it('should return correct outputs', async () => { + server.use( + rest.post( + 'https://api.bitbucket.org/2.0/repositories/workspace/repo/branch-restrictions', + (_, res, ctx) => + res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ), + ), + ); + + await action.handler(mockContext); + + expect(mockContext.output).toHaveBeenCalledWith('statusCode', 201); + expect(mockContext.output).toHaveBeenCalledWith('json', '{}'); + }); +}); diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts new file mode 100644 index 0000000000..d1628bcf12 --- /dev/null +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts @@ -0,0 +1,209 @@ +/* + * Copyright 2025 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 { ScmIntegrationRegistry } from '@backstage/integration'; +import { + createTemplateAction, + parseRepoUrl, +} from '@backstage/plugin-scaffolder-node'; +import { InputError } from '@backstage/errors'; +import { getAuthorizationHeader } from './helpers'; +import * as inputProps from './inputProperties'; +import { examples } from './bitbucketCloudBranchRestriction.examples'; + +const createBitbucketCloudBranchRestriction = async (opts: { + workspace: string; + repo: string; + kind: string; + branchMatchKind?: string; + branchType?: string; + pattern?: string; + value?: number; + users?: Array; + groups?: Array; + authorization: string; + apiBaseUrl: string; +}) => { + const { + workspace, + repo, + kind, + branchMatchKind, + branchType, + pattern, + value, + users, + groups, + authorization, + apiBaseUrl, + } = opts; + + const body = new Map(); + body.set('branch_match_kind', branchMatchKind || 'branching_model'); + if (kind in ['push', 'restrict_merges']) { + body.set('users', kind in ['push', 'restrict_merges'] ? users || [] : null); + body.set( + 'groups', + kind in ['push', 'restrict_merges'] ? groups || [] : null, + ); + } + body.set('kind', kind); + if ( + kind === 'require_approvals_to_merge' || + kind === 'require_default_reviewer_approvals_to_merge' || + kind === 'require_commits_behind' || + kind === 'require_passing_builds_to_merge' + ) { + body.set('value', value || 1); + } + if (branchMatchKind === 'glob') { + body.set('pattern', pattern || ''); + } + if (branchMatchKind === 'branching_model') { + body.set('branch_type', branchType || 'development'); + } + + const options: RequestInit = { + method: 'POST', + body: JSON.stringify(Object.fromEntries(body)), + headers: { + Authorization: authorization, + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + }; + + let response: Response; + try { + response = await fetch( + `${apiBaseUrl}/repositories/${workspace}/${repo}/branch-restrictions`, + options, + ); + } catch (e) { + throw new Error( + `Unable to set branch restrictions for the repository, ${e}`, + ); + } + + if (response.status !== 201) { + throw new Error( + `Unable to set branch restrictions for the repository, ${ + response.status + } ${response.statusText}, ${await response.text()}`, + ); + } + return response; +}; + +export function createBitbucketCloudBranchRestrictionAction(options: { + integrations: ScmIntegrationRegistry; +}) { + const { integrations } = options; + return createTemplateAction<{ + repoUrl: string; + kind: string; + branchMatchKind?: string; + branchType?: string; + pattern?: string; + value?: number; + users?: Array; + groups?: Array; + token?: string; + }>({ + id: 'bitbucketCloud:branchRestriction:create', + examples, + description: + 'Creates branch restrictions for a Bitbucket Cloud repository.', + schema: { + input: { + type: 'object', + required: ['repoUrl', 'kind'], + properties: { + repoUrl: inputProps.repoUrl, + kind: inputProps.restriction.kind, + branchMatchKind: inputProps.restriction.branchMatchKind, + branchType: inputProps.restriction.branchType, + pattern: inputProps.restriction.pattern, + value: inputProps.restriction.value, + users: inputProps.restriction.users, + groups: inputProps.restriction.groups, + token: inputProps.token, + }, + }, + output: { + type: 'object', + properties: { + json: { + title: 'The response from bitbucket cloud', + type: 'string', + }, + statusCode: { + title: 'The status code of the response', + type: 'number', + }, + }, + }, + }, + async handler(ctx) { + const { + repoUrl, + kind, + branchMatchKind = 'branching_model', + branchType = 'development', + pattern, + value, + users, + groups, + } = ctx.input; + + const { workspace, repo, host } = parseRepoUrl(repoUrl, integrations); + + if (!workspace) { + throw new InputError( + `Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing workspace`, + ); + } + + 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( + ctx.input.token ? { token: ctx.input.token } : integrationConfig.config, + ); + + const apiBaseUrl = integrationConfig.config.apiBaseUrl; + + const response = await createBitbucketCloudBranchRestriction({ + workspace: workspace, + repo, + kind: kind, + branchMatchKind: branchMatchKind, + branchType: branchType, + pattern: pattern, + value: value, + users: users, + groups: groups, + authorization, + apiBaseUrl, + }); + ctx.output('statusCode', response.status); + ctx.output('json', JSON.stringify(await response.json())); + }, + }); +} diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/index.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/index.ts index c4030c0b52..986df299d2 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/index.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/index.ts @@ -16,3 +16,4 @@ export * from './bitbucketCloud'; export { createBitbucketPipelinesRunAction } from './bitbucketCloudPipelinesRun'; export * from './bitbucketCloudPullRequest'; +export * from './bitbucketCloudBranchRestriction'; diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/inputProperties.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/inputProperties.ts index d37426e982..cff1457463 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/inputProperties.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/inputProperties.ts @@ -14,6 +14,12 @@ * limitations under the License. */ +const repoUrl = { + title: 'Repository Location', + description: `Accepts the format 'bitbucket.org?repo=reponame&workspace=workspace&project=project' where 'reponame' is the new repository name`, + type: 'string', +}; + const workspace = { title: 'Workspace', description: `The workspace name`, @@ -152,4 +158,95 @@ const pipelinesRunBody = { }, }; +const restriction = { + kind: { + title: 'kind', + description: 'The kind of restriction.', + type: 'string', + enum: [ + 'push', + 'force', + 'delete', + 'restrict_merges', + 'require_tasks_to_be_completed', + 'require_approvals_to_merge', + 'require_default_reviewer_approvals_to_merge', + 'require_no_changes_requested', + 'require_passing_builds_to_merge', + 'require_commits_behind', + 'reset_pullrequest_approvals_on_change', + 'smart_reset_pullrequest_approvals', + 'reset_pullrequest_changes_requested_on_change', + 'require_all_dependencies_merged', + 'enforce_merge_checks', + 'allow_auto_merge_when_builds_pass', + ], + }, + branchMatchKind: { + title: 'branch_match_kind', + description: 'The branch match kind.', + type: 'string', + enum: ['glob', 'branching_model'], + }, + branchType: { + title: 'branch_type', + description: + 'The branch type. When branchMatchKind is set to branching_model, this field is required.', + type: 'string', + enum: [ + 'feature', + 'bugfix', + 'release', + 'hotfix', + 'development', + 'production', + ], + }, + pattern: { + title: 'pattern', + description: + 'The pattern to match branches against. This field is required when branchMatchKind is set to glob.', + type: 'string', + }, + value: { + title: 'value', + description: + 'The value of the restriction. This field is required when kind is one of require_approvals_to_merge / require_default_reviewer_approvals_to_merge / require_passing_builds_to_merge / require_commits_behind.', + type: 'number', + }, + users: { + title: 'users', + description: + 'Names of users that can bypass the push / restrict_merges restriction kind. For any other kind, this field will be ignored.', + type: 'array', + items: { + type: 'object', + properties: { + uuid: { + title: 'uuid', + description: 'The UUID of the user in the format "{a-b-c-d}".', + type: 'string', + }, + }, + }, + }, + groups: { + title: 'groups', + description: + 'Names of groups that can bypass the push / restrict_merges restriction kind. For any other kind, this field will be ignored.', + type: 'array', + items: { + type: 'object', + properties: { + slug: { + title: 'slug', + description: 'The name of the group.', + type: 'string', + }, + }, + }, + }, +}; + export { workspace, repo_slug, pipelinesRunBody, token }; +export { repoUrl, restriction }; diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/module.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/module.ts index c0f922ca08..3a9871f39c 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/module.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/module.ts @@ -25,6 +25,7 @@ import { createBitbucketPipelinesRunAction, createPublishBitbucketCloudAction, createPublishBitbucketCloudPullRequestAction, + createBitbucketCloudBranchRestrictionAction, } from './actions'; import { ScmIntegrations } from '@backstage/integration'; import { handleAutocompleteRequest } from './autocomplete/autocomplete'; @@ -53,6 +54,9 @@ export const bitbucketCloudModule = createBackendModule({ integrations, config, }), + createBitbucketCloudBranchRestrictionAction({ + integrations, + }), ); autocomplete.addAutocompleteProvider({ From 255886748245d8d8ef60d05b9ac69604173f8026 Mon Sep 17 00:00:00 2001 From: "liad.shachoach" Date: Fri, 10 Jan 2025 16:44:16 +0200 Subject: [PATCH 02/11] updating report.api.md and marking createBitbucketCloudBranchRestrictionAction as public Signed-off-by: liad.shachoach --- .../src/actions/bitbucketCloudBranchRestriction.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts index d1628bcf12..fc997fd7b8 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts @@ -107,6 +107,10 @@ const createBitbucketCloudBranchRestriction = async (opts: { return response; }; +/** + * Creates a new action that adds a branch restriction to a Bitbucket Cloud repository. + * @public + */ export function createBitbucketCloudBranchRestrictionAction(options: { integrations: ScmIntegrationRegistry; }) { From 39f9b63ffb99cf0d6b81bd77b975a9d5ddb09bbe Mon Sep 17 00:00:00 2001 From: "liad.shachoach" Date: Fri, 10 Jan 2025 16:55:00 +0200 Subject: [PATCH 03/11] updating report.api.md Signed-off-by: liad.shachoach --- .../report.api.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md b/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md index b853023b07..67c05a4ea5 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md @@ -13,6 +13,24 @@ import { TemplateAction } from '@backstage/plugin-scaffolder-node'; const bitbucketCloudModule: BackendFeature; export default bitbucketCloudModule; +// @public +export function createBitbucketCloudBranchRestrictionAction(options: { + integrations: ScmIntegrationRegistry; +}): TemplateAction< + { + repoUrl: string; + kind: string; + branchMatchKind?: string | undefined; + branchType?: string | undefined; + pattern?: string | undefined; + value?: number | undefined; + users?: object[] | undefined; + groups?: object[] | undefined; + token?: string | undefined; + }, + JsonObject +>; + // @public export const createBitbucketPipelinesRunAction: (options: { integrations: ScmIntegrationRegistry; From d93b1abed0f3c649210bc4b2c5c39e249bc27790 Mon Sep 17 00:00:00 2001 From: "liad.shachoach" Date: Fri, 10 Jan 2025 17:12:09 +0200 Subject: [PATCH 04/11] validating that the fetch function is called with the correct body, excluding the headers to avoid exposing secrets in the tests Signed-off-by: liad.shachoach --- ...ketCloudBranchRestriction.examples.test.ts | 40 ++++--------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.examples.test.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.examples.test.ts index 4897d6acbf..0051ee511a 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.examples.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.examples.test.ts @@ -65,7 +65,7 @@ describe('bitbucketCloud:branchRestriction:create', () => { expect(global.fetch).toHaveBeenCalledWith( 'https://api.bitbucket.org/2.0/repositories/workspace/repo/branch-restrictions', - { + expect.objectContaining({ body: JSON.stringify({ branch_match_kind: 'branching_model', users: [{ uuid: '{a-b-c-d}' }, { uuid: '{e-f-g-h}' }], @@ -74,13 +74,7 @@ describe('bitbucketCloud:branchRestriction:create', () => { branch_type: 'development', }), method: 'POST', - headers: { - Accept: 'application/json', - Authorization: - 'Basic eC10b2tlbi1hdXRoOnlvdXItZGVmYXVsdC1hdXRoLXRva2Vu', - 'Content-Type': 'application/json', - }, - }, + }), ); }); @@ -91,7 +85,7 @@ describe('bitbucketCloud:branchRestriction:create', () => { expect(global.fetch).toHaveBeenCalledWith( 'https://api.bitbucket.org/2.0/repositories/workspace/repo/branch-restrictions', - { + expect.objectContaining({ body: JSON.stringify({ branch_match_kind: 'branching_model', users: [], @@ -100,13 +94,7 @@ describe('bitbucketCloud:branchRestriction:create', () => { branch_type: 'development', }), method: 'POST', - headers: { - Accept: 'application/json', - Authorization: - 'Basic eC10b2tlbi1hdXRoOnlvdXItZGVmYXVsdC1hdXRoLXRva2Vu', - 'Content-Type': 'application/json', - }, - }, + }), ); }); @@ -117,7 +105,7 @@ describe('bitbucketCloud:branchRestriction:create', () => { expect(global.fetch).toHaveBeenCalledWith( 'https://api.bitbucket.org/2.0/repositories/workspace/repo/branch-restrictions', - { + expect.objectContaining({ body: JSON.stringify({ branch_match_kind: 'glob', kind: 'require_passing_builds_to_merge', @@ -125,13 +113,7 @@ describe('bitbucketCloud:branchRestriction:create', () => { pattern: 'test-feature/*', }), method: 'POST', - headers: { - Accept: 'application/json', - Authorization: - 'Basic eC10b2tlbi1hdXRoOnlvdXItZGVmYXVsdC1hdXRoLXRva2Vu', - 'Content-Type': 'application/json', - }, - }, + }), ); }); @@ -142,7 +124,7 @@ describe('bitbucketCloud:branchRestriction:create', () => { expect(global.fetch).toHaveBeenCalledWith( 'https://api.bitbucket.org/2.0/repositories/workspace/repo/branch-restrictions', - { + expect.objectContaining({ body: JSON.stringify({ branch_match_kind: 'glob', kind: 'require_approvals_to_merge', @@ -150,13 +132,7 @@ describe('bitbucketCloud:branchRestriction:create', () => { pattern: 'test-feature/*', }), method: 'POST', - headers: { - Accept: 'application/json', - Authorization: - 'Basic eC10b2tlbi1hdXRoOnlvdXItZGVmYXVsdC1hdXRoLXRva2Vu', - 'Content-Type': 'application/json', - }, - }, + }), ); }); }); From f51cc4b333bb4ae7a001e33cc793beca7428fb04 Mon Sep 17 00:00:00 2001 From: "liad.shachoach" Date: Tue, 14 Jan 2025 10:21:16 +0200 Subject: [PATCH 05/11] removing function export from report.api.md and updating changeset Signed-off-by: liad.shachoach --- .changeset/chilled-rocks-rule.md | 2 +- .../report.api.md | 158 +++++++++++++----- 2 files changed, 117 insertions(+), 43 deletions(-) diff --git a/.changeset/chilled-rocks-rule.md b/.changeset/chilled-rocks-rule.md index bb2aad2a47..1f8439462c 100644 --- a/.changeset/chilled-rocks-rule.md +++ b/.changeset/chilled-rocks-rule.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch --- -Added bitbucketCloud:branchRestriction:create to allow users to create bitbucket cloud branch restrictions in templates +Added `bitbucketCloud:branchRestriction:create` to allow users to create bitbucket cloud branch restrictions in templates diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md b/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md index 67c05a4ea5..34bb643d31 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md @@ -1,13 +1,17 @@ -## API Report File for "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud" +## API Report File for "@backstage/plugin-user-settings" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { BackendFeature } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; -import { JsonObject } from '@backstage/types'; -import { ScmIntegrationRegistry } from '@backstage/integration'; -import { TemplateAction } from '@backstage/plugin-scaffolder-node'; +import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; +import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; +import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; +import { ExtensionInput } from '@backstage/frontend-plugin-api'; +import { FrontendPlugin } from '@backstage/frontend-plugin-api'; +import { IconComponent } from '@backstage/core-plugin-api'; +import { default as React_2 } from 'react'; +import { RouteRef } from '@backstage/frontend-plugin-api'; +import { TranslationRef } from '@backstage/core-plugin-api/alpha'; // @public const bitbucketCloudModule: BackendFeature; @@ -36,46 +40,116 @@ export const createBitbucketPipelinesRunAction: (options: { integrations: ScmIntegrationRegistry; }) => TemplateAction< { - workspace: string; - repo_slug: string; - body?: object | undefined; - token?: string | undefined; + root: RouteRef; }, - JsonObject ->; - -// @public -export function createPublishBitbucketCloudAction(options: { - integrations: ScmIntegrationRegistry; - config: Config; -}): TemplateAction< + {}, { - repoUrl: string; - description?: string | undefined; - defaultBranch?: string | undefined; - repoVisibility?: 'private' | 'public' | undefined; - gitCommitMessage?: string | undefined; - sourcePath?: string | undefined; - token?: string | undefined; - }, - JsonObject + 'nav-item:user-settings': ExtensionDefinition<{ + kind: 'nav-item'; + name: undefined; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >; + inputs: {}; + params: { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }; + }>; + 'page:user-settings': ExtensionDefinition<{ + config: { + path: string | undefined; + }; + configInput: { + path?: string | undefined; + }; + output: + | ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + > + | ConfigurableExtensionDataRef + | ConfigurableExtensionDataRef< + RouteRef, + 'core.routing.ref', + { + optional: true; + } + >; + inputs: { + providerSettings: ExtensionInput< + ConfigurableExtensionDataRef< + React_2.JSX.Element, + 'core.reactElement', + {} + >, + { + singleton: true; + optional: true; + } + >; + }; + kind: 'page'; + name: undefined; + params: { + defaultPath: string; + loader: () => Promise; + routeRef?: RouteRef | undefined; + }; + }>; + } >; +export default _default; -// @public -export function createPublishBitbucketCloudPullRequestAction(options: { - integrations: ScmIntegrationRegistry; - config: Config; -}): TemplateAction< - { - repoUrl: string; +// @alpha (undocumented) +export const settingsNavItem: ExtensionDefinition<{ + kind: 'nav-item'; + name: undefined; + config: {}; + configInput: {}; + output: ConfigurableExtensionDataRef< + { + title: string; + icon: IconComponent; + routeRef: RouteRef; + }, + 'core.nav-item.target', + {} + >; + inputs: {}; + params: { title: string; - description?: string | undefined; - targetBranch?: string | undefined; - sourceBranch: string; - token?: string | undefined; - gitAuthorName?: string | undefined; - gitAuthorEmail?: string | undefined; - }, - JsonObject + icon: IconComponent; + routeRef: RouteRef; + }; +}>; + +// @alpha (undocumented) +export const userSettingsTranslationRef: TranslationRef< + 'user-settings', + { + readonly 'languageToggle.description': 'Change the language'; + readonly 'languageToggle.select': 'Select language {{language}}'; + readonly 'languageToggle.title': 'Language'; + readonly 'themeToggle.description': 'Change the theme mode'; + readonly 'themeToggle.select': 'Select theme {{theme}}'; + readonly 'themeToggle.title': 'Theme'; + readonly 'themeToggle.names.light': 'Light'; + readonly 'themeToggle.names.dark': 'Dark'; + readonly 'themeToggle.names.auto': 'Auto'; + readonly 'themeToggle.selectAuto': 'Select Auto Theme'; + } >; + +// (No @packageDocumentation comment for this package) ``` From 75334075e706acaad21ac2200a9d093eb419714a Mon Sep 17 00:00:00 2001 From: "liad.shachoach" Date: Tue, 14 Jan 2025 11:08:24 +0200 Subject: [PATCH 06/11] updating report.api.md to pass CI Signed-off-by: liad.shachoach --- .../report.api.md | 164 +++++------------- 1 file changed, 45 insertions(+), 119 deletions(-) diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md b/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md index 34bb643d31..931658328f 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md @@ -1,17 +1,13 @@ -## API Report File for "@backstage/plugin-user-settings" +## API Report File for "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud" > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts -import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; -import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; -import { ExtensionDefinition } from '@backstage/frontend-plugin-api'; -import { ExtensionInput } from '@backstage/frontend-plugin-api'; -import { FrontendPlugin } from '@backstage/frontend-plugin-api'; -import { IconComponent } from '@backstage/core-plugin-api'; -import { default as React_2 } from 'react'; -import { RouteRef } from '@backstage/frontend-plugin-api'; -import { TranslationRef } from '@backstage/core-plugin-api/alpha'; +import { BackendFeature } from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; +import { JsonObject } from '@backstage/types'; +import { ScmIntegrationRegistry } from '@backstage/integration'; +import { TemplateAction } from '@backstage/plugin-scaffolder-node'; // @public const bitbucketCloudModule: BackendFeature; @@ -28,8 +24,8 @@ export function createBitbucketCloudBranchRestrictionAction(options: { branchType?: string | undefined; pattern?: string | undefined; value?: number | undefined; - users?: object[] | undefined; - groups?: object[] | undefined; + users?: Array; + groups?: Array; token?: string | undefined; }, JsonObject @@ -40,116 +36,46 @@ export const createBitbucketPipelinesRunAction: (options: { integrations: ScmIntegrationRegistry; }) => TemplateAction< { - root: RouteRef; + workspace: string; + repo_slug: string; + body?: object | undefined; + token?: string | undefined; }, - {}, - { - 'nav-item:user-settings': ExtensionDefinition<{ - kind: 'nav-item'; - name: undefined; - config: {}; - configInput: {}; - output: ConfigurableExtensionDataRef< - { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }, - 'core.nav-item.target', - {} - >; - inputs: {}; - params: { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }; - }>; - 'page:user-settings': ExtensionDefinition<{ - config: { - path: string | undefined; - }; - configInput: { - path?: string | undefined; - }; - output: - | ConfigurableExtensionDataRef< - React_2.JSX.Element, - 'core.reactElement', - {} - > - | ConfigurableExtensionDataRef - | ConfigurableExtensionDataRef< - RouteRef, - 'core.routing.ref', - { - optional: true; - } - >; - inputs: { - providerSettings: ExtensionInput< - ConfigurableExtensionDataRef< - React_2.JSX.Element, - 'core.reactElement', - {} - >, - { - singleton: true; - optional: true; - } - >; - }; - kind: 'page'; - name: undefined; - params: { - defaultPath: string; - loader: () => Promise; - routeRef?: RouteRef | undefined; - }; - }>; - } + JsonObject >; -export default _default; -// @alpha (undocumented) -export const settingsNavItem: ExtensionDefinition<{ - kind: 'nav-item'; - name: undefined; - config: {}; - configInput: {}; - output: ConfigurableExtensionDataRef< - { - title: string; - icon: IconComponent; - routeRef: RouteRef; - }, - 'core.nav-item.target', - {} - >; - inputs: {}; - params: { +// @public +export function createPublishBitbucketCloudAction(options: { + integrations: ScmIntegrationRegistry; + config: Config; +}): TemplateAction< + { + repoUrl: string; + description?: string | undefined; + defaultBranch?: string | undefined; + repoVisibility?: 'private' | 'public' | undefined; + gitCommitMessage?: string | undefined; + sourcePath?: string | undefined; + token?: string | undefined; + }, + JsonObject +>; + +// @public +export function createPublishBitbucketCloudPullRequestAction(options: { + integrations: ScmIntegrationRegistry; + config: Config; +}): TemplateAction< + { + repoUrl: string; title: string; - icon: IconComponent; - routeRef: RouteRef; - }; -}>; - -// @alpha (undocumented) -export const userSettingsTranslationRef: TranslationRef< - 'user-settings', - { - readonly 'languageToggle.description': 'Change the language'; - readonly 'languageToggle.select': 'Select language {{language}}'; - readonly 'languageToggle.title': 'Language'; - readonly 'themeToggle.description': 'Change the theme mode'; - readonly 'themeToggle.select': 'Select theme {{theme}}'; - readonly 'themeToggle.title': 'Theme'; - readonly 'themeToggle.names.light': 'Light'; - readonly 'themeToggle.names.dark': 'Dark'; - readonly 'themeToggle.names.auto': 'Auto'; - readonly 'themeToggle.selectAuto': 'Select Auto Theme'; - } + description?: string | undefined; + targetBranch?: string | undefined; + sourceBranch: string; + token?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; + }, + JsonObject >; - -// (No @packageDocumentation comment for this package) ``` From 9b13d54ad73ea3d72b47d24d7f1b7c2b60204927 Mon Sep 17 00:00:00 2001 From: "liad.shachoach" Date: Tue, 14 Jan 2025 11:22:52 +0200 Subject: [PATCH 07/11] updating report.api.md to pass CI Signed-off-by: liad.shachoach --- .../scaffolder-backend-module-bitbucket-cloud/report.api.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md b/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md index 931658328f..67c05a4ea5 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md @@ -24,8 +24,8 @@ export function createBitbucketCloudBranchRestrictionAction(options: { branchType?: string | undefined; pattern?: string | undefined; value?: number | undefined; - users?: Array; - groups?: Array; + users?: object[] | undefined; + groups?: object[] | undefined; token?: string | undefined; }, JsonObject From 7ae762de865722f86dce9ebeda682f4ae7b39086 Mon Sep 17 00:00:00 2001 From: "liad.shachoach" Date: Thu, 16 Jan 2025 15:03:31 +0200 Subject: [PATCH 08/11] replacing export of bitbucketCloudBranchRestriction in index.ts with direct import inside module.ts Signed-off-by: liad.shachoach --- .../report.api.md | 18 ------------------ .../src/actions/index.ts | 1 - .../src/module.ts | 2 +- 3 files changed, 1 insertion(+), 20 deletions(-) diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md b/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md index 67c05a4ea5..b853023b07 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md @@ -13,24 +13,6 @@ import { TemplateAction } from '@backstage/plugin-scaffolder-node'; const bitbucketCloudModule: BackendFeature; export default bitbucketCloudModule; -// @public -export function createBitbucketCloudBranchRestrictionAction(options: { - integrations: ScmIntegrationRegistry; -}): TemplateAction< - { - repoUrl: string; - kind: string; - branchMatchKind?: string | undefined; - branchType?: string | undefined; - pattern?: string | undefined; - value?: number | undefined; - users?: object[] | undefined; - groups?: object[] | undefined; - token?: string | undefined; - }, - JsonObject ->; - // @public export const createBitbucketPipelinesRunAction: (options: { integrations: ScmIntegrationRegistry; diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/index.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/index.ts index 986df299d2..c4030c0b52 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/index.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/index.ts @@ -16,4 +16,3 @@ export * from './bitbucketCloud'; export { createBitbucketPipelinesRunAction } from './bitbucketCloudPipelinesRun'; export * from './bitbucketCloudPullRequest'; -export * from './bitbucketCloudBranchRestriction'; diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/module.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/module.ts index 3a9871f39c..4001c064da 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/module.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/module.ts @@ -21,11 +21,11 @@ import { scaffolderActionsExtensionPoint, scaffolderAutocompleteExtensionPoint, } from '@backstage/plugin-scaffolder-node/alpha'; +import { createBitbucketCloudBranchRestrictionAction } from './actions/bitbucketCloudBranchRestriction'; import { createBitbucketPipelinesRunAction, createPublishBitbucketCloudAction, createPublishBitbucketCloudPullRequestAction, - createBitbucketCloudBranchRestrictionAction, } from './actions'; import { ScmIntegrations } from '@backstage/integration'; import { handleAutocompleteRequest } from './autocomplete/autocomplete'; From 9fa7e212d1446c35d026e19898cd87f1402963df Mon Sep 17 00:00:00 2001 From: "liad.shachoach" Date: Sat, 1 Feb 2025 17:24:36 +0200 Subject: [PATCH 09/11] using msw for tests in bitbucketCloudBranchRestriction.examples.test.ts Signed-off-by: liad.shachoach --- ...ketCloudBranchRestriction.examples.test.ts | 194 +++++++++++------- 1 file changed, 116 insertions(+), 78 deletions(-) diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.examples.test.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.examples.test.ts index 0051ee511a..0e568e3d22 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.examples.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.examples.test.ts @@ -15,6 +15,9 @@ */ import { createBitbucketCloudBranchRestrictionAction } from './bitbucketCloudBranchRestriction'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; import yaml from 'yaml'; @@ -39,100 +42,135 @@ describe('bitbucketCloud:branchRestriction:create', () => { input: { repoUrl: 'bitbucket.org?workspace=workspace&project=project&repo=repo&project=project', + kind: 'push', }, }); - - beforeEach(() => { - global.fetch = jest.fn().mockImplementation(() => - Promise.resolve({ - status: 201, - json: () => - Promise.resolve({ - status: 201, - }), - }), - ); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); + const server = setupServer(); + registerMswTestHooks(server); it('should restrict push to the main branch, except for the defined uuids, and the admins group', async () => { - const input = yaml.parse(examples[0].example).steps[0].input; - const ctx = Object.assign({}, mockContext, { input }); - await action.handler(ctx); - - expect(global.fetch).toHaveBeenCalledWith( - 'https://api.bitbucket.org/2.0/repositories/workspace/repo/branch-restrictions', - expect.objectContaining({ - body: JSON.stringify({ - branch_match_kind: 'branching_model', - users: [{ uuid: '{a-b-c-d}' }, { uuid: '{e-f-g-h}' }], - groups: [{ slug: 'admins' }], - kind: 'push', - branch_type: 'development', - }), - method: 'POST', - }), + server.use( + rest.post( + 'https://api.bitbucket.org/2.0/repositories/workspace/repo/branch-restrictions', + (req, res, ctx) => { + req.json().then(data => { + expect(data).toEqual({ + branch_match_kind: 'branching_model', + branch_type: 'development', + users: [{ uuid: '{a-b-c-d}' }, { uuid: '{e-f-g-h}' }], + groups: [{ slug: 'admins' }], + kind: 'push', + }); + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }, + ), ); + const input = yaml.parse(examples[0].example).steps[0].input; + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); }); it('should restrict push to the main branch, except for the admins group', async () => { - const input = yaml.parse(examples[1].example).steps[0].input; - const ctx = Object.assign({}, mockContext, { input }); - await action.handler(ctx); - - expect(global.fetch).toHaveBeenCalledWith( - 'https://api.bitbucket.org/2.0/repositories/workspace/repo/branch-restrictions', - expect.objectContaining({ - body: JSON.stringify({ - branch_match_kind: 'branching_model', - users: [], - groups: [{ slug: 'admins' }], - kind: 'push', - branch_type: 'development', - }), - method: 'POST', - }), + server.use( + rest.post( + 'https://api.bitbucket.org/2.0/repositories/workspace/repo/branch-restrictions', + (req, res, ctx) => { + req.json().then(data => { + expect(data).toEqual({ + branch_match_kind: 'branching_model', + users: [], + groups: [{ slug: 'admins' }], + kind: 'push', + branch_type: 'development', + }); + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }, + ), ); + const input = yaml.parse(examples[1].example).steps[0].input; + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); }); it('should require passing builds to merge to branches matching a pattern test-feature/*', async () => { - const input = yaml.parse(examples[2].example).steps[0].input; - const ctx = Object.assign({}, mockContext, { input }); - await action.handler(ctx); - - expect(global.fetch).toHaveBeenCalledWith( - 'https://api.bitbucket.org/2.0/repositories/workspace/repo/branch-restrictions', - expect.objectContaining({ - body: JSON.stringify({ - branch_match_kind: 'glob', - kind: 'require_passing_builds_to_merge', - value: 1, - pattern: 'test-feature/*', - }), - method: 'POST', - }), + server.use( + rest.post( + 'https://api.bitbucket.org/2.0/repositories/workspace/repo/branch-restrictions', + (req, res, ctx) => { + req.json().then(data => { + expect(data).toEqual({ + branch_match_kind: 'glob', + kind: 'require_passing_builds_to_merge', + value: 1, + pattern: 'test-feature/*', + }); + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }, + ), ); + const input = yaml.parse(examples[2].example).steps[0].input; + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); }); it('should require approvals to merge to branches matching a pattern test-feature/*', async () => { - const input = yaml.parse(examples[3].example).steps[0].input; - const ctx = Object.assign({}, mockContext, { input }); - await action.handler(ctx); - - expect(global.fetch).toHaveBeenCalledWith( - 'https://api.bitbucket.org/2.0/repositories/workspace/repo/branch-restrictions', - expect.objectContaining({ - body: JSON.stringify({ - branch_match_kind: 'glob', - kind: 'require_approvals_to_merge', - value: 1, - pattern: 'test-feature/*', - }), - method: 'POST', - }), + server.use( + rest.post( + 'https://api.bitbucket.org/2.0/repositories/workspace/repo/branch-restrictions', + (req, res, ctx) => { + req.json().then(data => { + expect(data).toEqual({ + branch_match_kind: 'glob', + kind: 'require_approvals_to_merge', + value: 1, + pattern: 'test-feature/*', + }); + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }, + ), ); + const input = yaml.parse(examples[3].example).steps[0].input; + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + ...input, + }, + }); }); }); From 231acf448f3c142021ae67ea4abae7a2ef1ff233 Mon Sep 17 00:00:00 2001 From: "liad.shachoach" Date: Fri, 28 Feb 2025 18:12:56 +0200 Subject: [PATCH 10/11] updating branch restriction code to use the bitubcket package instead of rest APIs, tests adjusted as well Signed-off-by: liad.shachoach --- .../package.json | 1 + ...ketCloudBranchRestriction.examples.test.ts | 212 ++++++++++-------- .../bitbucketCloudBranchRestriction.test.ts | 98 ++------ .../bitbucketCloudBranchRestriction.ts | 119 ++++------ .../src/actions/helpers.ts | 26 +++ .../python-sample/catalog-info.yaml | 107 +++++++++ 6 files changed, 317 insertions(+), 246 deletions(-) create mode 100644 plugins/scaffolder-backend/sample-templates/azure-devops-demo/python-sample/catalog-info.yaml diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json index 9703ab0c86..785d26e8a0 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json @@ -48,6 +48,7 @@ "@backstage/integration": "workspace:^", "@backstage/plugin-bitbucket-cloud-common": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", + "bitbucket": "^2.12.0", "fs-extra": "^11.2.0", "yaml": "^2.0.0" }, diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.examples.test.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.examples.test.ts index 0e568e3d22..7816580a01 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.examples.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.examples.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * Copyright 2025 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. @@ -15,22 +15,24 @@ */ import { createBitbucketCloudBranchRestrictionAction } from './bitbucketCloudBranchRestriction'; -import { rest } from 'msw'; -import { setupServer } from 'msw/node'; -import { registerMswTestHooks } from '@backstage/backend-test-utils'; import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; import yaml from 'yaml'; import { examples } from './bitbucketCloudBranchRestriction.examples'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { Bitbucket } from 'bitbucket'; + +jest.mock('bitbucket', () => ({ + Bitbucket: jest.fn(), +})); describe('bitbucketCloud:branchRestriction:create', () => { const config = new ConfigReader({ integrations: { bitbucketCloud: [ { - username: 'x-token-auth', - appPassword: 'your-default-auth-token', + username: 'u', + appPassword: 'p', }, ], }, @@ -45,94 +47,99 @@ describe('bitbucketCloud:branchRestriction:create', () => { kind: 'push', }, }); - const server = setupServer(); - registerMswTestHooks(server); - it('should restrict push to the main branch, except for the defined uuids, and the admins group', async () => { - server.use( - rest.post( - 'https://api.bitbucket.org/2.0/repositories/workspace/repo/branch-restrictions', - (req, res, ctx) => { - req.json().then(data => { - expect(data).toEqual({ - branch_match_kind: 'branching_model', - branch_type: 'development', - users: [{ uuid: '{a-b-c-d}' }, { uuid: '{e-f-g-h}' }], - groups: [{ slug: 'admins' }], - kind: 'push', - }); - }); - return res( - ctx.status(201), - ctx.set('Content-Type', 'application/json'), - ctx.json({}), - ); - }, - ), - ); - const input = yaml.parse(examples[0].example).steps[0].input; + const mockBranchRestrictionsApi = { + branchrestrictions: { + create: jest.fn().mockResolvedValue({ status: 201, data: {} }), + }, + }; + (Bitbucket as unknown as jest.Mock).mockImplementation( + () => mockBranchRestrictionsApi, + ); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + afterEach(() => + mockBranchRestrictionsApi.branchrestrictions.create.mockClear(), + ); + + it(`should restrict push to the main branch, except for two user ids, and the admins group`, async () => { await action.handler({ ...mockContext, input: { ...mockContext.input, - ...input, + ...yaml.parse(examples[0].example).steps[0].input, }, }); + + expect( + mockBranchRestrictionsApi.branchrestrictions.create, + ).toHaveBeenCalledWith({ + _body: { + branch_match_kind: 'branching_model', + branch_type: 'development', + users: [ + { + uuid: '{a-b-c-d}', + type: 'user', + }, + { + uuid: '{e-f-g-h}', + type: 'user', + }, + ], + groups: [ + { + slug: 'admins', + type: 'group', + }, + ], + kind: 'push', + value: null, + pattern: undefined, + type: 'branchrestriction', + }, + repo_slug: 'repo', + workspace: 'workspace', + }); }); it('should restrict push to the main branch, except for the admins group', async () => { - server.use( - rest.post( - 'https://api.bitbucket.org/2.0/repositories/workspace/repo/branch-restrictions', - (req, res, ctx) => { - req.json().then(data => { - expect(data).toEqual({ - branch_match_kind: 'branching_model', - users: [], - groups: [{ slug: 'admins' }], - kind: 'push', - branch_type: 'development', - }); - }); - return res( - ctx.status(201), - ctx.set('Content-Type', 'application/json'), - ctx.json({}), - ); - }, - ), - ); - const input = yaml.parse(examples[1].example).steps[0].input; await action.handler({ ...mockContext, input: { ...mockContext.input, - ...input, + ...yaml.parse(examples[1].example).steps[0].input, }, }); + + expect( + mockBranchRestrictionsApi.branchrestrictions.create, + ).toHaveBeenCalledWith({ + _body: { + branch_match_kind: 'branching_model', + branch_type: 'development', + users: [], + groups: [ + { + slug: 'admins', + type: 'group', + }, + ], + kind: 'push', + value: null, + pattern: undefined, + type: 'branchrestriction', + }, + repo_slug: 'repo', + workspace: 'workspace', + }); }); + // it('should require passing builds to merge to branches matching a pattern test-feature/*', async () => { - server.use( - rest.post( - 'https://api.bitbucket.org/2.0/repositories/workspace/repo/branch-restrictions', - (req, res, ctx) => { - req.json().then(data => { - expect(data).toEqual({ - branch_match_kind: 'glob', - kind: 'require_passing_builds_to_merge', - value: 1, - pattern: 'test-feature/*', - }); - }); - return res( - ctx.status(201), - ctx.set('Content-Type', 'application/json'), - ctx.json({}), - ); - }, - ), - ); const input = yaml.parse(examples[2].example).steps[0].input; await action.handler({ ...mockContext, @@ -141,29 +148,27 @@ describe('bitbucketCloud:branchRestriction:create', () => { ...input, }, }); + + expect( + mockBranchRestrictionsApi.branchrestrictions.create, + ).toHaveBeenCalledWith({ + _body: { + branch_match_kind: 'glob', + branch_type: undefined, + users: [], + groups: [], + kind: 'require_passing_builds_to_merge', + value: 1, + pattern: 'test-feature/*', + type: 'branchrestriction', + }, + repo_slug: 'repo', + workspace: 'workspace', + }); }); + // it('should require approvals to merge to branches matching a pattern test-feature/*', async () => { - server.use( - rest.post( - 'https://api.bitbucket.org/2.0/repositories/workspace/repo/branch-restrictions', - (req, res, ctx) => { - req.json().then(data => { - expect(data).toEqual({ - branch_match_kind: 'glob', - kind: 'require_approvals_to_merge', - value: 1, - pattern: 'test-feature/*', - }); - }); - return res( - ctx.status(201), - ctx.set('Content-Type', 'application/json'), - ctx.json({}), - ); - }, - ), - ); const input = yaml.parse(examples[3].example).steps[0].input; await action.handler({ ...mockContext, @@ -172,5 +177,22 @@ describe('bitbucketCloud:branchRestriction:create', () => { ...input, }, }); + + expect( + mockBranchRestrictionsApi.branchrestrictions.create, + ).toHaveBeenCalledWith({ + _body: { + branch_match_kind: 'glob', + branch_type: undefined, + users: [], + groups: [], + kind: 'require_approvals_to_merge', + value: 1, + pattern: 'test-feature/*', + type: 'branchrestriction', + }, + repo_slug: 'repo', + workspace: 'workspace', + }); }); }); diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.test.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.test.ts index f16a7f6f70..55ee430091 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.test.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.test.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 The Backstage Authors + * Copyright 2025 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. @@ -14,87 +14,33 @@ * limitations under the License. */ -import { createBitbucketCloudBranchRestrictionAction } from './bitbucketCloudBranchRestriction'; -import { rest } from 'msw'; -import { setupServer } from 'msw/node'; -import { registerMswTestHooks } from '@backstage/backend-test-utils'; -import { ScmIntegrations } from '@backstage/integration'; -import { ConfigReader } from '@backstage/config'; -import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { getBitbucketClient } from './helpers'; +import { Bitbucket } from 'bitbucket'; + +jest.mock('bitbucket', () => ({ + Bitbucket: jest.fn(), +})); describe('bitbucketCloud:branchRestriction:create', () => { - const config = new ConfigReader({ - integrations: { - bitbucketCloud: [ - { - username: 'u', - appPassword: 'p', - }, - ], - }, - }); - - const integrations = ScmIntegrations.fromConfig(config); - const action = createBitbucketCloudBranchRestrictionAction({ integrations }); - const mockContext = createMockActionContext({ - input: { - repoUrl: 'bitbucket.org?workspace=workspace&project=project&repo=repo', - kind: 'push', - }, - }); - const server = setupServer(); - registerMswTestHooks(server); - - it('should work if the token is provided through ctx.input', async () => { - expect.assertions(2); - const token = 'user-token'; - server.use( - rest.post( - 'https://api.bitbucket.org/2.0/repositories/workspace/repo/branch-restrictions', - (req, res, ctx) => { - expect(req.headers.get('Authorization')).toBe(`Bearer ${token}`); - req.json().then(data => { - expect(data).toEqual({ - branch_match_kind: 'branching_model', - branch_type: 'development', - users: [], - groups: [], - kind: 'push', - }); - }); - return res( - ctx.status(201), - ctx.set('Content-Type', 'application/json'), - ctx.json({}), - ); - }, - ), - ); - await action.handler({ - ...mockContext, - input: { - ...mockContext.input, - token: token, + it('getBitbucketClient should return the correct headers with username and password', () => { + expect.assertions(1); + const username = 'username'; + const password = 'password'; + getBitbucketClient({ username: username, appPassword: password }); + expect(Bitbucket).toHaveBeenCalledWith({ + auth: { + username: username, + password: password, }, }); }); - it('should return correct outputs', async () => { - server.use( - rest.post( - 'https://api.bitbucket.org/2.0/repositories/workspace/repo/branch-restrictions', - (_, res, ctx) => - res( - ctx.status(201), - ctx.set('Content-Type', 'application/json'), - ctx.json({}), - ), - ), - ); + it('getBitbucketClient should throw if only one of username or password is provided', () => { + expect.assertions(2); + const username = 'username'; + const password = 'password'; - await action.handler(mockContext); - - expect(mockContext.output).toHaveBeenCalledWith('statusCode', 201); - expect(mockContext.output).toHaveBeenCalledWith('json', '{}'); + expect(() => getBitbucketClient({ username })).toThrow(Error); + expect(() => getBitbucketClient({ appPassword: password })).toThrow(Error); }); }); diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts index fc997fd7b8..d63ccf1b7c 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudBranchRestriction.ts @@ -19,7 +19,7 @@ import { parseRepoUrl, } from '@backstage/plugin-scaffolder-node'; import { InputError } from '@backstage/errors'; -import { getAuthorizationHeader } from './helpers'; +import { getBitbucketClient } from './helpers'; import * as inputProps from './inputProperties'; import { examples } from './bitbucketCloudBranchRestriction.examples'; @@ -31,10 +31,13 @@ const createBitbucketCloudBranchRestriction = async (opts: { branchType?: string; pattern?: string; value?: number; - users?: Array; - groups?: Array; - authorization: string; - apiBaseUrl: string; + users?: { uuid: string; type: string }[]; + groups?: { slug: string; type: string }[]; + authorization: { + token?: string; + username?: string; + appPassword?: string; + }; }) => { const { workspace, @@ -47,64 +50,24 @@ const createBitbucketCloudBranchRestriction = async (opts: { users, groups, authorization, - apiBaseUrl, } = opts; - const body = new Map(); - body.set('branch_match_kind', branchMatchKind || 'branching_model'); - if (kind in ['push', 'restrict_merges']) { - body.set('users', kind in ['push', 'restrict_merges'] ? users || [] : null); - body.set( - 'groups', - kind in ['push', 'restrict_merges'] ? groups || [] : null, - ); - } - body.set('kind', kind); - if ( - kind === 'require_approvals_to_merge' || - kind === 'require_default_reviewer_approvals_to_merge' || - kind === 'require_commits_behind' || - kind === 'require_passing_builds_to_merge' - ) { - body.set('value', value || 1); - } - if (branchMatchKind === 'glob') { - body.set('pattern', pattern || ''); - } - if (branchMatchKind === 'branching_model') { - body.set('branch_type', branchType || 'development'); - } - - const options: RequestInit = { - method: 'POST', - body: JSON.stringify(Object.fromEntries(body)), - headers: { - Authorization: authorization, - 'Content-Type': 'application/json', - Accept: 'application/json', + const bitbucket = getBitbucketClient(authorization); + return await bitbucket.branchrestrictions.create({ + _body: { + groups: groups, + users: users, + branch_match_kind: branchMatchKind, + kind: kind, + type: 'branchrestriction', + value: kind === 'push' ? null : value, + pattern: branchMatchKind === 'glob' ? pattern : undefined, + branch_type: + branchMatchKind === 'branching_model' ? branchType : undefined, }, - }; - - let response: Response; - try { - response = await fetch( - `${apiBaseUrl}/repositories/${workspace}/${repo}/branch-restrictions`, - options, - ); - } catch (e) { - throw new Error( - `Unable to set branch restrictions for the repository, ${e}`, - ); - } - - if (response.status !== 201) { - throw new Error( - `Unable to set branch restrictions for the repository, ${ - response.status - } ${response.statusText}, ${await response.text()}`, - ); - } - return response; + repo_slug: repo, + workspace: workspace, + }); }; /** @@ -122,8 +85,8 @@ export function createBitbucketCloudBranchRestrictionAction(options: { branchType?: string; pattern?: string; value?: number; - users?: Array; - groups?: Array; + users?: { uuid: string }[]; + groups?: { slug: string }[]; token?: string; }>({ id: 'bitbucketCloud:branchRestriction:create', @@ -166,10 +129,11 @@ export function createBitbucketCloudBranchRestrictionAction(options: { kind, branchMatchKind = 'branching_model', branchType = 'development', - pattern, - value, - users, - groups, + pattern = '', + value = 1, + users = [], + groups = [], + token = '', } = ctx.input; const { workspace, repo, host } = parseRepoUrl(repoUrl, integrations); @@ -187,11 +151,7 @@ export function createBitbucketCloudBranchRestrictionAction(options: { ); } - const authorization = getAuthorizationHeader( - ctx.input.token ? { token: ctx.input.token } : integrationConfig.config, - ); - - const apiBaseUrl = integrationConfig.config.apiBaseUrl; + const authorization = token ? { token: token } : integrationConfig.config; const response = await createBitbucketCloudBranchRestriction({ workspace: workspace, @@ -201,13 +161,22 @@ export function createBitbucketCloudBranchRestrictionAction(options: { branchType: branchType, pattern: pattern, value: value, - users: users, - groups: groups, + users: users.map(user => ({ uuid: user.uuid, type: 'user' })), + groups: groups.map(group => ({ slug: group.slug, type: 'group' })), authorization, - apiBaseUrl, }); + if (response.data.errors) { + ctx.logger.error( + `Error from Bitbucket Cloud Branch Restrictions: ${JSON.stringify( + response.data.errors, + )}`, + ); + } + ctx.logger.info( + `Response from Bitbucket Cloud: ${JSON.stringify(response)}`, + ); ctx.output('statusCode', response.status); - ctx.output('json', JSON.stringify(await response.json())); + ctx.output('json', JSON.stringify(response)); }, }); } diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/helpers.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/helpers.ts index efa6fd64f4..1abffea393 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/helpers.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/helpers.ts @@ -14,6 +14,32 @@ * limitations under the License. */ +import { Bitbucket } from 'bitbucket'; + +export const getBitbucketClient = (config: { + token?: string; + username?: string; + appPassword?: string; +}) => { + if (config.username && config.appPassword) { + return new Bitbucket({ + auth: { + username: config.username, + password: config.appPassword, + }, + }); + } else if (config.token) { + return new Bitbucket({ + auth: { + token: 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`, + ); +}; + export const getAuthorizationHeader = (config: { username?: string; appPassword?: string; diff --git a/plugins/scaffolder-backend/sample-templates/azure-devops-demo/python-sample/catalog-info.yaml b/plugins/scaffolder-backend/sample-templates/azure-devops-demo/python-sample/catalog-info.yaml new file mode 100644 index 0000000000..a8c7e1fd7f --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/azure-devops-demo/python-sample/catalog-info.yaml @@ -0,0 +1,107 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: ${{ values.name }} + annotations: + backstage.io/techdocs-ref: dir:. +spec: + type: service + owner: ${{ values.owner }} + lifecycle: experimental + providesApis: + - ${{ values.name }}-hello-api + - ${{ values.name }}-hello-name-api + - ${{ values.name }}-isalive-api +--- +apiVersion: backstage.io/v1alpha1 +kind: API +metadata: + name: ${{ values.name }}-hello-api + description: A simple hello world API +spec: + type: openapi + lifecycle: experimental + owner: ${{ values.owner }} + system: devops-infra + definition: | + openapi: "3.0.0" + info: + version: 1.0.0 + title: Hello API + license: + name: MIT + servers: + - url: http://localhost:5000 + paths: + /v1/hello: + get: + responses: + '200': + description: Returns a hello world message + summary: Returns a hello world message + ... +--- +apiVersion: backstage.io/v1alpha1 +kind: API +metadata: + name: ${{ values.name }}-hello-name-api + description: A simple hello world API +spec: + type: openapi + lifecycle: experimental + owner: ${{ values.owner }} + system: devops-infra + definition: | + openapi: "3.0.0" + info: + version: 1.0.0 + title: Hello Name API + license: + name: MIT + servers: + - url: http://localhost:5000 + paths: + /v1/hello/{name}: + parameters: + - name: name + in: path + required: true + description: The name to say hello to + schema: + type: string + get: + responses: + '200': + description: Returns a hello world message + summary: Returns a hello world message + ... +--- +apiVersion: backstage.io/v1alpha1 +kind: API +metadata: + name: ${{ values.name }}-isalive-api + description: A simple health check API +spec: + type: openapi + lifecycle: experimental + owner: ${{ values.owner }} + system: devops-infra + definition: | + openapi: "3.0.0" + info: + version: 1.0.0 + title: IsAlive API + license: + name: MIT + servers: + - url: http://localhost:5000 + paths: + /v1/isalive: + get: + responses: + '200': + description: Confirmation that the service is alive + summary: Health check to be used inside Kubernetes + ... + + From 181e363d5de150b6dd1ccadf0d26557fd730abde Mon Sep 17 00:00:00 2001 From: "liad.shachoach" Date: Fri, 28 Feb 2025 18:17:26 +0200 Subject: [PATCH 11/11] adding yarn.lock as well Signed-off-by: liad.shachoach --- yarn.lock | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index f40ec341af..e314d3ba51 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7556,6 +7556,7 @@ __metadata: "@backstage/plugin-bitbucket-cloud-common": "workspace:^" "@backstage/plugin-scaffolder-node": "workspace:^" "@backstage/plugin-scaffolder-node-test-utils": "workspace:^" + bitbucket: ^2.12.0 fs-extra: ^11.2.0 msw: ^1.0.0 yaml: ^2.0.0 @@ -23709,10 +23710,10 @@ __metadata: languageName: node linkType: hard -"before-after-hook@npm:^2.2.0": - version: 2.2.2 - resolution: "before-after-hook@npm:2.2.2" - checksum: dc2e1ffe389e5afbef2a46790b1b5a50247ed57aba67649cfa9ec2552d248cc9278f222e72fb5a8ff59bbb39d78fbaa97e7234ead0c6b5e8418b67a8644ce207 +"before-after-hook@npm:^2.1.0, before-after-hook@npm:^2.2.0": + version: 2.2.3 + resolution: "before-after-hook@npm:2.2.3" + checksum: a1a2430976d9bdab4cd89cb50d27fa86b19e2b41812bf1315923b0cba03371ebca99449809226425dd3bcef20e010db61abdaff549278e111d6480034bebae87 languageName: node linkType: hard @@ -23823,6 +23824,19 @@ __metadata: languageName: node linkType: hard +"bitbucket@npm:^2.12.0": + version: 2.12.0 + resolution: "bitbucket@npm:2.12.0" + dependencies: + before-after-hook: ^2.1.0 + deepmerge: ^4.2.2 + is-plain-object: ^3.0.0 + node-fetch: ^2.6.0 + url-template: ^2.0.8 + checksum: 8b60bbe4430e876bf9029c8759418fc8ea6c5ef1eb3cde43f357db010fbbe040dbce3d07c0509a41de6abd5462862433001b7c84038ed82d6a1b1271e8b67bb5 + languageName: node + linkType: hard + "bl@npm:^4.0.3, bl@npm:^4.1.0": version: 4.1.0 resolution: "bl@npm:4.1.0" @@ -32520,6 +32534,13 @@ __metadata: languageName: node linkType: hard +"is-plain-object@npm:^3.0.0": + version: 3.0.1 + resolution: "is-plain-object@npm:3.0.1" + checksum: d13fe75db350d4ac669595cdfe0242ae87fcecddf2bca858d2dd443a6ed6eb1f69951fac8c2fa85b16106c6b0d7738fea86c2aca2ecee7fd61de15c1574f2cc5 + languageName: node + linkType: hard + "is-plain-object@npm:^5.0.0": version: 5.0.0 resolution: "is-plain-object@npm:5.0.0"