Merge pull request #5709 from TejasQ/tejask/2494
Scaffolder: Enable branch protection on new repo
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
---
|
||||
|
||||
Add branch protection for default branches of scaffolded GitHub repositories
|
||||
@@ -19,7 +19,10 @@ import {
|
||||
ScmIntegrationRegistry,
|
||||
} from '@backstage/integration';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import { initRepoAndPush } from '../../../stages/publish/helpers';
|
||||
import {
|
||||
enableBranchProtectionOnDefaultRepoBranch,
|
||||
initRepoAndPush,
|
||||
} from '../../../stages/publish/helpers';
|
||||
import { getRepoSourceDirectory, parseRepoUrl } from './util';
|
||||
import { createTemplateAction } from '../../createTemplateAction';
|
||||
|
||||
@@ -171,7 +174,7 @@ export function createPublishGithubAction(options: {
|
||||
description: description,
|
||||
});
|
||||
|
||||
const { data } = await repoCreationPromise;
|
||||
const { data: newRepo } = await repoCreationPromise;
|
||||
if (access?.startsWith(`${owner}/`)) {
|
||||
const [, team] = access.split('/');
|
||||
await client.teams.addOrUpdateRepoPermissionsInOrg({
|
||||
@@ -212,8 +215,8 @@ export function createPublishGithubAction(options: {
|
||||
}
|
||||
}
|
||||
|
||||
const remoteUrl = data.clone_url;
|
||||
const repoContentsUrl = `${data.html_url}/blob/master`;
|
||||
const remoteUrl = newRepo.clone_url;
|
||||
const repoContentsUrl = `${newRepo.html_url}/blob/master`;
|
||||
|
||||
await initRepoAndPush({
|
||||
dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath),
|
||||
@@ -225,6 +228,18 @@ export function createPublishGithubAction(options: {
|
||||
logger: ctx.logger,
|
||||
});
|
||||
|
||||
try {
|
||||
await enableBranchProtectionOnDefaultRepoBranch({
|
||||
owner,
|
||||
client,
|
||||
repoName: newRepo.name,
|
||||
});
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Failed to add branch protection to '${newRepo.name}', ${e}`,
|
||||
);
|
||||
}
|
||||
|
||||
ctx.output('remoteUrl', remoteUrl);
|
||||
ctx.output('repoContentsUrl', repoContentsUrl);
|
||||
},
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
*/
|
||||
|
||||
import { PublisherBase, PublisherOptions, PublisherResult } from './types';
|
||||
import { initRepoAndPush } from './helpers';
|
||||
import {
|
||||
enableBranchProtectionOnDefaultRepoBranch,
|
||||
initRepoAndPush,
|
||||
} from './helpers';
|
||||
import {
|
||||
GitHubIntegrationConfig,
|
||||
GithubCredentialsProvider,
|
||||
@@ -26,6 +29,7 @@ import path from 'path';
|
||||
|
||||
export type RepoVisibilityOptions = 'private' | 'internal' | 'public';
|
||||
|
||||
/** @deprecated use createPublishGithubAction instead */
|
||||
export class GithubPublisher implements PublisherBase {
|
||||
static async fromConfig(
|
||||
config: GitHubIntegrationConfig,
|
||||
@@ -99,6 +103,17 @@ export class GithubPublisher implements PublisherBase {
|
||||
/\.git$/,
|
||||
'/blob/master/catalog-info.yaml',
|
||||
);
|
||||
|
||||
try {
|
||||
await enableBranchProtectionOnDefaultRepoBranch({
|
||||
owner,
|
||||
client,
|
||||
repoName: name,
|
||||
});
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to add branch protection to '${name}', ${e}`);
|
||||
}
|
||||
|
||||
return { remoteUrl, catalogInfoUrl };
|
||||
}
|
||||
|
||||
@@ -130,7 +145,7 @@ export class GithubPublisher implements PublisherBase {
|
||||
description,
|
||||
});
|
||||
|
||||
const { data } = await repoCreationPromise;
|
||||
const { data: newRepo } = await repoCreationPromise;
|
||||
|
||||
try {
|
||||
if (access?.startsWith(`${owner}/`)) {
|
||||
@@ -156,6 +171,7 @@ export class GithubPublisher implements PublisherBase {
|
||||
`Failed to add access to '${access}'. Status ${e.status} ${e.message}`,
|
||||
);
|
||||
}
|
||||
return data?.clone_url;
|
||||
|
||||
return newRepo.clone_url;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import globby from 'globby';
|
||||
import { Logger } from 'winston';
|
||||
import { Git } from '@backstage/backend-common';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
|
||||
export async function initRepoAndPush({
|
||||
dir,
|
||||
@@ -67,3 +68,50 @@ export async function initRepoAndPush({
|
||||
remote: 'origin',
|
||||
});
|
||||
}
|
||||
|
||||
type BranchProtectionOptions = {
|
||||
client: Octokit;
|
||||
owner: string;
|
||||
repoName: string;
|
||||
isRetry?: boolean;
|
||||
};
|
||||
|
||||
export const enableBranchProtectionOnDefaultRepoBranch = async ({
|
||||
repoName,
|
||||
client,
|
||||
owner,
|
||||
}: BranchProtectionOptions): Promise<void> => {
|
||||
const tryOnce = () => {
|
||||
return client.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: 'master',
|
||||
required_status_checks: { strict: true, contexts: [] },
|
||||
restrictions: null,
|
||||
enforce_admins: true,
|
||||
required_pull_request_reviews: { required_approving_review_count: 1 },
|
||||
});
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user