diff --git a/.changeset/soft-turkeys-tie.md b/.changeset/soft-turkeys-tie.md new file mode 100644 index 0000000000..3962f49460 --- /dev/null +++ b/.changeset/soft-turkeys-tie.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Modified `createDryRunner` and corresponding route to include `templateMetaData` inside the `templateInfo`. This allows custom action writers to access things like `templateInfo.entity.metadata.name` via the action context while executing templates using the dry run framework. diff --git a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts index c993e0d46d..5fab0a80c7 100644 --- a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts @@ -15,10 +15,9 @@ */ import { ScmIntegrations } from '@backstage/integration'; -import { TaskSpec } from '@backstage/plugin-scaffolder-common'; +import { TaskSpec, TemplateInfo } from '@backstage/plugin-scaffolder-common'; import { JsonObject } from '@backstage/types'; -import { v4 as uuid } from 'uuid'; -import { pathToFileURL } from 'url'; +import { fileURLToPath } from 'url'; import { Logger } from 'winston'; import { createTemplateAction, @@ -29,19 +28,19 @@ import { SerializedFile, serializeDirectoryContents, } from '@backstage/plugin-scaffolder-node'; +import path from 'path'; import { TemplateActionRegistry } from '../actions'; import { NunjucksWorkflowRunner } from '../tasks/NunjucksWorkflowRunner'; import { DecoratedActionsRegistry } from './DecoratedActionsRegistry'; import fs from 'fs-extra'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; -import { - BackstageCredentials, - resolveSafeChildPath, -} from '@backstage/backend-plugin-api'; +import { BackstageCredentials } from '@backstage/backend-plugin-api'; import type { UserEntity } from '@backstage/catalog-model'; +import { v4 as uuid } from 'uuid'; interface DryRunInput { spec: TaskSpec; + templateInfo: TemplateInfo; secrets?: TaskSecrets; directoryContents: SerializedFile[]; credentials: BackstageCredentials; @@ -94,18 +93,21 @@ export function createDryRunner(options: TemplateTesterCreateOptions) { ]), }); + // Extracting contentsPath and dryRunId from the baseUrl + const baseUrl = input.templateInfo.baseUrl; + if (!baseUrl) { + throw new Error('baseUrl is required'); + } + const basePath = fileURLToPath(new URL(baseUrl)); + const contentsPath = path.dirname(basePath); const dryRunId = uuid(); + const log = new Array<{ body: JsonObject }>(); - const contentsPath = resolveSafeChildPath( - options.workingDirectory, - `dry-run-content-${dryRunId}`, - ); try { await deserializeDirectoryContents(contentsPath, input.directoryContents); const abortSignal = new AbortController().signal; - const result = await workflowRunner.execute({ spec: { ...input.spec, @@ -117,12 +119,7 @@ export function createDryRunner(options: TemplateTesterCreateOptions) { action: 'dry-run:extract', }, ], - templateInfo: { - entityRef: 'template:default/dry-run', - baseUrl: pathToFileURL( - resolveSafeChildPath(contentsPath, 'template.yaml'), - ).toString(), - }, + templateInfo: input.templateInfo, }, secrets: input.secrets, getInitiatorCredentials: () => Promise.resolve(input.credentials), diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts index 0bcbd2f917..25caf7bad0 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts @@ -1331,6 +1331,39 @@ describe('NunjucksWorkflowRunner', () => { expect(fakeActionHandler.mock.calls[0][0].isDryRun).toEqual(true); }); + + it('should have metadata in action context during dry run', async () => { + const task = createMockTaskWithSpec( + { + apiVersion: 'scaffolder.backstage.io/v1beta3', + templateInfo: { + entityRef: 'dryRun-Entity', + entity: { metadata: { name: 'test-template' } }, + }, + parameters: {}, + output: {}, + steps: [ + { + id: 'test', + name: 'name', + action: 'jest-validated-action', + input: { foo: 1 }, + }, + ], + }, + { + backstageToken: token, + }, + true, + ); + + await runner.execute(task); + + expect(fakeActionHandler.mock.calls[0][0].isDryRun).toEqual(true); + expect( + fakeActionHandler.mock.calls[0][0].templateInfo.entity.metadata.name, + ).toEqual('test-template'); + }); }); describe('permissions', () => { diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 35dacf6559..261beb1f56 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -94,6 +94,7 @@ import { PermissionsService, SchedulerService, UrlReaderService, + resolveSafeChildPath, } from '@backstage/backend-plugin-api'; import { IdentityApi, @@ -105,6 +106,8 @@ import { AutocompleteHandler, WorkspaceProvider, } from '@backstage/plugin-scaffolder-node/alpha'; +import { pathToFileURL } from 'url'; +import { v4 as uuid } from 'uuid'; /** * @@ -814,6 +817,22 @@ export async function createRouter( name: step.name ?? step.action, })); + const dryRunId = uuid(); + const contentsPath = resolveSafeChildPath( + workingDirectory, + `dry-run-content-${dryRunId}`, + ); + + const templateInfo = { + entityRef: stringifyEntityRef(template), + entity: { + metadata: template.metadata, + }, + baseUrl: pathToFileURL( + resolveSafeChildPath(contentsPath, 'template.yaml'), + ).toString(), + }; + const result = await dryRunner({ spec: { apiVersion: template.apiVersion, @@ -825,6 +844,7 @@ export async function createRouter( ref: userEntityRef, }, }, + templateInfo: templateInfo, directoryContents: (body.directoryContents ?? []).map(file => ({ path: file.path, content: Buffer.from(file.base64Content, 'base64'),