From 141f36662561d0c7cbc62a27a8aa0deff89603c7 Mon Sep 17 00:00:00 2001 From: Andre Wanlin Date: Fri, 21 Jun 2024 11:29:29 -0500 Subject: [PATCH] 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, + }), ); }, });