From fa33214ba89d2389fb3adb01eae9b14ccb25607b Mon Sep 17 00:00:00 2001 From: Kurt King Date: Sat, 5 Aug 2023 16:57:48 -0600 Subject: [PATCH 1/7] docs: provide example for github:repo:create Signed-off-by: Kurt King --- .../github/githubRepoCreate.examples.test.ts | 135 ++++++++++++++++++ .../github/githubRepoCreate.examples.ts | 35 +++++ .../builtin/github/githubRepoCreate.ts | 2 + 3 files changed, 172 insertions(+) create mode 100644 plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.test.ts create mode 100644 plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.test.ts new file mode 100644 index 0000000000..2f4adbf7db --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.test.ts @@ -0,0 +1,135 @@ +/* + * 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 { TemplateAction } from '@backstage/plugin-scaffolder-node'; + +jest.mock('../helpers'); + +import { getVoidLogger } from '@backstage/backend-common'; +import { ConfigReader } from '@backstage/config'; +import { + DefaultGithubCredentialsProvider, + GithubCredentialsProvider, + ScmIntegrations, +} from '@backstage/integration'; +import { PassThrough } from 'stream'; +import { createGithubRepoCreateAction } from './githubRepoCreate'; +import { entityRefToName } from '../helpers'; +import yaml from 'yaml'; +import { examples } from './githubRepoCreate.examples'; + +const publicKey = '2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvvcCU='; + +const mockOctokit = { + rest: { + users: { + getByUsername: jest.fn(), + }, + repos: { + addCollaborator: jest.fn(), + createInOrg: jest.fn(), + createForAuthenticatedUser: jest.fn(), + replaceAllTopics: jest.fn(), + }, + teams: { + addOrUpdateRepoPermissionsInOrg: jest.fn(), + getByName: jest.fn(), + }, + actions: { + createRepoVariable: jest.fn(), + createOrUpdateRepoSecret: jest.fn(), + getRepoPublicKey: jest.fn(), + }, + }, +}; +jest.mock('octokit', () => ({ + Octokit: class { + constructor() { + return mockOctokit; + } + }, +})); + +describe('github:repo:create', () => { + const config = new ConfigReader({ + integrations: { + github: [ + { host: 'github.com', token: 'tokenlols' }, + { host: 'ghe.github.com' }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + let githubCredentialsProvider: GithubCredentialsProvider; + let action: TemplateAction; + + const mockContext = { + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + }, + workspacePath: 'lol', + logger: getVoidLogger(), + logStream: new PassThrough(), + output: jest.fn(), + createTemporaryDirectory: jest.fn(), + }; + + beforeEach(() => { + githubCredentialsProvider = + DefaultGithubCredentialsProvider.fromIntegrations(integrations); + action = createGithubRepoCreateAction({ + integrations, + githubCredentialsProvider, + }); + (entityRefToName as jest.Mock).mockImplementation((s: string) => s); + mockOctokit.rest.actions.getRepoPublicKey.mockResolvedValue({ + data: { + key: publicKey, + key_id: 'keyid', + }, + }); + }); + + afterEach(jest.resetAllMocks); + + it('should call the githubApis with the correct values for createInOrg', async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[0].example).steps[0].input, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + }); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.ts new file mode 100644 index 0000000000..d64afe1338 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.ts @@ -0,0 +1,35 @@ +/* + * 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: 'Creates a GitHub repository.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.ts index 0ec1c83910..e0fde3b193 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.ts @@ -28,6 +28,7 @@ import { } from './helpers'; import * as inputProps from './inputProperties'; import * as outputProps from './outputProperties'; +import { examples } from './githubRepoCreate.examples'; /** * Creates a new action that initializes a git repository @@ -96,6 +97,7 @@ export function createGithubRepoCreateAction(options: { }>({ id: 'github:repo:create', description: 'Creates a GitHub repository.', + examples, schema: { input: { type: 'object', From 6c6115b6e1714e02622c6a9e05f0b590ddab3729 Mon Sep 17 00:00:00 2001 From: Kurt King Date: Sat, 5 Aug 2023 17:01:46 -0600 Subject: [PATCH 2/7] test: update description Signed-off-by: Kurt King --- .../actions/builtin/github/githubRepoCreate.examples.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.test.ts index 2f4adbf7db..15dc423dd8 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.test.ts @@ -63,7 +63,7 @@ jest.mock('octokit', () => ({ }, })); -describe('github:repo:create', () => { +describe('github:repo:create examples', () => { const config = new ConfigReader({ integrations: { github: [ From 2e7c770cda51a7009c5179c77155d0bb5a41458d Mon Sep 17 00:00:00 2001 From: Kurt King Date: Sat, 5 Aug 2023 18:03:20 -0600 Subject: [PATCH 3/7] docs: provide example for github:repo:push Signed-off-by: Kurt King --- .../github/githubRepoPush.examples.test.ts | 131 ++++++++++++++++++ .../builtin/github/githubRepoPush.examples.ts | 65 +++++++++ .../actions/builtin/github/githubRepoPush.ts | 2 + 3 files changed, 198 insertions(+) create mode 100644 plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.examples.test.ts create mode 100644 plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.examples.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.examples.test.ts new file mode 100644 index 0000000000..84d489272d --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.examples.test.ts @@ -0,0 +1,131 @@ +/* + * 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 { TemplateAction } from '@backstage/plugin-scaffolder-node'; +import { getVoidLogger } from '@backstage/backend-common'; +import { ConfigReader } from '@backstage/config'; +import { + DefaultGithubCredentialsProvider, + GithubCredentialsProvider, + ScmIntegrations, +} from '@backstage/integration'; +import { PassThrough } from 'stream'; +import { initRepoAndPush } from '../helpers'; +import { createGithubRepoPushAction } from './githubRepoPush'; +import { examples } from './githubRepoPush.examples'; +import yaml from 'yaml'; + +const mockGit = { + init: jest.fn(), + add: jest.fn(), + checkout: jest.fn(), + commit: jest + .fn() + .mockResolvedValue('220f19cc36b551763d157f1b5e4a4b446165dbd6'), + fetch: jest.fn(), + addRemote: jest.fn(), + push: jest.fn(), +}; + +jest.mock('@backstage/backend-common', () => ({ + Git: { + fromAuth() { + return mockGit; + }, + }, + getVoidLogger: jest.requireActual('@backstage/backend-common').getVoidLogger, +})); + +jest.mock('../helpers'); + +const initRepoAndPushMocked = initRepoAndPush as jest.Mock< + Promise<{ commitHash: string }> +>; + +const mockOctokit = { + rest: { + repos: { + get: jest.fn(), + }, + }, +}; +jest.mock('octokit', () => ({ + Octokit: class { + constructor() { + return mockOctokit; + } + }, +})); + +describe('github:repo:push examples', () => { + const config = new ConfigReader({ + integrations: { + github: [ + { host: 'github.com', token: 'tokenlols' }, + { host: 'ghe.github.com' }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + let githubCredentialsProvider: GithubCredentialsProvider; + let action: TemplateAction; + + const mockContext = { + workspacePath: 'lol', + logger: getVoidLogger(), + logStream: new PassThrough(), + output: jest.fn(), + createTemporaryDirectory: jest.fn(), + }; + + beforeEach(() => { + jest.resetAllMocks(); + + initRepoAndPushMocked.mockResolvedValue({ commitHash: 'test123' }); + + githubCredentialsProvider = + DefaultGithubCredentialsProvider.fromIntegrations(integrations); + action = createGithubRepoPushAction({ + integrations, + config, + githubCredentialsProvider, + }); + }); + + it('should call initRepoAndPush with the correct values', async () => { + mockOctokit.rest.repos.get.mockResolvedValue({ + data: { + clone_url: 'https://github.com/clone/url.git', + html_url: 'https://github.com/html/url', + }, + }); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[0].example).steps[0].input, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://github.com/clone/url.git', + defaultBranch: 'master', + auth: { username: 'x-access-token', password: 'tokenlols' }, + logger: mockContext.logger, + commitMessage: 'initial commit', + gitAuthorInfo: {}, + }); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.examples.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.examples.ts new file mode 100644 index 0000000000..5d3ba1e9d8 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.examples.ts @@ -0,0 +1,65 @@ +/* + * 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: 'Setup repo with no modifications to branch protection rules', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:push', + name: 'Create test repo with testuser as owner.', + input: { + repoUrl: 'github.com?repo=test&owner=testuser', + }, + }, + ], + }), + }, + { + description: 'Setup repo with required codeowners check', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:push', + name: 'Require codeowner branch protection rule', + input: { + repoUrl: 'github.com?repo=reponame&owner=owner', + requireCodeOwnerReviews: true, + }, + }, + ], + }), + }, + { + description: 'Change the default required number of approvals', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:push', + name: 'Require two approvals before merging', + input: { + repoUrl: 'github.com?repo=reponame&owner=owner', + requiredApprovingReviewCount: 2, + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.ts index fb15535453..e30eedad02 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.ts @@ -26,6 +26,7 @@ import { parseRepoUrl } from '../publish/util'; import { getOctokitOptions, initRepoPushAndProtect } from './helpers'; import * as inputProps from './inputProperties'; import * as outputProps from './outputProperties'; +import { examples } from './githubRepoPush.examples'; /** * Creates a new action that initializes a git repository of the content in the workspace @@ -76,6 +77,7 @@ export function createGithubRepoPushAction(options: { id: 'github:repo:push', description: 'Initializes a git repository of contents in workspace and publishes it to GitHub.', + examples, schema: { input: { type: 'object', From 563b8443e92ebe13ca1b08b6303f25621c579722 Mon Sep 17 00:00:00 2001 From: Kurt King Date: Sat, 5 Aug 2023 18:13:37 -0600 Subject: [PATCH 4/7] docs: add more examples Signed-off-by: Kurt King --- .../github/githubRepoCreate.examples.test.ts | 57 +++++++++++++++++++ .../github/githubRepoCreate.examples.ts | 33 ++++++++++- 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.test.ts index 15dc423dd8..71bbc7c1c6 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.test.ts @@ -132,4 +132,61 @@ describe('github:repo:create examples', () => { visibility: 'private', }); }); + + it('should call the githubApis with a description for createInOrg', async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[1].example).steps[0].input, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + description: 'My new repository', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + }); + }); + + it('should call the githubApis with wiki and issues disabled for createInOrg', async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'Organization' }, + }); + + mockOctokit.rest.repos.createInOrg.mockResolvedValue({ data: {} }); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[2].example).steps[0].input, + }); + + expect(mockOctokit.rest.repos.createInOrg).toHaveBeenCalledWith({ + name: 'repo', + org: 'owner', + private: true, + delete_branch_on_merge: false, + allow_squash_merge: true, + squash_merge_commit_title: 'COMMIT_OR_PR_TITLE', + squash_merge_commit_message: 'COMMIT_MESSAGES', + allow_merge_commit: true, + allow_rebase_merge: true, + allow_auto_merge: false, + visibility: 'private', + has_issues: false, // disable issues + has_wiki: false, // disable wiki + }); + }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.ts index d64afe1338..b2577fa97b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.ts @@ -19,7 +19,7 @@ import yaml from 'yaml'; export const examples: TemplateExample[] = [ { - description: 'Creates a GitHub repository.', + description: 'Creates a GitHub repository with default configuration.', example: yaml.stringify({ steps: [ { @@ -32,4 +32,35 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: 'Add a description.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + description: 'My new repository', + }, + }, + ], + }), + }, + { + description: 'Disable wiki and issues.', + example: yaml.stringify({ + steps: [ + { + action: 'github:repo:create', + name: 'Create a new GitHub repository', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + hasIssues: false, + hasWiki: false, + }, + }, + ], + }), + }, ]; From 0f873325068d05b709c2a5c3c80d57155eba1abe Mon Sep 17 00:00:00 2001 From: Kurt King Date: Sat, 5 Aug 2023 18:19:33 -0600 Subject: [PATCH 5/7] chore: add changeset Signed-off-by: Kurt King --- .changeset/orange-pandas-worry.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/orange-pandas-worry.md diff --git a/.changeset/orange-pandas-worry.md b/.changeset/orange-pandas-worry.md new file mode 100644 index 0000000000..78bd1fad79 --- /dev/null +++ b/.changeset/orange-pandas-worry.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +docs: add examples for github:repo:create and github:repo:push scaffolder actions From 1e945f311b17b0fc34a41872793cc69cb957c365 Mon Sep 17 00:00:00 2001 From: Kurt King Date: Sat, 5 Aug 2023 18:24:54 -0600 Subject: [PATCH 6/7] chore: minor description updates Signed-off-by: Kurt King --- .../actions/builtin/github/githubRepoCreate.examples.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.ts index b2577fa97b..a58da1ee29 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.examples.ts @@ -38,7 +38,7 @@ export const examples: TemplateExample[] = [ steps: [ { action: 'github:repo:create', - name: 'Create a new GitHub repository', + name: 'Create a new GitHub repository with a description', input: { repoUrl: 'github.com?repo=repo&owner=owner', description: 'My new repository', @@ -53,7 +53,7 @@ export const examples: TemplateExample[] = [ steps: [ { action: 'github:repo:create', - name: 'Create a new GitHub repository', + name: 'Create a new GitHub repository without wiki and issues', input: { repoUrl: 'github.com?repo=repo&owner=owner', hasIssues: false, From f09b2004267ea079559cd0ca8a38c3886850bd48 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 12 Aug 2023 17:36:46 +0200 Subject: [PATCH 7/7] Update .changeset/orange-pandas-worry.md Signed-off-by: Patrik Oldsberg --- .changeset/orange-pandas-worry.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/orange-pandas-worry.md b/.changeset/orange-pandas-worry.md index 78bd1fad79..8fd0553c29 100644 --- a/.changeset/orange-pandas-worry.md +++ b/.changeset/orange-pandas-worry.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder-backend': patch --- -docs: add examples for github:repo:create and github:repo:push scaffolder actions +Add examples for `github:repo:create` and `github:repo:push` scaffolder actions.