diff --git a/.changeset/funny-meals-tease.md b/.changeset/funny-meals-tease.md new file mode 100644 index 0000000000..3dce739952 --- /dev/null +++ b/.changeset/funny-meals-tease.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-github': minor +--- + +support oidc customization diff --git a/plugins/scaffolder-backend-module-github/api-report.md b/plugins/scaffolder-backend-module-github/api-report.md index a588840585..de03894a5a 100644 --- a/plugins/scaffolder-backend-module-github/api-report.md +++ b/plugins/scaffolder-backend-module-github/api-report.md @@ -205,6 +205,12 @@ export function createGithubRepoCreateAction(options: { [key: string]: string; } | undefined; + oidcCustomization?: + | { + useDefault: boolean; + includeClaimKeys?: string[] | undefined; + } + | undefined; requireCommitSigning?: boolean | undefined; }, JsonObject @@ -352,6 +358,12 @@ export function createPublishGithubAction(options: { [key: string]: string; } | undefined; + oidcCustomization?: + | { + useDefault: boolean; + includeClaimKeys?: string[] | undefined; + } + | undefined; requiredCommitSigning?: boolean | undefined; }, JsonObject diff --git a/plugins/scaffolder-backend-module-github/src/actions/github.test.ts b/plugins/scaffolder-backend-module-github/src/actions/github.test.ts index a2a8e11e15..75d9b74a8b 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/github.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.test.ts @@ -77,6 +77,7 @@ const mockOctokit = { getRepoPublicKey: jest.fn(), }, }, + request: jest.fn(), }; jest.mock('octokit', () => ({ Octokit: class { @@ -930,6 +931,40 @@ describe('publish:github', () => { }); }); + it('should configure oidc customizations when provided', async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'User' }, + }); + + mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ + data: { + clone_url: 'https://github.com/clone/url.git', + html_url: 'https://github.com/html/url', + }, + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + oidcCustomization: { + useDefault: false, + includeClaimKeys: ['foo', 'bar'], + }, + }, + }); + + expect(mockOctokit.request).toHaveBeenCalledWith( + 'PUT /repos/{owner}/{repo}/actions/oidc/customization/sub', + { + include_claim_keys: ['foo', 'bar'], + owner: 'owner', + repo: 'repo', + use_default: false, + }, + ); + }); + it('should call output with the remoteUrl and the repoContentsUrl', async () => { mockOctokit.rest.users.getByUsername.mockResolvedValue({ data: { type: 'User' }, diff --git a/plugins/scaffolder-backend-module-github/src/actions/github.ts b/plugins/scaffolder-backend-module-github/src/actions/github.ts index 91d7a34aea..f32b65bc9a 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/github.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/github.ts @@ -109,6 +109,10 @@ export function createPublishGithubAction(options: { topics?: string[]; repoVariables?: { [key: string]: string }; secrets?: { [key: string]: string }; + oidcCustomization?: { + useDefault: boolean; + includeClaimKeys?: string[]; + }; requiredCommitSigning?: boolean; }>({ id: 'publish:github', @@ -156,6 +160,7 @@ export function createPublishGithubAction(options: { topics: inputProps.topics, repoVariables: inputProps.repoVariables, secrets: inputProps.secrets, + oidcCustomization: inputProps.oidcCustomization, requiredCommitSigning: inputProps.requiredCommitSigning, }, }, @@ -203,6 +208,7 @@ export function createPublishGithubAction(options: { topics, repoVariables, secrets, + oidcCustomization, token: providedToken, requiredCommitSigning = false, } = ctx.input; @@ -243,6 +249,7 @@ export function createPublishGithubAction(options: { topics, repoVariables, secrets, + oidcCustomization, ctx.logger, ); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts index 4cb007275e..c72a969e96 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.test.ts @@ -58,6 +58,7 @@ const mockOctokit = { getRepoPublicKey: jest.fn(), }, }, + request: jest.fn(), }; jest.mock('octokit', () => ({ Octokit: class { @@ -652,6 +653,40 @@ describe('github:repo:create', () => { }); }); + it('should configure oidc customizations when provided', async () => { + mockOctokit.rest.users.getByUsername.mockResolvedValue({ + data: { type: 'User' }, + }); + + mockOctokit.rest.repos.createForAuthenticatedUser.mockResolvedValue({ + data: { + clone_url: 'https://github.com/clone/url.git', + html_url: 'https://github.com/html/url', + }, + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + oidcCustomization: { + useDefault: false, + includeClaimKeys: ['foo', 'bar'], + }, + }, + }); + + expect(mockOctokit.request).toHaveBeenCalledWith( + 'PUT /repos/{owner}/{repo}/actions/oidc/customization/sub', + { + include_claim_keys: ['foo', 'bar'], + owner: 'owner', + repo: 'repo', + use_default: false, + }, + ); + }); + it('should call output with the remoteUrl', async () => { mockOctokit.rest.users.getByUsername.mockResolvedValue({ data: { type: 'User' }, diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts index 735919794f..52bf9be9c0 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubRepoCreate.ts @@ -95,6 +95,10 @@ export function createGithubRepoCreateAction(options: { topics?: string[]; repoVariables?: { [key: string]: string }; secrets?: { [key: string]: string }; + oidcCustomization?: { + useDefault: boolean; + includeClaimKeys?: string[]; + }; requireCommitSigning?: boolean; }>({ id: 'github:repo:create', @@ -133,6 +137,7 @@ export function createGithubRepoCreateAction(options: { topics: inputProps.topics, repoVariables: inputProps.repoVariables, secrets: inputProps.secrets, + oidcCustomization: inputProps.oidcCustomization, requiredCommitSigning: inputProps.requiredCommitSigning, }, }, @@ -165,6 +170,7 @@ export function createGithubRepoCreateAction(options: { topics, repoVariables, secrets, + oidcCustomization, token: providedToken, } = ctx.input; @@ -204,6 +210,7 @@ export function createGithubRepoCreateAction(options: { topics, repoVariables, secrets, + oidcCustomization, ctx.logger, ); diff --git a/plugins/scaffolder-backend-module-github/src/actions/helpers.ts b/plugins/scaffolder-backend-module-github/src/actions/helpers.ts index 4d95a6494f..ec282c74d9 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/helpers.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/helpers.ts @@ -137,6 +137,12 @@ export async function createGithubRepoWithCollaboratorsAndTopics( topics: string[] | undefined, repoVariables: { [key: string]: string } | undefined, secrets: { [key: string]: string } | undefined, + oidcCustomization: + | { + useDefault: boolean; + includeClaimKeys?: string[]; + } + | undefined, logger: Logger, ) { // eslint-disable-next-line testing-library/no-await-sync-queries @@ -304,6 +310,18 @@ export async function createGithubRepoWithCollaboratorsAndTopics( } } + if (oidcCustomization) { + await client.request( + 'PUT /repos/{owner}/{repo}/actions/oidc/customization/sub', + { + owner, + repo, + use_default: oidcCustomization.useDefault, + include_claim_keys: oidcCustomization.includeClaimKeys, + }, + ); + } + return newRepo; } diff --git a/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts b/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts index 96e1f3b6e1..585526fe94 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts @@ -277,6 +277,28 @@ const secrets = { type: 'object', }; +const oidcCustomization = { + title: 'Repository OIDC customization template', + description: `OIDC customization template attached to the repository.`, + type: 'object', + additionalProperties: false, + properties: { + useDefault: { + title: 'Use Default', + type: 'boolean', + description: `Whether to use the default OIDC template or not.`, + }, + includeClaimKeys: { + title: 'Include claim keys', + type: 'array', + items: { + type: 'string', + }, + description: `Array of unique strings. Each claim key can only contain alphanumeric characters and underscores.`, + }, + }, +}; + export { access }; export { allowMergeCommit }; export { allowRebaseMerge }; @@ -313,3 +335,4 @@ export { topics }; export { requiredCommitSigning }; export { repoVariables }; export { secrets }; +export { oidcCustomization };