From 2a3dfb5e7312866a5a0dcbd38fcc4e0eb0772083 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Thu, 10 Jan 2019 14:04:59 -0800 Subject: [PATCH] feat: re-structure the CodePipeline Construct library API. --- packages/@aws-cdk/alexa-ask/README.md | 24 +-- .../alexa-ask/lib/pipeline-actions.ts | 15 +- packages/@aws-cdk/app-delivery/README.md | 41 ++-- .../lib/pipeline-deploy-stack-action.ts | 19 +- .../test/integ.cicd.expected.json | 6 +- .../@aws-cdk/app-delivery/test/integ.cicd.ts | 11 +- .../test/test.pipeline-deploy-stack-action.ts | 76 +++++--- .../lib/pipeline-actions.ts | 121 ++++++++---- .../test/test.pipeline-actions.ts | 162 +++++++++------- packages/@aws-cdk/aws-codebuild/README.md | 79 ++++---- .../aws-codebuild/lib/pipeline-actions.ts | 53 ++--- .../@aws-cdk/aws-codebuild/lib/project.ts | 55 ++---- packages/@aws-cdk/aws-codecommit/README.md | 17 +- .../aws-codecommit/lib/pipeline-action.ts | 26 ++- .../@aws-cdk/aws-codecommit/lib/repository.ts | 26 +-- packages/@aws-cdk/aws-codecommit/package.json | 2 +- packages/@aws-cdk/aws-codedeploy/README.md | 21 +- .../aws-codedeploy/lib/pipeline-action.ts | 35 ++-- .../lib/server/deployment-group.ts | 25 ++- packages/@aws-cdk/aws-codedeploy/package.json | 3 + .../aws-codepipeline-api/lib/action.ts | 134 ++++++------- .../aws-codepipeline-api/lib/artifact.ts | 18 +- .../aws-codepipeline-api/lib/build-action.ts | 15 +- .../aws-codepipeline-api/lib/deploy-action.ts | 13 +- .../aws-codepipeline-api/lib/source-action.ts | 15 +- .../aws-codepipeline-api/lib/test-action.ts | 15 +- packages/@aws-cdk/aws-codepipeline/README.md | 70 ++++--- .../lib/github-source-action.ts | 27 +-- .../@aws-cdk/aws-codepipeline/lib/index.ts | 1 - .../aws-codepipeline/lib/jenkins-actions.ts | 43 +++-- .../aws-codepipeline/lib/jenkins-provider.ts | 32 ++-- .../lib/manual-approval-action.ts | 46 +++-- .../@aws-cdk/aws-codepipeline/lib/pipeline.ts | 181 +++++++++--------- .../@aws-cdk/aws-codepipeline/lib/stage.ts | 148 +++++--------- .../test/integ.cfn-template-from-repo.lit.ts | 52 ++--- .../test/integ.lambda-pipeline.ts | 12 +- .../test/integ.pipeline-alexa-deploy.ts | 39 ++-- ...eg.pipeline-cfn-cross-region.expected.json | 12 +- .../test/integ.pipeline-cfn-cross-region.ts | 35 ++-- ...ipeline-cfn-wtih-action-role.expected.json | 12 +- .../integ.pipeline-cfn-wtih-action-role.ts | 39 ++-- .../test/integ.pipeline-cfn.ts | 45 +++-- ...uild-multiple-inputs-outputs.expected.json | 23 ++- ...line-code-build-multiple-inputs-outputs.ts | 27 ++- ...g.pipeline-code-commit-build.expected.json | 4 +- .../test/integ.pipeline-code-commit-build.ts | 35 +++- .../test/integ.pipeline-code-commit.ts | 28 ++- .../test/integ.pipeline-code-deploy.ts | 13 +- .../integ.pipeline-ecr-source.expected.json | 2 +- .../test/integ.pipeline-ecr-source.ts | 10 +- .../test/integ.pipeline-events.expected.json | 8 +- .../test/integ.pipeline-events.ts | 24 ++- .../test/integ.pipeline-jenkins.expected.json | 10 +- .../test/integ.pipeline-jenkins.ts | 36 ++-- ...teg.pipeline-manual-approval.expected.json | 12 +- .../test/integ.pipeline-manual-approval.ts | 34 ++-- .../test/integ.pipeline-s3-deploy.ts | 26 ++- .../aws-codepipeline/test/test.action.ts | 77 ++++++-- .../test.cloudformation-pipeline-actions.ts | 130 +++++++------ .../test/test.general-validation.ts | 32 ++-- .../aws-codepipeline/test/test.pipeline.ts | 172 ++++++++++------- .../aws-codepipeline/test/test.stages.ts | 40 ++-- packages/@aws-cdk/aws-ecr/README.md | 13 +- .../@aws-cdk/aws-ecr/lib/pipeline-action.ts | 26 ++- .../@aws-cdk/aws-ecr/lib/repository-ref.ts | 18 +- packages/@aws-cdk/aws-ecr/package.json | 4 + packages/@aws-cdk/aws-lambda/README.md | 18 +- .../@aws-cdk/aws-lambda/lib/function-base.ts | 26 +-- .../aws-lambda/lib/pipeline-action.ts | 51 ++--- packages/@aws-cdk/aws-s3/README.md | 51 +++-- packages/@aws-cdk/aws-s3/lib/bucket.ts | 42 ++-- .../@aws-cdk/aws-s3/lib/pipeline-actions.ts | 47 +++-- 72 files changed, 1590 insertions(+), 1270 deletions(-) diff --git a/packages/@aws-cdk/alexa-ask/README.md b/packages/@aws-cdk/alexa-ask/README.md index 9e3754166cf5d..ce3247e40160e 100644 --- a/packages/@aws-cdk/alexa-ask/README.md +++ b/packages/@aws-cdk/alexa-ask/README.md @@ -10,13 +10,13 @@ You can deploy to Alexa using CodePipeline with the following DeployAction. ```ts // Read the secrets from ParameterStore -const clientId = new cdk.SecretParameter(stack, 'AlexaClientId', {ssmParameter: '/Alexa/ClientId'}); -const clientSecret = new cdk.SecretParameter(stack, 'AlexaClientSecret', {ssmParameter: '/Alexa/ClientSecret'}); -const refreshToken = new cdk.SecretParameter(stack, 'AlexaRefreshToken', {ssmParameter: '/Alexa/RefreshToken'}); +const clientId = new cdk.SecretParameter(this, 'AlexaClientId', { ssmParameter: '/Alexa/ClientId' }); +const clientSecret = new cdk.SecretParameter(this, 'AlexaClientSecret', { ssmParameter: '/Alexa/ClientSecret' }); +const refreshToken = new cdk.SecretParameter(this, 'AlexaRefreshToken', { ssmParameter: '/Alexa/RefreshToken' }); // Add deploy action -new alexa.AlexaSkillDeployAction(stack, 'DeploySkill', { - stage: deployStage, +new alexaAsk.AlexaSkillDeployAction({ + actionName: 'DeploySkill', runOrder: 1, inputArtifact: sourceAction.outputArtifact, clientId: clientId.value, @@ -26,12 +26,14 @@ new alexa.AlexaSkillDeployAction(stack, 'DeploySkill', { }); ``` -If you need manifest overrides you can specify them as `overrideArtifact` in the action. +If you need manifest overrides you can specify them as `parameterOverridesArtifact` in the action: ```ts +const cloudformation = require('@aws-cdk/aws-cloudformation'); + // Deploy some CFN change set and store output -const executeChangeSetAction = new PipelineExecuteChangeSetAction(this, 'ExecuteChangesTest', { - stage: deployStage, +const executeChangeSetAction = new cloudformation.PipelineExecuteChangeSetAction({ + actionName: 'ExecuteChangesTest', runOrder: 2, stackName, changeSetName, @@ -40,8 +42,8 @@ const executeChangeSetAction = new PipelineExecuteChangeSetAction(this, 'Execute }); // Provide CFN output as manifest overrides -new AlexaSkillDeployAction(this, 'DeploySkill', { - stage: deployStage, +new alexaAsk.AlexaSkillDeployAction({ + actionName: 'DeploySkill', runOrder: 1, inputArtifact: sourceAction.outputArtifact, parameterOverridesArtifact: executeChangeSetAction.outputArtifact, @@ -50,4 +52,4 @@ new AlexaSkillDeployAction(this, 'DeploySkill', { refreshToken: refreshToken.value, skillId: 'amzn1.ask.skill.12345678-1234-1234-1234-123456789012', }); -``` \ No newline at end of file +``` diff --git a/packages/@aws-cdk/alexa-ask/lib/pipeline-actions.ts b/packages/@aws-cdk/alexa-ask/lib/pipeline-actions.ts index e74b267d75221..83d1b5f20738d 100644 --- a/packages/@aws-cdk/alexa-ask/lib/pipeline-actions.ts +++ b/packages/@aws-cdk/alexa-ask/lib/pipeline-actions.ts @@ -4,9 +4,7 @@ import cdk = require('@aws-cdk/cdk'); /** * Construction properties of the {@link AlexaSkillDeployAction Alexa deploy Action}. */ -export interface AlexaSkillDeployActionProps extends codepipeline.CommonActionProps, - codepipeline.CommonActionConstructProps { - +export interface AlexaSkillDeployActionProps extends codepipeline.CommonActionProps { /** * The client id of the developer console token */ @@ -30,7 +28,7 @@ export interface AlexaSkillDeployActionProps extends codepipeline.CommonActionPr /** * The source artifact containing the voice model and skill manifest */ - inputArtifact?: codepipeline.Artifact; + inputArtifact: codepipeline.Artifact; /** * An optional artifact containing overrides for the skill manifest @@ -42,8 +40,8 @@ export interface AlexaSkillDeployActionProps extends codepipeline.CommonActionPr * Deploys the skill to Alexa */ export class AlexaSkillDeployAction extends codepipeline.DeployAction { - constructor(scope: cdk.Construct, id: string, props: AlexaSkillDeployActionProps) { - super(scope, id, { + constructor(props: AlexaSkillDeployActionProps) { + super({ ...props, artifactBounds: { minInputs: 1, @@ -60,8 +58,13 @@ export class AlexaSkillDeployAction extends codepipeline.DeployAction { SkillId: props.skillId, }, }); + if (props.parameterOverridesArtifact) { this.addInputArtifact(props.parameterOverridesArtifact); } } + + protected bind(_stage: codepipeline.IStage, _scope: cdk.Construct): void { + // nothing to do + } } diff --git a/packages/@aws-cdk/app-delivery/README.md b/packages/@aws-cdk/app-delivery/README.md index 66a3f30947573..a7e877b954f25 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,26 +46,39 @@ 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({ + actionName: 'GitHub', /* ... */ }); +pipeline.addStage({ + name: 'source', + actions: [source], +}); + const project = new codebuild.PipelineProject(pipelineStack, 'CodeBuild', { /** - * Choose an environment configuration that meets your use case. For NodeJS - * this might be + * Choose an environment configuration that meets your use case. + * For NodeJS, this might be: + * * environment: { - * buildImage: codebuild.LinuxBuildImage.UBUNTU_14_04_NODEJS_10_1_0, + * buildImage: codebuild.LinuxBuildImage.UBUNTU_14_04_NODEJS_10_1_0, * }, */ }); -const buildStage = pipeline.addStage('build'); -const buildAction = project.addToPipeline(buildStage, 'CodeBuild'); +const buildAction = project.toCodePipelineBuildAction({ + actionName: 'CodeBuild', + inputArtifact: source.outputArtifact, +}); +pipeline.addStage({ + name: 'build', + actions: [buildAction], +}); const synthesizedApp = buildAction.outputArtifact; // Optionally, self-update the pipeline stack -const selfUpdateStage = pipeline.addStage('SelfUpdate'); +const selfUpdateStage = pipeline.addStage({ name: 'SelfUpdate' }); new cicd.PipelineDeployStackAction(pipelineStack, 'SelfUpdatePipeline', { stage: selfUpdateStage, stack: pipelineStack, @@ -73,9 +86,8 @@ new cicd.PipelineDeployStackAction(pipelineStack, 'SelfUpdatePipeline', { }); // Now add our service stacks -const deployStage = pipeline.addStage('Deploy'); +const deployStage = pipeline.addStage({ name: 'Deploy' }); 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 +96,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 +106,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 e5fd317df35a6..15b7d823072a0 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,25 @@ 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({ + actionName: 'ChangeSet', changeSetName, runOrder: createChangeSetRunOrder, stackName: props.stack.name, - stage: props.stage, templatePath: props.inputArtifact.atPath(`${props.stack.name}.template.yaml`), adminPermissions: props.adminPermissions, deploymentRole: props.role, capabilities, }); - this.deploymentRole = changeSetAction.deploymentRole; - - new cfn.PipelineExecuteChangeSetAction(this, 'Execute', { + props.stage.addAction(changeSetAction); + props.stage.addAction(new cfn.PipelineExecuteChangeSetAction({ + actionName: 'Execute', changeSetName, runOrder: executeChangeSetRunOrder, stackName: props.stack.name, - stage: props.stage, - }); + })); + + this.deploymentRole = changeSetAction.deploymentRole; } /** 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 3dfdea3ef5319..fc36956ba780d 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 @@ "CodePipelineRoleB3A660B4" ] }, - "DeployStackChangeSetRole4923A126": { + "CodePipelineDeployChangeSetRoleF9F2B343": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { diff --git a/packages/@aws-cdk/app-delivery/test/integ.cicd.ts b/packages/@aws-cdk/app-delivery/test/integ.cicd.ts index 48335409f9b5c..261cd071307b0 100644 --- a/packages/@aws-cdk/app-delivery/test/integ.cicd.ts +++ b/packages/@aws-cdk/app-delivery/test/integ.cicd.ts @@ -12,14 +12,19 @@ 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({ + actionName: 'GitHub', owner: 'awslabs', repo: 'aws-cdk', oauthToken: new cdk.Secret('DummyToken'), pollForSourceChanges: true, + outputArtifactName: 'Artifact_CICDGitHubF8BA7ADD', }); -const stage = pipeline.addStage('Deploy'); +pipeline.addStage({ + name: 'Source', + actions: [source], +}); +const stage = pipeline.addStage({ name: 'Deploy' }); 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 63a6a9089457b..b72c64234feb2 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,16 @@ 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({ + name: '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: pipeline.addStage({ name: 'DeployStage' }), adminPermissions: false, }); }, 'Cross-environment deployment is not supported'); @@ -54,14 +58,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({ + name: '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: pipeline.addStage({ name: 'DeployStage' }), adminPermissions: false, }); }, 'createChangeSetRunOrder must be < executeChangeSetRunOrder'); @@ -81,23 +89,26 @@ export = nodeunit.testCase({ const selfUpdatingStack = createSelfUpdatingStack(pipelineStack); const pipeline = selfUpdatingStack.pipeline; - const selfUpdateStage = pipeline.addStage('SelfUpdate'); + const selfUpdateStage1 = pipeline.addStage({ name: 'SelfUpdate1' }); + const selfUpdateStage2 = pipeline.addStage({ name: 'SelfUpdate2' }); + const selfUpdateStage3 = pipeline.addStage({ name: 'SelfUpdate3' }); + 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, @@ -144,7 +155,7 @@ export = nodeunit.testCase({ const selfUpdatingStack = createSelfUpdatingStack(pipelineStack); const pipeline = selfUpdatingStack.pipeline; - const selfUpdateStage = pipeline.addStage('SelfUpdate'); + const selfUpdateStage = pipeline.addStage({ name: 'SelfUpdate' }); new PipelineDeployStackAction(pipelineStack, 'SelfUpdatePipeline', { stage: selfUpdateStage, stack: pipelineStack, @@ -176,11 +187,11 @@ 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 = pipeline.addStage({ name: 'SelfUpdate' }); const deployAction = new PipelineDeployStackAction(pipelineStack, 'SelfUpdatePipeline', { stage: selfUpdateStage, stack: pipelineStack, @@ -203,7 +214,7 @@ export = nodeunit.testCase({ // WHEN // // this our app/service/infra to deploy - const deployStage = pipeline.addStage('Deploy'); + const deployStage = pipeline.addStage({ name: 'Deploy' }); const deployAction = new PipelineDeployStackAction(pipelineStack, 'DeployServiceStackA', { stage: deployStage, stack: emptyStack, @@ -248,7 +259,7 @@ export = nodeunit.testCase({ }, Roles: [ { - Ref: 'DeployServiceStackAChangeSetRoleA1245536', + Ref: 'CodePipelineDeployChangeSetRoleF9F2B343', }, ], })); @@ -262,13 +273,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({ + name: 'FakeStage', + actions: [fakeAction], + }); const deployedStack = new cdk.Stack(app, 'DeployedStack'); + const deployStage = pipeline.addStage({ name: '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 +302,19 @@ 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(_stage: api.IStage, _scope: cdk.Construct): void { + // do nothing } } @@ -309,15 +329,25 @@ 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({ + actionName: 'S3Source', bucket, bucketKey: 'the-great-key', - stage: pipeline.addStage('source'), + }); + pipeline.addStage({ + name: 'source', + actions: [sourceAction], }); const project = new codebuild.PipelineProject(pipelineStack, 'CodeBuild'); - const buildStage = pipeline.addStage('build'); - const buildAction = project.addToPipeline(buildStage, 'CodeBuild'); + const buildAction = project.toCodePipelineBuildAction({ + actionName: 'CodeBuild', + inputArtifact: sourceAction.outputArtifact, + }); + pipeline.addStage({ + name: '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 5c690a9ceee62..ea5e9c91772b1 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 */ @@ -66,8 +65,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, { + constructor(props: PipelineCloudFormationActionProps, configuration?: any) { + super({ ...props, region: props.region, artifactBounds: { @@ -87,7 +86,7 @@ export abstract class PipelineCloudFormationAction extends codepipeline.Action { if (props.outputFileName) { this.outputArtifact = this.addOutputArtifact(props.outputArtifactName || - (props.stage.name + this.node.id + 'Artifact')); + (`${props.actionName}_${props.stackName}_Artifact`)); } } } @@ -106,14 +105,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(props: PipelineExecuteChangeSetActionProps) { + super(props, { ActionMode: 'CHANGE_SET_EXECUTE', ChangeSetName: props.changeSetName, }); - SingletonPolicy.forRole(props.stage.pipeline.role) - .grantExecuteChangeSet(props); + this.props = props; + } + + protected bind(stage: codepipeline.IStage, _scope: cdk.Construct): void { + SingletonPolicy.forRole(stage.pipeline.role) + .grantExecuteChangeSet(this.props); } } @@ -202,40 +207,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 deploymentRole: iam.IRole; + private _deploymentRole?: iam.IRole; + private readonly props: PipelineCloudFormationDeployActionProps; - constructor(scope: cdk.Construct, id: string, props: PipelineCloudFormationDeployActionProps, configuration: any) { + constructor(props: PipelineCloudFormationDeployActionProps, configuration: any) { const capabilities = props.adminPermissions && props.capabilities === undefined ? CloudFormationCapabilities.NamedIAM : props.capabilities; - super(scope, id, props, { + super(props, { ...configuration, // None evaluates to empty string which is falsey and results in undefined Capabilities: (capabilities && capabilities.toString()) || undefined, RoleArn: new cdk.Token(() => this.deploymentRole.roleArn), - ParameterOverrides: new cdk.Token(() => this.node.stringifyJson(props.parameterOverrides)), + ParameterOverrides: new cdk.Token(() => this.scope.node.stringifyJson(props.parameterOverrides)), TemplateConfiguration: props.templateConfiguration ? props.templateConfiguration.location : undefined, StackName: props.stackName, }); - if (props.deploymentRole) { - this.deploymentRole = props.deploymentRole; + this.props = props; + } + + /** + * Add statement to the service role assumed by CloudFormation while executing this action. + */ + public addToDeploymentRolePolicy(statement: iam.PolicyStatement) { + return this.getDeploymentRole('method addToRolePolicy()').addToPolicy(statement); + } + + public get deploymentRole(): iam.IRole { + return this.getDeploymentRole('property role()'); + } + + protected bind(stage: codepipeline.IStage, scope: cdk.Construct): void { + if (this.props.deploymentRole) { + this._deploymentRole = this.props.deploymentRole; } else { - this.deploymentRole = new iam.Role(this, 'Role', { + this._deploymentRole = new iam.Role(scope, 'Role', { assumedBy: new iam.ServicePrincipal('cloudformation.amazonaws.com') }); - if (props.adminPermissions) { - this.deploymentRole.addToPolicy(new iam.PolicyStatement().addAction('*').addAllResources()); + if (this.props.adminPermissions) { + this._deploymentRole.addToPolicy(new iam.PolicyStatement().addAction('*').addAllResources()); } } - SingletonPolicy.forRole(props.stage.pipeline.role).grantPassRole(this.deploymentRole); + SingletonPolicy.forRole(stage.pipeline.role).grantPassRole(this._deploymentRole); } - /** - * Add statement to the service role assumed by CloudFormation while executing this action. - */ - public addToDeploymentRolePolicy(statement: iam.PolicyStatement) { - return this.deploymentRole.addToPolicy(statement); + private getDeploymentRole(member: string): iam.IRole { + if (this._deploymentRole) { + return this._deploymentRole; + } else { + throw new Error(`Cannot use the ${member} before the Action has been added to a Pipeline`); + } } } @@ -261,19 +283,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(props: PipelineCreateReplaceChangeSetActionProps) { + super(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(stage: codepipeline.IStage, scope: cdk.Construct): void { + super.bind(stage, scope); + + SingletonPolicy.forRole(stage.pipeline.role).grantCreateReplaceChangeSet(this.props2); } } @@ -317,18 +348,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(props: PipelineCreateUpdateStackActionProps) { + super(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(stage: codepipeline.IStage, scope: cdk.Construct): void { + super.bind(stage, scope); + + SingletonPolicy.forRole(stage.pipeline.role).grantCreateUpdateStack(this.props2); } } @@ -346,11 +386,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(props: PipelineDeleteStackActionProps) { + super(props, { ActionMode: 'DELETE_ONLY', }); - SingletonPolicy.forRole(props.stage.pipeline.role).grantDeleteStack(props); + + this.props2 = props; + } + + protected bind(stage: codepipeline.IStage, scope: cdk.Construct): void { + super.bind(stage, scope); + + SingletonPolicy.forRole(stage.pipeline.role).grantDeleteStack(this.props2); } } @@ -509,4 +558,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 fdd4fede38631..f22933e9ad784 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,18 @@ 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({ + actionName: '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.deploymentRole.roleArn); @@ -45,22 +48,25 @@ 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({ + actionName: 'ActionA', + changeSetName: 'MyChangeSet', + stackName: 'StackA', + adminPermissions: false, + templatePath: artifact.atPath('path/to/file') + }), + new cloudformation.PipelineCreateReplaceChangeSetAction({ + actionName: 'ActionB', + changeSetName: 'MyChangeSet', + stackName: 'StackB', + adminPermissions: false, + templatePath: artifact.atPath('path/to/other/file') + }), + ], }); test.deepEqual( @@ -70,8 +76,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 +107,15 @@ 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({ + actionName: 'Action', + changeSetName: 'MyChangeSet', + stackName: 'MyStack', + }), + ], }); const stackArn = _stackArn('MyStack', stack); @@ -124,17 +134,20 @@ 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({ + actionName: 'ActionA', + changeSetName: 'MyChangeSet', + stackName: 'StackA', + }), + new cloudformation.PipelineExecuteChangeSetAction({ + actionName: 'ActionB', + changeSetName: 'MyChangeSet', + stackName: 'StackB', + }), + ], }); test.deepEqual( @@ -161,13 +174,17 @@ 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({ + actionName: '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 +200,15 @@ 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({ + actionName: '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,50 +312,47 @@ class PipelineDouble extends cdk.Construct implements cpapi.IPipeline { this.role = role; } - public get uniqueId(): string { - throw new Error("Unsupported"); + public asEventRuleTarget(_ruleArn: string, _ruleUniqueId: string): events.EventRuleTargetProps { + throw new Error('asEventRuleTarget() is unsupported in PipelineDouble'); } - public grantBucketRead(): void { - throw new Error("Unsupported"); + public grantBucketRead(_identity?: iam.IPrincipal): void { + throw new Error('grantBucketRead() is unsupported in PipelineDouble'); } - public grantBucketReadWrite(): void { - throw new Error("Unsupported"); - } - - 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 { - public readonly dependencyRoots: cdk.IConstruct[] = [this]; - public readonly name: string; +class StageDouble implements cpapi.IStage { + public readonly stageName: string; public readonly pipeline: cpapi.IPipeline; - public readonly _internal = this; - - public readonly actions = new Array(); + public readonly actions: cpapi.Action[]; public get node(): cdk.ConstructNode { - throw new Error('this is not a real construct'); + throw new Error('StageDouble is not a real construct'); } - constructor({ name, pipeline }: { name?: string, pipeline: cpapi.IPipeline }) { - this.name = name || 'TestStage'; + constructor({ name, pipeline, actions }: { name?: string, pipeline: PipelineDouble, actions: cpapi.Action[] }) { + this.stageName = name || 'TestStage'; this.pipeline = pipeline; - } - public _attachAction(action: cpapi.Action) { - this.actions.push(action); + const stageParent = new cdk.Construct(pipeline, this.stageName); + for (const action of actions) { + const actionParent = new cdk.Construct(stageParent, action.actionName); + (action as any)._attachActionToPipeline(this, actionParent); + } + this.actions = actions; } - public _generateOutputArtifactName(): string { - throw new Error('Unsupported'); + public addAction(_action: cpapi.Action): void { + throw new Error('addAction() is not supported on StageDouble'); } - public _findInputArtifact(): cpapi.Artifact { - throw new Error('Unsupported'); + public onStateChange(_name: string, _target?: events.IEventRuleTarget, _options?: events.EventRuleProps): + events.EventRule { + throw new Error('onStateChange() is not supported on StageDouble'); } } @@ -353,4 +371,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 fe8ba968d8a00..a5eb9618abdf7 100644 --- a/packages/@aws-cdk/aws-codebuild/README.md +++ b/packages/@aws-cdk/aws-codebuild/README.md @@ -165,12 +165,11 @@ const rule = project.onStateChange('BuildStateChange'); rule.addTarget(lambdaFunction); ``` - ## Using a CodeBuild Project as an AWS CodePipeline action 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'); @@ -178,18 +177,26 @@ import codepipeline = require('@aws-cdk/aws-codepipeline'); const repository = new codecommit.Repository(this, 'MyRepository', { repositoryName: 'MyRepository', }); - const project = new codebuild.PipelineProject(this, 'MyProject'); -const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); - -const sourceStage = pipeline.addStage('Source'); -repository.addToPipeline(sourceStage, 'CodeCommit'); - -const buildStage = pipeline.addStage('Build'); -new codebuild.PipelineBuildAction(this, 'CodeBuild', { - stage: buildStage, +const sourceAction = repository.toCodePipelineSourceAction({ actionName: 'CodeCommit' }); +const buildAction = new codebuild.PipelineBuildAction({ + actionName: 'CodeBuild', project, + inputArtifact: sourceAction.outputArtifact, +}); + +new codepipeline.Pipeline(this, 'MyPipeline', { + stages: [ + { + name: 'Source', + actions: [sourceAction], + }, + { + name: 'Build', + actions: [buildAction], + }, + ], }); ``` @@ -204,11 +211,14 @@ const project = new codebuild.Project(this, 'MyProject', { } ``` -You can also add the Project to the Pipeline directly: +You can also create the action from the Project directly: ```ts // equivalent to the code above: -const buildAction = project.addToPipeline(buildStage, 'CodeBuild'); +const buildAction = project.toCodePipelineBuildAction({ + actionName: 'CodeBuild', + inputArtifact: sourceAction.outputArtifact, +}); ``` In addition to the build Action, there is also a test Action. It works very @@ -217,19 +227,22 @@ not always produce an output artifact. Examples: -```ts -new codebuild.PipelineTestAction(this, 'IntegrationTest', { - stage: buildStage, +```typescript +const testAction = new codebuild.PipelineTestAction({ + actionName: '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', { - // of course, this property is optional here as well - outputArtifactName: 'IntegrationTestOutput', +const testAction = project.toCodePipelineTestAction({ + actionName: 'IntegrationTest', + inputArtifact: sourceAction.outputArtifact, + // of course, this property is optional here as well + outputArtifactName: 'IntegrationTestOutput', }); ``` @@ -307,22 +320,24 @@ 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.toCodePipelineSourceAction({ + actionName: 'Source1', +}); +const sourceAction2 = repository2.toCodePipelineSourceAction({ + actionName: 'Source2', outputArtifactName: 'source2', }); -const buildStage = pipeline.addStage('Build'); -const buildAction = project.addToPipeline(buildStage, 'Build', { - inputArtifact: sourceAction1.outputArtifact, - outputArtifactName: 'artifact1', // for better buildspec readability - see below - additionalInputArtifacts: [ - sourceAction2.outputArtifact, // this is where 'source2' comes from - ], - additionalOutputArtifactNames: [ - 'artifact2', - ], +const buildAction = project.toCodePipelineBuildAction({ + actionName: 'Build', + inputArtifact: sourceAction1.outputArtifact, + outputArtifactName: 'artifact1', // for better buildspec readability - see below + additionalInputArtifacts: [ + sourceAction2.outputArtifact, // this is where 'source2' comes from + ], + additionalOutputArtifactNames: [ + 'artifact2', + ], }); ``` diff --git a/packages/@aws-cdk/aws-codebuild/lib/pipeline-actions.ts b/packages/@aws-cdk/aws-codebuild/lib/pipeline-actions.ts index 718553416a290..16589721b5738 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#toCodePipelineBuildAction}. */ 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,20 +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(props: PipelineBuildActionProps) { + super({ + ...props, provider: 'CodeBuild', artifactBounds: { minInputs: 1, maxInputs: 5, minOutputs: 0, maxOutputs: 5 }, + outputArtifactName: props.outputArtifactName || `Artifact_${props.actionName}_${props.project.node.uniqueId}`, configuration: { ProjectName: props.project.projectName, }, - ...props, }); - setCodeBuildNeededPermissions(props.stage, props.project, true); + this.props = props; handleAdditionalInputOutputArtifacts(props, this, // pass functions to get around protected members @@ -104,21 +101,23 @@ export class PipelineBuildAction extends codepipeline.BuildAction { public additionalOutputArtifact(name: string): codepipeline.Artifact { return findOutputArtifact(this.additionalOutputArtifacts(), name); } + + protected bind(stage: codepipeline.IStage, _scope: cdk.Construct): void { + setCodeBuildNeededPermissions(stage, this.props.project, true); + } } /** * Common properties for creating {@link PipelineTestAction} - * either directly, through its constructor, - * or through {@link IProject#addToPipelineAsTest}. + * or through {@link IProject#toCodePipelineTestAction}. */ 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,18 +141,19 @@ 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(props: PipelineTestActionProps) { + super({ + ...props, provider: 'CodeBuild', artifactBounds: { minInputs: 1, maxInputs: 5, minOutputs: 0, maxOutputs: 5 }, configuration: { ProjectName: props.project.projectName, }, - ...props, }); - // the Action needs write permissions only if it's producing an output artifact - setCodeBuildNeededPermissions(props.stage, props.project, !!props.outputArtifactName); + this.props = props; handleAdditionalInputOutputArtifacts(props, this, // pass functions to get around protected members @@ -190,6 +189,10 @@ export class PipelineTestAction extends codepipeline.TestAction { public additionalOutputArtifact(name: string): codepipeline.Artifact { return findOutputArtifact(this.additionalOutputArtifacts(), name); } + + protected bind(stage: codepipeline.IStage, _scope: cdk.Construct): void { + setCodeBuildNeededPermissions(stage, this.props.project, this._outputArtifacts.length > 0); + } } function setCodeBuildNeededPermissions(stage: codepipeline.IStage, project: IProject, @@ -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..6ad303d4a84ff 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,20 @@ 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 props the properties of the new Action - * @returns the newly created {@link PipelineBuildAction} build Action + * @param props the construction properties of the new Action + * @returns the newly created {@link PipelineBuildAction CodeBuild build Action} */ - addToPipeline(stage: codepipeline.IStage, name: string, props?: CommonPipelineBuildActionProps): PipelineBuildAction; + toCodePipelineBuildAction(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 props the properties of the new Action - * @returns the newly created {@link PipelineBuildAction} test Action + * @param props the construction properties of the new Action + * @returns the newly created {@link PipelineTestAction CodeBuild test Action} */ - addToPipelineAsTest(stage: codepipeline.IStage, name: string, props?: CommonPipelineTestActionProps): PipelineTestAction; + toCodePipelineTestAction(props: CommonPipelineTestActionProps): PipelineTestAction; /** * Defines a CloudWatch event rule triggered when the build project state @@ -196,37 +189,17 @@ 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, - project: this, + public toCodePipelineBuildAction(props: CommonPipelineBuildActionProps): PipelineBuildAction { + return new PipelineBuildAction({ ...props, + project: this, }); } - /** - * 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, - project: this, + public toCodePipelineTestAction(props: CommonPipelineTestActionProps): PipelineTestAction { + return new PipelineTestAction({ ...props, + project: this, }); } diff --git a/packages/@aws-cdk/aws-codecommit/README.md b/packages/@aws-cdk/aws-codecommit/README.md index 5a8f3bd8aea6f..7bc6d9ff341d8 100644 --- a/packages/@aws-cdk/aws-codecommit/README.md +++ b/packages/@aws-cdk/aws-codecommit/README.md @@ -31,20 +31,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({ + actionName: 'CodeCommit', + repository: repo, +}); +pipeline.addStage({ + name: 'Source', + actions: [sourceAction], }); ``` -You can also add the Repository to the Pipeline directly: +You can also create the action from the Repository directly: ```ts // equivalent to the code above: -const sourceAction = repo.addToPipeline(sourceStage, 'CodeCommit'); +const sourceAction = repo.toCodePipelineSourceAction({ actionName: '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 68d0444565b5b..26bedcca53747 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#toCodePipelineSourceAction}. */ 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,8 +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, { + private readonly props: PipelineSourceActionProps; + + constructor(props: PipelineSourceActionProps) { + super({ ...props, provider: 'CodeCommit', configuration: { @@ -55,11 +56,16 @@ export class PipelineSourceAction extends codepipeline.SourceAction { BranchName: props.branch || 'master', PollForSourceChanges: props.pollForSourceChanges || false, }, - outputArtifactName: props.outputArtifactName + outputArtifactName: props.outputArtifactName || `Artifact_${props.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(stage: codepipeline.IStage, _scope: cdk.Construct): void { + if (!this.props.pollForSourceChanges) { + this.props.repository.onCommit(stage.pipeline.node.uniqueId + 'EventRule', + stage.pipeline, this.props.branch || 'master'); } // https://docs.aws.amazon.com/codecommit/latest/userguide/auth-and-access-control-permissions-reference.html#aa-acp @@ -71,8 +77,8 @@ export class PipelineSourceAction extends codepipeline.SourceAction { 'codecommit:CancelUploadArchive', ]; - props.stage.pipeline.role.addToPolicy(new iam.PolicyStatement() - .addResource(props.repository.repositoryArn) + stage.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..1db388314c7bd 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,12 @@ 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 props the properties of the new Action + * @param props the construction properties of the new Action * @returns the newly created {@link PipelineSourceAction} */ - addToPipeline(stage: actions.IStage, name: string, props?: CommonPipelineSourceActionProps): PipelineSourceAction; + toCodePipelineSourceAction(props: CommonPipelineSourceActionProps): PipelineSourceAction; /** * Defines a CloudWatch event rule which triggers for repository events. Use @@ -123,20 +119,10 @@ 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 toCodePipelineSourceAction(props: CommonPipelineSourceActionProps): PipelineSourceAction { + return new PipelineSourceAction({ ...props, + repository: this, }); } diff --git a/packages/@aws-cdk/aws-codecommit/package.json b/packages/@aws-cdk/aws-codecommit/package.json index b35aa9bd8b254..16a84ca70e199 100644 --- a/packages/@aws-cdk/aws-codecommit/package.json +++ b/packages/@aws-cdk/aws-codecommit/package.json @@ -44,7 +44,7 @@ "nyc": { "statements": 30, "lines": 30, - "branches": 40 + "branches": 38 }, "keywords": [ "aws", diff --git a/packages/@aws-cdk/aws-codedeploy/README.md b/packages/@aws-cdk/aws-codedeploy/README.md index 8852de320fd31..7160525fc0433 100644 --- a/packages/@aws-cdk/aws-codedeploy/README.md +++ b/packages/@aws-cdk/aws-codedeploy/README.md @@ -170,23 +170,30 @@ Example: import codepipeline = require('@aws-cdk/aws-codepipeline'); const pipeline = new codepipeline.Pipeline(this, 'MyPipeline', { - pipelineName: 'MyPipeline', + pipelineName: '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({ + actionName: 'CodeDeploy', + inputArtifact: buildAction.outputArtifact, + deploymentGroup, +}); +pipeline.addStage({ + name: 'Deploy', + actions: [deployAction], }); ``` -You can also add the Deployment Group to the Pipeline directly: +You can also create an action from the Deployment Group directly: ```ts // equivalent to the code above: -deploymentGroup.addToPipeline(deployStage, 'CodeDeploy'); +const deployAction = deploymentGroup.toCodePipelineDeployAction({ + actionName: 'CodeDeploy', + inputArtifact: buildAction.outputArtifact, +}); ``` ### Lambda Applications diff --git a/packages/@aws-cdk/aws-codedeploy/lib/pipeline-action.ts b/packages/@aws-cdk/aws-codedeploy/lib/pipeline-action.ts index d03585c41e894..d2e23fa05b34a 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 './server'; /** * Common properties for creating a {@link PipelineDeployAction}, * either directly, through its constructor, - * or through {@link IServerDeploymentGroup#addToPipeline}. + * or through {@link IServerDeploymentGroup#toCodePipelineDeployAction}. */ 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,8 +26,10 @@ export interface PipelineDeployActionProps extends CommonPipelineDeployActionPro } export class PipelineDeployAction extends codepipeline.DeployAction { - constructor(scope: cdk.Construct, id: string, props: PipelineDeployActionProps) { - super(scope, id, { + private readonly deploymentGroup: IServerDeploymentGroup; + + constructor(props: PipelineDeployActionProps) { + super({ ...props, artifactBounds: { minInputs: 1, maxInputs: 1, minOutputs: 0, maxOutputs: 0 }, provider: 'CodeDeploy', @@ -41,32 +40,36 @@ export class PipelineDeployAction extends codepipeline.DeployAction { }, }); + this.deploymentGroup = props.deploymentGroup; + } + + protected bind(stage: codepipeline.IStage, scope: 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) + stage.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) + stage.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)) + stage.pipeline.role.addToPolicy(new iam.PolicyStatement() + .addResource(this.deploymentGroup.deploymentConfig.deploymentConfigArn(scope)) .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 || []) { + stage.pipeline.grantBucketRead(asg.role); } } } diff --git a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts index d9163c5ba022a..6460a847bc125 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/server/deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/server/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'); @@ -21,6 +20,14 @@ export interface IServerDeploymentGroup extends cdk.IConstruct { readonly deploymentConfig: IServerDeploymentConfig; readonly autoScalingGroups?: autoscaling.AutoScalingGroup[]; export(): ServerDeploymentGroupImportProps; + + /** + * Convenience method for creating a new {@link PipelineDeployAction}. + * + * @param props the construction properties of the new Action + * @returns the newly created {@link PipelineDeployAction} + */ + toCodePipelineDeployAction(props: CommonPipelineDeployActionProps): PipelineDeployAction; } /** @@ -75,21 +82,11 @@ 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 toCodePipelineDeployAction(props: CommonPipelineDeployActionProps): PipelineDeployAction { - return new PipelineDeployAction(this, name, { - deploymentGroup: this, - stage, + return new PipelineDeployAction({ ...props, + deploymentGroup: this, }); } } diff --git a/packages/@aws-cdk/aws-codedeploy/package.json b/packages/@aws-cdk/aws-codedeploy/package.json index 85e8456fdfb6b..631ead648b683 100644 --- a/packages/@aws-cdk/aws-codedeploy/package.json +++ b/packages/@aws-cdk/aws-codedeploy/package.json @@ -41,6 +41,9 @@ "cdk-build": { "cloudformation": "AWS::CodeDeploy" }, + "nyc": { + "statements": 79 + }, "keywords": [ "aws", "cdk", diff --git a/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts b/packages/@aws-cdk/aws-codepipeline-api/lib/action.ts index 6d44b0c146f96..5553cf105a57a 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,28 +75,32 @@ 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; + addAction(action: Action): void; + + onStateChange(name: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps): events.EventRule; } /** * Common properties shared by all Actions. */ export interface CommonActionProps { + /** + * The physical, human-readable name of the Action. + * Not that Action names must be unique within a single Stage. + */ + actionName: string; + /** * The runOrder property for this Action. * RunOrder determines the relative order in which multiple Actions in the same Stage execute. @@ -138,20 +111,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; @@ -182,7 +145,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 @@ -233,17 +196,17 @@ 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 _stage?: IStage; + private _scope?: cdk.Construct; - validation.validateName('Action', id); + constructor(props: ActionProps) { + validation.validateName('Action', props.actionName); this.owner = props.owner || 'AWS'; this.version = props.version || '1'; @@ -253,22 +216,20 @@ 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.actionName = props.actionName; this.role = props.role; - - this.stage._internal._attachAction(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.scope, name, options); rule.addTarget(target); rule.addEventPattern({ detailType: [ 'CodePipeline Stage Execution State Change' ], source: [ 'aws.codepipeline' ], resources: [ this.stage.pipeline.pipelineArn ], detail: { - stage: [ this.stage.name ], - action: [ this.node.id ], + stage: [ this.stage.stageName ], + action: [ this.actionName ], }, }); return rule; @@ -290,16 +251,61 @@ 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; } + + /** + * Retrieves the Construct scope of this Action. + * Only available after the Action has been added to a Stage, + * and that Stage to a Pipeline. + */ + protected get scope(): cdk.Construct { + if (this._scope) { + return this._scope; + } else { + throw new Error('Action must be added to a stage that is part of a pipeline first'); + } + } + + /** + * The method called when an Action is attached to a Pipeline. + * This method is guaranteed to be called only once for each Action instance. + * + * @param stage the stage this action has been added to + * (includes a reference to the pipeline as well) + * @param scope the scope construct for this action, + * can be used by the action implementation to create any resources it needs to work correctly + */ + protected abstract bind(stage: IStage, scope: cdk.Construct): void; + + // ignore unused private method (it's actually used in Stage) + // @ts-ignore + private _attachActionToPipeline(stage: IStage, scope: cdk.Construct): void { + if (this._stage) { + throw new Error(`Action '${this.actionName}' has been added to a pipeline twice`); + } + + this._stage = stage; + this._scope = scope; + + this.bind(stage, scope); + } + + private get stage(): IStage { + if (this._stage) { + return this._stage; + } else { + throw new Error('Action must be added to a stage that is part of a pipeline before using onStateChange'); + } + } } // 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..1fea727ef1b3c 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,10 +56,10 @@ 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, { - category: ActionCategory.Build, + constructor(props: BuildActionProps) { + super({ ...props, + category: ActionCategory.Build, }); this.addInputArtifact(props.inputArtifact); 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..4a4d0155453a4 100644 --- a/packages/@aws-cdk/aws-codepipeline-api/lib/deploy-action.ts +++ b/packages/@aws-cdk/aws-codepipeline-api/lib/deploy-action.ts @@ -1,24 +1,23 @@ -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, { - category: ActionCategory.Deploy, + constructor(props: DeployActionProps) { + super({ ...props, + category: ActionCategory.Deploy, }); this.addInputArtifact(props.inputArtifact); 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..77635f7a799d3 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,11 +49,11 @@ 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(props: SourceActionProps) { + super({ + ...props, category: ActionCategory.Source, artifactBounds: { minInputs: 0, maxInputs: 0, minOutputs: 1, maxOutputs: 1 }, - ...props, }); this.outputArtifact = this.addOutputArtifact(props.outputArtifactName); 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..944580ed6785a 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,10 +68,10 @@ 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, { - category: ActionCategory.Test, + constructor(props: TestActionProps) { + super({ ...props, + category: ActionCategory.Test, }); this.addInputArtifact(props.inputArtifact); diff --git a/packages/@aws-cdk/aws-codepipeline/README.md b/packages/@aws-cdk/aws-codepipeline/README.md index 8a1ae513e972e..f34f41cdb6815 100644 --- a/packages/@aws-cdk/aws-codepipeline/README.md +++ b/packages/@aws-cdk/aws-codepipeline/README.md @@ -20,19 +20,37 @@ const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { ### Stages -To append a Stage to a Pipeline: +You can provide Stages when creating the Pipeline: -```ts -const sourceStage = pipeline.addStage('Source'); +```typescript +const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { + stages: [ + { + name: 'Source', + actions: [ + // see below... + ], + }, + ], +}); ``` -You can also instantiate the `Stage` Construct directly, -which will add it to the Pipeline provided in its construction properties. +Or append a Stage to an existing Pipeline: + +```ts +const sourceStage = pipeline.addStage({ + name: 'Source', + actions: [ // optional property + // see below... + ], +}); +``` You can insert the new Stage at an arbitrary point in the Pipeline: ```ts -const sourceStage = pipeline.addStage('Source', { +pipeline.addStage({ + name: 'SomeStage', placement: { // note: you can only specify one of the below properties rightBefore: anotherStage, @@ -48,30 +66,15 @@ 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({ + actionName: '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 +82,17 @@ 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({ + actionName: '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 +134,8 @@ With a `JenkinsProvider`, we can create a Jenkins Action: ```ts -const buildAction = new codepipeline.JenkinsBuildAction(this, 'JenkinsBuild', { - stage: buildStage, +const buildAction = new codepipeline.JenkinsBuildAction({ + actionName: 'JenkinsBuild', jenkinsProvider: jenkinsProvider, projectName: 'MyProject', }); @@ -141,11 +146,13 @@ 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.toCodePipelineBuildAction({ + actionName: 'JenkinsBuild', projectName: 'MyProject', }); -const testAction = jenkinsProvider.addToPipelineAsTest(buildStage, 'JenkinsTest', { +const testAction = jenkinsProvider.toCodePipelineTestAction({ + actionName: 'JenkinsTest', projectName: 'MyProject', }); ``` @@ -167,7 +174,8 @@ const pipeline = new codepipeline.Pipeline(this, 'MyFirstPipeline', { }); // later in the code... -new cloudformation.PipelineCreateUpdateStackAction(this, 'CFN_US_West_1', { +new cloudformation.PipelineCreateUpdateStackAction({ + actionName: '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 841ca8da61154..73c1c636b77ce 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,8 +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, { + private readonly props: GitHubSourceActionProps; + + constructor(props: GitHubSourceActionProps) { + super({ ...props, owner: 'ThirdParty', provider: 'GitHub', @@ -71,11 +70,15 @@ export class GitHubSourceAction extends actions.SourceAction { outputArtifactName: props.outputArtifactName }); - if (!props.pollForSourceChanges) { - new CfnWebhook(this, 'WebhookResource', { + this.props = props; + } + + protected bind(stage: actions.IStage, scope: cdk.Construct): void { + if (!this.props.pollForSourceChanges) { + new CfnWebhook(scope, 'WebhookResource', { authentication: 'GITHUB_HMAC', authenticationConfiguration: { - secretToken: props.oauthToken.toString(), + secretToken: this.props.oauthToken.toString(), }, filters: [ { @@ -83,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: stage.pipeline.pipelineName, targetPipelineVersion: 1, registerWithThirdParty: true, }); diff --git a/packages/@aws-cdk/aws-codepipeline/lib/index.ts b/packages/@aws-cdk/aws-codepipeline/lib/index.ts index f104e7d9cc54b..2a1bec4de1230 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/index.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/index.ts @@ -4,7 +4,6 @@ export * from './jenkins-actions'; export * from './jenkins-provider'; export * from './manual-approval-action'; export * from './pipeline'; -export * from './stage'; // AWS::CodePipeline CloudFormation Resources: export * from './codepipeline.generated'; diff --git a/packages/@aws-cdk/aws-codepipeline/lib/jenkins-actions.ts b/packages/@aws-cdk/aws-codepipeline/lib/jenkins-actions.ts index 8de7c426739f3..0956e8fe147cf 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#toCodePipelineBuildAction}. */ 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,11 @@ 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(props: JenkinsBuildActionProps) { + super({ + ...props, provider: props.jenkinsProvider.providerName, owner: 'Custom', artifactBounds: jenkinsArtifactsBounds, @@ -62,17 +62,21 @@ export class JenkinsBuildAction extends cpapi.BuildAction { configuration: { ProjectName: props.projectName, }, - ...props, + outputArtifactName: props.outputArtifactName || `Artifact_${props.actionName}_${props.jenkinsProvider.node.uniqueId}`, }); - props.jenkinsProvider._registerBuildProvider(); + this.jenkinsProvider = props.jenkinsProvider; + } + + protected bind(_stage: cpapi.IStage, _scope: 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#toCodePipelineTestAction}. */ 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,11 @@ 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(props: JenkinsTestActionProps) { + super({ + ...props, provider: props.jenkinsProvider.providerName, owner: 'Custom', artifactBounds: jenkinsArtifactsBounds, @@ -112,9 +118,12 @@ export class JenkinsTestAction extends cpapi.TestAction { configuration: { ProjectName: props.projectName, }, - ...props, }); - props.jenkinsProvider._registerTestProvider(); + this.jenkinsProvider = props.jenkinsProvider; + } + + protected bind(_stage: cpapi.IStage, _scope: 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..fbff2e340fc32 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/jenkins-provider.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/jenkins-provider.ts @@ -17,33 +17,27 @@ 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 props construction properties of the new Action * @returns the newly created {@link JenkinsBuildAction} */ - addToPipeline(stage: cpapi.IStage, name: string, props: BasicJenkinsBuildActionProps): + toCodePipelineBuildAction(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 props construction properties of the new Action * @returns the newly created {@link JenkinsTestAction} */ - addToPipelineAsTest(stage: cpapi.IStage, name: string, props: BasicJenkinsTestActionProps): + toCodePipelineTestAction(props: BasicJenkinsTestActionProps): JenkinsTestAction; /** @@ -149,21 +143,17 @@ 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, - jenkinsProvider: this, + public toCodePipelineBuildAction(props: BasicJenkinsBuildActionProps): JenkinsBuildAction { + return new JenkinsBuildAction({ ...props, + jenkinsProvider: this, }); } - public addToPipelineAsTest(stage: cpapi.IStage, name: string, props: BasicJenkinsTestActionProps): - JenkinsTestAction { - return new JenkinsTestAction(this, name, { - stage, - jenkinsProvider: this, + public toCodePipelineTestAction(props: BasicJenkinsTestActionProps): JenkinsTestAction { + return new JenkinsTestAction({ ...props, + jenkinsProvider: this, }); } 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..a15a591acac13 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(props: ManualApprovalActionProps) { + super({ + ...props, category: actions.ActionCategory.Approval, provider: 'Manual', artifactBounds: { minInputs: 0, maxInputs: 0, minOutputs: 0, maxOutputs: 0 }, - configuration: new cdk.Token(() => this.actionConfiguration(props)), - ...props, + configuration: new cdk.Token(() => this.actionConfiguration()), }); - 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(stage: actions.IStage, scope: cdk.Construct): void { + if (this.props.notificationTopic) { + this._notificationTopic = this.props.notificationTopic; + } else if ((this.props.notifyEmails || []).length > 0) { + this._notificationTopic = new sns.Topic(scope, '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(stage.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 2dd94c6414baf..c439bd094fe89 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts @@ -5,7 +5,60 @@ 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 } from './stage'; + +/** + * Allows you to control where to place a new Stage when it's added to the Pipeline. + * Note that you can provide only one of the below properties - + * specifying more than one will result in a validation error. + * + * @see #rightBefore + * @see #justAfter + * @see #atIndex + */ +export interface StagePlacement { + /** + * Inserts the new Stage as a parent of the given Stage + * (changing its current parent Stage, if it had one). + */ + readonly rightBefore?: cpapi.IStage; + + /** + * Inserts the new Stage as a child of the given Stage + * (changing its current child Stage, if it had one). + */ + readonly justAfter?: cpapi.IStage; + + /** + * Inserts the new Stage at the given index in the Pipeline, + * moving the Stage currently at that index, + * and any subsequent ones, one index down. + * Indexing starts at 0. + * The maximum allowed value is {@link Pipeline#stageCount}, + * which will insert the new Stage at the end of the Pipeline. + */ + readonly atIndex?: number; +} + +/** + * Construction properties of a Pipeline Stage. + */ +export interface StageProps { + /** + * The physical, human-readable name to assign to this Pipeline Stage. + */ + name: string; + + /** + * The list of Actions to create this Stage with. + * You can always add more Actions later by calling {@link IStage#addAction}. + */ + actions?: cpapi.Action[]; +} + +export interface StageAddToPipelineProps extends StageProps { + placement?: StagePlacement; +} export interface PipelineProps { /** @@ -34,6 +87,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?: StageProps[]; } /** @@ -44,13 +104,14 @@ export interface PipelineProps { * const pipeline = new Pipeline(this, 'Pipeline'); * * // add a stage - * const sourceStage = new Stage(pipeline, 'Source'); + * const sourceStage = pipeline.addStage({ name: 'Source' }); * * // add a source action to the stage - * new codecommit.PipelineSourceAction(sourceStage, 'Source', { - * artifactName: 'SourceArtifact', + * sourceStage.addAction(new codecommit.PipelineSourceAction({ + * actionName: 'Source', + * outputArtifactName: 'SourceArtifact', * repository: repo, - * }); + * })); * * // ... add more stages */ @@ -131,21 +192,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. + * Creates a new Stage, and adds it to this Pipeline. * - * @param name the name of the newly created Stage - * @param props the optional construction properties of the new Stage + * @param props the creation properties of the new Stage * @returns the newly created Stage */ - public addStage(name: string, props?: CommonStageProps): Stage { - return new Stage(this, name, { - pipeline: this, - ...props, - }); + public addStage(props: StageAddToPipelineProps): cpapi.IStage { + // check for duplicate Stages and names + if (this.stages.find(s => s.stageName === props.name)) { + throw new Error(`Stage with duplicate name '${props.name}' added to the Pipeline`); + } + + const stage = new Stage(props, this); + + const index = props.placement + ? this.calculateInsertIndexFromPlacement(props.placement) + : this.stageCount; + + this.stages.splice(index, 0, stage); + + return stage; } /** @@ -254,35 +327,6 @@ export class Pipeline extends cdk.Construct implements cpapi.IPipeline { ]; } - /** - * Adds a Stage to this Pipeline. - * This is an internal operation - - * a Stage is added to a Pipeline when it's constructed - * (the Pipeline is passed through the {@link StageProps#pipeline} property), - * so there is never a need to call this method explicitly. - * - * @param stage the newly created Stage to add to this Pipeline - * @param placement an optional specification of where to place the newly added Stage in the Pipeline - */ - // ignore unused private method (it's actually used in Stage) - // @ts-ignore - private _attachStage(stage: Stage, placement?: StagePlacement): void { - // _attachStage should be idempotent, in case a customer ever calls it directly - if (this.stages.includes(stage)) { - return; - } - - if (this.stages.find(x => x.name === stage.name)) { - throw new Error(`A stage with name '${stage.name}' already exists`); - } - - const index = placement - ? this.calculateInsertIndexFromPlacement(placement) - : this.stageCount; - - this.stages.splice(index, 0, stage); - } - // ignore unused private method (it's actually used in Stage) // @ts-ignore private _attachActionToRegion(stage: Stage, action: cpapi.Action): void { @@ -328,47 +372,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 +386,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 +395,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 +413,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 => stage === targetStage); } private validateSourceActionLocations(): string[] { @@ -420,7 +423,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 69a2e8757006b..25070078db5d0 100644 --- a/packages/@aws-cdk/aws-codepipeline/lib/stage.ts +++ b/packages/@aws-cdk/aws-codepipeline/lib/stage.ts @@ -2,100 +2,38 @@ import cpapi = require('@aws-cdk/aws-codepipeline-api'); import events = require('@aws-cdk/aws-events'); import cdk = require('@aws-cdk/cdk'); import { CfnPipeline } from './codepipeline.generated'; -import { Pipeline } from './pipeline'; - -/** - * Allows you to control where to place a new Stage when it's added to the Pipeline. - * Note that you can provide only one of the below properties - - * specifying more than one will result in a validation error. - * - * @see #rightBefore - * @see #justAfter - * @see #atIndex - */ -export interface StagePlacement { - /** - * Inserts the new Stage as a parent of the given Stage - * (changing its current parent Stage, if it had one). - */ - readonly rightBefore?: Stage; - - /** - * Inserts the new Stage as a child of the given Stage - * (changing its current child Stage, if it had one). - */ - readonly justAfter?: Stage; - - /** - * Inserts the new Stage at the given index in the Pipeline, - * moving the Stage currently at that index, - * and any subsequent ones, one index down. - * Indexing starts at 0. - * The maximum allowed value is {@link Pipeline#stageCount}, - * which will insert the new Stage at the end of the Pipeline. - */ - 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 { - /** - * The Pipeline to add the newly created Stage to. - */ - pipeline: Pipeline; -} +import { Pipeline, StageProps } from './pipeline'; /** * A Stage in a Pipeline. - * Stages are added to a Pipeline by constructing a new Stage, - * and passing the Pipeline it belongs to through the {@link StageProps#pipeline} attribute. * - * @example - * // add a Stage to a Pipeline - * new Stage(this, 'MyStage', { - * pipeline: myPipeline, - * }); + * Stages are added to a Pipeline by calling {@link Pipeline#addStage}, + * which returns an instance of {@link cpapi.IStage}. + * + * This class is private to the CodePipeline module. */ -export class Stage extends cdk.Construct implements cpapi.IStage, cpapi.IInternalStage { +export class Stage implements cpapi.IStage { /** * The Pipeline this Stage is a part of. */ public readonly pipeline: cpapi.IPipeline; - public readonly name: string; - - /** - * The API of Stage used internally by the CodePipeline Construct. - * You should never need to call any of the methods inside of it yourself. - */ - public readonly _internal = this; - + public readonly stageName: string; + private readonly scope: cdk.Construct; private readonly _actions = new Array(); /** * 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(props: StageProps, pipeline: Pipeline) { + cpapi.validateName('Stage', props.name); + + this.stageName = props.name; + this.pipeline = pipeline; + this.scope = new cdk.Construct(pipeline, this.stageName); - (this.pipeline as any)._attachStage(this, props.placement); + for (const action of props.actions || []) { + this.addAction(action); + } } /** @@ -107,52 +45,52 @@ export class Stage extends cdk.Construct implements cpapi.IStage, cpapi.IInterna public render(): CfnPipeline.StageDeclarationProperty { return { - name: this.node.id, + name: this.stageName, actions: this._actions.map(action => this.renderAction(action)), }; } - public onStateChange(name: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps) { - const rule = new events.EventRule(this, name, options); + public addAction(action: cpapi.Action): void { + // 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); + this.attachActionToPipeline(action); + } + + public onStateChange(name: string, target?: events.IEventRuleTarget, options?: events.EventRuleProps): events.EventRule { + const rule = new events.EventRule(this.scope, name, options); rule.addTarget(target); rule.addEventPattern({ detailType: [ 'CodePipeline Stage Execution State Change' ], source: [ 'aws.codepipeline' ], 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); - - (this.pipeline as any)._attachActionToRegion(this, action); - } - } - - public _generateOutputArtifactName(action: cpapi.Action): string { - return (this.pipeline as any)._generateOutputArtifactName(this, action); + protected validate(): string[] { + return this.validateHasActions(); } - 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.scope, action.actionName); + (action as any)._attachActionToPipeline(this, actionParent); - protected validate(): string[] { - return this.validateHasActions(); + // also notify the Pipeline of the new Action + // (useful for cross-region Actions, for example) + (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,7 +98,7 @@ 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, roleArn: action.role ? action.role.roleArn : undefined }; @@ -168,7 +106,7 @@ export class Stage extends cdk.Construct implements cpapi.IStage, cpapi.IInterna 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..fcb20bdd3f97d 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,49 @@ 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({ + actionName: 'Source', repository: repo, outputArtifactName: 'SourceArtifact', pollForSourceChanges: true, }); +const sourceStage = { + name: '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, -}); - -new cfn.PipelineExecuteChangeSetAction(stack, 'ExecuteChanges', { - stage: prodStage, - stackName, - changeSetName, +const prodStage = { + name: 'Deploy', + actions: [ + new cfn.PipelineCreateReplaceChangeSetAction({ + actionName: 'PrepareChanges', + stackName, + changeSetName, + adminPermissions: true, + templatePath: source.outputArtifact.atPath('template.yaml'), + }), + new codepipeline.ManualApprovalAction({ actionName: 'ApproveChanges' }), + new cfn.PipelineExecuteChangeSetAction({ + actionName: 'ExecuteChanges', + stackName, + changeSetName, + }), + ], +}; + +new codepipeline.Pipeline(stack, 'Pipeline', { + stages: [ + sourceStage, + prodStage, + ], }); /// !hide 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 f75228dce9fe4..2c04d51eef70e 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.lambda-pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.lambda-pipeline.ts @@ -10,7 +10,7 @@ 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 = pipeline.addStage({ name: 'Source' }); const bucket = new s3.Bucket(stack, 'PipelineBucket', { versioned: true, removalPolicy: cdk.RemovalPolicy.Destroy, @@ -18,13 +18,13 @@ const bucket = new s3.Bucket(stack, 'PipelineBucket', { const key = 'key'; const trail = new cloudtrail.CloudTrail(stack, 'CloudTrail'); trail.addS3EventSelector([bucket.arnForObjects(key)], cloudtrail.ReadWriteType.WriteOnly); -new s3.PipelineSourceAction(stack, 'Source', { - stage: sourceStage, +sourceStage.addAction(new s3.PipelineSourceAction({ + actionName: 'Source', outputArtifactName: 'SourceArtifact', bucket, bucketKey: key, pollForSourceChanges: false, -}); +})); const lambdaFun = new lambda.Function(stack, 'LambdaFun', { code: new lambda.InlineCode(` @@ -35,7 +35,7 @@ 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 = pipeline.addStage({ name: 'Lambda' }); +lambdaStage.addAction(lambdaFun.toCodePipelineInvokeAction({ actionName: 'Lambda' })); app.run(); diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-alexa-deploy.ts b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-alexa-deploy.ts index d01f6147d57e7..654d4d2325f97 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-alexa-deploy.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-alexa-deploy.ts @@ -7,30 +7,41 @@ const app = new cdk.App(); const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-alexa-deploy'); -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 sourceAction = new s3.PipelineSourceAction(stack, 'Source', { - stage: sourceStage, +const sourceAction = new s3.PipelineSourceAction({ + actionName: 'Source', outputArtifactName: 'SourceArtifact', bucket, bucketKey: 'key', }); +const sourceStage = { + name: 'Source', + actions: [sourceAction], +}; -const deployStage = new codepipeline.Stage(pipeline, 'Deploy', { pipeline }); +const deployStage = { + name: 'Deploy', + actions: [ + new alexa.AlexaSkillDeployAction({ + actionName: 'DeploySkill', + runOrder: 1, + inputArtifact: sourceAction.outputArtifact, + clientId: new cdk.Secret('clientId'), + clientSecret: new cdk.Secret('clientSecret'), + refreshToken: new cdk.Secret('refreshToken'), + skillId: 'amzn1.ask.skill.12345678-1234-1234-1234-123456789012', + }), + ], +}; -new alexa.AlexaSkillDeployAction(stack, 'DeploySkill', { - stage: deployStage, - runOrder: 1, - inputArtifact: sourceAction.outputArtifact, - clientId: new cdk.Secret('clientId'), - clientSecret: new cdk.Secret('clientSecret'), - refreshToken: new cdk.Secret('refreshToken'), - skillId: 'amzn1.ask.skill.12345678-1234-1234-1234-123456789012', +new codepipeline.Pipeline(stack, 'Pipeline', { + stages: [ + sourceStage, + deployStage, + ], }); 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 c6c39076795ce..fd39353cb858f 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" ] } @@ -167,7 +167,7 @@ "Name": "S3", "OutputArtifacts": [ { - "Name": "Artifact_awscdkcodepipelinecloudformationcrossregionMyBucketS3DBF7878C" + "Name": "Artifact_S3_awscdkcodepipelinecloudformationcrossregionMyBucket99D95A28" } ], "RunOrder": 1 @@ -187,17 +187,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", @@ -226,7 +226,7 @@ "MyPipelineRoleC0D47CA4" ] }, - "CFNDeployRole68D5E8D3": { + "MyPipelineCFNCFNDeployRole9CC99B3F": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { 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..f1820a8a596e5 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,31 @@ 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.toCodePipelineSourceAction({ + actionName: '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: [ + { + name: 'Source', + actions: [sourceAction], + }, + { + name: 'CFN', + actions: [ + new cloudformation.PipelineCreateUpdateStackAction({ + actionName: '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-wtih-action-role.expected.json b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-wtih-action-role.expected.json index 6e3db81ef398c..9f0201d747877 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-wtih-action-role.expected.json +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-wtih-action-role.expected.json @@ -98,7 +98,7 @@ "Effect": "Allow", "Resource": { "Fn::GetAtt": [ - "CFNDeployRole68D5E8D3", + "MyPipelineCFNCFNDeployRole9CC99B3F", "Arn" ] } @@ -183,7 +183,7 @@ "Name": "S3", "OutputArtifacts": [ { - "Name": "Artifact_awscdkcodepipelinecloudformationcrossregionwithactionroleMyBucketS30423514B" + "Name": "Artifact_S3_awscdkcodepipelinecloudformationcrossregionwithactionroleMyBucketBDA230BF" } ], "RunOrder": 1 @@ -203,17 +203,17 @@ "Configuration": { "StackName": "aws-cdk-codepipeline-cross-region-deploy-stack", "ActionMode": "CREATE_UPDATE", - "TemplatePath": "Artifact_awscdkcodepipelinecloudformationcrossregionwithactionroleMyBucketS30423514B::template.yml", + "TemplatePath": "Artifact_S3_awscdkcodepipelinecloudformationcrossregionwithactionroleMyBucketBDA230BF::template.yml", "RoleArn": { "Fn::GetAtt": [ - "CFNDeployRole68D5E8D3", + "MyPipelineCFNCFNDeployRole9CC99B3F", "Arn" ] } }, "InputArtifacts": [ { - "Name": "Artifact_awscdkcodepipelinecloudformationcrossregionwithactionroleMyBucketS30423514B" + "Name": "Artifact_S3_awscdkcodepipelinecloudformationcrossregionwithactionroleMyBucketBDA230BF" } ], "Name": "CFN_Deploy", @@ -295,7 +295,7 @@ ] } }, - "CFNDeployRole68D5E8D3": { + "MyPipelineCFNCFNDeployRole9CC99B3F": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-wtih-action-role.ts b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-wtih-action-role.ts index 7d66c3d73bdc3..78983df28f6d0 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-wtih-action-role.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn-wtih-action-role.ts @@ -13,16 +13,14 @@ 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.toCodePipelineSourceAction({ + actionName: 'S3', bucketKey: 'some/path', }); - -const cfnStage = pipeline.addStage('CFN'); +const sourceStage = { + name: 'Source', + actions: [sourceAction], +}; const role = new iam.Role(stack, 'ActionRole', { assumedBy: new iam.AccountPrincipal(new cdk.Aws().accountId) @@ -31,15 +29,26 @@ role.addToPolicy(new iam.PolicyStatement() .addAction('sqs:*') .addAllResources() ); +const cfnStage = { + name: 'CFN', + actions: [ + new cloudformation.PipelineCreateUpdateStackAction({ + actionName: 'CFN_Deploy', + stackName: 'aws-cdk-codepipeline-cross-region-deploy-stack', + templatePath: sourceAction.outputArtifact.atPath('template.yml'), + adminPermissions: false, + role + }), + ], +}; -new cloudformation.PipelineCreateUpdateStackAction(stack, 'CFN_Deploy', { - stage: cfnStage, - stackName: 'aws-cdk-codepipeline-cross-region-deploy-stack', - templatePath: sourceAction.outputArtifact.atPath('template.yml'), - adminPermissions: false, - role +const pipeline = new codepipeline.Pipeline(stack, 'MyPipeline', { + artifactBucket: bucket, + stages: [ + sourceStage, + cfnStage, + ], }); - pipeline.addToRolePolicy(new iam.PolicyStatement() .addActions("sts:AssumeRole", "iam:PassRole") .addAllResources() 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 ffff4d7b54789..b047a5efe333c 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-cfn.ts @@ -11,40 +11,47 @@ 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({ + actionName: 'Source', outputArtifactName: 'SourceArtifact', bucket, bucketKey: 'key', }); - -const cfnStage = new codepipeline.Stage(stack, 'CFN', { pipeline }); +const sourceStage = { + name: '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, - deploymentRole: role, - templatePath: source.outputArtifact.atPath('test.yaml'), - adminPermissions: false, - parameterOverrides: { - BucketName: source.outputArtifact.bucketName, - ObjectKey: source.outputArtifact.objectKey, - Url: source.outputArtifact.url, - OtherParam: source.outputArtifact.getParam('params.json', 'OtherParam'), - }, +pipeline.addStage(sourceStage); +pipeline.addStage({ + name: 'CFN', + actions: [ + new cfn.PipelineCreateReplaceChangeSetAction({ + actionName: 'DeployCFN', + changeSetName, + stackName, + deploymentRole: role, + templatePath: source.outputArtifact.atPath('test.yaml'), + adminPermissions: false, + parameterOverrides: { + BucketName: source.outputArtifact.bucketName, + ObjectKey: source.outputArtifact.objectKey, + Url: source.outputArtifact.url, + OtherParam: source.outputArtifact.getParam('params.json', 'OtherParam'), + }, + }), + ], }); 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 e038fb3a5e13b..430f6c0117146 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 @@ -281,7 +281,7 @@ "Name": "Source2", "OutputArtifacts": [ { - "Name": "Artifact_awscdkcodepipelinecodebuildmultipleinputsoutputsMyBucketSource22F03F24C" + "Name": "Artifact_Source2_awscdkcodepipelinecodebuildmultipleinputsoutputsMyBucketF8E74713" } ], "RunOrder": 1 @@ -302,20 +302,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" @@ -334,14 +334,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", @@ -538,7 +538,10 @@ "Action": [ "s3:GetObject*", "s3:GetBucket*", - "s3:List*" + "s3:List*", + "s3:DeleteObject*", + "s3:PutObject*", + "s3:Abort*" ], "Effect": "Allow", "Resource": [ 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..611d573635e88 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,22 @@ 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.toCodePipelineSourceAction({ actionName: 'Source1' }); +const sourceAction2 = bucket.toCodePipelineSourceAction({ + actionName: 'Source2', bucketKey: 'some/path', }); +pipeline.addStage({ + name: '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.toCodePipelineBuildAction({ + actionName: 'Build1', inputArtifact: sourceAction1.outputArtifact, additionalInputArtifacts: [ sourceAction2.outputArtifact, @@ -37,7 +44,8 @@ const buildAction = project.addToPipeline(buildStage, 'Build1', { 'CustomOutput1', ], }); -const testAction = project.addToPipelineAsTest(buildStage, 'Build2', { +const testAction = project.toCodePipelineTestAction({ + actionName: 'Build2', inputArtifact: sourceAction2.outputArtifact, additionalInputArtifacts: [ sourceAction1.outputArtifact, @@ -46,6 +54,13 @@ const testAction = project.addToPipelineAsTest(buildStage, 'Build2', { 'CustomOutput2', ], }); +pipeline.addStage({ + name: '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 5bdbf836e3627..561d94c727f52 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..617c624d735e6 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,8 @@ 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({ + actionName: 'source', outputArtifactName: 'SourceArtifact', repository, pollForSourceChanges: true, @@ -24,9 +20,30 @@ new codecommit.PipelineSourceAction(stack, 'source', { const project = new codebuild.Project(stack, 'MyBuildProject', { source: new codebuild.CodePipelineSource(), }); +const buildAction = new codebuild.PipelineBuildAction({ + actionName: 'build', + project, + inputArtifact: sourceAction.outputArtifact, +}); +const testAction = new codebuild.PipelineTestAction({ + actionName: '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: [ + { + name: 'source', + actions: [sourceAction], + }, + ], +}).addStage({ + name: '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..0ee53d78b74b2 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,24 @@ 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: [ + { + name: 'source', + actions: [ + repo.toCodePipelineSourceAction({ + actionName: 'source', + outputArtifactName: 'SourceArtifact', + }), + ], + }, + { + name: 'build', + actions: [ + new codepipeline.ManualApprovalAction({ actionName: '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..990cc5f4168fa 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,18 @@ const pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { artifactBucket: bucket, }); -const sourceStage = new codepipeline.Stage(stack, 'Source', { pipeline }); -bucket.addToPipeline(sourceStage, 'S3Source', { +const sourceStage = pipeline.addStage({ name: 'Source' }); +const sourceAction = bucket.toCodePipelineSourceAction({ + actionName: 'S3Source', bucketKey: 'application.zip', outputArtifactName: 'SourceOutput', }); +sourceStage.addAction(sourceAction); -const deployStage = new codepipeline.Stage(stack, 'Deploy', { pipeline }); -deploymentGroup.addToPipeline(deployStage, 'CodeDeploy'); +const deployStage = pipeline.addStage({ name: 'Deploy' }); +deployStage.addAction(deploymentGroup.toCodePipelineDeployAction({ + actionName: 'CodeDeploy', + inputArtifact: sourceAction.outputArtifact, +})); 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 d83bf9168554b..6fc92474b2a3c 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..4a2278608482a 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 @@ -15,12 +15,10 @@ const pipeline = new codepipeline.Pipeline(stack, 'MyPipeline', { }); const repository = new ecr.Repository(stack, 'MyEcrRepo'); -const sourceStage = pipeline.addStage('Source'); -repository.addToPipeline(sourceStage, 'ECR_Source'); +const sourceStage = pipeline.addStage({ name: 'Source' }); +sourceStage.addAction(repository.toCodePipelineSourceAction({ actionName: 'ECR_Source' })); -const approveStage = pipeline.addStage('Approve'); -new codepipeline.ManualApprovalAction(stack, 'ManualApproval', { - stage: approveStage, -}); +const approveStage = pipeline.addStage({ name: 'Approve' }); +approveStage.addAction(new codepipeline.ManualApprovalAction({ actionName: 'ManualApproval' })); 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 d5dbe738cd94e..86d9041ddaefa 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..50a3207e69265 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-events.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-events.ts @@ -11,24 +11,32 @@ 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 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({ + actionName: 'CodeCommitSource', outputArtifactName: 'Source', repository, pollForSourceChanges: true, }); -new codebuild.PipelineBuildAction(stack, 'CodeBuildAction', { - stage: buildStage, - inputArtifact: sourceAction.outputArtifact, - project +const sourceStage = pipeline.addStage({ + name: 'Source', + actions: [sourceAction], +}); + +pipeline.addStage({ + name: 'Build', + actions: [ + new codebuild.PipelineBuildAction({ + actionName: '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 85bfa3a5b6ed1..0894f252078f1 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 @@ -140,7 +140,7 @@ "Name": "S3", "OutputArtifacts": [ { - "Name": "Artifact_awscdkcodepipelinejenkinsMyBucketS32154568D" + "Name": "Artifact_S3_awscdkcodepipelinejenkinsMyBucket1F504D45" } ], "RunOrder": 1 @@ -162,13 +162,13 @@ }, "InputArtifacts": [ { - "Name": "Artifact_awscdkcodepipelinejenkinsMyBucketS32154568D" + "Name": "Artifact_S3_awscdkcodepipelinejenkinsMyBucket1F504D45" } ], "Name": "JenkinsBuild", "OutputArtifacts": [ { - "Name": "Artifact_awscdkcodepipelinejenkinsJenkinsProviderJenkinsBuild6007E9FD" + "Name": "Artifact_JenkinsBuild_awscdkcodepipelinejenkinsJenkinsProviderF1B32E8D" } ], "RunOrder": 1 @@ -185,7 +185,7 @@ }, "InputArtifacts": [ { - "Name": "Artifact_awscdkcodepipelinejenkinsMyBucketS32154568D" + "Name": "Artifact_S3_awscdkcodepipelinejenkinsMyBucket1F504D45" } ], "Name": "JenkinsTest", @@ -204,7 +204,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..9dd6fbadbb058 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,14 @@ const pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { artifactBucket: bucket, }); -const sourceStage = pipeline.addStage('Source'); -bucket.addToPipeline(sourceStage, 'S3', { +const sourceAction = bucket.toCodePipelineSourceAction({ + actionName: 'S3', bucketKey: 'some/path', }); +pipeline.addStage({ + name: 'Source', + actions: [sourceAction], +}); const jenkinsProvider = new codepipeline.JenkinsProvider(stack, 'JenkinsProvider', { providerName: 'JenkinsProvider', @@ -25,15 +29,25 @@ 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({ + name: 'Build', + actions: [ + jenkinsProvider.toCodePipelineBuildAction({ + actionName: 'JenkinsBuild', + projectName: 'JenkinsProject1', + inputArtifact: sourceAction.outputArtifact, + }), + jenkinsProvider.toCodePipelineTestAction({ + actionName: 'JenkinsTest', + projectName: 'JenkinsProject2', + inputArtifact: sourceAction.outputArtifact, + }), + jenkinsProvider.toCodePipelineTestAction({ + actionName: '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 83a73e2128b66..4c52c22a1a003 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" } } ], @@ -136,7 +136,7 @@ "Name": "S3", "OutputArtifacts": [ { - "Name": "Artifact_awscdkcodepipelinemanualapprovalBucketS39750AFE7" + "Name": "Artifact_S3_awscdkcodepipelinemanualapprovalBucketFF0B256C" } ], "RunOrder": 1 @@ -155,7 +155,7 @@ }, "Configuration": { "NotificationArn": { - "Ref": "ManualApprovalTopicResource300641E2" + "Ref": "PipelineApproveManualApprovalTopicResourceF5A35B20" } }, "InputArtifacts": [], @@ -179,16 +179,16 @@ "PipelineRoleD68726F7" ] }, - "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..e7e6628bf1578 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 @@ -8,19 +8,29 @@ const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-manual-approval'); const bucket = new s3.Bucket(stack, 'Bucket'); -const pipeline = new codepipeline.Pipeline(stack, 'Pipeline', { +new codepipeline.Pipeline(stack, 'Pipeline', { artifactBucket: bucket, -}); - -const sourceStage = pipeline.addStage('Source'); -bucket.addToPipeline(sourceStage, 'S3', { - bucketKey: 'file.zip', -}); - -const approveStage = pipeline.addStage('Approve'); -new codepipeline.ManualApprovalAction(stack, 'ManualApproval', { - stage: approveStage, - notifyEmails: ['adamruka85@gmail.com'] + stages: [ + { + name: 'Source', + actions: [ + new s3.PipelineSourceAction({ + actionName: 'S3', + bucket, + bucketKey: 'file.zip', + }), + ], + }, + { + name: 'Approve', + actions: [ + new codepipeline.ManualApprovalAction({ + actionName: 'ManualApproval', + notifyEmails: ['adamruka85@gmail.com'], + }), + ], + }, + ], }); app.run(); diff --git a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-s3-deploy.ts b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-s3-deploy.ts index dc0943aa77ac5..a13b1b47d0630 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-s3-deploy.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/integ.pipeline-s3-deploy.ts @@ -6,15 +6,12 @@ const app = new cdk.App(); const stack = new cdk.Stack(app, 'aws-cdk-codepipeline-s3-deploy'); -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 sourceAction = new s3.PipelineSourceAction(stack, 'Source', { - stage: sourceStage, +const sourceAction = new s3.PipelineSourceAction({ + actionName: 'Source', outputArtifactName: 'SourceArtifact', bucket, bucketKey: 'key', @@ -22,9 +19,22 @@ const sourceAction = new s3.PipelineSourceAction(stack, 'Source', { const deployBucket = new s3.Bucket(stack, 'DeployBucket', {}); -const deployStage = new codepipeline.Stage(pipeline, 'Deploy', { pipeline }); -deployBucket.addToPipelineAsDeploy(deployStage, 'DeployAction', { - inputArtifact: sourceAction.outputArtifact, +new codepipeline.Pipeline(stack, 'Pipeline', { + stages: [ + { + name: 'Source', + actions: [sourceAction], + }, + { + name: 'Deploy', + actions: [ + deployBucket.toCodePipelineDeployAction({ + actionName: 'DeployAction', + inputArtifact: sourceAction.outputArtifact, + }), + ], + }, + ], }); 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..adc8a8f62c03f 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,22 @@ export = { const repo = new codecommit.Repository(stack, 'Repo', { repositoryName: 'Repo', }); - const sourceStage = pipeline.addStage('Source'); - repo.addToPipeline(sourceStage, 'CodeCommit'); + const sourceAction = repo.toCodePipelineSourceAction({ actionName: 'CodeCommit' }); + pipeline.addStage({ + name: 'Source', + actions: [sourceAction], + }); const project = new codebuild.PipelineProject(stack, 'Project'); - const buildStage = pipeline.addStage('Build'); - project.addToPipeline(buildStage, 'CodeBuild'); + pipeline.addStage({ + name: 'Build', + actions: [ + project.toCodePipelineBuildAction({ + actionName: 'CodeBuild', + inputArtifact: sourceAction.outputArtifact, + }), + ], + }); expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { "Stages": [ @@ -88,7 +96,7 @@ export = { "InputArtifacts": [], "OutputArtifacts": [ { - "Name": "Artifact_RepoCodeCommit7910F5F9", + "Name": "Artifact_CodeCommit_Repo", }, ], } @@ -101,12 +109,12 @@ export = { "Name": "CodeBuild", "InputArtifacts": [ { - "Name": "Artifact_RepoCodeCommit7910F5F9", + "Name": "Artifact_CodeCommit_Repo", } ], "OutputArtifacts": [ { - "Name": "Artifact_ProjectCodeBuildE34AD2EC", + "Name": "Artifact_CodeBuild_Project", }, ], } @@ -117,21 +125,52 @@ 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 = { + name: 'Stage1', + actions: [ + action, + ], + }; + const stage2 = { + name: 'Stage2', + actions: [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(_stage: actions.IStage, _scope: 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 701628e188b9c..cb65b3292595b 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 @@ -2,12 +2,12 @@ import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; import { PipelineCreateReplaceChangeSetAction, PipelineCreateUpdateStackAction, PipelineExecuteChangeSetAction } from '@aws-cdk/aws-cloudformation'; import { CodePipelineBuildArtifacts, CodePipelineSource, PipelineBuildAction, Project } from '@aws-cdk/aws-codebuild'; import { PipelineSourceAction, Repository } from '@aws-cdk/aws-codecommit'; -import { ArtifactPath } from '@aws-cdk/aws-codepipeline-api'; -import { Role } from '@aws-cdk/aws-iam'; +import cpapi = require('@aws-cdk/aws-codepipeline-api'); import { PolicyStatement, ServicePrincipal } from '@aws-cdk/aws-iam'; +import { Role } from '@aws-cdk/aws-iam'; import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; -import { Pipeline, Stage } from '../lib'; +import { Pipeline } from '../lib'; // tslint:disable:object-literal-key-quotes @@ -24,54 +24,61 @@ 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({ + actionName: 'source', outputArtifactName: 'SourceArtifact', repository: repo, pollForSourceChanges: true, }); + pipeline.addStage({ + name: 'source', + actions: [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({ + actionName: 'build', project, inputArtifact: source.outputArtifact, outputArtifactName: "OutputYo" }); + pipeline.addStage({ + name: 'build', + actions: [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, - deploymentRole: 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({ + name: 'prod', + actions: [ + new PipelineCreateReplaceChangeSetAction({ + actionName: 'BuildChangeSetProd', + stackName, + changeSetName, + deploymentRole: changeSetExecRole, + templatePath: new cpapi.ArtifactPath(buildAction.outputArtifact, 'template.yaml'), + templateConfiguration: new cpapi.ArtifactPath(buildAction.outputArtifact, 'templateConfig.json'), + adminPermissions: false, + }), + new PipelineExecuteChangeSetAction({ + actionName: 'ExecuteChangeSetProd', + stackName, + changeSetName, + }), + ], }); expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { @@ -202,12 +209,12 @@ export = { const stack = new TestFixture(); // WHEN - new PipelineCreateUpdateStackAction(stack.deployStage, 'CreateUpdate', { - stage: stack.deployStage, + stack.deployStage.addAction(new PipelineCreateUpdateStackAction({ + actionName: 'CreateUpdate', stackName: 'MyStack', templatePath: stack.source.outputArtifact.atPath('template.yaml'), adminPermissions: true, - }); + })); const roleId = "PipelineDeployCreateUpdateRole515CB7D4"; @@ -257,13 +264,13 @@ export = { const stack = new TestFixture(); // WHEN - new PipelineCreateUpdateStackAction(stack, 'CreateUpdate', { - stage: stack.deployStage, + stack.deployStage.addAction(new PipelineCreateUpdateStackAction({ + actionName: '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 +280,7 @@ export = { "Name": "Deploy", "Actions": [ { - "OutputArtifacts": [{"Name": "DeployCreateUpdateArtifact"}], + "OutputArtifacts": [{"Name": "CreateUpdate_MyStack_Artifact"}], "Name": "CreateUpdate", }, ], @@ -289,13 +296,13 @@ export = { const stack = new TestFixture(); // WHEN - new PipelineCreateUpdateStackAction(stack, 'CreateUpdate', { - stage: stack.deployStage, + stack.deployStage.addAction(new PipelineCreateUpdateStackAction({ + actionName: '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 +330,15 @@ export = { const stack = new TestFixture(); // WHEN - new PipelineCreateUpdateStackAction(stack, 'CreateUpdate', { - stage: stack.deployStage, + stack.deployStage.addAction(new PipelineCreateUpdateStackAction({ + actionName: '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', { @@ -368,19 +375,19 @@ export = { assumedBy: new ServicePrincipal('magicservice') }); - new PipelineExecuteChangeSetAction(stack.pipeline, 'ImportedRoleAction', { + stack.deployStage.addAction(new PipelineExecuteChangeSetAction({ + actionName: 'ImportedRoleAction', role: importedRole, changeSetName: 'magicSet', stackName: 'magicStack', - stage: stack.deployStage - }); + })); - new PipelineExecuteChangeSetAction(stack.pipeline, 'FreshRoleAction', { + stack.deployStage.addAction(new PipelineExecuteChangeSetAction({ + actionName: 'FreshRoleAction', role: freshRole, changeSetName: 'magicSet', stackName: 'magicStack', - stage: stack.deployStage - }); + })); expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { "Stages": [ @@ -417,22 +424,29 @@ export = { */ class TestFixture extends cdk.Stack { public readonly pipeline: Pipeline; - public readonly sourceStage: Stage; - public readonly deployStage: Stage; + public readonly sourceStage: cpapi.IStage; + public readonly deployStage: cpapi.IStage; public readonly repo: Repository; 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.pipeline = new Pipeline(this, 'Pipeline'); + this.sourceStage = this.pipeline.addStage({ name: 'Source' }); + this.deployStage = this.pipeline.addStage({ name: 'Deploy' }); + this.repo = new Repository(this, 'MyVeryImportantRepo', { repositoryName: 'my-very-important-repo' }); + this.source = new PipelineSourceAction({ + actionName: 'Source', + outputArtifactName: 'SourceArtifact', + repository: this.repo, + }); + this.sourceStage.addAction(this.source); + // 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..92b400299a67d 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.general-validation.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.general-validation.ts @@ -3,7 +3,6 @@ import s3 = require('@aws-cdk/aws-s3'); import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; import { Pipeline } from '../lib/pipeline'; -import { Stage } from '../lib/stage'; interface NameValidationTestCase { name: string; @@ -37,7 +36,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,21 +55,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, - outputArtifactName: 'FirstArtifact', - bucket, - bucketKey: 'key', - }); - new s3.PipelineSourceAction(stack, 'SecondAction', { - stage: secondStage, - outputArtifactName: 'SecondAction', - bucket, - bucketKey: 'key', + pipeline.addStage({ + name: 'FirstStage', + actions: [ + new s3.PipelineSourceAction({ + actionName: 'FirstAction', + outputArtifactName: 'FirstArtifact', + bucket, + bucketKey: 'key', + }) + ], }); test.deepEqual(pipeline.node.validateTree().length, 1); @@ -80,8 +76,8 @@ export = { } }; -function stageForTesting(): Stage { +function stageForTesting(): actions.IStage { const stack = new cdk.Stack(); - const pipeline = new Pipeline(stack, 'pipeline'); - return new Stage(stack, 'stage', { pipeline }); + const pipeline = new Pipeline(stack, 'Pipeline'); + return pipeline.addStage({ name: '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..4657cd20d5626 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.pipeline.ts @@ -2,6 +2,7 @@ import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; import cloudformation = require('@aws-cdk/aws-cloudformation'); import codebuild = require('@aws-cdk/aws-codebuild'); import codecommit = require('@aws-cdk/aws-codecommit'); +import cpapi = require('@aws-cdk/aws-codepipeline-api'); import lambda = require('@aws-cdk/aws-lambda'); import s3 = require('@aws-cdk/aws-s3'); import sns = require('@aws-cdk/aws-sns'); @@ -20,21 +21,28 @@ 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({ + actionName: 'source', outputArtifactName: 'SourceArtifact', repository, }); + pipeline.addStage({ + name: 'source', + actions: [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, - inputArtifact: source.outputArtifact, - project, + pipeline.addStage({ + name: 'build', + actions: [ + new codebuild.PipelineBuildAction({ + actionName: 'build', + inputArtifact: source.outputArtifact, + project, + }), + ], }); test.notDeepEqual(stack.toCloudFormation(), {}); @@ -49,19 +57,27 @@ export = { const p = new codepipeline.Pipeline(stack, 'P'); - const s1 = new codepipeline.Stage(stack, 'Source', { pipeline: p }); - new codepipeline.GitHubSourceAction(stack, 'GH', { - stage: s1, - runOrder: 8, - outputArtifactName: 'A', - branch: 'branch', - oauthToken: secret.value, - owner: 'foo', - repo: 'bar' + p.addStage({ + name: 'Source', + actions: [ + new codepipeline.GitHubSourceAction({ + actionName: '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({ + name: 'Two', + actions: [ + new codepipeline.ManualApprovalAction({ actionName: 'Boo' }), + ], + }); expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { "ArtifactStore": { @@ -138,16 +154,24 @@ export = { const pipeline = new codepipeline.Pipeline(stack, 'PL'); - const stage1 = new codepipeline.Stage(stack, 'S1', { pipeline }); - new s3.PipelineSourceAction(stack, 'A1', { - stage: stage1, - outputArtifactName: 'Artifact', - bucket: new s3.Bucket(stack, 'Bucket'), - bucketKey: 'Key' + pipeline.addStage({ + name: 'S1', + actions: [ + new s3.PipelineSourceAction({ + actionName: '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({ + name: 'S2', + actions: [ + new codepipeline.ManualApprovalAction({ actionName: 'A2' }), + ], + }); pipeline.onStateChange('OnStateChange', topic, { description: 'desc', @@ -219,10 +243,11 @@ 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({ + actionName: 'Approve', notificationTopic: topic, }); + stageForTesting(stack).addAction(manualApprovalAction); test.equal(manualApprovalAction.notificationTopic, topic); @@ -278,19 +303,26 @@ 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.toCodePipelineSourceAction({ + actionName: 'SourceAction1', bucketKey: 'some/key', outputArtifactName: 'sourceArtifact1', }); - const source2 = bucket.addToPipeline(sourceStage, 'SourceAction2', { + const source2 = bucket.toCodePipelineSourceAction({ + actionName: 'SourceAction2', bucketKey: 'another/key', outputArtifactName: 'sourceArtifact2', }); + pipeline.addStage({ + name: 'Source', + actions: [ + source1, + source2, + ], + }); - const stage = new codepipeline.Stage(stack, 'Stage', { pipeline }); - const lambdaAction = new lambda.PipelineInvokeAction(stack, 'InvokeAction', { - stage, + const lambdaAction = new lambda.PipelineInvokeAction({ + actionName: 'InvokeAction', lambda: lambdaFun, userParameters: 'foo-bar/42', inputArtifacts: [ @@ -303,6 +335,10 @@ export = { 'lambdaOutput3', ], }); + pipeline.addStage({ + name: 'Stage', + actions: [lambdaAction], + }); expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { "ArtifactStore": { @@ -385,8 +421,8 @@ 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({ + actionName: 'stage', outputArtifactName: 'SomeArtifact', repository: repositoryForTesting(stack), }); @@ -398,8 +434,8 @@ 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({ + actionName: 'stage', outputArtifactName: 'SomeArtifact', repository: repositoryForTesting(stack), pollForSourceChanges: false, @@ -431,32 +467,40 @@ export = { }, }); - const stage1 = pipeline.addStage('Stage1'); - const sourceAction = bucket.addToPipeline(stage1, 'BucketSource', { + const sourceAction = bucket.toCodePipelineSourceAction({ + actionName: '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, + pipeline.addStage({ + name: 'Stage1', + actions: [sourceAction], }); - 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({ + name: 'Stage2', + actions: [ + new cloudformation.PipelineCreateReplaceChangeSetAction({ + actionName: 'Action1', + changeSetName: 'ChangeSet', + templatePath: sourceAction.outputArtifact.atPath('template.yaml'), + stackName: 'SomeStack', + region: pipelineRegion, + adminPermissions: false, + }), + new cloudformation.PipelineCreateUpdateStackAction({ + actionName: 'Action2', + templatePath: sourceAction.outputArtifact.atPath('template.yaml'), + stackName: 'OtherStack', + region: 'us-east-1', + adminPermissions: false, + }), + new cloudformation.PipelineExecuteChangeSetAction({ + actionName: 'Action3', + changeSetName: 'ChangeSet', + stackName: 'SomeStack', + region: 'us-west-1', + }), + ], }); expect(stack).to(haveResourceLike('AWS::CodePipeline::Pipeline', { @@ -520,9 +564,9 @@ export = { }, }; -function stageForTesting(stack: cdk.Stack): codepipeline.Stage { +function stageForTesting(stack: cdk.Stack): cpapi.IStage { const pipeline = new codepipeline.Pipeline(stack, 'pipeline'); - return new codepipeline.Stage(pipeline, 'stage', { pipeline }); + return pipeline.addStage({ name: '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..81b6f703e099d 100644 --- a/packages/@aws-cdk/aws-codepipeline/test/test.stages.ts +++ b/packages/@aws-cdk/aws-codepipeline/test/test.stages.ts @@ -11,9 +11,9 @@ 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, + pipeline.addStage({ name: 'SecondStage' }); + pipeline.addStage({ + name: 'FirstStage', placement: { atIndex: 0, }, @@ -33,8 +33,9 @@ export = { const stack = new cdk.Stack(); const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); - const secondStage = pipeline.addStage('SecondStage'); - pipeline.addStage('FirstStage', { + const secondStage = pipeline.addStage({ name: 'SecondStage' }); + pipeline.addStage({ + name: 'FirstStage', placement: { rightBefore: secondStage, }, @@ -54,9 +55,10 @@ export = { const stack = new cdk.Stack(); const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); - const firstStage = pipeline.addStage('FirstStage'); - pipeline.addStage('ThirdStage'); - pipeline.addStage('SecondStage', { + const firstStage = pipeline.addStage({ name: 'FirstStage' }); + pipeline.addStage({ name: 'ThirdStage' }); + pipeline.addStage({ + name: 'SecondStage', placement: { justAfter: firstStage, }, @@ -78,8 +80,8 @@ export = { const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); test.throws(() => { - new codepipeline.Stage(stack, 'Stage', { - pipeline, + pipeline.addStage({ + name: 'Stage', placement: { atIndex: -1, }, @@ -94,7 +96,8 @@ export = { const pipeline = new codepipeline.Pipeline(stack, 'Pipeline'); test.throws(() => { - pipeline.addStage('Stage', { + pipeline.addStage({ + name: 'Stage', placement: { atIndex: 1, }, @@ -107,11 +110,12 @@ 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 = pipeline.addStage({ name: 'Stage' }); const anotherPipeline = new codepipeline.Pipeline(stack, 'AnotherPipeline'); test.throws(() => { - anotherPipeline.addStage('AnotherStage', { + anotherPipeline.addStage({ + name: 'AnotherStage', placement: { rightBefore: stage, }, @@ -124,11 +128,12 @@ 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 = pipeline.addStage({ name: 'Stage' }); const anotherPipeline = new codepipeline.Pipeline(stack, 'AnotherPipeline'); test.throws(() => { - anotherPipeline.addStage('AnotherStage', { + anotherPipeline.addStage({ + name: 'AnotherStage', placement: { justAfter: stage, }, @@ -141,10 +146,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 = pipeline.addStage({ name: 'Stage' }); test.throws(() => { - pipeline.addStage('SecondStage', { + pipeline.addStage({ + name: 'SecondStage', placement: { rightBefore: stage, justAfter: stage, diff --git a/packages/@aws-cdk/aws-ecr/README.md b/packages/@aws-cdk/aws-ecr/README.md index cefec43725fbb..bdcafd84698f5 100644 --- a/packages/@aws-cdk/aws-ecr/README.md +++ b/packages/@aws-cdk/aws-ecr/README.md @@ -33,18 +33,21 @@ 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({ + actionName: 'ECR', repository: ecrRepository, imageTag: 'some-tag', // optional, default: 'latest' outputArtifactName: 'SomeName', // optional }); +pipeline.addStage({ + actionName: 'Source', + actions: [sourceAction], +}); ``` -You can also add the Repository to the Pipeline directly: +You can also create the action from the Repository directly: ```ts // equivalent to the code above: -const sourceAction = ecrRepository.addToPipeline(sourceStage, 'ECR'); +const sourceAction = ecrRepository.toCodePipelineSourceAction({ actionName: '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..d231e8c593ead 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#toCodePipelineSourceAction} 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(props: PipelineSourceActionProps) { + super({ + ...props, provider: 'ECR', configuration: { RepositoryName: props.repository.repositoryName, ImageTag: props.imageTag, }, - ...props, + outputArtifactName: props.outputArtifactName || `Artifact_${props.actionName}_${props.repository.node.uniqueId}`, }); - props.stage.pipeline.role.addToPolicy(new iam.PolicyStatement() + this.props = props; + } + + protected bind(stage: codepipeline.IStage, _scope: cdk.Construct): void { + stage.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(stage.pipeline.node.uniqueId + 'SourceEventRule', + stage.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..b79c1de336f94 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,12 @@ 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 props the optional construction properties of the new Action + * @param props the construction properties of the new Action * @returns the newly created {@link PipelineSourceAction} */ - addToPipeline(stage: codepipeline.IStage, name: string, props?: CommonPipelineSourceActionProps): + toCodePipelineSourceAction(props: CommonPipelineSourceActionProps): PipelineSourceAction; /** @@ -173,12 +169,10 @@ 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, - repository: this, + public toCodePipelineSourceAction(props: CommonPipelineSourceActionProps): PipelineSourceAction { + return new PipelineSourceAction({ ...props, + repository: this, }); } diff --git a/packages/@aws-cdk/aws-ecr/package.json b/packages/@aws-cdk/aws-ecr/package.json index 1eabc7de7f936..d3860041fbd0c 100644 --- a/packages/@aws-cdk/aws-ecr/package.json +++ b/packages/@aws-cdk/aws-ecr/package.json @@ -41,6 +41,10 @@ "cdk-build": { "cloudformation": "AWS::ECR" }, + "nyc": { + "lines": 78, + "statements": 79 + }, "keywords": [ "aws", "cdk", diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index 10374daeeb80e..2bf3c4a7bef4e 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -80,25 +80,29 @@ 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({ + actionName: 'Lambda', + lambda: fn, +}); +pipeline.addStage({ + actionName: 'Lambda', + actions: [lambdaAction], }); ``` -You can also add the Lambda to the Pipeline directly: +You can also create the action from the Lambda directly: ```ts // equivalent to the code above: -fn.addToPipeline(lambdaStage, 'Lambda'); +const lambdaAction = fn.toCodePipelineInvokeAction({ actionName: '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.toCodePipelineInvokeAction({ + actionName: 'Lambda', inputArtifacts: [ sourceAction.outputArtifact, buildAction.outputArtifact, diff --git a/packages/@aws-cdk/aws-lambda/lib/function-base.ts b/packages/@aws-cdk/aws-lambda/lib/function-base.ts index 9e8c28b14042c..0690d64df156c 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function-base.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function-base.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,12 @@ 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 props the properties of the new Action + * @param props the construction properties of the new Action * @returns the newly created {@link PipelineInvokeAction} */ - addToPipeline(stage: codepipeline.IStage, name: string, props?: CommonPipelineInvokeActionProps): PipelineInvokeAction; + toCodePipelineInvokeAction(props: CommonPipelineInvokeActionProps): PipelineInvokeAction; addToRolePolicy(statement: iam.PolicyStatement): void; @@ -190,20 +186,10 @@ 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, - lambda: this, + public toCodePipelineInvokeAction(props: CommonPipelineInvokeActionProps): PipelineInvokeAction { + return new PipelineInvokeAction({ ...props, + lambda: this, }); } diff --git a/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts b/packages/@aws-cdk/aws-lambda/lib/pipeline-action.ts index 060e463d74fad..e7dcb4c447a40 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 './function-base'; /** * Common properties for creating a {@link PipelineInvokeAction} - * either directly, through its constructor, - * or through {@link IFunction#addToPipeline}. + * or through {@link IFunction#toCodePipelineInvokeAction}. */ 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,8 +80,10 @@ 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, { + private readonly props: PipelineInvokeActionProps; + + constructor(props: PipelineInvokeActionProps) { + super({ ...props, category: codepipeline.ActionCategory.Invoke, provider: 'Lambda', @@ -103,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(stage: codepipeline.IStage, _scope: cdk.Construct): void { // allow pipeline to list functions - props.stage.pipeline.role.addToPolicy(new iam.PolicyStatement() + stage.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() + stage.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 63d581b63ea73..bc3c6d6b1c8b3 100644 --- a/packages/@aws-cdk/aws-s3/README.md +++ b/packages/@aws-cdk/aws-s3/README.md @@ -91,24 +91,28 @@ import codepipeline = require('@aws-cdk/aws-codepipeline'); import s3 = require('@aws-cdk/aws-s3'); const sourceBucket = new s3.Bucket(this, 'MyBucket', { - versioned: true, // a Bucket used as a source in CodePipeline must be versioned + versioned: true, // a Bucket used as a source in CodePipeline must be versioned }); const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); -const sourceStage = pipeline.addStage('Source'); -const sourceAction = new s3.PipelineSourceAction(this, 'S3Source', { - stage: sourceStage, - bucket: sourceBucket, - bucketKey: 'path/to/file.zip', +const sourceAction = new s3.PipelineSourceAction({ + actionName: 'S3Source', + bucket: sourceBucket, + bucketKey: 'path/to/file.zip', +}); +pipeline.addStage({ + name: 'Source', + actions: [sourceAction], }); ``` -You can also add the Bucket to the Pipeline directly: +You can also create the action from the Bucket directly: ```ts // equivalent to the code above: -const sourceAction = sourceBucket.addToPipeline(sourceStage, 'S3Source', { - bucketKey: 'path/to/file.zip', +const sourceAction = sourceBucket.toCodePipelineSourceAction({ + actionName: 'S3Source', + bucketKey: 'path/to/file.zip', }); ``` @@ -126,7 +130,8 @@ import cloudtrail = require('@aws-cdk/aws-cloudtrail'); const key = 'some/key.zip'; const trail = new cloudtrail.CloudTrail(this, 'CloudTrail'); trail.addS3EventSelector([sourceBucket.arnForObjects(key)], cloudtrail.ReadWriteType.WriteOnly); -const sourceAction = sourceBucket.addToPipeline(sourceStage, 'S3Source', { +const sourceAction = sourceBucket.toCodePipelineSourceAction({ + actionName: 'S3Source', bucketKey: key, pollForSourceChanges: false, // default: true }); @@ -144,11 +149,27 @@ import s3 = require('@aws-cdk/aws-s3'); const targetBucket = new s3.Bucket(this, 'MyBucket', {}); const pipeline = new codepipeline.Pipeline(this, 'MyPipeline'); -const deployStage = pipeline.addStage('Deploy'); -const deployAction = new s3.PipelineDeployAction(this, 'S3Deploy', { - stage: deployStage, - bucket: targetBucket, - inputArtifact: sourceAction.outputArtifact, +const deployAction = new s3.PipelineDeployAction({ + actionName: 'S3Deploy', + stage: deployStage, + bucket: targetBucket, + inputArtifact: sourceAction.outputArtifact, +}); +const deployStage = pipeline.addStage({ + name: 'Deploy', + actions: [deployAction], +}); +``` + +You can also create the action from the Bucket directly: + +```ts +// equivalent to the code above: +const deployAction = targetBucket.toCodePipelineDeployAction({ + actionName: 'S3Deploy', + extract: false, // default: true + objectKey: 'path/in/bucket', // required if extract is false + inputArtifact: sourceAction.outputArtifact, }); ``` diff --git a/packages/@aws-cdk/aws-s3/lib/bucket.ts b/packages/@aws-cdk/aws-s3/lib/bucket.ts index df76d4520044f..c69d907c11141 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 events = require('@aws-cdk/aws-events'); import iam = require('@aws-cdk/aws-iam'); import kms = require('@aws-cdk/aws-kms'); @@ -57,26 +56,20 @@ 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 props the properties of the new Action + * @param props the construction properties of the new Action * @returns the newly created {@link PipelineSourceAction} */ - addToPipeline(stage: actions.IStage, name: string, props: CommonPipelineSourceActionProps): PipelineSourceAction; + toCodePipelineSourceAction(props: CommonPipelineSourceActionProps): PipelineSourceAction; /** - * Convenience method for creating a new {@link PipelineDeployAction}, - * and adding it to the given Stage. + * Convenience method for creating a new {@link PipelineDeployAction}. * - * @param stage the Pipeline Stage to add the new Action to - * @param name the name of the newly created Action - * @param props the optional properties of the new Action + * @param props the construction properties of the new Action * @returns the newly created {@link PipelineDeployAction} */ - addToPipelineAsDeploy(stage: actions.IStage, name: string, props?: CommonPipelineDeployActionProps): PipelineDeployAction; + toCodePipelineDeployAction(props: CommonPipelineDeployActionProps): PipelineDeployAction; /** * Adds a statement to the resource policy for a principal (i.e. @@ -303,28 +296,17 @@ 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, - bucket: this, + public toCodePipelineSourceAction(props: CommonPipelineSourceActionProps): PipelineSourceAction { + return new PipelineSourceAction({ ...props, + bucket: this, }); } - public addToPipelineAsDeploy(stage: actions.IStage, name: string, props: CommonPipelineDeployActionProps = {}): PipelineDeployAction { - return new PipelineDeployAction(this, name, { - stage, - bucket: this, + public toCodePipelineDeployAction(props: CommonPipelineDeployActionProps): PipelineDeployAction { + return new PipelineDeployAction({ ...props, + bucket: this, }); } diff --git a/packages/@aws-cdk/aws-s3/lib/pipeline-actions.ts b/packages/@aws-cdk/aws-s3/lib/pipeline-actions.ts index 6dbb6fcfd24c0..2343c33d3c81e 100644 --- a/packages/@aws-cdk/aws-s3/lib/pipeline-actions.ts +++ b/packages/@aws-cdk/aws-s3/lib/pipeline-actions.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#toCodePipelineSourceAction}. */ export interface CommonPipelineSourceActionProps extends codepipeline.CommonActionProps { /** @@ -38,7 +38,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 */ @@ -49,31 +49,38 @@ 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 props: PipelineSourceActionProps; + + constructor(props: PipelineSourceActionProps) { + super({ + ...props, provider: 'S3', + outputArtifactName: props.outputArtifactName || `Artifact_${props.actionName}_${props.bucket.node.uniqueId}`, configuration: { S3Bucket: props.bucket.bucketName, S3ObjectKey: props.bucketKey, PollForSourceChanges: props.pollForSourceChanges, }, - ...props, }); - if (props.pollForSourceChanges === false) { - props.bucket.onPutObject(props.stage.pipeline.node.uniqueId + 'SourceEventRule', - props.stage.pipeline, props.bucketKey); + this.props = props; + } + + protected bind(stage: codepipeline.IStage, _scope: cdk.Construct): void { + if (this.props.pollForSourceChanges === false) { + this.props.bucket.onPutObject(stage.pipeline.node.uniqueId + 'SourceEventRule', + stage.pipeline, this.props.bucketKey); } // pipeline needs permissions to read from the S3 bucket - props.bucket.grantRead(props.stage.pipeline.role); + this.props.bucket.grantRead(stage.pipeline.role); } } /** * Common properties for creating {@link PipelineDeployAction} - * either directly, through its constructor, - * or through {@link IBucket#addToPipelineAsDeploy}. + * or through {@link IBucket#toCodePipelineDeployAction}. */ export interface CommonPipelineDeployActionProps extends codepipeline.CommonActionProps { /** @@ -91,14 +98,13 @@ export interface CommonPipelineDeployActionProps extends codepipeline.CommonActi /** * The inputArtifact to deploy to Amazon S3. */ - inputArtifact?: codepipeline.Artifact; + inputArtifact: codepipeline.Artifact; } /** * Construction properties of the {@link PipelineDeployAction S3 deploy Action}. */ -export interface PipelineDeployActionProps extends CommonPipelineDeployActionProps, - codepipeline.CommonActionConstructProps { +export interface PipelineDeployActionProps extends CommonPipelineDeployActionProps { /** * The Amazon S3 bucket that is the deploy target. */ @@ -109,8 +115,11 @@ export interface PipelineDeployActionProps extends CommonPipelineDeployActionPro * Deploys the sourceArtifact to Amazon S3. */ export class PipelineDeployAction extends codepipeline.DeployAction { - constructor(scope: cdk.Construct, id: string, props: PipelineDeployActionProps) { - super(scope, id, { + private readonly bucket: IBucket; + + constructor(props: PipelineDeployActionProps) { + super({ + ...props, provider: 'S3', artifactBounds: { minInputs: 1, @@ -123,9 +132,13 @@ export class PipelineDeployAction extends codepipeline.DeployAction { Extract: (props.extract === false) ? 'false' : 'true', ObjectKey: props.objectKey, }, - ...props, }); + + this.bucket = props.bucket; + } + + protected bind(stage: codepipeline.IStage, _scope: cdk.Construct): void { // pipeline needs permissions to write to the S3 bucket - props.bucket.grantWrite(props.stage.pipeline.role); + this.bucket.grantWrite(stage.pipeline.role); } }