From f3ba1fc4639ce484ecbaaa925f434611baec9cfb Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Tue, 23 Feb 2021 10:20:16 +0100 Subject: [PATCH 1/6] Add TaskWorker test Signed-off-by: Johan Haals --- .../src/scaffolder/tasks/TaskWorker.test.ts | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts new file mode 100644 index 0000000000..221b6dc625 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts @@ -0,0 +1,112 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + getVoidLogger, + SingleConnectionDatabaseManager, +} from '@backstage/backend-common'; +import { TaskWorker } from './TaskWorker'; +import os from 'os'; +import { ActionContext, TemplateActionRegistry } from './TemplateConverter'; +import { ConfigReader, JsonObject } from '@backstage/config'; +import { StorageTaskBroker } from './StorageTaskBroker'; +import { DatabaseTaskStore } from './DatabaseTaskStore'; + +async function createStore(): Promise { + const manager = SingleConnectionDatabaseManager.fromConfig( + new ConfigReader({ + backend: { + database: { + client: 'sqlite3', + connection: ':memory:', + }, + }, + }), + ).forPlugin('scaffolder'); + return await DatabaseTaskStore.create(await manager.getClient()); +} + +describe('TaskWorker', () => { + let storage: DatabaseTaskStore; + + beforeAll(async () => { + storage = await createStore(); + }); + + const logger = getVoidLogger(); + const actionRegistry = new TemplateActionRegistry(); + actionRegistry.register({ + id: 'test-action', + handler: async (ctx: ActionContext) => { + ctx.output('testOutput', 'winning'); + }, + }); + + it('should fail when action does not exist', async () => { + const broker = new StorageTaskBroker(storage, logger); + const taskWorker = new TaskWorker({ + logger, + workingDirectory: os.tmpdir(), + actionRegistry, + taskBroker: broker, + }); + const { taskId } = await broker.dispatch({ + steps: [{ id: 'test', name: 'test', action: 'not-found-action' }], + output: { + result: '{{ steps.test.output.testOutput }}', + }, + }); + const task = await broker.claim(); + await taskWorker.runOneTask(task); + const { events } = await storage.listEvents({ taskId }); + const event = events.find(e => e.type === 'completion'); + + if (!event) { + throw new Error('Expected event'); + } + + expect((event.body?.error as JsonObject)?.message).toBe( + "Template action with ID 'not-found-action' is not registered.", + ); + }); + + it('should template output', async () => { + const broker = new StorageTaskBroker(storage, logger); + const taskWorker = new TaskWorker({ + logger, + workingDirectory: os.tmpdir(), + actionRegistry, + taskBroker: broker, + }); + + const { taskId } = await broker.dispatch({ + steps: [{ id: 'test', name: 'test', action: 'test-action' }], + output: { + result: '{{ steps.test.output.testOutput }}', + }, + }); + + const task = await broker.claim(); + await taskWorker.runOneTask(task); + + const { events } = await storage.listEvents({ taskId }); + const event = events.find(e => e.type === 'completion'); + if (!event) { + throw new Error('Expected event'); + } + expect((event.body?.output as JsonObject).result).toBe('winning'); + }); +}); From 561fd1645bc43e39590ff91ff2e8ad8c341c7aa2 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Thu, 25 Feb 2021 09:21:37 +0100 Subject: [PATCH 2/6] chore: fix expect Signed-off-by: Johan Haals --- .../src/scaffolder/tasks/TaskWorker.test.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts index 221b6dc625..a75338778a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts @@ -74,11 +74,7 @@ describe('TaskWorker', () => { const { events } = await storage.listEvents({ taskId }); const event = events.find(e => e.type === 'completion'); - if (!event) { - throw new Error('Expected event'); - } - - expect((event.body?.error as JsonObject)?.message).toBe( + expect((event?.body?.error as JsonObject)?.message).toBe( "Template action with ID 'not-found-action' is not registered.", ); }); @@ -104,9 +100,6 @@ describe('TaskWorker', () => { const { events } = await storage.listEvents({ taskId }); const event = events.find(e => e.type === 'completion'); - if (!event) { - throw new Error('Expected event'); - } - expect((event.body?.output as JsonObject).result).toBe('winning'); + expect((event?.body?.output as JsonObject).result).toBe('winning'); }); }); From 6e435e9a0814fcd41d16c4d28547de3b768212a6 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 5 Mar 2021 09:48:37 +0100 Subject: [PATCH 3/6] Update tests Signed-off-by: Johan Haals --- .../src/scaffolder/tasks/TaskWorker.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts index a75338778a..6facaa21f2 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts @@ -20,10 +20,10 @@ import { } from '@backstage/backend-common'; import { TaskWorker } from './TaskWorker'; import os from 'os'; -import { ActionContext, TemplateActionRegistry } from './TemplateConverter'; import { ConfigReader, JsonObject } from '@backstage/config'; import { StorageTaskBroker } from './StorageTaskBroker'; import { DatabaseTaskStore } from './DatabaseTaskStore'; +import { TemplateActionRegistry } from '../actions'; async function createStore(): Promise { const manager = SingleConnectionDatabaseManager.fromConfig( @@ -50,7 +50,7 @@ describe('TaskWorker', () => { const actionRegistry = new TemplateActionRegistry(); actionRegistry.register({ id: 'test-action', - handler: async (ctx: ActionContext) => { + handler: async ctx => { ctx.output('testOutput', 'winning'); }, }); @@ -68,6 +68,7 @@ describe('TaskWorker', () => { output: { result: '{{ steps.test.output.testOutput }}', }, + values: {}, }); const task = await broker.claim(); await taskWorker.runOneTask(task); @@ -93,6 +94,7 @@ describe('TaskWorker', () => { output: { result: '{{ steps.test.output.testOutput }}', }, + values: {}, }); const task = await broker.claim(); From c20bc8abc454b2ec93cf10e2e16f267cba3b84a7 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 5 Mar 2021 09:54:27 +0100 Subject: [PATCH 4/6] Don't json parse undefined input Signed-off-by: Johan Haals --- .../src/scaffolder/tasks/TaskWorker.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index 4d8f652ae0..bd0a418598 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -98,9 +98,9 @@ export class TaskWorker { throw new Error(`Action '${step.action}' does not exist`); } - const input = JSON.parse( - JSON.stringify(step.input), - (_key, value) => { + const input = + step.input && + JSON.parse(JSON.stringify(step.input), (_key, value) => { if (typeof value === 'string') { return handlebars.compile(value, { noEscape: true, @@ -110,8 +110,7 @@ export class TaskWorker { })(templateCtx); } return value; - }, - ); + }); if (action.schema?.input) { const validateResult = validateJsonSchema(input, action.schema, { From c532c16828d3994fb05d063e2d2c941da543c961 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 5 Mar 2021 10:14:14 +0100 Subject: [PATCH 5/6] Add changeset Signed-off-by: Johan Haals --- .changeset/many-eyes-rest.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/many-eyes-rest.md diff --git a/.changeset/many-eyes-rest.md b/.changeset/many-eyes-rest.md new file mode 100644 index 0000000000..d7a5a806b2 --- /dev/null +++ b/.changeset/many-eyes-rest.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Fixes task failures caused by undefined step input From c3eb4917fb9de6ba9919cdf726ade4de3db7f6c3 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 5 Mar 2021 10:28:13 +0100 Subject: [PATCH 6/6] Add input template test Signed-off-by: Johan Haals --- .../src/scaffolder/tasks/TaskWorker.test.ts | 64 ++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts index 6facaa21f2..b598b91036 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts @@ -23,7 +23,7 @@ import os from 'os'; import { ConfigReader, JsonObject } from '@backstage/config'; import { StorageTaskBroker } from './StorageTaskBroker'; import { DatabaseTaskStore } from './DatabaseTaskStore'; -import { TemplateActionRegistry } from '../actions'; +import { createTemplateAction, TemplateActionRegistry } from '../actions'; async function createStore(): Promise { const manager = SingleConnectionDatabaseManager.fromConfig( @@ -104,4 +104,66 @@ describe('TaskWorker', () => { const event = events.find(e => e.type === 'completion'); expect((event?.body?.output as JsonObject).result).toBe('winning'); }); + + it('should template input', async () => { + const inputAction = createTemplateAction<{ + name: string; + }>({ + id: 'test-input', + schema: { + input: { + type: 'object', + required: ['name'], + properties: { + name: { + title: 'name', + description: 'Enter name', + type: 'string', + }, + }, + }, + }, + async handler(ctx) { + if (ctx.input.name !== 'winning') { + throw new Error( + `expected name to be "winning" got ${ctx.input.name}`, + ); + } + }, + }); + actionRegistry.register(inputAction); + + const broker = new StorageTaskBroker(storage, logger); + const taskWorker = new TaskWorker({ + logger, + workingDirectory: os.tmpdir(), + actionRegistry, + taskBroker: broker, + }); + + const { taskId } = await broker.dispatch({ + steps: [ + { id: 'test', name: 'test', action: 'test-action' }, + { + id: 'test-input', + name: 'test-input', + action: 'test-input', + input: { + name: '{{ steps.test.output.testOutput }}', + }, + }, + ], + output: { + result: '{{ steps.test.output.testOutput }}', + }, + values: {}, + }); + + const task = await broker.claim(); + await taskWorker.runOneTask(task); + + const { events } = await storage.listEvents({ taskId }); + const event = events.find(e => e.type === 'completion'); + expect((event?.body?.output as JsonObject).result).toBe('winning'); + }); });