diff --git a/.changeset/many-pears-drop.md b/.changeset/many-pears-drop.md index 085d0fb8b2..f2557c84a1 100644 --- a/.changeset/many-pears-drop.md +++ b/.changeset/many-pears-drop.md @@ -15,7 +15,6 @@ '@backstage/plugin-catalog-backend-module-azure': patch '@backstage/plugin-catalog-backend-module-aws': patch '@backstage/plugin-catalog-backend-module-gcp': patch -'@backstage/plugin-scaffolder-node-test-utils': patch '@backstage/plugin-entity-feedback-backend': patch '@backstage/plugin-code-coverage-backend': patch '@backstage/plugin-tech-insights-backend': patch @@ -40,7 +39,6 @@ '@backstage/plugin-search-backend': patch '@backstage/plugin-kafka-backend': patch '@backstage/plugin-nomad-backend': patch -'@backstage/plugin-techdocs-node': patch '@backstage/plugin-todo-backend': patch '@backstage/plugin-adr-backend': patch '@backstage/plugin-app-backend': patch diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.examples.test.ts index 4f03faae45..8bc29a74c9 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.examples.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.examples.test.ts @@ -15,6 +15,7 @@ */ import { createWaitAction } from './wait'; +import { Writable } from 'stream'; import { examples } from './wait.examples'; import yaml from 'yaml'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; @@ -22,7 +23,13 @@ import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test- describe('debug:wait examples', () => { const action = createWaitAction(); - const mockContext = createMockActionContext({}); + const logStream = { + write: jest.fn(), + } as jest.Mocked> as jest.Mocked; + + const mockContext = createMockActionContext({ + logStream, + }); beforeEach(() => { jest.resetAllMocks(); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.test.ts index 1cb643905e..0dcbd10f0f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/wait.test.ts @@ -15,12 +15,19 @@ */ import { createWaitAction } from './wait'; +import { Writable } from 'stream'; import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; describe('debug:wait', () => { const action = createWaitAction(); - const mockContext = createMockActionContext({}); + const logStream = { + write: jest.fn(), + } as jest.Mocked> as jest.Mocked; + + const mockContext = createMockActionContext({ + logStream, + }); beforeEach(() => { jest.resetAllMocks(); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 070dbb910d..81897a96f8 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -31,17 +31,16 @@ import { SecureTemplateRenderer, } from '../../lib/templating/SecureTemplater'; import { - TaskRecovery, TaskSpec, TaskSpecV1beta3, TaskStep, } from '@backstage/plugin-scaffolder-common'; import { - TaskContext, TemplateAction, TemplateFilter, TemplateGlobal, + TaskContext, } from '@backstage/plugin-scaffolder-node'; import { createConditionAuthorizer } from '@backstage/plugin-permission-node'; import { UserEntity } from '@backstage/catalog-model'; @@ -53,7 +52,9 @@ import { } from '@backstage/plugin-permission-common'; import { scaffolderActionRules } from '../../service/rules'; import { actionExecutePermission } from '@backstage/plugin-scaffolder-common/alpha'; +import { TaskRecovery } from '@backstage/plugin-scaffolder-common'; import { PermissionsService } from '@backstage/backend-plugin-api'; +import { loggerToWinstonLogger } from '@backstage/backend-common'; import { WinstonLogger } from './logger'; type NunjucksWorkflowRunnerOptions = { @@ -123,7 +124,22 @@ const createStepLogger = ({ taskLogger.addRedactions(Object.values(task.secrets ?? {})); - return taskLogger; + // This stream logger should be deprecated. We're going to replace it with + // just using the logger directly, as all those logs get written to step logs + // using the stepLogStream above. + // Initially this stream used to be the only way to write to the client logs, but that + // has changed over time, there's not really a need for this anymore. + // You can just create a simple wrapper like the below in your action to write to the main logger. + // This way we also get recactions for free. + const streamLogger = new PassThrough(); + streamLogger.on('data', async data => { + const message = data.toString().trim(); + if (message?.length > 1) { + taskLogger.info(message); + } + }); + + return { taskLogger, streamLogger }; }; const isActionAuthorized = createConditionAuthorizer( @@ -246,7 +262,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { const action: TemplateAction = this.options.actionRegistry.get(step.action); - const taskLogger = createStepLogger({ task, step }); + const { taskLogger, streamLogger } = createStepLogger({ task, step }); if (task.isDryRun) { const redactedSecrets = Object.fromEntries( @@ -358,7 +374,9 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { await action.handler({ input: iteration.input, secrets: task.secrets ?? {}, - logger: taskLogger, + // TODO(blam): move to LoggerService and away from Winston + logger: loggerToWinstonLogger(taskLogger), + logStream: streamLogger, workspacePath, async checkpoint( keySuffix: string, diff --git a/plugins/scaffolder-node-test-utils/src/actions/mockActionConext.ts b/plugins/scaffolder-node-test-utils/src/actions/mockActionConext.ts index 6d81017245..15ad5eb769 100644 --- a/plugins/scaffolder-node-test-utils/src/actions/mockActionConext.ts +++ b/plugins/scaffolder-node-test-utils/src/actions/mockActionConext.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { PassThrough } from 'stream'; import { getVoidLogger } from '@backstage/backend-common'; import { createMockDirectory, @@ -37,6 +38,7 @@ export const createMockActionContext = < const credentials = mockCredentials.user(); const defaultContext = { logger: getVoidLogger(), + logStream: new PassThrough(), output: jest.fn(), createTemporaryDirectory: jest.fn(), input: {} as TActionInput, @@ -55,7 +57,8 @@ export const createMockActionContext = < }; } - const { input, logger, secrets, templateInfo, workspacePath } = options; + const { input, logger, logStream, secrets, templateInfo, workspacePath } = + options; return { ...defaultContext, @@ -64,6 +67,7 @@ export const createMockActionContext = < createTemporaryDirectory: jest.fn().mockResolvedValue(workspacePath), }), ...(logger && { logger }), + ...(logStream && { logStream }), ...(input && { input }), ...(secrets && { secrets }), templateInfo, diff --git a/yarn.lock b/yarn.lock index 4489b5c214..344861274a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8811,6 +8811,7 @@ __metadata: globby: ^11.0.0 jsonschema: ^1.2.6 p-limit: ^3.1.0 + winston: ^3.2.1 zod: ^3.22.4 zod-to-json-schema: ^3.20.4 languageName: unknown @@ -9775,6 +9776,7 @@ __metadata: p-limit: ^3.1.0 recursive-readdir: ^2.2.2 supertest: ^6.1.3 + winston: ^3.2.1 languageName: unknown linkType: soft