diff --git a/.changeset/fruity-rivers-arrive.md b/.changeset/fruity-rivers-arrive.md new file mode 100644 index 0000000000..a816946bcc --- /dev/null +++ b/.changeset/fruity-rivers-arrive.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +--- + +Add `defaultEnvironment` config to scaffolder to enable more flexible and custom templates. Now it's possible enable access to default parameters and secrets in templates, improving security and reducing complexity. diff --git a/app-config.yaml b/app-config.yaml index 1489a39105..c1a894dcc7 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -209,6 +209,11 @@ scaffolder: email: scaffolder@backstage.io # Use to customize the default commit message when new components are created defaultCommitMessage: 'Initial commit' + defaultEnvironment: + parameters: + region: eu-west-1 + secrets: + environment: ${NODE_ENV} auth: experimentalDynamicClientRegistration: diff --git a/docs/features/software-templates/configuration.md b/docs/features/software-templates/configuration.md index a692335ffe..bbf35557ce 100644 --- a/docs/features/software-templates/configuration.md +++ b/docs/features/software-templates/configuration.md @@ -48,6 +48,65 @@ add the `repoVisibility` key within a software template: repoVisibility: public # or 'internal' or 'private' ``` +### Default Environment + +The scaffolder supports a `defaultEnvironment` configuration that provides default parameters and secrets to all templates. This reduces template complexity and improves security by centralizing common values. + +```yaml +scaffolder: + defaultEnvironment: + parameters: + region: eu-west-1 + organizationName: acme-corp + defaultRegistry: registry.acme-corp.com + secrets: + AWS_ACCESS_KEY: ${AWS_ACCESS_KEY} + GITHUB_TOKEN: ${GITHUB_TOKEN} + DOCKER_REGISTRY_TOKEN: ${DOCKER_REGISTRY_TOKEN} +``` + +#### Default parameters + +Default parameters are accessible via `${{ environment.parameters.* }}` in templates. Default parameters are isolated in their own context to avoid naming conflicts. + +```yaml + parameters: + - title: Fill in some steps + required: + - organizationName + properties: + organizationName: + title: organizationName + type: string + description: Unique name of the organization + ui:autofocus: true + ui:options: + rows: 5 + + steps: + - id: deploy + name: Deploy Application + action: aws:deploy + input: + region: ${{ environment.parameters.region }} # Resolves to defaultEnvironment.parameters.region + organization: ${{ parameters.organizationName }} # Resolves to frontend input value + otherOrganization: ${{ environment.parameters.organizationName }} # Resolves to defaultEnvironment.parameters.organizationName +``` + +#### Secrets + +Default secrets are resolved from environment variables and accessible via `${{ environment.secrets.* }}` in template actions. Secrets are only available during action execution, not in frontend forms. + +```yaml +- id: deploy + name: Deploy with credentials + action: aws:deploy + input: + accessKey: ${{ environment.secrets.AWS_ACCESS_KEY }} # Resolves to defaultEnvironment.secrets.AWS_ACCESS_KEY +``` + +**Security Note:** Secrets are automatically masked in logs and are only available to backend actions, never exposed to the frontend. + ## Disabling Docker in Docker situation (Optional) Software templates use the `fetch:template` action by default, which requires no diff --git a/plugins/scaffolder-backend/README.md b/plugins/scaffolder-backend/README.md index 34a378ba44..3c4a2cb283 100644 --- a/plugins/scaffolder-backend/README.md +++ b/plugins/scaffolder-backend/README.md @@ -64,6 +64,67 @@ you will not have any templates available to use. These need to be [added to the To get up and running and try out some templates quickly, you can or copy the catalog locations from the [create-app template](https://github.com/backstage/backstage/blob/master/packages/create-app/templates/default-app/app-config.yaml.hbs). +## Configuration + +### Default Environment + +The scaffolder supports a `defaultEnvironment` configuration that provides default parameters and secrets to all templates. This reduces template complexity and improves security by centralizing common values. + +```yaml +scaffolder: + defaultEnvironment: + parameters: + region: eu-west-1 + organizationName: acme-corp + defaultRegistry: registry.acme-corp.com + secrets: + AWS_ACCESS_KEY: ${AWS_ACCESS_KEY} + GITHUB_TOKEN: ${GITHUB_TOKEN} + DOCKER_REGISTRY_TOKEN: ${DOCKER_REGISTRY_TOKEN} +``` + +#### Default parameters + +Default parameters are accessible via `${{ environment.parameters.* }}` in templates. Default parameters are isolated in their own context to avoid naming conflicts. + +```yaml + parameters: + - title: Fill in some steps + required: + - organizationName + properties: + organizationName: + title: organizationName + type: string + description: Unique name of the organization + ui:autofocus: true + ui:options: + rows: 5 + + steps: + - id: deploy + name: Deploy Application + action: aws:deploy + input: + region: ${{ environment.parameters.region }} # Resolves to defaultEnvironment.parameters.region + organization: ${{ parameters.organizationName }} # Resolves to frontend input value + otherOrganization: ${{ environment.parameters.organizationName }} # Resolves to defaultEnvironment.parameters.organizationName +``` + +#### Secrets + +Default secrets are resolved from environment variables and accessible via `${{ environment.secrets.* }}` in template actions. Secrets are only available during action execution, not in frontend forms. + +```yaml +- id: deploy + name: Deploy with credentials + action: aws:deploy + input: + accessKey: ${{ environment.secrets.AWS_ACCESS_KEY }} # Resolves to defaultEnvironment.secrets.AWS_ACCESS_KEY +``` + +**Security Note:** Secrets are automatically masked in logs and are only available to backend actions, never exposed to the frontend. + ## Audit Events The Scaffolder backend emits audit events for various operations. Events are grouped logically by `eventId`, with `subEventId` providing further distinction when needed. diff --git a/plugins/scaffolder-backend/config.d.ts b/plugins/scaffolder-backend/config.d.ts index b4fb532901..866ededb50 100644 --- a/plugins/scaffolder-backend/config.d.ts +++ b/plugins/scaffolder-backend/config.d.ts @@ -108,5 +108,26 @@ export interface Config { auditor?: { taskParameterMaxLength?: number; }; + + /** + * Default environment variables and secrets available to all templates. + */ + defaultEnvironment?: { + /** + * Default parameters accessible via ${{ environment.parameters.* }} in templates. + */ + parameters?: { + [key: string]: string; + }; + + /** + * Secret values from environment variables accessible via ${{ environment.secrets.* }} in templates. + * Values should reference environment variables like ${SECRET_NAME}. + * @visibility secret + */ + secrets?: { + [key: string]: string; + }; + }; }; } diff --git a/plugins/scaffolder-backend/src/lib/defaultEnvironment.test.ts b/plugins/scaffolder-backend/src/lib/defaultEnvironment.test.ts new file mode 100644 index 0000000000..c47863e0a2 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/defaultEnvironment.test.ts @@ -0,0 +1,87 @@ +/* + * Copyright 2025 The Backstage Authors + * + * 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 { ConfigReader } from '@backstage/config'; +import { resolveDefaultEnvironment } from './defaultEnvironment'; + +describe('defaultEnvironment', () => { + describe('resolveDefaultEnvironment', () => { + it('should return empty when no defaultEnvironment config is provided', () => { + const config = new ConfigReader({}); + const result = resolveDefaultEnvironment(config); + + expect(result).toEqual({ + parameters: {}, + secrets: {}, + }); + }); + + it('should resolve parameters and secrets from config', () => { + const config = new ConfigReader({ + scaffolder: { + defaultEnvironment: { + parameters: { + region: 'us-east-1', + organizationName: 'acme-corp', + version: '1.0.0', + }, + secrets: { + AWS_ACCESS_KEY: 'test-secret-value', + DATABASE_PASSWORD: 'db-password', + LITERAL_SECRET: 'literal-value', + }, + }, + }, + }); + + const result = resolveDefaultEnvironment(config); + + expect(result).toEqual({ + parameters: { + region: 'us-east-1', + organizationName: 'acme-corp', + version: '1.0.0', + }, + secrets: { + AWS_ACCESS_KEY: 'test-secret-value', + DATABASE_PASSWORD: 'db-password', + LITERAL_SECRET: 'literal-value', + }, + }); + }); + + it('should handle only parameters without secrets', () => { + const config = new ConfigReader({ + scaffolder: { + defaultEnvironment: { + parameters: { + region: 'eu-west-1', + }, + }, + }, + }); + + const result = resolveDefaultEnvironment(config); + + expect(result).toEqual({ + parameters: { + region: 'eu-west-1', + }, + secrets: {}, + }); + }); + }); +}); diff --git a/plugins/scaffolder-backend/src/lib/defaultEnvironment.ts b/plugins/scaffolder-backend/src/lib/defaultEnvironment.ts new file mode 100644 index 0000000000..cfb47a0130 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/defaultEnvironment.ts @@ -0,0 +1,66 @@ +/* + * Copyright 2025 The Backstage Authors + * + * 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 { Config } from '@backstage/config'; +import { JsonObject } from '@backstage/types'; + +export interface DefaultEnvironmentConfig { + parameters?: Record; + secrets?: Record; +} + +export interface ResolvedDefaultEnvironment { + parameters: JsonObject; + secrets: Record; +} + +export function resolveDefaultEnvironment( + config: Config, +): ResolvedDefaultEnvironment { + const defaultEnvConfig = config.getOptionalConfig( + 'scaffolder.defaultEnvironment', + ); + if (!defaultEnvConfig) { + return { + parameters: {}, + secrets: {}, + }; + } + + const parameters: JsonObject = {}; + const secrets: Record = {}; + + const parametersConfig = defaultEnvConfig.getOptionalConfig('parameters'); + if (parametersConfig) { + for (const paramKey of parametersConfig.keys()) { + const paramValue = parametersConfig.getString(paramKey); + parameters[paramKey] = paramValue; + } + } + + const secretsConfig = defaultEnvConfig.getOptionalConfig('secrets'); + if (secretsConfig) { + for (const secretKey of secretsConfig.keys()) { + const secretValue = secretsConfig.getString(secretKey); + secrets[secretKey] = secretValue; + } + } + + return { + parameters, + secrets, + }; +} diff --git a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts index 43c5aea40f..8b6d909752 100644 --- a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts @@ -20,6 +20,7 @@ import { LoggerService, } from '@backstage/backend-plugin-api'; import type { UserEntity } from '@backstage/catalog-model'; +import { Config } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { @@ -79,6 +80,7 @@ export type TemplateTesterCreateOptions = { additionalTemplateFilters?: Record; additionalTemplateGlobals?: Record; permissions?: PermissionEvaluator; + config?: Config; }; /** @@ -105,6 +107,7 @@ export function createDryRunner(options: TemplateTesterCreateOptions) { }, }), ]), + config: options.config, }); // Extracting contentsPath and dryRunId from the baseUrl diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts index 2c661fab56..07a25b9e0e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts @@ -57,6 +57,16 @@ describe('NunjucksWorkflowRunner', () => { const integrations = ScmIntegrations.fromConfig( new ConfigReader({ + scaffolder: { + defaultEnvironment: { + parameters: { + region: 'us-east-1', + }, + secrets: { + AWS_ACCESS_KEY: 'test-secret-value', + }, + }, + }, integrations: { github: [{ host: 'github.com', token: 'token' }], }, @@ -219,12 +229,26 @@ describe('NunjucksWorkflowRunner', () => { { result: AuthorizeResult.ALLOW }, ]); + const config = new ConfigReader({ + scaffolder: { + defaultEnvironment: { + parameters: { + region: 'us-east-1', + }, + secrets: { + AWS_ACCESS_KEY: 'test-secret-value', + }, + }, + }, + }); + runner = new NunjucksWorkflowRunner({ actionRegistry, integrations, workingDirectory: mockDir.path, logger, permissions: mockedPermissionApi, + config, }); }); @@ -501,6 +525,7 @@ describe('NunjucksWorkflowRunner', () => { action: 'jest-mock-action', input: { foo: '${{parameters.input | lower }}', + region: '${{environment.parameters.region}}', }, }, ], @@ -512,7 +537,9 @@ describe('NunjucksWorkflowRunner', () => { await runner.execute(task); expect(fakeActionHandler).toHaveBeenCalledWith( - expect.objectContaining({ input: { foo: 'backstage' } }), + expect.objectContaining({ + input: { foo: 'backstage', region: 'us-east-1' }, + }), ); }); @@ -784,6 +811,49 @@ describe('NunjucksWorkflowRunner', () => { expectTaskLog('info: ***'); }); + // eslint-disable-next-line jest/expect-expect + it('should redact secrets that are passed in the environment', async () => { + actionRegistry.register({ + id: 'log-secret', + description: 'Mock action for testing', + supportsDryRun: true, + handler: async ctx => { + ctx.logger.info(ctx.input.secret); + }, + schema: { + input: { + type: 'object', + required: ['secret'], + properties: { + secret: { + type: 'string', + }, + }, + }, + }, + }); + + const task = createMockTaskWithSpec( + { + steps: [ + { + id: 'test', + name: 'name', + action: 'log-secret', + input: { + secret: '${{ environment.secrets.AWS_ACCESS_KEY }}', + }, + }, + ], + }, + { secret: 'my-secret-value' }, + ); + + await runner.execute(task); + + expectTaskLog('info: ***'); + }); + // eslint-disable-next-line jest/expect-expect it('should redact meta fields properly', async () => { actionRegistry.register({ @@ -1090,7 +1160,9 @@ describe('NunjucksWorkflowRunner', () => { await runner.execute(task); expect(fakeActionHandler).toHaveBeenCalledWith( - expect.objectContaining({ secrets: { foo: 'bar' } }), + expect.objectContaining({ + secrets: { foo: 'bar' }, + }), ); }); @@ -1104,6 +1176,7 @@ describe('NunjucksWorkflowRunner', () => { action: 'jest-mock-action', input: { b: '${{ secrets.foo }}', + aws_key: '${{ environment.secrets.AWS_ACCESS_KEY }}', }, }, ], @@ -1114,7 +1187,41 @@ describe('NunjucksWorkflowRunner', () => { await runner.execute(task); expect(fakeActionHandler).toHaveBeenCalledWith( - expect.objectContaining({ input: { b: 'bar' } }), + expect.objectContaining({ + input: { b: 'bar', aws_key: 'test-secret-value' }, + }), + ); + }); + + it('should separate task secrets from environment secrets', async () => { + const task = createMockTaskWithSpec( + { + steps: [ + { + id: 'test', + name: 'name', + action: 'jest-mock-action', + input: { + b: '${{ secrets.foo }}', + aws_key: '${{ secrets.AWS_ACCESS_KEY }}', + env_aws_key: '${{ environment.secrets.AWS_ACCESS_KEY }}', + }, + }, + ], + }, + { foo: 'bar', AWS_ACCESS_KEY: 'another-value-from-task' }, + ); + + await runner.execute(task); + + expect(fakeActionHandler).toHaveBeenCalledWith( + expect.objectContaining({ + input: { + b: 'bar', + aws_key: 'another-value-from-task', + env_aws_key: 'test-secret-value', + }, + }), ); }); @@ -1133,6 +1240,7 @@ describe('NunjucksWorkflowRunner', () => { ], output: { b: '${{ secrets.foo }}', + c: '${{ environment.secrets.AWS_ACCESS_KEY }}', }, }, { foo: 'bar' }, @@ -1141,6 +1249,7 @@ describe('NunjucksWorkflowRunner', () => { const executedTask = await runner.execute(task); expect(executedTask.output.b).toBeUndefined(); + expect(executedTask.output.c).toBeUndefined(); }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index e2e36438a1..61cba6cb7a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -51,6 +51,7 @@ import { createConditionAuthorizer } from '@backstage/plugin-permission-node'; import { actionExecutePermission } from '@backstage/plugin-scaffolder-common/alpha'; import { TaskContext, + TaskSecrets, TemplateAction, TemplateFilter, TemplateGlobal, @@ -64,6 +65,8 @@ import { CheckpointContext, CheckpointState, } from '@backstage/plugin-scaffolder-node/alpha'; +import { Config } from '@backstage/config'; +import { resolveDefaultEnvironment } from '../../lib/defaultEnvironment'; type NunjucksWorkflowRunnerOptions = { workingDirectory: string; @@ -74,10 +77,12 @@ type NunjucksWorkflowRunnerOptions = { additionalTemplateFilters?: Record; additionalTemplateGlobals?: Record; permissions?: PermissionsService; + config?: Config; }; type TemplateContext = { parameters: JsonObject; + environment: JsonObject; EXPERIMENTAL_recovery?: TaskRecovery; steps: { [stepName: string]: { output: { [outputName: string]: JsonValue } }; @@ -103,10 +108,12 @@ const createStepLogger = ({ task, step, rootLogger, + redactions, }: { task: TaskContext; step: TaskStep; rootLogger: LoggerService; + redactions?: Record; }) => { const taskLogger = WinstonLogger.create({ level: process.env.LOG_LEVEL || 'info', @@ -117,7 +124,7 @@ const createStepLogger = ({ transports: [new BackstageLoggerTransport(rootLogger, task, step.id)], }); - taskLogger.addRedactions(Object.values(task.secrets ?? {})); + taskLogger.addRedactions(Object.values(redactions ?? {})); return { taskLogger }; }; @@ -129,6 +136,10 @@ const isActionAuthorized = createConditionAuthorizer( export class NunjucksWorkflowRunner implements WorkflowRunner { private readonly defaultTemplateFilters: Record; private readonly options: NunjucksWorkflowRunnerOptions; + private environment: { + parameters: JsonObject; + secrets?: Record; + } = { parameters: {}, secrets: {} }; constructor(options: NunjucksWorkflowRunnerOptions) { this.options = options; @@ -141,6 +152,24 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { private readonly tracker = scaffoldingTracker(); + async getEnvironmentConfig(): Promise<{ + parameters: JsonObject; + secrets?: TaskSecrets; + }> { + if (this.options.config) { + const defaultEnvironment = resolveDefaultEnvironment(this.options.config); + return { + parameters: defaultEnvironment.parameters, + secrets: defaultEnvironment.secrets, + }; + } + + return { + parameters: {}, + secrets: {}, + }; + } + private isSingleTemplateString(input: string) { const { parser, nodes } = nunjucks as unknown as { parser: { @@ -254,18 +283,33 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { task, step, rootLogger: this.options.logger, + redactions: { + ...task.secrets, + ...this.environment?.secrets, + }, }); if (task.isDryRun) { const redactedSecrets = Object.fromEntries( Object.entries(task.secrets ?? {}).map(secret => [secret[0], '***']), ); + + const redactedEnvironmentSecrets = Object.fromEntries( + Object.entries(this.environment?.secrets ?? {}).map(secret => [ + secret[0], + '***', + ]), + ); const debugInput = (step.input && this.render( step.input, { ...context, + environment: { + parameters: this.environment?.parameters || {}, + secrets: redactedEnvironmentSecrets, + }, secrets: redactedSecrets, }, renderTemplate, @@ -300,7 +344,14 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { step.each && this.render( step.each, - { ...context, secrets: task.secrets ?? {} }, + { + ...context, + environment: { + parameters: this.environment?.parameters || {}, + secrets: this.environment?.secrets ?? {}, + }, + secrets: task?.secrets ?? {}, + }, renderTemplate, ); @@ -322,7 +373,15 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { input: step.input ? this.render( step.input, - { ...context, secrets: task.secrets ?? {}, ...i }, + { + ...context, + environment: { + parameters: this.environment?.parameters ?? {}, + secrets: this.environment?.secrets ?? {}, + }, + secrets: task.secrets ?? {}, + ...i, + }, renderTemplate, ) : {}, @@ -485,6 +544,8 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { const { additionalTemplateFilters, additionalTemplateGlobals } = this.options; + this.environment = await this.getEnvironmentConfig(); + const renderTemplate = await SecureTemplater.loadRenderer({ templateFilters: { ...this.defaultTemplateFilters, @@ -501,6 +562,10 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { const context: TemplateContext = { parameters: task.spec.parameters, + environment: { + parameters: this.environment?.parameters || {}, + secrets: {}, + }, steps: {}, user: task.spec.user, context: { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts index 2b13130908..d162f2abbf 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts @@ -103,6 +103,22 @@ describe('StorageTaskBroker', () => { expect(task.secrets).toEqual(fakeSecrets); }, 10000); + it('should return secrets with priority over defaults', async () => { + const broker = new StorageTaskBroker(storage, logger); + await broker.dispatch(emptyTaskWithFakeSecretsSpec); + const task = await broker.claim(); + + expect(task.secrets).toEqual(fakeSecrets); + }, 10000); + + it('should return all secrets', async () => { + const broker = new StorageTaskBroker(storage, logger); + await broker.dispatch(emptyTaskWithFakeSecretsSpec); + const task = await broker.claim(); + + expect(task.secrets).toEqual({ ...fakeSecrets }); + }, 10000); + it('should complete a task', async () => { const broker = new StorageTaskBroker(storage, logger); const dispatchResult = await broker.dispatch(emptyTaskSpec); @@ -232,7 +248,7 @@ describe('StorageTaskBroker', () => { id: taskId, }), ]), - totalTasks: 13, + totalTasks: 15, }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index 874e32b838..22882e8f71 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -160,6 +160,10 @@ export type WorkflowResponse = { output: { [key: string]: JsonValue } }; export interface WorkflowRunner { execute(task: TaskContext): Promise; + getEnvironmentConfig?(): Promise<{ + parameters: JsonObject; + secrets?: TaskSecrets; + }>; } export type TaskTrackType = { diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 3894ffb876..690126da6e 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -330,6 +330,7 @@ export async function createRouter( auditor, workingDirectory, permissions, + config, ...templateExtensions, });