diff --git a/.changeset/yellow-falcons-whisper.md b/.changeset/yellow-falcons-whisper.md
new file mode 100644
index 0000000000..9168b32c27
--- /dev/null
+++ b/.changeset/yellow-falcons-whisper.md
@@ -0,0 +1,6 @@
+---
+'@backstage/plugin-scaffolder-backend': patch
+'@backstage/plugin-scaffolder-common': patch
+---
+
+Expose template metadata to custom action handler in Scaffolder.
diff --git a/docs/features/software-templates/writing-custom-actions.md b/docs/features/software-templates/writing-custom-actions.md
index 07c4891a6c..e578918cc0 100644
--- a/docs/features/software-templates/writing-custom-actions.md
+++ b/docs/features/software-templates/writing-custom-actions.md
@@ -91,6 +91,8 @@ argument. It looks like the following:
- `createTemporaryDirectory` a function to call to give you a temporary
directory somewhere on the runner so you can store some files there rather
than polluting the `workspacePath`
+- `ctx.metadata` - an object containing a `name` field, indicating the template
+ name. More metadata fields may be added later.
### Registering Custom Actions
diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md
index 3f9b77a3a3..4b27b938c9 100644
--- a/plugins/scaffolder-backend/api-report.md
+++ b/plugins/scaffolder-backend/api-report.md
@@ -41,6 +41,7 @@ export type ActionContext = {
input: Input;
output(name: string, value: JsonValue): void;
createTemporaryDirectory(): Promise;
+ metadata?: TemplateMetadata;
};
// Warning: (ae-missing-release-tag) "CatalogEntityClient" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@@ -432,6 +433,8 @@ export interface TaskSpecV1beta2 {
// (undocumented)
baseUrl?: string;
// (undocumented)
+ metadata?: TemplateMetadata;
+ // (undocumented)
output: {
[name: string]: string;
};
@@ -454,6 +457,8 @@ export interface TaskSpecV1beta3 {
// (undocumented)
baseUrl?: string;
// (undocumented)
+ metadata?: TemplateMetadata;
+ // (undocumented)
output: {
[name: string]: JsonValue;
};
@@ -558,4 +563,9 @@ export class TemplateActionRegistry {
action: TemplateAction,
): void;
}
+
+// @public
+export type TemplateMetadata = {
+ name: string;
+};
```
diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/types.ts b/plugins/scaffolder-backend/src/scaffolder/actions/types.ts
index ca3459d29c..52149be724 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/types.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/actions/types.ts
@@ -18,6 +18,7 @@ import { Logger } from 'winston';
import { Writable } from 'stream';
import { JsonValue, JsonObject } from '@backstage/types';
import { Schema } from 'jsonschema';
+import { TemplateMetadata } from '../tasks/types';
type PartialJsonObject = Partial;
type PartialJsonValue = PartialJsonObject | JsonValue | undefined;
@@ -44,6 +45,8 @@ export type ActionContext = {
* Creates a temporary directory for use by the action, which is then cleaned up automatically.
*/
createTemporaryDirectory(): Promise;
+
+ metadata?: TemplateMetadata;
};
export type TemplateAction = {
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DefaultWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DefaultWorkflowRunner.test.ts
index d07ff29fae..1f7877791c 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/DefaultWorkflowRunner.test.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DefaultWorkflowRunner.test.ts
@@ -146,6 +146,30 @@ describe('DefaultWorkflowRunner', () => {
expect(fakeActionHandler).toHaveBeenCalledTimes(1);
});
+
+ it('should pass metadata through', async () => {
+ const templateName = 'template name';
+ const task = createMockTaskWithSpec({
+ apiVersion: 'scaffolder.backstage.io/v1beta3',
+ parameters: {},
+ output: {},
+ steps: [
+ {
+ id: 'test',
+ name: 'name',
+ action: 'jest-validated-action',
+ input: { foo: 1 },
+ },
+ ],
+ metadata: { name: templateName },
+ });
+
+ await runner.execute(task);
+
+ expect(fakeActionHandler.mock.calls[0][0].metadata).toEqual({
+ name: templateName,
+ });
+ });
});
describe('conditionals', () => {
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.ts
index 77d0a7be09..5582b53023 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.ts
@@ -225,6 +225,12 @@ export class HandlebarsWorkflowRunner implements WorkflowRunner {
input: JSON.stringify(input, null, 2),
});
+ if (!task.spec.metadata) {
+ console.warn(
+ 'DEPRECATION NOTICE: metadata is undefined. metadata will be required in the future.',
+ );
+ }
+
await action.handler({
baseUrl: task.spec.baseUrl,
logger: taskLogger,
@@ -242,6 +248,7 @@ export class HandlebarsWorkflowRunner implements WorkflowRunner {
output(name: string, value: JsonValue) {
stepOutputs[name] = value;
},
+ metadata: task.spec.metadata,
});
// Remove all temporary directories that were created when executing the action
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/LegacyWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/LegacyWorkflowRunner.test.ts
index 91e772dc4e..9b05c43831 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/LegacyWorkflowRunner.test.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/LegacyWorkflowRunner.test.ts
@@ -28,6 +28,7 @@ describe('LegacyWorkflowRunner', () => {
let runner: HandlebarsWorkflowRunner;
const logger = getVoidLogger();
let actionRegistry = new TemplateActionRegistry();
+ let fakeActionHandler: jest.Mock;
const integrations = ScmIntegrations.fromConfig(
new ConfigReader({
@@ -59,6 +60,11 @@ describe('LegacyWorkflowRunner', () => {
ctx.output('badOutput', false);
},
});
+ fakeActionHandler = jest.fn();
+ actionRegistry.register({
+ id: 'test-metadata-action',
+ handler: fakeActionHandler,
+ });
runner = new HandlebarsWorkflowRunner({
actionRegistry,
@@ -87,6 +93,30 @@ describe('LegacyWorkflowRunner', () => {
);
});
+ it('should pass metadata through', async () => {
+ const templateName = 'template name';
+ const task = createMockTaskWithSpec({
+ apiVersion: 'backstage.io/v1beta2',
+ steps: [
+ {
+ id: 'test',
+ name: 'name',
+ action: 'test-metadata-action',
+ input: { foo: 1 },
+ },
+ ],
+ output: {},
+ values: {},
+ metadata: { name: templateName },
+ });
+
+ await runner.execute(task);
+
+ expect(fakeActionHandler.mock.calls[0][0].metadata).toEqual({
+ name: templateName,
+ });
+ });
+
describe('templating', () => {
it('should template the output', async () => {
const task = createMockTaskWithSpec({
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts
index 587a93e1f2..c66d967103 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts
@@ -228,6 +228,12 @@ export class NunjucksWorkflowRunner implements WorkflowRunner {
const tmpDirs = new Array();
const stepOutput: { [outputName: string]: JsonValue } = {};
+ if (!task.spec.metadata) {
+ console.warn(
+ 'DEPRECATION NOTICE: metadata is undefined. metadata will be required in the future.',
+ );
+ }
+
await action.handler({
baseUrl: task.spec.baseUrl,
input,
@@ -244,6 +250,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner {
output(name: string, value: JsonValue) {
stepOutput[name] = value;
},
+ metadata: task.spec.metadata,
});
// Remove all temporary directories that were created when executing the action
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts
index 510c862dae..f563a08df6 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts
@@ -34,4 +34,5 @@ export type {
TaskContext,
TaskStore,
DispatchResult,
+ TemplateMetadata,
} from './types';
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts
index 6cfd914bd2..b492f8ad2c 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts
@@ -69,6 +69,15 @@ export type SerializedTaskEvent = {
createdAt: string;
};
+/**
+ * TemplateMetadata
+ *
+ * @public
+ */
+export type TemplateMetadata = {
+ name: string;
+};
+
/**
* TaskSpecV1beta2
*
@@ -86,6 +95,7 @@ export interface TaskSpecV1beta2 {
if?: string | boolean;
}>;
output: { [name: string]: string };
+ metadata?: TemplateMetadata;
}
export interface TaskStep {
@@ -107,6 +117,7 @@ export interface TaskSpecV1beta3 {
parameters: JsonObject;
steps: TaskStep[];
output: { [name: string]: JsonValue };
+ metadata?: TemplateMetadata;
}
/**
diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts
index 2ae30593f2..b8f2968287 100644
--- a/plugins/scaffolder-backend/src/service/router.ts
+++ b/plugins/scaffolder-backend/src/service/router.ts
@@ -209,6 +209,7 @@ export async function createRouter(
name: step.name ?? step.action,
})),
output: template.spec.output ?? {},
+ metadata: { name: template.metadata?.name },
}
: {
apiVersion: template.apiVersion,
@@ -220,6 +221,7 @@ export async function createRouter(
name: step.name ?? step.action,
})),
output: template.spec.output ?? {},
+ metadata: { name: template.metadata?.name },
};
} else {
throw new InputError(