Skip to content

Commit

Permalink
feat: add mutable defaultArtifact attribute to Pipeline class to …
Browse files Browse the repository at this point in the history
…use as default `inputArtifact` for all pipeline stages following build or sign stage (#1473)

This PR adds a `defaultArtifact` attribute to the `Pipeline` class. The `defaultArtifact` attribute will act as the default `inputArtifact` based on the following conditions:
1. If a signing stage is not added, then the `defaultArtifact` for all stages following the build stage will be the `buildOutput` artifact
2. If a signing stage is added to the `Pipeline` via `signNuGetWithSigner` or `addSigning`, then the `defaultArtifact` for all stages following the sign stage will be the `signingOutput` artifact

-----

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
  • Loading branch information
colifran authored Oct 11, 2023
1 parent 81a6134 commit 0faa6b1
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 16 deletions.
101 changes: 101 additions & 0 deletions lib/__tests__/pipeline.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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),
Expand Down
10 changes: 5 additions & 5 deletions lib/__tests__/signing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -51,7 +51,7 @@ describe('with standard pipeline', () => {
{
Name: 'SCRIPT_S3_KEY',
Type: 'PLAINTEXT',
Value: '1ccec5da3e38e2c229307a68b2feb159deb67f714ce2209b3c680115486b707f.zip',
Value: '304990045086f467d5effaa1d1aa90d3f19411750a41f9cb37ab387399f92e39.zip',
},
{
Name: 'SIGNING_BUCKET_NAME',
Expand Down Expand Up @@ -83,7 +83,7 @@ describe('with standard pipeline', () => {
},
},
{
Name: 'SIGNING_ACCESS_ROLE_ARN',
Name: 'ACCESS_ROLE_ARN',
Type: 'PLAINTEXT',
Value: {
'Fn::Join': [
Expand All @@ -97,7 +97,7 @@ describe('with standard pipeline', () => {
{
Ref: 'AWS::AccountId',
},
':role/signing-access-role',
':role/access-role',
],
],
},
Expand Down
21 changes: 14 additions & 7 deletions lib/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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),
});
}
Expand All @@ -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) {
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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;
}
4 changes: 2 additions & 2 deletions lib/signing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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', {
Expand Down
4 changes: 2 additions & 2 deletions lib/signing/nuget/sign.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 0faa6b1

Please sign in to comment.