aws / aws-cdk

The AWS Cloud Development Kit is a framework for defining cloud infrastructure in code
https://aws.amazon.com/cdk
Apache License 2.0
11.68k stars 3.93k forks source link

(pipelines): Adding a ManualApprovalStep to a Wave #16130

Open markusl opened 3 years ago

markusl commented 3 years ago

It seems that the following code does not really add the ManualApprovalStep before the build action. What would be the proper way to do this with the new CDK Pipelines?

  pipeline.addWave('CustomWave', {
    pre: [new pipelines.ManualApprovalStep('ManualApproval')],
    post: [
      new pipelines.CodeBuildStep('CustomWaveAction', {
      projectName: 'CustomWaveActionBuild',
      commands: [
          // add rest of the commands here..
      ],
    })],
  });

Environment

Other


This is :bug: Bug Report

ryparker commented 3 years ago

Hey @markusl 👋🏻 I'm happy to help

I relabeled this to guidance until we can confirm this is broken functionality w/expected vs actual result reports. Let me try to reproduce this on my end and i'll update this with my results.

peterwoodworth commented 3 years ago

@ryparker any update on this?

ryparker commented 3 years ago

@markusl The pre and post params are used for defining actions you wish to run before/after deploying the Stages associated with the Wave.

_Param definitions from WaveOptions docs:_

pre: "Additional steps to run before any of the stages in the wave." post: "Additional steps to run after all of the stages in the wave."

Whenever you're initializing a Wave you should also define a Stage to associate with your Wave using the wave.addStage() method.

Waves will execute in this order:

  1. Execute actions defined in pre param
  2. Create change set for defined Stages
  3. Execute change set for defined Stages
  4. Execute actions defined in post param

Here is an example where I define a wave with both pre and post props like you have above. However I also create a new Stage that is used to define another stack with some resources.

const app = new App();
const stack = new Stack(app, "MyStack");

const pipeline = new CodePipeline(stack, "Pipeline", {
  pipelineName: "MyPipeline",
  selfMutation: false,
  synth: new ShellStep('Synth', {
    input: CodePipelineSource.gitHub('ryparker/aws-cdk-sample-pipeline', 'main'),
    commands: [
      'yarn install',
      'yarn build'
    ],
    primaryOutputDirectory: 'build/cloudformation',
  }),
});

const wave = pipeline.addWave('CustomWave', {
  pre: [new ManualApprovalStep('ManualApproval')],
  post: [
    new CodeBuildStep('CustomWaveAction', {
      projectName: 'CustomWaveActionBuild',
      commands: [
        'echo Hello World'
      ],
    })],
});

// New code: Adds a new Stage to be deployed by the Wave.
const lambdaStage = new Stage(stack, 'LambdaWaveStage');
const lambdaStack = new Stack(lambdaStage, 'lambdaWaveStack');
new Function(lambdaStack, 'MyFunction', {
  code: Code.fromInline('console.log("hello world");'),
  runtime: Runtime.NODEJS_14_X,
  handler: 'index.handler',
});
wave.addStage(lambdaStage);

Full repro code available here

CleanShot 2021-09-08 at 16 15 08@2x

markusl commented 3 years ago

@ryparker yeah, that is clear. However, we just need to run a CodeBuild action and have a manual approval gate just before it.

With previous pipelines this was possible by adding a Stage to the pipeline, and the required actions to the stage. In https://github.com/aws/aws-cdk/issues/15945 @rix0rrr explained that Stage and Stage are not the same anymore and we need to use a Wave when we don't have a stack to deploy.

Now the problem is that the pre and post-actions within a Wave do not seem to work very intuitively. Any ideas on how to proceed with this?

ryparker commented 3 years ago

@markusl I see what you're trying to implement. Unfortunately I don't believe the @aws-cdk/pipelines project supports adding custom CodePipeline stages, other than using escape hatches (which can get ugly here). Perhaps @rix0rrr knows better and can comment.

Assuming i'm correct, i'd like to offer an alternative solution that uses the @aws-cdk/aws-codepipeline package.

import { App, Stack, SecretValue } from "@aws-cdk/core";
import { Pipeline, Artifact } from '@aws-cdk/aws-codepipeline';
import { PipelineProject } from '@aws-cdk/aws-codebuild';
import { ShellScriptAction } from '@aws-cdk/pipelines';
import { ManualApprovalAction, GitHubSourceAction, CodeBuildAction } from '@aws-cdk/aws-codepipeline-actions';
import { PolicyStatement, Effect } from '@aws-cdk/aws-iam';

const app = new App();
const stack = new Stack(app, "AwsCodepipelinesStack");

const sourceArtifact = new Artifact('SourceCode');

new Pipeline(stack, 'Pipeline', {
  pipelineName: 'Pipeline-Using-AwsCodepipelines',
  stages: [
    {
      stageName: 'Source',
      actions: [
        new GitHubSourceAction({
          actionName: 'GitHub',
          oauthToken: SecretValue.secretsManager('github-token'),
          owner: 'ryparker',
          repo: 'aws-cdk-sample-pipeline',
          branch: 'main',
          output: sourceArtifact,
          runOrder: 1,
        }),
      ],
    },
    {
      stageName: 'Deploy',
      actions: [
        new ShellScriptAction({
          actionName: 'Deploy',
          additionalArtifacts: [sourceArtifact],
          commands: [
            'yarn install',
            'yarn run tsc',
            'yarn run cdk synth',
            'yarn run cdk deploy --all --require-approval never'
          ],
          rolePolicyStatements: [
            new PolicyStatement({
              effect: Effect.ALLOW,
              actions: ['*'],
              resources: ['*'],
            })
          ],
          runOrder: 1,
        })
      ]
    },
    {
      stageName: 'Post-Deploy-Actions',
      actions: [
        new ManualApprovalAction({
          actionName: 'Approve',
          runOrder: 1,
        }),
        new CodeBuildAction({
          actionName: 'CustomCodeBuildAction',
          input: sourceArtifact,
          project: new PipelineProject(stack, 'CustomProject'),
          runOrder: 2,
        }),
      ]
    }
  ],
});

My example pipeline executes in the following order:

  1. Source code from GitHub
  2. Install dependencies, build, synth, and deploy the CDK code
  3. Require a manual approval
  4. Execute a custom CodeBuild project

This pipeline implementation IMO is much easier to customize. Although it will require more code than the abstract @aws-cdk/pipelines's CodePipeline.

markusl commented 3 years ago

@ryparker I don't fully understand the limitation which is basically implemented here: https://github.com/aws/aws-cdk/blob/d499c85e4c09cc00b457ca7f2f4611a925ca8aeb/packages/%40aws-cdk/pipelines/lib/blueprint/stage-deployment.ts#L56-L60

What would be the problem of allowing adding Stages to the pipelines.CodePipeline which do not contain any Stacks but only CodeBuild or other actions?

ryparker commented 3 years ago

What would be the problem of allowing adding Stages to the pipelines.CodePipeline which do not contain any Stacks but only CodeBuild or other actions?

I don't have a great answer for this. Both CdkPipeline and CodePipeline from the @aws-cdk/pipelines package reduce the required code to implement a simple self mutating CDK pipeline. Ideally it should be easy to add something like a pipeline stage that includes a manual approval step however at the moment this is not supported. We continue to add features and improvements to these constructs so they will evolve with time.

As a workaround it is possible to use the Pipeline construct from @aws-cdk/aws-codepipeline to build out custom stages as I did in the example pipeline above.

Support for adding pipeline stages to CodePipeline would be a good feature request. We also appreciate any pull requests if you already have an idea of how this should be implemented.

markusl commented 3 years ago

Thanks for the reply, @ryparker. What you are proposing does not really sound like a workaround but a rewrite of the pipeline :)

The support for easily adding stages is already there, but for some reason, it is prevented by the code I linked before: https://github.com/aws/aws-cdk/blob/d499c85e4c09cc00b457ca7f2f4611a925ca8aeb/packages/%40aws-cdk/pipelines/lib/blueprint/stage-deployment.ts#L56-L60

None of the options seem very good to me:

The actual workaround we can use is adding two waves where the manual approval is in the first one, and the CodeBuild action is in the second one.

Is there any possibility you could look into this?

ryparker commented 3 years ago

The actual workaround we can use is adding two waves where the manual approval is in the first one, and the CodeBuild action is in the second one.

👍🏻 Thanks for providing the workaround.

Is there any possibility you could look into this?

We can certainly give this a look once we have bandwidth. I've changed this issue back to a bug report, unassigned, and prioritized it as p1. p1 means it has been prioritized as important, although please keep in mind that we do have a large number of issues at the moment. It may be some time before we are able to solve this particular issue. We use +1s to help us prioritize our work, and as always we are happy to take contributions if anyone is interested to pick this up and submit a PR.

rix0rrr commented 2 years ago

Classifying as p2 since there is a workaround

github-actions[bot] commented 1 year ago

This issue has not received any attention in 1 year. If you want to keep this issue open, please leave a comment below and auto-close will be canceled.

markusl commented 1 year ago

Still an issue.

NicolasGorga commented 1 year ago

Hello, is there any update on this? In my case I have a Lambda invoke action that i need to run as a Pre step and also as a Post step, right before a Manual Approval step that deploys everything to our production account. The thing is when i add it as a Post step, it is always being added as the first step (as if it were a Pre step).

@markusl have you had something like that happen to you?

oliverschenk commented 1 year ago

I have a similar issue, but not quite the same. I do have a stage that needs to be deployed, so I'm using addPost to add something else to the wave. However, I need to add more than one step in sequence, yet it generates a pipeline that runs the actions in parallel.

I'm also a bit confused about the addPost method on the StageDeployment class and another one on the Wave class. One says deploy after Stacks and one after Stage.

I ended up reading this in FULL. As in trying to absorb everything and it did fill some gaps about running in parallel and in sequence and creating dependencies between steps to force the pipeline to go in sequence. It's easy to miss so I suggest read it ALL. Maybe it will help...

https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.pipelines-readme.html

physcx commented 1 month ago

Sharing easy workaround if you don't want to create multiple waves, use dummy stacks, or model the step dependencies yourself between post and pre...

Extend the CodePipeline from "aws-cdk-lib/pipelines" with your subclass name and override the doPipelineBuild to add the deps between post and pre steps.

class MyPipeline extends CodePipeline {
  protected doBuildPipeline() {
    // For all waves, add an explicit step dependency for the wave's post steps on the wave's pre steps
    // to enforce ordering. This should probably be done implicitly by the CodePipeline class given the
    // naming. Without this dependency when there are no stages between pre and post steps they
    // run concurrently (no ordering).
    this.waves.forEach(wave =>
      wave.post.forEach(postStep =>
        wave.pre.forEach(preStep => postStep.addStepDependency(preStep))));
    super.doBuildPipeline();
  }
}

Now any wave pre steps will all run before any post steps in that same wave. This behavior probably should be the default in general as it is unexpected behavior for steps in pre to run concurrently with steps in post when using a wave without stages / stacks. When a wave has a stage this ordering gets added for you.