diff --git a/packages/@aws-cdk/app-delivery/README.md b/packages/@aws-cdk/app-delivery/README.md index 66a3f30947573..437aa3b06aeb8 100644 --- a/packages/@aws-cdk/app-delivery/README.md +++ b/packages/@aws-cdk/app-delivery/README.md @@ -1,5 +1,5 @@ ## Continuous Integration / Continuous Delivery for CDK Applications -This library includes a *CodePipeline* action for deploying AWS CDK Applications. +This library includes a *CodePipeline* composite Action for deploying AWS CDK Applications. This module is part of the [AWS Cloud Development Kit](https://github.com/awslabs/aws-cdk) project. @@ -29,7 +29,7 @@ The example below defines a *CDK App* that contains 3 stacks: ``` #### `index.ts` -```ts +```typescript import codebuild = require('@aws-cdk/aws-codebuild'); import codepipeline = require('@aws-cdk/aws-codepipeline'); import cdk = require('@aws-cdk/cdk'); @@ -46,11 +46,13 @@ const pipeline = new codepipeline.Pipeline(pipelineStack, 'CodePipeline', { restartExecutionOnUpdate: true, /* ... */ }); + // Configure the CodePipeline source - where your CDK App's source code is hosted -const source = new codepipeline.GitHubSourceAction(pipelineStack, 'GitHub', { - stage: pipeline.addStage('source'), +const source = new codepipeline.GitHubSourceAction('GitHub', { /* ... */ }); +pipeline.addStage(new codepipeline.Stage('source').addAction(source)); + const project = new codebuild.PipelineProject(pipelineStack, 'CodeBuild', { /** * Choose an environment configuration that meets your use case. For NodeJS @@ -60,12 +62,15 @@ const project = new codebuild.PipelineProject(pipelineStack, 'CodeBuild', { * }, */ }); -const buildStage = pipeline.addStage('build'); -const buildAction = project.addToPipeline(buildStage, 'CodeBuild'); +const buildAction = project.asCodePipelineAction('CodeBuild', { + inputArtifact: source.outputArtifact, +}); +pipeline.addStage(new codepipeline.Stage('build').addAction(buildAction)); const synthesizedApp = buildAction.outputArtifact; // Optionally, self-update the pipeline stack -const selfUpdateStage = pipeline.addStage('SelfUpdate'); +const selfUpdateStage = new codepipeline.Stage('SelfUpdate'); +pipeline.addStage(selfUpdateStage); new cicd.PipelineDeployStackAction(pipelineStack, 'SelfUpdatePipeline', { stage: selfUpdateStage, stack: pipelineStack, @@ -73,9 +78,9 @@ new cicd.PipelineDeployStackAction(pipelineStack, 'SelfUpdatePipeline', { }); // Now add our service stacks -const deployStage = pipeline.addStage('Deploy'); +const deployStage = new codepipeline.Stage('Deploy'); +pipeline.addStage(deployStage); const serviceStackA = new MyServiceStackA(app, 'ServiceStackA', { /* ... */ }); -const serviceStackB = new MyServiceStackB(app, 'ServiceStackB', { /* ... */ }); // Add actions to deploy the stacks in the deploy stage: const deployServiceAAction = new cicd.PipelineDeployStackAction(pipelineStack, 'DeployServiceStackA', { stage: deployStage, @@ -84,7 +89,6 @@ const deployServiceAAction = new cicd.PipelineDeployStackAction(pipelineStack, ' // See the note below for details about this option. adminPermissions: false, }); - // Add the necessary permissions for you service deploy action. This role is // is passed to CloudFormation and needs the permissions necessary to deploy // stack. Alternatively you can enable [Administrator](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_job-functions.html#jf_administrator) permissions above, @@ -95,7 +99,9 @@ deployServiceAAction.addToRolePolicy( .addResource(myResource.myResourceArn) // add more Action(s) and/or Resource(s) here, as needed ); -const deployServiceBAction = new cicd.PipelineDeployStackAction(pipelineStack, 'DeployServiceStackB', { + +const serviceStackB = new MyServiceStackB(app, 'ServiceStackB', { /* ... */ }); +new cicd.PipelineDeployStackAction(pipelineStack, 'DeployServiceStackB', { stage: deployStage, stack: serviceStackB, inputArtifact: synthesizedApp, diff --git a/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts b/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts index 4b9873f6b48b2..05a0c30f0c549 100644 --- a/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts +++ b/packages/@aws-cdk/app-delivery/lib/pipeline-deploy-stack-action.ts @@ -85,8 +85,8 @@ export interface PipelineDeployStackActionProps { } /** - * A CodePipeline action to deploy a stack that is part of a CDK App. This - * action takes care of preparing and executing a CloudFormation ChangeSet. + * A Construct to deploy a stack that is part of a CDK App, using CodePipeline. + * This composite Action takes care of preparing and executing a CloudFormation ChangeSet. * * It currently does *not* support stacks that make use of ``Asset``s, and * requires the deployed stack is in the same account and region where the @@ -120,24 +120,24 @@ export class PipelineDeployStackAction extends cdk.Construct { const changeSetName = props.changeSetName || 'CDK-CodePipeline-ChangeSet'; const capabilities = cfnCapabilities(props.adminPermissions, props.capabilities); - const changeSetAction = new cfn.PipelineCreateReplaceChangeSetAction(this, 'ChangeSet', { + const changeSetAction = new cfn.PipelineCreateReplaceChangeSetAction('ChangeSet', { changeSetName, runOrder: createChangeSetRunOrder, stackName: props.stack.name, - stage: props.stage, templatePath: props.inputArtifact.atPath(`${props.stack.name}.template.yaml`), adminPermissions: props.adminPermissions, role: props.role, capabilities, }); - this.role = changeSetAction.role; + props.stage + .addAction(changeSetAction) + .addAction(new cfn.PipelineExecuteChangeSetAction('Execute', { + changeSetName, + runOrder: executeChangeSetRunOrder, + stackName: props.stack.name, + })); - new cfn.PipelineExecuteChangeSetAction(this, 'Execute', { - changeSetName, - runOrder: executeChangeSetRunOrder, - stackName: props.stack.name, - stage: props.stage, - }); + this.role = changeSetAction.role; } /** diff --git a/packages/@aws-cdk/app-delivery/test/integ.cicd.expected.json b/packages/@aws-cdk/app-delivery/test/integ.cicd.expected.json index e143489256939..e37f96aa079e2 100644 --- a/packages/@aws-cdk/app-delivery/test/integ.cicd.expected.json +++ b/packages/@aws-cdk/app-delivery/test/integ.cicd.expected.json @@ -63,7 +63,7 @@ "Effect": "Allow", "Resource": { "Fn::GetAtt": [ - "DeployStackChangeSetRole4923A126", + "CodePipelineDeployChangeSetRoleF9F2B343", "Arn" ] } @@ -202,7 +202,7 @@ "TemplatePath": "Artifact_CICDGitHubF8BA7ADD::CICD.template.yaml", "RoleArn": { "Fn::GetAtt": [ - "DeployStackChangeSetRole4923A126", + "CodePipelineDeployChangeSetRoleF9F2B343", "Arn" ] } @@ -243,7 +243,7 @@ "CodePipelineRoleDefaultPolicy8D520A8D" ] }, - "DeployStackChangeSetRole4923A126": { + "CodePipelineDeployChangeSetRoleF9F2B343": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -261,4 +261,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/app-delivery/test/integ.cicd.ts b/packages/@aws-cdk/app-delivery/test/integ.cicd.ts index 48335409f9b5c..44218746ace1a 100644 --- a/packages/@aws-cdk/app-delivery/test/integ.cicd.ts +++ b/packages/@aws-cdk/app-delivery/test/integ.cicd.ts @@ -12,14 +12,18 @@ const pipeline = new code.Pipeline(stack, 'CodePipeline', { removalPolicy: cdk.RemovalPolicy.Destroy }) }); -const source = new code.GitHubSourceAction(stack, 'GitHub', { - stage: pipeline.addStage('Source'), +const source = new code.GitHubSourceAction('GitHub', { owner: 'awslabs', repo: 'aws-cdk', oauthToken: new cdk.Secret('DummyToken'), pollForSourceChanges: true, + outputArtifactName: 'Artifact_CICDGitHubF8BA7ADD', }); -const stage = pipeline.addStage('Deploy'); +pipeline.addStage(new code.Stage('Source', { + actions: [source], +})); +const stage = new code.Stage('Deploy'); +pipeline.addStage(stage); new cicd.PipelineDeployStackAction(stack, 'DeployStack', { stage, stack, diff --git a/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts b/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts index 25f41261ec849..850009164a07d 100644 --- a/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts +++ b/packages/@aws-cdk/app-delivery/test/test.pipeline-deploy-stack-action.ts @@ -29,12 +29,15 @@ export = nodeunit.testCase({ const app = new cdk.App(); const stack = new cdk.Stack(app, 'Test', { env: { account: pipelineAccount } }); const pipeline = new code.Pipeline(stack, 'Pipeline'); - const fakeAction = new FakeAction(stack, 'Fake', pipeline); + const fakeAction = new FakeAction('Fake'); + pipeline.addStage(new code.Stage('FakeStage', { + actions: [fakeAction], + })); new PipelineDeployStackAction(stack, 'Action', { changeSetName: 'ChangeSet', inputArtifact: fakeAction.outputArtifact, stack: new cdk.Stack(app, 'DeployedStack', { env: { account: stackAccount } }), - stage: pipeline.addStage('DeployStage'), + stage: new code.Stage('DeployStage'), adminPermissions: false, }); }, 'Cross-environment deployment is not supported'); @@ -54,14 +57,17 @@ export = nodeunit.testCase({ const app = new cdk.App(); const stack = new cdk.Stack(app, 'Test'); const pipeline = new code.Pipeline(stack, 'Pipeline'); - const fakeAction = new FakeAction(stack, 'Fake', pipeline); + const fakeAction = new FakeAction('Fake'); + pipeline.addStage(new code.Stage('FakeStage', { + actions: [fakeAction], + })); new PipelineDeployStackAction(stack, 'Action', { changeSetName: 'ChangeSet', createChangeSetRunOrder: createRunOrder, executeChangeSetRunOrder: executeRunOrder, inputArtifact: fakeAction.outputArtifact, stack: new cdk.Stack(app, 'DeployedStack'), - stage: pipeline.addStage('DeployStage'), + stage: new code.Stage('DeployStage'), adminPermissions: false, }); }, 'createChangeSetRunOrder must be < executeChangeSetRunOrder'); @@ -80,24 +86,30 @@ export = nodeunit.testCase({ const selfUpdatingStack = createSelfUpdatingStack(pipelineStack); + const selfUpdateStage1 = new code.Stage('SelfUpdate1'); + const selfUpdateStage2 = new code.Stage('SelfUpdate2'); + const selfUpdateStage3 = new code.Stage('SelfUpdate3'); const pipeline = selfUpdatingStack.pipeline; - const selfUpdateStage = pipeline.addStage('SelfUpdate'); + pipeline + .addStage(selfUpdateStage1) + .addStage(selfUpdateStage2) + .addStage(selfUpdateStage3); new PipelineDeployStackAction(pipelineStack, 'SelfUpdatePipeline', { - stage: selfUpdateStage, + stage: selfUpdateStage1, stack: pipelineStack, inputArtifact: selfUpdatingStack.synthesizedApp, capabilities: cfn.CloudFormationCapabilities.NamedIAM, adminPermissions: false, }); new PipelineDeployStackAction(pipelineStack, 'DeployStack', { - stage: selfUpdateStage, + stage: selfUpdateStage2, stack: stackWithNoCapability, inputArtifact: selfUpdatingStack.synthesizedApp, capabilities: cfn.CloudFormationCapabilities.None, adminPermissions: false, }); new PipelineDeployStackAction(pipelineStack, 'DeployStack2', { - stage: selfUpdateStage, + stage: selfUpdateStage3, stack: stackWithAnonymousCapability, inputArtifact: selfUpdatingStack.synthesizedApp, capabilities: cfn.CloudFormationCapabilities.AnonymousIAM, @@ -143,8 +155,9 @@ export = nodeunit.testCase({ const pipelineStack = getTestStack(); const selfUpdatingStack = createSelfUpdatingStack(pipelineStack); + const selfUpdateStage = new code.Stage('SelfUpdate'); const pipeline = selfUpdatingStack.pipeline; - const selfUpdateStage = pipeline.addStage('SelfUpdate'); + pipeline.addStage(selfUpdateStage); new PipelineDeployStackAction(pipelineStack, 'SelfUpdatePipeline', { stage: selfUpdateStage, stack: pipelineStack, @@ -176,11 +189,12 @@ export = nodeunit.testCase({ const pipelineStack = getTestStack(); const selfUpdatingStack = createSelfUpdatingStack(pipelineStack); - const pipeline = selfUpdatingStack.pipeline; const role = new iam.Role(pipelineStack, 'MyRole', { assumedBy: new iam.ServicePrincipal('cloudformation.amazonaws.com'), }); - const selfUpdateStage = pipeline.addStage('SelfUpdate'); + const pipeline = selfUpdatingStack.pipeline; + const selfUpdateStage = new code.Stage('SelfUpdate'); + pipeline.addStage(selfUpdateStage); const deployAction = new PipelineDeployStackAction(pipelineStack, 'SelfUpdatePipeline', { stage: selfUpdateStage, stack: pipelineStack, @@ -203,7 +217,8 @@ export = nodeunit.testCase({ // WHEN // // this our app/service/infra to deploy - const deployStage = pipeline.addStage('Deploy'); + const deployStage = new code.Stage('Deploy'); + pipeline.addStage(deployStage); const deployAction = new PipelineDeployStackAction(pipelineStack, 'DeployServiceStackA', { stage: deployStage, stack: emptyStack, @@ -248,7 +263,7 @@ export = nodeunit.testCase({ }, Roles: [ { - Ref: 'DeployServiceStackAChangeSetRoleA1245536', + Ref: 'CodePipelineDeployChangeSetRoleF9F2B343', }, ], })); @@ -262,13 +277,18 @@ export = nodeunit.testCase({ const app = new cdk.App(); const stack = new cdk.Stack(app, 'Test'); const pipeline = new code.Pipeline(stack, 'Pipeline'); - const fakeAction = new FakeAction(stack, 'Fake', pipeline); + const fakeAction = new FakeAction('Fake'); + pipeline.addStage(new code.Stage('FakeStage', { + actions: [fakeAction], + })); const deployedStack = new cdk.Stack(app, 'DeployedStack'); + const deployStage = new code.Stage('DeployStage'); + pipeline.addStage(deployStage); const action = new PipelineDeployStackAction(stack, 'Action', { changeSetName: 'ChangeSet', inputArtifact: fakeAction.outputArtifact, stack: deployedStack, - stage: pipeline.addStage('DeployStage'), + stage: deployStage, adminPermissions: false, }); for (let i = 0 ; i < assetCount ; i++) { @@ -286,15 +306,18 @@ export = nodeunit.testCase({ class FakeAction extends api.Action { public readonly outputArtifact: api.Artifact; - constructor(scope: cdk.Construct, id: string, pipeline: code.Pipeline) { - super(scope, id, { + constructor(actionName: string) { + super(actionName, { artifactBounds: api.defaultBounds(), category: api.ActionCategory.Test, provider: 'Test', - stage: pipeline.addStage('FakeStage'), }); - this.outputArtifact = new api.Artifact(this, 'OutputArtifact'); + this.outputArtifact = new api.Artifact('OutputArtifact'); + } + + protected bind(_pipeline: api.IPipeline, _parent: cdk.Construct): void { + // do nothing } } @@ -309,15 +332,21 @@ function createSelfUpdatingStack(pipelineStack: cdk.Stack): SelfUpdatingPipeline // simple source const bucket = s3.Bucket.import( pipeline, 'PatternBucket', { bucketArn: 'arn:aws:s3:::totally-fake-bucket' }); - new s3.PipelineSourceAction(pipeline, 'S3Source', { + const sourceAction = new s3.PipelineSourceAction('S3Source', { bucket, bucketKey: 'the-great-key', - stage: pipeline.addStage('source'), }); + pipeline.addStage(new code.Stage('source', { + actions: [sourceAction], + })); const project = new codebuild.PipelineProject(pipelineStack, 'CodeBuild'); - const buildStage = pipeline.addStage('build'); - const buildAction = project.addToPipeline(buildStage, 'CodeBuild'); + const buildAction = project.asCodePipelineAction('CodeBuild', { + inputArtifact: sourceAction.outputArtifact, + }); + pipeline.addStage(new code.Stage('build', { + actions: [buildAction], + })); const synthesizedApp = buildAction.outputArtifact; return {synthesizedApp, pipeline}; } diff --git a/packages/@aws-cdk/aws-cloudformation/lib/pipeline-actions.ts b/packages/@aws-cdk/aws-cloudformation/lib/pipeline-actions.ts index 1c3ee7be6767f..9d53d50491605 100644 --- a/packages/@aws-cdk/aws-cloudformation/lib/pipeline-actions.ts +++ b/packages/@aws-cdk/aws-cloudformation/lib/pipeline-actions.ts @@ -5,8 +5,7 @@ import cdk = require('@aws-cdk/cdk'); /** * Properties common to all CloudFormation actions */ -export interface PipelineCloudFormationActionProps extends codepipeline.CommonActionProps, - codepipeline.CommonActionConstructProps { +export interface PipelineCloudFormationActionProps extends codepipeline.CommonActionProps { /** * The name of the stack to apply this action to */ @@ -57,9 +56,8 @@ export abstract class PipelineCloudFormationAction extends codepipeline.Action { */ public outputArtifact?: codepipeline.Artifact; - constructor(scope: cdk.Construct, id: string, props: PipelineCloudFormationActionProps, configuration?: any) { - super(scope, id, { - stage: props.stage, + constructor(actionName: string, props: PipelineCloudFormationActionProps, configuration?: any) { + super(actionName, { runOrder: props.runOrder, region: props.region, artifactBounds: { @@ -79,7 +77,7 @@ export abstract class PipelineCloudFormationAction extends codepipeline.Action { if (props.outputFileName) { this.outputArtifact = this.addOutputArtifact(props.outputArtifactName || - (props.stage.name + this.node.id + 'Artifact')); + (`${actionName}_${props.stackName}_Artifact`)); } } } @@ -98,14 +96,20 @@ export interface PipelineExecuteChangeSetActionProps extends PipelineCloudFormat * CodePipeline action to execute a prepared change set. */ export class PipelineExecuteChangeSetAction extends PipelineCloudFormationAction { - constructor(scope: cdk.Construct, id: string, props: PipelineExecuteChangeSetActionProps) { - super(scope, id, props, { + private readonly props: PipelineExecuteChangeSetActionProps; + + constructor(actionName: string, props: PipelineExecuteChangeSetActionProps) { + super(actionName, props, { ActionMode: 'CHANGE_SET_EXECUTE', ChangeSetName: props.changeSetName, }); - SingletonPolicy.forRole(props.stage.pipeline.role) - .grantExecuteChangeSet(props); + this.props = props; + } + + protected bind(pipeline: codepipeline.IPipeline, _parent: cdk.Construct): void { + SingletonPolicy.forRole(pipeline.role) + .grantExecuteChangeSet(this.props); } } @@ -194,40 +198,57 @@ export interface PipelineCloudFormationDeployActionProps extends PipelineCloudFo * Base class for all CloudFormation actions that execute or stage deployments. */ export abstract class PipelineCloudFormationDeployAction extends PipelineCloudFormationAction { - public readonly role: iam.IRole; + private _role?: iam.IRole; + private readonly props: PipelineCloudFormationDeployActionProps; - constructor(scope: cdk.Construct, id: string, props: PipelineCloudFormationDeployActionProps, configuration: any) { + constructor(actionName: string, props: PipelineCloudFormationDeployActionProps, configuration: any) { const capabilities = props.adminPermissions && props.capabilities === undefined ? CloudFormationCapabilities.NamedIAM : props.capabilities; - super(scope, id, props, { + super(actionName, props, { ...configuration, // None evaluates to empty string which is falsey and results in undefined Capabilities: (capabilities && capabilities.toString()) || undefined, RoleArn: new cdk.Token(() => this.role.roleArn), - ParameterOverrides: new cdk.Token(() => this.node.stringifyJson(props.parameterOverrides)), + ParameterOverrides: new cdk.Token(() => this.getParent('resolve').node.stringifyJson(props.parameterOverrides)), TemplateConfiguration: props.templateConfiguration ? props.templateConfiguration.location : undefined, StackName: props.stackName, }); - if (props.role) { - this.role = props.role; + this.props = props; + } + + /** + * Add statement to the service role assumed by CloudFormation while executing this action. + */ + public addToRolePolicy(statement: iam.PolicyStatement): void { + return this.getRole('method addToRolePolicy()').addToPolicy(statement); + } + + public get role(): iam.IRole { + return this.getRole('property role()'); + } + + protected bind(pipeline: codepipeline.IPipeline, parent: cdk.Construct): void { + if (this.props.role) { + this._role = this.props.role; } else { - this.role = new iam.Role(this, 'Role', { + this._role = new iam.Role(parent, 'Role', { assumedBy: new iam.ServicePrincipal('cloudformation.amazonaws.com') }); - if (props.adminPermissions) { - this.role.addToPolicy(new iam.PolicyStatement().addAction('*').addAllResources()); + if (this.props.adminPermissions) { + this._role.addToPolicy(new iam.PolicyStatement().addAction('*').addAllResources()); } } - SingletonPolicy.forRole(props.stage.pipeline.role).grantPassRole(this.role); + SingletonPolicy.forRole(pipeline.role).grantPassRole(this._role); } - /** - * Add statement to the service role assumed by CloudFormation while executing this action. - */ - public addToRolePolicy(statement: iam.PolicyStatement) { - return this.role.addToPolicy(statement); + private getRole(member: string): iam.IRole { + if (this._role) { + return this._role; + } else { + throw new Error(`Cannot use the ${member} before the Action has been added to a Pipeline`); + } } } @@ -253,19 +274,28 @@ export interface PipelineCreateReplaceChangeSetActionProps extends PipelineCloud * If the change set exists, AWS CloudFormation deletes it, and then creates a new one. */ export class PipelineCreateReplaceChangeSetAction extends PipelineCloudFormationDeployAction { - constructor(scope: cdk.Construct, id: string, props: PipelineCreateReplaceChangeSetActionProps) { - super(scope, id, props, { + private readonly props2: PipelineCreateReplaceChangeSetActionProps; + + constructor(actionName: string, props: PipelineCreateReplaceChangeSetActionProps) { + super(actionName, props, { ActionMode: 'CHANGE_SET_REPLACE', ChangeSetName: props.changeSetName, TemplatePath: props.templatePath.location, }); this.addInputArtifact(props.templatePath.artifact); - if (props.templateConfiguration && props.templateConfiguration.artifact.name !== props.templatePath.artifact.name) { + if (props.templateConfiguration && + props.templateConfiguration.artifact.artifactName !== props.templatePath.artifact.artifactName) { this.addInputArtifact(props.templateConfiguration.artifact); } - SingletonPolicy.forRole(props.stage.pipeline.role).grantCreateReplaceChangeSet(props); + this.props2 = props; + } + + protected bind(pipeline: codepipeline.IPipeline, parent: cdk.Construct): void { + super.bind(pipeline, parent); + + SingletonPolicy.forRole(pipeline.role).grantCreateReplaceChangeSet(this.props2); } } @@ -309,18 +339,27 @@ export interface PipelineCreateUpdateStackActionProps extends PipelineCloudForma * troubleshooting them. You would typically choose this mode for testing. */ export class PipelineCreateUpdateStackAction extends PipelineCloudFormationDeployAction { - constructor(scope: cdk.Construct, id: string, props: PipelineCreateUpdateStackActionProps) { - super(scope, id, props, { + private readonly props2: PipelineCreateUpdateStackActionProps; + + constructor(actionName: string, props: PipelineCreateUpdateStackActionProps) { + super(actionName, props, { ActionMode: props.replaceOnFailure ? 'REPLACE_ON_FAILURE' : 'CREATE_UPDATE', TemplatePath: props.templatePath.location }); this.addInputArtifact(props.templatePath.artifact); - if (props.templateConfiguration && props.templateConfiguration.artifact.name !== props.templatePath.artifact.name) { + if (props.templateConfiguration && + props.templateConfiguration.artifact.artifactName !== props.templatePath.artifact.artifactName) { this.addInputArtifact(props.templateConfiguration.artifact); } - SingletonPolicy.forRole(props.stage.pipeline.role).grantCreateUpdateStack(props); + this.props2 = props; + } + + protected bind(pipeline: codepipeline.IPipeline, parent: cdk.Construct): void { + super.bind(pipeline, parent); + + SingletonPolicy.forRole(pipeline.role).grantCreateUpdateStack(this.props2); } } @@ -338,11 +377,20 @@ export interface PipelineDeleteStackActionProps extends PipelineCloudFormationDe * without deleting a stack. */ export class PipelineDeleteStackAction extends PipelineCloudFormationDeployAction { - constructor(scope: cdk.Construct, id: string, props: PipelineDeleteStackActionProps) { - super(scope, id, props, { + private readonly props2: PipelineDeleteStackActionProps; + + constructor(actionName: string, props: PipelineDeleteStackActionProps) { + super(actionName, props, { ActionMode: 'DELETE_ONLY', }); - SingletonPolicy.forRole(props.stage.pipeline.role).grantDeleteStack(props); + + this.props2 = props; + } + + protected bind(pipeline: codepipeline.IPipeline, parent: cdk.Construct): void { + super.bind(pipeline, parent); + + SingletonPolicy.forRole(pipeline.role).grantDeleteStack(this.props2); } } @@ -501,4 +549,4 @@ interface StatementTemplate { conditions?: StatementCondition; } -type StatementCondition = { [op: string]: { [attribute: string]: string } }; \ No newline at end of file +type StatementCondition = { [op: string]: { [attribute: string]: string } }; diff --git a/packages/@aws-cdk/aws-cloudformation/test/test.pipeline-actions.ts b/packages/@aws-cdk/aws-cloudformation/test/test.pipeline-actions.ts index 2e512336c0ab5..c9e4fc1bb9c21 100644 --- a/packages/@aws-cdk/aws-cloudformation/test/test.pipeline-actions.ts +++ b/packages/@aws-cdk/aws-cloudformation/test/test.pipeline-actions.ts @@ -11,15 +11,17 @@ export = nodeunit.testCase({ 'works'(test: nodeunit.Test) { const stack = new cdk.Stack(); const pipelineRole = new RoleDouble(stack, 'PipelineRole'); - const stage = new StageDouble({ pipeline: new PipelineDouble(stack, 'Pipeline', { role: pipelineRole }) }); - const artifact = new cpapi.Artifact(stack as any, 'TestArtifact'); - const action = new cloudformation.PipelineCreateReplaceChangeSetAction(stack, 'Action', { - stage, + const artifact = new cpapi.Artifact('TestArtifact'); + const action = new cloudformation.PipelineCreateReplaceChangeSetAction('Action', { changeSetName: 'MyChangeSet', stackName: 'MyStack', templatePath: artifact.atPath('path/to/file'), adminPermissions: false, }); + const stage = new StageDouble({ + pipeline: new PipelineDouble(stack, 'Pipeline', { role: pipelineRole }), + actions: [action], + }); _assertPermissionGranted(test, pipelineRole.statements, 'iam:PassRole', action.role.roleArn); @@ -45,22 +47,23 @@ export = nodeunit.testCase({ 'uses a single permission statement if the same ChangeSet name is used'(test: nodeunit.Test) { const stack = new cdk.Stack(); const pipelineRole = new RoleDouble(stack, 'PipelineRole'); - const stage = new StageDouble({ pipeline: new PipelineDouble(stack, 'Pipeline', { role: pipelineRole }) }); - const artifact = new cpapi.Artifact(stack as any, 'TestArtifact'); - new cloudformation.PipelineCreateReplaceChangeSetAction(stack, 'ActionA', { - stage, - changeSetName: 'MyChangeSet', - stackName: 'StackA', - adminPermissions: false, - templatePath: artifact.atPath('path/to/file') - }); - - new cloudformation.PipelineCreateReplaceChangeSetAction(stack, 'ActionB', { - stage, - changeSetName: 'MyChangeSet', - stackName: 'StackB', - adminPermissions: false, - templatePath: artifact.atPath('path/to/other/file') + const artifact = new cpapi.Artifact('TestArtifact'); + new StageDouble({ + pipeline: new PipelineDouble(stack, 'Pipeline', { role: pipelineRole }), + actions: [ + new cloudformation.PipelineCreateReplaceChangeSetAction('ActionA', { + changeSetName: 'MyChangeSet', + stackName: 'StackA', + adminPermissions: false, + templatePath: artifact.atPath('path/to/file') + }), + new cloudformation.PipelineCreateReplaceChangeSetAction('ActionB', { + changeSetName: 'MyChangeSet', + stackName: 'StackB', + adminPermissions: false, + templatePath: artifact.atPath('path/to/other/file') + }), + ], }); test.deepEqual( @@ -70,8 +73,8 @@ export = nodeunit.testCase({ Action: 'iam:PassRole', Effect: 'Allow', Resource: [ - { 'Fn::GetAtt': [ 'ActionARole72759154', 'Arn' ] }, - { 'Fn::GetAtt': [ 'ActionBRole6A2F6804', 'Arn' ] } + { 'Fn::GetAtt': [ 'PipelineTestStageActionARole9283FBE3', 'Arn' ] }, + { 'Fn::GetAtt': [ 'PipelineTestStageActionBRoleCABC8FA5', 'Arn' ] } ], }, { @@ -101,11 +104,14 @@ export = nodeunit.testCase({ 'works'(test: nodeunit.Test) { const stack = new cdk.Stack(); const pipelineRole = new RoleDouble(stack, 'PipelineRole'); - const stage = new StageDouble({ pipeline: new PipelineDouble(stack, 'Pipeline', { role: pipelineRole }) }); - new cloudformation.PipelineExecuteChangeSetAction(stack, 'Action', { - stage, - changeSetName: 'MyChangeSet', - stackName: 'MyStack', + const stage = new StageDouble({ + pipeline: new PipelineDouble(stack, 'Pipeline', { role: pipelineRole }), + actions: [ + new cloudformation.PipelineExecuteChangeSetAction('Action', { + changeSetName: 'MyChangeSet', + stackName: 'MyStack', + }), + ], }); const stackArn = _stackArn('MyStack', stack); @@ -124,17 +130,18 @@ export = nodeunit.testCase({ 'uses a single permission statement if the same ChangeSet name is used'(test: nodeunit.Test) { const stack = new cdk.Stack(); const pipelineRole = new RoleDouble(stack, 'PipelineRole'); - const stage = new StageDouble({ pipeline: new PipelineDouble(stack, 'Pipeline', { role: pipelineRole }) }); - new cloudformation.PipelineExecuteChangeSetAction(stack, 'ActionA', { - stage, - changeSetName: 'MyChangeSet', - stackName: 'StackA', - }); - - new cloudformation.PipelineExecuteChangeSetAction(stack, 'ActionB', { - stage, - changeSetName: 'MyChangeSet', - stackName: 'StackB', + new StageDouble({ + pipeline: new PipelineDouble(stack, 'Pipeline', { role: pipelineRole }), + actions: [ + new cloudformation.PipelineExecuteChangeSetAction('ActionA', { + changeSetName: 'MyChangeSet', + stackName: 'StackA', + }), + new cloudformation.PipelineExecuteChangeSetAction('ActionB', { + changeSetName: 'MyChangeSet', + stackName: 'StackB', + }), + ], }); test.deepEqual( @@ -161,13 +168,16 @@ export = nodeunit.testCase({ 'the CreateUpdateStack Action sets the DescribeStack*, Create/Update/DeleteStack & PassRole permissions'(test: nodeunit.Test) { const stack = new cdk.Stack(); const pipelineRole = new RoleDouble(stack, 'PipelineRole'); - const action = new cloudformation.PipelineCreateUpdateStackAction(stack, 'Action', { - stage: new StageDouble({ pipeline: new PipelineDouble(stack, 'Pipeline', { role: pipelineRole }) }), - templatePath: new cpapi.Artifact(stack as any, 'TestArtifact').atPath('some/file'), + const action = new cloudformation.PipelineCreateUpdateStackAction('Action', { + templatePath: new cpapi.Artifact('TestArtifact').atPath('some/file'), stackName: 'MyStack', - adminPermissions: false, + adminPermissions: false, replaceOnFailure: true, }); + new StageDouble({ + pipeline: new PipelineDouble(stack, 'Pipeline', { role: pipelineRole }), + actions: [action], + }); const stackArn = _stackArn('MyStack', stack); _assertPermissionGranted(test, pipelineRole.statements, 'cloudformation:DescribeStack*', stackArn); @@ -183,11 +193,14 @@ export = nodeunit.testCase({ 'the DeleteStack Action sets the DescribeStack*, DeleteStack & PassRole permissions'(test: nodeunit.Test) { const stack = new cdk.Stack(); const pipelineRole = new RoleDouble(stack, 'PipelineRole'); - const action = new cloudformation.PipelineDeleteStackAction(stack, 'Action', { - stage: new StageDouble({ pipeline: new PipelineDouble(stack, 'Pipeline', { role: pipelineRole }) }), - adminPermissions: false, + const action = new cloudformation.PipelineDeleteStackAction('Action', { + adminPermissions: false, stackName: 'MyStack', }); + new StageDouble({ + pipeline: new PipelineDouble(stack, 'Pipeline', { role: pipelineRole }), + actions: [action], + }); const stackArn = _stackArn('MyStack', stack); _assertPermissionGranted(test, pipelineRole.statements, 'cloudformation:DescribeStack*', stackArn); @@ -291,49 +304,50 @@ class PipelineDouble extends cdk.Construct implements cpapi.IPipeline { this.role = role; } - public get uniqueId(): string { - throw new Error("Unsupported"); - } - - public grantBucketRead(): void { - throw new Error("Unsupported"); + public asEventRuleTarget(_ruleArn: string, _ruleUniqueId: string): events.EventRuleTargetProps { + throw new Error('asEventRuleTarget() is unsupported in PipelineDouble'); } - public grantBucketReadWrite(): void { - throw new Error("Unsupported"); + public grantBucketRead(_identity?: iam.IPrincipal): void { + throw new Error('grantBucketRead() is unsupported in PipelineDouble'); } - public asEventRuleTarget(): events.EventRuleTargetProps { - throw new Error("Unsupported"); + public grantBucketReadWrite(_identity?: iam.IPrincipal): void { + throw new Error('grantBucketReadWrite() is unsupported in PipelineDouble'); } } -class StageDouble implements cpapi.IStage, cpapi.IInternalStage { +class StageDouble implements cpapi.IStage { public readonly name: string; public readonly pipeline: cpapi.IPipeline; - public readonly _internal = this; + public readonly actions: cpapi.Action[]; - public readonly actions = new Array(); + constructor({ name, pipeline, actions }: { name?: string, pipeline: PipelineDouble, actions: cpapi.Action[] }) { + this.name = name || 'TestStage'; + this.pipeline = pipeline; - public get node(): cdk.ConstructNode { - throw new Error('this is not a real construct'); + const stageParent = new cdk.Construct(pipeline, this.name); + for (const action of actions) { + const actionParent = new cdk.Construct(stageParent, action.actionName); + (action as any)._attachActionToPipeline(this.pipeline, this, actionParent); + } + this.actions = actions; } - constructor({ name, pipeline }: { name?: string, pipeline: cpapi.IPipeline }) { - this.name = name || 'TestStage'; - this.pipeline = pipeline; + public get stageName(): string { + return this.name; } - public _attachAction(action: cpapi.Action) { - this.actions.push(action); + public _actionAddedToStage(_action: cpapi.Action): void { + // do nothing } - public _generateOutputArtifactName(): string { - throw new Error('Unsupported'); + public addAction(_action: cpapi.Action): cpapi.IStage { + throw new Error('addAction() is not supported on StageDouble'); } - public _findInputArtifact(): cpapi.Artifact { - throw new Error('Unsupported'); + public render(): any { + throw new Error('render() is not supported on StageDouble'); } } @@ -352,4 +366,4 @@ class RoleDouble extends iam.Role { function resolve(x: any): any { return new cdk.Stack().node.resolve(x); -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-codebuild/README.md b/packages/@aws-cdk/aws-codebuild/README.md index 1c29ef6edbcf3..dd97b5fb0b2b7 100644 --- a/packages/@aws-cdk/aws-codebuild/README.md +++ b/packages/@aws-cdk/aws-codebuild/README.md @@ -170,7 +170,7 @@ rule.addTarget(lambdaFunction); Example of a Project used in CodePipeline, alongside CodeCommit: -```ts +```typescript import codebuild = require('@aws-cdk/aws-codebuild'); import codecommit = require('@aws-cdk/aws-codecommit'); import codepipeline = require('@aws-cdk/aws-codepipeline'); @@ -181,15 +181,27 @@ const repository = new codecommit.Repository(this, 'MyRepository', { const project = new codebuild.PipelineProject(this, 'MyProject'); -const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); +const sourceAction = repository.asCodePipelineAction('CodeCommit'); +const sourceStage = new codepipeline.Stage('Source', { + actions: [ + sourceAction, + ], +}); -const sourceStage = pipeline.addStage('Source'); -repository.addToPipeline(sourceStage, 'CodeCommit'); +const buildAction = project.asCodePipelineAction('CodeBuild', { + inputArtifact: sourceAction.outputArtifact, +}); +const buildStage = new codepipeline.Stage('Build', { + actions: [ + buildAction, + ], +}); -const buildStage = pipeline.addStage('Build'); -new codebuild.PipelineBuildAction(this, 'CodeBuild', { - stage: buildStage, - project, +new codepipeline.Pipeline(this, 'MyPipeline', { + stages: [ + sourceStage, + buildStage, + ], }); ``` @@ -204,30 +216,24 @@ const project = new codebuild.Project(this, 'MyProject', { } ``` -You can also add the Project to the Pipeline directly: - -```ts -// equivalent to the code above: -const buildAction = project.addToPipeline(buildStage, 'CodeBuild'); -``` - In addition to the build Action, there is also a test Action. It works very similarly to the build Action, the only difference is that the test Action does not always produce an output artifact. Examples: -```ts -new codebuild.PipelineTestAction(this, 'IntegrationTest', { - stage: buildStage, +```typescript +const testAction = new codebuild.PipelineTestAction('IntegrationTest', { project, + inputArtifact: sourceAction.outputArtifact, // outputArtifactName is optional - if you don't specify it, // the Action will have an undefined `outputArtifact` property outputArtifactName: 'IntegrationTestOutput', }); // equivalent to the code above: -project.addToPipelineAsTest(buildStage, 'IntegrationTest', { +const testAction = project.asCodePipelineTestAction('IntegrationTest', { + inputArtifact: sourceAction.outputArtifact, // of course, this property is optional here as well outputArtifactName: 'IntegrationTestOutput', }); @@ -307,14 +313,12 @@ properties, you need to use the `additionalInputArtifacts` and Actions. Example: ```ts -const sourceStage = pipeline.addStage('Source'); -const sourceAction1 = repository1.addToPipeline(sourceStage, 'Source1'); -const sourceAction2 = repository2.addToPipeline(sourceStage, 'Source2', { +const sourceAction1 = repository1.asCodePipelineAction('Source1'); +const sourceAction2 = repository2.asCodePipelineAction('Source2', { outputArtifactName: 'source2', }); -const buildStage = pipeline.addStage('Build'); -const buildAction = project.addToPipeline(buildStage, 'Build', { +const buildAction = project.asCodePipelineAction('Build', { inputArtifact: sourceAction1.outputArtifact, outputArtifactName: 'artifact1', // for better buildspec readability - see below additionalInputArtifacts: [ diff --git a/packages/@aws-cdk/aws-codebuild/lib/pipeline-actions.ts b/packages/@aws-cdk/aws-codebuild/lib/pipeline-actions.ts index 718553416a290..a968198d1baa7 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/pipeline-actions.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/pipeline-actions.ts @@ -23,16 +23,14 @@ export interface CommonCodeBuildActionProps { /** * Common properties for creating {@link PipelineBuildAction} - * either directly, through its constructor, - * or through {@link IProject#addToPipeline}. + * or through {@link IProject#asCodePipelineAction}. */ export interface CommonPipelineBuildActionProps extends CommonCodeBuildActionProps, codepipeline.CommonActionProps { /** * The source to use as input for this build. - * - * @default CodePipeline will use the output of the last Action from a previous Stage as input */ - inputArtifact?: codepipeline.Artifact; + inputArtifact: codepipeline.Artifact; /** * The name of the build's output artifact. @@ -45,8 +43,7 @@ export interface CommonPipelineBuildActionProps extends CommonCodeBuildActionPro /** * Construction properties of the {@link PipelineBuildAction CodeBuild build CodePipeline Action}. */ -export interface PipelineBuildActionProps extends CommonPipelineBuildActionProps, - codepipeline.CommonActionConstructProps { +export interface PipelineBuildActionProps extends CommonPipelineBuildActionProps { /** * The build project */ @@ -57,25 +54,20 @@ export interface PipelineBuildActionProps extends CommonPipelineBuildActionProps * CodePipeline build Action that uses AWS CodeBuild. */ export class PipelineBuildAction extends codepipeline.BuildAction { - constructor(scope: cdk.Construct, id: string, props: PipelineBuildActionProps) { - // This happened when ProjectName was accidentally set to the project's ARN: - // https://qiita.com/ikeisuke/items/2fbc0b80b9bbd981b41f + private readonly props: PipelineBuildActionProps; - super(scope, id, { + constructor(actionName: string, props: PipelineBuildActionProps) { + super(actionName, { provider: 'CodeBuild', artifactBounds: { minInputs: 1, maxInputs: 5, minOutputs: 0, maxOutputs: 5 }, + outputArtifactName: props.outputArtifactName || `Artifact_${actionName}_${props.project.node.uniqueId}`, configuration: { ProjectName: props.project.projectName, }, ...props, }); - setCodeBuildNeededPermissions(props.stage, props.project, true); - - handleAdditionalInputOutputArtifacts(props, this, - // pass functions to get around protected members - (artifact) => this.addInputArtifact(artifact), - (artifactName) => this.addOutputArtifact(artifactName)); + this.props = props; } /** @@ -104,21 +96,28 @@ export class PipelineBuildAction extends codepipeline.BuildAction { public additionalOutputArtifact(name: string): codepipeline.Artifact { return findOutputArtifact(this.additionalOutputArtifacts(), name); } + + protected bind(pipeline: codepipeline.IPipeline, _parent: cdk.Construct): void { + setCodeBuildNeededPermissions(pipeline, this.props.project, true); + + handleAdditionalInputOutputArtifacts(this.props, this, + // pass functions to get around protected members + (artifact) => this.addInputArtifact(artifact), + (artifactName) => this.addOutputArtifact(artifactName)); + } } /** * Common properties for creating {@link PipelineTestAction} - * either directly, through its constructor, - * or through {@link IProject#addToPipelineAsTest}. + * or through {@link IProject#asCodePipelineTestAction}. */ export interface CommonPipelineTestActionProps extends CommonCodeBuildActionProps, codepipeline.CommonActionProps { /** * The source to use as input for this test. - * - * @default CodePipeline will use the output of the last Action from a previous Stage as input */ - inputArtifact?: codepipeline.Artifact; + inputArtifact: codepipeline.Artifact; /** * The optional name of the primary output artifact. @@ -134,8 +133,7 @@ export interface CommonPipelineTestActionProps extends CommonCodeBuildActionProp /** * Construction properties of the {@link PipelineTestAction CodeBuild test CodePipeline Action}. */ -export interface PipelineTestActionProps extends CommonPipelineTestActionProps, - codepipeline.CommonActionConstructProps { +export interface PipelineTestActionProps extends CommonPipelineTestActionProps { /** * The build Project. */ @@ -143,8 +141,10 @@ export interface PipelineTestActionProps extends CommonPipelineTestActionProps, } export class PipelineTestAction extends codepipeline.TestAction { - constructor(scope: cdk.Construct, id: string, props: PipelineTestActionProps) { - super(scope, id, { + private readonly props: PipelineTestActionProps; + + constructor(actionName: string, props: PipelineTestActionProps) { + super(actionName, { provider: 'CodeBuild', artifactBounds: { minInputs: 1, maxInputs: 5, minOutputs: 0, maxOutputs: 5 }, configuration: { @@ -153,13 +153,7 @@ export class PipelineTestAction extends codepipeline.TestAction { ...props, }); - // the Action needs write permissions only if it's producing an output artifact - setCodeBuildNeededPermissions(props.stage, props.project, !!props.outputArtifactName); - - handleAdditionalInputOutputArtifacts(props, this, - // pass functions to get around protected members - (artifact) => this.addInputArtifact(artifact), - (artifactName) => this.addOutputArtifact(artifactName)); + this.props = props; } /** @@ -190,12 +184,21 @@ export class PipelineTestAction extends codepipeline.TestAction { public additionalOutputArtifact(name: string): codepipeline.Artifact { return findOutputArtifact(this.additionalOutputArtifacts(), name); } + + protected bind(pipeline: codepipeline.IPipeline, _parent: cdk.Construct): void { + setCodeBuildNeededPermissions(pipeline, this.props.project, !!this.props.outputArtifactName); + + handleAdditionalInputOutputArtifacts(this.props, this, + // pass functions to get around protected members + (artifact) => this.addInputArtifact(artifact), + (artifactName) => this.addOutputArtifact(artifactName)); + } } -function setCodeBuildNeededPermissions(stage: codepipeline.IStage, project: IProject, +function setCodeBuildNeededPermissions(pipeline: codepipeline.IPipeline, project: IProject, needsPipelineBucketWrite: boolean) { // grant the Pipeline role the required permissions to this Project - stage.pipeline.role.addToPolicy(new iam.PolicyStatement() + pipeline.role.addToPolicy(new iam.PolicyStatement() .addResource(project.projectArn) .addActions( 'codebuild:BatchGetBuilds', @@ -205,9 +208,9 @@ function setCodeBuildNeededPermissions(stage: codepipeline.IStage, project: IPro // allow the Project access to the Pipline's artifact Bucket if (needsPipelineBucketWrite) { - stage.pipeline.grantBucketReadWrite(project.role); + pipeline.grantBucketReadWrite(project.role); } else { - stage.pipeline.grantBucketRead(project.role); + pipeline.grantBucketRead(project.role); } } @@ -216,7 +219,7 @@ function handleAdditionalInputOutputArtifacts(props: CommonCodeBuildActionProps, addOutputArtifact: (_: string) => void) { if ((props.additionalInputArtifacts || []).length > 0) { // we have to set the primary source in the configuration - action.configuration.PrimarySource = action._inputArtifacts[0].name; + action.configuration.PrimarySource = action._inputArtifacts[0].artifactName; // add the additional artifacts for (const additionalInputArtifact of props.additionalInputArtifacts || []) { addInputArtifact(additionalInputArtifact); @@ -229,7 +232,7 @@ function handleAdditionalInputOutputArtifacts(props: CommonCodeBuildActionProps, } function findOutputArtifact(artifacts: codepipeline.Artifact[], name: string): codepipeline.Artifact { - const ret = artifacts.find((artifact) => artifact.name === name); + const ret = artifacts.find((artifact) => artifact.artifactName === name); if (!ret) { throw new Error(`Could not find output artifact with name '${name}'`); } diff --git a/packages/@aws-cdk/aws-codebuild/lib/project.ts b/packages/@aws-cdk/aws-codebuild/lib/project.ts index 2ea98fa5e0c15..98e35bb3c14da 100644 --- a/packages/@aws-cdk/aws-codebuild/lib/project.ts +++ b/packages/@aws-cdk/aws-codebuild/lib/project.ts @@ -1,7 +1,6 @@ import assets = require('@aws-cdk/assets'); import { DockerImageAsset, DockerImageAssetProps } from '@aws-cdk/assets-docker'; import cloudwatch = require('@aws-cdk/aws-cloudwatch'); -import codepipeline = require('@aws-cdk/aws-codepipeline-api'); import ecr = require('@aws-cdk/aws-ecr'); import events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); @@ -31,26 +30,22 @@ export interface IProject extends cdk.IConstruct, events.IEventRuleTarget { readonly role?: iam.Role; /** - * Convenience method for creating a new {@link PipelineBuildAction} build Action, - * and adding it to the given Stage. + * Convenience method for creating a new {@link PipelineBuildAction CodeBuild build Action}. * - * @param stage the Pipeline Stage to add the new Action to - * @param name the name of the newly created Action + * @param actionName the name of the newly created Action * @param props the properties of the new Action - * @returns the newly created {@link PipelineBuildAction} build Action + * @returns the newly created {@link PipelineBuildAction CodeBuild build Action} */ - addToPipeline(stage: codepipeline.IStage, name: string, props?: CommonPipelineBuildActionProps): PipelineBuildAction; + asCodePipelineAction(actionName: string, props: CommonPipelineBuildActionProps): PipelineBuildAction; /** - * Convenience method for creating a new {@link PipelineTestAction} test Action, - * and adding it to the given Stage. + * Convenience method for creating a new {@link PipelineTestAction CodeBuild test Action}. * - * @param stage the Pipeline Stage to add the new Action to - * @param name the name of the newly created Action + * @param actionName the name of the newly created Action * @param props the properties of the new Action - * @returns the newly created {@link PipelineBuildAction} test Action + * @returns the newly created {@link PipelineBuildAction CodeBuild test Action} */ - addToPipelineAsTest(stage: codepipeline.IStage, name: string, props?: CommonPipelineTestActionProps): PipelineTestAction; + asCodePipelineTestAction(actionName: string, props: CommonPipelineTestActionProps): PipelineTestAction; /** * Defines a CloudWatch event rule triggered when the build project state @@ -196,35 +191,15 @@ export abstract class ProjectBase extends cdk.Construct implements IProject { public abstract export(): ProjectImportProps; - /** - * Convenience method for creating a new {@link PipelineBuildAction} build Action, - * and adding it to the given Stage. - * - * @param stage the Pipeline Stage to add the new Action to - * @param name the name of the newly created Action - * @param props the properties of the new Action - * @returns the newly created {@link PipelineBuildAction} build Action - */ - public addToPipeline(stage: codepipeline.IStage, name: string, props: CommonPipelineBuildActionProps = {}): PipelineBuildAction { - return new PipelineBuildAction(this, name, { - stage, + public asCodePipelineAction(actionName: string, props: CommonPipelineBuildActionProps): PipelineBuildAction { + return new PipelineBuildAction(actionName, { project: this, ...props, }); } - /** - * Convenience method for creating a new {@link PipelineTestAction} test Action, - * and adding it to the given Stage. - * - * @param stage the Pipeline Stage to add the new Action to - * @param name the name of the newly created Action - * @param props the properties of the new Action - * @returns the newly created {@link PipelineBuildAction} test Action - */ - public addToPipelineAsTest(stage: codepipeline.IStage, name: string, props: CommonPipelineTestActionProps = {}): PipelineTestAction { - return new PipelineTestAction(this, name, { - stage, + public asCodePipelineTestAction(actionName: string, props: CommonPipelineTestActionProps): PipelineTestAction { + return new PipelineTestAction(actionName, { project: this, ...props, }); diff --git a/packages/@aws-cdk/aws-codecommit/README.md b/packages/@aws-cdk/aws-codecommit/README.md index b8b4753296a07..d1bdd319a6518 100644 --- a/packages/@aws-cdk/aws-codecommit/README.md +++ b/packages/@aws-cdk/aws-codecommit/README.md @@ -26,20 +26,23 @@ To use a CodeCommit Repository in a CodePipeline: import codepipeline = require('@aws-cdk/aws-codepipeline'); const pipeline = new codepipeline.Pipeline(this, 'MyPipeline', { - pipelineName: 'MyPipeline', + pipelineName: 'MyPipeline', }); -const sourceStage = pipeline.addStage('Source'); -const sourceAction = new codecommit.PipelineSourceAction(this, 'CodeCommit', { - stage: sourceStage, - repository: repo, +const sourceAction = new codecommit.PipelineSourceAction('CodeCommit', { + repository: repo, }); +pipeline.addStage(new codepipeline.Stage('Source', { + actions: [ + sourceAction, + ], +})); ``` You can also add the Repository to the Pipeline directly: ```ts // equivalent to the code above: -const sourceAction = repo.addToPipeline(sourceStage, 'CodeCommit'); +const sourceAction = repo.asCodePipelineAction('CodeCommit'); ``` ### Events diff --git a/packages/@aws-cdk/aws-codecommit/lib/pipeline-action.ts b/packages/@aws-cdk/aws-codecommit/lib/pipeline-action.ts index 596a7d7882cad..688cc67e4468b 100644 --- a/packages/@aws-cdk/aws-codecommit/lib/pipeline-action.ts +++ b/packages/@aws-cdk/aws-codecommit/lib/pipeline-action.ts @@ -6,7 +6,7 @@ import { IRepository } from './repository'; /** * Common properties for creating {@link PipelineSourceAction} - * either directly, through its constructor, - * or through {@link IRepository#addToPipeline}. + * or through {@link IRepository#asCodePipelineAction}. */ export interface CommonPipelineSourceActionProps extends codepipeline.CommonActionProps { /** @@ -34,8 +34,7 @@ export interface CommonPipelineSourceActionProps extends codepipeline.CommonActi /** * Construction properties of the {@link PipelineSourceAction CodeCommit source CodePipeline Action}. */ -export interface PipelineSourceActionProps extends CommonPipelineSourceActionProps, - codepipeline.CommonActionConstructProps { +export interface PipelineSourceActionProps extends CommonPipelineSourceActionProps { /** * The CodeCommit repository. */ @@ -46,9 +45,10 @@ export interface PipelineSourceActionProps extends CommonPipelineSourceActionPro * CodePipeline Source that is provided by an AWS CodeCommit repository. */ export class PipelineSourceAction extends codepipeline.SourceAction { - constructor(scope: cdk.Construct, id: string, props: PipelineSourceActionProps) { - super(scope, id, { - stage: props.stage, + private readonly props: PipelineSourceActionProps; + + constructor(actionName: string, props: PipelineSourceActionProps) { + super(actionName, { runOrder: props.runOrder, provider: 'CodeCommit', configuration: { @@ -56,11 +56,15 @@ export class PipelineSourceAction extends codepipeline.SourceAction { BranchName: props.branch || 'master', PollForSourceChanges: props.pollForSourceChanges || false, }, - outputArtifactName: props.outputArtifactName + outputArtifactName: props.outputArtifactName || `Artifact_${actionName}_${props.repository.node.uniqueId}`, }); - if (!props.pollForSourceChanges) { - props.repository.onCommit(props.stage.pipeline.node.uniqueId + 'EventRule', props.stage.pipeline, props.branch || 'master'); + this.props = props; + } + + protected bind(pipeline: codepipeline.IPipeline, _parent: cdk.Construct): void { + if (!this.props.pollForSourceChanges) { + this.props.repository.onCommit(pipeline.node.uniqueId + 'EventRule', pipeline, this.props.branch || 'master'); } // https://docs.aws.amazon.com/codecommit/latest/userguide/auth-and-access-control-permissions-reference.html#aa-acp @@ -72,8 +76,8 @@ export class PipelineSourceAction extends codepipeline.SourceAction { 'codecommit:CancelUploadArchive', ]; - props.stage.pipeline.role.addToPolicy(new iam.PolicyStatement() - .addResource(props.repository.repositoryArn) + pipeline.role.addToPolicy(new iam.PolicyStatement() + .addResource(this.props.repository.repositoryArn) .addActions(...actions)); } } diff --git a/packages/@aws-cdk/aws-codecommit/lib/repository.ts b/packages/@aws-cdk/aws-codecommit/lib/repository.ts index 80bb733212c59..7a4d6dfd9bb84 100644 --- a/packages/@aws-cdk/aws-codecommit/lib/repository.ts +++ b/packages/@aws-cdk/aws-codecommit/lib/repository.ts @@ -1,4 +1,3 @@ -import actions = require('@aws-cdk/aws-codepipeline-api'); import events = require('@aws-cdk/aws-events'); import cdk = require('@aws-cdk/cdk'); import { CfnRepository } from './codecommit.generated'; @@ -18,15 +17,13 @@ export interface IRepository extends cdk.IConstruct { readonly repositoryCloneUrlSsh: string; /** - * Convenience method for creating a new {@link PipelineSourceAction}, - * and adding it to the given Stage. + * Convenience method for creating a new {@link PipelineSourceAction}. * - * @param stage the Pipeline Stage to add the new Action to - * @param name the name of the newly created Action + * @param actionName the name of the newly created Action * @param props the properties of the new Action * @returns the newly created {@link PipelineSourceAction} */ - addToPipeline(stage: actions.IStage, name: string, props?: CommonPipelineSourceActionProps): PipelineSourceAction; + asCodePipelineAction(actionName: string, props?: CommonPipelineSourceActionProps): PipelineSourceAction; /** * Defines a CloudWatch event rule which triggers for repository events. Use @@ -123,19 +120,9 @@ export abstract class RepositoryBase extends cdk.Construct implements IRepositor public abstract export(): RepositoryImportProps; - /** - * Convenience method for creating a new {@link PipelineSourceAction}, - * and adding it to the given Stage. - * - * @param stage the Pipeline Stage to add the new Action to - * @param name the name of the newly created Action - * @param props the properties of the new Action - * @returns the newly created {@link PipelineSourceAction} - */ - public addToPipeline(stage: actions.IStage, name: string, props: CommonPipelineSourceActionProps = {}): PipelineSourceAction { - return new PipelineSourceAction(this, name, { - stage, - repository: this, + public asCodePipelineAction(actionName: string, props: CommonPipelineSourceActionProps = {}): PipelineSourceAction { + return new PipelineSourceAction(actionName, { + repository: this, ...props, }); } diff --git a/packages/@aws-cdk/aws-codecommit/package.json b/packages/@aws-cdk/aws-codecommit/package.json index 875694c99bdff..8fa8ab7ec3514 100644 --- a/packages/@aws-cdk/aws-codecommit/package.json +++ b/packages/@aws-cdk/aws-codecommit/package.json @@ -43,7 +43,7 @@ }, "nyc": { "lines": 30, - "branches": 40 + "branches": 38 }, "keywords": [ "aws", @@ -81,4 +81,4 @@ "engines": { "node": ">= 8.10.0" } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-codedeploy/README.md b/packages/@aws-cdk/aws-codedeploy/README.md index 6bb1f64527847..ee605da2c1974 100644 --- a/packages/@aws-cdk/aws-codedeploy/README.md +++ b/packages/@aws-cdk/aws-codedeploy/README.md @@ -171,16 +171,20 @@ const pipeline = new codepipeline.Pipeline(this, 'MyPipeline', { // add the source and build Stages to the Pipeline... -const deployStage = pipeline.addStage('Deploy'); -new codedeploy.PipelineDeployAction(this, 'CodeDeploy', { - stage: deployStage, - deploymentGroup, +const deployAction = new codedeploy.PipelineDeployAction('CodeDeploy', { + inputArtifact: buildAction.outputArtifact, + deploymentGroup, }); +pipeline.addStage(new codepipeline.Stage('Deploy', { + actions: [deployAction], +})); ``` You can also add the Deployment Group to the Pipeline directly: ```ts // equivalent to the code above: -deploymentGroup.addToPipeline(deployStage, 'CodeDeploy'); +const deployAction = deploymentGroup.asCodePipelineAction('CodeDeploy', { + inputArtifact: buildAction.outputArtifact, +}); ``` diff --git a/packages/@aws-cdk/aws-codedeploy/lib/deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/lib/deployment-group.ts index 083cb853653ad..952b009837026 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/deployment-group.ts @@ -1,7 +1,6 @@ import autoscaling = require("@aws-cdk/aws-autoscaling"); import cloudwatch = require("@aws-cdk/aws-cloudwatch"); import codedeploylb = require("@aws-cdk/aws-codedeploy-api"); -import codepipeline = require("@aws-cdk/aws-codepipeline-api"); import ec2 = require("@aws-cdk/aws-ec2"); import iam = require('@aws-cdk/aws-iam'); import s3 = require("@aws-cdk/aws-s3"); @@ -19,6 +18,15 @@ export interface IServerDeploymentGroup extends cdk.IConstruct { readonly deploymentConfig: IServerDeploymentConfig; readonly autoScalingGroups?: autoscaling.AutoScalingGroup[]; export(): ServerDeploymentGroupImportProps; + + /** + * Convenience method for creating a new {@link PipelineDeployAction}. + * + * @param actionName the name of the newly created Action + * @param props the properties of the new Action + * @returns the newly created {@link PipelineDeployAction} deploy Action + */ + asCodePipelineAction(actionName: string, props: CommonPipelineDeployActionProps): PipelineDeployAction; } /** @@ -73,20 +81,10 @@ export abstract class ServerDeploymentGroupBase extends cdk.Construct implements public abstract export(): ServerDeploymentGroupImportProps; - /** - * Convenience method for creating a new {@link PipelineDeployAction} - * and adding it to the given Stage. - * - * @param stage the Pipeline Stage to add the new Action to - * @param name the name of the newly created Action - * @param props the properties of the new Action - * @returns the newly created {@link PipelineDeployAction} deploy Action - */ - public addToPipeline(stage: codepipeline.IStage, name: string, props: CommonPipelineDeployActionProps = {}): + public asCodePipelineAction(actionName: string, props: CommonPipelineDeployActionProps): PipelineDeployAction { - return new PipelineDeployAction(this, name, { + return new PipelineDeployAction(actionName, { deploymentGroup: this, - stage, ...props, }); } diff --git a/packages/@aws-cdk/aws-codedeploy/lib/pipeline-action.ts b/packages/@aws-cdk/aws-codedeploy/lib/pipeline-action.ts index 27bd3485d3ba0..51ab1c27f7ebe 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/pipeline-action.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/pipeline-action.ts @@ -6,22 +6,19 @@ import { IServerDeploymentGroup } from './deployment-group'; /** * Common properties for creating a {@link PipelineDeployAction}, * either directly, through its constructor, - * or through {@link IServerDeploymentGroup#addToPipeline}. + * or through {@link IServerDeploymentGroup#asCodePipelineAction}. */ export interface CommonPipelineDeployActionProps extends codepipeline.CommonActionProps { /** * The source to use as input for deployment. - * - * @default CodePipeline will use the output of the last Action from a previous Stage as input */ - inputArtifact?: codepipeline.Artifact; + inputArtifact: codepipeline.Artifact; } /** * Construction properties of the {@link PipelineDeployAction CodeDeploy deploy CodePipeline Action}. */ -export interface PipelineDeployActionProps extends CommonPipelineDeployActionProps, - codepipeline.CommonActionConstructProps { +export interface PipelineDeployActionProps extends CommonPipelineDeployActionProps { /** * The CodeDeploy Deployment Group to deploy to. */ @@ -29,9 +26,10 @@ export interface PipelineDeployActionProps extends CommonPipelineDeployActionPro } export class PipelineDeployAction extends codepipeline.DeployAction { - constructor(scope: cdk.Construct, id: string, props: PipelineDeployActionProps) { - super(scope, id, { - stage: props.stage, + private readonly deploymentGroup: IServerDeploymentGroup; + + constructor(actionName: string, props: PipelineDeployActionProps) { + super(actionName, { runOrder: props.runOrder, artifactBounds: { minInputs: 1, maxInputs: 1, minOutputs: 0, maxOutputs: 0 }, provider: 'CodeDeploy', @@ -42,32 +40,36 @@ export class PipelineDeployAction extends codepipeline.DeployAction { }, }); + this.deploymentGroup = props.deploymentGroup; + } + + protected bind(pipeline: codepipeline.IPipeline, parent: cdk.Construct): void { // permissions, based on: // https://docs.aws.amazon.com/codedeploy/latest/userguide/auth-and-access-control-permissions-reference.html - props.stage.pipeline.role.addToPolicy(new iam.PolicyStatement() - .addResource(props.deploymentGroup.application.applicationArn) + pipeline.role.addToPolicy(new iam.PolicyStatement() + .addResource(this.deploymentGroup.application.applicationArn) .addActions( 'codedeploy:GetApplicationRevision', 'codedeploy:RegisterApplicationRevision', )); - props.stage.pipeline.role.addToPolicy(new iam.PolicyStatement() - .addResource(props.deploymentGroup.deploymentGroupArn) + pipeline.role.addToPolicy(new iam.PolicyStatement() + .addResource(this.deploymentGroup.deploymentGroupArn) .addActions( 'codedeploy:CreateDeployment', 'codedeploy:GetDeployment', )); - props.stage.pipeline.role.addToPolicy(new iam.PolicyStatement() - .addResource(props.deploymentGroup.deploymentConfig.deploymentConfigArn(this)) + pipeline.role.addToPolicy(new iam.PolicyStatement() + .addResource(this.deploymentGroup.deploymentConfig.deploymentConfigArn(parent)) .addActions( 'codedeploy:GetDeploymentConfig', )); // grant the ASG Role permissions to read from the Pipeline Bucket - for (const asg of props.deploymentGroup.autoScalingGroups || []) { - props.stage.pipeline.grantBucketRead(asg.role); + for (const asg of this.deploymentGroup.autoScalingGroups || []) { + pipeline.grantBucketRead(asg.role); } } } diff --git a/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts b/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts index 0e1bbf2344590..b99e90d174ed7 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts +++ b/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts @@ -36,37 +36,6 @@ export function defaultBounds(): ActionArtifactBounds { }; } -/** - * The API of Stage used internally by the CodePipeline Construct. - * You should never need to call any of the methods inside of it yourself. - */ -export interface IInternalStage { - /** - * Adds an Action to this Stage. - * - * @param action the Action to add to this Stage - */ - _attachAction(action: Action): void; - - /** - * Generates a unique output artifact name for the given Action. - * - * @param action the Action to generate the output artifact name for - */ - _generateOutputArtifactName(action: Action): string; - - /** - * Finds an input artifact for the given Action. - * The chosen artifact will be the output artifact of the - * last Action in the Pipeline - * (up to the Stage this Action belongs to) - * with the highest runOrder that has an output artifact. - * - * @param action the Action to find the input artifact for - */ - _findInputArtifact(action: Action): Artifact; -} - /** * The abstract view of an AWS CodePipeline as required and used by Actions. * It extends {@link events.IEventRuleTarget}, @@ -106,22 +75,15 @@ export interface IPipeline extends cdk.IConstruct, events.IEventRuleTarget { /** * The abstract interface of a Pipeline Stage that is used by Actions. */ -export interface IStage extends cdk.IConstruct { +export interface IStage { /** * The physical, human-readable name of this Pipeline Stage. */ - readonly name: string; + readonly stageName: string; - /** - * The Pipeline this Stage belongs to. - */ - readonly pipeline: IPipeline; - - /** - * The API of Stage used internally by the CodePipeline Construct. - * You should never need to call any of the methods inside of it yourself. - */ - readonly _internal: IInternalStage; + readonly actions: Action[]; + addAction(action: Action): IStage; + render(): any; } /** @@ -138,20 +100,10 @@ export interface CommonActionProps { runOrder?: number; } -/** - * Common properties shared by all Action Constructs. - */ -export interface CommonActionConstructProps { - /** - * The Pipeline Stage to add this Action to. - */ - stage: IStage; -} - /** * Construction properties of the low-level {@link Action Action class}. */ -export interface ActionProps extends CommonActionProps, CommonActionConstructProps { +export interface ActionProps extends CommonActionProps { category: ActionCategory; provider: string; @@ -173,7 +125,7 @@ export interface ActionProps extends CommonActionProps, CommonActionConstructPro * It is recommended that concrete types are used instead, such as {@link codecommit.PipelineSourceAction} or * {@link codebuild.PipelineBuildAction}. */ -export abstract class Action extends cdk.Construct { +export abstract class Action { /** * The category of the action. * The category defines which action type the owner @@ -215,16 +167,18 @@ export abstract class Action extends cdk.Construct { public readonly owner: string; public readonly version: string; + public readonly actionName: string; private readonly _actionInputArtifacts = new Array(); private readonly _actionOutputArtifacts = new Array(); private readonly artifactBounds: ActionArtifactBounds; - private readonly stage: IStage; - constructor(scope: cdk.Construct, id: string, props: ActionProps) { - super(scope, id); + private pipeline?: IPipeline; + private stage?: IStage; + private parent?: cdk.Construct; - validation.validateName('Action', id); + constructor(actionName: string, props: ActionProps) { + validation.validateName('Action', actionName); this.owner = props.owner || 'AWS'; this.version = props.version || '1'; @@ -234,21 +188,19 @@ export abstract class Action extends cdk.Construct { this.configuration = props.configuration; this.artifactBounds = props.artifactBounds; this.runOrder = props.runOrder === undefined ? 1 : props.runOrder; - this.stage = props.stage; - - this.stage._internal._attachAction(this); + this.actionName = actionName; } public onStateChange(name: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps) { - const rule = new events.EventRule(this, name, options); + const rule = new events.EventRule(this.getParent('onStateChange'), name, options); rule.addTarget(target); rule.addEventPattern({ detailType: [ 'CodePipeline Stage Execution State Change' ], source: [ 'aws.codepipeline' ], - resources: [ this.stage.pipeline.pipelineArn ], + resources: [ this.pipeline!.pipelineArn ], detail: { - stage: [ this.stage.name ], - action: [ this.node.id ], + stage: [ this.stage!.stageName ], + action: [ this.actionName ], }, }); return rule; @@ -270,16 +222,46 @@ export abstract class Action extends cdk.Construct { ); } - protected addOutputArtifact(name: string = this.stage._internal._generateOutputArtifactName(this)): Artifact { - const artifact = new Artifact(this, name); + protected addOutputArtifact(name: string): Artifact { + const artifact = new Artifact(name); this._actionOutputArtifacts.push(artifact); return artifact; } - protected addInputArtifact(artifact: Artifact = this.stage._internal._findInputArtifact(this)): Action { + protected addInputArtifact(artifact: Artifact): Action { this._actionInputArtifacts.push(artifact); return this; } + + protected getParent(methodName: string): cdk.Construct { + if (this.parent) { + return this.parent; + } else { + throw new Error(`Cannot call method '${methodName}' until the Action has been added to a Pipeline with addAction`); + } + } + + protected abstract bind(pipeline: IPipeline, parent: cdk.Construct): void; + + // ignore unused private method (it's actually used in Stage) + // @ts-ignore + private _attachActionToPipeline(pipeline: IPipeline, stage: IStage, parent: cdk.Construct): void { + if (this.actionIsAttachedToPipeline()) { + throw new Error(`Action '${this.actionName}' has been added to a Pipeline twice`); + } + + this.pipeline = pipeline; + this.stage = stage; + this.parent = parent; + + this.bind(pipeline, parent); + + (stage as any)._actionAddedToStage(this); + } + + private actionIsAttachedToPipeline(): boolean { + return this.pipeline !== undefined; + } } // export class ElasticBeanstalkDeploy extends DeployAction { diff --git a/packages/@aws-cdk/aws-codepipeline-api/lib/artifact.ts b/packages/@aws-cdk/aws-codepipeline-api/lib/artifact.ts index b236622d33f62..4b0d9826e7976 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/lib/artifact.ts +++ b/packages/@aws-cdk/aws-codepipeline-api/lib/artifact.ts @@ -1,12 +1,10 @@ -import { Construct, Token } from "@aws-cdk/cdk"; -import { Action } from "./action"; +import { Token } from "@aws-cdk/cdk"; /** * An output artifact of an action. Artifacts can be used as input by some actions. */ -export class Artifact extends Construct { - constructor(scope: Action, readonly name: string) { - super(scope, name); +export class Artifact { + constructor(readonly artifactName: string) { } /** @@ -14,7 +12,7 @@ export class Artifact extends Construct { * Output is in the form "::" * @param fileName The name of the file */ - public atPath(fileName: string) { + public atPath(fileName: string): ArtifactPath { return new ArtifactPath(this, fileName); } @@ -51,7 +49,7 @@ export class Artifact extends Construct { } public toString() { - return this.node.id; + return this.artifactName; } } @@ -67,14 +65,14 @@ export class ArtifactPath { } get location() { - return `${this.artifact.name}::${this.fileName}`; + return `${this.artifact.artifactName}::${this.fileName}`; } } function artifactAttribute(artifact: Artifact, attributeName: string) { - return new Token(() => ({ 'Fn::GetArtifactAtt': [artifact.name, attributeName] })).toString(); + return new Token(() => ({ 'Fn::GetArtifactAtt': [artifact.artifactName, attributeName] })).toString(); } function artifactGetParam(artifact: Artifact, jsonFile: string, keyName: string) { - return new Token(() => ({ 'Fn::GetParam': [artifact.name, jsonFile, keyName] })).toString(); + return new Token(() => ({ 'Fn::GetParam': [artifact.artifactName, jsonFile, keyName] })).toString(); } diff --git a/packages/@aws-cdk/aws-codepipeline-api/lib/build-action.ts b/packages/@aws-cdk/aws-codepipeline-api/lib/build-action.ts index b3c3330aa4a05..2c7c627b3492c 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/lib/build-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-api/lib/build-action.ts @@ -1,15 +1,14 @@ -import cdk = require("@aws-cdk/cdk"); -import { Action, ActionArtifactBounds, ActionCategory, CommonActionConstructProps, CommonActionProps } from "./action"; +import { Action, ActionArtifactBounds, ActionCategory, CommonActionProps } from "./action"; import { Artifact } from "./artifact"; /** * Construction properties of the low level {@link BuildAction build action}. */ -export interface BuildActionProps extends CommonActionProps, CommonActionConstructProps { +export interface BuildActionProps extends CommonActionProps { /** * The source to use as input for this build. */ - inputArtifact?: Artifact; + inputArtifact: Artifact; /** * The service provider that the action calls. For example, a valid provider for Source actions is CodeBuild. @@ -38,7 +37,7 @@ export interface BuildActionProps extends CommonActionProps, CommonActionConstru /** * The name of the build's output artifact. */ - outputArtifactName?: string; + outputArtifactName: string; /** * The action's configuration. These are key-value pairs that specify input values for an action. @@ -57,8 +56,8 @@ export interface BuildActionProps extends CommonActionProps, CommonActionConstru export abstract class BuildAction extends Action { public readonly outputArtifact: Artifact; - constructor(scope: cdk.Construct, id: string, props: BuildActionProps) { - super(scope, id, { + constructor(actionName: string, props: BuildActionProps) { + super(actionName, { category: ActionCategory.Build, ...props, }); diff --git a/packages/@aws-cdk/aws-codepipeline-api/lib/deploy-action.ts b/packages/@aws-cdk/aws-codepipeline-api/lib/deploy-action.ts index 5a86fd5ce2a21..caaaf6f2e3f19 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/lib/deploy-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-api/lib/deploy-action.ts @@ -1,22 +1,21 @@ -import cdk = require('@aws-cdk/cdk'); -import { Action, ActionArtifactBounds, ActionCategory, CommonActionConstructProps, CommonActionProps } from "./action"; +import { Action, ActionArtifactBounds, ActionCategory, CommonActionProps } from "./action"; import { Artifact } from './artifact'; -export interface DeployActionProps extends CommonActionProps, CommonActionConstructProps { +export interface DeployActionProps extends CommonActionProps { provider: string; owner?: string; artifactBounds: ActionArtifactBounds; - inputArtifact?: Artifact; + inputArtifact: Artifact; configuration?: any; } export abstract class DeployAction extends Action { - constructor(scope: cdk.Construct, id: string, props: DeployActionProps) { - super(scope, id, { + constructor(actionName: string, props: DeployActionProps) { + super(actionName, { category: ActionCategory.Deploy, ...props, }); diff --git a/packages/@aws-cdk/aws-codepipeline-api/lib/source-action.ts b/packages/@aws-cdk/aws-codepipeline-api/lib/source-action.ts index b18984fefb47a..1fe4519a98650 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/lib/source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-api/lib/source-action.ts @@ -1,11 +1,10 @@ -import cdk = require("@aws-cdk/cdk"); -import { Action, ActionCategory, CommonActionConstructProps, CommonActionProps } from "./action"; +import { Action, ActionCategory, CommonActionProps } from "./action"; import { Artifact } from "./artifact"; /** * Construction properties of the low-level {@link SourceAction source Action}. */ -export interface SourceActionProps extends CommonActionProps, CommonActionConstructProps { +export interface SourceActionProps extends CommonActionProps { /** * The source action owner (could be "AWS", "ThirdParty" or "Custom"). * @@ -23,10 +22,8 @@ export interface SourceActionProps extends CommonActionProps, CommonActionConstr /** * The name of the source's output artifact. * Output artifacts are used by CodePipeline as inputs into other actions. - * - * @default a name will be auto-generated */ - outputArtifactName?: string; + outputArtifactName: string; /** * The service provider that the action calls. @@ -52,8 +49,8 @@ export interface SourceActionProps extends CommonActionProps, CommonActionConstr export abstract class SourceAction extends Action { public readonly outputArtifact: Artifact; - constructor(scope: cdk.Construct, id: string, props: SourceActionProps) { - super(scope, id, { + constructor(actionName: string, props: SourceActionProps) { + super(actionName, { category: ActionCategory.Source, artifactBounds: { minInputs: 0, maxInputs: 0, minOutputs: 1, maxOutputs: 1 }, ...props, diff --git a/packages/@aws-cdk/aws-codepipeline-api/lib/test-action.ts b/packages/@aws-cdk/aws-codepipeline-api/lib/test-action.ts index b77b74e71fb9e..858e3ffc2160b 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/lib/test-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-api/lib/test-action.ts @@ -1,17 +1,14 @@ -import cdk = require("@aws-cdk/cdk"); -import { Action, ActionArtifactBounds, ActionCategory, CommonActionConstructProps, CommonActionProps } from "./action"; +import { Action, ActionArtifactBounds, ActionCategory, CommonActionProps } from "./action"; import { Artifact } from "./artifact"; /** * Construction properties of the low-level {@link TestAction test Action}. */ -export interface TestActionProps extends CommonActionProps, CommonActionConstructProps { +export interface TestActionProps extends CommonActionProps { /** * The source to use as input for this test. - * - * @default CodePipeline will use the output of the last Action from a previous Stage as input */ - inputArtifact?: Artifact; + inputArtifact: Artifact; /** * The optional name of the output artifact. @@ -71,8 +68,8 @@ export interface TestActionProps extends CommonActionProps, CommonActionConstruc export abstract class TestAction extends Action { public readonly outputArtifact?: Artifact; - constructor(scope: cdk.Construct, id: string, props: TestActionProps) { - super(scope, id, { + constructor(actionName: string, props: TestActionProps) { + super(actionName, { category: ActionCategory.Test, ...props, }); diff --git a/packages/@aws-cdk/aws-codepipeline/README.md b/packages/@aws-cdk/aws-codepipeline/README.md index 8a1ae513e972e..07e3f503e99ab 100644 --- a/packages/@aws-cdk/aws-codepipeline/README.md +++ b/packages/@aws-cdk/aws-codepipeline/README.md @@ -23,16 +23,14 @@ const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { To append a Stage to a Pipeline: ```ts -const sourceStage = pipeline.addStage('Source'); +const sourceStage = new codepipeline.Stage('Source'); +pipeline.addStage(sourceStage); ``` -You can also instantiate the `Stage` Construct directly, -which will add it to the Pipeline provided in its construction properties. - You can insert the new Stage at an arbitrary point in the Pipeline: ```ts -const sourceStage = pipeline.addStage('Source', { +pipeline.addStage(sourceStage, { placement: { // note: you can only specify one of the below properties rightBefore: anotherStage, @@ -48,30 +46,14 @@ const sourceStage = pipeline.addStage('Source', { To add an Action to a Stage: ```ts -new codepipeline.GitHubSourceAction(this, 'GitHub_Source', { - stage: sourceStage, +const sourceAction = new codepipeline.GitHubSourceAction('GitHub_Source', { owner: 'awslabs', repo: 'aws-cdk', branch: 'develop', // default: 'master' oauthToken: ..., -}) -``` - -The Pipeline construct will automatically generate and wire together the artifact names CodePipeline uses. -If you need, you can also name the artifacts explicitly: - -```ts -const sourceAction = new codepipeline.GitHubSourceAction(this, 'GitHub_Source', { - // other properties as above... outputArtifactName: 'SourceOutput', // this will be the name of the output artifact in the Pipeline }); - -// in a build Action later... - -new codepipeline.JenkinsBuildAction(this, 'Jenkins_Build', { - // other properties... - inputArtifact: sourceAction.outputArtifact, -}); +sourceStage.addAction(sourceAction); ``` #### Manual approval Action @@ -79,15 +61,16 @@ new codepipeline.JenkinsBuildAction(this, 'Jenkins_Build', { This package contains an Action that stops the Pipeline until someone manually clicks the approve button: ```typescript -const manualApprovalAction = new codepipeline.ManualApprovalAction(this, 'Approve', { - stage: approveStage, +const manualApprovalAction = new codepipeline.ManualApprovalAction('Approve', { notificationTopic: new sns.Topic(this, 'Topic'), // optional notifyEmails: [ 'some_email@example.com', ], // optional additionalInformation: 'additional info', // optional }); +approveStage.addAction(manualApprovalAction); // `manualApprovalAction.notificationTopic` can be used to access the Topic +// after the Action has been added to a Pipeline ``` If the `notificationTopic` has not been provided, @@ -129,8 +112,7 @@ With a `JenkinsProvider`, we can create a Jenkins Action: ```ts -const buildAction = new codepipeline.JenkinsBuildAction(this, 'JenkinsBuild', { - stage: buildStage, +const buildAction = new codepipeline.JenkinsBuildAction('JenkinsBuild', { jenkinsProvider: jenkinsProvider, projectName: 'MyProject', }); @@ -141,11 +123,11 @@ You can also add the Action to the Pipeline directly: ```ts // equivalent to the code above: -const buildAction = jenkinsProvider.addToPipeline(buildStage, 'JenkinsBuild', { +const buildAction = jenkinsProvider.asCodePipelineAction('JenkinsBuild', { projectName: 'MyProject', }); -const testAction = jenkinsProvider.addToPipelineAsTest(buildStage, 'JenkinsTest', { +const testAction = jenkinsProvider.asCodePipelineTestAction('JenkinsTest', { projectName: 'MyProject', }); ``` @@ -167,7 +149,7 @@ const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { }); // later in the code... -new cloudformation.PipelineCreateUpdateStackAction(this, 'CFN_US_West_1', { +new cloudformation.PipelineCreateUpdateStackAction('CFN_US_West_1', { // ... region: 'us-west-1', }); diff --git a/packages/@aws-cdk/aws-codepipeline/lib/github-source-action.ts b/packages/@aws-cdk/aws-codepipeline/lib/github-source-action.ts index c70c2d048ddf7..5053e44f5dfef 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/github-source-action.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/github-source-action.ts @@ -5,15 +5,12 @@ import { CfnWebhook } from './codepipeline.generated'; /** * Construction properties of the {@link GitHubSourceAction GitHub source action}. */ -export interface GitHubSourceActionProps extends actions.CommonActionProps, - actions.CommonActionConstructProps { +export interface GitHubSourceActionProps extends actions.CommonActionProps { /** * The name of the source's output artifact. Output artifacts are used by CodePipeline as * inputs into other actions. - * - * @default a name will be auto-generated */ - outputArtifactName?: string; + outputArtifactName: string; /** * The GitHub account/user that owns the repo. @@ -56,9 +53,10 @@ export interface GitHubSourceActionProps extends actions.CommonActionProps, * Source that is provided by a GitHub repository. */ export class GitHubSourceAction extends actions.SourceAction { - constructor(scope: cdk.Construct, id: string, props: GitHubSourceActionProps) { - super(scope, id, { - stage: props.stage, + private readonly props: GitHubSourceActionProps; + + constructor(actionName: string, props: GitHubSourceActionProps) { + super(actionName, { runOrder: props.runOrder, owner: 'ThirdParty', provider: 'GitHub', @@ -72,11 +70,15 @@ export class GitHubSourceAction extends actions.SourceAction { outputArtifactName: props.outputArtifactName }); - if (!props.pollForSourceChanges) { - new CfnWebhook(this, 'WebhookResource', { + this.props = props; + } + + protected bind(pipeline: actions.IPipeline, parent: cdk.Construct): void { + if (!this.props.pollForSourceChanges) { + new CfnWebhook(parent, 'WebhookResource', { authentication: 'GITHUB_HMAC', authenticationConfiguration: { - secretToken: props.oauthToken.toString(), + secretToken: this.props.oauthToken.toString(), }, filters: [ { @@ -84,8 +86,8 @@ export class GitHubSourceAction extends actions.SourceAction { matchEquals: 'refs/heads/{Branch}', }, ], - targetAction: this.node.id, - targetPipeline: props.stage.pipeline.pipelineName, + targetAction: this.actionName, + targetPipeline: pipeline.pipelineName, targetPipelineVersion: 1, registerWithThirdParty: true, }); diff --git a/packages/@aws-cdk/aws-codepipeline/lib/jenkins-actions.ts b/packages/@aws-cdk/aws-codepipeline/lib/jenkins-actions.ts index 8de7c426739f3..bb0b3a28e3949 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/jenkins-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/jenkins-actions.ts @@ -16,16 +16,14 @@ export interface BasicJenkinsActionProps extends cpapi.CommonActionProps { /** * The source to use as input for this build. - * - * @default CodePipeline will use the output of the last Action from a previous Stage as input */ - inputArtifact?: cpapi.Artifact; + inputArtifact: cpapi.Artifact; } /** * Common properties for creating {@link JenkinsBuildAction} - * either directly, through its constructor, - * or through {@link JenkinsProvider#addToPipeline}. + * or through {@link IJenkinsProvider#asCodePipelineAction}. */ export interface BasicJenkinsBuildActionProps extends BasicJenkinsActionProps { /** @@ -39,8 +37,7 @@ export interface BasicJenkinsBuildActionProps extends BasicJenkinsActionProps { /** * Construction properties of {@link JenkinsBuildAction}. */ -export interface JenkinsBuildActionProps extends BasicJenkinsBuildActionProps, - cpapi.CommonActionConstructProps { +export interface JenkinsBuildActionProps extends BasicJenkinsBuildActionProps { /** * The Jenkins Provider for this Action. */ @@ -53,8 +50,10 @@ export interface JenkinsBuildActionProps extends BasicJenkinsBuildActionProps, * @see https://docs.aws.amazon.com/codepipeline/latest/userguide/tutorials-four-stage-pipeline.html */ export class JenkinsBuildAction extends cpapi.BuildAction { - constructor(scope: cdk.Construct, id: string, props: JenkinsBuildActionProps) { - super(scope, id, { + private readonly jenkinsProvider: IJenkinsProvider; + + constructor(actionName: string, props: JenkinsBuildActionProps) { + super(actionName, { provider: props.jenkinsProvider.providerName, owner: 'Custom', artifactBounds: jenkinsArtifactsBounds, @@ -62,17 +61,22 @@ export class JenkinsBuildAction extends cpapi.BuildAction { configuration: { ProjectName: props.projectName, }, + outputArtifactName: props.outputArtifactName || `Artifact_${actionName}_${props.jenkinsProvider.node.uniqueId}`, ...props, }); - props.jenkinsProvider._registerBuildProvider(); + this.jenkinsProvider = props.jenkinsProvider; + } + + protected bind(_pipeline: cpapi.IPipeline, _parent: cdk.Construct): void { + this.jenkinsProvider._registerBuildProvider(); } } /** * Common properties for creating {@link JenkinsTestAction} - * either directly, through its constructor, - * or through {@link JenkinsProvider#addToPipelineAsTest}. + * or through {@link IJenkinsProvider#asCodePipelineTestAction}. */ export interface BasicJenkinsTestActionProps extends BasicJenkinsActionProps { /** @@ -89,8 +93,7 @@ export interface BasicJenkinsTestActionProps extends BasicJenkinsActionProps { /** * Construction properties of {@link JenkinsTestAction}. */ -export interface JenkinsTestActionProps extends BasicJenkinsTestActionProps, - cpapi.CommonActionConstructProps { +export interface JenkinsTestActionProps extends BasicJenkinsTestActionProps { /** * The Jenkins Provider for this Action. */ @@ -103,8 +106,10 @@ export interface JenkinsTestActionProps extends BasicJenkinsTestActionProps, * @see https://docs.aws.amazon.com/codepipeline/latest/userguide/tutorials-four-stage-pipeline.html */ export class JenkinsTestAction extends cpapi.TestAction { - constructor(scope: cdk.Construct, id: string, props: JenkinsTestActionProps) { - super(scope, id, { + private readonly jenkinsProvider: IJenkinsProvider; + + constructor(actionName: string, props: JenkinsTestActionProps) { + super(actionName, { provider: props.jenkinsProvider.providerName, owner: 'Custom', artifactBounds: jenkinsArtifactsBounds, @@ -115,6 +120,10 @@ export class JenkinsTestAction extends cpapi.TestAction { ...props, }); - props.jenkinsProvider._registerTestProvider(); + this.jenkinsProvider = props.jenkinsProvider; + } + + protected bind(_pipeline: cpapi.IPipeline, _parent: cdk.Construct): void { + this.jenkinsProvider._registerTestProvider(); } } diff --git a/packages/@aws-cdk/aws-codepipeline/lib/jenkins-provider.ts b/packages/@aws-cdk/aws-codepipeline/lib/jenkins-provider.ts index b6315e5b8b4e4..775dd99633efc 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/jenkins-provider.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/jenkins-provider.ts @@ -17,33 +17,29 @@ import { * If you want to reference an already registered provider, * use the {@link JenkinsProvider#import} method. */ -export interface IJenkinsProvider { +export interface IJenkinsProvider extends cdk.IConstruct { readonly providerName: string; readonly serverUrl: string; readonly version: string; /** - * Convenience method for creating a new {@link JenkinsBuildAction}, - * and adding it to the given Stage. + * Convenience method for creating a new {@link JenkinsBuildAction}. * - * @param stage the Pipeline Stage to add the new Action to - * @param name the name of the newly created Action + * @param actionName the name of the newly created Action * @param props construction properties of the new Action * @returns the newly created {@link JenkinsBuildAction} */ - addToPipeline(stage: cpapi.IStage, name: string, props: BasicJenkinsBuildActionProps): + asCodePipelineAction(actionName: string, props: BasicJenkinsBuildActionProps): JenkinsBuildAction; /** - * Convenience method for creating a new {@link JenkinsTestAction}, - * and adding it to the given Stage. + * Convenience method for creating a new {@link JenkinsTestAction}. * - * @param stage the Pipeline Stage to add the new Action to - * @param name the name of the newly created Action + * @param actionName the name of the newly created Action * @param props construction properties of the new Action * @returns the newly created {@link JenkinsTestAction} */ - addToPipelineAsTest(stage: cpapi.IStage, name: string, props: BasicJenkinsTestActionProps): + asCodePipelineTestAction(actionName: string, props: BasicJenkinsTestActionProps): JenkinsTestAction; /** @@ -149,19 +145,15 @@ export abstract class BaseJenkinsProvider extends cdk.Construct implements IJenk }; } - public addToPipeline(stage: cpapi.IStage, name: string, props: BasicJenkinsBuildActionProps): - JenkinsBuildAction { - return new JenkinsBuildAction(this, name, { - stage, + public asCodePipelineAction(actionName: string, props: BasicJenkinsBuildActionProps): JenkinsBuildAction { + return new JenkinsBuildAction(actionName, { jenkinsProvider: this, ...props, }); } - public addToPipelineAsTest(stage: cpapi.IStage, name: string, props: BasicJenkinsTestActionProps): - JenkinsTestAction { - return new JenkinsTestAction(this, name, { - stage, + public asCodePipelineTestAction(actionName: string, props: BasicJenkinsTestActionProps): JenkinsTestAction { + return new JenkinsTestAction(actionName, { jenkinsProvider: this, ...props, }); diff --git a/packages/@aws-cdk/aws-codepipeline/lib/manual-approval-action.ts b/packages/@aws-cdk/aws-codepipeline/lib/manual-approval-action.ts index 9b5a839d44dd8..d1e9b3a537b76 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/manual-approval-action.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/manual-approval-action.ts @@ -5,8 +5,7 @@ import cdk = require('@aws-cdk/cdk'); /** * Construction properties of the {@link ManualApprovalAction}. */ -export interface ManualApprovalActionProps extends actions.CommonActionProps, - actions.CommonActionConstructProps { +export interface ManualApprovalActionProps extends actions.CommonActionProps { /** * Optional SNS topic to send notifications to when an approval is pending. */ @@ -34,36 +33,45 @@ export class ManualApprovalAction extends actions.Action { * If no Topic was passed, but `notifyEmails` were provided, * a new Topic will be created. */ - public readonly notificationTopic?: sns.ITopic; + private _notificationTopic?: sns.ITopic; + private readonly props: ManualApprovalActionProps; - constructor(scope: cdk.Construct, id: string, props: ManualApprovalActionProps) { - super(scope, id, { + constructor(actionName: string, props: ManualApprovalActionProps = {}) { + super(actionName, { category: actions.ActionCategory.Approval, provider: 'Manual', artifactBounds: { minInputs: 0, maxInputs: 0, minOutputs: 0, maxOutputs: 0 }, - configuration: new cdk.Token(() => this.actionConfiguration(props)), + configuration: new cdk.Token(() => this.actionConfiguration()), ...props, }); - if (props.notificationTopic) { - this.notificationTopic = props.notificationTopic; - } else if ((props.notifyEmails || []).length > 0) { - this.notificationTopic = new sns.Topic(this, 'TopicResource'); + this.props = props; + } + + public get notificationTopic(): sns.ITopic | undefined { + return this._notificationTopic; + } + + protected bind(pipeline: actions.IPipeline, parent: cdk.Construct): void { + if (this.props.notificationTopic) { + this._notificationTopic = this.props.notificationTopic; + } else if ((this.props.notifyEmails || []).length > 0) { + this._notificationTopic = new sns.Topic(parent, 'TopicResource'); } - if (this.notificationTopic) { - this.notificationTopic.grantPublish(props.stage.pipeline.role); - for (const notifyEmail of props.notifyEmails || []) { - this.notificationTopic.subscribeEmail(`Subscription-${notifyEmail}`, notifyEmail); + if (this._notificationTopic) { + this._notificationTopic.grantPublish(pipeline.role); + for (const notifyEmail of this.props.notifyEmails || []) { + this._notificationTopic.subscribeEmail(`Subscription-${notifyEmail}`, notifyEmail); } } } - private actionConfiguration(props: ManualApprovalActionProps): any { - return this.notificationTopic + private actionConfiguration(): any { + return this._notificationTopic ? { - NotificationArn: this.notificationTopic.topicArn, - CustomData: props.additionalInformation, + NotificationArn: this._notificationTopic.topicArn, + CustomData: this.props.additionalInformation, } : undefined; } diff --git a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts index 7c29de60f231a..2680562af6474 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts @@ -5,7 +5,17 @@ import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); import { CfnPipeline } from './codepipeline.generated'; import { CrossRegionScaffoldStack } from './cross-region-scaffold-stack'; -import { CommonStageProps, Stage, StagePlacement } from './stage'; +import { Stage, StagePlacement } from './stage'; + +export interface StageInsertionProps { + /** + * Allows specifying where should the newly created {@link Stage} + * be placed in the Pipeline. + * + * @default the stage is added at the end of the Pipeline + */ + placement?: StagePlacement; +} export interface PipelineProps { /** @@ -34,6 +44,13 @@ export interface PipelineProps { * You can query the generated Stacks using the {@link Pipeline#crossRegionScaffoldStacks} property. */ crossRegionReplicationBuckets?: { [region: string]: string }; + + /** + * The list of Stages, in order, + * to create this Pipeline with. + * You can always add more Stages later by calling {@link Pipeline#addStage}. + */ + stages?: cpapi.IStage[]; } /** @@ -44,15 +61,16 @@ export interface PipelineProps { * const pipeline = new Pipeline(this, 'Pipeline'); * * // add a stage - * const sourceStage = new Stage(pipeline, 'Source'); + * const sourceStage = new Stage('Source'); + * pipeline.addStage(sourceStage); * * // add a source action to the stage - * new codecommit.PipelineSourceAction(sourceStage, 'Source', { + * sourceStage.addAction(new codecommit.PipelineSourceAction('Source', { * artifactName: 'SourceArtifact', * repository: repo, - * }); + * })); * - * // ... add more stages + * // add more Stages & Actions... */ export class Pipeline extends cdk.Construct implements cpapi.IPipeline { /** @@ -81,7 +99,7 @@ export class Pipeline extends cdk.Construct implements cpapi.IPipeline { */ public readonly artifactBucket: s3.IBucket; - private readonly stages = new Array(); + private readonly stages = new Array(); private eventsRole?: iam.Role; private readonly pipelineResource: CfnPipeline; private readonly crossRegionReplicationBuckets: { [region: string]: string }; @@ -131,21 +149,33 @@ export class Pipeline extends cdk.Construct implements cpapi.IPipeline { service: 'codepipeline', resource: this.pipelineName }); + + for (const stage of props.stages || []) { + this.addStage(stage); + } } /** - * Convenience method for creating a new {@link Stage}, - * and adding it to this Pipeline. + * Adds a Stage to this Pipeline. * - * @param name the name of the newly created Stage - * @param props the optional construction properties of the new Stage - * @returns the newly created Stage + * @param stage the newly created Stage to add to this Pipeline + * @param props optional Stage insertion properties */ - public addStage(name: string, props?: CommonStageProps): Stage { - return new Stage(this, name, { - pipeline: this, - ...props, - }); + public addStage(stage: cpapi.IStage, props: StageInsertionProps = {}): Pipeline { + // check for duplicate Stages and names + if (this.stages.find(s => s.stageName === stage.stageName)) { + throw new Error(`Stage with duplicate name '${stage.stageName}' added to the Pipeline`); + } + + (stage as any)._attachStageToPipeline(this); + + const index = props.placement + ? this.calculateInsertIndexFromPlacement(props.placement) + : this.stageCount; + + this.stages.splice(index, 0, stage); + + return this; } /** @@ -272,8 +302,8 @@ export class Pipeline extends cdk.Construct implements cpapi.IPipeline { return; } - if (this.stages.find(x => x.name === stage.name)) { - throw new Error(`A stage with name '${stage.name}' already exists`); + if (this.stages.find(x => x.stageName === stage.stageName)) { + throw new Error(`A stage with name '${stage.stageName}' already exists`); } const index = placement @@ -328,47 +358,6 @@ export class Pipeline extends cdk.Construct implements cpapi.IPipeline { }; } - // ignore unused private method (it's actually used in Stage) - // @ts-ignore - private _generateOutputArtifactName(stage: cpapi.IStage, action: cpapi.Action): string { - // generate the artifact name based on the Action's full logical ID, - // thus guaranteeing uniqueness - return 'Artifact_' + action.node.uniqueId; - } - - /** - * Finds an input artifact for the given Action. - * The chosen artifact will be the output artifact of the - * last Action in the Pipeline - * (up to the Stage this Action belongs to), - * with the highest runOrder, that has an output artifact. - * - * @param stage the Stage `action` belongs to - * @param action the Action to find the input artifact for - */ - // ignore unused private method (it's actually used in Stage) - // @ts-ignore - private _findInputArtifact(stage: cpapi.IStage, action: cpapi.Action): cpapi.Artifact { - // search for the first Action that has an outputArtifact, - // and return that - const startIndex = this.stages.findIndex(s => s === stage); - for (let i = startIndex; i >= 0; i--) { - const currentStage = this.stages[i]; - - // get all of the Actions in the Stage, sorted by runOrder, descending - const currentActions = currentStage.actions.sort((a1, a2) => -(a1.runOrder - a2.runOrder)); - for (const currentAction of currentActions) { - // for the first Stage (the one that `action` belongs to) - // we need to only take into account Actions with a smaller runOrder than `action` - if ((i !== startIndex || currentAction.runOrder < action.runOrder) && currentAction._outputArtifacts.length > 0) { - return currentAction._outputArtifacts[0]; - } - } - } - throw new Error(`Could not determine the input artifact for Action with name '${action.node.id}'. ` + - 'Please provide it explicitly with the inputArtifact property.'); - } - private calculateInsertIndexFromPlacement(placement: StagePlacement): number { // check if at most one placement property was provided const providedPlacementProps = ['rightBefore', 'justAfter', 'atIndex'] @@ -383,7 +372,7 @@ export class Pipeline extends cdk.Construct implements cpapi.IPipeline { const targetIndex = this.findStageIndex(placement.rightBefore); if (targetIndex === -1) { throw new Error("Error adding Stage to the Pipeline: " + - `the requested Stage to add it before, '${placement.rightBefore.name}', was not found`); + `the requested Stage to add it before, '${placement.rightBefore.stageName}', was not found`); } return targetIndex; } @@ -392,7 +381,7 @@ export class Pipeline extends cdk.Construct implements cpapi.IPipeline { const targetIndex = this.findStageIndex(placement.justAfter); if (targetIndex === -1) { throw new Error("Error adding Stage to the Pipeline: " + - `the requested Stage to add it after, '${placement.justAfter.name}', was not found`); + `the requested Stage to add it after, '${placement.justAfter.stageName}', was not found`); } return targetIndex + 1; } @@ -410,8 +399,8 @@ export class Pipeline extends cdk.Construct implements cpapi.IPipeline { return this.stageCount; } - private findStageIndex(targetStage: Stage) { - return this.stages.findIndex((stage: Stage) => stage === targetStage); + private findStageIndex(targetStage: cpapi.IStage) { + return this.stages.findIndex((stage: cpapi.IStage) => stage === targetStage); } private validateSourceActionLocations(): string[] { @@ -420,7 +409,7 @@ export class Pipeline extends cdk.Construct implements cpapi.IPipeline { for (const stage of this.stages) { const onlySourceActionsPermitted = firstStage; for (const action of stage.actions) { - errors.push(...cpapi.validateSourceAction(onlySourceActionsPermitted, action.category, action.node.id, stage.node.id)); + errors.push(...cpapi.validateSourceAction(onlySourceActionsPermitted, action.category, action.actionName, stage.stageName)); } firstStage = false; } diff --git a/packages/@aws-cdk/aws-codepipeline/lib/stage.ts b/packages/@aws-cdk/aws-codepipeline/lib/stage.ts index 1a1eb46e6e4ea..c683c3e2b0f89 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/stage.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/stage.ts @@ -37,27 +37,15 @@ export interface StagePlacement { readonly atIndex?: number; } -/** - * The properties for the {@link Pipeline#addStage} method. - */ -export interface CommonStageProps { - /** - * Allows specifying where should the newly created {@link Stage} - * be placed in the Pipeline. - * - * @default the stage is added at the end of the Pipeline - */ - placement?: StagePlacement; -} - /** * The construction properties for {@link Stage}. */ -export interface StageProps extends CommonStageProps { +export interface StageProps { /** - * The Pipeline to add the newly created Stage to. + * The list of Actions to create this Stage with. + * You can always add more Actions later by calling {@link Stage#addAction}. */ - pipeline: Pipeline; + actions?: cpapi.Action[]; } /** @@ -67,35 +55,32 @@ export interface StageProps extends CommonStageProps { * * @example * // add a Stage to a Pipeline - * new Stage(this, 'MyStage', { - * pipeline: myPipeline, + * pipeline.addStage(new Stage('MyStage', { + * actions: [ + * // ... + * ], * }); */ -export class Stage extends cdk.Construct implements cpapi.IStage, cpapi.IInternalStage { - /** - * The Pipeline this Stage is a part of. - */ - public readonly pipeline: cpapi.IPipeline; - public readonly name: string; +export class Stage implements cpapi.IStage { + public readonly stageName: string; + private readonly _actions = new Array(); /** - * The API of Stage used internally by the CodePipeline Construct. - * You should never need to call any of the methods inside of it yourself. + * The Pipeline this Stage is a part of. */ - public readonly _internal = this; - - private readonly _actions = new Array(); + private pipeline?: Pipeline; + private parent?: cdk.Construct; /** * Create a new Stage. */ - constructor(scope: cdk.Construct, id: string, props: StageProps) { - super(scope, id); - this.name = id; - this.pipeline = props.pipeline; - cpapi.validateName('Stage', id); + constructor(stageName: string, props: StageProps = {}) { + cpapi.validateName('Stage', stageName); + this.stageName = stageName; - (this.pipeline as any)._attachStage(this, props.placement); + for (const action of props.actions || []) { + this.addAction(action); + } } /** @@ -105,54 +90,85 @@ export class Stage extends cdk.Construct implements cpapi.IStage, cpapi.IInterna return this._actions.slice(); } - public render(): CfnPipeline.StageDeclarationProperty { + public render(): any { return { - name: this.node.id, + name: this.stageName, actions: this._actions.map(action => this.renderAction(action)), }; } + public addAction(action: cpapi.Action): cpapi.IStage { + // check for duplicate Actions and names + if (this._actions.find(a => a.actionName === action.actionName)) { + throw new Error(`Stage ${this.stageName} already contains an Action with name '${action.actionName}'`); + } + + this._actions.push(action); + if (this.stageIsAttachedToPipeline()) { + this.attachActionToPipeline(action); + } + + return this; + } + public onStateChange(name: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps) { - const rule = new events.EventRule(this, name, options); + const rule = new events.EventRule(this.getParent('onStateChange'), name, options); rule.addTarget(target); rule.addEventPattern({ detailType: [ 'CodePipeline Stage Execution State Change' ], source: [ 'aws.codepipeline' ], - resources: [ this.pipeline.pipelineArn ], + resources: [ this.pipeline!.pipelineArn ], detail: { - stage: [ this.node.id ], + stage: [ this.stageName ], }, }); return rule; } - // can't make this method private like Pipeline#_attachStage, - // as it comes from the IStage interface - public _attachAction(action: cpapi.Action): void { - // _attachAction should be idempotent in case a customer ever calls it directly - if (!this._actions.includes(action)) { - this._actions.push(action); + protected validate(): string[] { + return this.validateHasActions(); + } - (this.pipeline as any)._attachActionToRegion(this, action); + // ignore unused private method (it's actually used in Pipeline) + // @ts-ignore + private _attachStageToPipeline(pipeline: Pipeline): void { + if (this.stageIsAttachedToPipeline()) { + throw new Error(`Stage '${this.stageName}' has been added to a Pipeline twice`); + } + this.pipeline = pipeline; + this.parent = new cdk.Construct(pipeline, this.stageName); + for (const action of this._actions) { + this.attachActionToPipeline(action); } } - public _generateOutputArtifactName(action: cpapi.Action): string { - return (this.pipeline as any)._generateOutputArtifactName(this, action); + private stageIsAttachedToPipeline() { + return this.pipeline !== undefined; } - public _findInputArtifact(action: cpapi.Action): cpapi.Artifact { - return (this.pipeline as any)._findInputArtifact(this, action); + private attachActionToPipeline(action: cpapi.Action) { + const actionParent = new cdk.Construct(this.parent!, action.actionName); + (action as any)._attachActionToPipeline(this.pipeline, this, actionParent); } - protected validate(): string[] { - return this.validateHasActions(); + private getParent(methodName: string): cdk.Construct { + if (this.parent) { + return this.parent; + } else { + throw new Error(`Cannot call method '${methodName}' until the Stage has been added to a Pipeline with addStage`); + } + } + + // ignore unused private method (it's actually used in Action) + // @ts-ignore + private _actionAddedToStage(action: cpapi.Action): void { + (this.pipeline as any)._attachActionToRegion(this, action); } private renderAction(action: cpapi.Action): CfnPipeline.ActionDeclarationProperty { return { - name: action.node.id, - inputArtifacts: action._inputArtifacts.map(a => ({ name: a.name })), + name: action.actionName, + inputArtifacts: action._inputArtifacts.map(a => ({ name: a.artifactName })), actionTypeId: { category: action.category.toString(), version: action.version, @@ -160,14 +176,14 @@ export class Stage extends cdk.Construct implements cpapi.IStage, cpapi.IInterna provider: action.provider, }, configuration: action.configuration, - outputArtifacts: action._outputArtifacts.map(a => ({ name: a.name })), + outputArtifacts: action._outputArtifacts.map(a => ({ name: a.artifactName })), runOrder: action.runOrder, }; } private validateHasActions(): string[] { if (this._actions.length === 0) { - return [`Stage '${this.node.id}' must have at least one action`]; + return [`Stage '${this.stageName}' must have at least one Action`]; } return []; } diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.cfn-template-from-repo.lit.ts b/packages/@aws-cdk/aws-codepipeline/test/integ.cfn-template-from-repo.lit.ts index 42593fd8ecdfb..a2ab931b324e6 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.cfn-template-from-repo.lit.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.cfn-template-from-repo.lit.ts @@ -7,41 +7,44 @@ const app = new cdk.App(); const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-cloudformation'); /// !show -const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); - // Source stage: read from repository const repo = new codecommit.Repository(stack, 'TemplateRepo', { repositoryName: 'template-repo' }); -const sourceStage = new codepipeline.Stage(pipeline, 'Source', { pipeline }); -const source = new codecommit.PipelineSourceAction(stack, 'Source', { - stage: sourceStage, +const source = new codecommit.PipelineSourceAction('Source', { repository: repo, outputArtifactName: 'SourceArtifact', pollForSourceChanges: true, }); +const sourceStage = new codepipeline.Stage('Source', { + actions: [source], +}); // Deployment stage: create and deploy changeset with manual approval -const prodStage = new codepipeline.Stage(pipeline, 'Deploy', { pipeline }); const stackName = 'OurStack'; const changeSetName = 'StagedChangeSet'; -new cfn.PipelineCreateReplaceChangeSetAction(prodStage, 'PrepareChanges', { - stage: prodStage, - stackName, - changeSetName, - adminPermissions: true, - templatePath: source.outputArtifact.atPath('template.yaml'), -}); - -new codepipeline.ManualApprovalAction(stack, 'ApproveChanges', { - stage: prodStage, +const prodStage = new codepipeline.Stage('Deploy', { + actions: [ + new cfn.PipelineCreateReplaceChangeSetAction('PrepareChanges', { + stackName, + changeSetName, + adminPermissions: true, + templatePath: source.outputArtifact.atPath('template.yaml'), + }), + new codepipeline.ManualApprovalAction('ApproveChanges'), + new cfn.PipelineExecuteChangeSetAction('ExecuteChanges', { + stackName, + changeSetName, + }), + ], }); -new cfn.PipelineExecuteChangeSetAction(stack, 'ExecuteChanges', { - stage: prodStage, - stackName, - changeSetName, +new codepipeline.Pipeline(stack, 'Pipeline', { + stages: [ + sourceStage, + prodStage, + ], }); /// !hide diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.lambda-pipeline.expected.json b/packages/@aws-cdk/aws-codepipeline/test/integ.lambda-pipeline.expected.json index c8b43657dfa5c..c4eba9bf3313a 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.lambda-pipeline.expected.json +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.lambda-pipeline.expected.json @@ -272,4 +272,4 @@ ] } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.lambda-pipeline.ts b/packages/@aws-cdk/aws-codepipeline/test/integ.lambda-pipeline.ts index 2ec36f02994b5..d1b7fd68dc804 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.lambda-pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.lambda-pipeline.ts @@ -9,17 +9,16 @@ const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-lambda'); const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); -const sourceStage = new codepipeline.Stage(pipeline, 'Source', { pipeline }); +const sourceStage = new codepipeline.Stage('Source'); const bucket = new s3.Bucket(stack, 'PipelineBucket', { versioned: true, removalPolicy: cdk.RemovalPolicy.Destroy, }); -new s3.PipelineSourceAction(stack, 'Source', { - stage: sourceStage, +sourceStage.addAction(new s3.PipelineSourceAction('Source', { outputArtifactName: 'SourceArtifact', bucket, bucketKey: 'key', -}); +})); const lambdaFun = new lambda.Function(stack, 'LambdaFun', { code: new lambda.InlineCode(` @@ -30,7 +29,11 @@ const lambdaFun = new lambda.Function(stack, 'LambdaFun', { handler: 'index.handler', runtime: lambda.Runtime.NodeJS610, }); -const lambdaStage = new codepipeline.Stage(pipeline, 'Lambda', { pipeline }); -lambdaFun.addToPipeline(lambdaStage, 'Lambda'); +const lambdaStage = new codepipeline.Stage('Lambda'); +lambdaStage.addAction(lambdaFun.asCodePipelineAction('Lambda')); + +pipeline + .addStage(sourceStage) + .addStage(lambdaStage); app.run(); diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-cross-region.expected.json b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-cross-region.expected.json index 99f7e7886620d..a3d0efa9a27ce 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-cross-region.expected.json +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-cross-region.expected.json @@ -98,7 +98,7 @@ "Effect": "Allow", "Resource": { "Fn::GetAtt": [ - "CFNDeployRole68D5E8D3", + "MyPipelineCFNCFNDeployRole9CC99B3F", "Arn" ] } @@ -168,7 +168,7 @@ "Name": "S3", "OutputArtifacts": [ { - "Name": "Artifact_awscdkcodepipelinecloudformationcrossregionMyBucketS3DBF7878C" + "Name": "Artifact_S3_awscdkcodepipelinecloudformationcrossregionMyBucket99D95A28" } ], "RunOrder": 1 @@ -188,17 +188,17 @@ "Configuration": { "StackName": "aws-cdk-codepipeline-cross-region-deploy-stack", "ActionMode": "CREATE_UPDATE", - "TemplatePath": "Artifact_awscdkcodepipelinecloudformationcrossregionMyBucketS3DBF7878C::template.yml", + "TemplatePath": "Artifact_S3_awscdkcodepipelinecloudformationcrossregionMyBucket99D95A28::template.yml", "RoleArn": { "Fn::GetAtt": [ - "CFNDeployRole68D5E8D3", + "MyPipelineCFNCFNDeployRole9CC99B3F", "Arn" ] } }, "InputArtifacts": [ { - "Name": "Artifact_awscdkcodepipelinecloudformationcrossregionMyBucketS3DBF7878C" + "Name": "Artifact_S3_awscdkcodepipelinecloudformationcrossregionMyBucket99D95A28" } ], "Name": "CFN_Deploy", @@ -227,7 +227,7 @@ "MyPipelineRoleDefaultPolicy34F09EFA" ] }, - "CFNDeployRole68D5E8D3": { + "MyPipelineCFNCFNDeployRole9CC99B3F": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { @@ -245,4 +245,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-cross-region.ts b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-cross-region.ts index 85e2a579b68b8..a68b848de8ada 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-cross-region.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-cross-region.ts @@ -17,22 +17,27 @@ const bucket = new s3.Bucket(stack, 'MyBucket', { removalPolicy: cdk.RemovalPolicy.Destroy, }); -const pipeline = new codepipeline.Pipeline(stack, 'MyPipeline', { - artifactBucket: bucket, -}); - -const sourceStage = pipeline.addStage('Source'); -const sourceAction = bucket.addToPipeline(sourceStage, 'S3', { +const sourceAction = bucket.asCodePipelineAction('S3', { bucketKey: 'some/path', }); -const cfnStage = pipeline.addStage('CFN'); -new cloudformation.PipelineCreateUpdateStackAction(stack, 'CFN_Deploy', { - stage: cfnStage, - stackName: 'aws-cdk-codepipeline-cross-region-deploy-stack', - templatePath: sourceAction.outputArtifact.atPath('template.yml'), - adminPermissions: false, - region, +new codepipeline.Pipeline(stack, 'MyPipeline', { + artifactBucket: bucket, + stages: [ + new codepipeline.Stage('Source', { + actions: [sourceAction], + }), + new codepipeline.Stage('CFN', { + actions: [ + new cloudformation.PipelineCreateUpdateStackAction('CFN_Deploy', { + stackName: 'aws-cdk-codepipeline-cross-region-deploy-stack', + templatePath: sourceAction.outputArtifact.atPath('template.yml'), + adminPermissions: false, + region, + }), + ], + }), + ], }); app.run(); diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn.ts b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn.ts index 8d11ce56897ad..fe761871bf452 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn.ts @@ -11,34 +11,38 @@ const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-cloudformation'); const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); -const sourceStage = new codepipeline.Stage(pipeline, 'Source', { pipeline }); const bucket = new s3.Bucket(stack, 'PipelineBucket', { versioned: true, removalPolicy: cdk.RemovalPolicy.Destroy, }); -const source = new s3.PipelineSourceAction(stack, 'Source', { - stage: sourceStage, + +const source = new s3.PipelineSourceAction('Source', { outputArtifactName: 'SourceArtifact', bucket, bucketKey: 'key', }); - -const cfnStage = new codepipeline.Stage(stack, 'CFN', { pipeline }); +const sourceStage = new codepipeline.Stage('Source', { + actions: [source], +}); const changeSetName = "ChangeSetIntegTest"; const stackName = "IntegTest-TestActionStack"; - const role = new Role(stack, 'CfnChangeSetRole', { assumedBy: new ServicePrincipal('cloudformation.amazonaws.com'), }); -new cfn.PipelineCreateReplaceChangeSetAction(stack, 'DeployCFN', { - stage: cfnStage, - changeSetName, - stackName, - role, - templatePath: source.outputArtifact.atPath('test.yaml'), - adminPermissions: false, -}); +pipeline + .addStage(sourceStage) + .addStage(new codepipeline.Stage('CFN', { + actions: [ + new cfn.PipelineCreateReplaceChangeSetAction('DeployCFN', { + changeSetName, + stackName, + role, + templatePath: source.outputArtifact.atPath('test.yaml'), + adminPermissions: false, + }), + ], + })); app.run(); diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-build-multiple-inputs-outputs.expected.json b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-build-multiple-inputs-outputs.expected.json index fced0f5d6d44e..b963d67086dae 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-build-multiple-inputs-outputs.expected.json +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-build-multiple-inputs-outputs.expected.json @@ -259,7 +259,7 @@ "Name": "Source1", "OutputArtifacts": [ { - "Name": "Artifact_awscdkcodepipelinecodebuildmultipleinputsoutputsMyRepoSource1FB3F9DF8" + "Name": "Artifact_Source1_awscdkcodepipelinecodebuildmultipleinputsoutputsMyRepo8C065AEB" } ], "RunOrder": 1 @@ -282,7 +282,7 @@ "Name": "Source2", "OutputArtifacts": [ { - "Name": "Artifact_awscdkcodepipelinecodebuildmultipleinputsoutputsMyBucketSource22F03F24C" + "Name": "Artifact_Source2_awscdkcodepipelinecodebuildmultipleinputsoutputsMyBucketF8E74713" } ], "RunOrder": 1 @@ -303,20 +303,20 @@ "ProjectName": { "Ref": "MyBuildProject30DB9D6E" }, - "PrimarySource": "Artifact_awscdkcodepipelinecodebuildmultipleinputsoutputsMyRepoSource1FB3F9DF8" + "PrimarySource": "Artifact_Source1_awscdkcodepipelinecodebuildmultipleinputsoutputsMyRepo8C065AEB" }, "InputArtifacts": [ { - "Name": "Artifact_awscdkcodepipelinecodebuildmultipleinputsoutputsMyRepoSource1FB3F9DF8" + "Name": "Artifact_Source1_awscdkcodepipelinecodebuildmultipleinputsoutputsMyRepo8C065AEB" }, { - "Name": "Artifact_awscdkcodepipelinecodebuildmultipleinputsoutputsMyBucketSource22F03F24C" + "Name": "Artifact_Source2_awscdkcodepipelinecodebuildmultipleinputsoutputsMyBucketF8E74713" } ], "Name": "Build1", "OutputArtifacts": [ { - "Name": "Artifact_awscdkcodepipelinecodebuildmultipleinputsoutputsMyBuildProjectBuild121179895" + "Name": "Artifact_Build1_awscdkcodepipelinecodebuildmultipleinputsoutputsMyBuildProject66A94291" }, { "Name": "CustomOutput1" @@ -335,14 +335,14 @@ "ProjectName": { "Ref": "MyBuildProject30DB9D6E" }, - "PrimarySource": "Artifact_awscdkcodepipelinecodebuildmultipleinputsoutputsMyBucketSource22F03F24C" + "PrimarySource": "Artifact_Source2_awscdkcodepipelinecodebuildmultipleinputsoutputsMyBucketF8E74713" }, "InputArtifacts": [ { - "Name": "Artifact_awscdkcodepipelinecodebuildmultipleinputsoutputsMyBucketSource22F03F24C" + "Name": "Artifact_Source2_awscdkcodepipelinecodebuildmultipleinputsoutputsMyBucketF8E74713" }, { - "Name": "Artifact_awscdkcodepipelinecodebuildmultipleinputsoutputsMyRepoSource1FB3F9DF8" + "Name": "Artifact_Source1_awscdkcodepipelinecodebuildmultipleinputsoutputsMyRepo8C065AEB" } ], "Name": "Build2", diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-build-multiple-inputs-outputs.ts b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-build-multiple-inputs-outputs.ts index 53d87d2b31a96..64a1c55a562d2 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-build-multiple-inputs-outputs.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-build-multiple-inputs-outputs.ts @@ -20,15 +20,19 @@ const pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { artifactBucket: bucket, }); -const sourceStage = pipeline.addStage('Source'); -const sourceAction1 = repository.addToPipeline(sourceStage, 'Source1'); -const sourceAction2 = bucket.addToPipeline(sourceStage, 'Source2', { +const sourceAction1 = repository.asCodePipelineAction('Source1'); +const sourceAction2 = bucket.asCodePipelineAction('Source2', { bucketKey: 'some/path', }); +pipeline.addStage(new codepipeline.Stage('Source', { + actions: [ + sourceAction1, + sourceAction2, + ], +})); const project = new codebuild.PipelineProject(stack, 'MyBuildProject'); -const buildStage = pipeline.addStage('Build'); -const buildAction = project.addToPipeline(buildStage, 'Build1', { +const buildAction = project.asCodePipelineAction('Build1', { inputArtifact: sourceAction1.outputArtifact, additionalInputArtifacts: [ sourceAction2.outputArtifact, @@ -37,7 +41,7 @@ const buildAction = project.addToPipeline(buildStage, 'Build1', { 'CustomOutput1', ], }); -const testAction = project.addToPipelineAsTest(buildStage, 'Build2', { +const testAction = project.asCodePipelineTestAction('Build2', { inputArtifact: sourceAction2.outputArtifact, additionalInputArtifacts: [ sourceAction1.outputArtifact, @@ -46,6 +50,12 @@ const testAction = project.addToPipelineAsTest(buildStage, 'Build2', { 'CustomOutput2', ], }); +pipeline.addStage(new codepipeline.Stage('Build', { + actions: [ + buildAction, + testAction, + ], +})); // some assertions on the Action helper methods if (buildAction.additionalOutputArtifacts().length !== 1) { diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit-build.expected.json b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit-build.expected.json index c0083e6923c60..2b00bc31e7057 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit-build.expected.json +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit-build.expected.json @@ -190,7 +190,7 @@ "Name": "build", "OutputArtifacts": [ { - "Name": "Artifact_awscdkcodepipelinecodecommitcodebuildMyBuildProjectbuild61B48DC4" + "Name": "Artifact_build_awscdkcodepipelinecodecommitcodebuildMyBuildProject20018D4F" } ], "RunOrder": 1 @@ -403,4 +403,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit-build.ts b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit-build.ts index 47ee8fe3477a6..e5ea15b4f4fe6 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit-build.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit-build.ts @@ -10,12 +10,7 @@ const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-codecommit-codebuild'); const repository = new codecommit.Repository(stack, 'MyRepo', { repositoryName: 'my-repo', }); - -const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); - -const sourceStage = new codepipeline.Stage(pipeline, 'source', { pipeline }); -new codecommit.PipelineSourceAction(stack, 'source', { - stage: sourceStage, +const sourceAction = new codecommit.PipelineSourceAction('source', { outputArtifactName: 'SourceArtifact', repository, pollForSourceChanges: true, @@ -24,9 +19,25 @@ new codecommit.PipelineSourceAction(stack, 'source', { const project = new codebuild.Project(stack, 'MyBuildProject', { source: new codebuild.CodePipelineSource(), }); +const buildAction = new codebuild.PipelineBuildAction('build', { + project, + inputArtifact: sourceAction.outputArtifact, +}); +const testAction = new codebuild.PipelineTestAction('test', { + project, + inputArtifact: sourceAction.outputArtifact, +}); -const buildStage = new codepipeline.Stage(pipeline, 'build', { pipeline }); -project.addToPipeline(buildStage, 'build'); -project.addToPipelineAsTest(buildStage, 'test'); +new codepipeline.Pipeline(stack, 'Pipeline', { + stages: [ + new codepipeline.Stage('source') + .addAction(sourceAction) + ], +}).addStage(new codepipeline.Stage('build', { + actions: [ + buildAction, + testAction, + ], +})); app.run(); diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit.ts b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit.ts index dc5d80ea8e0ea..4eef93ff99444 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-commit.ts @@ -8,16 +8,21 @@ const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-codecommit'); const repo = new codecommit.Repository(stack, 'MyRepo', { repositoryName: 'my-repo' }); -const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); - -const sourceStage = pipeline.addStage('source'); -repo.addToPipeline(sourceStage, 'source', { - outputArtifactName: 'SourceArtifact', -}); - -const buildStage = new codepipeline.Stage(stack, 'build', { pipeline }); -new codepipeline.ManualApprovalAction(stack, 'manual', { - stage: buildStage, +new codepipeline.Pipeline(stack, 'Pipeline', { + stages: [ + new codepipeline.Stage('source', { + actions: [ + repo.asCodePipelineAction('source', { + outputArtifactName: 'SourceArtifact', + }), + ], + }), + new codepipeline.Stage('build', { + actions: [ + new codepipeline.ManualApprovalAction('manual'), + ], + }), + ], }); app.run(); diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-deploy.ts b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-deploy.ts index dfe6f662ba28e..0df8d3cf7b438 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-deploy.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-code-deploy.ts @@ -30,13 +30,20 @@ const pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { artifactBucket: bucket, }); -const sourceStage = new codepipeline.Stage(stack, 'Source', { pipeline }); -bucket.addToPipeline(sourceStage, 'S3Source', { +const sourceStage = new codepipeline.Stage('Source'); +const sourceAction = bucket.asCodePipelineAction('S3Source', { bucketKey: 'application.zip', outputArtifactName: 'SourceOutput', }); +sourceStage.addAction(sourceAction); -const deployStage = new codepipeline.Stage(stack, 'Deploy', { pipeline }); -deploymentGroup.addToPipeline(deployStage, 'CodeDeploy'); +const deployStage = new codepipeline.Stage('Deploy'); +deployStage.addAction(deploymentGroup.asCodePipelineAction('CodeDeploy', { + inputArtifact: sourceAction.outputArtifact, +})); + +pipeline + .addStage(sourceStage) + .addStage(deployStage); app.run(); diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-ecr-source.expected.json b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-ecr-source.expected.json index 6236aba51716e..f9bc433dc4761 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-ecr-source.expected.json +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-ecr-source.expected.json @@ -107,7 +107,7 @@ "Name": "ECR_Source", "OutputArtifacts": [ { - "Name": "Artifact_awscdkcodepipelineecrsourceMyEcrRepoECRSource8525F033" + "Name": "Artifact_ECR_Source_awscdkcodepipelineecrsourceMyEcrRepo7074CE15" } ], "RunOrder": 1 diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-ecr-source.ts b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-ecr-source.ts index ed7b9751ba403..c815a2dd3cb6d 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-ecr-source.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-ecr-source.ts @@ -7,20 +7,22 @@ const app = new cdk.App(); const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-ecr-source'); +const repository = new ecr.Repository(stack, 'MyEcrRepo'); +const sourceStage = new codepipeline.Stage('Source'); +sourceStage.addAction(repository.asCodePipelineAction('ECR_Source')); + +const approveStage = new codepipeline.Stage('Approve'); +approveStage.addAction(new codepipeline.ManualApprovalAction('ManualApproval')); + const bucket = new s3.Bucket(stack, 'MyBucket', { removalPolicy: cdk.RemovalPolicy.Destroy, }); -const pipeline = new codepipeline.Pipeline(stack, 'MyPipeline', { +new codepipeline.Pipeline(stack, 'MyPipeline', { artifactBucket: bucket, -}); - -const repository = new ecr.Repository(stack, 'MyEcrRepo'); -const sourceStage = pipeline.addStage('Source'); -repository.addToPipeline(sourceStage, 'ECR_Source'); - -const approveStage = pipeline.addStage('Approve'); -new codepipeline.ManualApprovalAction(stack, 'ManualApproval', { - stage: approveStage, + stages: [ + sourceStage, + approveStage, + ], }); app.run(); diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-events.expected.json b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-events.expected.json index b977353f76690..c78e106199b5e 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-events.expected.json +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-events.expected.json @@ -169,7 +169,7 @@ "Name": "CodeBuildAction", "OutputArtifacts": [ { - "Name": "Artifact_awscdkpipelineeventtargetCodeBuildAction37D411C2" + "Name": "Artifact_CodeBuildAction_awscdkpipelineeventtargetBuildProject106B7735" } ], "RunOrder": 1 @@ -238,7 +238,7 @@ ] } }, - "SourceOnSourceStateChangeEF8EB16D": { + "MyPipelineSourceOnSourceStateChange6DEE3A75": { "Type": "AWS::Events::Rule", "Properties": { "EventPattern": { @@ -290,7 +290,7 @@ ] } }, - "MyPipelineCodeCommitSourceOnActionStateChange39DCCFC1": { + "MyPipelineSourceCodeCommitSourceOnActionStateChangeDCAF781A": { "Type": "AWS::Events::Rule", "Properties": { "EventPattern": { @@ -531,4 +531,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-events.ts b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-events.ts index 99d484f3658d6..08af84fb1c7b6 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-events.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-events.ts @@ -11,25 +11,25 @@ const app = new cdk.App(); const stack = new cdk.Stack(app, 'aws-cdk-pipeline-event-target'); const pipeline = new codepipeline.Pipeline(stack, 'MyPipeline'); -const sourceStage = new codepipeline.Stage(stack, 'Source', { pipeline }); -const buildStage = new codepipeline.Stage(stack, 'Build', { pipeline }); +const sourceStage = new codepipeline.Stage('Source'); +const buildStage = new codepipeline.Stage('Build'); const repository = new codecommit.Repository(stack, 'CodeCommitRepo', { repositoryName: 'foo' }); const project = new codebuild.PipelineProject(stack, 'BuildProject'); -const sourceAction = new codecommit.PipelineSourceAction(pipeline, 'CodeCommitSource', { - stage: sourceStage, +const sourceAction = new codecommit.PipelineSourceAction('CodeCommitSource', { outputArtifactName: 'Source', repository, pollForSourceChanges: true, }); -new codebuild.PipelineBuildAction(stack, 'CodeBuildAction', { - stage: buildStage, +pipeline.addStage(sourceStage.addAction(sourceAction)); + +pipeline.addStage(buildStage.addAction(new codebuild.PipelineBuildAction('CodeBuildAction', { inputArtifact: sourceAction.outputArtifact, project -}); +}))); const topic = new sns.Topic(stack, 'MyTopic'); diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-jenkins.expected.json b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-jenkins.expected.json index 56d66c4978305..cc3c404bfd42d 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-jenkins.expected.json +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-jenkins.expected.json @@ -141,7 +141,7 @@ "Name": "S3", "OutputArtifacts": [ { - "Name": "Artifact_awscdkcodepipelinejenkinsMyBucketS32154568D" + "Name": "Artifact_S3_awscdkcodepipelinejenkinsMyBucket1F504D45" } ], "RunOrder": 1 @@ -163,13 +163,13 @@ }, "InputArtifacts": [ { - "Name": "Artifact_awscdkcodepipelinejenkinsMyBucketS32154568D" + "Name": "Artifact_S3_awscdkcodepipelinejenkinsMyBucket1F504D45" } ], "Name": "JenkinsBuild", "OutputArtifacts": [ { - "Name": "Artifact_awscdkcodepipelinejenkinsJenkinsProviderJenkinsBuild6007E9FD" + "Name": "Artifact_JenkinsBuild_awscdkcodepipelinejenkinsJenkinsProviderF1B32E8D" } ], "RunOrder": 1 @@ -186,7 +186,7 @@ }, "InputArtifacts": [ { - "Name": "Artifact_awscdkcodepipelinejenkinsMyBucketS32154568D" + "Name": "Artifact_S3_awscdkcodepipelinejenkinsMyBucket1F504D45" } ], "Name": "JenkinsTest", @@ -205,7 +205,7 @@ }, "InputArtifacts": [ { - "Name": "Artifact_awscdkcodepipelinejenkinsMyBucketS32154568D" + "Name": "Artifact_S3_awscdkcodepipelinejenkinsMyBucket1F504D45" } ], "Name": "JenkinsTest2", diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-jenkins.ts b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-jenkins.ts index 5a1c6c7847101..5aee7536c10fe 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-jenkins.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-jenkins.ts @@ -14,10 +14,12 @@ const pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { artifactBucket: bucket, }); -const sourceStage = pipeline.addStage('Source'); -bucket.addToPipeline(sourceStage, 'S3', { +const sourceAction = bucket.asCodePipelineAction('S3', { bucketKey: 'some/path', }); +pipeline.addStage(new codepipeline.Stage('Source', { + actions: [sourceAction], +})); const jenkinsProvider = new codepipeline.JenkinsProvider(stack, 'JenkinsProvider', { providerName: 'JenkinsProvider', @@ -25,15 +27,21 @@ const jenkinsProvider = new codepipeline.JenkinsProvider(stack, 'JenkinsProvider version: '2', }); -const buildStage = pipeline.addStage('Build'); -jenkinsProvider.addToPipeline(buildStage, 'JenkinsBuild', { - projectName: 'JenkinsProject1', -}); -jenkinsProvider.addToPipelineAsTest(buildStage, 'JenkinsTest', { - projectName: 'JenkinsProject2', -}); -jenkinsProvider.addToPipelineAsTest(buildStage, 'JenkinsTest2', { - projectName: 'JenkinsProject3', -}); +pipeline.addStage(new codepipeline.Stage('Build', { + actions: [ + jenkinsProvider.asCodePipelineAction('JenkinsBuild', { + projectName: 'JenkinsProject1', + inputArtifact: sourceAction.outputArtifact, + }), + jenkinsProvider.asCodePipelineTestAction('JenkinsTest', { + projectName: 'JenkinsProject2', + inputArtifact: sourceAction.outputArtifact, + }), + jenkinsProvider.asCodePipelineTestAction('JenkinsTest2', { + projectName: 'JenkinsProject3', + inputArtifact: sourceAction.outputArtifact, + }), + ], +})); app.run(); diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-manual-approval.expected.json b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-manual-approval.expected.json index 6015e4df4dd50..c139fd8e92794 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-manual-approval.expected.json +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-manual-approval.expected.json @@ -93,7 +93,7 @@ "Action": "sns:Publish", "Effect": "Allow", "Resource": { - "Ref": "ManualApprovalTopicResource300641E2" + "Ref": "PipelineApproveManualApprovalTopicResourceF5A35B20" } } ], @@ -137,7 +137,7 @@ "Name": "S3", "OutputArtifacts": [ { - "Name": "Artifact_awscdkcodepipelinemanualapprovalBucketS39750AFE7" + "Name": "Artifact_S3_awscdkcodepipelinemanualapprovalBucketFF0B256C" } ], "RunOrder": 1 @@ -156,7 +156,7 @@ }, "Configuration": { "NotificationArn": { - "Ref": "ManualApprovalTopicResource300641E2" + "Ref": "PipelineApproveManualApprovalTopicResourceF5A35B20" } }, "InputArtifacts": [], @@ -180,16 +180,16 @@ "PipelineRoleDefaultPolicyC7A05455" ] }, - "ManualApprovalTopicResource300641E2": { + "PipelineApproveManualApprovalTopicResourceF5A35B20": { "Type": "AWS::SNS::Topic" }, - "ManualApprovalTopicResourceSubscriptionadamruka85gmailcomBACEE98E": { + "PipelineApproveManualApprovalTopicResourceSubscriptionadamruka85gmailcom76398FFA": { "Type": "AWS::SNS::Subscription", "Properties": { "Endpoint": "adamruka85@gmail.com", "Protocol": "email", "TopicArn": { - "Ref": "ManualApprovalTopicResource300641E2" + "Ref": "PipelineApproveManualApprovalTopicResourceF5A35B20" } } } diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-manual-approval.ts b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-manual-approval.ts index 90feeac7133df..a21294bbfbed0 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-manual-approval.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-manual-approval.ts @@ -12,15 +12,19 @@ const pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { artifactBucket: bucket, }); -const sourceStage = pipeline.addStage('Source'); -bucket.addToPipeline(sourceStage, 'S3', { +const sourceStage = new codepipeline.Stage('Source'); +sourceStage.addAction(new s3.PipelineSourceAction('S3', { + bucket, bucketKey: 'file.zip', -}); +})); -const approveStage = pipeline.addStage('Approve'); -new codepipeline.ManualApprovalAction(stack, 'ManualApproval', { - stage: approveStage, - notifyEmails: ['adamruka85@gmail.com'] -}); +const approveStage = new codepipeline.Stage('Approve'); +approveStage.addAction(new codepipeline.ManualApprovalAction('ManualApproval', { + notifyEmails: ['adamruka85@gmail.com'], +})); + +pipeline + .addStage(sourceStage) + .addStage(approveStage); app.run(); diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.action.ts b/packages/@aws-cdk/aws-codepipeline/test/test.action.ts index 33e2709e700e2..094a4ee019a30 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.action.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.action.ts @@ -9,8 +9,6 @@ import codepipeline = require('../lib'); // tslint:disable:object-literal-key-quotes -class TestAction extends actions.Action {} - export = { 'artifact bounds validation': { @@ -71,12 +69,17 @@ export = { const repo = new codecommit.Repository(stack, 'Repo', { repositoryName: 'Repo', }); - const sourceStage = pipeline.addStage('Source'); - repo.addToPipeline(sourceStage, 'CodeCommit'); + const sourceAction = repo.asCodePipelineAction('CodeCommit'); + pipeline.addStage(new codepipeline.Stage('Source').addAction(sourceAction)); const project = new codebuild.PipelineProject(stack, 'Project'); - const buildStage = pipeline.addStage('Build'); - project.addToPipeline(buildStage, 'CodeBuild'); + pipeline.addStage(new codepipeline.Stage('Build', { + actions: [ + project.asCodePipelineAction('CodeBuild', { + inputArtifact: sourceAction.outputArtifact, + }), + ], + })); expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { "Stages": [ @@ -88,7 +91,7 @@ export = { "InputArtifacts": [], "OutputArtifacts": [ { - "Name": "Artifact_RepoCodeCommit7910F5F9", + "Name": "Artifact_CodeCommit_Repo", }, ], } @@ -101,12 +104,12 @@ export = { "Name": "CodeBuild", "InputArtifacts": [ { - "Name": "Artifact_RepoCodeCommit7910F5F9", + "Name": "Artifact_CodeCommit_Repo", } ], "OutputArtifacts": [ { - "Name": "Artifact_ProjectCodeBuildE34AD2EC", + "Name": "Artifact_CodeBuild_Project", }, ], } @@ -117,21 +120,47 @@ export = { test.done(); }, + + 'the same Action cannot be added to 2 different Stages'(test: Test) { + const stack = new cdk.Stack(); + const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); + const action = new FakeAction('FakeAction'); + + const stage1 = new codepipeline.Stage('Stage1', { + actions: [ + action, + ] + }); + const stage2 = new codepipeline.Stage('Stage2').addAction(action); + + pipeline.addStage(stage1); // fine + + test.throws(() => { + pipeline.addStage(stage2); + }, /FakeAction/); + + test.done(); + }, }; function boundsValidationResult(numberOfArtifacts: number, min: number, max: number): string[] { - const stack = new cdk.Stack(); - const pipeline = new codepipeline.Pipeline(stack, 'pipeline'); - const stage = new codepipeline.Stage(stack, 'stage', { pipeline }); - const action = new TestAction(stack, 'TestAction', { - stage, - artifactBounds: actions.defaultBounds(), - category: actions.ActionCategory.Test, - provider: 'test provider' - }); const artifacts: actions.Artifact[] = []; for (let i = 0; i < numberOfArtifacts; i++) { - artifacts.push(new actions.Artifact(action, `TestArtifact${i}`)); + artifacts.push(new actions.Artifact(`TestArtifact${i}`)); } return actions.validateArtifactBounds('output', artifacts, min, max, 'testCategory', 'testProvider'); } + +class FakeAction extends actions.Action { + constructor(actionName: string) { + super(actionName, { + category: actions.ActionCategory.Source, + provider: 'SomeService', + artifactBounds: actions.defaultBounds(), + }); + } + + protected bind(_pipeline: actions.IPipeline, _parent: cdk.Construct): void { + // do nothing + } +} diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.cloudformation-pipeline-actions.ts b/packages/@aws-cdk/aws-codepipeline/test/test.cloudformation-pipeline-actions.ts index a33581084b12a..fb555a1738e5e 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.cloudformation-pipeline-actions.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.cloudformation-pipeline-actions.ts @@ -24,55 +24,51 @@ export = { /** Source! */ const repo = new Repository(stack, 'MyVeryImportantRepo', { repositoryName: 'my-very-important-repo' }); - const sourceStage = new Stage(pipeline, 'source', { pipeline }); - - const source = new PipelineSourceAction(stack, 'source', { - stage: sourceStage, + const source = new PipelineSourceAction('source', { outputArtifactName: 'SourceArtifact', repository: repo, pollForSourceChanges: true, }); + pipeline.addStage(new Stage('source').addAction(source)); /** Build! */ - const buildStage = new Stage(pipeline, 'build', { pipeline }); const buildArtifacts = new CodePipelineBuildArtifacts(); const project = new Project(stack, 'MyBuildProject', { source: new CodePipelineSource(), artifacts: buildArtifacts, }); - const buildAction = new PipelineBuildAction(stack, 'build', { - stage: buildStage, + const buildAction = new PipelineBuildAction('build', { project, inputArtifact: source.outputArtifact, outputArtifactName: "OutputYo" }); + pipeline.addStage(new Stage('build').addAction(buildAction)); /** Deploy! */ // To execute a change set - yes, you probably do need *:* 🤷‍♀️ changeSetExecRole.addToPolicy(new PolicyStatement().addAllResources().addAction("*")); - const prodStage = new Stage(stack, 'prod', { pipeline }); const stackName = 'BrelandsStack'; const changeSetName = 'MyMagicalChangeSet'; - - new PipelineCreateReplaceChangeSetAction(stack, 'BuildChangeSetProd', { - stage: prodStage, - stackName, - changeSetName, - role: changeSetExecRole, - templatePath: new ArtifactPath(buildAction.outputArtifact, 'template.yaml'), - templateConfiguration: new ArtifactPath(buildAction.outputArtifact, 'templateConfig.json'), - adminPermissions: false, - }); - - new PipelineExecuteChangeSetAction(stack, 'ExecuteChangeSetProd', { - stage: prodStage, - stackName, - changeSetName, - }); + pipeline.addStage(new Stage('prod', { + actions: [ + new PipelineCreateReplaceChangeSetAction('BuildChangeSetProd', { + stackName, + changeSetName, + role: changeSetExecRole, + templatePath: new ArtifactPath(buildAction.outputArtifact, 'template.yaml'), + templateConfiguration: new ArtifactPath(buildAction.outputArtifact, 'templateConfig.json'), + adminPermissions: false, + }), + new PipelineExecuteChangeSetAction('ExecuteChangeSetProd', { + stackName, + changeSetName, + }), + ], + })); expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { "ArtifactStore": { @@ -202,12 +198,11 @@ export = { const stack = new TestFixture(); // WHEN - new PipelineCreateUpdateStackAction(stack.deployStage, 'CreateUpdate', { - stage: stack.deployStage, + stack.deployStage.addAction(new PipelineCreateUpdateStackAction('CreateUpdate', { stackName: 'MyStack', templatePath: stack.source.outputArtifact.atPath('template.yaml'), adminPermissions: true, - }); + })); const roleId = "PipelineDeployCreateUpdateRole515CB7D4"; @@ -257,13 +252,12 @@ export = { const stack = new TestFixture(); // WHEN - new PipelineCreateUpdateStackAction(stack, 'CreateUpdate', { - stage: stack.deployStage, + stack.deployStage.addAction(new PipelineCreateUpdateStackAction('CreateUpdate', { stackName: 'MyStack', templatePath: stack.source.outputArtifact.atPath('template.yaml'), outputFileName: 'CreateResponse.json', adminPermissions: false, - }); + })); // THEN: Action has output artifacts expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { @@ -273,7 +267,7 @@ export = { "Name": "Deploy", "Actions": [ { - "OutputArtifacts": [{"Name": "DeployCreateUpdateArtifact"}], + "OutputArtifacts": [{"Name": "CreateUpdate_MyStack_Artifact"}], "Name": "CreateUpdate", }, ], @@ -289,13 +283,12 @@ export = { const stack = new TestFixture(); // WHEN - new PipelineCreateUpdateStackAction(stack, 'CreateUpdate', { - stage: stack.deployStage, + stack.deployStage.addAction(new PipelineCreateUpdateStackAction('CreateUpdate', { stackName: 'MyStack', templatePath: stack.source.outputArtifact.atPath('template.yaml'), replaceOnFailure: true, adminPermissions: false, - }); + })); // THEN: Action has output artifacts expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { @@ -323,15 +316,14 @@ export = { const stack = new TestFixture(); // WHEN - new PipelineCreateUpdateStackAction(stack, 'CreateUpdate', { - stage: stack.deployStage, + stack.deployStage.addAction(new PipelineCreateUpdateStackAction('CreateUpdate', { stackName: 'MyStack', templatePath: stack.source.outputArtifact.atPath('template.yaml'), adminPermissions: false, parameterOverrides: { RepoName: stack.repo.repositoryName } - }); + })); // THEN expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { @@ -370,16 +362,20 @@ class TestFixture extends cdk.Stack { public readonly source: PipelineSourceAction; constructor() { - super(); - - this.pipeline = new Pipeline(this, 'Pipeline'); - this.sourceStage = new Stage(this.pipeline, 'Source', { pipeline: this.pipeline }); - this.deployStage = new Stage(this.pipeline, 'Deploy', { pipeline: this.pipeline }); - this.repo = new Repository(this, 'MyVeryImportantRepo', { repositoryName: 'my-very-important-repo' }); - this.source = new PipelineSourceAction(this, 'Source', { - stage: this.sourceStage, - outputArtifactName: 'SourceArtifact', - repository: this.repo, - }); + super(); + + this.sourceStage = new Stage('Source'); + this.deployStage = new Stage('Deploy'); + this.repo = new Repository(this, 'MyVeryImportantRepo', { repositoryName: 'my-very-important-repo' }); + this.source = new PipelineSourceAction('Source', { + outputArtifactName: 'SourceArtifact', + repository: this.repo, + }); + this.pipeline = new Pipeline(this, 'Pipeline', { + stages: [ + this.sourceStage.addAction(this.source), + this.deployStage, + ], + }); } } diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.general-validation.ts b/packages/@aws-cdk/aws-codepipeline/test/test.general-validation.ts index a152b39a76579..fb786334e7c1e 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.general-validation.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.general-validation.ts @@ -37,7 +37,7 @@ export = { 'should fail if Stage has no Actions'(test: Test) { const stage = stageForTesting(); - test.deepEqual(stage.node.validateTree().length, 1); + test.deepEqual((stage as any).validate().length, 1); test.done(); } @@ -56,22 +56,18 @@ export = { 'should fail if Pipeline has a Source Action in a non-first Stage'(test: Test) { const stack = new cdk.Stack(); const pipeline = new Pipeline(stack, 'Pipeline'); - const firstStage = new Stage(stack, 'FirstStage', { pipeline }); - const secondStage = new Stage(stack, 'SecondStage', { pipeline }); const bucket = new s3.Bucket(stack, 'PipelineBucket'); - new s3.PipelineSourceAction(stack, 'FirstAction', { - stage: firstStage, + pipeline.addStage(new Stage('FirstStage').addAction(new s3.PipelineSourceAction('FirstAction', { outputArtifactName: 'FirstArtifact', bucket, bucketKey: 'key', - }); - new s3.PipelineSourceAction(stack, 'SecondAction', { - stage: secondStage, + }))); + pipeline.addStage(new Stage('SecondStage').addAction(new s3.PipelineSourceAction('SecondAction', { outputArtifactName: 'SecondAction', bucket, bucketKey: 'key', - }); + }))); test.deepEqual(pipeline.node.validateTree().length, 1); @@ -81,7 +77,5 @@ export = { }; function stageForTesting(): Stage { - const stack = new cdk.Stack(); - const pipeline = new Pipeline(stack, 'pipeline'); - return new Stage(stack, 'stage', { pipeline }); + return new Stage('stage'); } diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts b/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts index de1b69b01c5be..55d8485ec9c7a 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts @@ -20,22 +20,19 @@ export = { }); const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); - const sourceStage = new codepipeline.Stage(pipeline, 'source', { pipeline }); - const source = new codecommit.PipelineSourceAction(stack, 'source', { - stage: sourceStage, + const source = new codecommit.PipelineSourceAction('source', { outputArtifactName: 'SourceArtifact', repository, }); + pipeline.addStage(new codepipeline.Stage('source').addAction(source)); - const buildStage = new codepipeline.Stage(pipeline, 'build', { pipeline }); const project = new codebuild.Project(stack, 'MyBuildProject', { source: new codebuild.CodePipelineSource() }); - new codebuild.PipelineBuildAction(stack, 'build', { - stage: buildStage, + pipeline.addStage(new codepipeline.Stage('build').addAction(new codebuild.PipelineBuildAction('build', { inputArtifact: source.outputArtifact, project, - }); + }))); test.notDeepEqual(stack.toCloudFormation(), {}); test.deepEqual([], pipeline.node.validateTree()); @@ -49,19 +46,16 @@ export = { const p = new codepipeline.Pipeline(stack, 'P'); - const s1 = new codepipeline.Stage(stack, 'Source', { pipeline: p }); - new codepipeline.GitHubSourceAction(stack, 'GH', { - stage: s1, + p.addStage(new codepipeline.Stage('Source').addAction(new codepipeline.GitHubSourceAction('GH', { runOrder: 8, outputArtifactName: 'A', branch: 'branch', oauthToken: secret.value, owner: 'foo', repo: 'bar' - }); + }))); - const s2 = new codepipeline.Stage(stack, 'Two', { pipeline: p }); - new codepipeline.ManualApprovalAction(stack, 'Boo', { stage: s2 }); + p.addStage(new codepipeline.Stage('Two').addAction(new codepipeline.ManualApprovalAction('Boo'))); expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { "ArtifactStore": { @@ -138,16 +132,13 @@ export = { const pipeline = new codepipeline.Pipeline(stack, 'PL'); - const stage1 = new codepipeline.Stage(stack, 'S1', { pipeline }); - new s3.PipelineSourceAction(stack, 'A1', { - stage: stage1, + pipeline.addStage(new codepipeline.Stage('S1').addAction(new s3.PipelineSourceAction('A1', { outputArtifactName: 'Artifact', bucket: new s3.Bucket(stack, 'Bucket'), bucketKey: 'Key' - }); + }))); - const stage2 = new codepipeline.Stage(stack, 'S2', { pipeline }); - new codepipeline.ManualApprovalAction(stack, 'A2', { stage: stage2 }); + pipeline.addStage(new codepipeline.Stage('S2').addAction(new codepipeline.ManualApprovalAction('A2'))); pipeline.onStateChange('OnStateChange', topic, { description: 'desc', @@ -219,10 +210,10 @@ export = { 'allows passing an SNS Topic when constructing it'(test: Test) { const stack = new cdk.Stack(); const topic = new sns.Topic(stack, 'Topic'); - const manualApprovalAction = new codepipeline.ManualApprovalAction(stack, 'Approve', { - stage: stageForTesting(stack), + const manualApprovalAction = new codepipeline.ManualApprovalAction('Approve', { notificationTopic: topic, }); + stageForTesting(stack).addAction(manualApprovalAction); test.equal(manualApprovalAction.notificationTopic, topic); @@ -278,19 +269,19 @@ export = { const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); const bucket = new s3.Bucket(stack, 'Bucket'); - const sourceStage = pipeline.addStage('Source'); - const source1 = bucket.addToPipeline(sourceStage, 'SourceAction1', { + const source1 = bucket.asCodePipelineAction('SourceAction1', { bucketKey: 'some/key', outputArtifactName: 'sourceArtifact1', }); - const source2 = bucket.addToPipeline(sourceStage, 'SourceAction2', { + const source2 = bucket.asCodePipelineAction('SourceAction2', { bucketKey: 'another/key', outputArtifactName: 'sourceArtifact2', }); + pipeline.addStage(new codepipeline.Stage('Source') + .addAction(source1) + .addAction(source2)); - const stage = new codepipeline.Stage(stack, 'Stage', { pipeline }); - const lambdaAction = new lambda.PipelineInvokeAction(stack, 'InvokeAction', { - stage, + const lambdaAction = new lambda.PipelineInvokeAction('InvokeAction', { lambda: lambdaFun, userParameters: 'foo-bar/42', inputArtifacts: [ @@ -303,6 +294,7 @@ export = { 'lambdaOutput3', ], }); + pipeline.addStage(new codepipeline.Stage('Stage').addAction(lambdaAction)); expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { "ArtifactStore": { @@ -385,8 +377,7 @@ export = { 'CodeCommit Action': { 'does not poll for changes by default'(test: Test) { const stack = new cdk.Stack(); - const sourceAction = new codecommit.PipelineSourceAction(stack, 'stage', { - stage: stageForTesting(stack), + const sourceAction = new codecommit.PipelineSourceAction('stage', { outputArtifactName: 'SomeArtifact', repository: repositoryForTesting(stack), }); @@ -398,8 +389,7 @@ export = { 'does not poll for source changes when explicitly set to false'(test: Test) { const stack = new cdk.Stack(); - const sourceAction = new codecommit.PipelineSourceAction(stack, 'stage', { - stage: stageForTesting(stack), + const sourceAction = new codecommit.PipelineSourceAction('stage', { outputArtifactName: 'SomeArtifact', repository: repositoryForTesting(stack), pollForSourceChanges: false, @@ -431,33 +421,33 @@ export = { }, }); - const stage1 = pipeline.addStage('Stage1'); - const sourceAction = bucket.addToPipeline(stage1, 'BucketSource', { + const sourceAction = bucket.asCodePipelineAction('BucketSource', { bucketKey: '/some/key', }); - - const stage2 = pipeline.addStage('Stage2'); - new cloudformation.PipelineCreateReplaceChangeSetAction(stack, 'Action1', { - stage: stage2, - changeSetName: 'ChangeSet', - templatePath: sourceAction.outputArtifact.atPath('template.yaml'), - stackName: 'SomeStack', - region: pipelineRegion, - adminPermissions: false, - }); - new cloudformation.PipelineCreateUpdateStackAction(stack, 'Action2', { - stage: stage2, - templatePath: sourceAction.outputArtifact.atPath('template.yaml'), - stackName: 'OtherStack', - region: 'us-east-1', - adminPermissions: false, - }); - new cloudformation.PipelineExecuteChangeSetAction(stack, 'Action3', { - stage: stage2, - changeSetName: 'ChangeSet', - stackName: 'SomeStack', - region: 'us-west-1', - }); + pipeline.addStage(new codepipeline.Stage('Stage1').addAction(sourceAction)); + + pipeline.addStage(new codepipeline.Stage('Stage2', { + actions: [ + new cloudformation.PipelineCreateReplaceChangeSetAction('Action1', { + changeSetName: 'ChangeSet', + templatePath: sourceAction.outputArtifact.atPath('template.yaml'), + stackName: 'SomeStack', + region: pipelineRegion, + adminPermissions: false, + }), + new cloudformation.PipelineCreateUpdateStackAction('Action2', { + templatePath: sourceAction.outputArtifact.atPath('template.yaml'), + stackName: 'OtherStack', + region: 'us-east-1', + adminPermissions: false, + }), + new cloudformation.PipelineExecuteChangeSetAction('Action3', { + changeSetName: 'ChangeSet', + stackName: 'SomeStack', + region: 'us-west-1', + }), + ], + })); expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { "ArtifactStores": [ @@ -522,7 +512,9 @@ export = { function stageForTesting(stack: cdk.Stack): codepipeline.Stage { const pipeline = new codepipeline.Pipeline(stack, 'pipeline'); - return new codepipeline.Stage(pipeline, 'stage', { pipeline }); + const stage = new codepipeline.Stage('stage'); + pipeline.addStage(stage); + return stage; } function repositoryForTesting(stack: cdk.Stack): codecommit.Repository { diff --git a/packages/@aws-cdk/aws-codepipeline/test/test.stages.ts b/packages/@aws-cdk/aws-codepipeline/test/test.stages.ts index 26edc5a07a3f6..1b74e253e9ad7 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.stages.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.stages.ts @@ -11,13 +11,13 @@ export = { const stack = new cdk.Stack(); const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); - new codepipeline.Stage(stack, 'SecondStage', { pipeline }); - new codepipeline.Stage(stack, 'FirstStage', { - pipeline, - placement: { - atIndex: 0, - }, - }); + pipeline + .addStage(new codepipeline.Stage('SecondStage')) + .addStage(new codepipeline.Stage('FirstStage'), { + placement: { + atIndex: 0, + }, + }); expect(stack, true).to(haveResourceLike('AWS::CodePipeline::Pipeline', { "Stages": [ @@ -33,12 +33,14 @@ export = { const stack = new cdk.Stack(); const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); - const secondStage = pipeline.addStage('SecondStage'); - pipeline.addStage('FirstStage', { - placement: { - rightBefore: secondStage, - }, - }); + const secondStage = new codepipeline.Stage('SecondStage'); + pipeline + .addStage(secondStage) + .addStage(new codepipeline.Stage('FirstStage'), { + placement: { + rightBefore: secondStage, + }, + }); expect(stack, true).to(haveResourceLike('AWS::CodePipeline::Pipeline', { "Stages": [ @@ -54,13 +56,15 @@ export = { const stack = new cdk.Stack(); const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); - const firstStage = pipeline.addStage('FirstStage'); - pipeline.addStage('ThirdStage'); - pipeline.addStage('SecondStage', { - placement: { - justAfter: firstStage, - }, - }); + const firstStage = new codepipeline.Stage('FirstStage'); + pipeline + .addStage(firstStage) + .addStage(new codepipeline.Stage('ThirdStage')) + .addStage(new codepipeline.Stage('SecondStage'), { + placement: { + justAfter: firstStage, + }, + }); expect(stack, true).to(haveResourceLike('AWS::CodePipeline::Pipeline', { "Stages": [ @@ -78,8 +82,7 @@ export = { const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); test.throws(() => { - new codepipeline.Stage(stack, 'Stage', { - pipeline, + pipeline.addStage(new codepipeline.Stage('Stage'), { placement: { atIndex: -1, }, @@ -94,7 +97,7 @@ export = { const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); test.throws(() => { - pipeline.addStage('Stage', { + pipeline.addStage(new codepipeline.Stage('Stage'), { placement: { atIndex: 1, }, @@ -106,12 +109,11 @@ export = { "attempting to insert a Stage before a Stage that doesn't exist results in an error"(test: Test) { const stack = new cdk.Stack(); - const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); - const stage = pipeline.addStage('Stage'); + const stage = new codepipeline.Stage('Stage'); const anotherPipeline = new codepipeline.Pipeline(stack, 'AnotherPipeline'); test.throws(() => { - anotherPipeline.addStage('AnotherStage', { + anotherPipeline.addStage(new codepipeline.Stage('AnotherStage'), { placement: { rightBefore: stage, }, @@ -123,12 +125,11 @@ export = { "attempting to insert a Stage after a Stage that doesn't exist results in an error"(test: Test) { const stack = new cdk.Stack(); - const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); - const stage = pipeline.addStage('Stage'); + const stage = new codepipeline.Stage('Stage'); const anotherPipeline = new codepipeline.Pipeline(stack, 'AnotherPipeline'); test.throws(() => { - anotherPipeline.addStage('AnotherStage', { + anotherPipeline.addStage(new codepipeline.Stage('AnotherStage'), { placement: { justAfter: stage, }, @@ -141,10 +142,11 @@ export = { "providing more than one placement value results in an error"(test: Test) { const stack = new cdk.Stack(); const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); - const stage = pipeline.addStage('FirstStage'); + const stage = new codepipeline.Stage('FirstStage'); + pipeline.addStage(stage); test.throws(() => { - pipeline.addStage('SecondStage', { + pipeline.addStage(new codepipeline.Stage('SecondStage'), { placement: { rightBefore: stage, justAfter: stage, @@ -159,5 +161,19 @@ export = { test.done(); }, + + 'adding the same Stage to 2 different Pipelines results in an error'(test: Test) { + const stack = new cdk.Stack(); + const pipeline1 = new codepipeline.Pipeline(stack, 'Pipeline1'); + const pipeline2 = new codepipeline.Pipeline(stack, 'Pipeline2'); + const stage = new codepipeline.Stage('MyStage'); + + pipeline1.addStage(stage); // fine + test.throws(() => { + pipeline2.addStage(stage); + }, /MyStage/); + + test.done(); + } }, }; diff --git a/packages/@aws-cdk/aws-ecr/README.md b/packages/@aws-cdk/aws-ecr/README.md index cefec43725fbb..9fdf9b0ee32b3 100644 --- a/packages/@aws-cdk/aws-ecr/README.md +++ b/packages/@aws-cdk/aws-ecr/README.md @@ -33,18 +33,19 @@ Example: import codepipeline = require('@aws-cdk/aws-codepipeline'); const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); -const sourceStage = pipeline.addStage('Source'); -const sourceAction = new ecr.PipelineSourceAction(this, 'ECR', { - stage: sourceStage, +const sourceAction = new ecr.PipelineSourceAction('ECR', { repository: ecrRepository, imageTag: 'some-tag', // optional, default: 'latest' outputArtifactName: 'SomeName', // optional }); +pipeline.addStage(new codepipeline.Stage('Source', { + actions: [sourceAction], +})); ``` You can also add the Repository to the Pipeline directly: ```ts // equivalent to the code above: -const sourceAction = ecrRepository.addToPipeline(sourceStage, 'ECR'); +const sourceAction = ecrRepository.asCodePipelineAction('ECR'); ``` diff --git a/packages/@aws-cdk/aws-ecr/lib/pipeline-action.ts b/packages/@aws-cdk/aws-ecr/lib/pipeline-action.ts index 4ccb694afc95a..86773ce61f2f4 100644 --- a/packages/@aws-cdk/aws-ecr/lib/pipeline-action.ts +++ b/packages/@aws-cdk/aws-ecr/lib/pipeline-action.ts @@ -6,7 +6,7 @@ import { IRepository } from './repository-ref'; /** * Common properties for the {@link PipelineSourceAction CodePipeline source Action}, * whether creating it directly, - * or through the {@link IRepository#addToPipeline} method. + * or through the {@link IRepository#asCodePipelineAction} method. */ export interface CommonPipelineSourceActionProps extends codepipeline.CommonActionProps { /** @@ -28,8 +28,7 @@ export interface CommonPipelineSourceActionProps extends codepipeline.CommonActi /** * Construction properties of {@link PipelineSourceAction}. */ -export interface PipelineSourceActionProps extends CommonPipelineSourceActionProps, - codepipeline.CommonActionConstructProps { +export interface PipelineSourceActionProps extends CommonPipelineSourceActionProps { /** * The repository that will be watched for changes. */ @@ -40,23 +39,30 @@ export interface PipelineSourceActionProps extends CommonPipelineSourceActionPro * The ECR Repository source CodePipeline Action. */ export class PipelineSourceAction extends codepipeline.SourceAction { - constructor(scope: cdk.Construct, id: string, props: PipelineSourceActionProps) { - super(scope, id, { + private readonly props: PipelineSourceActionProps; + + constructor(actionName: string, props: PipelineSourceActionProps) { + super(actionName, { provider: 'ECR', configuration: { RepositoryName: props.repository.repositoryName, ImageTag: props.imageTag, }, + outputArtifactName: props.outputArtifactName || `Artifact_${actionName}_${props.repository.node.uniqueId}`, ...props, }); - props.stage.pipeline.role.addToPolicy(new iam.PolicyStatement() + this.props = props; + } + + protected bind(pipeline: codepipeline.IPipeline, _parent: cdk.Construct): void { + pipeline.role.addToPolicy(new iam.PolicyStatement() .addActions( 'ecr:DescribeImages', ) - .addResource(props.repository.repositoryArn)); + .addResource(this.props.repository.repositoryArn)); - props.repository.onImagePushed(props.stage.pipeline.node.uniqueId + 'SourceEventRule', - props.stage.pipeline, props.imageTag); + this.props.repository.onImagePushed(pipeline.node.uniqueId + 'SourceEventRule', + pipeline, this.props.imageTag); } } diff --git a/packages/@aws-cdk/aws-ecr/lib/repository-ref.ts b/packages/@aws-cdk/aws-ecr/lib/repository-ref.ts index ad7e8870f7ac1..98e893e212c5c 100644 --- a/packages/@aws-cdk/aws-ecr/lib/repository-ref.ts +++ b/packages/@aws-cdk/aws-ecr/lib/repository-ref.ts @@ -1,4 +1,3 @@ -import codepipeline = require('@aws-cdk/aws-codepipeline-api'); import events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); @@ -41,15 +40,13 @@ export interface IRepository extends cdk.IConstruct { addToResourcePolicy(statement: iam.PolicyStatement): void; /** - * Convenience method for creating a new {@link PipelineSourceAction}, - * and adding it to the given Stage. + * Convenience method for creating a new {@link PipelineSourceAction}. * - * @param stage the Pipeline Stage to add the new Action to - * @param name the name of the newly created Action + * @param actionName the name of the newly created Action * @param props the optional construction properties of the new Action * @returns the newly created {@link PipelineSourceAction} */ - addToPipeline(stage: codepipeline.IStage, name: string, props?: CommonPipelineSourceActionProps): + asCodePipelineAction(actionName: string, props?: CommonPipelineSourceActionProps): PipelineSourceAction; /** @@ -173,10 +170,8 @@ export abstract class RepositoryBase extends cdk.Construct implements IRepositor */ public abstract export(): RepositoryImportProps; - public addToPipeline(stage: codepipeline.IStage, name: string, props: CommonPipelineSourceActionProps = {}): - PipelineSourceAction { - return new PipelineSourceAction(this, name, { - stage, + public asCodePipelineAction(actionName: string, props: CommonPipelineSourceActionProps = {}): PipelineSourceAction { + return new PipelineSourceAction(actionName, { repository: this, ...props, }); diff --git a/packages/@aws-cdk/aws-ecr/package.json b/packages/@aws-cdk/aws-ecr/package.json index 3293a5b602b2e..2abc318139a71 100644 --- a/packages/@aws-cdk/aws-ecr/package.json +++ b/packages/@aws-cdk/aws-ecr/package.json @@ -41,6 +41,9 @@ "cdk-build": { "cloudformation": "AWS::ECR" }, + "nyc": { + "lines": 79 + }, "keywords": [ "aws", "cdk", @@ -81,4 +84,4 @@ "import:@aws-cdk/aws-ecr.Repository" ] } -} \ No newline at end of file +} diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index 7e7c7ad76ad66..be558cad7cf90 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -73,25 +73,26 @@ This module also contains an Action that allows you to invoke a Lambda function import codepipeline = require('@aws-cdk/aws-codepipeline'); const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); -const lambdaStage = pipeline.addStage('Lambda'); -new lambda.PipelineInvokeAction(this, 'Lambda', { - stage: lambdaStage, - lambda: fn, +const lambdaAction = new lambda.PipelineInvokeAction('Lambda', { + lambda: fn, }); +pipeline.addStage(new codepipeline.Stage('Lambda', { + actions: [lambdaAction], +})); ``` You can also add the Lambda to the Pipeline directly: ```ts // equivalent to the code above: -fn.addToPipeline(lambdaStage, 'Lambda'); +const lambdaAction = fn.asCodePipelineAction('Lambda'); ``` The Lambda Action can have up to 5 inputs, and up to 5 outputs: ```typescript -const lambdaAction = fn.addToPipeline(lambdaStage, 'Lambda', { +const lambdaAction = fn.asCodePipelineAction('Lambda', { inputArtifacts: [ sourceAction.outputArtifact, buildAction.outputArtifact, diff --git a/packages/@aws-cdk/aws-lambda/lib/lambda-ref.ts b/packages/@aws-cdk/aws-lambda/lib/lambda-ref.ts index 8c241d5428af6..02e3ad2435cde 100644 --- a/packages/@aws-cdk/aws-lambda/lib/lambda-ref.ts +++ b/packages/@aws-cdk/aws-lambda/lib/lambda-ref.ts @@ -1,5 +1,4 @@ import cloudwatch = require('@aws-cdk/aws-cloudwatch'); -import codepipeline = require('@aws-cdk/aws-codepipeline-api'); import ec2 = require('@aws-cdk/aws-ec2'); import events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); @@ -49,15 +48,13 @@ export interface IFunction extends cdk.IConstruct, events.IEventRuleTarget, logs addPermission(id: string, permission: Permission): void; /** - * Convenience method for creating a new {@link PipelineInvokeAction}, - * and adding it to the given Stage. + * Convenience method for creating a new {@link PipelineInvokeAction}. * - * @param stage the Pipeline Stage to add the new Action to - * @param name the name of the newly created Action + * @param actionName the name of the newly created Action * @param props the properties of the new Action * @returns the newly created {@link PipelineInvokeAction} */ - addToPipeline(stage: codepipeline.IStage, name: string, props?: CommonPipelineInvokeActionProps): PipelineInvokeAction; + asCodePipelineAction(actionName: string, props?: CommonPipelineInvokeActionProps): PipelineInvokeAction; addToRolePolicy(statement: iam.PolicyStatement): void; @@ -197,18 +194,8 @@ export abstract class FunctionBase extends cdk.Construct implements IFunction { return this.node.id; } - /** - * Convenience method for creating a new {@link PipelineInvokeAction}, - * and adding it to the given Stage. - * - * @param stage the Pipeline Stage to add the new Action to - * @param name the name of the newly created Action - * @param props the properties of the new Action - * @returns the newly created {@link PipelineInvokeAction} - */ - public addToPipeline(stage: codepipeline.IStage, name: string, props: CommonPipelineInvokeActionProps = {}): PipelineInvokeAction { - return new PipelineInvokeAction(this, name, { - stage, + public asCodePipelineAction(actionName: string, props: CommonPipelineInvokeActionProps = {}): PipelineInvokeAction { + return new PipelineInvokeAction(actionName, { lambda: this, ...props, }); diff --git a/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts b/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts index ac14ea505feb0..094ca4ac6087a 100644 --- a/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts +++ b/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts @@ -6,7 +6,7 @@ import { IFunction } from './lambda-ref'; /** * Common properties for creating a {@link PipelineInvokeAction} - * either directly, through its constructor, - * or through {@link IFunction#addToPipeline}. + * or through {@link IFunction#asCodePipelineAction}. */ export interface CommonPipelineInvokeActionProps extends codepipeline.CommonActionProps { // because of @see links @@ -67,8 +67,7 @@ export interface CommonPipelineInvokeActionProps extends codepipeline.CommonActi /** * Construction properties of the {@link PipelineInvokeAction Lambda invoke CodePipeline Action}. */ -export interface PipelineInvokeActionProps extends CommonPipelineInvokeActionProps, - codepipeline.CommonActionConstructProps { +export interface PipelineInvokeActionProps extends CommonPipelineInvokeActionProps { /** * The lambda function to invoke. */ @@ -81,17 +80,18 @@ export interface PipelineInvokeActionProps extends CommonPipelineInvokeActionPro * @see https://docs.aws.amazon.com/codepipeline/latest/userguide/actions-invoke-lambda-function.html */ export class PipelineInvokeAction extends codepipeline.Action { - constructor(scope: cdk.Construct, id: string, props: PipelineInvokeActionProps) { - super(scope, id, { - stage: props.stage, - runOrder: props.runOrder, + private readonly props: PipelineInvokeActionProps; + + constructor(actionName: string, props: PipelineInvokeActionProps) { + super(actionName, { category: codepipeline.ActionCategory.Invoke, provider: 'Lambda', artifactBounds: codepipeline.defaultBounds(), configuration: { FunctionName: props.lambda.functionName, UserParameters: props.userParameters - } + }, + ...props, }); // handle input artifacts @@ -104,36 +104,40 @@ export class PipelineInvokeAction extends codepipeline.Action { this.addOutputArtifact(outputArtifactName); } + this.props = props; + } + + public outputArtifacts(): codepipeline.Artifact[] { + return this._outputArtifacts; + } + + public outputArtifact(artifactName: string): codepipeline.Artifact { + const result = this._outputArtifacts.find(a => (a.artifactName === artifactName)); + if (result === undefined) { + throw new Error(`Could not find the output Artifact with name '${artifactName}'`); + } else { + return result; + } + } + + protected bind(pipeline: codepipeline.IPipeline, _parent: cdk.Construct): void { // allow pipeline to list functions - props.stage.pipeline.role.addToPolicy(new iam.PolicyStatement() + pipeline.role.addToPolicy(new iam.PolicyStatement() .addAction('lambda:ListFunctions') .addAllResources()); // allow pipeline to invoke this lambda functionn - props.stage.pipeline.role.addToPolicy(new iam.PolicyStatement() + pipeline.role.addToPolicy(new iam.PolicyStatement() .addAction('lambda:InvokeFunction') - .addResource(props.lambda.functionArn)); + .addResource(this.props.lambda.functionArn)); // allow lambda to put job results for this pipeline. - const addToPolicy = props.addPutJobResultPolicy !== undefined ? props.addPutJobResultPolicy : true; + const addToPolicy = this.props.addPutJobResultPolicy !== undefined ? this.props.addPutJobResultPolicy : true; if (addToPolicy) { - props.lambda.addToRolePolicy(new iam.PolicyStatement() + this.props.lambda.addToRolePolicy(new iam.PolicyStatement() .addAllResources() // to avoid cycles (see docs) .addAction('codepipeline:PutJobSuccessResult') .addAction('codepipeline:PutJobFailureResult')); } } - - public outputArtifacts(): codepipeline.Artifact[] { - return this._outputArtifacts; - } - - public outputArtifact(artifactName: string): codepipeline.Artifact { - const result = this._outputArtifacts.find(a => (a.name === artifactName)); - if (result === undefined) { - throw new Error(`Could not find the output Artifact with name '${artifactName}'`); - } else { - return result; - } - } } diff --git a/packages/@aws-cdk/aws-s3/README.md b/packages/@aws-cdk/aws-s3/README.md index c70f58d600df9..61b0942549e49 100644 --- a/packages/@aws-cdk/aws-s3/README.md +++ b/packages/@aws-cdk/aws-s3/README.md @@ -95,19 +95,20 @@ const sourceBucket = new s3.Bucket(this, 'MyBucket', { }); const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); -const sourceStage = pipeline.addStage('Source'); -const sourceAction = new s3.PipelineSourceAction(this, 'S3Source', { - stage: sourceStage, +const sourceAction = new s3.PipelineSourceAction('S3Source', { bucket: sourceBucket, bucketKey: 'path/to/file.zip', }); +pipeline.addStage(new codepipeline.Stage('Source', { + actions: [sourceAction], +})); ``` You can also add the Bucket to the Pipeline directly: ```ts // equivalent to the code above: -const sourceAction = sourceBucket.addToPipeline(sourceStage, 'S3Source', { +const sourceAction = sourceBucket.asCodePipelineAction('S3Source', { bucketKey: 'path/to/file.zip', }); ``` diff --git a/packages/@aws-cdk/aws-s3/lib/bucket.ts b/packages/@aws-cdk/aws-s3/lib/bucket.ts index 1154ba5139a32..239150f62d74c 100644 --- a/packages/@aws-cdk/aws-s3/lib/bucket.ts +++ b/packages/@aws-cdk/aws-s3/lib/bucket.ts @@ -1,4 +1,3 @@ -import actions = require('@aws-cdk/aws-codepipeline-api'); import iam = require('@aws-cdk/aws-iam'); import kms = require('@aws-cdk/aws-kms'); import { IBucketNotificationDestination } from '@aws-cdk/aws-s3-notifications'; @@ -53,15 +52,13 @@ export interface IBucket extends cdk.IConstruct { export(): BucketImportProps; /** - * Convenience method for creating a new {@link PipelineSourceAction}, - * and adding it to the given Stage. + * Convenience method for creating a new {@link PipelineSourceAction}. * - * @param stage the Pipeline Stage to add the new Action to - * @param name the name of the newly created Action + * @param actionName the name of the newly created Action * @param props the properties of the new Action * @returns the newly created {@link PipelineSourceAction} */ - addToPipeline(stage: actions.IStage, name: string, props: CommonPipelineSourceActionProps): PipelineSourceAction; + asCodePipelineAction(actionName: string, props: CommonPipelineSourceActionProps): PipelineSourceAction; /** * Adds a statement to the resource policy for a principal (i.e. @@ -273,18 +270,8 @@ export abstract class BucketBase extends cdk.Construct implements IBucket { */ public abstract export(): BucketImportProps; - /** - * Convenience method for creating a new {@link PipelineSourceAction}, - * and adding it to the given Stage. - * - * @param stage the Pipeline Stage to add the new Action to - * @param name the name of the newly created Action - * @param props the properties of the new Action - * @returns the newly created {@link PipelineSourceAction} - */ - public addToPipeline(stage: actions.IStage, name: string, props: CommonPipelineSourceActionProps): PipelineSourceAction { - return new PipelineSourceAction(this, name, { - stage, + public asCodePipelineAction(actionName: string, props: CommonPipelineSourceActionProps): PipelineSourceAction { + return new PipelineSourceAction(actionName, { bucket: this, ...props, }); diff --git a/packages/@aws-cdk/aws-s3/lib/pipeline-action.ts b/packages/@aws-cdk/aws-s3/lib/pipeline-action.ts index 5fb5ca522547c..801798872de26 100644 --- a/packages/@aws-cdk/aws-s3/lib/pipeline-action.ts +++ b/packages/@aws-cdk/aws-s3/lib/pipeline-action.ts @@ -5,7 +5,7 @@ import { IBucket } from './bucket'; /** * Common properties for creating {@link PipelineSourceAction} - * either directly, through its constructor, - * or through {@link IBucket#addToPipeline}. + * or through {@link IBucket#asCodePipelineAction}. */ export interface CommonPipelineSourceActionProps extends codepipeline.CommonActionProps { /** @@ -35,8 +35,7 @@ export interface CommonPipelineSourceActionProps extends codepipeline.CommonActi /** * Construction properties of the {@link PipelineSourceAction S3 source Action}. */ -export interface PipelineSourceActionProps extends CommonPipelineSourceActionProps, - codepipeline.CommonActionConstructProps { +export interface PipelineSourceActionProps extends CommonPipelineSourceActionProps { /** * The Amazon S3 bucket that stores the source code */ @@ -47,9 +46,12 @@ export interface PipelineSourceActionProps extends CommonPipelineSourceActionPro * Source that is provided by a specific Amazon S3 object. */ export class PipelineSourceAction extends codepipeline.SourceAction { - constructor(scope: cdk.Construct, id: string, props: PipelineSourceActionProps) { - super(scope, id, { + private readonly bucket: IBucket; + + constructor(actionName: string, props: PipelineSourceActionProps) { + super(actionName, { provider: 'S3', + outputArtifactName: props.outputArtifactName || `Artifact_${actionName}_${props.bucket.node.uniqueId}`, configuration: { S3Bucket: props.bucket.bucketName, S3ObjectKey: props.bucketKey, @@ -58,7 +60,11 @@ export class PipelineSourceAction extends codepipeline.SourceAction { ...props, }); + this.bucket = props.bucket; + } + + protected bind(pipeline: codepipeline.IPipeline, _parent: cdk.Construct): void { // pipeline needs permissions to read from the S3 bucket - props.bucket.grantRead(props.stage.pipeline.role); + this.bucket.grantRead(pipeline.role); } }