diff --git a/.changeset/three-eggs-punch.md b/.changeset/three-eggs-punch.md new file mode 100644 index 0000000000..28dbd2439d --- /dev/null +++ b/.changeset/three-eggs-punch.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +GitHub branch protection option 'Require review from Code Owners' can be enabled by adding `requireCodeOwnersReview: true` in context input. diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts index 7a10046fd0..c1ad3adf3e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts @@ -124,6 +124,7 @@ type BranchProtectionOptions = { owner: string; repoName: string; logger: Logger; + requireCodeOwnerReviews: boolean; defaultBranch?: string; }; @@ -132,6 +133,7 @@ export const enableBranchProtectionOnDefaultRepoBranch = async ({ client, owner, logger, + requireCodeOwnerReviews, defaultBranch = 'master', }: BranchProtectionOptions): Promise => { const tryOnce = async () => { @@ -153,7 +155,10 @@ export const enableBranchProtectionOnDefaultRepoBranch = async ({ required_status_checks: { strict: true, contexts: [] }, restrictions: null, enforce_admins: true, - required_pull_request_reviews: { required_approving_review_count: 1 }, + required_pull_request_reviews: { + required_approving_review_count: 1, + require_code_owner_reviews: requireCodeOwnerReviews, + }, }); } catch (e) { if ( diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.test.ts index 5e948b52b1..4461d227e8 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.test.ts @@ -22,7 +22,10 @@ import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; import { getVoidLogger } from '@backstage/backend-common'; import { PassThrough } from 'stream'; -import { initRepoAndPush } from '../helpers'; +import { + enableBranchProtectionOnDefaultRepoBranch, + initRepoAndPush, +} from '../helpers'; import { when } from 'jest-when'; describe('publish:github', () => { @@ -583,4 +586,61 @@ describe('publish:github', () => { 'https://github.com/html/url/blob/main', ); }); + + it('should call enableBranchProtectionOnDefaultRepoBranch with the correct values of requireCodeOwnerReviews', async () => { + mockGithubClient.users.getByUsername.mockResolvedValue({ + data: { type: 'User' }, + }); + + mockGithubClient.repos.createForAuthenticatedUser.mockResolvedValue({ + data: { + name: 'repository', + }, + }); + + await action.handler(mockContext); + + expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ + owner: 'owner', + client: mockGithubClient, + repoName: 'repository', + logger: mockContext.logger, + defaultBranch: 'master', + requireCodeOwnerReviews: false, + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + requireCodeOwnerReviews: true, + }, + }); + + expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ + owner: 'owner', + client: mockGithubClient, + repoName: 'repository', + logger: mockContext.logger, + defaultBranch: 'master', + requireCodeOwnerReviews: true, + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + requireCodeOwnerReviews: false, + }, + }); + + expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ + owner: 'owner', + client: mockGithubClient, + repoName: 'repository', + logger: mockContext.logger, + defaultBranch: 'master', + requireCodeOwnerReviews: false, + }); + }); }); 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 f767d6d38c..5a358822af 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts @@ -49,6 +49,7 @@ export function createPublishGithubAction(options: { access?: string; defaultBranch?: string; sourcePath?: string; + requireCodeOwnerReviews?: boolean; repoVisibility: 'private' | 'internal' | 'public'; collaborators: Collaborator[]; topics?: string[]; @@ -75,6 +76,11 @@ export function createPublishGithubAction(options: { description: `Sets an admin collaborator on the repository. Can either be a user reference different from 'owner' in 'repoUrl' or team reference, eg. 'org/team-name'`, type: 'string', }, + requireCodeOwnerReviews: { + title: + 'Require an approved review in PR including files with a designated Code Owner', + type: 'boolean', + }, repoVisibility: { title: 'Repository Visibility', type: 'string', @@ -138,6 +144,7 @@ export function createPublishGithubAction(options: { repoUrl, description, access, + requireCodeOwnerReviews = false, repoVisibility = 'private', defaultBranch = 'master', collaborators, @@ -283,6 +290,7 @@ export function createPublishGithubAction(options: { repoName: newRepo.name, logger: ctx.logger, defaultBranch, + requireCodeOwnerReviews, }); } catch (e) { ctx.logger.warn(