chore: created github module

Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2023-12-06 12:31:05 +01:00
parent d5ab1b32db
commit 8ecb0b9a20
45 changed files with 393 additions and 53 deletions
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
@@ -0,0 +1,5 @@
# @backstage/plugin-scaffolder-backend-module-github
The github module for [@backstage/plugin-scaffolder-backend](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend).
_This plugin was created through the Backstage CLI_
@@ -0,0 +1,49 @@
{
"name": "@backstage/plugin-scaffolder-backend-module-github",
"description": "The github module for @backstage/plugin-scaffolder-backend",
"version": "0.0.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
"types": "dist/index.d.ts"
},
"backstage": {
"role": "backend-plugin-module"
},
"scripts": {
"start": "backstage-cli package start",
"build": "backstage-cli package build",
"lint": "backstage-cli package lint",
"test": "backstage-cli package test",
"clean": "backstage-cli package clean",
"prepack": "backstage-cli package prepack",
"postpack": "backstage-cli package postpack"
},
"dependencies": {
"@backstage/backend-common": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/errors": "workspace:^",
"@backstage/integration": "workspace:^",
"@backstage/plugin-scaffolder-node": "workspace:^",
"@octokit/webhooks": "^10.0.0",
"libsodium-wrappers": "^0.7.11",
"octokit": "^2.0.0",
"octokit-plugin-create-pull-request": "^3.10.0",
"winston": "^3.2.1",
"yaml": "^2.0.0"
},
"devDependencies": {
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@types/libsodium-wrappers": "^0.7.10",
"fs-extra": "10.1.0",
"jest-when": "^3.1.0",
"jsonschema": "^1.2.6"
},
"files": [
"dist"
]
}
@@ -0,0 +1,143 @@
/*
* 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 { Config } from '@backstage/config';
import { assertError } from '@backstage/errors';
import { Octokit } from 'octokit';
import { Logger } from 'winston';
type BranchProtectionOptions = {
client: Octokit;
owner: string;
repoName: string;
logger: Logger;
requireCodeOwnerReviews: boolean;
requiredStatusCheckContexts?: string[];
bypassPullRequestAllowances?: {
users?: string[];
teams?: string[];
apps?: string[];
};
requiredApprovingReviewCount?: number;
restrictions?: {
users: string[];
teams: string[];
apps?: string[];
};
requireBranchesToBeUpToDate?: boolean;
requiredConversationResolution?: boolean;
defaultBranch?: string;
enforceAdmins?: boolean;
dismissStaleReviews?: boolean;
requiredCommitSigning?: boolean;
};
export const enableBranchProtectionOnDefaultRepoBranch = async ({
repoName,
client,
owner,
logger,
requireCodeOwnerReviews,
bypassPullRequestAllowances,
requiredApprovingReviewCount,
restrictions,
requiredStatusCheckContexts = [],
requireBranchesToBeUpToDate = true,
requiredConversationResolution = false,
defaultBranch = 'master',
enforceAdmins = true,
dismissStaleReviews = false,
requiredCommitSigning = false,
}: BranchProtectionOptions): Promise<void> => {
const tryOnce = async () => {
try {
await client.rest.repos.updateBranchProtection({
mediaType: {
/**
* 👇 we need this preview because allowing a custom
* reviewer count on branch protection is a preview
* feature
*
* More here: https://docs.github.com/en/rest/overview/api-previews#require-multiple-approving-reviews
*/
previews: ['luke-cage-preview'],
},
owner,
repo: repoName,
branch: defaultBranch,
required_status_checks: {
strict: requireBranchesToBeUpToDate,
contexts: requiredStatusCheckContexts,
},
restrictions: restrictions ?? null,
enforce_admins: enforceAdmins,
required_pull_request_reviews: {
required_approving_review_count: requiredApprovingReviewCount,
require_code_owner_reviews: requireCodeOwnerReviews,
bypass_pull_request_allowances: bypassPullRequestAllowances,
dismiss_stale_reviews: dismissStaleReviews,
},
required_conversation_resolution: requiredConversationResolution,
});
if (requiredCommitSigning) {
await client.rest.repos.createCommitSignatureProtection({
owner,
repo: repoName,
branch: defaultBranch,
});
}
} catch (e) {
assertError(e);
if (
e.message.includes(
'Upgrade to GitHub Pro or make this repository public to enable this feature',
)
) {
logger.warn(
'Branch protection was not enabled as it requires GitHub Pro for private repositories',
);
} else {
throw e;
}
}
};
try {
await tryOnce();
} catch (e) {
if (!e.message.includes('Branch not found')) {
throw e;
}
// GitHub has eventual consistency. Fail silently, wait, and try again.
await new Promise(resolve => setTimeout(resolve, 600));
await tryOnce();
}
};
export function getGitCommitMessage(
gitCommitMessage: string | undefined,
config: Config,
): string | undefined {
return gitCommitMessage
? gitCommitMessage
: config.getOptionalString('scaffolder.defaultCommitMessage');
}
export function entityRefToName(name: string): string {
return name.replace(/^.*[:/]/g, '');
}
@@ -13,9 +13,29 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
jest.mock('../helpers');
jest.mock('./gitHelpers', () => {
return {
...jest.requireActual('./gitHelpers'),
entityRefToName: jest.fn(),
};
});
import { TemplateAction } from '@backstage/plugin-scaffolder-node';
jest.mock('@backstage/plugin-scaffolder-node', () => {
return {
...jest.requireActual('@backstage/plugin-scaffolder-node'),
initRepoAndPush: jest.fn().mockResolvedValue({
commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6',
}),
commitAndPushRepo: jest.fn().mockResolvedValue({
commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6',
}),
};
});
import {
TemplateAction,
initRepoAndPush,
} from '@backstage/plugin-scaffolder-node';
import { getVoidLogger } from '@backstage/backend-common';
import { ConfigReader } from '@backstage/config';
import {
@@ -24,10 +44,10 @@ import {
ScmIntegrations,
} from '@backstage/integration';
import { PassThrough } from 'stream';
import { entityRefToName, initRepoAndPush } from '../helpers';
import { createPublishGithubAction } from './github';
import { examples } from './github.examples';
import yaml from 'yaml';
import { entityRefToName } from './gitHelpers';
const publicKey = '2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvvcCU=';
@@ -76,7 +96,7 @@ describe('publish:github', () => {
});
const { entityRefToName: realFamiliarizeEntityName } =
jest.requireActual('../helpers');
jest.requireActual('./helpers');
const integrations = ScmIntegrations.fromConfig(config);
let githubCredentialsProvider: GithubCredentialsProvider;
let action: TemplateAction<any>;
@@ -13,7 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
jest.mock('../helpers');
jest.mock('./gitHelpers', () => {
return {
...jest.requireActual('./gitHelpers'),
enableBranchProtectionOnDefaultRepoBranch: jest.fn(),
entityRefToName: jest.fn(),
};
});
jest.mock('@backstage/plugin-scaffolder-node', () => {
return {
...jest.requireActual('@backstage/plugin-scaffolder-node'),
initRepoAndPush: jest.fn().mockResolvedValue({
commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6',
}),
commitAndPushRepo: jest.fn().mockResolvedValue({
commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6',
}),
};
});
import { TemplateAction } from '@backstage/plugin-scaffolder-node';
import { getVoidLogger } from '@backstage/backend-common';
@@ -25,12 +43,12 @@ import {
} from '@backstage/integration';
import { when } from 'jest-when';
import { PassThrough } from 'stream';
import { createPublishGithubAction } from './github';
import { initRepoAndPush } from '@backstage/plugin-scaffolder-node';
import {
enableBranchProtectionOnDefaultRepoBranch,
entityRefToName,
initRepoAndPush,
} from '../helpers';
import { createPublishGithubAction } from './github';
} from './gitHelpers';
const publicKey = '2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvvcCU=';
@@ -79,7 +97,7 @@ describe('publish:github', () => {
});
const { entityRefToName: realFamiliarizeEntityName } =
jest.requireActual('../helpers');
jest.requireActual('./gitHelpers');
const integrations = ScmIntegrations.fromConfig(config);
let githubCredentialsProvider: GithubCredentialsProvider;
let action: TemplateAction<any>;
@@ -21,15 +21,17 @@ import {
ScmIntegrationRegistry,
} from '@backstage/integration';
import { Octokit } from 'octokit';
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
import {
createTemplateAction,
parseRepoUrl,
} from '@backstage/plugin-scaffolder-node';
import {
createGithubRepoWithCollaboratorsAndTopics,
getOctokitOptions,
initRepoPushAndProtect,
} from '../github/helpers';
import * as inputProps from '../github/inputProperties';
import * as outputProps from '../github/outputProperties';
import { parseRepoUrl } from './util';
} from './helpers';
import * as inputProps from './inputProperties';
import * as outputProps from './outputProperties';
import { examples } from './github.examples';
/**
@@ -19,9 +19,11 @@ import {
GithubCredentialsProvider,
ScmIntegrations,
} from '@backstage/integration';
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
import {
createTemplateAction,
parseRepoUrl,
} from '@backstage/plugin-scaffolder-node';
import { Octokit } from 'octokit';
import { parseRepoUrl } from '../publish/util';
import { getOctokitOptions } from './helpers';
import { examples } from './githubActionsDispatch.examples';
@@ -15,9 +15,11 @@
*/
import { InputError } from '@backstage/errors';
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
import {
createTemplateAction,
parseRepoUrl,
} from '@backstage/plugin-scaffolder-node';
import { ScmIntegrationRegistry } from '@backstage/integration';
import { parseRepoUrl } from '../publish/util';
import { getOctokitOptions } from './helpers';
import { Octokit } from 'octokit';
import Sodium from 'libsodium-wrappers';
@@ -15,9 +15,11 @@
*/
import { InputError } from '@backstage/errors';
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
import {
createTemplateAction,
parseRepoUrl,
} from '@backstage/plugin-scaffolder-node';
import { ScmIntegrationRegistry } from '@backstage/integration';
import { parseRepoUrl } from '../publish/util';
import { getOctokitOptions } from './helpers';
import { Octokit } from 'octokit';
import Sodium from 'libsodium-wrappers';
@@ -18,11 +18,13 @@ import {
GithubCredentialsProvider,
ScmIntegrationRegistry,
} from '@backstage/integration';
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
import {
createTemplateAction,
parseRepoUrl,
} from '@backstage/plugin-scaffolder-node';
import { assertError, InputError } from '@backstage/errors';
import { Octokit } from 'octokit';
import { getOctokitOptions } from './helpers';
import { parseRepoUrl } from '../publish/util';
import { examples } from './githubIssuesLabel.examples';
/**
@@ -15,21 +15,21 @@
*/
import path from 'path';
import { parseRepoUrl } from './util';
import {
GithubCredentialsProvider,
ScmIntegrationRegistry,
} from '@backstage/integration';
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
import {
createTemplateAction,
parseRepoUrl,
SerializedFile,
serializeDirectoryContents,
} from '@backstage/plugin-scaffolder-node';
import { Octokit } from 'octokit';
import { InputError, CustomErrorBase } from '@backstage/errors';
import { resolveSafeChildPath } from '@backstage/backend-common';
import { createPullRequest } from 'octokit-plugin-create-pull-request';
import { getOctokitOptions } from '../github/helpers';
import {
SerializedFile,
serializeDirectoryContents,
} from '../../../../lib/files';
import { getOctokitOptions } from './helpers';
import { Logger } from 'winston';
import { examples } from './githubPullRequest.examples';
@@ -16,7 +16,12 @@
import { TemplateAction } from '@backstage/plugin-scaffolder-node';
jest.mock('../helpers');
jest.mock('./gitHelpers', () => {
return {
...jest.requireActual('./gitHelpers'),
entityRefToName: jest.fn(),
};
});
import { getVoidLogger } from '@backstage/backend-common';
import { ConfigReader } from '@backstage/config';
@@ -27,7 +32,7 @@ import {
} from '@backstage/integration';
import { PassThrough } from 'stream';
import { createGithubRepoCreateAction } from './githubRepoCreate';
import { entityRefToName } from '../helpers';
import { entityRefToName } from './gitHelpers';
import yaml from 'yaml';
import { examples } from './githubRepoCreate.examples';
@@ -16,7 +16,12 @@
import { TemplateAction } from '@backstage/plugin-scaffolder-node';
jest.mock('../helpers');
jest.mock('./gitHelpers', () => {
return {
...jest.requireActual('./gitHelpers'),
entityRefToName: jest.fn(),
};
});
import { getVoidLogger } from '@backstage/backend-common';
import { ConfigReader } from '@backstage/config';
@@ -28,7 +33,7 @@ import {
import { when } from 'jest-when';
import { PassThrough } from 'stream';
import { createGithubRepoCreateAction } from './githubRepoCreate';
import { entityRefToName } from '../helpers';
import { entityRefToName } from './gitHelpers';
const publicKey = '2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvvcCU=';
@@ -20,8 +20,10 @@ import {
ScmIntegrationRegistry,
} from '@backstage/integration';
import { Octokit } from 'octokit';
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
import { parseRepoUrl } from '../publish/util';
import {
createTemplateAction,
parseRepoUrl,
} from '@backstage/plugin-scaffolder-node';
import {
createGithubRepoWithCollaboratorsAndTopics,
getOctokitOptions,
@@ -13,7 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { TemplateAction } from '@backstage/plugin-scaffolder-node';
jest.mock('@backstage/plugin-scaffolder-node', () => {
return {
...jest.requireActual('@backstage/plugin-scaffolder-node'),
initRepoAndPush: jest.fn().mockResolvedValue({
commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6',
}),
commitAndPushRepo: jest.fn().mockResolvedValue({
commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6',
}),
};
});
import {
TemplateAction,
initRepoAndPush,
} from '@backstage/plugin-scaffolder-node';
import { getVoidLogger } from '@backstage/backend-common';
import { ConfigReader } from '@backstage/config';
import {
@@ -22,7 +37,6 @@ import {
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';
@@ -48,7 +62,12 @@ jest.mock('@backstage/backend-common', () => ({
getVoidLogger: jest.requireActual('@backstage/backend-common').getVoidLogger,
}));
jest.mock('../helpers');
jest.mock('./helpers', () => {
return {
...jest.requireActual('./helpers'),
entityRefToName: jest.fn(),
};
});
const initRepoAndPushMocked = initRepoAndPush as jest.Mock<
Promise<{ commitHash: string }>
@@ -98,6 +117,7 @@ describe('github:repo:push examples', () => {
githubCredentialsProvider =
DefaultGithubCredentialsProvider.fromIntegrations(integrations);
action = createGithubRepoPushAction({
integrations,
config,
@@ -35,9 +35,30 @@ jest.mock('@backstage/backend-common', () => ({
getVoidLogger: jest.requireActual('@backstage/backend-common').getVoidLogger,
}));
jest.mock('../helpers');
jest.mock('./gitHelpers', () => {
return {
...jest.requireActual('./gitHelpers'),
entityRefToName: jest.fn(),
enableBranchProtectionOnDefaultRepoBranch: jest.fn(),
};
});
import { TemplateAction } from '@backstage/plugin-scaffolder-node';
jest.mock('@backstage/plugin-scaffolder-node', () => {
return {
...jest.requireActual('@backstage/plugin-scaffolder-node'),
initRepoAndPush: jest.fn().mockResolvedValue({
commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6',
}),
commitAndPushRepo: jest.fn().mockResolvedValue({
commitHash: '220f19cc36b551763d157f1b5e4a4b446165dbd6',
}),
};
});
import {
TemplateAction,
initRepoAndPush,
} from '@backstage/plugin-scaffolder-node';
import { getVoidLogger } from '@backstage/backend-common';
import { ConfigReader } from '@backstage/config';
import {
@@ -46,10 +67,7 @@ import {
ScmIntegrations,
} from '@backstage/integration';
import { PassThrough } from 'stream';
import {
enableBranchProtectionOnDefaultRepoBranch,
initRepoAndPush,
} from '../helpers';
import { enableBranchProtectionOnDefaultRepoBranch } from './gitHelpers';
import { createGithubRepoPushAction } from './githubRepoPush';
const initRepoAndPushMocked = initRepoAndPush as jest.Mock<
@@ -21,8 +21,10 @@ import {
ScmIntegrationRegistry,
} from '@backstage/integration';
import { Octokit } from 'octokit';
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
import { parseRepoUrl } from '../publish/util';
import {
createTemplateAction,
parseRepoUrl,
} from '@backstage/plugin-scaffolder-node';
import { getOctokitOptions, initRepoPushAndProtect } from './helpers';
import * as inputProps from './inputProperties';
import * as outputProps from './outputProperties';
@@ -18,12 +18,14 @@ import {
GithubCredentialsProvider,
ScmIntegrationRegistry,
} from '@backstage/integration';
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
import {
createTemplateAction,
parseRepoUrl,
} from '@backstage/plugin-scaffolder-node';
import { emitterEventNames } from '@octokit/webhooks';
import { assertError, InputError } from '@backstage/errors';
import { Octokit } from 'octokit';
import { getOctokitOptions } from './helpers';
import { parseRepoUrl } from '../publish/util';
import { examples } from './githubWebhook.examples';
/**
@@ -24,13 +24,18 @@ import {
import { OctokitOptions } from '@octokit/core/dist-types/types';
import { Octokit } from 'octokit';
import { Logger } from 'winston';
import {
getRepoSourceDirectory,
initRepoAndPush,
parseRepoUrl,
} from '@backstage/plugin-scaffolder-node';
import Sodium from 'libsodium-wrappers';
import {
enableBranchProtectionOnDefaultRepoBranch,
initRepoAndPush,
} from '../helpers';
import { getRepoSourceDirectory, parseRepoUrl } from '../publish/util';
import { entityRefToName } from '../../builtin/helpers';
import Sodium from 'libsodium-wrappers';
entityRefToName,
} from './gitHelpers';
const DEFAULT_TIMEOUT_MS = 60_000;
@@ -422,3 +427,12 @@ async function validateAccessTeam(client: Octokit, access: string) {
}
}
}
export function getGitCommitMessage(
gitCommitMessage: string | undefined,
config: Config,
): string | undefined {
return gitCommitMessage
? gitCommitMessage
: config.getOptionalString('scaffolder.defaultCommitMessage');
}
@@ -21,3 +21,6 @@ export { createGithubRepoPushAction } from './githubRepoPush';
export { createGithubWebhookAction } from './githubWebhook';
export { createGithubDeployKeyAction } from './githubDeployKey';
export { createGithubEnvironmentAction } from './githubEnvironment';
export { createPublishGithubPullRequestAction } from './githubPullRequest';
export { createPublishGithubAction } from './github';
@@ -0,0 +1,23 @@
/*
* 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.
*/
/**
* The github module for @backstage/plugin-scaffolder-backend.
*
* @packageDocumentation
*/
export * from './actions';