diff --git a/.changeset/young-dancers-shave.md b/.changeset/young-dancers-shave.md new file mode 100644 index 0000000000..f7cdc3f3c2 --- /dev/null +++ b/.changeset/young-dancers-shave.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-scaffolder-node': patch +--- + +Generating new tokens on each Scaffolder Task Retry diff --git a/plugins/scaffolder-backend/report.api.md b/plugins/scaffolder-backend/report.api.md index 0438922de3..41c228e3f1 100644 --- a/plugins/scaffolder-backend/report.api.md +++ b/plugins/scaffolder-backend/report.api.md @@ -502,7 +502,10 @@ export class DatabaseTaskStore implements TaskStore { targetPath: string; }): Promise; // (undocumented) - retryTask?(options: { taskId: string }): Promise; + retryTask?(options: { + secrets?: TaskSecrets_2; + taskId: string; + }): Promise; // (undocumented) saveTaskState(options: { taskId: string; state?: JsonObject }): Promise; // (undocumented) @@ -769,7 +772,10 @@ export interface TaskStore { targetPath: string; }): Promise; // (undocumented) - retryTask?(options: { taskId: string }): Promise; + retryTask?(options: { + secrets?: TaskSecrets_2; + taskId: string; + }): Promise; // (undocumented) saveTaskState?(options: { taskId: string; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts index 4dbe938679..51a30a20ec 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts @@ -654,12 +654,18 @@ export class DatabaseTaskStore implements TaskStore { }); } - async retryTask?(options: { taskId: string }): Promise { + async retryTask?(options: { + secrets?: TaskSecrets; + taskId: string; + }): Promise { + const { secrets, taskId } = options; + await this.db.transaction(async tx => { const result = await tx('tasks') - .where('id', options.taskId) + .where('id', taskId) .update( { + ...(secrets && { secrets: JSON.stringify(secrets) }), status: 'open', last_heartbeat_at: this.db.fn.now(), }, diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index 0fd18a9ee5..27a4499772 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -503,8 +503,11 @@ export class StorageTaskBroker implements TaskBroker { }); } - async retry?(taskId: string): Promise { - await this.storage.retryTask?.({ taskId }); + async retry?(options: { + secrets?: TaskSecrets; + taskId: string; + }): Promise { + await this.storage.retryTask?.(options); this.signalDispatch(); } } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index 874793922f..1556c4838c 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -171,7 +171,7 @@ export interface TaskStore { options: TaskStoreCreateTaskOptions, ): Promise; - retryTask?(options: { taskId: string }): Promise; + retryTask?(options: { secrets?: TaskSecrets; taskId: string }): Promise; recoverTasks?( options: TaskStoreRecoverTaskOptions, diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index e5c384987a..f2e323e831 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -828,7 +828,22 @@ export async function createRouter( await auditorEvent?.success(); - await taskBroker.retry?.(taskId); + const { token } = await auth.getPluginRequestToken({ + onBehalfOf: credentials, + targetPluginId: 'catalog', + }); + + const secrets: InternalTaskSecrets = { + ...req.body.secrets, + backstageToken: token, + __initiatorCredentials: JSON.stringify({ + ...credentials, + // credentials.token is nonenumerable and will not be serialized, so we need to add it explicitly + token: (credentials as any).token, + }), + }; + + await taskBroker.retry?.({ secrets, taskId }); res.status(201).json({ id: taskId }); } catch (err) { await auditorEvent?.fail({ error: err }); diff --git a/plugins/scaffolder-node/report.api.md b/plugins/scaffolder-node/report.api.md index 6dabcbf346..f2e8501527 100644 --- a/plugins/scaffolder-node/report.api.md +++ b/plugins/scaffolder-node/report.api.md @@ -414,7 +414,7 @@ export interface TaskBroker { // (undocumented) recoverTasks?(): Promise; // (undocumented) - retry?(taskId: string): Promise; + retry?(options: { secrets?: TaskSecrets; taskId: string }): Promise; // (undocumented) vacuumTasks(options: { timeoutS: number }): Promise; } diff --git a/plugins/scaffolder-node/src/tasks/types.ts b/plugins/scaffolder-node/src/tasks/types.ts index fdfeb49a0b..d8cfd24530 100644 --- a/plugins/scaffolder-node/src/tasks/types.ts +++ b/plugins/scaffolder-node/src/tasks/types.ts @@ -165,7 +165,7 @@ export interface TaskContext { export interface TaskBroker { cancel?(taskId: string): Promise; - retry?(taskId: string): Promise; + retry?(options: { secrets?: TaskSecrets; taskId: string }): Promise; claim(): Promise;