From e934a2777571f7c04f01f1c78ca3372971f8a031 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Fri, 12 Sep 2025 08:58:17 +0200 Subject: [PATCH] chore: added the ability to pull actions from actions registry Signed-off-by: benjdlambert --- .changeset/dark-teams-give.md | 5 ++ .../src/ScaffolderPlugin.ts | 4 ++ .../scaffolder-backend/src/service/router.ts | 46 ++++++++++++++++++- 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 .changeset/dark-teams-give.md diff --git a/.changeset/dark-teams-give.md b/.changeset/dark-teams-give.md new file mode 100644 index 0000000000..692f015618 --- /dev/null +++ b/.changeset/dark-teams-give.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Updating `catalog:get-catalog-entity` action to be `readOnly` and non destructive diff --git a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts index 7ef3abcbe3..a00da9e524 100644 --- a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts +++ b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts @@ -56,6 +56,7 @@ import { convertFiltersToRecord, convertGlobalsToRecord, } from './util/templating'; +import { actionsServiceRef } from '@backstage/backend-plugin-api/alpha'; /** * Scaffolder plugin @@ -139,6 +140,7 @@ export const scaffolderPlugin = createBackendPlugin({ auditor: coreServices.auditor, catalog: catalogServiceRef, events: eventsServiceRef, + actionsRegistry: actionsServiceRef, }, async init({ logger, @@ -153,6 +155,7 @@ export const scaffolderPlugin = createBackendPlugin({ permissions, events, auditor, + actionsRegistry, }) { const log = loggerToWinstonLogger(logger); const integrations = ScmIntegrations.fromConfig(config); @@ -222,6 +225,7 @@ export const scaffolderPlugin = createBackendPlugin({ additionalWorkspaceProviders, events, auditor, + actionsRegistry, }); httpRouter.use(router); }, diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 8296a6003f..2c9df0c8c4 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -26,6 +26,7 @@ import { resolveSafeChildPath, SchedulerService, } from '@backstage/backend-plugin-api'; +import { Schema } from 'jsonschema'; import { CompoundEntityRef, Entity, @@ -131,6 +132,8 @@ import { } from './rules'; import { TaskFilters } from '@backstage/plugin-scaffolder-node'; +import { ActionsService } from '@backstage/backend-plugin-api/alpha'; +import { isObject } from 'lodash'; /** * RouterOptions @@ -163,6 +166,7 @@ export interface RouterOptions { events?: EventsService; auditor?: AuditorService; autocompleteHandlers?: Record; + actionsRegistry: ActionsService; } function isSupportedTemplate(entity: TemplateEntityV1beta3) { @@ -198,7 +202,7 @@ export async function createRouter( config, database, catalog, - actions, + actions = [], scheduler, additionalTemplateFilters, additionalTemplateGlobals, @@ -210,6 +214,7 @@ export async function createRouter( auth, httpAuth, auditor, + actionsRegistry, } = options; const concurrentTasksLimit = @@ -301,7 +306,44 @@ export async function createRouter( workers.push(worker); } - actions?.forEach(action => actionRegistry.register(action)); + // TODO(blam): it's a little unfortunate that you have to restart the scaffolder + // backend in order to pick these up. We should really just make `ActionsRegistry.get()` async + // and then we can move this logic into the there instead. + const { actions: distributedActions } = await actionsRegistry.list({ + credentials: await auth.getOwnServiceCredentials(), + }); + + for (const action of actions) { + actionRegistry.register(action); + } + + for (const action of distributedActions) { + actionRegistry.register({ + id: action.id, + description: action.description, + examples: [], + supportsDryRun: + action.attributes?.readOnly === true && + action.attributes?.destructive === false, + handler: async ctx => { + const { output } = await actionsRegistry.invoke({ + id: action.id, + input: ctx.input, + credentials: await ctx.getInitiatorCredentials(), + }); + + if (isObject(output)) { + for (const [key, value] of Object.entries(output)) { + ctx.output(key as keyof typeof output, value); + } + } + }, + schema: { + input: action.schema.input as Schema, + output: action.schema.output as Schema, + }, + }); + } const launchWorkers = () => workers.forEach(worker => worker.start());