From 51173d89d8e5d878edbafe35534e3d8397045ca8 Mon Sep 17 00:00:00 2001 From: kaizen3031593 <36202692+kaizen3031593@users.noreply.github.com> Date: Thu, 14 Oct 2021 12:17:48 -0400 Subject: [PATCH] docs(pipelines): make examples compile (#16963) Draft PR because I have not compiled the snippets in `ORIGINAL_API.md`. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/pipelines/ORIGINAL_API.md | 98 +++--- packages/@aws-cdk/pipelines/README.md | 310 ++++++++++++------ .../pipelines/lib/blueprint/shell-step.ts | 8 +- .../lib/codepipeline/codepipeline-source.ts | 9 +- .../pipelines/lib/main/pipeline-base.ts | 6 +- .../pipelines/rosetta/default.ts-fixture | 29 ++ 6 files changed, 304 insertions(+), 156 deletions(-) create mode 100644 packages/@aws-cdk/pipelines/rosetta/default.ts-fixture diff --git a/packages/@aws-cdk/pipelines/ORIGINAL_API.md b/packages/@aws-cdk/pipelines/ORIGINAL_API.md index 73ac108d9c67f..447e39bb09bc2 100644 --- a/packages/@aws-cdk/pipelines/ORIGINAL_API.md +++ b/packages/@aws-cdk/pipelines/ORIGINAL_API.md @@ -41,7 +41,9 @@ all commands necessary to do a full CDK build and synth, so do include installing dependencies and running the CDK CLI. For example, the old API: ```ts -SimpleSynthAction.standardNpmSynth({ +const sourceArtifact = new codepipeline.Artifact(); +const cloudAssemblyArtifact = new codepipeline.Artifact(); +pipelines.SimpleSynthAction.standardNpmSynth({ sourceArtifact, cloudAssemblyArtifact, @@ -54,8 +56,10 @@ SimpleSynthAction.standardNpmSynth({ Becomes: ```ts -new ShellStep('Synth', { - input: /* source */, +new pipelines.ShellStep('Synth', { + input: pipelines.CodePipelineSource.connection('my-org/my-app', 'main', { + connectionArn: 'arn:aws:codestar-connections:us-east-1:222222222222:connection/7d2469ff-514a-4e4f-9003-5ca4a43cdc41', // Created using the AWS console * });', + }), commands: [ 'npm ci', 'npm run build', @@ -71,7 +75,7 @@ You can use any of the factory functions on `CodePipelineSource`. For example, for a GitHub source, the following old API: ```ts -sourceAction: new codepipeline_actions.GitHubSourceAction({ +sourceAction: new cpactions.GitHubSourceAction({ actionName: 'GitHub', output: sourceArtifact, // Replace these with your actual GitHub project name @@ -84,8 +88,8 @@ sourceAction: new codepipeline_actions.GitHubSourceAction({ Translates into: ```ts -input: CodePipelineSource.gitHub('OWNER/REPO', 'main', { - authentication: SecretValue.secretsManager('GITHUB_TOKEN_NAME'), +input: pipelines.CodePipelineSource.gitHub('OWNER/REPO', 'main', { + authentication: cdk.SecretValue.secretsManager('GITHUB_TOKEN_NAME'), }), ``` @@ -111,8 +115,9 @@ putting manual approvals in `pre` steps, and automated approvals in `post` steps For example, specifying a manual approval on a stage deployment in old API: ```ts +declare const pipeline: pipelines.CdkPipeline; const stage = pipeline.addApplicationStage(...); -stage.addAction(new ManualApprovalAction({ +stage.addAction(new pipelines.ManualApprovalAction({ actionName: 'ManualApproval', runOrder: testingStage.nextSequentialRunOrder(), })); @@ -121,9 +126,10 @@ stage.addAction(new ManualApprovalAction({ Becomes: ```ts -pipeline.addStage(..., { +const stage = new MyApplicationStage(this, 'MyApplication'); +pipeline.addStage(stage, { pre: [ - new ManualApprovalStep('ManualApproval'), + new pipelines.ManualApprovalStep('ManualApproval'), ], }); ``` @@ -139,7 +145,7 @@ For example, specifying an automated approval after a stage is deployed in the f ```ts const stage = pipeline.addApplicationStage(...); -stage.addActions(new ShellScriptAction({ +stage.addActions(new pipelines.ShellScriptAction({ actionName: 'MyValidation', commands: ['curl -Ssf $VAR'], useOutputs: { @@ -153,10 +159,10 @@ stage.addActions(new ShellScriptAction({ Becomes: ```ts -const stage = new MyStage(...); +const stage = new MyApplicationStage(this, 'MyApplication'); pipeline.addStage(stage, { post: [ - new CodeBuildStep('MyValidation', { + new pipelines.CodeBuildStep('MyValidation', { commands: ['curl -Ssf $VAR'], envFromCfnOutput: { VAR: stage.cfnOutput, @@ -174,7 +180,19 @@ customizations (like `buildEnvironment`). #### Change set approvals In the old API, there were two properties that were used to add actions to the pipeline -in between the `CreateChangeSet` and `ExecuteChangeSet` actions: `manualApprovals` and `extraRunOrderSpace`. These are not supported in the new API. +in between the `CreateChangeSet` and `ExecuteChangeSet` actions: `manualApprovals` and `extraRunOrderSpace`. +This can be achieved in the modern API via the `stackSteps` property, which allows steps to be added +at the stack level: + +```ts +const stage = new MyApplicationStage(this, 'MyApplication'); +pipeline.addStage(stage, { + stackSteps: [{ + stack: stage.stack1, + changeSet: [new pipelines.ManualApprovalStep('ChangeSet Approval')], + }], +}); +``` ### Custom CodePipeline Actions @@ -190,7 +208,6 @@ artifacts: ```ts import { Construct, Stage, Stack, StackProps, StageProps } from '@aws-cdk/core'; -import { CdkPipeline } from '@aws-cdk/pipelines'; import * as codepipeline from '@aws-cdk/aws-codepipeline'; /** @@ -203,20 +220,20 @@ class MyPipelineStack extends Stack { const sourceArtifact = new codepipeline.Artifact(); const cloudAssemblyArtifact = new codepipeline.Artifact(); - const pipeline = new CdkPipeline(this, 'Pipeline', { + const pipeline = new pipelines.CdkPipeline(this, 'Pipeline', { cloudAssemblyArtifact, - sourceAction: new codepipeline_actions.GitHubSourceAction({ + sourceAction: new cpactions.GitHubSourceAction({ actionName: 'GitHub', output: sourceArtifact, - oauthToken: SecretValue.secretsManager('GITHUB_TOKEN_NAME'), + oauthToken: cdk.SecretValue.secretsManager('GITHUB_TOKEN_NAME'), // Replace these with your actual GitHub project name owner: 'OWNER', repo: 'REPO', branch: 'main', // default: 'master' }), - synthAction: SimpleSynthAction.standardNpmSynth({ + synthAction: pipelines.SimpleSynthAction.standardNpmSynth({ sourceArtifact, cloudAssemblyArtifact, @@ -274,21 +291,21 @@ class MyPipelineStack extends Stack { const sourceArtifact = new codepipeline.Artifact(); const cloudAssemblyArtifact = new codepipeline.Artifact(); - const pipeline = new CdkPipeline(this, 'Pipeline', { + const pipeline = new pipelines.CdkPipeline(this, 'Pipeline', { pipelineName: 'MyAppPipeline', cloudAssemblyArtifact, - sourceAction: new codepipeline_actions.GitHubSourceAction({ + sourceAction: new cpactions.GitHubSourceAction({ actionName: 'GitHub', output: sourceArtifact, - oauthToken: SecretValue.secretsManager('GITHUB_TOKEN_NAME'), + oauthToken: cdk.SecretValue.secretsManager('GITHUB_TOKEN_NAME'), // Replace these with your actual GitHub project name owner: 'OWNER', repo: 'REPO', branch: 'main', // default: 'master' }), - synthAction: SimpleSynthAction.standardNpmSynth({ + synthAction: pipelines.SimpleSynthAction.standardNpmSynth({ sourceArtifact, cloudAssemblyArtifact, @@ -316,7 +333,7 @@ If you prefer more control over the underlying CodePipeline object, you can create one yourself, including custom Source and Build stages: ```ts -const codePipeline = new cp.Pipeline(pipelineStack, 'CodePipeline', { +const codePipeline = new codepipeline.Pipeline(pipelineStack, 'CodePipeline', { stages: [ { stageName: 'CustomSource', @@ -330,7 +347,7 @@ const codePipeline = new cp.Pipeline(pipelineStack, 'CodePipeline', { }); const app = new App(); -const cdkPipeline = new CdkPipeline(app, 'CdkPipeline', { +const cdkPipeline = new pipelines.CdkPipeline(app, 'CdkPipeline', { codePipeline, cloudAssemblyArtifact, }); @@ -360,9 +377,9 @@ using these, the source repository does not need to have a `buildspec.yml`. An e of using `SimpleSynthAction` to run a Maven build followed by a CDK synth: ```ts -const pipeline = new CdkPipeline(this, 'Pipeline', { +const pipeline = new pipelines.CdkPipeline(this, 'Pipeline', { // ... - synthAction: new SimpleSynthAction({ + synthAction: new pipelines.SimpleSynthAction({ sourceArtifact, cloudAssemblyArtifact, installCommands: ['npm install -g aws-cdk'], @@ -396,16 +413,16 @@ from the CA repo instead of NPM. class MyPipelineStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { ... - const pipeline = new CdkPipeline(this, 'Pipeline', { + const pipeline = new pipelines.CdkPipeline(this, 'Pipeline', { ... - synthAction: SimpleSynthAction.standardNpmSynth({ + synthAction: pipelines.SimpleSynthAction.standardNpmSynth({ sourceArtifact, cloudAssemblyArtifact, // Use this to customize and a permissions required for the build // and synth rolePolicyStatements: [ - new PolicyStatement({ + new iam.PolicyStatement({ actions: ['codeartifact:*', 'sts:GetServiceBearerToken'], resources: ['arn:codeartifact:repo:arn'], }), @@ -477,7 +494,7 @@ const testingStage = pipeline.addApplicationStage(new MyApplication(this, 'Testi // Add a action -- in this case, a Manual Approval action // (for illustration purposes: testingStage.addManualApprovalAction() is a // convenience shorthand that does the same) -testingStage.addAction(new ManualApprovalAction({ +testingStage.addAction(new pipelines.ManualApprovalAction({ actionName: 'ManualApproval', runOrder: testingStage.nextSequentialRunOrder(), })); @@ -522,7 +539,7 @@ In its simplest form, adding validation actions looks like this: ```ts const stage = pipeline.addApplicationStage(new MyApplication(/* ... */)); -stage.addActions(new ShellScriptAction({ +stage.addActions(new pipelines.ShellScriptAction({ actionName: 'MyValidation', commands: ['curl -Ssf https://my.webservice.com/'], // Optionally specify a VPC if, for example, the service is deployed with a private load balancer @@ -563,7 +580,7 @@ const lbApp = new MyLbApplication(this, 'MyApp', { env: { /* ... */ } }); const stage = pipeline.addApplicationStage(lbApp); -stage.addActions(new ShellScriptAction({ +stage.addActions(new pipelines.ShellScriptAction({ // ... useOutputs: { // When the test is executed, this will make $URL contain the @@ -594,7 +611,7 @@ two ways. Either pass additional policy statements in the `rolePolicyStatements` property: ```ts -new ShellScriptAction({ +new pipelines.ShellScriptAction({ // ... rolePolicyStatements: [ new iam.PolicyStatement({ @@ -608,7 +625,7 @@ new ShellScriptAction({ The Action can also be used as a Grantable after having been added to a Pipeline: ```ts -const action = new ShellScriptAction({ /* ... */ }); +const action = new pipelines.ShellScriptAction({ /* ... */ }); pipeline.addStage('Test').addActions(action); bucket.grantRead(action); @@ -623,11 +640,11 @@ if they are executable shell scripts themselves). Pass the `sourceArtifact`: ```ts const sourceArtifact = new codepipeline.Artifact(); -const pipeline = new CdkPipeline(this, 'Pipeline', { +const pipeline = new pipelines.CdkPipeline(this, 'Pipeline', { // ... }); -const validationAction = new ShellScriptAction({ +const validationAction = new pipelines.ShellScriptAction({ actionName: 'TestUsingSourceArtifact', additionalArtifacts: [sourceArtifact], @@ -651,8 +668,8 @@ in the `ShellScriptAction`'s `additionalArtifacts`: const cloudAssemblyArtifact = new codepipeline.Artifact('CloudAsm'); const integTestsArtifact = new codepipeline.Artifact('IntegTests'); -const pipeline = new CdkPipeline(this, 'Pipeline', { - synthAction: SimpleSynthAction.standardNpmSynth({ +const pipeline = new pipelines.CdkPipeline(this, 'Pipeline', { + synthAction: pipelines.SimpleSynthAction.standardNpmSynth({ sourceArtifact, cloudAssemblyArtifact, buildCommands: ['npm run build'], @@ -666,7 +683,7 @@ const pipeline = new CdkPipeline(this, 'Pipeline', { // ... }); -const validationAction = new ShellScriptAction({ +const validationAction = new pipelines.ShellScriptAction({ actionName: 'TestUsingBuildArtifact', additionalArtifacts: [integTestsArtifact], // 'test.js' was produced from 'test/test.ts' during the synth step @@ -715,12 +732,11 @@ create an SNS Topic, subscribe your own email address, and pass it in via ```ts import * as sns from '@aws-cdk/aws-sns'; import * as subscriptions from '@aws-cdk/aws-sns-subscriptions'; -import * as pipelines from '@aws-cdk/pipelines'; const topic = new sns.Topic(this, 'SecurityChangesTopic'); topic.addSubscription(new subscriptions.EmailSubscription('test@email.com')); -const pipeline = new CdkPipeline(app, 'Pipeline', { /* ... */ }); +const pipeline = new pipelines.CdkPipeline(app, 'Pipeline', { /* ... */ }); const stage = pipeline.addApplicationStage(new MyApplication(this, 'PreProd'), { confirmBroadeningPermissions: true, securityNotificationTopic: topic, diff --git a/packages/@aws-cdk/pipelines/README.md b/packages/@aws-cdk/pipelines/README.md index 3013140344bef..bd6525542920b 100644 --- a/packages/@aws-cdk/pipelines/README.md +++ b/packages/@aws-cdk/pipelines/README.md @@ -43,14 +43,31 @@ CodePipeline engine, define a `CodePipeline` construct. The following example creates a CodePipeline that deploys an application from GitHub: ```ts -/** The stacks for our app are defined in my-stacks.ts. The internals of these +/** The stacks for our app are minimally defined here. The internals of these * stacks aren't important, except that DatabaseStack exposes an attribute * "table" for a database table it defines, and ComputeStack accepts a reference * to this table in its properties. */ -import { DatabaseStack, ComputeStack } from '../lib/my-stacks'; -import { Construct, Stage, Stack, StackProps, StageProps } from '@aws-cdk/core'; -import { CodePipeline, CodePipelineSource, ShellStep } from '@aws-cdk/pipelines'; +class DatabaseStack extends Stack { + public readonly table: dynamodb.Table; + + constructor(scope: Construct, id: string) { + super(scope, id); + this.table = new dynamodb.Table(this, 'Table', { + partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING } + }); + } +} + +interface ComputeProps { + readonly table: dynamodb.Table; +} + +class ComputeStack extends Stack { + constructor(scope: Construct, id: string, props: ComputeProps) { + super(scope, id); + } +} /** * Stack to hold the pipeline @@ -59,11 +76,11 @@ class MyPipelineStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); - const pipeline = new CodePipeline(this, 'Pipeline', { - synth: new ShellStep('Synth', { + const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { + synth: new pipelines.ShellStep('Synth', { // Use a connection created using the AWS console to authenticate to GitHub // Other sources are available. - input: CodePipelineSource.connection('my-org/my-app', 'main', { + input: pipelines.CodePipelineSource.connection('my-org/my-app', 'main', { connectionArn: 'arn:aws:codestar-connections:us-east-1:222222222222:connection/7d2469ff-514a-4e4f-9003-5ca4a43cdc41', // Created using the AWS console * });', }), commands: [ @@ -81,7 +98,7 @@ class MyPipelineStack extends Stack { env: { account: '123456789012', region: 'eu-west-1', - } + }, })); } } @@ -106,7 +123,7 @@ class MyApplication extends Stage { } // In your main file -new MyPipelineStack(app, 'PipelineStack', { +new MyPipelineStack(this, 'PipelineStack', { env: { account: '123456789012', region: 'eu-west-1', @@ -172,15 +189,25 @@ off temporarily, by passing `selfMutation: false` property, example: ```ts // Modern API -const pipeline = new CodePipeline(this, 'Pipeline', { +const modernPipeline = new pipelines.CodePipeline(this, 'Pipeline', { selfMutation: false, - ... + synth: new pipelines.ShellStep('Synth', { + input: pipelines.CodePipelineSource.connection('my-org/my-app', 'main', { + connectionArn: 'arn:aws:codestar-connections:us-east-1:222222222222:connection/7d2469ff-514a-4e4f-9003-5ca4a43cdc41', // Created using the AWS console * });', + }), + commands: [ + 'npm ci', + 'npm run build', + 'npx cdk synth', + ], + }), }); // Original API -const pipeline = new CdkPipeline(this, 'Pipeline', { +const cloudAssemblyArtifact = new codepipeline.Artifact(); +const originalPipeline = new pipelines.CdkPipeline(this, 'Pipeline', { selfMutating: false, - ... + cloudAssemblyArtifact, }); ``` @@ -204,10 +231,10 @@ commands required will depend on the programming language you are using. For a typical NPM-based project, the synth will look like this: ```ts -const source = /* the repository source */; +declare const source: pipelines.IFileSetProducer; // the repository source -const pipeline = new CodePipeline(this, 'Pipeline', { - synth: new ShellStep('Synth', { +const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { + synth: new pipelines.ShellStep('Synth', { input: source, commands: [ 'npm ci', @@ -224,8 +251,10 @@ CDK project lives in a subdirectory, be sure to adjust the `primaryOutputDirectory` to match: ```ts -const pipeline = new CodePipeline(this, 'Pipeline', { - synth: new ShellStep('Synth', { +declare const source: pipelines.IFileSetProducer; // the repository source + +const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { + synth: new pipelines.ShellStep('Synth', { input: source, commands: [ 'cd mysubdir', @@ -254,8 +283,10 @@ look like in a number of different situations. For Yarn, the install commands are different: ```ts -const pipeline = new CodePipeline(this, 'Pipeline', { - synth: new ShellStep('Synth', { +declare const source: pipelines.IFileSetProducer; // the repository source + +const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { + synth: new pipelines.ShellStep('Synth', { input: source, commands: [ 'yarn install --frozen-lockfile', @@ -270,8 +301,10 @@ For Python projects, remember to install the CDK CLI globally (as there is no `package.json` to automatically install it for you): ```ts -const pipeline = new CodePipeline(this, 'Pipeline', { - synth: new ShellStep('Synth', { +declare const source: pipelines.IFileSetProducer; // the repository source + +const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { + synth: new pipelines.ShellStep('Synth', { input: source, commands: [ 'pip install -r requirements.txt', @@ -288,8 +321,10 @@ and the Maven compilation step is automatically executed for you as you run `cdk synth`: ```ts -const pipeline = new CodePipeline(this, 'Pipeline', { - synth: new ShellStep('Synth', { +declare const source: pipelines.IFileSetProducer; // the repository source + +const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { + synth: new pipelines.ShellStep('Synth', { input: source, commands: [ 'npm install -g aws-cdk', @@ -314,7 +349,7 @@ You will first use the AWS Console to authenticate to the source control provider, and then use the connection ARN in your pipeline definition: ```ts -CodePipelineSource.connection('org/repo', 'branch', { +pipelines.CodePipelineSource.connection('org/repo', 'branch', { connectionArn: 'arn:aws:codestar-connections:us-east-1:222222222222:connection/7d2469ff-514a-4e4f-9003-5ca4a43cdc41', }); ``` @@ -328,9 +363,9 @@ you can change the name. The token should have the **repo** and **admin:repo_hoo scopes. ```ts -CodePipelineSource.gitHub('org/repo', 'branch', { +pipelines.CodePipelineSource.gitHub('org/repo', 'branch', { // This is optional - authentication: SecretValue.secretsManager('my-token'), + authentication: cdk.SecretValue.secretsManager('my-token'), }); ``` @@ -341,8 +376,8 @@ that the CodeCommit repository and then use `CodePipelineSource.codeCommit` to reference it: ```ts -const repository = codecommit.fromRepositoryName(this, 'Repository', 'my-repository'); -CodePipelineSource.codeCommit(repository); +const repository = codecommit.Repository.fromRepositoryName(this, 'Repository', 'my-repository'); +pipelines.CodePipelineSource.codeCommit(repository, 'main'); ``` ##### S3 @@ -352,7 +387,7 @@ triggered every time the file in S3 is changed: ```ts const bucket = s3.Bucket.fromBucketName(this, 'Bucket', 'my-bucket'); -CodePipelineSource.s3(bucket, 'my/source.zip'); +pipelines.CodePipelineSource.s3(bucket, 'my/source.zip'); ``` #### Additional inputs @@ -363,17 +398,17 @@ output file set can be used as an input, such as a `CodePipelineSource`, but also other `ShellStep`: ```ts -const prebuild = new ShellStep('Prebuild', { - input: CodePipelineSource.gitHub('myorg/repo1'), +const prebuild = new pipelines.ShellStep('Prebuild', { + input: pipelines.CodePipelineSource.gitHub('myorg/repo1', 'main'), primaryOutputDirectory: './build', commands: ['./build.sh'], }); -const pipeline = new CodePipeline(this, 'Pipeline', { - synth: new ShellStep('Synth', { - input: CodePipelineSource.gitHub('myorg/repo2'), +const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { + synth: new pipelines.ShellStep('Synth', { + input: pipelines.CodePipelineSource.gitHub('myorg/repo2', 'main'), additionalInputs: { - 'subdir': CodePipelineSource.gitHub('myorg/repo3'), + 'subdir': pipelines.CodePipelineSource.gitHub('myorg/repo3', 'main'), '../siblingdir': prebuild, }, @@ -389,6 +424,7 @@ more CDK `Stages` which will be deployed to their target environments. To do so, call `pipeline.addStage()` on the Stage object: ```ts +declare const pipeline: pipelines.CodePipeline; // Do this as many times as necessary with any account and region // Account and region may different from the pipeline's. pipeline.addStage(new MyApplicationStage(this, 'Prod', { @@ -421,6 +457,7 @@ deployed in sequence. For example, the following will deploy two copies of your application to `eu-west-1` and `eu-central-1` in parallel: ```ts +declare const pipeline: pipelines.CodePipeline; const europeWave = pipeline.addWave('Europe'); europeWave.addStage(new MyApplicationStage(this, 'Ireland', { env: { region: 'eu-west-1' } @@ -445,9 +482,19 @@ KMS key. Example: ```ts -const pipeline = new CodePipeline(this, 'Pipeline', { +const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { // Encrypt artifacts, required for cross-account deployments crossAccountKeys: true, + synth: new pipelines.ShellStep('Synth', { + input: pipelines.CodePipelineSource.connection('my-org/my-app', 'main', { + connectionArn: 'arn:aws:codestar-connections:us-east-1:222222222222:connection/7d2469ff-514a-4e4f-9003-5ca4a43cdc41', // Created using the AWS console * });', + }), + commands: [ + 'npm ci', + 'npm run build', + 'npx cdk synth', + ], + }), }); ``` @@ -464,19 +511,20 @@ a manual approval in the form of a `ManualApprovalStep` added to the pipeline. B pass in order to promote from the `PreProd` to the `Prod` environment: ```ts -const preprod = new MyApplicationStage(this, 'PreProd', { ... }); -const prod = new MyApplicationStage(this, 'Prod', { ... }); +declare const pipeline: pipelines.CodePipeline; +const preprod = new MyApplicationStage(this, 'PreProd'); +const prod = new MyApplicationStage(this, 'Prod'); pipeline.addStage(preprod, { post: [ - new ShellStep('Validate Endpoint', { + new pipelines.ShellStep('Validate Endpoint', { commands: ['curl -Ssf https://my.webservice.com/'], }), ], }); pipeline.addStage(prod, { pre: [ - new ManualApprovalStep('PromoteToProd'), + new pipelines.ManualApprovalStep('PromoteToProd'), ], }); ``` @@ -484,15 +532,29 @@ pipeline.addStage(prod, { You can also specify steps to be executed at the stack level. To achieve this, you can specify the stack and step via the `stackSteps` property: ```ts +class MyStacksStage extends Stage { + public readonly stack1: Stack; + public readonly stack2: Stack; + + constructor(scope: Construct, id: string, props?: StageProps) { + super(scope, id, props); + this.stack1 = new Stack(this, 'stack1'); + this.stack2 = new Stack(this, 'stack2'); + } +} + +declare const pipeline: pipelines.CodePipeline; +const prod = new MyStacksStage(this, 'Prod'); + pipeline.addStage(prod, { stackSteps: [{ stack: prod.stack1, - pre: [new ManualApprovalStep('Pre-Stack Check')], // Executed before stack is prepared - changeSet: [new ManualApprovalStep('ChangeSet Approval')], // Executed after stack is prepared but before the stack is deployed - post: [new ManualApprovalStep('Post-Deploy Check')], // Executed after staack is deployed + pre: [new pipelines.ManualApprovalStep('Pre-Stack Check')], // Executed before stack is prepared + changeSet: [new pipelines.ManualApprovalStep('ChangeSet Approval')], // Executed after stack is prepared but before the stack is deployed + post: [new pipelines.ManualApprovalStep('Post-Deploy Check')], // Executed after staack is deployed }, { stack: prod.stack2, - post: [new ManualApprovalStep('Post-Deploy Check')], // Executed after staack is deployed + post: [new pipelines.ManualApprovalStep('Post-Deploy Check')], // Executed after staack is deployed }], }); ``` @@ -507,21 +569,26 @@ To use Stack Outputs, expose the `CfnOutput` object you're interested in, and pass it to `envFromCfnOutputs` of the `ShellStep`: ```ts -class MyApplicationStage extends Stage { +class MyOutputStage extends Stage { public readonly loadBalancerAddress: CfnOutput; - // ... + + constructor(scope: Construct, id: string, props?: StageProps) { + super(scope, id, props); + this.loadBalancerAddress = new CfnOutput(this, 'Output', {value: 'value'}); + } } -const lbApp = new MyApplicationStage(this, 'MyApp', { /* ... */ }); +const lbApp = new MyOutputStage(this, 'MyApp'); +declare const pipeline: pipelines.CodePipeline; pipeline.addStage(lbApp, { post: [ - new ShellStep('HitEndpoint', { + new pipelines.ShellStep('HitEndpoint', { envFromCfnOutputs: { // Make the load balancer address available as $URL inside the commands URL: lbApp.loadBalancerAddress, }, commands: ['curl -Ssf $URL'], - }); + }), ], }); ``` @@ -539,12 +606,13 @@ Here's an example that captures an additional output directory in the synth step and runs tests from there: ```ts -const synth = new ShellStep('Synth', { /* ... */ }); -const pipeline = new CodePipeline(this, 'Pipeline', { synth }); +declare const synth: pipelines.ShellStep; +const stage = new MyApplicationStage(this, 'MyApplication'); +const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { synth }); -pipeline.addStage(/* ... */, { +pipeline.addStage(stage, { post: [ - new ShellStep('Approve', { + new pipelines.ShellStep('Approve', { // Use the contents of the 'integ' directory from the synth step as the input input: synth.addOutputDirectory('integ'), commands: ['cd integ && ./run.sh'], @@ -562,7 +630,9 @@ generated, use a `CodeBuildStep` instead of a `ShellStep`. This class has a numb of properties that allow you to customize various aspects of the projects: ```ts -new CodeBuildStep('Synth', { +declare const vpc: ec2.Vpc; +declare const mySecurityGroup: ec2.SecurityGroup; +new pipelines.CodeBuildStep('Synth', { // ...standard ShellStep props... commands: [/* ... */], env: { /* ... */ }, @@ -602,8 +672,20 @@ or just for the synth, asset publishing, and self-mutation projects by passing ` `assetPublishingCodeBuildDefaults`, or `selfMutationCodeBuildDefaults`: ```ts -new CodePipeline(this, 'Pipeline', { - // ... +declare const vpc: ec2.Vpc; +declare const mySecurityGroup: ec2.SecurityGroup; +new pipelines.CodePipeline(this, 'Pipeline', { + // Standard CodePipeline properties + synth: new pipelines.ShellStep('Synth', { + input: pipelines.CodePipelineSource.connection('my-org/my-app', 'main', { + connectionArn: 'arn:aws:codestar-connections:us-east-1:222222222222:connection/7d2469ff-514a-4e4f-9003-5ca4a43cdc41', // Created using the AWS console * });', + }), + commands: [ + 'npm ci', + 'npm run build', + 'npx cdk synth', + ], + }), // Defaults for all CodeBuild projects codeBuildDefaults: { @@ -644,15 +726,19 @@ doesn't have a matching class yet, you can define your own step class that exten Here's an example that adds a Jenkins step: ```ts -class MyJenkinsStep extends Step implements ICodePipelineActionFactory { - constructor(private readonly provider: codepipeline_actions.JenkinsProvider, private readonly input: FileSet) { +class MyJenkinsStep extends pipelines.Step implements pipelines.ICodePipelineActionFactory { + constructor( + private readonly provider: cpactions.JenkinsProvider, + private readonly input: pipelines.FileSet, + ) { + super('MyJenkinsStep'); } - public produceAction(stage: codepipeline.IStage, options: ProduceActionOptions): CodePipelineActionFactoryResult { + public produceAction(stage: codepipeline.IStage, options: pipelines.ProduceActionOptions): pipelines.CodePipelineActionFactoryResult { // This is where you control what type of Action gets added to the // CodePipeline - stage.addAction(new codepipeline_actions.JenkinsAction({ + stage.addAction(new cpactions.JenkinsAction({ // Copy 'actionName' and 'runOrder' from the options actionName: options.actionName, runOrder: options.runOrder, @@ -700,8 +786,13 @@ stacks the pipeline is deploying), for example by the use of `LinuxBuildImage.fr you need to pass `dockerEnabledForSelfMutation: true` to the pipeline. For example: ```ts -const pipeline = new CodePipeline(this, 'Pipeline', { - // ... +const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { + synth: new pipelines.ShellStep('Synth', { + input: pipelines.CodePipelineSource.connection('my-org/my-app', 'main', { + connectionArn: 'arn:aws:codestar-connections:us-east-1:222222222222:connection/7d2469ff-514a-4e4f-9003-5ca4a43cdc41', // Created using the AWS console * });', + }), + commands: ['npm ci','npm run build','npx cdk synth'], + }), // Turn this on because the pipeline uses Docker image assets dockerEnabledForSelfMutation: true, @@ -709,16 +800,16 @@ const pipeline = new CodePipeline(this, 'Pipeline', { pipeline.addWave('MyWave', { post: [ - new CodeBuildStep('RunApproval', { + new pipelines.CodeBuildStep('RunApproval', { commands: ['command-from-image'], buildEnvironment: { // The user of a Docker image asset in the pipeline requires turning on // 'dockerEnabledForSelfMutation'. - buildImage: LinuxBuildImage.fromAsset(this, 'Image', { + buildImage: codebuild.LinuxBuildImage.fromAsset(this, 'Image', { directory: './docker-image', - }) + }), }, - }) + }), ], }); ``` @@ -734,8 +825,13 @@ if you add a construct like `@aws-cdk/aws-lambda-nodejs`), you need to pass `dockerEnabledForSynth: true` to the pipeline. For example: ```ts -const pipeline = new CodePipeline(this, 'Pipeline', { - // ... +const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { + synth: new pipelines.ShellStep('Synth', { + input: pipelines.CodePipelineSource.connection('my-org/my-app', 'main', { + connectionArn: 'arn:aws:codestar-connections:us-east-1:222222222222:connection/7d2469ff-514a-4e4f-9003-5ca4a43cdc41', // Created using the AWS console * });', + }), + commands: ['npm ci','npm run build','npx cdk synth'], + }), // Turn this on because the application uses bundled file assets dockerEnabledForSynth: true, @@ -756,16 +852,21 @@ different environment (e.g., ECR repo) or to avoid throttling (e.g., DockerHub). ```ts const dockerHubSecret = secretsmanager.Secret.fromSecretCompleteArn(this, 'DHSecret', 'arn:aws:...'); const customRegSecret = secretsmanager.Secret.fromSecretCompleteArn(this, 'CRSecret', 'arn:aws:...'); -const repo1 = ecr.Repository.fromRepositoryArn(stack, 'Repo', 'arn:aws:ecr:eu-west-1:0123456789012:repository/Repo1'); -const repo2 = ecr.Repository.fromRepositoryArn(stack, 'Repo', 'arn:aws:ecr:eu-west-1:0123456789012:repository/Repo2'); +const repo1 = ecr.Repository.fromRepositoryArn(this, 'Repo', 'arn:aws:ecr:eu-west-1:0123456789012:repository/Repo1'); +const repo2 = ecr.Repository.fromRepositoryArn(this, 'Repo', 'arn:aws:ecr:eu-west-1:0123456789012:repository/Repo2'); -const pipeline = new CodePipeline(this, 'Pipeline', { +const pipeline = new pipelines.CodePipeline(this, 'Pipeline', { dockerCredentials: [ - DockerCredential.dockerHub(dockerHubSecret), - DockerCredential.customRegistry('dockerregistry.example.com', customRegSecret), - DockerCredential.ecr([repo1, repo2]); + pipelines.DockerCredential.dockerHub(dockerHubSecret), + pipelines.DockerCredential.customRegistry('dockerregistry.example.com', customRegSecret), + pipelines.DockerCredential.ecr([repo1, repo2]), ], - // ... + synth: new pipelines.ShellStep('Synth', { + input: pipelines.CodePipelineSource.connection('my-org/my-app', 'main', { + connectionArn: 'arn:aws:codestar-connections:us-east-1:222222222222:connection/7d2469ff-514a-4e4f-9003-5ca4a43cdc41', // Created using the AWS console * });', + }), + commands: ['npm ci','npm run build','npx cdk synth'], + }), }); ``` @@ -784,7 +885,7 @@ the **Synth**, **Self-Update**, and **Asset Publishing** actions within the ```ts const dockerHubSecret = secretsmanager.Secret.fromSecretCompleteArn(this, 'DHSecret', 'arn:aws:...'); // Only the image asset publishing actions will be granted read access to the secret. -const creds = DockerCredential.dockerHub(dockerHubSecret, { usages: [DockerCredentialUsage.ASSET_PUBLISHING] }); +const creds = pipelines.DockerCredential.dockerHub(dockerHubSecret, { usages: [pipelines.DockerCredentialUsage.ASSET_PUBLISHING] }); ``` ## CDK Environment Bootstrapping @@ -953,9 +1054,11 @@ give the synth CodeBuild execution role permissions to assume the bootstrapped lookup roles. As an example, doing so would look like this: ```ts -new CodePipeline(this, 'Pipeline', { - synth: new CodeBuildStep('Synth', { - input: // ...input... +new pipelines.CodePipeline(this, 'Pipeline', { + synth: new pipelines.CodeBuildStep('Synth', { + input: pipelines.CodePipelineSource.connection('my-org/my-app', 'main', { + connectionArn: 'arn:aws:codestar-connections:us-east-1:222222222222:connection/7d2469ff-514a-4e4f-9003-5ca4a43cdc41', // Created using the AWS console * });', + }), commands: [ // Commands to load cdk.context.json from somewhere here '...', @@ -1034,10 +1137,11 @@ Pipeline You can insert the security check by using a `ConfirmPermissionsBroadening` step: ```ts +declare const pipeline: pipelines.CodePipeline; const stage = new MyApplicationStage(this, 'MyApplication'); pipeline.addStage(stage, { pre: [ - new ConfirmPermissionsBroadening('Check', { stage }), + new pipelines.ConfirmPermissionsBroadening('Check', { stage }), ], }); ``` @@ -1047,17 +1151,14 @@ create an SNS Topic, subscribe your own email address, and pass it in as as the `notificationTopic` property: ```ts -import * as sns from '@aws-cdk/aws-sns'; -import * as subscriptions from '@aws-cdk/aws-sns-subscriptions'; -import * as pipelines from '@aws-cdk/pipelines'; - +declare const pipeline: pipelines.CodePipeline; const topic = new sns.Topic(this, 'SecurityChangesTopic'); topic.addSubscription(new subscriptions.EmailSubscription('test@email.com')); const stage = new MyApplicationStage(this, 'MyApplication'); pipeline.addStage(stage, { pre: [ - new ConfirmPermissionsBroadening('Check', { + new pipelines.ConfirmPermissionsBroadening('Check', { stage, notificationTopic: topic, }), @@ -1162,19 +1263,19 @@ that bundles asset using tools run via Docker, like `aws-lambda-nodejs`, `aws-la Make sure you set the `privileged` environment variable to `true` in the synth definition: -```typescript - const pipeline = new CdkPipeline(this, 'MyPipeline', { - ... - - synthAction: SimpleSynthAction.standardNpmSynth({ - sourceArtifact: ..., - cloudAssemblyArtifact: ..., - - environment: { - privileged: true, - }, - }), - }); +```ts +const sourceArtifact = new codepipeline.Artifact(); +const cloudAssemblyArtifact = new codepipeline.Artifact(); +const pipeline = new pipelines.CdkPipeline(this, 'MyPipeline', { + cloudAssemblyArtifact, + synthAction: pipelines.SimpleSynthAction.standardNpmSynth({ + sourceArtifact, + cloudAssemblyArtifact, + environment: { + privileged: true, + }, + }), +}); ``` After turning on `privilegedMode: true`, you will need to do a one-time manual cdk deploy of your @@ -1201,10 +1302,11 @@ This happens because the pipeline is not self-mutating and, as a consequence, th build projects get out-of-sync with the generated templates. To fix this, make sure the `selfMutating` property is set to `true`: -```typescript -const pipeline = new CdkPipeline(this, 'MyPipeline', { +```ts +const cloudAssemblyArtifact = new codepipeline.Artifact(); +const pipeline = new pipelines.CdkPipeline(this, 'MyPipeline', { selfMutating: true, - ... + cloudAssemblyArtifact, }); ``` @@ -1240,9 +1342,9 @@ $ env CDK_NEW_BOOTSTRAP=1 npx cdk bootstrap \ See https://docs.aws.amazon.com/cdk/latest/guide/bootstrapping.html for more info. ```ts -new MyStack(this, 'MyStack', { +new Stack(this, 'MyStack', { // Update this qualifier to match the one used above. - synthesizer: new DefaultStackSynthesizer({ + synthesizer: new cdk.DefaultStackSynthesizer({ qualifier: 'randchars1234', }), }); diff --git a/packages/@aws-cdk/pipelines/lib/blueprint/shell-step.ts b/packages/@aws-cdk/pipelines/lib/blueprint/shell-step.ts index 75c1883d92419..1f03105c78ee9 100644 --- a/packages/@aws-cdk/pipelines/lib/blueprint/shell-step.ts +++ b/packages/@aws-cdk/pipelines/lib/blueprint/shell-step.ts @@ -65,11 +65,11 @@ export interface ShellStepProps { * following configuration: * * ```ts - * const script = new ShellStep('MainScript', { - * // ... - * input: MyEngineSource.gitHub('org/source1'), + * const script = new pipelines.ShellStep('MainScript', { + * commands: ['npm ci','npm run build','npx cdk synth'], + * input: pipelines.CodePipelineSource.gitHub('org/source1', 'main'), * additionalInputs: { - * '../siblingdir': MyEngineSource.gitHub('org/source2'), + * '../siblingdir': pipelines.CodePipelineSource.gitHub('org/source2', 'main'), * } * }); * ``` diff --git a/packages/@aws-cdk/pipelines/lib/codepipeline/codepipeline-source.ts b/packages/@aws-cdk/pipelines/lib/codepipeline/codepipeline-source.ts index b6d10b03f2f67..382bed08028ff 100644 --- a/packages/@aws-cdk/pipelines/lib/codepipeline/codepipeline-source.ts +++ b/packages/@aws-cdk/pipelines/lib/codepipeline/codepipeline-source.ts @@ -26,7 +26,7 @@ export abstract class CodePipelineSource extends Step implements ICodePipelineAc * Pass in the owner and repository in a single string, like this: * * ```ts - * CodePipelineSource.gitHub('owner/repo', 'main'); + * pipelines.CodePipelineSource.gitHub('owner/repo', 'main'); * ``` * * Authentication will be done by a secret called `github-token` in AWS @@ -51,8 +51,8 @@ export abstract class CodePipelineSource extends Step implements ICodePipelineAc * Example: * * ```ts - * const bucket: IBucket = ... - * CodePipelineSource.s3(bucket, { + * declare const bucket: s3.Bucket; + * pipelines.CodePipelineSource.s3(bucket, { * key: 'path/to/file.zip', * }); * ``` @@ -74,7 +74,7 @@ export abstract class CodePipelineSource extends Step implements ICodePipelineAc * Example: * * ```ts - * CodePipelineSource.connection('owner/repo', 'main', { + * pipelines.CodePipelineSource.connection('owner/repo', 'main', { * connectionArn: 'arn:aws:codestar-connections:us-east-1:222222222222:connection/7d2469ff-514a-4e4f-9003-5ca4a43cdc41', // Created using the AWS console * }); * ``` @@ -131,7 +131,6 @@ export interface GitHubSourceOptions { * * ```ts * const oauth = cdk.SecretValue.secretsManager('my-github-token'); - * new GitHubSource(this, 'GitHubSource', { authentication: oauth, ... }); * ``` * * The GitHub Personal Access Token should have these scopes: diff --git a/packages/@aws-cdk/pipelines/lib/main/pipeline-base.ts b/packages/@aws-cdk/pipelines/lib/main/pipeline-base.ts index d69c2a6c89ab1..2f90df9de6f1a 100644 --- a/packages/@aws-cdk/pipelines/lib/main/pipeline-base.ts +++ b/packages/@aws-cdk/pipelines/lib/main/pipeline-base.ts @@ -95,9 +95,11 @@ export abstract class PipelineBase extends CoreConstruct { * Example: * * ```ts + * declare const pipeline: pipelines.CodePipeline; + * * const wave = pipeline.addWave('MyWave'); - * wave.addStage(new MyStage('Stage1', ...)); - * wave.addStage(new MyStage('Stage2', ...)); + * wave.addStage(new MyApplicationStage(this, 'Stage1')); + * wave.addStage(new MyApplicationStage(this, 'Stage2')); * ``` */ public addWave(id: string, options?: WaveOptions) { diff --git a/packages/@aws-cdk/pipelines/rosetta/default.ts-fixture b/packages/@aws-cdk/pipelines/rosetta/default.ts-fixture new file mode 100644 index 0000000000000..61a973840f007 --- /dev/null +++ b/packages/@aws-cdk/pipelines/rosetta/default.ts-fixture @@ -0,0 +1,29 @@ +// Fixture with packages imported, but nothing else +import { Construct, CfnOutput, Stage, Stack, StackProps, StageProps } from '@aws-cdk/core'; +import cdk = require('@aws-cdk/core'); +import codepipeline = require('@aws-cdk/aws-codepipeline'); +import cpactions = require('@aws-cdk/aws-codepipeline-actions'); +import codebuild = require('@aws-cdk/aws-codebuild'); +import codecommit = require('@aws-cdk/aws-codecommit'); +import dynamodb = require('@aws-cdk/aws-dynamodb'); +import ecr = require('@aws-cdk/aws-ecr'); +import ec2 = require('@aws-cdk/aws-ec2'); +import iam = require('@aws-cdk/aws-iam'); +import pipelines = require('@aws-cdk/pipelines'); +import secretsmanager = require('@aws-cdk/aws-secretsmanager'); +import sns = require('@aws-cdk/aws-sns'); +import subscriptions = require('@aws-cdk/aws-sns-subscriptions'); +import s3 = require('@aws-cdk/aws-s3'); + +class MyApplicationStage extends Stage { + constructor(scope: Construct, id: string, props?: StageProps) { + super(scope, id, props); + } +} + +class Fixture extends Stack { + constructor(scope: Construct, id: string) { + super(scope, id); + /// here + } +}