diff --git a/.changeset/honest-planets-sin.md b/.changeset/honest-planets-sin.md new file mode 100644 index 0000000000..bb55e881c6 --- /dev/null +++ b/.changeset/honest-planets-sin.md @@ -0,0 +1,23 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +--- + +Fixed a bug in `publish:github` action that didn't permit to add users as collaborators. +This fix required changing the way parameters are passed to the action. +In order to add a team as collaborator, now you must use the `team` field instead of `username`. +In order to add a user as collaborator, you must use the `user` field. + +It's still possible to use the field `username` but is deprecated in favor of `team`. + +```yaml +- id: publish + name: Publish + action: publish:github + input: + repoUrl: ... + collaborators: + - access: ... + team: my_team + - access: ... + user: my_username +``` diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index 1b2676cd00..e2cfc8da13 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -284,10 +284,20 @@ export function createPublishGithubAction(options: { requiredStatusCheckContexts?: string[] | undefined; repoVisibility?: 'internal' | 'private' | 'public' | undefined; collaborators?: - | { - username: string; - access: 'pull' | 'push' | 'admin' | 'maintain' | 'triage'; - }[] + | ( + | { + user: string; + access: 'pull' | 'push' | 'admin' | 'maintain' | 'triage'; + } + | { + team: string; + access: 'pull' | 'push' | 'admin' | 'maintain' | 'triage'; + } + | { + username: string; + access: 'pull' | 'push' | 'admin' | 'maintain' | 'triage'; + } + )[] | undefined; token?: string | undefined; topics?: string[] | undefined; 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 be1ac4b369..ff20d1014b 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 @@ -399,41 +399,35 @@ describe('publish:github', () => { collaborators: [ { access: 'pull', - username: 'robot-1', + user: 'robot-1', }, { access: 'push', - username: 'robot-2', + team: 'robot-2', }, ], }, }); const commonProperties = { - org: 'owner', owner: 'owner', repo: 'repo', }; - expect( - mockOctokit.rest.teams.addOrUpdateRepoPermissionsInOrg.mock.calls[1], - ).toEqual([ - { - ...commonProperties, - team_slug: 'robot-1', - permission: 'pull', - }, - ]); + expect(mockOctokit.rest.repos.addCollaborator).toHaveBeenCalledWith({ + ...commonProperties, + username: 'robot-1', + permission: 'pull', + }); expect( - mockOctokit.rest.teams.addOrUpdateRepoPermissionsInOrg.mock.calls[2], - ).toEqual([ - { - ...commonProperties, - team_slug: 'robot-2', - permission: 'push', - }, - ]); + mockOctokit.rest.teams.addOrUpdateRepoPermissionsInOrg, + ).toHaveBeenCalledWith({ + ...commonProperties, + org: 'owner', + team_slug: 'robot-2', + permission: 'push', + }); }); it('should ignore failures when adding multiple collaborators', async () => { @@ -465,11 +459,11 @@ describe('publish:github', () => { collaborators: [ { access: 'pull', - username: 'robot-1', + team: 'robot-1', }, { access: 'push', - username: 'robot-2', + team: 'robot-2', }, ], }, 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 73c0b10bca..d9a7112fc9 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts @@ -58,10 +58,21 @@ export function createPublishGithubAction(options: { requireCodeOwnerReviews?: boolean; requiredStatusCheckContexts?: string[]; repoVisibility?: 'private' | 'internal' | 'public'; - collaborators?: Array<{ - username: string; - access: 'pull' | 'push' | 'admin' | 'maintain' | 'triage'; - }>; + collaborators?: Array< + | { + user: string; + access: 'pull' | 'push' | 'admin' | 'maintain' | 'triage'; + } + | { + team: string; + access: 'pull' | 'push' | 'admin' | 'maintain' | 'triage'; + } + | { + /** @deprecated This field is deprecated in favor of team */ + username: string; + access: 'pull' | 'push' | 'admin' | 'maintain' | 'triage'; + } + >; token?: string; topics?: string[]; }>({ @@ -160,22 +171,39 @@ export function createPublishGithubAction(options: { }, collaborators: { title: 'Collaborators', - description: 'Provide additional users with permissions', + description: 'Provide additional users or teams with permissions', type: 'array', items: { type: 'object', - required: ['username', 'access'], + additionalProperties: false, + required: ['access'], properties: { access: { type: 'string', description: 'The type of access for the user', enum: ['push', 'pull', 'admin', 'maintain', 'triage'], }, + user: { + type: 'string', + description: + 'The name of the user that will be added as a collaborator', + }, username: { type: 'string', - description: 'The username or group', + description: + 'Deprecated. Use the `team` or `user` field instead.', + }, + team: { + type: 'string', + description: + 'The name of the team that will be added as a collaborator', }, }, + oneOf: [ + { required: ['user'] }, + { required: ['username'] }, + { required: ['team'] }, + ], }, }, token: { @@ -306,22 +334,40 @@ export function createPublishGithubAction(options: { } if (collaborators) { - for (const { - access: permission, - username: team_slug, - } of collaborators) { + for (const collaborator of collaborators) { try { - await client.rest.teams.addOrUpdateRepoPermissionsInOrg({ - org: owner, - team_slug, - owner, - repo, - permission, - }); + if ('user' in collaborator) { + await client.rest.repos.addCollaborator({ + owner, + repo, + username: collaborator.user, + permission: collaborator.access, + }); + } else if ('username' in collaborator) { + ctx.logger.warn( + 'The field `username` is deprecated in favor of `team` and will be removed in the future.', + ); + await client.rest.teams.addOrUpdateRepoPermissionsInOrg({ + org: owner, + team_slug: collaborator.username, + owner, + repo, + permission: collaborator.access, + }); + } else if ('team' in collaborator) { + await client.rest.teams.addOrUpdateRepoPermissionsInOrg({ + org: owner, + team_slug: collaborator.team, + owner, + repo, + permission: collaborator.access, + }); + } } catch (e) { assertError(e); + const name = extractCollaboratorName(collaborator); ctx.logger.warn( - `Skipping ${permission} access for ${team_slug}, ${e.message}`, + `Skipping ${collaborator.access} access for ${name}, ${e.message}`, ); } } @@ -391,3 +437,11 @@ export function createPublishGithubAction(options: { }, }); } + +function extractCollaboratorName( + collaborator: { user: string } | { team: string } | { username: string }, +) { + if ('username' in collaborator) return collaborator.username; + if ('user' in collaborator) return collaborator.user; + return collaborator.team; +}