diff --git a/.changeset/silver-ties-marry.md b/.changeset/silver-ties-marry.md
new file mode 100644
index 0000000000..2a3b01f2fc
--- /dev/null
+++ b/.changeset/silver-ties-marry.md
@@ -0,0 +1,10 @@
+---
+'@backstage/plugin-scaffolder': patch
+'@backstage/plugin-scaffolder-backend': patch
+---
+
+Added the ability to support supplying secrets when creating tasks in the `scaffolder-backend`.
+
+**deprecation**: Deprecated `ctx.token` from actions in the `scaffolder-backend`. Please move to using `ctx.secrets.backstageToken` instead.
+
+**deprecation**: Deprecated `task.token` in `TaskSpec` in the `scaffolder-backend`. Please move to using `task.secrets.backstageToken` instead.
diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md
index ee84e06ae9..12b29cae39 100644
--- a/plugins/scaffolder-backend/api-report.md
+++ b/plugins/scaffolder-backend/api-report.md
@@ -43,6 +43,7 @@ export type ActionContext = {
logger: Logger_2;
logStream: Writable;
token?: string | undefined;
+ secrets?: TaskSecrets;
workspacePath: string;
input: Input;
output(name: string, value: JsonValue): void;
@@ -439,8 +440,9 @@ export class TaskManager implements TaskContext {
}
// @public
-export type TaskSecrets = {
- token: string | undefined;
+export type TaskSecrets = Record & {
+ token?: string;
+ backstageToken?: string;
};
export { TaskSpec };
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 249939b0e6..65d0e1b1e7 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts
@@ -110,7 +110,9 @@ export function createCatalogRegisterAction(options: {
type: 'url',
target: catalogInfoUrl,
},
- ctx.token ? { token: ctx.token } : {},
+ ctx.secrets?.backstageToken
+ ? { token: ctx.secrets.backstageToken }
+ : {},
);
try {
@@ -120,7 +122,9 @@ export function createCatalogRegisterAction(options: {
type: 'url',
target: catalogInfoUrl,
},
- ctx.token ? { token: ctx.token } : {},
+ ctx.secrets?.backstageToken
+ ? { token: ctx.secrets.backstageToken }
+ : {},
);
if (result.entities.length > 0) {
diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/types.ts b/plugins/scaffolder-backend/src/scaffolder/actions/types.ts
index 52149be724..4b842e8c5c 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/types.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/actions/types.ts
@@ -18,7 +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';
+import { TaskSecrets, TemplateMetadata } from '../tasks/types';
type PartialJsonObject = Partial;
type PartialJsonValue = PartialJsonObject | JsonValue | undefined;
@@ -35,8 +35,10 @@ export type ActionContext = {
/**
* User token forwarded from initial request, for use in subsequent api requests
+ * @deprecated use `secrets.backstageToken` instead
*/
token?: string | undefined;
+ secrets?: TaskSecrets;
workspacePath: string;
input: Input;
output(name: string, value: JsonValue): void;
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts
index 813d8d0f5b..dcda3b921e 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts
@@ -43,7 +43,7 @@ export type RawDbTaskRow = {
status: Status;
last_heartbeat_at?: string;
created_at: string;
- secrets?: string;
+ secrets?: string | null;
};
export type RawDbTaskEventRow = {
@@ -139,6 +139,8 @@ export class DatabaseTaskStore implements TaskStore {
.update({
status: 'processing',
last_heartbeat_at: this.db.fn.now(),
+ // remove the secrets when moving moving to processing state.
+ secrets: null,
});
if (updateCount < 1) {
@@ -235,8 +237,8 @@ export class DatabaseTaskStore implements TaskStore {
})
.update({
status,
- secrets: null as any,
});
+
if (updateCount !== 1) {
throw new ConflictError(
`Failed to update status to '${status}' for taskId ${taskId}`,
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/LegacyWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.test.ts
similarity index 100%
rename from plugins/scaffolder-backend/src/scaffolder/tasks/LegacyWorkflowRunner.test.ts
rename to plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.test.ts
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.ts
index 5582b53023..91c82edebb 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.ts
@@ -236,7 +236,9 @@ export class HandlebarsWorkflowRunner implements WorkflowRunner {
logger: taskLogger,
logStream: stream,
input,
+ // this token is deprecated, and will be removed in favour of secrets.backstageToken instead.
token: task.secrets?.token,
+ secrets: task.secrets ?? {},
workspacePath,
async createTemporaryDirectory() {
const tmpDir = await fs.mkdtemp(
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts
index aace2cb437..161d5b865b 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts
@@ -472,6 +472,33 @@ describe('DefaultWorkflowRunner', () => {
});
});
+ describe('secrets', () => {
+ it('should pass through the secrets to the context', async () => {
+ const task = createMockTaskWithSpec(
+ {
+ apiVersion: 'scaffolder.backstage.io/v1beta3',
+ steps: [
+ {
+ id: 'test',
+ name: 'name',
+ action: 'jest-mock-action',
+ input: {},
+ },
+ ],
+ output: {},
+ parameters: {},
+ },
+ { foo: 'bar' },
+ );
+
+ await runner.execute(task);
+
+ expect(fakeActionHandler).toHaveBeenCalledWith(
+ expect.objectContaining({ secrets: { foo: 'bar' } }),
+ );
+ });
+ });
+
describe('filters', () => {
it('provides the parseRepoUrl filter', 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 71d53cc7ec..46f87c1d00 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts
@@ -257,7 +257,9 @@ export class NunjucksWorkflowRunner implements WorkflowRunner {
await action.handler({
baseUrl: task.spec.baseUrl,
input,
+ // this token is deprecated, and will be removed in favour of secrets.backstageToken instead.
token: task.secrets?.token,
+ secrets: task.secrets ?? {},
logger: taskLogger,
logStream: streamLogger,
workspacePath,
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts
index 76fb8e00f0..130f6ced13 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts
@@ -94,13 +94,12 @@ describe('StorageTaskBroker', () => {
expect(taskRow.status).toBe('completed');
}, 10000);
- it('should remove secrets after completing a task', async () => {
+ it('should remove secrets after picking up a task', async () => {
const broker = new StorageTaskBroker(storage, logger);
const dispatchResult = await broker.dispatch({} as TaskSpec, fakeSecrets);
- const task = await broker.claim();
- await task.complete('completed');
+ await broker.claim();
+
const taskRow = await storage.getTask(dispatchResult.taskId);
- expect(taskRow.status).toBe('completed');
expect(taskRow.secrets).toBeUndefined();
}, 10000);
@@ -113,16 +112,6 @@ describe('StorageTaskBroker', () => {
expect(taskRow.status).toBe('failed');
});
- it('should remove secrets after failing a task', async () => {
- const broker = new StorageTaskBroker(storage, logger);
- const dispatchResult = await broker.dispatch({} as TaskSpec, fakeSecrets);
- const task = await broker.claim();
- await task.complete('failed');
- const taskRow = await storage.getTask(dispatchResult.taskId);
- expect(taskRow.status).toBe('failed');
- expect(taskRow.secrets).toBeUndefined();
- });
-
it('multiple brokers should be able to observe a single task', async () => {
const broker1 = new StorageTaskBroker(storage, logger);
const broker2 = new StorageTaskBroker(storage, logger);
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts
index 1a69f28fb6..8caee87f57 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts
@@ -89,8 +89,10 @@ export type SerializedTaskEvent = {
*
* @public
*/
-export type TaskSecrets = {
- token: string | undefined;
+export type TaskSecrets = Record & {
+ /** @deprecated Use `backstageToken` instead */
+ token?: string;
+ backstageToken?: string;
};
/**
diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts
index 9d26857ab9..f4f7398c8a 100644
--- a/plugins/scaffolder-backend/src/service/router.ts
+++ b/plugins/scaffolder-backend/src/service/router.ts
@@ -232,6 +232,9 @@ export async function createRouter(
}
const result = await taskBroker.dispatch(taskSpec, {
+ ...req.body.secrets,
+ backstageToken: token,
+ // This is deprecated, but we need to support it for now if people are running their own task broker.
token: token,
});
diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md
index 8443de563e..d2bbb2a5e1 100644
--- a/plugins/scaffolder/api-report.md
+++ b/plugins/scaffolder/api-report.md
@@ -192,7 +192,11 @@ export interface ScaffolderApi {
//
// (undocumented)
listActions(): Promise;
- scaffold(templateName: string, values: Record): Promise;
+ scaffold(
+ templateName: string,
+ values: Record,
+ secrets?: Record,
+ ): Promise;
// Warning: (ae-forgotten-export) The symbol "LogEvent" needs to be exported by the entry point index.d.ts
//
// (undocumented)
@@ -236,7 +240,11 @@ export class ScaffolderClient implements ScaffolderApi {
): Promise;
// (undocumented)
listActions(): Promise;
- scaffold(templateName: string, values: Record): Promise;
+ scaffold(
+ templateName: string,
+ values: Record,
+ secrets?: Record,
+ ): Promise;
// (undocumented)
streamLogs(opts: { taskId: string; after?: number }): Observable;
}
diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts
index 70ed803ef7..7f096a02f7 100644
--- a/plugins/scaffolder/src/api.ts
+++ b/plugins/scaffolder/src/api.ts
@@ -69,8 +69,13 @@ export interface ScaffolderApi {
*
* @param templateName - Name of the Template entity for the scaffolder to use. New project is going to be created out of this template.
* @param values - Parameters for the template, e.g. name, description
+ * @param secrets - Optional secrets to pass to as the secrets parameter to the template.
*/
- scaffold(templateName: string, values: Record): Promise;
+ scaffold(
+ templateName: string,
+ values: Record,
+ secrets?: Record,
+ ): Promise;
getTask(taskId: string): Promise;
@@ -151,10 +156,12 @@ export class ScaffolderClient implements ScaffolderApi {
*
* @param templateName - Template name for the scaffolder to use. New project is going to be created out of this template.
* @param values - Parameters for the template, e.g. name, description
+ * @param secrets - Optional secrets to pass to as the secrets parameter to the template.
*/
async scaffold(
templateName: string,
values: Record,
+ secrets: Record = {},
): Promise {
const { token } = await this.identityApi.getCredentials();
const url = `${await this.discoveryApi.getBaseUrl('scaffolder')}/v2/tasks`;
@@ -164,7 +171,11 @@ export class ScaffolderClient implements ScaffolderApi {
'Content-Type': 'application/json',
...(token && { Authorization: `Bearer ${token}` }),
},
- body: JSON.stringify({ templateName, values: { ...values } }),
+ body: JSON.stringify({
+ templateName,
+ values: { ...values },
+ secrets,
+ }),
});
if (response.status !== 201) {