diff --git a/packages/@aws-cdk/pipelines/README.md b/packages/@aws-cdk/pipelines/README.md index 09679d761cb09..626dd06937462 100644 --- a/packages/@aws-cdk/pipelines/README.md +++ b/packages/@aws-cdk/pipelines/README.md @@ -696,6 +696,9 @@ new pipelines.CodeBuildStep('Synth', { subnetSelection: { subnetType: ec2.SubnetType.PRIVATE }, securityGroups: [mySecurityGroup], + // You can customize the variable namespace of exported variables if needed + variablesNamespace: 'MyNamespace', + // Additional policy statements for the execution role rolePolicyStatements: [ new iam.PolicyStatement({ /* ... */ }), diff --git a/packages/@aws-cdk/pipelines/lib/blueprint/shell-step.ts b/packages/@aws-cdk/pipelines/lib/blueprint/shell-step.ts index d36f0999fca17..a2da2179dcea4 100644 --- a/packages/@aws-cdk/pipelines/lib/blueprint/shell-step.ts +++ b/packages/@aws-cdk/pipelines/lib/blueprint/shell-step.ts @@ -88,6 +88,12 @@ export interface ShellStepProps { */ readonly primaryOutputDirectory?: string; + /** + * The name of the namespace to use for variables emitted by this action. + * + * @default No namespace. + */ + readonly variablesNamespace?: string; } /** @@ -140,6 +146,13 @@ export class ShellStep extends Step { */ public readonly outputs: FileSetLocation[] = []; + /** + * The name of the namespace to use for variables emitted by this action. + * + * @default Id of the step. + */ + public readonly variablesNamespace: string; + private readonly _additionalOutputs: Record = {}; private _primaryOutputDirectory?: string; @@ -151,6 +164,7 @@ export class ShellStep extends Step { this.installCommands = props.installCommands ?? []; this.env = props.env ?? {}; this.envFromCfnOutputs = mapValues(props.envFromCfnOutputs ?? {}, StackOutputReference.fromCfnOutput); + this.variablesNamespace = props.variablesNamespace ?? id; // Inputs if (props.input) { @@ -229,6 +243,17 @@ export class ShellStep extends Step { } return fileSet; } + + /** + * Reference a CodePipeline variable defined by the ShellStep. + * + * Variables in CodeBuild actions are defined using the 'exported-variables' subsection of the 'env' section of the buildspec. + * + * @param variableName the name of the variable for reference. + */ + public variable(variableName: string): string { + return `#{${this.variablesNamespace}.${variableName}}`; + } } /** diff --git a/packages/@aws-cdk/pipelines/lib/codepipeline/_codebuild-factory.ts b/packages/@aws-cdk/pipelines/lib/codepipeline/_codebuild-factory.ts index 3414554cd5197..bef264ef40619 100644 --- a/packages/@aws-cdk/pipelines/lib/codepipeline/_codebuild-factory.ts +++ b/packages/@aws-cdk/pipelines/lib/codepipeline/_codebuild-factory.ts @@ -98,6 +98,7 @@ export interface CodeBuildFactoryProps { readonly env?: Record; readonly envFromCfnOutputs?: Record; + readonly variablesNamespace?: string; /** * If given, override the scope from the produce call with this scope. @@ -128,6 +129,7 @@ export class CodeBuildFactory implements ICodePipelineActionFactory { inputs: shellStep.inputs, outputs: shellStep.outputs, stepId: shellStep.id, + variablesNamespace: shellStep.variablesNamespace, installCommands: shellStep.installCommands, ...additional, }); @@ -145,6 +147,7 @@ export class CodeBuildFactory implements ICodePipelineActionFactory { partialBuildSpec: step.partialBuildSpec, vpc: step.vpc, subnetSelection: step.subnetSelection, + variablesNamespace: step.variablesNamespace, timeout: step.timeout, }), }); @@ -314,6 +317,7 @@ export class CodeBuildFactory implements ICodePipelineActionFactory { outputs: outputArtifacts, project, runOrder: options.runOrder, + variablesNamespace: this.props.variablesNamespace, // Inclusion of the hash here will lead to the pipeline structure for any changes // made the config of the underlying CodeBuild Project. diff --git a/packages/@aws-cdk/pipelines/lib/codepipeline/codebuild-step.ts b/packages/@aws-cdk/pipelines/lib/codepipeline/codebuild-step.ts index f835e261aba3d..f57b0cd20fba8 100644 --- a/packages/@aws-cdk/pipelines/lib/codepipeline/codebuild-step.ts +++ b/packages/@aws-cdk/pipelines/lib/codepipeline/codebuild-step.ts @@ -1,7 +1,7 @@ -import { Duration } from '@aws-cdk/core'; import * as codebuild from '@aws-cdk/aws-codebuild'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; +import { Duration } from '@aws-cdk/core'; import { ShellStep, ShellStepProps } from '../blueprint'; /** diff --git a/packages/@aws-cdk/pipelines/lib/codepipeline/codepipeline.ts b/packages/@aws-cdk/pipelines/lib/codepipeline/codepipeline.ts index 0beae3ea56fa1..d0b0a8030e788 100644 --- a/packages/@aws-cdk/pipelines/lib/codepipeline/codepipeline.ts +++ b/packages/@aws-cdk/pipelines/lib/codepipeline/codepipeline.ts @@ -225,6 +225,13 @@ export interface CodeBuildOptions { */ readonly rolePolicy?: iam.PolicyStatement[]; + /** + * The name of the namespace to use for variables emitted by this action. + * + * @default The projectName will be used if it is defined. Otherwise, the construct ID will be used. + */ + readonly variablesNamespace?: string; + /** * Partial buildspec, will be combined with other buildspecs that apply * diff --git a/packages/@aws-cdk/pipelines/test/codepipeline/codebuild-step.test.ts b/packages/@aws-cdk/pipelines/test/codepipeline/codebuild-step.test.ts index 393f1ffb965ba..ef093892fe8a8 100644 --- a/packages/@aws-cdk/pipelines/test/codepipeline/codebuild-step.test.ts +++ b/packages/@aws-cdk/pipelines/test/codepipeline/codebuild-step.test.ts @@ -141,4 +141,28 @@ test('envFromOutputs works even with very long stage and stack names', () => { }); // THEN - did not throw an error about identifier lengths -}); \ No newline at end of file +}); + +test('variable namespace is set in CodeBuildStep', () => { + // WHEN + const pipeline = new ModernTestGitHubNpmPipeline(pipelineStack, 'Cdk'); + const myApp = new AppWithOutput(app, 'VariableNamespaceApp'); + + const step1 = new cdkp.CodeBuildStep('Synth', { + commands: ['/bin/true'], + input: cdkp.CodePipelineSource.gitHub('test/test', 'main'), + variablesNamespace: 'CodeBuildStep-namespace', + }); + const step2 = new cdkp.ShellStep('Approve', { + commands: ['/bin/true'], + variablesNamespace: 'ShellStep-namespace', + }); + pipeline.addStage(myApp, { + pre: [step1], + post: [step2], + }); + + // THEN + expect(step1.variable('test')).toEqual('#{CodeBuildStep-namespace.test}'); + expect(step2.variable('test')).toEqual('#{ShellStep-namespace.test}'); +}); diff --git a/packages/@aws-cdk/pipelines/test/integ.newpipeline-with-vpc.expected.json b/packages/@aws-cdk/pipelines/test/integ.newpipeline-with-vpc.expected.json index 4bd2e638afb4c..ba267af68b410 100644 --- a/packages/@aws-cdk/pipelines/test/integ.newpipeline-with-vpc.expected.json +++ b/packages/@aws-cdk/pipelines/test/integ.newpipeline-with-vpc.expected.json @@ -833,6 +833,7 @@ } ], "Name": "Synth", + "Namespace": "Synth", "OutputArtifacts": [ { "Name": "Synth_Output" @@ -870,6 +871,7 @@ } ], "Name": "SelfMutate", + "Namespace": "SelfMutate", "RoleArn": { "Fn::GetAtt": [ "PipelineUpdatePipelineSelfMutateCodePipelineActionRoleD6D4E5CF", @@ -901,6 +903,7 @@ } ], "Name": "FileAsset1", + "Namespace": "FileAsset1", "RoleArn": { "Fn::GetAtt": [ "PipelineAssetsFileAsset1CodePipelineActionRoleC0EC649A", @@ -927,6 +930,7 @@ } ], "Name": "FileAsset2", + "Namespace": "FileAsset2", "RoleArn": { "Fn::GetAtt": [ "PipelineAssetsFileAsset2CodePipelineActionRole06965A59", diff --git a/packages/@aws-cdk/pipelines/test/integ.newpipeline.expected.json b/packages/@aws-cdk/pipelines/test/integ.newpipeline.expected.json index 37cd5d99fd7f8..8b136ee8832f2 100644 --- a/packages/@aws-cdk/pipelines/test/integ.newpipeline.expected.json +++ b/packages/@aws-cdk/pipelines/test/integ.newpipeline.expected.json @@ -299,6 +299,7 @@ } ], "Name": "Synth", + "Namespace": "Synth", "OutputArtifacts": [ { "Name": "Synth_Output" @@ -336,6 +337,7 @@ } ], "Name": "SelfMutate", + "Namespace": "SelfMutate", "RoleArn": { "Fn::GetAtt": [ "PipelineUpdatePipelineSelfMutateCodePipelineActionRoleD6D4E5CF",