diff --git a/lib/__tests__/pipeline.test.ts b/lib/__tests__/pipeline.test.ts index 8b0db18a..45dd0a83 100644 --- a/lib/__tests__/pipeline.test.ts +++ b/lib/__tests__/pipeline.test.ts @@ -7,6 +7,9 @@ import { aws_codepipeline_actions as cpipeline_actions, } from 'aws-cdk-lib'; import { Capture, Template, Match } from 'aws-cdk-lib/assertions'; +import { Role } from 'aws-cdk-lib/aws-iam'; +import { Function } from 'aws-cdk-lib/aws-lambda'; +import { Bucket } from 'aws-cdk-lib/aws-s3'; import { Construct } from 'constructs'; import * as delivlib from '../../lib'; import { determineRunOrder } from '../../lib/util'; @@ -336,6 +339,104 @@ test('metricActionFailures', () => { expect(stack.resolve(pipeline.metricActionFailures({}))).toEqual(expectedMetrics); }); +test('signing output artifact is used as input artifact for all stages after signing stage', () => { + // GIVEN + const app = new App(); + const stack = new Stack(app, 'TestStack'); + const signingLambda = Function.fromFunctionName(stack, 'SigningLambda', 'signing-lambda'); + const signingBucket = Bucket.fromBucketName(stack, 'SigningBucket', 'signing-bucket'); + const accessRole = Role.fromRoleName(stack, 'AccessRole', 'access-role'); + const pipeline = new delivlib.Pipeline(stack, 'Pipeline', { + repo: createTestRepo(stack), + pipelineName: 'TestPipeline', + }); + + // WHEN + pipeline.signNuGetWithSigner({ + signingLambda, + signingBucket, + accessRole, + }); + + pipeline.publishToNuGet({ + nugetApiKeySecret: { + secretArn: 'arn:aws:secretsmanager:us-east-1:123456789012:secret:nuget-secret', + }, + }); + + pipeline.addTest('test1', { + scriptDirectory: path.join(__dirname, 'delivlib-tests/assume-role'), + entrypoint: 'test.sh', + }); + + // THEN + Template.fromStack(stack).hasResourceProperties('AWS::CodePipeline::Pipeline', { + Stages: [ + { + Actions: [ + Match.objectLike({ + InputArtifacts: Match.absent(), + OutputArtifacts: [ + { Name: 'Source' }, + ], + }), + ], + Name: 'Source', + }, + { + Actions: [ + Match.objectLike({ + InputArtifacts: [ + { Name: 'Source' }, + ], + OutputArtifacts: [ + { Name: 'Artifact_Build_Build' }, + ], + }), + ], + Name: 'Build', + }, + { + Actions: [ + Match.objectLike({ + InputArtifacts: [ + { Name: 'Artifact_Build_Build' }, + ], + OutputArtifacts: [ + { Name: 'Artifact_Sign_NuGetSigningSign' }, + ], + }), + ], + Name: 'Sign', + }, + { + Actions: [ + Match.objectLike({ + InputArtifacts: [ + { Name: 'Artifact_Sign_NuGetSigningSign' }, + ], + OutputArtifacts: Match.absent(), + }), + ], + Name: 'Publish', + }, + { + Actions: [ + Match.objectLike({ + InputArtifacts: [ + { Name: 'Artifact_Sign_NuGetSigningSign' }, + ], + OutputArtifacts: [ + { Name: 'Artifact_c8383dfefa10c0c326ab2bfac48dcf263ea515d7e1' }, + ], + }), + ], + Name: 'Test', + }, + ], + }); +}); + function createTestPipelineForConcurrencyTests(stack: Stack, props?: delivlib.PipelineProps) { const pipeline = new delivlib.Pipeline(stack, 'Pipeline', { repo: createTestRepo(stack), diff --git a/lib/__tests__/signing.test.ts b/lib/__tests__/signing.test.ts index d0566f75..a0f5101a 100644 --- a/lib/__tests__/signing.test.ts +++ b/lib/__tests__/signing.test.ts @@ -23,13 +23,13 @@ describe('with standard pipeline', () => { // GIVEN const signingBucket = Bucket.fromBucketName(stack, 'SigningBucket', 'signing-bucket'); const signingLambda = Function.fromFunctionName(stack, 'SigningLambda', 'signing-lambda'); - const signingAccessRole = Role.fromRoleName(stack, 'SigningAccessRole', 'signing-access-role'); + const accessRole = Role.fromRoleName(stack, 'AccessRole', 'access-role'); // WHEN pipeline.signNuGetWithSigner({ signingBucket, signingLambda, - signingAccessRole, + accessRole, }); // THEN @@ -51,7 +51,7 @@ describe('with standard pipeline', () => { { Name: 'SCRIPT_S3_KEY', Type: 'PLAINTEXT', - Value: '1ccec5da3e38e2c229307a68b2feb159deb67f714ce2209b3c680115486b707f.zip', + Value: '304990045086f467d5effaa1d1aa90d3f19411750a41f9cb37ab387399f92e39.zip', }, { Name: 'SIGNING_BUCKET_NAME', @@ -83,7 +83,7 @@ describe('with standard pipeline', () => { }, }, { - Name: 'SIGNING_ACCESS_ROLE_ARN', + Name: 'ACCESS_ROLE_ARN', Type: 'PLAINTEXT', Value: { 'Fn::Join': [ @@ -97,7 +97,7 @@ describe('with standard pipeline', () => { { Ref: 'AWS::AccountId', }, - ':role/signing-access-role', + ':role/access-role', ], ], }, diff --git a/lib/pipeline.ts b/lib/pipeline.ts index ae1f67ab..b920cd4d 100644 --- a/lib/pipeline.ts +++ b/lib/pipeline.ts @@ -27,7 +27,7 @@ import * as signing from './signing'; import { determineRunOrder, flatMap } from './util'; const PUBLISH_STAGE_NAME = 'Publish'; -const SIGINING_STAGE_NAME = 'Sign'; +const SIGNING_STAGE_NAME = 'Sign'; const TEST_STAGE_NAME = 'Test'; const METRIC_NAMESPACE = 'CDK/Delivlib'; const FAILURE_METRIC_NAME = 'Failures'; @@ -207,6 +207,7 @@ export class Pipeline extends Construct { public readonly pipeline: cpipeline.Pipeline; private readonly branch: string; private readonly notify?: sns.Topic; + private defaultArtifact: cpipeline.Artifact; private stages: { [name: string]: cpipeline.IStage } = { }; private _signingOutput?: cpipeline.Artifact; @@ -259,6 +260,7 @@ export class Pipeline extends Construct { outputs: [buildOutput], })); this.buildOutput = buildOutput; + this.defaultArtifact = buildOutput; if (props.notificationEmail) { this.notify = new sns.Topic(this, 'NotificationsTopic'); @@ -311,7 +313,7 @@ export class Pipeline extends Construct { const action = sh.addToPipeline( stage, options.actionName || `Action${id}`, - options.inputArtifact || this.buildOutput, + options.inputArtifact || this.defaultArtifact, this.determineRunOrderForNewAction(stage)); if (options.failureNotification) { @@ -346,7 +348,7 @@ export class Pipeline extends Construct { const stage = this.getOrCreateStage(publishStageName); publisher.addToPipeline(stage, `${publisher.node.id}Publish`, { - inputArtifact: options.inputArtifact || this.buildOutput, + inputArtifact: options.inputArtifact || this.defaultArtifact, runOrder: this.determineRunOrderForNewAction(stage), }); } @@ -368,13 +370,14 @@ export class Pipeline extends Construct { } public addSigning(signer: signing.ISigner, options: signing.AddSigningOptions = {}) { - const signingStageName = options.stageName ?? SIGINING_STAGE_NAME; + const signingStageName = options.stageName ?? SIGNING_STAGE_NAME; const stage = this.getOrCreateStage(signingStageName); this._signingOutput = signer.addToPipeline(stage, `${signer.node.id}Sign`, { - inputArtifact: options.inputArtifact || this.buildOutput, + inputArtifact: options.inputArtifact || this.defaultArtifact, runOrder: this.determineRunOrderForNewAction(stage), }); + this.defaultArtifact = this._signingOutput; } public signNuGetWithSigner(options: signing.SignNuGetWithSignerProps & signing.AddSigningOptions) { @@ -623,7 +626,9 @@ export interface AddPublishOptions { /** * The input artifact to use * - * @default Build output artifact + * @default Signing output artifact when a signing stage is added to the + * pipeline via `addSigning` or `signNuGetWithSigner`. Otherwise, the default + * will be the build output artifact. */ inputArtifact?: cpipeline.Artifact; @@ -660,7 +665,9 @@ export interface AddShellableOptions extends ShellableProps { /** * The input artifact to use * - * @default Build output artifact + * @default Signing output artifact when a signing stage is added to the + * pipeline via `addSigning` or `signNuGetWithSigner`. Otherwise, the default + * will be the build output artifact. */ inputArtifact?: cpipeline.Artifact; } diff --git a/lib/signing.ts b/lib/signing.ts index fe84d6df..8d38049c 100644 --- a/lib/signing.ts +++ b/lib/signing.ts @@ -44,7 +44,7 @@ export interface SignNuGetWithSignerProps { /** * A role used provide access to the signing bucket and signing lambda */ - readonly signingAccessRole: IRole; + readonly accessRole: IRole; /** * The build image to do the signing in @@ -66,7 +66,7 @@ export class SignNuGetWithSigner extends Construct implements ISigner { const environment = { SIGNING_BUCKET_NAME: props.signingBucket.bucketName, SIGNING_LAMBDA_ARN: props.signingLambda.functionArn, - SIGNING_ACCESS_ROLE_ARN: props.signingAccessRole.roleArn, + ACCESS_ROLE_ARN: props.accessRole.roleArn, }; const shellable = new Shellable(this, 'Default', { diff --git a/lib/signing/nuget/sign.sh b/lib/signing/nuget/sign.sh index f7633d5d..dea2cdb3 100644 --- a/lib/signing/nuget/sign.sh +++ b/lib/signing/nuget/sign.sh @@ -11,8 +11,8 @@ else echo "!!! Neither an apt nor yum distribution - could not install jq, things might break!" fi -if [ -n "${SIGNING_ACCESS_ROLE_ARN:-}" ]; then - ROLE=$(aws sts assume-role --role-arn "${SIGNING_ACCESS_ROLE_ARN:-}" --role-session-name "signer_access") +if [ -n "${ACCESS_ROLE_ARN:-}" ]; then + ROLE=$(aws sts assume-role --role-arn "${ACCESS_ROLE_ARN:-}" --role-session-name "signer_access") export AWS_ACCESS_KEY_ID=$(echo $ROLE | jq -r .Credentials.AccessKeyId) export AWS_SECRET_ACCESS_KEY=$(echo $ROLE | jq -r .Credentials.SecretAccessKey) export AWS_SESSION_TOKEN=$(echo $ROLE | jq -r .Credentials.SessionToken)