Skip to content

Commit

Permalink
adding the ability to pass in CloudFormation Capabilities, enable ful…
Browse files Browse the repository at this point in the history
…l permissions, or pass in a role
  • Loading branch information
moofish32 committed Nov 15, 2018
1 parent ea9aac3 commit 9dcfa2f
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 22 deletions.
28 changes: 28 additions & 0 deletions packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,29 @@ export interface PipelineDeployStackActionProps {
* @default ``createChangeSetRunOrder + 1``
*/
executeChangeSetRunOrder?: number;

/**
* The role to use when creating and executing the ChangeSet.
*
* @default a new role is created
*/
role?: iam.Role;

/**
* The CloudFormation Capabilities enabled for the ChangeSet.
*
* @default None
*/
capabilities?: cfn.CloudFormationCapabilities[];

/**
* Should CloudFormation receive full permissions for this ChangeSet.
*
* This results in a role with Administrator Permissions or *:*.
*
* @default false
*/
fullPermissions?: boolean;
}

/**
Expand Down Expand Up @@ -79,12 +102,17 @@ export class PipelineDeployStackAction extends cdk.Construct {

this.stack = props.stack;

const fullPermissions = props.fullPermissions === true;

const changeSetAction = new cfn.PipelineCreateReplaceChangeSetAction(this, 'ChangeSet', {
changeSetName,
runOrder: createChangeSetRunOrder,
stackName: props.stack.name,
stage: props.stage,
templatePath: props.inputArtifact.atPath(`${props.stack.name}.template.yaml`),
fullPermissions,
role: props.role,
capabilities: props.capabilities,
});
this.role = changeSetAction.role;

Expand Down
1 change: 1 addition & 0 deletions packages/@aws-cdk/app-delivery/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
],
"peerDependencies": {
"@aws-cdk/aws-codepipeline-api": "^0.16.0",
"@aws-cdk/aws-cloudformation": "^0.16.0",
"@aws-cdk/aws-iam": "^0.16.0",
"@aws-cdk/cdk": "^0.16.0"
}
Expand Down
125 changes: 103 additions & 22 deletions packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import cfn = require('@aws-cdk/aws-cloudformation');
import codebuild = require('@aws-cdk/aws-codebuild');
import code = require('@aws-cdk/aws-codepipeline');
import api = require('@aws-cdk/aws-codepipeline-api');
Expand All @@ -8,9 +9,13 @@ import cxapi = require('@aws-cdk/cx-api');
import fc = require('fast-check');
import nodeunit = require('nodeunit');

import { countResources, expect, haveResource } from '@aws-cdk/assert';
import { countResources, expect, haveResource, isSuperObject } from '@aws-cdk/assert';
import { PipelineDeployStackAction } from '../lib/pipeline-deploy-stack-action';

interface SelfUpdatingPipeline {
synthesizedApp: api.Artifact;
pipeline: code.Pipeline;
}
const accountId = fc.array(fc.integer(0, 9), 12, 12).map(arr => arr.join());

export = nodeunit.testCase({
Expand Down Expand Up @@ -63,44 +68,87 @@ export = nodeunit.testCase({
);
test.done();
},

'users can specify IAM permissions for the deploy action'(test: nodeunit.Test) {
// GIVEN //
'users can supply CloudFormation capabilities'(test: nodeunit.Test) {
const pipelineStack = getTestStack();
const selfUpdatingStack = createSelfUpdatingStack(pipelineStack);

// the fake stack to deploy
const emptyStack = getTestStack();

const pipeline = new code.Pipeline(pipelineStack, 'CodePipeline', {
restartExecutionOnUpdate: true,
const pipeline = selfUpdatingStack.pipeline;
const selfUpdateStage = pipeline.addStage('SelfUpdate');
new PipelineDeployStackAction(pipelineStack, 'SelfUpdatePipeline', {
stage: selfUpdateStage,
stack: pipelineStack,
inputArtifact: selfUpdatingStack.synthesizedApp,
capabilities: [cfn.CloudFormationCapabilities.IAM],
});
expect(pipelineStack).to(haveResource('AWS::CodePipeline::Pipeline', hasPipelineAction({
Configuration: {
StackName: "TestStack",
ActionMode: "CHANGE_SET_REPLACE",
Capabilities: "CAPABILITY_IAM",
}
})));
test.done();
},
'users can supply enable full permissions'(test: nodeunit.Test) {
const pipelineStack = getTestStack();
const selfUpdatingStack = createSelfUpdatingStack(pipelineStack);

// simple source
const bucket = s3.Bucket.import( pipeline, 'PatternBucket', { bucketArn: 'arn:aws:s3:::totally-fake-buckert' });
new s3.PipelineSourceAction(pipeline, 'S3Source', {
bucket,
bucketKey: 'the-great-key',
stage: pipeline.addStage('source'),
const pipeline = selfUpdatingStack.pipeline;
const selfUpdateStage = pipeline.addStage('SelfUpdate');
new PipelineDeployStackAction(pipelineStack, 'SelfUpdatePipeline', {
stage: selfUpdateStage,
stack: pipelineStack,
inputArtifact: selfUpdatingStack.synthesizedApp,
fullPermissions: true,
});
expect(pipelineStack).to(haveResource('AWS::IAM::Policy', {
PolicyDocument: {
Statement: [
{
Action: '*',
Effect: 'Allow',
Resource: '*',
}
],
}
}));
test.done();
},
'users can supply a role for deploy action'(test: nodeunit.Test) {
const pipelineStack = getTestStack();
const selfUpdatingStack = createSelfUpdatingStack(pipelineStack);

const project = new codebuild.PipelineProject(pipelineStack, 'CodeBuild');
const buildStage = pipeline.addStage('build');
const buildAction = project.addBuildToPipeline(buildStage, 'CodeBuild');
const synthesizedApp = buildAction.outputArtifact;
const pipeline = selfUpdatingStack.pipeline;
const role = new iam.Role(pipelineStack, 'MyRole', {
assumedBy: new iam.ServicePrincipal('cloudformation.amazonaws.com'),
});
const selfUpdateStage = pipeline.addStage('SelfUpdate');
new PipelineDeployStackAction(pipelineStack, 'SelfUpdatePipeline', {
const deployAction = new PipelineDeployStackAction(pipelineStack, 'SelfUpdatePipeline', {
stage: selfUpdateStage,
stack: pipelineStack,
inputArtifact: synthesizedApp,
inputArtifact: selfUpdatingStack.synthesizedApp,
role
});
test.deepEqual(role.id, deployAction.role.id);
test.done();
},
'users can specify IAM permissions for the deploy action'(test: nodeunit.Test) {
// GIVEN //
const pipelineStack = getTestStack();

// the fake stack to deploy
const emptyStack = getTestStack();

const selfUpdatingStack = createSelfUpdatingStack(pipelineStack);
const pipeline = selfUpdatingStack.pipeline;

// WHEN //
// this our app/service/infra to deploy
const deployStage = pipeline.addStage('Deploy');
const deployAction = new PipelineDeployStackAction(pipelineStack, 'DeployServiceStackA', {
stage: deployStage,
stack: emptyStack,
inputArtifact: synthesizedApp,
inputArtifact: selfUpdatingStack.synthesizedApp,
});
// we might need to add permissions
deployAction.role.addToPolicy( new iam.PolicyStatement().
Expand Down Expand Up @@ -189,3 +237,36 @@ class FakeAction extends api.Action {
function getTestStack(): cdk.Stack {
return new cdk.Stack(undefined, 'TestStack', { env: { account: '123456789012', region: 'us-east-1' } });
}

function createSelfUpdatingStack(pipelineStack: cdk.Stack): SelfUpdatingPipeline {
const pipeline = new code.Pipeline(pipelineStack, 'CodePipeline', {
restartExecutionOnUpdate: true,
});

// simple source
const bucket = s3.Bucket.import( pipeline, 'PatternBucket', { bucketArn: 'arn:aws:s3:::totally-fake-bucket' });
new s3.PipelineSourceAction(pipeline, 'S3Source', {
bucket,
bucketKey: 'the-great-key',
stage: pipeline.addStage('source'),
});

const project = new codebuild.PipelineProject(pipelineStack, 'CodeBuild');
const buildStage = pipeline.addStage('build');
const buildAction = project.addBuildToPipeline(buildStage, 'CodeBuild');
const synthesizedApp = buildAction.outputArtifact;
return {synthesizedApp, pipeline};
}

function hasPipelineAction(expectedAction: any): (props: any) => boolean {
return (props: any) => {
for (const stage of props.Stages) {
for (const action of stage.Actions) {
if (isSuperObject(action, expectedAction)) {
return true;
}
}
}
return false;
};
}

0 comments on commit 9dcfa2f

Please sign in to comment.