From 141f36662561d0c7cbc62a27a8aa0deff89603c7 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Fri, 21 Jun 2024 11:29:29 -0500 Subject: [PATCH 1/2] Added action to enable GitHub Pages on a repo Signed-off-by: Andre Wanlin --- .changeset/spotty-crabs-shop.md | 5 + .../api-report.md | 15 ++ .../src/actions/githubPages.examples.test.ts | 90 ++++++++++++ .../src/actions/githubPages.examples.ts | 40 ++++++ .../src/actions/githubPages.test.ts | 93 +++++++++++++ .../src/actions/githubPages.ts | 130 ++++++++++++++++++ .../src/actions/index.ts | 2 +- .../src/module.ts | 5 + 8 files changed, 379 insertions(+), 1 deletion(-) create mode 100644 .changeset/spotty-crabs-shop.md create mode 100644 plugins/scaffolder-backend-module-github/src/actions/githubPages.examples.test.ts create mode 100644 plugins/scaffolder-backend-module-github/src/actions/githubPages.examples.ts create mode 100644 plugins/scaffolder-backend-module-github/src/actions/githubPages.test.ts create mode 100644 plugins/scaffolder-backend-module-github/src/actions/githubPages.ts diff --git a/.changeset/spotty-crabs-shop.md b/.changeset/spotty-crabs-shop.md new file mode 100644 index 0000000000..6bc1bcfe0c --- /dev/null +++ b/.changeset/spotty-crabs-shop.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-github': patch +--- + +Added action to enable GitHub Pages on a repo diff --git a/plugins/scaffolder-backend-module-github/api-report.md b/plugins/scaffolder-backend-module-github/api-report.md index 94b642bdbf..aa4d94dcd6 100644 --- a/plugins/scaffolder-backend-module-github/api-report.md +++ b/plugins/scaffolder-backend-module-github/api-report.md @@ -107,6 +107,21 @@ export function createGithubIssuesLabelAction(options: { JsonObject >; +// @public +export function createGithubPagesAction(options: { + integrations: ScmIntegrationRegistry; + githubCredentialsProvider?: GithubCredentialsProvider; +}): TemplateAction< + { + repoUrl: string; + buildType?: 'legacy' | 'workflow' | undefined; + sourceBranch?: string | undefined; + sourcePath?: '/' | '/docs' | undefined; + token?: string | undefined; + }, + JsonObject +>; + // @public export interface CreateGithubPullRequestActionOptions { clientFactory?: (input: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPages.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPages.examples.test.ts new file mode 100644 index 0000000000..7480d8d478 --- /dev/null +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPages.examples.test.ts @@ -0,0 +1,90 @@ +/* + * 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 { TemplateAction } from '@backstage/plugin-scaffolder-node'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { + DefaultGithubCredentialsProvider, + GithubCredentialsProvider, + ScmIntegrations, +} from '@backstage/integration'; +import { createGithubPagesAction } from './githubPages'; +import { examples } from './githubPages.examples'; +import yaml from 'yaml'; + +const mockOctokit = { + request: jest.fn(), +}; + +jest.mock('octokit', () => ({ + Octokit: class { + constructor() { + return mockOctokit; + } + }, +})); + +describe('github:pages', () => { + const config = new ConfigReader({ + integrations: { + github: [ + { host: 'github.com', token: 'mock-token' }, + { host: 'ghe.github.com' }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + let githubCredentialsProvider: GithubCredentialsProvider; + let action: TemplateAction; + + const input = yaml.parse(examples[0].example).steps[0].input; + const mockContext = createMockActionContext({ + input, + }); + + beforeEach(() => { + githubCredentialsProvider = + DefaultGithubCredentialsProvider.fromIntegrations(integrations); + action = createGithubPagesAction({ + integrations, + githubCredentialsProvider, + }); + }); + + afterEach(jest.resetAllMocks); + + it('should work with example input', async () => { + await action.handler(mockContext); + + expect(mockOctokit.request).toHaveBeenCalledWith( + 'POST /repos/{owner}/{repo}/pages', + { + owner: 'owner', + repo: 'repo', + build_type: 'workflow', + source: { + branch: 'main', + path: '/', + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + }); +}); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPages.examples.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPages.examples.ts new file mode 100644 index 0000000000..be4a33e5df --- /dev/null +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPages.examples.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2024 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: 'Enables GitHub Pages for a repository.', + example: yaml.stringify({ + steps: [ + { + action: 'github:pages', + id: 'github-pages', + name: 'Enable GitHub Pages', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + buildType: 'workflow', + sourceBranch: 'main', + sourcePath: '/', + token: 'gph_YourGitHubToken', + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPages.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPages.test.ts new file mode 100644 index 0000000000..266f4c5a53 --- /dev/null +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPages.test.ts @@ -0,0 +1,93 @@ +/* + * 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 { TemplateAction } from '@backstage/plugin-scaffolder-node'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { + DefaultGithubCredentialsProvider, + GithubCredentialsProvider, + ScmIntegrations, +} from '@backstage/integration'; +import { createGithubPagesAction } from './githubPages'; + +const mockOctokit = { + request: jest.fn(), +}; + +jest.mock('octokit', () => ({ + Octokit: class { + constructor() { + return mockOctokit; + } + }, +})); + +describe('github:pages', () => { + const config = new ConfigReader({ + integrations: { + github: [ + { host: 'github.com', token: 'mock-token' }, + { host: 'ghe.github.com' }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + let githubCredentialsProvider: GithubCredentialsProvider; + let action: TemplateAction; + + const mockContext = createMockActionContext({ + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + buildType: 'workflow', + sourceBranch: 'main', + sourcePath: '/', + token: 'gph_YourGitHubToken', + }, + }); + + beforeEach(() => { + githubCredentialsProvider = + DefaultGithubCredentialsProvider.fromIntegrations(integrations); + action = createGithubPagesAction({ + integrations, + githubCredentialsProvider, + }); + }); + + afterEach(jest.resetAllMocks); + + it('should work happy path', async () => { + await action.handler(mockContext); + + expect(mockOctokit.request).toHaveBeenCalledWith( + 'POST /repos/{owner}/{repo}/pages', + { + owner: 'owner', + repo: 'repo', + build_type: 'workflow', + source: { + branch: 'main', + path: '/', + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }, + ); + }); +}); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPages.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPages.ts new file mode 100644 index 0000000000..09910319fe --- /dev/null +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPages.ts @@ -0,0 +1,130 @@ +/* + * Copyright 2024 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 { InputError } from '@backstage/errors'; +import { + GithubCredentialsProvider, + ScmIntegrationRegistry, +} from '@backstage/integration'; +import { Octokit } from 'octokit'; +import { + createTemplateAction, + parseRepoUrl, +} from '@backstage/plugin-scaffolder-node'; +import { examples } from './githubPages.examples'; +import { getOctokitOptions } from '@backstage/plugin-scaffolder-backend-module-github'; + +/** + * Creates a new action that enables GitHub Pages for a repository. + * + * @public + */ +export function createGithubPagesAction(options: { + integrations: ScmIntegrationRegistry; + githubCredentialsProvider?: GithubCredentialsProvider; +}) { + const { integrations, githubCredentialsProvider } = options; + + return createTemplateAction<{ + repoUrl: string; + buildType?: 'legacy' | 'workflow'; + sourceBranch?: string; + sourcePath?: '/' | '/docs'; + token?: string; + }>({ + id: 'github:pages', + examples, + description: 'Enables GitHub Pages for a repository.', + schema: { + input: { + type: 'object', + required: ['repoUrl'], + properties: { + repoUrl: { + title: 'Repository Location', + description: `Accepts the format 'github.com?repo=reponame&owner=owner' where 'reponame' is the new repository name and 'owner' is an organization or username`, + type: 'string', + }, + buildType: { + title: 'Build Type', + type: 'string', + description: + 'The GitHub Pages build type - "legacy" or "workflow". Default is "workflow', + }, + sourceBranch: { + title: 'Source Branch', + type: 'string', + description: + 'The the GitHub Pages source branch. Default is "main"', + }, + sourcePath: { + title: 'Source Path', + type: 'string', + description: + 'The the GitHub Pages source path - "/" or "/docs". Default is "/"', + }, + token: { + title: 'Authorization Token', + type: 'string', + description: 'The token to use for authorization to GitHub', + }, + }, + }, + }, + async handler(ctx) { + const { + repoUrl, + buildType = 'workflow', + sourceBranch = 'main', + sourcePath = '/', + token: providedToken, + } = ctx.input; + + const octokitOptions = await getOctokitOptions({ + integrations, + credentialsProvider: githubCredentialsProvider, + token: providedToken, + repoUrl: repoUrl, + }); + const client = new Octokit(octokitOptions); + + const { owner, repo } = parseRepoUrl(repoUrl, integrations); + + if (!owner) { + throw new InputError('Invalid repository owner provided in repoUrl'); + } + + ctx.logger.info( + `Attempting to enable GitHub Pages for ${owner}/${repo} with "${buildType}" build type, on source branch "${sourceBranch}" and source path "${sourcePath}"`, + ); + + await client.request('POST /repos/{owner}/{repo}/pages', { + owner: owner, + repo: repo, + build_type: buildType, + source: { + branch: sourceBranch, + path: sourcePath, + }, + headers: { + 'X-GitHub-Api-Version': '2022-11-28', + }, + }); + + ctx.logger.info('Completed enabling GitHub Pages'); + }, + }); +} diff --git a/plugins/scaffolder-backend-module-github/src/actions/index.ts b/plugins/scaffolder-backend-module-github/src/actions/index.ts index 40a0da5455..e9a11e9377 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/index.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/index.ts @@ -21,12 +21,12 @@ export { createGithubRepoPushAction } from './githubRepoPush'; export { createGithubWebhookAction } from './githubWebhook'; export { createGithubDeployKeyAction } from './githubDeployKey'; export { createGithubEnvironmentAction } from './githubEnvironment'; - export { createPublishGithubPullRequestAction, type CreateGithubPullRequestActionOptions, } from './githubPullRequest'; export { createPublishGithubAction } from './github'; export { createGithubAutolinksAction } from './githubAutolinks'; +export { createGithubPagesAction } from './githubPages'; export { getOctokitOptions } from './helpers'; diff --git a/plugins/scaffolder-backend-module-github/src/module.ts b/plugins/scaffolder-backend-module-github/src/module.ts index 1817a68bfb..a26fdd7d2d 100644 --- a/plugins/scaffolder-backend-module-github/src/module.ts +++ b/plugins/scaffolder-backend-module-github/src/module.ts @@ -29,6 +29,7 @@ import { createGithubWebhookAction, createPublishGithubAction, createPublishGithubPullRequestAction, + createGithubPagesAction, } from './actions'; import { DefaultGithubCredentialsProvider, @@ -91,6 +92,10 @@ export const githubModule = createBackendModule({ githubCredentialsProvider, config, }), + createGithubPagesAction({ + integrations, + githubCredentialsProvider, + }), ); }, }); From 1469ddade4ee796fac4ebee923433594f3c1e72e Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Fri, 28 Jun 2024 15:43:55 -0500 Subject: [PATCH 2/2] Rename based on feedback Signed-off-by: Andre Wanlin --- plugins/scaffolder-backend-module-github/api-report.md | 2 +- ....examples.test.ts => githubPagesEnable.examples.test.ts} | 6 +++--- ...ithubPages.examples.ts => githubPagesEnable.examples.ts} | 0 .../{githubPages.test.ts => githubPagesEnable.test.ts} | 4 ++-- .../src/actions/{githubPages.ts => githubPagesEnable.ts} | 6 +++--- .../scaffolder-backend-module-github/src/actions/index.ts | 2 +- plugins/scaffolder-backend-module-github/src/module.ts | 4 ++-- 7 files changed, 12 insertions(+), 12 deletions(-) rename plugins/scaffolder-backend-module-github/src/actions/{githubPages.examples.test.ts => githubPagesEnable.examples.test.ts} (93%) rename plugins/scaffolder-backend-module-github/src/actions/{githubPages.examples.ts => githubPagesEnable.examples.ts} (100%) rename plugins/scaffolder-backend-module-github/src/actions/{githubPages.test.ts => githubPagesEnable.test.ts} (95%) rename plugins/scaffolder-backend-module-github/src/actions/{githubPages.ts => githubPagesEnable.ts} (96%) diff --git a/plugins/scaffolder-backend-module-github/api-report.md b/plugins/scaffolder-backend-module-github/api-report.md index aa4d94dcd6..0572052fd4 100644 --- a/plugins/scaffolder-backend-module-github/api-report.md +++ b/plugins/scaffolder-backend-module-github/api-report.md @@ -108,7 +108,7 @@ export function createGithubIssuesLabelAction(options: { >; // @public -export function createGithubPagesAction(options: { +export function createGithubPagesEnableAction(options: { integrations: ScmIntegrationRegistry; githubCredentialsProvider?: GithubCredentialsProvider; }): TemplateAction< diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPages.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.test.ts similarity index 93% rename from plugins/scaffolder-backend-module-github/src/actions/githubPages.examples.test.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.test.ts index 7480d8d478..ee7b5caa65 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPages.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.test.ts @@ -22,8 +22,8 @@ import { GithubCredentialsProvider, ScmIntegrations, } from '@backstage/integration'; -import { createGithubPagesAction } from './githubPages'; -import { examples } from './githubPages.examples'; +import { createGithubPagesEnableAction } from './githubPagesEnable'; +import { examples } from './githubPagesEnable.examples'; import yaml from 'yaml'; const mockOctokit = { @@ -60,7 +60,7 @@ describe('github:pages', () => { beforeEach(() => { githubCredentialsProvider = DefaultGithubCredentialsProvider.fromIntegrations(integrations); - action = createGithubPagesAction({ + action = createGithubPagesEnableAction({ integrations, githubCredentialsProvider, }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPages.examples.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.ts similarity index 100% rename from plugins/scaffolder-backend-module-github/src/actions/githubPages.examples.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.examples.ts diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPages.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.test.ts similarity index 95% rename from plugins/scaffolder-backend-module-github/src/actions/githubPages.test.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.test.ts index 266f4c5a53..203014b67b 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPages.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.test.ts @@ -22,7 +22,7 @@ import { GithubCredentialsProvider, ScmIntegrations, } from '@backstage/integration'; -import { createGithubPagesAction } from './githubPages'; +import { createGithubPagesEnableAction } from './githubPagesEnable'; const mockOctokit = { request: jest.fn(), @@ -63,7 +63,7 @@ describe('github:pages', () => { beforeEach(() => { githubCredentialsProvider = DefaultGithubCredentialsProvider.fromIntegrations(integrations); - action = createGithubPagesAction({ + action = createGithubPagesEnableAction({ integrations, githubCredentialsProvider, }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPages.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.ts similarity index 96% rename from plugins/scaffolder-backend-module-github/src/actions/githubPages.ts rename to plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.ts index 09910319fe..044005961e 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPages.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPagesEnable.ts @@ -24,7 +24,7 @@ import { createTemplateAction, parseRepoUrl, } from '@backstage/plugin-scaffolder-node'; -import { examples } from './githubPages.examples'; +import { examples } from './githubPagesEnable.examples'; import { getOctokitOptions } from '@backstage/plugin-scaffolder-backend-module-github'; /** @@ -32,7 +32,7 @@ import { getOctokitOptions } from '@backstage/plugin-scaffolder-backend-module-g * * @public */ -export function createGithubPagesAction(options: { +export function createGithubPagesEnableAction(options: { integrations: ScmIntegrationRegistry; githubCredentialsProvider?: GithubCredentialsProvider; }) { @@ -45,7 +45,7 @@ export function createGithubPagesAction(options: { sourcePath?: '/' | '/docs'; token?: string; }>({ - id: 'github:pages', + id: 'github:pages:enable', examples, description: 'Enables GitHub Pages for a repository.', schema: { diff --git a/plugins/scaffolder-backend-module-github/src/actions/index.ts b/plugins/scaffolder-backend-module-github/src/actions/index.ts index e9a11e9377..6f37678099 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/index.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/index.ts @@ -27,6 +27,6 @@ export { } from './githubPullRequest'; export { createPublishGithubAction } from './github'; export { createGithubAutolinksAction } from './githubAutolinks'; -export { createGithubPagesAction } from './githubPages'; +export { createGithubPagesEnableAction } from './githubPagesEnable'; export { getOctokitOptions } from './helpers'; diff --git a/plugins/scaffolder-backend-module-github/src/module.ts b/plugins/scaffolder-backend-module-github/src/module.ts index a26fdd7d2d..a5676ab8c9 100644 --- a/plugins/scaffolder-backend-module-github/src/module.ts +++ b/plugins/scaffolder-backend-module-github/src/module.ts @@ -29,7 +29,7 @@ import { createGithubWebhookAction, createPublishGithubAction, createPublishGithubPullRequestAction, - createGithubPagesAction, + createGithubPagesEnableAction, } from './actions'; import { DefaultGithubCredentialsProvider, @@ -92,7 +92,7 @@ export const githubModule = createBackendModule({ githubCredentialsProvider, config, }), - createGithubPagesAction({ + createGithubPagesEnableAction({ integrations, githubCredentialsProvider, }),