Added action to enable GitHub Pages on a repo

Signed-off-by: Andre Wanlin <awanlin@spotify.com>
This commit is contained in:
Andre Wanlin
2024-06-21 11:29:29 -05:00
parent 87e583f04f
commit 141f366625
8 changed files with 379 additions and 1 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend-module-github': patch
---
Added action to enable GitHub Pages on a repo
@@ -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: {
@@ -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<any>;
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',
},
},
);
});
});
@@ -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',
},
},
],
}),
},
];
@@ -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<any>;
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',
},
},
);
});
});
@@ -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');
},
});
}
@@ -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';
@@ -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,
}),
);
},
});