From 04c0b15da7e958a340b6a77d0986e2110499a940 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Wed, 4 Jun 2025 15:49:52 +0200 Subject: [PATCH 1/4] 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:^" From 5863b0418cce1373f4008ab2f1f31260703681f0 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Thu, 5 Jun 2025 10:43:21 +0200 Subject: [PATCH 2/4] chore: changesets Signed-off-by: benjdlambert Signed-off-by: benjdlambert --- .changeset/scaffolder-breakages-4.md | 60 ++++++++++++++++++++++++++++ .changeset/scaffolder-breakages-5.md | 33 +++++++++++++++ .changeset/scaffolder-breakages-6.md | 39 ++++++++++++++++++ .changeset/scaffolder-breakages-7.md | 7 ++++ 4 files changed, 139 insertions(+) create mode 100644 .changeset/scaffolder-breakages-4.md create mode 100644 .changeset/scaffolder-breakages-5.md create mode 100644 .changeset/scaffolder-breakages-6.md create mode 100644 .changeset/scaffolder-breakages-7.md diff --git a/.changeset/scaffolder-breakages-4.md b/.changeset/scaffolder-breakages-4.md new file mode 100644 index 0000000000..e9f0c81256 --- /dev/null +++ b/.changeset/scaffolder-breakages-4.md @@ -0,0 +1,60 @@ +--- +'@backstage/plugin-scaffolder-node': minor +--- + +**BREAKING CHANGES** + +The legacy methods to define `createTemplateActions` have been replaced with the new native `zod` approaches for defining input and output schemas. + +You can migrate actions that look like the following with the below examples: + +```ts +// really old legacy json schema +createTemplateAction<{ repoUrl: string }, { repoOutput: string }>({ + id: 'test', + schema: { + input: { + type: 'object' + required: ['repoUrl'] + properties: { + repoUrl: { + type: 'string', + description: 'repository url description' + } + } + } + } +}); + +// old zod method +createTemplateAction({ + id: 'test' + schema: { + input: { + repoUrl: z.string({ description: 'repository url description' }) + } + } +}) + +// new method: +createTemplateAction({ + id: 'test', + schema: { + input: { + repoUrl: z => z.string({ description: 'repository url description' }) + } + } +}) + +// or for more complex zod types like unions +createTemplateAction({ + id: 'test', + schema: { + input: z => z.object({ + repoUrl: z.string({ description: 'repository url description' }) + }) + } +}) +``` + +This breaking change also means that `logStream` has been removed entirely from `ActionsContext`, and that the `logger` is now just a `LoggerService` implementation instead. There is no replacement for the `logStream`, if you wish to still keep using a `logStream` we recommend that you create your own stream that writes to `ctx.logger` instead. diff --git a/.changeset/scaffolder-breakages-5.md b/.changeset/scaffolder-breakages-5.md new file mode 100644 index 0000000000..b255a2b60e --- /dev/null +++ b/.changeset/scaffolder-breakages-5.md @@ -0,0 +1,33 @@ +--- +'@backstage/plugin-scaffolder-backend-module-github': minor +--- + +**BREAKING CHANGES** + +The `createGithubEnvironmentAction` action no longer requires an `AuthService`, and now accepts a `CatalogService` instead of `CatalogClient`. + +Unless you're providing your own override action to the default, this should be a non-breaking change. + +You can migrate using the following if you're getting typescript errors: + +```ts +export const myModule = createBackendModule({ + pluginId: 'scaffolder', + moduleId: 'test', + register({ registerInit }) { + registerInit({ + deps: { + scaffolder: scaffolderActionsExtensionPoint, + catalogService: catalogServiceRef, + }, + async init({ scaffolder, catalogService }) { + scaffolder.addActions( + createGithubEnvironmentAction({ + catalogService, + }), + ); + }, + }); + }, +}); +``` diff --git a/.changeset/scaffolder-breakages-6.md b/.changeset/scaffolder-breakages-6.md new file mode 100644 index 0000000000..5d1ff54a4a --- /dev/null +++ b/.changeset/scaffolder-breakages-6.md @@ -0,0 +1,39 @@ +--- +'@backstage/plugin-scaffolder-backend': major +--- + +**BREAKING CHANGES** + +- The `createBuiltinActions` method has been removed, as this should no longer be needed with the new backend system route, and was only useful when passing the default list of actions again in the old backend system. You should be able to rely on the default behaviour of the new backend system which is to merge the actions. + +- The `createCatalogRegisterAction` and `createFetchCatalogEntityAction` actions no longer require an `AuthService`, and now accepts a `CatalogService` instead of `CatalogClient`. + +Unless you're providing your own override action to the default, this should be a non-breaking change. + +You can migrate using the following if you're getting typescript errors: + +```ts +export const myModule = createBackendModule({ + pluginId: 'scaffolder', + moduleId: 'test', + register({ registerInit }) { + registerInit({ + deps: { + scaffolder: scaffolderActionsExtensionPoint, + catalogService: catalogServiceRef, + }, + async init({ scaffolder, catalogService }) { + scaffolder.addActions( + createCatalogRegisterAction({ + catalogService, + }), + createFetchCatalogEntityAction({ + catalogService, + integrations, + }), + ); + }, + }); + }, +}); +``` diff --git a/.changeset/scaffolder-breakages-7.md b/.changeset/scaffolder-breakages-7.md new file mode 100644 index 0000000000..01ca4c6582 --- /dev/null +++ b/.changeset/scaffolder-breakages-7.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-scaffolder-node': minor +--- + +**BREAKING CHANGES** + +The soon to be removed `TaskWorker` and `CreateWorkerOptions` now take a `LoggerService` implementation as the `logger` option instead of the old `winston` logger interface. Technically this is marked as a breaking change, albeit only in rare circumstances. From facbac877d88b7e32fe3bb659f48338115dea7af Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Thu, 5 Jun 2025 12:03:08 +0200 Subject: [PATCH 3/4] chore: fix of tests Signed-off-by: benjdlambert --- .../src/service/router.test.ts | 45 ++++++++++++++----- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index 17c893d78f..75cd19788e 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -26,6 +26,10 @@ import ObservableImpl from 'zen-observable'; * Due to a circular dependency between this plugin and the * plugin-scaffolder-backend-module-cookiecutter plugin, it results in an error: * TypeError: _pluginscaffolderbackend.createTemplateAction is not a function + * + * TODO: These tests need refactoring. Seems like the identityApi tests don't do anything different anymore. + * And there's very little value re-reunning all the tests again with just additional template filters and values. + * Let's break them out into better tests. Didn't want to do it in the same PR i'm working on right now. */ import { parseEntityRef, @@ -33,6 +37,7 @@ import { UserEntity, } from '@backstage/catalog-model'; import { + createTemplateAction, TaskBroker, TemplateFilter, TemplateGlobal, @@ -194,10 +199,6 @@ describe.each([ } as unknown as EventsService; const credentials = mockCredentials.user(); - const token = mockCredentials.service.token({ - onBehalfOf: credentials, - targetPluginId: 'catalog', - }); const getMockTemplate = (): TemplateEntityV1beta3 => ({ apiVersion: 'scaffolder.backstage.io/v1beta3', @@ -305,6 +306,19 @@ describe.each([ events, additionalTemplateFilters, additionalTemplateGlobals, + actions: [ + createTemplateAction({ + id: 'test', + description: 'test', + schema: { + input: z => + z.object({ + test: z.string(), + }), + }, + handler: async () => {}, + }), + ], }); app = express().use(router); @@ -348,7 +362,7 @@ describe.each([ const response = await request(app).get('/v2/actions').send(); expect(response.status).toEqual(200); expect(response.body[0].id).toBeDefined(); - expect(response.body.length).toBeGreaterThan(8); + expect(response.body.length).toBe(1); }); }); @@ -440,7 +454,6 @@ describe.each([ expect.objectContaining({ createdBy: 'user:default/mock', secrets: { - backstageToken: token, __initiatorCredentials: JSON.stringify(credentials), }, @@ -592,7 +605,6 @@ describe.each([ status: 'completed', createdAt: '', secrets: { - backstageToken: token, __initiatorCredentials: JSON.stringify(credentials), }, createdBy: '', @@ -877,6 +889,19 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ permissions: permissionApi, auth, httpAuth, + actions: [ + createTemplateAction({ + id: 'test', + description: 'test', + schema: { + input: z => + z.object({ + test: z.string(), + }), + }, + handler: async () => {}, + }), + ], }); app = express().use(router); @@ -919,7 +944,7 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ const response = await request(app).get('/v2/actions').send(); expect(response.status).toEqual(200); expect(response.body[0].id).toBeDefined(); - expect(response.body.length).toBeGreaterThan(8); + expect(response.body.length).toBe(1); }); }); @@ -1091,7 +1116,6 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ expect.objectContaining({ createdBy: 'user:default/mock', secrets: { - backstageToken: token, __initiatorCredentials: JSON.stringify(credentials), }, @@ -1161,7 +1185,6 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ expect.objectContaining({ createdBy: 'user:default/mock', secrets: { - backstageToken: token, __initiatorCredentials: JSON.stringify(credentials), }, @@ -1250,7 +1273,6 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ expect.objectContaining({ createdBy: 'user:default/mock', secrets: { - backstageToken: token, __initiatorCredentials: JSON.stringify(credentials), }, @@ -1394,7 +1416,6 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ status: 'completed', createdAt: '', secrets: { - backstageToken: token, __initiatorCredentials: JSON.stringify(credentials), }, createdBy: '', From 3bfa1d392fef9dc01cd79b61debe8ff17b1553e3 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Thu, 5 Jun 2025 15:28:22 +0200 Subject: [PATCH 4/4] chore: small tweaks Signed-off-by: benjdlambert --- .changeset/scaffolder-breakages-5.md | 9 ++- .changeset/scaffolder-breakages-6.md | 11 ++- .changeset/scaffolder-breakages-7.md | 7 -- .changeset/scaffolder-breakges-4.md | 60 -------------- packages/app/src/apis.ts | 19 +++++ .../report.api.md | 2 +- .../githubEnvironment.examples.test.ts | 2 +- .../src/actions/githubEnvironment.test.ts | 2 +- .../src/actions/githubEnvironment.ts | 6 +- .../src/module.ts | 6 +- plugins/scaffolder-backend/report.api.md | 4 +- .../src/ScaffolderPlugin.ts | 10 +-- .../builtin/catalog/fetch.examples.test.ts | 8 +- .../actions/builtin/catalog/fetch.test.ts | 78 ++++++++++--------- .../actions/builtin/catalog/fetch.ts | 8 +- .../builtin/catalog/register.examples.test.ts | 11 +-- .../actions/builtin/catalog/register.test.ts | 30 +++---- .../actions/builtin/catalog/register.ts | 8 +- .../scaffolder-backend/src/service/helpers.ts | 6 +- .../src/service/router.test.ts | 16 ++-- .../scaffolder-backend/src/service/router.ts | 10 +-- 21 files changed, 140 insertions(+), 173 deletions(-) delete mode 100644 .changeset/scaffolder-breakages-7.md delete mode 100644 .changeset/scaffolder-breakges-4.md diff --git a/.changeset/scaffolder-breakages-5.md b/.changeset/scaffolder-breakages-5.md index b255a2b60e..8047ec3dc3 100644 --- a/.changeset/scaffolder-breakages-5.md +++ b/.changeset/scaffolder-breakages-5.md @@ -11,6 +11,9 @@ Unless you're providing your own override action to the default, this should be You can migrate using the following if you're getting typescript errors: ```ts +import { catalogServiceRef } from '@backstage/plugin-catalog-node'; +import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha'; + export const myModule = createBackendModule({ pluginId: 'scaffolder', moduleId: 'test', @@ -18,12 +21,12 @@ export const myModule = createBackendModule({ registerInit({ deps: { scaffolder: scaffolderActionsExtensionPoint, - catalogService: catalogServiceRef, + catalog: catalogServiceRef, }, - async init({ scaffolder, catalogService }) { + async init({ scaffolder, catalog }) { scaffolder.addActions( createGithubEnvironmentAction({ - catalogService, + catalog, }), ); }, diff --git a/.changeset/scaffolder-breakages-6.md b/.changeset/scaffolder-breakages-6.md index 5d1ff54a4a..74ff76c27d 100644 --- a/.changeset/scaffolder-breakages-6.md +++ b/.changeset/scaffolder-breakages-6.md @@ -13,6 +13,9 @@ Unless you're providing your own override action to the default, this should be You can migrate using the following if you're getting typescript errors: ```ts +import { catalogServiceRef } from '@backstage/plugin-catalog-node'; +import { scaffolderActionsExtensionPoint } from '@backstage/plugin-scaffolder-node/alpha'; + export const myModule = createBackendModule({ pluginId: 'scaffolder', moduleId: 'test', @@ -20,15 +23,15 @@ export const myModule = createBackendModule({ registerInit({ deps: { scaffolder: scaffolderActionsExtensionPoint, - catalogService: catalogServiceRef, + catalog: catalogServiceRef, }, - async init({ scaffolder, catalogService }) { + async init({ scaffolder, catalog }) { scaffolder.addActions( createCatalogRegisterAction({ - catalogService, + catalog, }), createFetchCatalogEntityAction({ - catalogService, + catalog, integrations, }), ); diff --git a/.changeset/scaffolder-breakages-7.md b/.changeset/scaffolder-breakages-7.md deleted file mode 100644 index 01ca4c6582..0000000000 --- a/.changeset/scaffolder-breakages-7.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-scaffolder-node': minor ---- - -**BREAKING CHANGES** - -The soon to be removed `TaskWorker` and `CreateWorkerOptions` now take a `LoggerService` implementation as the `logger` option instead of the old `winston` logger interface. Technically this is marked as a breaking change, albeit only in rare circumstances. diff --git a/.changeset/scaffolder-breakges-4.md b/.changeset/scaffolder-breakges-4.md deleted file mode 100644 index e9f0c81256..0000000000 --- a/.changeset/scaffolder-breakges-4.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -'@backstage/plugin-scaffolder-node': minor ---- - -**BREAKING CHANGES** - -The legacy methods to define `createTemplateActions` have been replaced with the new native `zod` approaches for defining input and output schemas. - -You can migrate actions that look like the following with the below examples: - -```ts -// really old legacy json schema -createTemplateAction<{ repoUrl: string }, { repoOutput: string }>({ - id: 'test', - schema: { - input: { - type: 'object' - required: ['repoUrl'] - properties: { - repoUrl: { - type: 'string', - description: 'repository url description' - } - } - } - } -}); - -// old zod method -createTemplateAction({ - id: 'test' - schema: { - input: { - repoUrl: z.string({ description: 'repository url description' }) - } - } -}) - -// new method: -createTemplateAction({ - id: 'test', - schema: { - input: { - repoUrl: z => z.string({ description: 'repository url description' }) - } - } -}) - -// or for more complex zod types like unions -createTemplateAction({ - id: 'test', - schema: { - input: z => z.object({ - repoUrl: z.string({ description: 'repository url description' }) - }) - } -}) -``` - -This breaking change also means that `logStream` has been removed entirely from `ActionsContext`, and that the `logger` is now just a `LoggerService` implementation instead. There is no replacement for the `logStream`, if you wish to still keep using a `logStream` we recommend that you create your own stream that writes to `ctx.logger` instead. diff --git a/packages/app/src/apis.ts b/packages/app/src/apis.ts index e39122337f..5d300d67cb 100644 --- a/packages/app/src/apis.ts +++ b/packages/app/src/apis.ts @@ -24,11 +24,14 @@ import { configApiRef, createApiFactory, discoveryApiRef, + fetchApiRef, } from '@backstage/core-plugin-api'; import { AuthProxyDiscoveryApi } from './AuthProxyDiscoveryApi'; import { formDecoratorsApiRef } from '@backstage/plugin-scaffolder/alpha'; import { DefaultScaffolderFormDecoratorsApi } from '@backstage/plugin-scaffolder/alpha'; import { mockDecorator } from './components/scaffolder/decorators'; +import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react'; +import { ScaffolderClient } from '@backstage/plugin-scaffolder'; export const apis: AnyApiFactory[] = [ createApiFactory({ @@ -42,6 +45,22 @@ export const apis: AnyApiFactory[] = [ factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi), }), + createApiFactory({ + api: scaffolderApiRef, + deps: { + discoveryApi: discoveryApiRef, + fetchApi: fetchApiRef, + scmIntegrationsApi: scmIntegrationsApiRef, + }, + factory: ({ discoveryApi, fetchApi, scmIntegrationsApi }) => + new ScaffolderClient({ + useLongPollingLogs: true, + discoveryApi, + fetchApi, + scmIntegrationsApi, + }), + }), + createApiFactory({ api: formDecoratorsApiRef, deps: {}, diff --git a/plugins/scaffolder-backend-module-github/report.api.md b/plugins/scaffolder-backend-module-github/report.api.md index ebc08f32a8..28f710557a 100644 --- a/plugins/scaffolder-backend-module-github/report.api.md +++ b/plugins/scaffolder-backend-module-github/report.api.md @@ -110,7 +110,7 @@ export function createGithubDeployKeyAction(options: { // @public export function createGithubEnvironmentAction(options: { integrations: ScmIntegrationRegistry; - catalogService: CatalogService; + catalog: 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 dc1952f9cb..c1ddbb5b0e 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 @@ -112,7 +112,7 @@ describe('github:environment:create examples', () => { action = createGithubEnvironmentAction({ integrations, - catalogService: mockCatalogService, + catalog: 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 a4fa9ceff1..fc7c3a8276 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.test.ts @@ -123,7 +123,7 @@ describe('github:environment:create', () => { action = createGithubEnvironmentAction({ integrations, - catalogService: mockCatalogService, + catalog: mockCatalogService, }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts index e8812091b3..4a36be3b5b 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubEnvironment.ts @@ -34,9 +34,9 @@ import { CatalogService } from '@backstage/plugin-catalog-node'; */ export function createGithubEnvironmentAction(options: { integrations: ScmIntegrationRegistry; - catalogService: CatalogService; + catalog: CatalogService; }) { - const { integrations, catalogService } = options; + const { integrations, catalog } = options; // For more information on how to define custom actions, see // https://backstage.io/docs/features/software-templates/writing-custom-actions return createTemplateAction({ @@ -183,7 +183,7 @@ Wildcard characters will not match \`/\`. For example, to match tags that begin if (reviewers) { let reviewersEntityRefs: Array = []; // Fetch reviewers from Catalog - const catalogResponse = await catalogService?.getEntitiesByRefs( + const catalogResponse = await catalog.getEntitiesByRefs( { entityRefs: reviewers, }, diff --git a/plugins/scaffolder-backend-module-github/src/module.ts b/plugins/scaffolder-backend-module-github/src/module.ts index 59e1398c87..5812a22560 100644 --- a/plugins/scaffolder-backend-module-github/src/module.ts +++ b/plugins/scaffolder-backend-module-github/src/module.ts @@ -54,10 +54,10 @@ export const githubModule = createBackendModule({ deps: { scaffolder: scaffolderActionsExtensionPoint, config: coreServices.rootConfig, - catalogService: catalogServiceRef, + catalog: catalogServiceRef, autocomplete: scaffolderAutocompleteExtensionPoint, }, - async init({ scaffolder, config, autocomplete, catalogService }) { + async init({ scaffolder, config, autocomplete, catalog }) { const integrations = ScmIntegrations.fromConfig(config); const githubCredentialsProvider = DefaultGithubCredentialsProvider.fromIntegrations(integrations); @@ -76,7 +76,7 @@ export const githubModule = createBackendModule({ }), createGithubEnvironmentAction({ integrations, - catalogService, + catalog, }), createGithubIssuesLabelAction({ integrations, diff --git a/plugins/scaffolder-backend/report.api.md b/plugins/scaffolder-backend/report.api.md index 39504bb524..1f2a17a384 100644 --- a/plugins/scaffolder-backend/report.api.md +++ b/plugins/scaffolder-backend/report.api.md @@ -53,7 +53,7 @@ export type ActionPermissionRuleInput< // @public export function createCatalogRegisterAction(options: { - catalogService: CatalogService; + catalog: CatalogService; integrations: ScmIntegrations; }): TemplateAction< | { @@ -106,7 +106,7 @@ export function createDebugLogAction(): TemplateAction< // @public export function createFetchCatalogEntityAction(options: { - catalogService: CatalogService; + catalog: CatalogService; }): TemplateAction< { entityRef?: string | undefined; diff --git a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts index 2c34fb1fdb..7ef3abcbe3 100644 --- a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts +++ b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts @@ -137,7 +137,7 @@ export const scaffolderPlugin = createBackendPlugin({ httpRouter: coreServices.httpRouter, httpAuth: coreServices.httpAuth, auditor: coreServices.auditor, - catalogService: catalogServiceRef, + catalog: catalogServiceRef, events: eventsServiceRef, }, async init({ @@ -149,7 +149,7 @@ export const scaffolderPlugin = createBackendPlugin({ auth, httpRouter, httpAuth, - catalogService, + catalog, permissions, events, auditor, @@ -191,8 +191,8 @@ export const scaffolderPlugin = createBackendPlugin({ createDebugLogAction(), createWaitAction(), // todo(blam): maybe these should be a -catalog module? - createCatalogRegisterAction({ catalogService, integrations }), - createFetchCatalogEntityAction({ catalogService }), + createCatalogRegisterAction({ catalog, integrations }), + createFetchCatalogEntityAction({ catalog }), createCatalogWriteAction(), createFilesystemDeleteAction(), createFilesystemRenameAction(), @@ -209,7 +209,7 @@ export const scaffolderPlugin = createBackendPlugin({ logger, config, database, - catalogService, + catalog, 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 d230a1b251..b10670d8a8 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 @@ -31,10 +31,10 @@ describe('catalog:fetch examples', () => { }, } as Entity; - const catalogService = catalogServiceMock({ entities: [entity] }); + const catalogMock = catalogServiceMock({ entities: [entity] }); const action = createFetchCatalogEntityAction({ - catalogService, + catalog: catalogMock, }); const credentials = mockCredentials.user(); @@ -43,7 +43,7 @@ describe('catalog:fetch examples', () => { beforeEach(() => { jest.resetAllMocks(); - jest.spyOn(catalogService, 'getEntityByRef'); + jest.spyOn(catalogMock, 'getEntityByRef'); }); describe('fetch single entity', () => { @@ -53,7 +53,7 @@ describe('catalog:fetch examples', () => { input: yaml.parse(examples[0].example).steps[0].input, }); - expect(catalogService.getEntityByRef).toHaveBeenCalledWith( + expect(catalogMock.getEntityByRef).toHaveBeenCalledWith( 'component:default/name', { credentials }, ); 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 03953ea7ae..0bea408355 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 @@ -39,10 +39,11 @@ describe('catalog:fetch', () => { describe('fetch single entity', () => { it('should return entity from catalog', async () => { - const catalogService = catalogServiceMock({ entities: [component] }); - jest.spyOn(catalogService, 'getEntityByRef'); + const catalogMock = catalogServiceMock({ entities: [component] }); + jest.spyOn(catalogMock, 'getEntityByRef'); + const action = createFetchCatalogEntityAction({ - catalogService, + catalog: catalogMock, }); await action.handler({ @@ -52,7 +53,7 @@ describe('catalog:fetch', () => { }, }); - expect(catalogService.getEntityByRef).toHaveBeenCalledWith( + expect(catalogMock.getEntityByRef).toHaveBeenCalledWith( 'component:default/test', { credentials }, ); @@ -60,12 +61,12 @@ describe('catalog:fetch', () => { }); it('should throw error if entity fetch fails from catalog and optional is false', async () => { - const catalogService = catalogServiceMock.mock({ + const catalogMock = catalogServiceMock.mock({ getEntityByRef: () => Promise.reject(new Error('Not found')), }); - jest.spyOn(catalogService, 'getEntityByRef'); + const action = createFetchCatalogEntityAction({ - catalogService, + catalog: catalogMock, }); await expect( @@ -77,7 +78,7 @@ describe('catalog:fetch', () => { }), ).rejects.toThrow('Not found'); - expect(catalogService.getEntityByRef).toHaveBeenCalledWith( + expect(catalogMock.getEntityByRef).toHaveBeenCalledWith( 'component:default/test', { credentials }, ); @@ -85,10 +86,11 @@ describe('catalog:fetch', () => { }); it('should throw error if entity not in catalog and optional is false', async () => { - const catalogService = catalogServiceMock({ entities: [] }); - jest.spyOn(catalogService, 'getEntityByRef'); + const catalogMock = catalogServiceMock({ entities: [] }); + jest.spyOn(catalogMock, 'getEntityByRef'); + const action = createFetchCatalogEntityAction({ - catalogService, + catalog: catalogMock, }); await expect( @@ -100,7 +102,7 @@ describe('catalog:fetch', () => { }), ).rejects.toThrow('Entity component:default/test not found'); - expect(catalogService.getEntityByRef).toHaveBeenCalledWith( + expect(catalogMock.getEntityByRef).toHaveBeenCalledWith( 'component:default/test', { credentials }, ); @@ -115,10 +117,12 @@ describe('catalog:fetch', () => { namespace: 'ns', }, } as Entity; - const catalogService = catalogServiceMock({ entities: [entity] }); - jest.spyOn(catalogService, 'getEntityByRef'); + + const catalogMock = catalogServiceMock({ entities: [entity] }); + jest.spyOn(catalogMock, 'getEntityByRef'); + const action = createFetchCatalogEntityAction({ - catalogService, + catalog: catalogMock, }); await action.handler({ @@ -130,20 +134,21 @@ describe('catalog:fetch', () => { }, }); - expect(catalogService.getEntityByRef).toHaveBeenCalledWith( - 'group:ns/test', - { credentials }, - ); + expect(catalogMock.getEntityByRef).toHaveBeenCalledWith('group:ns/test', { + credentials, + }); + expect(mockContext.output).toHaveBeenCalledWith('entity', entity); }); }); describe('fetch multiple entities', () => { it('should return entities from catalog', async () => { - const catalogService = catalogServiceMock({ entities: [component] }); - jest.spyOn(catalogService, 'getEntitiesByRefs'); + const catalogMock = catalogServiceMock({ entities: [component] }); + jest.spyOn(catalogMock, 'getEntitiesByRefs'); + const action = createFetchCatalogEntityAction({ - catalogService, + catalog: catalogMock, }); await action.handler({ @@ -153,7 +158,7 @@ describe('catalog:fetch', () => { }, }); - expect(catalogService.getEntitiesByRefs).toHaveBeenCalledWith( + expect(catalogMock.getEntitiesByRefs).toHaveBeenCalledWith( { entityRefs: ['component:default/test'] }, { credentials }, ); @@ -161,10 +166,10 @@ describe('catalog:fetch', () => { }); it('should throw error if undefined is returned for some entity', async () => { - const catalogService = catalogServiceMock({ entities: [component] }); - jest.spyOn(catalogService, 'getEntitiesByRefs'); + const catalogMock = catalogServiceMock({ entities: [component] }); + jest.spyOn(catalogMock, 'getEntitiesByRefs'); const action = createFetchCatalogEntityAction({ - catalogService, + catalog: catalogMock, }); await expect( @@ -177,7 +182,7 @@ describe('catalog:fetch', () => { }), ).rejects.toThrow('Entity component:default/test2 not found'); - expect(catalogService.getEntitiesByRefs).toHaveBeenCalledWith( + expect(catalogMock.getEntitiesByRefs).toHaveBeenCalledWith( { entityRefs: ['component:default/test', 'component:default/test2'], }, @@ -187,10 +192,11 @@ describe('catalog:fetch', () => { }); it('should return null in case some of the entities not found and optional is true', async () => { - const catalogService = catalogServiceMock({ entities: [component] }); - jest.spyOn(catalogService, 'getEntitiesByRefs'); + const catalogMock = catalogServiceMock({ entities: [component] }); + jest.spyOn(catalogMock, 'getEntitiesByRefs'); + const action = createFetchCatalogEntityAction({ - catalogService, + catalog: catalogMock, }); await action.handler({ @@ -201,7 +207,7 @@ describe('catalog:fetch', () => { }, }); - expect(catalogService.getEntitiesByRefs).toHaveBeenCalledWith( + expect(catalogMock.getEntitiesByRefs).toHaveBeenCalledWith( { entityRefs: ['component:default/test', 'component:default/test2'] }, { credentials }, ); @@ -226,12 +232,14 @@ describe('catalog:fetch', () => { }, kind: 'User', } as Entity; - const catalogService = catalogServiceMock({ + + const catalogMock = catalogServiceMock({ entities: [entity1, entity2], }); - jest.spyOn(catalogService, 'getEntitiesByRefs'); + jest.spyOn(catalogMock, 'getEntitiesByRefs'); + const action = createFetchCatalogEntityAction({ - catalogService, + catalog: catalogMock, }); await action.handler({ @@ -243,7 +251,7 @@ describe('catalog:fetch', () => { }, }); - expect(catalogService.getEntitiesByRefs).toHaveBeenCalledWith( + expect(catalogMock.getEntitiesByRefs).toHaveBeenCalledWith( { entityRefs: ['group:ns/test', 'user:default/test'] }, { credentials }, ); 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 d47159ad49..e73c882a9a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.ts @@ -27,9 +27,9 @@ const id = 'catalog:fetch'; * @public */ export function createFetchCatalogEntityAction(options: { - catalogService: CatalogService; + catalog: CatalogService; }) { - const { catalogService } = options; + const { catalog } = options; return createTemplateAction({ id, @@ -94,7 +94,7 @@ export function createFetchCatalogEntityAction(options: { } if (entityRef) { - const entity = await catalogService.getEntityByRef( + const entity = await catalog.getEntityByRef( stringifyEntityRef( parseEntityRef(entityRef, { defaultKind, defaultNamespace }), ), @@ -110,7 +110,7 @@ export function createFetchCatalogEntityAction(options: { } if (entityRefs) { - const entities = await catalogService.getEntitiesByRefs( + const entities = await catalog.getEntitiesByRefs( { entityRefs: entityRefs.map(ref => stringifyEntityRef( 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 b5d7d21ae7..b0f17d98c3 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 @@ -33,11 +33,11 @@ describe('catalog:register', () => { }), ); - const catalogService = catalogServiceMock.mock(); + const catalogMock = catalogServiceMock.mock(); const action = createCatalogRegisterAction({ integrations, - catalogService, + catalog: catalogMock, }); const credentials = mockCredentials.user(); @@ -48,7 +48,7 @@ describe('catalog:register', () => { }); it('should register location in catalog', async () => { - catalogService.addLocation + catalogMock.addLocation .mockResolvedValueOnce({ location: null as any, entities: [], @@ -65,12 +65,13 @@ describe('catalog:register', () => { } as Entity, ], }); + await action.handler({ ...mockContext, input: yaml.parse(examples[0].example).steps[0].input, }); - expect(catalogService.addLocation).toHaveBeenNthCalledWith( + expect(catalogMock.addLocation).toHaveBeenNthCalledWith( 1, { type: 'url', @@ -79,7 +80,7 @@ describe('catalog:register', () => { }, { credentials }, ); - expect(catalogService.addLocation).toHaveBeenNthCalledWith( + expect(catalogMock.addLocation).toHaveBeenNthCalledWith( 2, { dryRun: true, 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 f905535ad3..ad84624129 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 @@ -31,11 +31,11 @@ describe('catalog:register', () => { }), ); - const catalogService = catalogServiceMock.mock(); + const catalogMock = catalogServiceMock.mock(); const action = createCatalogRegisterAction({ integrations, - catalogService, + catalog: catalogMock, }); const credentials = mockCredentials.user(); @@ -60,7 +60,7 @@ describe('catalog:register', () => { }); it('should register location in catalog', async () => { - catalogService.addLocation + catalogMock.addLocation .mockResolvedValueOnce({ location: null as any, entities: [], @@ -84,7 +84,7 @@ describe('catalog:register', () => { }, }); - expect(catalogService.addLocation).toHaveBeenNthCalledWith( + expect(catalogMock.addLocation).toHaveBeenNthCalledWith( 1, { type: 'url', @@ -92,7 +92,7 @@ describe('catalog:register', () => { }, { credentials }, ); - expect(catalogService.addLocation).toHaveBeenNthCalledWith( + expect(catalogMock.addLocation).toHaveBeenNthCalledWith( 2, { dryRun: true, @@ -113,7 +113,7 @@ describe('catalog:register', () => { }); it('should return entityRef with the Component entity and not the generated location', async () => { - catalogService.addLocation + catalogMock.addLocation .mockResolvedValueOnce({ location: null as any, entities: [], @@ -164,7 +164,7 @@ describe('catalog:register', () => { }); it('should return entityRef with the next non-generated entity if no Component kind can be found', async () => { - catalogService.addLocation + catalogMock.addLocation .mockResolvedValueOnce({ location: null as any, entities: [], @@ -208,7 +208,7 @@ describe('catalog:register', () => { }); it('should return entityRef with the first entity if no non-generated entities can be found', async () => { - catalogService.addLocation + catalogMock.addLocation .mockResolvedValueOnce({ location: null as any, entities: [], @@ -245,7 +245,7 @@ describe('catalog:register', () => { }); it('should not return entityRef if there are no entities', async () => { - catalogService.addLocation + catalogMock.addLocation .mockResolvedValueOnce({ location: null as any, entities: [], @@ -267,7 +267,7 @@ describe('catalog:register', () => { }); it('should ignore failures when dry running the location in the catalog if `optional` is set', async () => { - catalogService.addLocation + catalogMock.addLocation .mockRejectedValueOnce(new Error('Not found')) .mockRejectedValueOnce(new Error('Not found')); await action.handler({ @@ -278,7 +278,7 @@ describe('catalog:register', () => { }, }); - expect(catalogService.addLocation).toHaveBeenNthCalledWith( + expect(catalogMock.addLocation).toHaveBeenNthCalledWith( 1, { type: 'url', @@ -286,7 +286,7 @@ describe('catalog:register', () => { }, { credentials }, ); - expect(catalogService.addLocation).toHaveBeenNthCalledWith( + expect(catalogMock.addLocation).toHaveBeenNthCalledWith( 2, { dryRun: true, @@ -303,7 +303,7 @@ describe('catalog:register', () => { }); it('should fetch entities when adding location in the catalog fails and `optional` is set', async () => { - catalogService.addLocation + catalogMock.addLocation .mockRejectedValueOnce(new Error('Already registered')) .mockResolvedValueOnce({ location: null as any, @@ -325,7 +325,7 @@ describe('catalog:register', () => { }, }); - expect(catalogService.addLocation).toHaveBeenNthCalledWith( + expect(catalogMock.addLocation).toHaveBeenNthCalledWith( 1, { type: 'url', @@ -333,7 +333,7 @@ describe('catalog:register', () => { }, { credentials }, ); - expect(catalogService.addLocation).toHaveBeenNthCalledWith( + expect(catalogMock.addLocation).toHaveBeenNthCalledWith( 2, { dryRun: true, 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 c881293d3a..65102d8375 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts @@ -28,10 +28,10 @@ const id = 'catalog:register'; * @public */ export function createCatalogRegisterAction(options: { - catalogService: CatalogService; + catalog: CatalogService; integrations: ScmIntegrations; }) { - const { catalogService, integrations } = options; + const { catalog, integrations } = options; return createTemplateAction({ id, @@ -99,7 +99,7 @@ export function createCatalogRegisterAction(options: { try { // 1st try to register the location, this will throw an error if the location already exists (see catch) - await catalogService.addLocation( + await catalog.addLocation( { type: 'url', target: catalogInfoUrl, @@ -115,7 +115,7 @@ 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 catalogService.addLocation( + const result = await catalog.addLocation( { dryRun: true, type: 'url', diff --git a/plugins/scaffolder-backend/src/service/helpers.ts b/plugins/scaffolder-backend/src/service/helpers.ts index d92fa2dfd7..e29eec4d64 100644 --- a/plugins/scaffolder-backend/src/service/helpers.ts +++ b/plugins/scaffolder-backend/src/service/helpers.ts @@ -92,16 +92,16 @@ export function getEntityBaseUrl(entity: Entity): string | undefined { */ export async function findTemplate(options: { entityRef: CompoundEntityRef; - catalogService: CatalogService; + catalog: CatalogService; credentials: BackstageCredentials; }): Promise { - const { entityRef, catalogService, credentials } = options; + const { entityRef, catalog, credentials } = options; if (entityRef.kind.toLocaleLowerCase('en-US') !== 'template') { throw new InputError(`Invalid kind, only 'Template' kind is supported`); } - const template = await catalogService.getEntityByRef(entityRef, { + const template = await catalog.getEntityByRef(entityRef, { credentials, }); if (!template) { diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index 75cd19788e..04f619fd02 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -187,7 +187,7 @@ describe.each([ let app: express.Express; let loggerSpy: jest.SpyInstance; let taskBroker: TaskBroker; - const catalogService = catalogServiceMock.mock(); + const catalogMock = catalogServiceMock.mock(); const permissionApi = { authorize: jest.fn(), authorizeConditional: jest.fn(), @@ -298,7 +298,7 @@ describe.each([ logger: logger, config: new ConfigReader({}), database: createDatabase(), - catalogService, + catalog: catalogMock, taskBroker, permissions: permissionApi, auth, @@ -322,7 +322,7 @@ describe.each([ }); app = express().use(router); - catalogService.getEntityByRef.mockImplementation(async ref => { + catalogMock.getEntityByRef.mockImplementation(async ref => { const { kind } = parseEntityRef(ref); if (kind.toLocaleLowerCase() === 'template') { @@ -856,9 +856,9 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ directoryContents: [], }); - expect(catalogService.getEntityByRef).toHaveBeenCalledTimes(1); + expect(catalogMock.getEntityByRef).toHaveBeenCalledTimes(1); - expect(catalogService.getEntityByRef).toHaveBeenCalledWith( + expect(catalogMock.getEntityByRef).toHaveBeenCalledWith( 'user:default/mock', expect.anything(), ); @@ -884,7 +884,7 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ logger: logger, config: new ConfigReader({}), database: createDatabase(), - catalogService, + catalog: catalogMock, taskBroker, permissions: permissionApi, auth, @@ -905,7 +905,7 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ }); app = express().use(router); - catalogService.getEntityByRef.mockImplementation(async ref => { + catalogMock.getEntityByRef.mockImplementation(async ref => { const { kind } = parseEntityRef(ref); if (kind.toLocaleLowerCase() === 'template') { @@ -1662,7 +1662,7 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ logger: loggerToWinstonLogger(mockServices.logger.mock()), config: new ConfigReader({}), database: createDatabase(), - catalogService, + catalog: catalogMock, taskBroker, permissions: permissionApi, auth, diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 5541ab3934..0ea6293f36 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -122,7 +122,7 @@ export interface RouterOptions { config: Config; lifecycle?: LifecycleService; database: DatabaseService; - catalogService: CatalogService; + catalog: CatalogService; scheduler?: SchedulerService; actions?: TemplateAction[]; /** @@ -178,7 +178,7 @@ export async function createRouter( logger: parentLogger, config, database, - catalogService, + catalog, actions, scheduler, additionalTemplateFilters, @@ -439,7 +439,7 @@ export async function createRouter( : undefined; const userEntity = userEntityRef - ? await catalogService.getEntityByRef(userEntityRef, { credentials }) + ? await catalog.getEntityByRef(userEntityRef, { credentials }) : undefined; let auditLog = `Scaffolding task for ${templateRef}`; @@ -862,7 +862,7 @@ export async function createRouter( : undefined; const userEntity = userEntityRef - ? await catalogService.getEntityByRef(userEntityRef, { credentials }) + ? await catalog.getEntityByRef(userEntityRef, { credentials }) : undefined; const templateRef: string = `${template.kind}:${ @@ -994,7 +994,7 @@ export async function createRouter( credentials: BackstageCredentials, ) { const template = await findTemplate({ - catalogService, + catalog, entityRef, credentials, });