How to work with manual approvals

In this guide we will demonstrate how to use the manual approval step.

Introduction

It’s sometimes necessary to pause a deployment and manually continue it later. Maybe we want to create and inspect a changeset before we update our resources, or maybe we simply want to run part of the pipeline in the afternoon and complete it in the morning. To accomplish this Attini provides the “AttiniManualApproval” step.

How to use

The AttiniManualApproval step is fairly straightforward to use. It does not require any properties, so it just looks like this:

- Name: PauseForManualApproval
  Type: AttiniManualApproval

When the deployment plan reaches the manual approval step it will pause until it is manually continued. To continue the deployment, use the attini deploy continue command. For the example above it would look like this if the distribution was called “manual-approval-demo”:

attini deploy continue -n manual-approval-demo --step-name PauseForManualApproval

The Attini CLI follow function will print the right command to the terminal when it reaches a manual approval step, so it is easiest to copy it from there and run it in a different shell.

Demo

In this Demo we will use the “AttiniManualApproval” step to pause the deployment plan so that we can inspect a changeset before deploying any resources. We will use the AWS CDK to create our resources and changeset, but we could use the same strategy for other tools, like Terraform or Pulumi. In order to use the CDK we will use an Attini Runner to interact with the CDK CLI (This guide makes heavy use of the Attini Runner, so I highly recommend reading the Runner guide if you have never used it before.)

The AWS CDK allows you to create cloud resources using different programming languages. After you have defined your resources in the language of your choice, the CDK will create CloudFormation stacks for your resources. We will only use two CDK commands in our deployment:

  1. cdk diff, to create the changeset.
  2. cdk deploy, to deploy the resources.
Create the project

To begin we need to create an attini configuration file, a deployment plan and a CDK project. This guide is not about working with the CDK, so we will not go into detail about how it works. In this example we will use the CDK with typescript to create an SNS Topic.

A complete example of the demo is available on GitHub.

Let’s create a folder for our distribution, and then create another folder to create our CDK project in.

mkdir "manual-approval-demo"
cd manual-approval-demo
mkdir cdk-project
cd cdk-project
cdk init --language typescript
cd ..

To create the SNS topic, update the “cdk-project/lib/cdk-project-stack.ts” file to look like this:

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as sns from 'aws-cdk-lib/aws-sns';

export class CdkProjectStack extends cdk.Stack {
    constructor(scope: Construct, id: string, props?: cdk.StackProps) {
        super(scope, id, props);


        const topic = new sns.Topic(this, 'Topic', {
            displayName: 'My sns topic',
        });
    }
}

We can keep the Attini configuration file pretty slim, we just to specify our distribution name and what file contains our deployment plan. The configuration file should be located in the root of our project.

attini-config.yaml

distributionName: attini-manual-approval-demo
initDeployConfig:
  template: deployment-plan.yaml
  stackName: ${environment}-${distributionName}-deployment-plan

Now let’s create our deployment plan template. The first thing it should do is to create our changeset. We will use the Attini Runner to do this. To create a changeset with the cdk diff command we need some permissions that are not a part of the Runners default permissions. We therefore need to provide our own role.

deployment-plan.yaml

AWSTemplateFormatVersion: "2010-09-09"
Transform:
  - AttiniDeploymentPlan
  - AWS::Serverless-2016-10-31

Resources:
  CdkRunner:
    Type: Attini::Deploy::Runner
    Properties:
      RoleArn: !GetAtt RunnerRole.Arn

  ManualApprovalDemo:
    Type: Attini::Deploy::DeploymentPlan
    Properties:
      DeploymentPlan:
        - Name: GetChangeSet
          Type: AttiniRunnerJob
          Properties:
            Runner: CdkRunner
            Commands:
              - bash get-change-set.sh

  # The role for our runner.
  RunnerRole:
    Type: AWS::IAM::Role
    Properties:
      Description: Attini runner task role
      Path: /attini/
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service: ecs-tasks.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: inline-policy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - cloudformation:DescribeStacks
                  - cloudformation:GetTemplate
                Resource:
                  - "*"
              - Effect: Allow
                Action:
                  - sts:AssumeRole
                Resource:
                  - !Sub arn:aws:iam::${AWS::AccountId}:role/cdk*
      ManagedPolicyArns:
        - !Sub arn:aws:iam::${AWS::AccountId}:policy/attini-runner-basic-execution-policy-${AWS::Region}

Wow, that was a lot of text 😬. To say that CloudFormation is a bit wordy is an understatement. Most of the text is the IAM Role, so we will discuss it a bit here and then never look at it again.

The “ManagedPolicyArns” property just makes sure that the Role includes all the permission the Runner needs to function. Because we are overriding its default permissions we need to provide this or add all the permissions ourselves. We are also adding the following permissions.

  1. cloudformation:DescribeStacks
  2. cloudformation:GetTemplate
  3. sts:AssumeRole

These are all needed by the CDK.

The deployment plan

Let’s forget about the IAM Role now and look at the deployment plan. Right now it only has one step, called “GetChangeSet”. This step executes a shell script called “get-change-set.sh”, so we need to create this script as well.

cd cdk-project
npm install
if cdk diff CdkProjectStack --fail; then
  echo no-change > ${ATTINI_OUTPUT}
else
  echo change-detected > ${ATTINI_OUTPUT}
fi

The script will perform the diff command and print “no-change” or “change-detected” to the steps output. The cdk diff command will print the changeset to Stdout, so we can see it in the terminal when we run the deployment plan.

Now we can add our manual approval step, but we probably don’t want to prompt for manual approval every time we deploy. It is enough if only to do it when there is a change. We can use the “Choice” step for this:

  ManualApprovalDemo:
    Type: Attini::Deploy::DeploymentPlan
    Properties:
      DeploymentPlan:
        - Name: GetChangeSet
          Type: AttiniRunnerJob
          Properties:
            Runner: CdkRunner
            Commands:
              - bash get-change-set.sh
        - Name: "Need approval?"
          Type: Choice
          Condition:
            Variable: $.output.GetChangeSet.result
            StringEquals: change-detected
          IsTrue:
            - Name: ManualApproval
              Type: AttiniManualApproval

The “Need approval?” step will check if the output for the previous step equals “change-detected”. If so, it will execute the “IsTrue” branch that contains our manual approval step.

So now we have added our manual approval step. All we need to do now is add the final step that will deploy our changes after we manually approved them. Let’s update our deployment plan one final time so it looks like this:

  ManualApprovalDemo:
    Type: Attini::Deploy::DeploymentPlan
    Properties:
      DeploymentPlan:
        - Name: GetChangeSet
          Type: AttiniRunnerJob
          Properties:
            Runner: CdkRunner
            Commands:
               - bash get-change-set.sh
        - Name: "Need approval?"
          Type: Choice
          Condition:
            Variable: $.output.GetChangeSet.result
            StringEquals: change-detected
          IsTrue:
            - Name: ManualApproval
              Type: AttiniManualApproval
        - Name: ApplyChanges
          Type: AttiniRunnerJob
          Properties:
            Runner: CdkRunner
            Commands:
              - cd cdk-project
              - npm install
              - cdk deploy

Because the different steps don’t share state, we have to run npm install again. In order for us to deploy with the CDK it has to be bootstrapped in our AWS account. If it is not, run the cdk bootstrap command, you only need to run the command once per account.

Let’s deploy the distribution and see if it works!

attini deploy run .
example pass data with script file

Success 🥳! We got our changeset and the deployment waits for us to approve it. We can copy paste the command from the terminal to continue:

 attini deploy continue -n attini-manual-approval-demo -e dev --step-name 'ManualApproval'

If we wanted to abort instead of continue, we simply add the --abort flag to the command.

In this example we used the Runners default image. It has both node and the CDK installed. However, it might give you a little trouble with versions etc, so you might need to tweak the package.json file or use your own image.

See the entire example project on GitHub.