From b5e002b1cccc3a6dcb0f1095ae1f4b2e0b94a498 Mon Sep 17 00:00:00 2001 From: Jason Liu Date: Sat, 23 Nov 2024 14:14:17 +1100 Subject: [PATCH 1/2] Make GitHub environment Scaffolder action use auth to resolve reviewers Changes the github:environment:create Scaffolder action to request and use a backend auth token when resolving the reviewer entityRefs from the Backstage catalog. This is because previously it would throw a 401 error when backend auth was not disabled. The logic for requesting the token is copied from the existing catalog:fetch action, which needs to do a similar thing. Also slightly clarifies that Backstage entityRefs are expected in the reviewers list for this action. Signed-off-by: Jason Liu --- .changeset/long-geese-report.md | 5 ++++ .../report.api.md | 2 ++ .../src/actions/githubEnvironment.test.ts | 18 +++++++++++++++ .../src/actions/githubEnvironment.ts | 23 +++++++++++++++---- 4 files changed, 43 insertions(+), 5 deletions(-) create mode 100644 .changeset/long-geese-report.md diff --git a/.changeset/long-geese-report.md b/.changeset/long-geese-report.md new file mode 100644 index 0000000000000..1e638703c9bf9 --- /dev/null +++ b/.changeset/long-geese-report.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-github': patch +--- + +Change `github:environment:create` action to request and use a token when resolving reviewer entity refs from the Backstage catalog. diff --git a/plugins/scaffolder-backend-module-github/report.api.md b/plugins/scaffolder-backend-module-github/report.api.md index 547c61768fff7..e954384444d02 100644 --- a/plugins/scaffolder-backend-module-github/report.api.md +++ b/plugins/scaffolder-backend-module-github/report.api.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AuthService } from '@backstage/backend-plugin-api'; import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogApi } from '@backstage/catalog-client'; import { Config } from '@backstage/config'; @@ -103,6 +104,7 @@ export function createGithubDeployKeyAction(options: { export function createGithubEnvironmentAction(options: { integrations: ScmIntegrationRegistry; catalogClient?: CatalogApi; + auth?: AuthService; }): TemplateAction< { repoUrl: string; diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.test.ts index 1397942a7cf0b..898e1842f7357 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.test.ts @@ -20,6 +20,7 @@ import { TemplateAction } from '@backstage/plugin-scaffolder-node'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { CatalogApi } from '@backstage/catalog-client'; +import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; const mockOctokit = { rest: { @@ -71,6 +72,14 @@ describe('github:environment:create', () => { }); const integrations = ScmIntegrations.fromConfig(config); + + const credentials = mockCredentials.user(); + + const token = mockCredentials.service.token({ + onBehalfOf: credentials, + targetPluginId: 'catalog', + }); + let action: TemplateAction; const mockContext = createMockActionContext({ @@ -78,6 +87,7 @@ describe('github:environment:create', () => { repoUrl: 'github.com?repo=repository&owner=owner', name: 'envname', }, + secrets: { backstageToken: token }, }); beforeEach(() => { @@ -122,6 +132,7 @@ describe('github:environment:create', () => { action = createGithubEnvironmentAction({ integrations, catalogClient: mockCatalogClient as CatalogApi, + auth: mockServices.auth(), }); }); @@ -453,6 +464,13 @@ describe('github:environment:create', () => { }, }); + expect(mockCatalogClient.getEntitiesByRefs).toHaveBeenCalledWith( + { + entityRefs: ['group:default/team-a', 'user:default/johndoe'], + }, + { token }, + ); + expect( mockOctokit.rest.repos.createOrUpdateEnvironment, ).toHaveBeenCalledWith({ diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts index 29c0a3ce3830a..ef59b611fe2ed 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts @@ -26,6 +26,7 @@ import Sodium from 'libsodium-wrappers'; import { examples } from './gitHubEnvironment.examples'; import { CatalogApi } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; +import { AuthService } from '@backstage/backend-plugin-api'; /** * Creates an `github:environment:create` Scaffolder action that creates a Github Environment. @@ -35,8 +36,9 @@ import { Entity } from '@backstage/catalog-model'; export function createGithubEnvironmentAction(options: { integrations: ScmIntegrationRegistry; catalogClient?: CatalogApi; + auth?: AuthService; }) { - const { integrations, catalogClient } = options; + const { integrations, catalogClient, auth } = options; // For more information on how to define custom actions, see // https://backstage.io/docs/features/software-templates/writing-custom-actions return createTemplateAction<{ @@ -140,7 +142,8 @@ export function createGithubEnvironmentAction(options: { reviewers: { title: 'Reviewers', type: 'array', - description: 'Reviewers for this environment', + description: + 'Reviewers for this environment. Must be a list of Backstage entity references.', items: { type: 'string', }, @@ -163,6 +166,11 @@ export function createGithubEnvironmentAction(options: { reviewers, } = ctx.input; + const { token } = (await auth?.getPluginRequestToken({ + onBehalfOf: await ctx.getInitiatorCredentials(), + targetPluginId: 'catalog', + })) ?? { token: ctx.secrets?.backstageToken }; + // When environment creation step is executed right after a repo publish step, the repository might not be available immediately. // Add a 2-second delay before initiating the steps in this action. await new Promise(resolve => setTimeout(resolve, 2000)); @@ -190,9 +198,14 @@ export function createGithubEnvironmentAction(options: { if (reviewers) { let reviewersEntityRefs: Array = []; // Fetch reviewers from Catalog - const catalogResponse = await catalogClient?.getEntitiesByRefs({ - entityRefs: reviewers, - }); + const catalogResponse = await catalogClient?.getEntitiesByRefs( + { + entityRefs: reviewers, + }, + { + token, + }, + ); if (catalogResponse?.items?.length) { reviewersEntityRefs = catalogResponse.items; } From 2a9bd224a82e04a0ee97a8a9ce053e30437c23d2 Mon Sep 17 00:00:00 2001 From: Jason Liu Date: Thu, 5 Dec 2024 22:18:52 +1100 Subject: [PATCH 2/2] Pass auth service when initializing GitHub scaffolder actions Signed-off-by: Jason Liu --- plugins/scaffolder-backend-module-github/src/module.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend-module-github/src/module.ts b/plugins/scaffolder-backend-module-github/src/module.ts index f837ad045698c..0387473fdb0fe 100644 --- a/plugins/scaffolder-backend-module-github/src/module.ts +++ b/plugins/scaffolder-backend-module-github/src/module.ts @@ -51,8 +51,9 @@ export const githubModule = createBackendModule({ scaffolder: scaffolderActionsExtensionPoint, config: coreServices.rootConfig, discovery: coreServices.discovery, + auth: coreServices.auth, }, - async init({ scaffolder, config, discovery }) { + async init({ scaffolder, config, discovery, auth }) { const integrations = ScmIntegrations.fromConfig(config); const githubCredentialsProvider = DefaultGithubCredentialsProvider.fromIntegrations(integrations); @@ -75,6 +76,7 @@ export const githubModule = createBackendModule({ createGithubEnvironmentAction({ integrations, catalogClient, + auth, }), createGithubIssuesLabelAction({ integrations,