diff --git a/.changeset/angry-cheetahs-hide.md b/.changeset/angry-cheetahs-hide.md new file mode 100644 index 0000000000..d294c507e9 --- /dev/null +++ b/.changeset/angry-cheetahs-hide.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Implement "Required approving review count", "Restrictions", and "Required commit signing" support for "publish:github" action diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index c89ead6d2b..3e25311ca6 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -211,6 +211,14 @@ export function createGithubRepoCreateAction(options: { apps?: string[] | undefined; } | undefined; + requiredApprovingReviewCount?: number | undefined; + restrictions?: + | { + users: string[] | undefined; + teams: string[] | undefined; + apps?: string[] | undefined; + } + | undefined; requiredStatusCheckContexts?: string[] | undefined; requireBranchesToBeUpToDate?: boolean | undefined; requiredConversationResolution?: boolean | undefined; @@ -236,6 +244,7 @@ export function createGithubRepoCreateAction(options: { hasIssues?: boolean | undefined; token?: string | undefined; topics?: string[] | undefined; + requireCommitSigning?: boolean | undefined; }>; // @public @@ -261,11 +270,20 @@ export function createGithubRepoPushAction(options: { apps?: string[]; } | undefined; + requiredApprovingReviewCount?: number | undefined; + restrictions?: + | { + users: string[] | undefined; + teams: string[] | undefined; + apps?: string[] | undefined; + } + | undefined; requiredStatusCheckContexts?: string[] | undefined; requireBranchesToBeUpToDate?: boolean | undefined; requiredConversationResolution?: boolean | undefined; sourcePath?: string | undefined; token?: string | undefined; + requireCommitSigning?: boolean | undefined; }>; // @public @@ -405,6 +423,14 @@ export function createPublishGithubAction(options: { apps?: string[]; } | undefined; + requiredApprovingReviewCount?: number | undefined; + restrictions?: + | { + users: string[] | undefined; + teams: string[] | undefined; + apps?: string[] | undefined; + } + | undefined; requireCodeOwnerReviews?: boolean | undefined; dismissStaleReviews?: boolean | undefined; requiredStatusCheckContexts?: string[] | undefined; @@ -432,6 +458,7 @@ export function createPublishGithubAction(options: { hasIssues?: boolean | undefined; token?: string | undefined; topics?: string[] | undefined; + requireCommitSigning?: boolean | undefined; }>; // @public diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.ts index f41150b504..835fc79238 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.ts @@ -59,6 +59,12 @@ export function createGithubRepoCreateAction(options: { teams?: string[]; apps?: string[]; }; + requiredApprovingReviewCount?: number; + restrictions?: { + users: string[]; + teams: string[]; + apps?: string[]; + }; requiredStatusCheckContexts?: string[]; requireBranchesToBeUpToDate?: boolean; requiredConversationResolution?: boolean; @@ -83,6 +89,7 @@ export function createGithubRepoCreateAction(options: { hasIssues?: boolean; token?: string; topics?: string[]; + requireCommitSigning?: boolean; }>({ id: 'github:repo:create', description: 'Creates a GitHub repository.', @@ -97,6 +104,8 @@ export function createGithubRepoCreateAction(options: { access: inputProps.access, requireCodeOwnerReviews: inputProps.requireCodeOwnerReviews, bypassPullRequestAllowances: inputProps.bypassPullRequestAllowances, + requiredApprovingReviewCount: inputProps.requiredApprovingReviewCount, + restrictions: inputProps.restrictions, requiredStatusCheckContexts: inputProps.requiredStatusCheckContexts, requireBranchesToBeUpToDate: inputProps.requireBranchesToBeUpToDate, requiredConversationResolution: @@ -115,6 +124,7 @@ export function createGithubRepoCreateAction(options: { hasIssues: inputProps.hasIssues, token: inputProps.token, topics: inputProps.topics, + requiredCommitSigning: inputProps.requiredCommitSigning, }, }, output: { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.ts index 9d470790fa..5a5ffc015c 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.ts @@ -57,11 +57,20 @@ export function createGithubRepoPushAction(options: { apps?: string[]; } | undefined; + requiredApprovingReviewCount?: number; + restrictions?: + | { + users: string[]; + teams: string[]; + apps?: string[]; + } + | undefined; requiredStatusCheckContexts?: string[]; requireBranchesToBeUpToDate?: boolean; requiredConversationResolution?: boolean; sourcePath?: string; token?: string; + requiredCommitSigning?: boolean; }>({ id: 'github:repo:push', description: @@ -76,6 +85,8 @@ export function createGithubRepoPushAction(options: { dismissStaleReviews: inputProps.dismissStaleReviews, requiredStatusCheckContexts: inputProps.requiredStatusCheckContexts, bypassPullRequestAllowances: inputProps.bypassPullRequestAllowances, + requiredApprovingReviewCount: inputProps.requiredApprovingReviewCount, + restrictions: inputProps.restrictions, requireBranchesToBeUpToDate: inputProps.requireBranchesToBeUpToDate, requiredConversationResolution: inputProps.requiredConversationResolution, @@ -87,6 +98,7 @@ export function createGithubRepoPushAction(options: { gitAuthorEmail: inputProps.gitAuthorEmail, sourcePath: inputProps.sourcePath, token: inputProps.token, + requiredCommitSigning: inputProps.requiredCommitSigning, }, }, output: { @@ -109,10 +121,13 @@ export function createGithubRepoPushAction(options: { requireCodeOwnerReviews = false, dismissStaleReviews = false, bypassPullRequestAllowances, + requiredApprovingReviewCount = 1, + restrictions, requiredStatusCheckContexts = [], requireBranchesToBeUpToDate = true, requiredConversationResolution = false, token: providedToken, + requiredCommitSigning = false, } = ctx.input; const { owner, repo } = parseRepoUrl(repoUrl, integrations); @@ -148,6 +163,8 @@ export function createGithubRepoPushAction(options: { repo, requireCodeOwnerReviews, bypassPullRequestAllowances, + requiredApprovingReviewCount, + restrictions, requiredStatusCheckContexts, requireBranchesToBeUpToDate, requiredConversationResolution, @@ -157,6 +174,7 @@ export function createGithubRepoPushAction(options: { gitAuthorName, gitAuthorEmail, dismissStaleReviews, + requiredCommitSigning, ); ctx.output('remoteUrl', remoteUrl); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/helpers.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/helpers.ts index 9caee49f98..dae5e16374 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/helpers.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/helpers.ts @@ -271,6 +271,14 @@ export async function initRepoPushAndProtect( apps?: string[]; } | undefined, + requiredApprovingReviewCount: number, + restrictions: + | { + users: string[]; + teams: string[]; + apps?: string[]; + } + | undefined, requiredStatusCheckContexts: string[], requireBranchesToBeUpToDate: boolean, requiredConversationResolution: boolean, @@ -280,6 +288,7 @@ export async function initRepoPushAndProtect( gitAuthorName?: string, gitAuthorEmail?: string, dismissStaleReviews?: boolean, + requiredCommitSigning?: boolean, ) { const gitAuthorInfo = { name: gitAuthorName @@ -316,12 +325,15 @@ export async function initRepoPushAndProtect( logger, defaultBranch, bypassPullRequestAllowances, + requiredApprovingReviewCount, + restrictions, requireCodeOwnerReviews, requiredStatusCheckContexts, requireBranchesToBeUpToDate, requiredConversationResolution, enforceAdmins: protectEnforceAdmins, dismissStaleReviews: dismissStaleReviews, + requiredCommitSigning: requiredCommitSigning, }); } catch (e) { assertError(e); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/inputProperties.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/inputProperties.ts index 3fc7126754..b1286953a6 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/inputProperties.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/inputProperties.ts @@ -225,6 +225,46 @@ const sourcePath = { type: 'string', }; +const requiredApprovingReviewCount = { + title: 'Required approving review count', + type: 'number', + description: `Specify the number of reviewers required to approve pull requests. Use a number between 1 and 6 or 0 to not require reviewers. Defaults to 1.`, +}; + +const restrictions = { + title: 'Restrict who can push to the protected branch', + description: + 'Restrict who can push to the protected branch. User, app, and team restrictions are only available for organization-owned repositories.', + type: 'object', + additionalProperties: false, + properties: { + apps: { + type: 'array', + items: { + type: 'string', + }, + }, + users: { + type: 'array', + items: { + type: 'string', + }, + }, + teams: { + type: 'array', + items: { + type: 'string', + }, + }, + }, +}; + +const requiredCommitSigning = { + title: 'Require commit signing', + type: 'boolean', + description: `Require commit signing so that you must sign commits on this branch.`, +}; + export { access }; export { allowMergeCommit }; export { allowRebaseMerge }; @@ -243,6 +283,8 @@ export { homepage }; export { protectDefaultBranch }; export { protectEnforceAdmins }; export { bypassPullRequestAllowances }; +export { requiredApprovingReviewCount }; +export { restrictions }; export { repoUrl }; export { repoVisibility }; export { requireCodeOwnerReviews }; @@ -256,3 +298,4 @@ export { hasWiki }; export { sourcePath }; export { token }; export { topics }; +export { requiredCommitSigning }; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts index 74e5d4580b..8bed279a1b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts @@ -190,11 +190,18 @@ type BranchProtectionOptions = { 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 ({ @@ -204,12 +211,15 @@ export const enableBranchProtectionOnDefaultRepoBranch = async ({ logger, requireCodeOwnerReviews, bypassPullRequestAllowances, + requiredApprovingReviewCount, + restrictions, requiredStatusCheckContexts = [], requireBranchesToBeUpToDate = true, requiredConversationResolution = false, defaultBranch = 'master', enforceAdmins = true, dismissStaleReviews = false, + requiredCommitSigning = false, }: BranchProtectionOptions): Promise => { const tryOnce = async () => { try { @@ -231,16 +241,24 @@ export const enableBranchProtectionOnDefaultRepoBranch = async ({ strict: requireBranchesToBeUpToDate, contexts: requiredStatusCheckContexts, }, - restrictions: null, + restrictions: restrictions ?? null, enforce_admins: enforceAdmins, required_pull_request_reviews: { - required_approving_review_count: 1, + 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 ( diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts index 5430f30657..add6fcc79c 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts @@ -69,6 +69,14 @@ export function createPublishGithubAction(options: { apps?: string[]; } | undefined; + requiredApprovingReviewCount?: number; + restrictions?: + | { + users: string[]; + teams: string[]; + apps?: string[]; + } + | undefined; requireCodeOwnerReviews?: boolean; dismissStaleReviews?: boolean; requiredStatusCheckContexts?: string[]; @@ -95,6 +103,7 @@ export function createPublishGithubAction(options: { hasIssues?: boolean | undefined; token?: string; topics?: string[]; + requiredCommitSigning?: boolean; }>({ id: 'publish:github', description: @@ -109,6 +118,8 @@ export function createPublishGithubAction(options: { homepage: inputProps.homepage, access: inputProps.access, bypassPullRequestAllowances: inputProps.bypassPullRequestAllowances, + requiredApprovingReviewCount: inputProps.requiredApprovingReviewCount, + restrictions: inputProps.restrictions, requireCodeOwnerReviews: inputProps.requireCodeOwnerReviews, dismissStaleReviews: inputProps.dismissStaleReviews, requiredStatusCheckContexts: inputProps.requiredStatusCheckContexts, @@ -136,6 +147,7 @@ export function createPublishGithubAction(options: { hasIssues: inputProps.hasIssues, token: inputProps.token, topics: inputProps.topics, + requiredCommitSigning: inputProps.requiredCommitSigning, }, }, output: { @@ -155,6 +167,8 @@ export function createPublishGithubAction(options: { requireCodeOwnerReviews = false, dismissStaleReviews = false, bypassPullRequestAllowances, + requiredApprovingReviewCount = 1, + restrictions, requiredStatusCheckContexts = [], requireBranchesToBeUpToDate = true, requiredConversationResolution = false, @@ -178,6 +192,7 @@ export function createPublishGithubAction(options: { hasIssues = undefined, topics, token: providedToken, + requiredCommitSigning = false, } = ctx.input; const octokitOptions = await getOctokitOptions({ @@ -233,6 +248,8 @@ export function createPublishGithubAction(options: { repo, requireCodeOwnerReviews, bypassPullRequestAllowances, + requiredApprovingReviewCount, + restrictions, requiredStatusCheckContexts, requireBranchesToBeUpToDate, requiredConversationResolution, @@ -242,6 +259,7 @@ export function createPublishGithubAction(options: { gitAuthorName, gitAuthorEmail, dismissStaleReviews, + requiredCommitSigning, ); ctx.output('remoteUrl', remoteUrl);