From 04c0b15da7e958a340b6a77d0986e2110499a940 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Wed, 4 Jun 2025 15:49:52 +0200 Subject: [PATCH] feat: migrating things to using catalogService and removing older exports Signed-off-by: benjdlambert --- .../package.json | 2 +- .../report.api.md | 6 +- .../githubEnvironment.examples.test.ts | 16 +- .../src/actions/githubEnvironment.test.ts | 31 +- .../src/actions/githubEnvironment.ts | 17 +- .../src/module.ts | 13 +- plugins/scaffolder-backend/package.json | 1 - plugins/scaffolder-backend/report.api.md | 31 +- .../src/ScaffolderPlugin.ts | 15 +- .../builtin/catalog/fetch.examples.test.ts | 22 +- .../actions/builtin/catalog/fetch.test.ts | 99 +++---- .../actions/builtin/catalog/fetch.ts | 23 +- .../builtin/catalog/register.examples.test.ts | 22 +- .../actions/builtin/catalog/register.test.ts | 50 ++-- .../actions/builtin/catalog/register.ts | 21 +- .../actions/builtin/createBuiltinActions.ts | 268 ------------------ .../src/scaffolder/actions/builtin/index.ts | 1 - .../src/scaffolder/dryrun/createDryRunner.ts | 4 +- .../src/scaffolder/tasks/StorageTaskBroker.ts | 8 +- .../src/scaffolder/tasks/TaskWorker.ts | 9 +- .../scaffolder-backend/src/service/helpers.ts | 19 +- .../src/service/router.test.ts | 25 +- .../scaffolder-backend/src/service/router.ts | 62 +--- yarn.lock | 3 +- 24 files changed, 179 insertions(+), 589 deletions(-) delete mode 100644 plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts diff --git a/plugins/scaffolder-backend-module-github/package.json b/plugins/scaffolder-backend-module-github/package.json index ae5c7ede35..1df39a57be 100644 --- a/plugins/scaffolder-backend-module-github/package.json +++ b/plugins/scaffolder-backend-module-github/package.json @@ -43,11 +43,11 @@ }, "dependencies": { "@backstage/backend-plugin-api": "workspace:^", - "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", "@backstage/types": "workspace:^", "@octokit/webhooks": "^10.9.2", diff --git a/plugins/scaffolder-backend-module-github/report.api.md b/plugins/scaffolder-backend-module-github/report.api.md index 2c437d16dd..ebc08f32a8 100644 --- a/plugins/scaffolder-backend-module-github/report.api.md +++ b/plugins/scaffolder-backend-module-github/report.api.md @@ -3,9 +3,8 @@ > 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 { CatalogService } from '@backstage/plugin-catalog-node'; import { Config } from '@backstage/config'; import { createPullRequest } from 'octokit-plugin-create-pull-request'; import { GithubCredentialsProvider } from '@backstage/integration'; @@ -111,8 +110,7 @@ export function createGithubDeployKeyAction(options: { // @public export function createGithubEnvironmentAction(options: { integrations: ScmIntegrationRegistry; - catalogClient?: CatalogApi; - auth?: AuthService; + catalogService: CatalogService; }): TemplateAction< { repoUrl: string; diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.examples.test.ts index 4bb41a7273..dc1952f9cb 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.examples.test.ts @@ -16,11 +16,11 @@ import { createGithubEnvironmentAction } from './githubEnvironment'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; +import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import yaml from 'yaml'; import { examples } from './gitHubEnvironment.examples'; -import { CatalogApi } from '@backstage/catalog-client'; const mockOctokit = { rest: { @@ -42,9 +42,7 @@ const mockOctokit = { }, }, }; -const mockCatalogClient: Partial = { - getEntitiesByRefs: jest.fn(), -}; + jest.mock('octokit', () => ({ Octokit: class { constructor() { @@ -52,9 +50,6 @@ jest.mock('octokit', () => ({ } }, })); -jest.mock('@backstage/catalog-client', () => ({ - CatalogClient: mockCatalogClient, -})); const publicKey = '2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvvcCU='; @@ -72,6 +67,7 @@ describe('github:environment:create examples', () => { let action: TemplateAction; const mockContext = createMockActionContext(); + const mockCatalogService = catalogServiceMock.mock(); beforeEach(() => { mockOctokit.rest.actions.getEnvironmentPublicKey.mockResolvedValue({ @@ -95,15 +91,17 @@ describe('github:environment:create examples', () => { id: 2, }, }); - (mockCatalogClient.getEntitiesByRefs as jest.Mock).mockResolvedValue({ + mockCatalogService.getEntitiesByRefs.mockResolvedValue({ items: [ { + apiVersion: 'v1', kind: 'User', metadata: { name: 'johndoe', }, }, { + apiVersion: 'v1', kind: 'Group', metadata: { name: 'team-a', @@ -114,7 +112,7 @@ describe('github:environment:create examples', () => { action = createGithubEnvironmentAction({ integrations, - catalogClient: mockCatalogClient as CatalogApi, + catalogService: mockCatalogService, }); }); 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 997d21ec4b..a4fa9ceff1 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.test.ts @@ -19,10 +19,10 @@ import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test- 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'; +import { mockCredentials } from '@backstage/backend-test-utils'; import { Octokit } from 'octokit'; +import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; const octokitMock = Octokit as unknown as jest.Mock; @@ -47,18 +47,10 @@ const mockOctokit = { }, }; -const mockCatalogClient: Partial = { - getEntitiesByRefs: jest.fn(), -}; - jest.mock('octokit', () => ({ Octokit: jest.fn(), })); -jest.mock('@backstage/catalog-client', () => ({ - CatalogClient: mockCatalogClient, -})); - const publicKey = '2Sg8iYjAxxmI2LvUXpJjkYrMxURPc8r+dB7TJyvvcCU='; describe('github:environment:create', () => { @@ -72,14 +64,10 @@ describe('github:environment:create', () => { }); const integrations = ScmIntegrations.fromConfig(config); + const mockCatalogService = catalogServiceMock.mock(); const credentials = mockCredentials.user(); - const token = mockCredentials.service.token({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - let action: TemplateAction; const mockContext = createMockActionContext({ @@ -87,7 +75,6 @@ describe('github:environment:create', () => { repoUrl: 'github.com?repo=repository&owner=owner', name: 'envname', }, - secrets: { backstageToken: token }, }); beforeEach(() => { @@ -114,15 +101,18 @@ describe('github:environment:create', () => { id: 2, }, }); - (mockCatalogClient.getEntitiesByRefs as jest.Mock).mockResolvedValue({ + + mockCatalogService.getEntitiesByRefs.mockResolvedValue({ items: [ { + apiVersion: '1', kind: 'User', metadata: { name: 'johndoe', }, }, { + apiVersion: '1', kind: 'Group', metadata: { name: 'team-a', @@ -133,8 +123,7 @@ describe('github:environment:create', () => { action = createGithubEnvironmentAction({ integrations, - catalogClient: mockCatalogClient as CatalogApi, - auth: mockServices.auth(), + catalogService: mockCatalogService, }); }); @@ -496,11 +485,11 @@ describe('github:environment:create', () => { }, }); - expect(mockCatalogClient.getEntitiesByRefs).toHaveBeenCalledWith( + expect(mockCatalogService.getEntitiesByRefs).toHaveBeenCalledWith( { entityRefs: ['group:default/team-a', 'user:default/johndoe'], }, - { token }, + { credentials }, ); expect( diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts index 11a30fa4e2..e8812091b3 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts @@ -24,9 +24,8 @@ import { getOctokitOptions } from '../util'; import { Octokit } from 'octokit'; 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'; +import { CatalogService } from '@backstage/plugin-catalog-node'; /** * Creates an `github:environment:create` Scaffolder action that creates a Github Environment. @@ -35,10 +34,9 @@ import { AuthService } from '@backstage/backend-plugin-api'; */ export function createGithubEnvironmentAction(options: { integrations: ScmIntegrationRegistry; - catalogClient?: CatalogApi; - auth?: AuthService; + catalogService: CatalogService; }) { - const { integrations, catalogClient, auth } = options; + const { integrations, catalogService } = options; // For more information on how to define custom actions, see // https://backstage.io/docs/features/software-templates/writing-custom-actions return createTemplateAction({ @@ -147,11 +145,6 @@ Wildcard characters will not match \`/\`. For example, to match tags that begin 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,12 +183,12 @@ Wildcard characters will not match \`/\`. For example, to match tags that begin if (reviewers) { let reviewersEntityRefs: Array = []; // Fetch reviewers from Catalog - const catalogResponse = await catalogClient?.getEntitiesByRefs( + const catalogResponse = await catalogService?.getEntitiesByRefs( { entityRefs: reviewers, }, { - token, + credentials: await ctx.getInitiatorCredentials(), }, ); if (catalogResponse?.items?.length) { diff --git a/plugins/scaffolder-backend-module-github/src/module.ts b/plugins/scaffolder-backend-module-github/src/module.ts index 02dfc2d7f3..59e1398c87 100644 --- a/plugins/scaffolder-backend-module-github/src/module.ts +++ b/plugins/scaffolder-backend-module-github/src/module.ts @@ -39,8 +39,8 @@ import { DefaultGithubCredentialsProvider, ScmIntegrations, } from '@backstage/integration'; -import { CatalogClient } from '@backstage/catalog-client'; import { createHandleAutocompleteRequest } from './autocomplete/autocomplete'; +import { catalogServiceRef } from '@backstage/plugin-catalog-node'; /** * @public @@ -54,17 +54,13 @@ export const githubModule = createBackendModule({ deps: { scaffolder: scaffolderActionsExtensionPoint, config: coreServices.rootConfig, - discovery: coreServices.discovery, - auth: coreServices.auth, + catalogService: catalogServiceRef, autocomplete: scaffolderAutocompleteExtensionPoint, }, - async init({ scaffolder, config, discovery, auth, autocomplete }) { + async init({ scaffolder, config, autocomplete, catalogService }) { const integrations = ScmIntegrations.fromConfig(config); const githubCredentialsProvider = DefaultGithubCredentialsProvider.fromIntegrations(integrations); - const catalogClient = new CatalogClient({ - discoveryApi: discovery, - }); scaffolder.addActions( createGithubActionsDispatchAction({ @@ -80,8 +76,7 @@ export const githubModule = createBackendModule({ }), createGithubEnvironmentAction({ integrations, - catalogClient, - auth, + catalogService, }), createGithubIssuesLabelAction({ integrations, diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index c0e67af14c..d3db857d91 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -64,7 +64,6 @@ "@backstage/backend-common": "^0.25.0", "@backstage/backend-defaults": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", - "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", diff --git a/plugins/scaffolder-backend/report.api.md b/plugins/scaffolder-backend/report.api.md index 9b0e0b05c3..39504bb524 100644 --- a/plugins/scaffolder-backend/report.api.md +++ b/plugins/scaffolder-backend/report.api.md @@ -7,7 +7,7 @@ import { AuditorService } from '@backstage/backend-plugin-api'; import { AuthService } from '@backstage/backend-plugin-api'; import { BackendFeature } from '@backstage/backend-plugin-api'; import { BackstageCredentials } from '@backstage/backend-plugin-api'; -import { CatalogApi } from '@backstage/catalog-client'; +import { CatalogService } from '@backstage/plugin-catalog-node'; import { Config } from '@backstage/config'; import { DatabaseService } from '@backstage/backend-plugin-api'; import { Duration } from 'luxon'; @@ -16,7 +16,7 @@ import { HumanDuration } from '@backstage/types'; import { JsonObject } from '@backstage/types'; import { JsonValue } from '@backstage/types'; import { Knex } from 'knex'; -import { Logger } from 'winston'; +import { LoggerService } from '@backstage/backend-plugin-api'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { PermissionRule } from '@backstage/plugin-permission-node'; import { PermissionRuleParams } from '@backstage/plugin-permission-common'; @@ -51,28 +51,10 @@ export type ActionPermissionRuleInput< TParams >; -// @public -export const createBuiltinActions: ( - options: CreateBuiltInActionsOptions, -) => TemplateAction[]; - -// @public -export interface CreateBuiltInActionsOptions { - additionalTemplateFilters?: Record; - // (undocumented) - additionalTemplateGlobals?: Record; - auth?: AuthService; - catalogClient: CatalogApi; - config: Config; - integrations: ScmIntegrations; - reader: UrlReaderService; -} - // @public export function createCatalogRegisterAction(options: { - catalogClient: CatalogApi; + catalogService: CatalogService; integrations: ScmIntegrations; - auth?: AuthService; }): TemplateAction< | { catalogInfoUrl: string; @@ -124,8 +106,7 @@ export function createDebugLogAction(): TemplateAction< // @public export function createFetchCatalogEntityAction(options: { - catalogClient: CatalogApi; - auth?: AuthService; + catalogService: CatalogService; }): TemplateAction< { entityRef?: string | undefined; @@ -290,7 +271,7 @@ export type CreateWorkerOptions = { actionRegistry: TemplateActionRegistry; integrations: ScmIntegrations; workingDirectory: string; - logger: Logger; + logger: LoggerService; auditor?: AuditorService; additionalTemplateFilters?: Record; concurrentTasksLimit?: number; @@ -427,7 +408,7 @@ export class TaskManager implements TaskContext { task: CurrentClaimedTask, storage: TaskStore, abortSignal: AbortSignal, - logger: Logger, + logger: LoggerService, auth?: AuthService, config?: Config, additionalWorkspaceProviders?: Record, diff --git a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts index fbfb75fe21..2c34fb1fdb 100644 --- a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts +++ b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts @@ -19,7 +19,7 @@ import { createBackendPlugin, } from '@backstage/backend-plugin-api'; import { ScmIntegrations } from '@backstage/integration'; -import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; +import { catalogServiceRef } from '@backstage/plugin-catalog-node'; import { eventsServiceRef } from '@backstage/plugin-events-node'; import { TaskBroker, TemplateAction } from '@backstage/plugin-scaffolder-node'; import { @@ -137,7 +137,7 @@ export const scaffolderPlugin = createBackendPlugin({ httpRouter: coreServices.httpRouter, httpAuth: coreServices.httpAuth, auditor: coreServices.auditor, - catalogClient: catalogServiceRef, + catalogService: catalogServiceRef, events: eventsServiceRef, }, async init({ @@ -149,7 +149,7 @@ export const scaffolderPlugin = createBackendPlugin({ auth, httpRouter, httpAuth, - catalogClient, + catalogService, permissions, events, auditor, @@ -191,8 +191,8 @@ export const scaffolderPlugin = createBackendPlugin({ createDebugLogAction(), createWaitAction(), // todo(blam): maybe these should be a -catalog module? - createCatalogRegisterAction({ catalogClient, integrations, auth }), - createFetchCatalogEntityAction({ catalogClient, auth }), + createCatalogRegisterAction({ catalogService, integrations }), + createFetchCatalogEntityAction({ catalogService }), createCatalogWriteAction(), createFilesystemDeleteAction(), createFilesystemRenameAction(), @@ -206,11 +206,10 @@ export const scaffolderPlugin = createBackendPlugin({ ); const router = await createRouter({ - logger: log, + logger, config, database, - catalogClient, - reader, + catalogService, lifecycle, actions, taskBroker, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.examples.test.ts index 4854e39f3b..d230a1b251 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.examples.test.ts @@ -19,7 +19,7 @@ import { Entity } from '@backstage/catalog-model'; import { createFetchCatalogEntityAction } from './fetch'; import { examples } from './fetch.examples'; import yaml from 'yaml'; -import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; +import { mockCredentials } from '@backstage/backend-test-utils'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; describe('catalog:fetch examples', () => { @@ -31,27 +31,19 @@ describe('catalog:fetch examples', () => { }, } as Entity; - const catalogClient = catalogServiceMock({ entities: [entity] }); + const catalogService = catalogServiceMock({ entities: [entity] }); const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalogService, }); const credentials = mockCredentials.user(); - const token = mockCredentials.service.token({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - - const mockContext = createMockActionContext({ - secrets: { backstageToken: token }, - }); + const mockContext = createMockActionContext(); beforeEach(() => { jest.resetAllMocks(); - jest.spyOn(catalogClient, 'getEntityByRef'); + jest.spyOn(catalogService, 'getEntityByRef'); }); describe('fetch single entity', () => { @@ -61,9 +53,9 @@ describe('catalog:fetch examples', () => { input: yaml.parse(examples[0].example).steps[0].input, }); - expect(catalogClient.getEntityByRef).toHaveBeenCalledWith( + expect(catalogService.getEntityByRef).toHaveBeenCalledWith( 'component:default/name', - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith('entity', entity); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.test.ts index e049c7522c..03953ea7ae 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.test.ts @@ -17,7 +17,7 @@ import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; import { Entity } from '@backstage/catalog-model'; import { createFetchCatalogEntityAction } from './fetch'; -import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; +import { mockCredentials } from '@backstage/backend-test-utils'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; describe('catalog:fetch', () => { @@ -31,14 +31,7 @@ describe('catalog:fetch', () => { const credentials = mockCredentials.user(); - const token = mockCredentials.service.token({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - - const mockContext = createMockActionContext({ - secrets: { backstageToken: token }, - }); + const mockContext = createMockActionContext(); beforeEach(() => { jest.resetAllMocks(); @@ -46,11 +39,10 @@ describe('catalog:fetch', () => { describe('fetch single entity', () => { it('should return entity from catalog', async () => { - const catalogClient = catalogServiceMock({ entities: [component] }); - jest.spyOn(catalogClient, 'getEntityByRef'); + const catalogService = catalogServiceMock({ entities: [component] }); + jest.spyOn(catalogService, 'getEntityByRef'); const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalogService, }); await action.handler({ @@ -60,21 +52,20 @@ describe('catalog:fetch', () => { }, }); - expect(catalogClient.getEntityByRef).toHaveBeenCalledWith( + expect(catalogService.getEntityByRef).toHaveBeenCalledWith( 'component:default/test', - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith('entity', component); }); it('should throw error if entity fetch fails from catalog and optional is false', async () => { - const catalogClient = catalogServiceMock.mock({ + const catalogService = catalogServiceMock.mock({ getEntityByRef: () => Promise.reject(new Error('Not found')), }); - jest.spyOn(catalogClient, 'getEntityByRef'); + jest.spyOn(catalogService, 'getEntityByRef'); const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalogService, }); await expect( @@ -86,19 +77,18 @@ describe('catalog:fetch', () => { }), ).rejects.toThrow('Not found'); - expect(catalogClient.getEntityByRef).toHaveBeenCalledWith( + expect(catalogService.getEntityByRef).toHaveBeenCalledWith( 'component:default/test', - { token }, + { credentials }, ); expect(mockContext.output).not.toHaveBeenCalled(); }); it('should throw error if entity not in catalog and optional is false', async () => { - const catalogClient = catalogServiceMock({ entities: [] }); - jest.spyOn(catalogClient, 'getEntityByRef'); + const catalogService = catalogServiceMock({ entities: [] }); + jest.spyOn(catalogService, 'getEntityByRef'); const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalogService, }); await expect( @@ -110,9 +100,9 @@ describe('catalog:fetch', () => { }), ).rejects.toThrow('Entity component:default/test not found'); - expect(catalogClient.getEntityByRef).toHaveBeenCalledWith( + expect(catalogService.getEntityByRef).toHaveBeenCalledWith( 'component:default/test', - { token }, + { credentials }, ); expect(mockContext.output).not.toHaveBeenCalled(); }); @@ -125,11 +115,10 @@ describe('catalog:fetch', () => { namespace: 'ns', }, } as Entity; - const catalogClient = catalogServiceMock({ entities: [entity] }); - jest.spyOn(catalogClient, 'getEntityByRef'); + const catalogService = catalogServiceMock({ entities: [entity] }); + jest.spyOn(catalogService, 'getEntityByRef'); const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalogService, }); await action.handler({ @@ -141,9 +130,9 @@ describe('catalog:fetch', () => { }, }); - expect(catalogClient.getEntityByRef).toHaveBeenCalledWith( + expect(catalogService.getEntityByRef).toHaveBeenCalledWith( 'group:ns/test', - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith('entity', entity); }); @@ -151,11 +140,10 @@ describe('catalog:fetch', () => { describe('fetch multiple entities', () => { it('should return entities from catalog', async () => { - const catalogClient = catalogServiceMock({ entities: [component] }); - jest.spyOn(catalogClient, 'getEntitiesByRefs'); + const catalogService = catalogServiceMock({ entities: [component] }); + jest.spyOn(catalogService, 'getEntitiesByRefs'); const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalogService, }); await action.handler({ @@ -165,19 +153,18 @@ describe('catalog:fetch', () => { }, }); - expect(catalogClient.getEntitiesByRefs).toHaveBeenCalledWith( + expect(catalogService.getEntitiesByRefs).toHaveBeenCalledWith( { entityRefs: ['component:default/test'] }, - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith('entities', [component]); }); it('should throw error if undefined is returned for some entity', async () => { - const catalogClient = catalogServiceMock({ entities: [component] }); - jest.spyOn(catalogClient, 'getEntitiesByRefs'); + const catalogService = catalogServiceMock({ entities: [component] }); + jest.spyOn(catalogService, 'getEntitiesByRefs'); const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalogService, }); await expect( @@ -190,21 +177,20 @@ describe('catalog:fetch', () => { }), ).rejects.toThrow('Entity component:default/test2 not found'); - expect(catalogClient.getEntitiesByRefs).toHaveBeenCalledWith( + expect(catalogService.getEntitiesByRefs).toHaveBeenCalledWith( { entityRefs: ['component:default/test', 'component:default/test2'], }, - { token }, + { credentials }, ); expect(mockContext.output).not.toHaveBeenCalled(); }); it('should return null in case some of the entities not found and optional is true', async () => { - const catalogClient = catalogServiceMock({ entities: [component] }); - jest.spyOn(catalogClient, 'getEntitiesByRefs'); + const catalogService = catalogServiceMock({ entities: [component] }); + jest.spyOn(catalogService, 'getEntitiesByRefs'); const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalogService, }); await action.handler({ @@ -215,9 +201,9 @@ describe('catalog:fetch', () => { }, }); - expect(catalogClient.getEntitiesByRefs).toHaveBeenCalledWith( + expect(catalogService.getEntitiesByRefs).toHaveBeenCalledWith( { entityRefs: ['component:default/test', 'component:default/test2'] }, - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith('entities', [ component, @@ -240,13 +226,12 @@ describe('catalog:fetch', () => { }, kind: 'User', } as Entity; - const catalogClient = catalogServiceMock({ + const catalogService = catalogServiceMock({ entities: [entity1, entity2], }); - jest.spyOn(catalogClient, 'getEntitiesByRefs'); + jest.spyOn(catalogService, 'getEntitiesByRefs'); const action = createFetchCatalogEntityAction({ - catalogClient, - auth: mockServices.auth(), + catalogService, }); await action.handler({ @@ -258,9 +243,9 @@ describe('catalog:fetch', () => { }, }); - expect(catalogClient.getEntitiesByRefs).toHaveBeenCalledWith( + expect(catalogService.getEntitiesByRefs).toHaveBeenCalledWith( { entityRefs: ['group:ns/test', 'user:default/test'] }, - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith('entities', [ diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.ts index e21930ed78..d47159ad49 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.ts @@ -14,11 +14,10 @@ * limitations under the License. */ -import { CatalogApi } from '@backstage/catalog-client'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model'; import { examples } from './fetch.examples'; -import { AuthService } from '@backstage/backend-plugin-api'; +import { CatalogService } from '@backstage/plugin-catalog-node'; const id = 'catalog:fetch'; @@ -28,10 +27,9 @@ const id = 'catalog:fetch'; * @public */ export function createFetchCatalogEntityAction(options: { - catalogClient: CatalogApi; - auth?: AuthService; + catalogService: CatalogService; }) { - const { catalogClient, auth } = options; + const { catalogService } = options; return createTemplateAction({ id, @@ -95,18 +93,13 @@ export function createFetchCatalogEntityAction(options: { throw new Error('Missing entity reference or references'); } - const { token } = (await auth?.getPluginRequestToken({ - onBehalfOf: await ctx.getInitiatorCredentials(), - targetPluginId: 'catalog', - })) ?? { token: ctx.secrets?.backstageToken }; - if (entityRef) { - const entity = await catalogClient.getEntityByRef( + const entity = await catalogService.getEntityByRef( stringifyEntityRef( parseEntityRef(entityRef, { defaultKind, defaultNamespace }), ), { - token, + credentials: await ctx.getInitiatorCredentials(), }, ); @@ -117,7 +110,7 @@ export function createFetchCatalogEntityAction(options: { } if (entityRefs) { - const entities = await catalogClient.getEntitiesByRefs( + const entities = await catalogService.getEntitiesByRefs( { entityRefs: entityRefs.map(ref => stringifyEntityRef( @@ -125,9 +118,7 @@ export function createFetchCatalogEntityAction(options: { ), ), }, - { - token, - }, + { credentials: await ctx.getInitiatorCredentials() }, ); const finalEntities = entities.items.map((e, i) => { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.examples.test.ts index 27690b0df3..b5d7d21ae7 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.examples.test.ts @@ -21,7 +21,7 @@ import { createCatalogRegisterAction } from './register'; import { Entity } from '@backstage/catalog-model'; import { examples } from './register.examples'; import yaml from 'yaml'; -import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; +import { mockCredentials } from '@backstage/backend-test-utils'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; describe('catalog:register', () => { @@ -33,28 +33,22 @@ describe('catalog:register', () => { }), ); - const catalogClient = catalogServiceMock.mock(); + const catalogService = catalogServiceMock.mock(); const action = createCatalogRegisterAction({ integrations, - catalogClient, - auth: mockServices.auth(), + catalogService, }); const credentials = mockCredentials.user(); - const token = mockCredentials.service.token({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - const mockContext = createMockActionContext(); beforeEach(() => { jest.resetAllMocks(); }); it('should register location in catalog', async () => { - catalogClient.addLocation + catalogService.addLocation .mockResolvedValueOnce({ location: null as any, entities: [], @@ -76,16 +70,16 @@ describe('catalog:register', () => { input: yaml.parse(examples[0].example).steps[0].input, }); - expect(catalogClient.addLocation).toHaveBeenNthCalledWith( + expect(catalogService.addLocation).toHaveBeenNthCalledWith( 1, { type: 'url', target: 'http://github.com/backstage/backstage/blob/master/catalog-info.yaml', }, - { token }, + { credentials }, ); - expect(catalogClient.addLocation).toHaveBeenNthCalledWith( + expect(catalogService.addLocation).toHaveBeenNthCalledWith( 2, { dryRun: true, @@ -93,7 +87,7 @@ describe('catalog:register', () => { target: 'http://github.com/backstage/backstage/blob/master/catalog-info.yaml', }, - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith( diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.test.ts index 839ec50886..f905535ad3 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.test.ts @@ -19,7 +19,7 @@ import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { createCatalogRegisterAction } from './register'; import { Entity } from '@backstage/catalog-model'; -import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; +import { mockCredentials } from '@backstage/backend-test-utils'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; describe('catalog:register', () => { @@ -31,21 +31,15 @@ describe('catalog:register', () => { }), ); - const catalogClient = catalogServiceMock.mock(); + const catalogService = catalogServiceMock.mock(); const action = createCatalogRegisterAction({ integrations, - catalogClient, - auth: mockServices.auth(), + catalogService, }); const credentials = mockCredentials.user(); - const token = mockCredentials.service.token({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - const mockContext = createMockActionContext(); beforeEach(() => { @@ -66,7 +60,7 @@ describe('catalog:register', () => { }); it('should register location in catalog', async () => { - catalogClient.addLocation + catalogService.addLocation .mockResolvedValueOnce({ location: null as any, entities: [], @@ -90,22 +84,22 @@ describe('catalog:register', () => { }, }); - expect(catalogClient.addLocation).toHaveBeenNthCalledWith( + expect(catalogService.addLocation).toHaveBeenNthCalledWith( 1, { type: 'url', target: 'http://foo/var', }, - { token }, + { credentials }, ); - expect(catalogClient.addLocation).toHaveBeenNthCalledWith( + expect(catalogService.addLocation).toHaveBeenNthCalledWith( 2, { dryRun: true, type: 'url', target: 'http://foo/var', }, - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith( @@ -119,7 +113,7 @@ describe('catalog:register', () => { }); it('should return entityRef with the Component entity and not the generated location', async () => { - catalogClient.addLocation + catalogService.addLocation .mockResolvedValueOnce({ location: null as any, entities: [], @@ -170,7 +164,7 @@ describe('catalog:register', () => { }); it('should return entityRef with the next non-generated entity if no Component kind can be found', async () => { - catalogClient.addLocation + catalogService.addLocation .mockResolvedValueOnce({ location: null as any, entities: [], @@ -214,7 +208,7 @@ describe('catalog:register', () => { }); it('should return entityRef with the first entity if no non-generated entities can be found', async () => { - catalogClient.addLocation + catalogService.addLocation .mockResolvedValueOnce({ location: null as any, entities: [], @@ -251,7 +245,7 @@ describe('catalog:register', () => { }); it('should not return entityRef if there are no entities', async () => { - catalogClient.addLocation + catalogService.addLocation .mockResolvedValueOnce({ location: null as any, entities: [], @@ -273,7 +267,7 @@ describe('catalog:register', () => { }); it('should ignore failures when dry running the location in the catalog if `optional` is set', async () => { - catalogClient.addLocation + catalogService.addLocation .mockRejectedValueOnce(new Error('Not found')) .mockRejectedValueOnce(new Error('Not found')); await action.handler({ @@ -284,22 +278,22 @@ describe('catalog:register', () => { }, }); - expect(catalogClient.addLocation).toHaveBeenNthCalledWith( + expect(catalogService.addLocation).toHaveBeenNthCalledWith( 1, { type: 'url', target: 'http://foo/var', }, - { token }, + { credentials }, ); - expect(catalogClient.addLocation).toHaveBeenNthCalledWith( + expect(catalogService.addLocation).toHaveBeenNthCalledWith( 2, { dryRun: true, type: 'url', target: 'http://foo/var', }, - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith( @@ -309,7 +303,7 @@ describe('catalog:register', () => { }); it('should fetch entities when adding location in the catalog fails and `optional` is set', async () => { - catalogClient.addLocation + catalogService.addLocation .mockRejectedValueOnce(new Error('Already registered')) .mockResolvedValueOnce({ location: null as any, @@ -331,22 +325,22 @@ describe('catalog:register', () => { }, }); - expect(catalogClient.addLocation).toHaveBeenNthCalledWith( + expect(catalogService.addLocation).toHaveBeenNthCalledWith( 1, { type: 'url', target: 'http://foo/var', }, - { token }, + { credentials }, ); - expect(catalogClient.addLocation).toHaveBeenNthCalledWith( + expect(catalogService.addLocation).toHaveBeenNthCalledWith( 2, { dryRun: true, type: 'url', target: 'http://foo/var', }, - { token }, + { credentials }, ); expect(mockContext.output).toHaveBeenCalledWith( diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts index 46b0e12de7..c881293d3a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts @@ -16,11 +16,10 @@ import { InputError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; -import { CatalogApi } from '@backstage/catalog-client'; import { stringifyEntityRef, Entity } from '@backstage/catalog-model'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { examples } from './register.examples'; -import { AuthService } from '@backstage/backend-plugin-api'; +import { CatalogService } from '@backstage/plugin-catalog-node'; const id = 'catalog:register'; @@ -29,11 +28,10 @@ const id = 'catalog:register'; * @public */ export function createCatalogRegisterAction(options: { - catalogClient: CatalogApi; + catalogService: CatalogService; integrations: ScmIntegrations; - auth?: AuthService; }) { - const { catalogClient, integrations, auth } = options; + const { catalogService, integrations } = options; return createTemplateAction({ id, @@ -99,19 +97,14 @@ export function createCatalogRegisterAction(options: { ctx.logger.info(`Registering ${catalogInfoUrl} in the catalog`); - const { token } = (await auth?.getPluginRequestToken({ - onBehalfOf: await ctx.getInitiatorCredentials(), - targetPluginId: 'catalog', - })) ?? { token: ctx.secrets?.backstageToken }; - try { // 1st try to register the location, this will throw an error if the location already exists (see catch) - await catalogClient.addLocation( + await catalogService.addLocation( { type: 'url', target: catalogInfoUrl, }, - token ? { token } : {}, + { credentials: await ctx.getInitiatorCredentials() }, ); } catch (e) { if (!input.optional) { @@ -122,13 +115,13 @@ export function createCatalogRegisterAction(options: { try { // 2nd retry the registration as a dry run, this will not throw an error if the location already exists - const result = await catalogClient.addLocation( + const result = await catalogService.addLocation( { dryRun: true, type: 'url', target: catalogInfoUrl, }, - token ? { token } : {}, + { credentials: await ctx.getInitiatorCredentials() }, ); if (result.entities.length) { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts deleted file mode 100644 index d2a03412cc..0000000000 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { CatalogApi } from '@backstage/catalog-client'; -import { Config } from '@backstage/config'; -import { - DefaultGithubCredentialsProvider, - GithubCredentialsProvider, - ScmIntegrations, -} from '@backstage/integration'; -import { - TemplateAction, - TemplateFilter, - TemplateGlobal, -} from '@backstage/plugin-scaffolder-node'; -import { - createCatalogRegisterAction, - createCatalogWriteAction, - createFetchCatalogEntityAction, -} from './catalog'; - -import { createDebugLogAction, createWaitAction } from './debug'; -import { - createFetchPlainAction, - createFetchPlainFileAction, - createFetchTemplateAction, - createFetchTemplateFileAction, -} from './fetch'; -import { - createFilesystemDeleteAction, - createFilesystemReadDirAction, - createFilesystemRenameAction, -} from './filesystem'; -import { - createGithubActionsDispatchAction, - createGithubAutolinksAction, - createGithubDeployKeyAction, - createGithubEnvironmentAction, - createGithubIssuesLabelAction, - createGithubRepoCreateAction, - createGithubRepoPushAction, - createGithubWebhookAction, - createPublishGithubAction, - createPublishGithubPullRequestAction, -} from '@backstage/plugin-scaffolder-backend-module-github'; - -import { createPublishAzureAction } from '@backstage/plugin-scaffolder-backend-module-azure'; - -import { createPublishBitbucketAction } from '@backstage/plugin-scaffolder-backend-module-bitbucket'; - -import { - createPublishBitbucketCloudAction, - createBitbucketPipelinesRunAction, - createPublishBitbucketCloudPullRequestAction, -} from '@backstage/plugin-scaffolder-backend-module-bitbucket-cloud'; - -import { - createPublishBitbucketServerAction, - createPublishBitbucketServerPullRequestAction, -} from '@backstage/plugin-scaffolder-backend-module-bitbucket-server'; - -import { - createPublishGerritAction, - createPublishGerritReviewAction, -} from '@backstage/plugin-scaffolder-backend-module-gerrit'; - -import { - createPublishGitlabAction, - createGitlabRepoPushAction, - createPublishGitlabMergeRequestAction, -} from '@backstage/plugin-scaffolder-backend-module-gitlab'; - -import { createPublishGiteaAction } from '@backstage/plugin-scaffolder-backend-module-gitea'; -import { AuthService, UrlReaderService } from '@backstage/backend-plugin-api'; - -/** - * The options passed to {@link createBuiltinActions} - * @public - */ -export interface CreateBuiltInActionsOptions { - /** - * The {@link @backstage/backend-plugin-api#UrlReaderService} interface that will be used in the default actions. - */ - reader: UrlReaderService; - /** - * The {@link @backstage/integrations#ScmIntegrations} that will be used in the default actions. - */ - integrations: ScmIntegrations; - /** - * The {@link @backstage/catalog-client#CatalogApi} that will be used in the default actions. - */ - catalogClient: CatalogApi; - /** - * The {@link @backstage/backend-plugin-api#AuthService} that will be used in the default actions. - */ - auth?: AuthService; - /** - * The {@link @backstage/config#Config} that will be used in the default actions. - */ - config: Config; - /** - * Additional custom filters that will be passed to the nunjucks template engine for use in - * Template Manifests and also template skeleton files when using `fetch:template`. - */ - additionalTemplateFilters?: Record; - additionalTemplateGlobals?: Record; -} - -/** - * A function to generate create a list of default actions that the scaffolder provides. - * Is called internally in the default setup, but can be used when adding your own actions or overriding the default ones - * - * TODO(blam): version 2 of the scaffolder shouldn't ship with the additional modules. We should ship the basics, and let people install - * modules for the providers they want to use. - * @public - * @returns A list of actions that can be used in the scaffolder - * - */ -export const createBuiltinActions = ( - options: CreateBuiltInActionsOptions, -): TemplateAction[] => { - const { - reader, - integrations, - catalogClient, - auth, - config, - additionalTemplateFilters, - additionalTemplateGlobals, - } = options; - - const githubCredentialsProvider: GithubCredentialsProvider = - DefaultGithubCredentialsProvider.fromIntegrations(integrations); - - const actions = [ - createFetchPlainAction({ - reader, - integrations, - }), - createFetchPlainFileAction({ - reader, - integrations, - }), - createFetchTemplateAction({ - integrations, - reader, - additionalTemplateFilters, - additionalTemplateGlobals, - }), - createFetchTemplateFileAction({ - integrations, - reader, - additionalTemplateFilters, - additionalTemplateGlobals, - }), - createPublishGerritAction({ - integrations, - config, - }), - createPublishGerritReviewAction({ - integrations, - config, - }), - createPublishGiteaAction({ - integrations, - config, - }), - createPublishGithubAction({ - integrations, - config, - githubCredentialsProvider, - }), - createPublishGithubPullRequestAction({ - integrations, - githubCredentialsProvider, - config, - }), - createPublishGitlabAction({ - integrations, - config, - }), - createPublishGitlabMergeRequestAction({ - integrations, - }), - createGitlabRepoPushAction({ - integrations, - }), - createPublishBitbucketAction({ - integrations, - config, - }), - createPublishBitbucketCloudAction({ - integrations, - config, - }), - createPublishBitbucketCloudPullRequestAction({ integrations, config }), - createPublishBitbucketServerAction({ - integrations, - config, - }), - createPublishBitbucketServerPullRequestAction({ - integrations, - config, - }), - createPublishAzureAction({ - integrations, - config, - }), - createDebugLogAction(), - createWaitAction(), - createCatalogRegisterAction({ catalogClient, integrations, auth }), - createFetchCatalogEntityAction({ catalogClient, auth }), - createCatalogWriteAction(), - createFilesystemDeleteAction(), - createFilesystemReadDirAction(), - createFilesystemRenameAction(), - createGithubActionsDispatchAction({ - integrations, - githubCredentialsProvider, - }), - createGithubWebhookAction({ - integrations, - githubCredentialsProvider, - }), - createGithubIssuesLabelAction({ - integrations, - githubCredentialsProvider, - }), - createGithubRepoCreateAction({ - integrations, - githubCredentialsProvider, - }), - createGithubRepoPushAction({ - integrations, - config, - githubCredentialsProvider, - }), - createGithubEnvironmentAction({ - integrations, - catalogClient, - }), - createGithubDeployKeyAction({ - integrations, - }), - createGithubAutolinksAction({ - integrations, - githubCredentialsProvider, - }), - createBitbucketPipelinesRunAction({ - integrations, - }), - ]; - - return actions as TemplateAction[]; -}; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts index 6fdbb24484..a4501c805f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts @@ -15,7 +15,6 @@ */ export * from './catalog'; -export * from './createBuiltinActions'; export * from './debug'; export * from './fetch'; export * from './filesystem'; diff --git a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts index aa5a3f5119..9d4494c536 100644 --- a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts @@ -17,6 +17,7 @@ import { AuditorService, BackstageCredentials, + LoggerService, } from '@backstage/backend-plugin-api'; import type { UserEntity } from '@backstage/catalog-model'; import { ScmIntegrations } from '@backstage/integration'; @@ -36,7 +37,6 @@ import fs from 'fs-extra'; import path from 'path'; import { fileURLToPath } from 'url'; import { v4 as uuid } from 'uuid'; -import { Logger } from 'winston'; import { TemplateActionRegistry } from '../actions'; import { NunjucksWorkflowRunner } from '../tasks/NunjucksWorkflowRunner'; import { DecoratedActionsRegistry } from './DecoratedActionsRegistry'; @@ -61,7 +61,7 @@ interface DryRunResult { /** @internal */ export type TemplateTesterCreateOptions = { - logger: Logger; + logger: LoggerService; auditor?: AuditorService; integrations: ScmIntegrations; actionRegistry: TemplateActionRegistry; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index 7c7b3e6ac3..86f9f7fcf7 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -18,6 +18,7 @@ import { AuditorService, AuthService, BackstageCredentials, + LoggerService, } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; @@ -38,7 +39,6 @@ import { Observable, createDeferred, } from '@backstage/types'; -import { Logger } from 'winston'; import ObservableImpl from 'zen-observable'; import { DefaultWorkspaceService, WorkspaceService } from './WorkspaceService'; import { readDuration } from './helper'; @@ -71,7 +71,7 @@ export class TaskManager implements TaskContext { task: CurrentClaimedTask, storage: TaskStore, abortSignal: AbortSignal, - logger: Logger, + logger: LoggerService, auth?: AuthService, config?: Config, additionalWorkspaceProviders?: Record, @@ -100,7 +100,7 @@ export class TaskManager implements TaskContext { private readonly task: CurrentClaimedTask, private readonly storage: TaskStore, private readonly signal: AbortSignal, - private readonly logger: Logger, + private readonly logger: LoggerService, private readonly workspaceService: WorkspaceService, private readonly auth?: AuthService, ) {} @@ -269,7 +269,7 @@ export interface CurrentClaimedTask { export class StorageTaskBroker implements TaskBroker { constructor( private readonly storage: TaskStore, - private readonly logger: Logger, + private readonly logger: LoggerService, private readonly config?: Config, private readonly auth?: AuthService, private readonly additionalWorkspaceProviders?: Record< diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index 10fa5d4245..b0c3166ff6 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { AuditorService } from '@backstage/backend-plugin-api'; +import { AuditorService, LoggerService } from '@backstage/backend-plugin-api'; import { assertError, stringifyError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; @@ -25,7 +25,6 @@ import { TemplateGlobal, } from '@backstage/plugin-scaffolder-node'; import PQueue from 'p-queue'; -import { Logger } from 'winston'; import { TemplateActionRegistry } from '../actions'; import { NunjucksWorkflowRunner } from './NunjucksWorkflowRunner'; import { WorkflowRunner } from './types'; @@ -43,7 +42,7 @@ export type TaskWorkerOptions = { }; concurrentTasksLimit: number; permissions?: PermissionEvaluator; - logger?: Logger; + logger?: LoggerService; auditor?: AuditorService; gracefulShutdown?: boolean; }; @@ -58,7 +57,7 @@ export type CreateWorkerOptions = { actionRegistry: TemplateActionRegistry; integrations: ScmIntegrations; workingDirectory: string; - logger: Logger; + logger: LoggerService; auditor?: AuditorService; additionalTemplateFilters?: Record; /** @@ -86,7 +85,7 @@ export type CreateWorkerOptions = { */ export class TaskWorker { private taskQueue: PQueue; - private logger: Logger | undefined; + private logger: LoggerService | undefined; private auditor: AuditorService | undefined; private stopWorkers: boolean; diff --git a/plugins/scaffolder-backend/src/service/helpers.ts b/plugins/scaffolder-backend/src/service/helpers.ts index cd55068d98..d92fa2dfd7 100644 --- a/plugins/scaffolder-backend/src/service/helpers.ts +++ b/plugins/scaffolder-backend/src/service/helpers.ts @@ -14,7 +14,10 @@ * limitations under the License. */ -import { CatalogApi } from '@backstage/catalog-client'; +import { + BackstageCredentials, + LoggerService, +} from '@backstage/backend-plugin-api'; import { ANNOTATION_LOCATION, ANNOTATION_SOURCE_LOCATION, @@ -25,14 +28,14 @@ import { } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { assertError, InputError, NotFoundError } from '@backstage/errors'; +import { CatalogService } from '@backstage/plugin-catalog-node'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import fs from 'fs-extra'; import os from 'os'; -import { Logger } from 'winston'; export async function getWorkingDirectory( config: Config, - logger: Logger, + logger: LoggerService, ): Promise { if (!config.has('backend.workingDirectory')) { return os.tmpdir(); @@ -89,16 +92,18 @@ export function getEntityBaseUrl(entity: Entity): string | undefined { */ export async function findTemplate(options: { entityRef: CompoundEntityRef; - token?: string; - catalogApi: CatalogApi; + catalogService: CatalogService; + credentials: BackstageCredentials; }): Promise { - const { entityRef, token, catalogApi } = options; + const { entityRef, catalogService, credentials } = options; if (entityRef.kind.toLocaleLowerCase('en-US') !== 'template') { throw new InputError(`Invalid kind, only 'Template' kind is supported`); } - const template = await catalogApi.getEntityByRef(entityRef, { token }); + const template = await catalogService.getEntityByRef(entityRef, { + credentials, + }); if (!template) { throw new NotFoundError( `Template ${stringifyEntityRef(entityRef)} not found`, diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index 8808e516ee..17c893d78f 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -53,7 +53,6 @@ import { createTemplateGlobalFunction, createTemplateGlobalValue, } from '@backstage/plugin-scaffolder-node/alpha'; -import { UrlReaders } from '@backstage/backend-defaults/urlReader'; import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils'; import { EventsService } from '@backstage/plugin-events-node'; import { DatabaseService } from '@backstage/backend-plugin-api'; @@ -96,11 +95,6 @@ function createDatabase(): DatabaseService { ).forPlugin('scaffolder'); } -const mockUrlReader = UrlReaders.default({ - logger: mockServices.logger.mock(), - config: new ConfigReader({}), -}); - const config = new ConfigReader({}); describe.each([ @@ -188,7 +182,7 @@ describe.each([ let app: express.Express; let loggerSpy: jest.SpyInstance; let taskBroker: TaskBroker; - const catalogClient = catalogServiceMock.mock(); + const catalogService = catalogServiceMock.mock(); const permissionApi = { authorize: jest.fn(), authorizeConditional: jest.fn(), @@ -303,8 +297,7 @@ describe.each([ logger: logger, config: new ConfigReader({}), database: createDatabase(), - catalogClient, - reader: mockUrlReader, + catalogService, taskBroker, permissions: permissionApi, auth, @@ -315,7 +308,7 @@ describe.each([ }); app = express().use(router); - catalogClient.getEntityByRef.mockImplementation(async ref => { + catalogService.getEntityByRef.mockImplementation(async ref => { const { kind } = parseEntityRef(ref); if (kind.toLocaleLowerCase() === 'template') { @@ -851,9 +844,9 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ directoryContents: [], }); - expect(catalogClient.getEntityByRef).toHaveBeenCalledTimes(1); + expect(catalogService.getEntityByRef).toHaveBeenCalledTimes(1); - expect(catalogClient.getEntityByRef).toHaveBeenCalledWith( + expect(catalogService.getEntityByRef).toHaveBeenCalledWith( 'user:default/mock', expect.anything(), ); @@ -879,8 +872,7 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ logger: logger, config: new ConfigReader({}), database: createDatabase(), - catalogClient, - reader: mockUrlReader, + catalogService, taskBroker, permissions: permissionApi, auth, @@ -888,7 +880,7 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ }); app = express().use(router); - catalogClient.getEntityByRef.mockImplementation(async ref => { + catalogService.getEntityByRef.mockImplementation(async ref => { const { kind } = parseEntityRef(ref); if (kind.toLocaleLowerCase() === 'template') { @@ -1649,8 +1641,7 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ logger: loggerToWinstonLogger(mockServices.logger.mock()), config: new ConfigReader({}), database: createDatabase(), - catalogClient, - reader: mockUrlReader, + catalogService, taskBroker, permissions: permissionApi, auth, diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index f451e41851..5541ab3934 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -21,12 +21,11 @@ import { DatabaseService, HttpAuthService, LifecycleService, + LoggerService, PermissionsService, resolveSafeChildPath, SchedulerService, - UrlReaderService, } from '@backstage/backend-plugin-api'; -import { CatalogApi } from '@backstage/catalog-client'; import { CompoundEntityRef, Entity, @@ -81,10 +80,8 @@ import { validate } from 'jsonschema'; import { Duration } from 'luxon'; import { pathToFileURL } from 'url'; import { v4 as uuid } from 'uuid'; -import { Logger } from 'winston'; import { z } from 'zod'; import { - createBuiltinActions, DatabaseTaskStore, TaskWorker, TemplateActionRegistry, @@ -115,17 +112,17 @@ import { isTemplatePermissionRuleInput, TemplatePermissionRuleInput, } from './permissions'; +import { CatalogService } from '@backstage/plugin-catalog-node'; /** * RouterOptions */ export interface RouterOptions { - logger: Logger; + logger: LoggerService; config: Config; - reader: UrlReaderService; lifecycle?: LifecycleService; database: DatabaseService; - catalogClient: CatalogApi; + catalogService: CatalogService; scheduler?: SchedulerService; actions?: TemplateAction[]; /** @@ -180,9 +177,8 @@ export async function createRouter( const { logger: parentLogger, config, - reader, database, - catalogClient, + catalogService, actions, scheduler, additionalTemplateFilters, @@ -285,18 +281,7 @@ export async function createRouter( workers.push(worker); } - const actionsToRegister = Array.isArray(actions) - ? actions - : createBuiltinActions({ - integrations, - catalogClient, - reader, - config, - auth, - ...templateExtensions, - }); - - actionsToRegister.forEach(action => actionRegistry.register(action)); + actions?.forEach(action => actionRegistry.register(action)); const launchWorkers = () => workers.forEach(worker => worker.start()); @@ -370,16 +355,7 @@ export async function createRouter( try { const credentials = await httpAuth.credentials(req); - const { token } = await auth.getPluginRequestToken({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - - const template = await authorizeTemplate( - req.params, - token, - credentials, - ); + const template = await authorizeTemplate(req.params, credentials); const parameters = [template.spec.parameters ?? []].flat(); @@ -458,17 +434,12 @@ export async function createRouter( permissionService: permissions, }); - const { token } = await auth.getPluginRequestToken({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - const userEntityRef = auth.isPrincipal(credentials, 'user') ? credentials.principal.userEntityRef : undefined; const userEntity = userEntityRef - ? await catalogClient.getEntityByRef(userEntityRef, { token }) + ? await catalogService.getEntityByRef(userEntityRef, { credentials }) : undefined; let auditLog = `Scaffolding task for ${templateRef}`; @@ -481,7 +452,6 @@ export async function createRouter( const template = await authorizeTemplate( { kind, namespace, name }, - token, credentials, ); @@ -529,7 +499,7 @@ export async function createRouter( const secrets: InternalTaskSecrets = { ...req.body.secrets, - backstageToken: token, + backstageToken: (credentials as any).token, __initiatorCredentials: JSON.stringify({ ...credentials, // credentials.token is nonenumerable and will not be serialized, so we need to add it explicitly @@ -887,17 +857,12 @@ export async function createRouter( throw new InputError('Input template is not a template'); } - const { token } = await auth.getPluginRequestToken({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); - const userEntityRef = auth.isPrincipal(credentials, 'user') ? credentials.principal.userEntityRef : undefined; const userEntity = userEntityRef - ? await catalogClient.getEntityByRef(userEntityRef, { token }) + ? await catalogService.getEntityByRef(userEntityRef, { credentials }) : undefined; const templateRef: string = `${template.kind}:${ @@ -963,7 +928,7 @@ export async function createRouter( })), secrets: { ...body.secrets, - ...(token && { backstageToken: token }), + backstageToken: (credentials as any).token, }, credentials, }); @@ -1026,13 +991,12 @@ export async function createRouter( async function authorizeTemplate( entityRef: CompoundEntityRef, - token: string | undefined, credentials: BackstageCredentials, ) { const template = await findTemplate({ - catalogApi: catalogClient, + catalogService, entityRef, - token, + credentials, }); if (!isSupportedTemplate(template)) { diff --git a/yarn.lock b/yarn.lock index 142885e348..d8736591ea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7601,12 +7601,12 @@ __metadata: dependencies: "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" - "@backstage/catalog-client": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" "@backstage/plugin-scaffolder-node": "workspace:^" "@backstage/plugin-scaffolder-node-test-utils": "workspace:^" "@backstage/types": "workspace:^" @@ -7721,7 +7721,6 @@ __metadata: "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" - "@backstage/catalog-client": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^"