AWS Cloud Operations Blog

Automate suspension of an AWS CodePipeline release during critical events using AWS Systems Manager Change Calendar and Amazon EventBridge

In this blog post, I show you how to set up public holidays calendars using AWS Systems Manager Change Calendar and suspend your AWS CodePipeline pipelines during the critical holidays in these calendar events. For example, let’s say an application release pipeline in your AWS account builds and deploys a new version of the application after the changes are pushed to the code repository. On New Year’s Day, even if changes are pushed to the code repository, you don’t want a new build to be deployed as the team members might not be available to handle any issues or the application is business critical for New Year’s holiday.

Prerequisites

To complete the steps in this post, you need a release pipeline created using AWS CodePipeline. You can deploy this sample pipeline manually or by using this AWS CloudFormation template.

The template takes the following input parameters:

CodePipelineName — Name of the pipeline.

PublicSubnetId — Subnet ID with internet access.

SecurityGroupId — Security group ID that allows ingress and egress and HTTP and HTTPS connections.

EC2KeypairName — Name of EC2 key pair.

AmiId — AMI ID to launch the instance. The Amazon Linux 2 AMI is used by default.

To deploy the pipeline using CloudFormation, save the template’s contents to the deploycodepipeline.yaml file and execute the following command to create a CloudFormation stack. Replace the values in red with your own values.

Note: The code in this post is intended for the Linux command line interface (CLI). For Windows, replace any occurrences of  \ with ^

aws cloudformation create-stack --stack-name DeployPipeline \
--template-body file://deploycodepipeline.yaml \
--parameter ParameterKey=CodePipelineName,ParameterValue=myPipeline \
ParameterKey=PublicSubnetId,ParameterValue=subnet-xxxx \
ParameterKey=SecurityGroupId,ParameterValue=sg-xxxxxxx \
ParameterKey=EC2KeypairName,ParameterValue=ssh_keypair \
--capabilities CAPABILITY_IAM \
--region eu-west-1

Note: AWS CloudFormation uses a temporary session generated from your user credentials. Make sure the user who creates the stack has permissions to do so. You can also explicitly specify the role while executing the command. For information, see create-stack documentation in the AWS CLI Command Reference.

Solution overview

Systems Manager Change Calendar lets you set up date and time ranges (referred to as events) to specify when actions can be performed in your AWS account. When you create a Change Calendar entry, you are creating a Systems Manager document of the type Change Calendar that contains iCalendar 2.0 data in plaintext format. A Change Calendar entry can be one of two types:

  • Open by default (DEFAULT_OPEN): Actions that are tracking Change Calendar can run by default but are blocked from running during associated events. During events, the state of a DEFAULT_OPEN calendar is CLOSED.
  • Closed by default (DEFAULT_CLOSE): Actions that are tracking Change Calendar do not run by default but can run during events associated with the calendar entry. During events, the state of a DEFAULT_CLOSED calendar is OPEN.

Let’s use the software release example. On New Year’s Day, you don’t want a new build of your application to be released even if new changes are pushed to the code repository. Deployment on the other days of the week is fine. In other words, there is only one day when deployment must be blocked.

You create a Change Calendar entry of DEFAULT_OPEN  that allows deployments and then you create an event for New Year’s Day, which changes the calendar state to CLOSED during the event. You can monitor this change in the default state of the calendar with Amazon EventBridge rules that trigger the configured workflows.

In this blog post, you’ll create a public calendar of DEFAULT_OPEN with public holidays as calendar events. Using Amazon EventBridge, you’ll track the CLOSED events, causing a change in the calendar’s state and triggering an automation workflow to suspend the pipeline deployment.

The solution described in this blog post includes the following steps:

  1. Create a Systems Manager Change Calendar entry using the DEFAULT_OPEN type.
  2. Create a Systems Manager Automation document to suspend the pipeline release.
  3. Create an AWS Identity and Access Management (IAM) policy and a role to delegate permissions to the Systems Manager Automation document.
  4. Create an IAM role to delegate permissions to EventBridge events, which invokes the Systems Manager Automation document.
  5. Create an EventBridge rule to monitor the calendar’s state and trigger the Automation document when the calendar state changes.

Step 1: Create a Systems Manager Change Calendar entry using the DEFAULT_OPEN type

When you create a Change Calendar entry, you are creating a Systems Manager document of the type ChangeCalendar. These documents can be created using either the AWS Systems Manager console or the CreateDocument API call. You can copy the example public calendar documents in this GitHub repository to your local machine and create Change Calendar entries in your AWS account.

In this blog post, I use this public holidays calendar. First, copy the contents of any calendar file (.ics) and save the contents locally as SampleHolidays2021_AWS.ics. Then use the create-document AWS CLI command to create the calendar in your AWS account. For example:

aws ssm create-document --name PublicHolidays \
--content file://SampleHolidays2021_AWS.ics \
--document-type ChangeCalendar --document-format TEXT \
--region eu-west-1

After you create the calendar, you can review it in the Systems Manager console.

The PublicHolidays calendar is displayed for January 2021.

 

Step 2: Create a Systems Manager Automation document to suspend the pipeline release

In AWS CodePipeline, a pipeline has multiple stages. Each stage includes actions to be performed on the application artifacts. When the pipeline execution moves from one stage to another, it’s called a transition. You can disable a stage’s inbound transition to prevent executions from entering that stage.

To enable or disable this transition, create a Systems Manager Automation document, using the source to change your pipeline’s desired stage after taking input from the user. This document enables or disables the pipeline transition using EnableStageTransition and DisableStageTransition API calls.

The document takes the following input parameters:

AutomationAssumeRole — IAM role to execute the Automation document.

PipelineName — Name of the pipeline for which you want to enable or disable transitions.

DesiredState — Whether to enable or disable transition of the artifacts.

StageName — Name of the stage where you want to modify the inbound or outbound transition of artifacts.

Transition — Specifies whether artifacts are prevented from transitioning into the stage and being processed by the actions in that stage (inbound) or prevented from transitioning from the stage after they have been processed by the actions in that stage (outbound).

Reason — Reason for modifying the transition state.

description: >

  # Suspend CodePipeline

  This document suspends the transition of the pipelines stages created in AWS
  CodePipeline


  ## Parameters

  1. **_AutomationAssumeRole_**: (Optional) Provide the AWS IAM role that
  automation will use for the execution of the document. For more information,
  visit  the
  [link](https://docs.thinkwithwp.com/systems-manager/latest/userguide/automation-setup.html) 


  2. **_PipelineName_**: (Required) Provide the name of the pipeline in which
  you want to disable the flow of artifacts from one stage to another.


  3. **_StageName_**: (Required) The name of the stage where you want to disable
  the inbound or outbound transition of artifacts.


  4. **_TransitionType_**: (Required) Specifies whether artifacts are prevented
  from transitioning into the stage and being processed by the actions in that
  stage (inbound), or prevented from transitioning from the stage after they
  have been processed by the actions in that stage (outbound). Allowed values
  are 

  * *Inbound* 

  * *Outbound*


  5. **_Reason_**: (Required) The reason given to the user that a stage is
  disabled, such as waiting for manual approval or manual tests. This message is
  displayed in the pipeline console UI.

  6. **_DesiredState_**: (Required) Whether to enable or disable transition of the artifacts. Allowed values
  are 

  * *Enable* 

  * *Disable*

  ---
assumeRole: '{{AutomationAssumeRole}}'
schemaVersion: '0.3'
parameters:
  AutomationAssumeRole:
    default: ''
    description: >-
      (Optional) IAM role which Automation will assume to execute this automation. For more
      information, visit -
      https://docs.thinkwithwp.com/systems-manager/latest/userguide/automation-setup.html
    type: String
  PipelineName:
    description: >-
      (Required) Provide the name of the pipeline in which you want to disable
      the flow of artifacts from one stage to another.
    type: String
  StageName:
    description: >-
      (Required) The name of the stage where you want to disable the inbound or
      outbound transition of artifacts.
    type: String
  TransitionType:
    allowedValues:
      - Inbound
      - Outbound
    description: >-
      (Required) Specifies whether artifacts are prevented from transitioning
      into the stage and being processed by the actions in that stage (inbound),
      or prevented from transitioning from the stage after they have been
      processed by the actions in that stage (outbound)
    type: String
  DesiredState:
    allowedValues:
      - Enable
      - Disable
    description: Desired state of the pipeline
    type: String
  Reason:
    description: >-
      (Required) The reason given to the user that a stage is disabled, such as
      waiting for manual approval or manual tests. This message is displayed in
      the pipeline console UI.
    type: String
    default: Disabled due to Calendar state change
mainSteps:
  - name: BranchingOnDesiredState
    action: 'aws:branch'
    inputs:
      Choices:
        - StringEquals: Enable
          Variable: '{{DesiredState}}'
          NextStep: EnablingPipelineTransition
      Default: SuspendingPipelineTransition
    onFailure: Abort
    isCritical: true
    isEnd: true
  - name: SuspendingPipelineTransition
    action: 'aws:executeAwsApi'
    maxAttempts: 3
    inputs:
      pipelineName: '{{PipelineName}}'
      reason: '{{Reason}}'
      stageName: '{{StageName}}'
      Service: codepipeline
      Api: DisableStageTransition
      transitionType: '{{TransitionType}}'
    isCritical: true
    isEnd: true
  - name: EnablingPipelineTransition
    action: 'aws:executeAwsApi'
    maxAttempts: 3
    inputs:
      pipelineName: '{{PipelineName}}'
      stageName: '{{StageName}}'
      Service: codepipeline
      Api: EnableStageTransition
      transitionType: '{{TransitionType}}'
    isCritical: true
    isEnd: true

Note: The Reason input parameter in the Automation document cannot be blank while suspending the pipeline. See DisableStageTransition in the AWS CodePipeline API Reference.

 To create the Automation document, save the contents of this Automation document to a file named suspendPipelineAutomation.yaml. Save the file locally and then execute the following command:

aws ssm create-document --name SuspendCodePipeline \
--content file://suspendPipelineAutomation.yaml \
--document-type Automation \
--document-format YAML \
--region eu-west-1

Step 3: Create IAM policies and a role to delegate permissions to the Systems Manager Automation document

Next, create an IAM policy that provides the permissions required to enable and disable the pipeline stage transition. Copy and save the following contents to a file named codepipelinePolicy.json. Replace the ARN in red with the ARN of your pipeline.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "codepipeline:EnableStageTransition",
                "codepipeline:DisableStageTransition"
            ],
            "Resource": "arn:aws:codepipeline:eu-west-1:0123456789012:myPipeline/*"
        }
    ]
}

To use this policy, create an IAM role, AutomationExecutionRoleForCodePipeline, and then attach the policy to the role. This role has a trust relationship with AWS Systems Manager. Save the contents of the policy to a file named assumeRolePolicySSM.json.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ssm.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Execute the following command to create the IAM role. Make a note of the ARN.

aws iam create-role \
--role-name AutomationExecutionRoleForCodePipeline \
--assume-role-policy-document file://assumeRolePolicySSM.json

Use the following command to attach CodepipelineTransitionPolicy to the role.

aws iam put-role-policy \
--role-name AutomationExecutionRoleForCodePipeline \
--policy-name CodepipelineTransitionPolicy \
--policy-document file://codepipelinePolicy.json

Step 4: Create an IAM role to delegate permissions to EventBridge

 Create a second IAM role, EventBridgeInvocationRole. EventBridge uses this role to start the automation workflow. This role has two custom IAM policies:

  • CodePipelineStartAutomation, which allows EventBridge to start the automation when a Change Calendar state change event occurs.
  • CodePipelineAutomationPassRole, which allows EventBridge to pass the AutomationExecutionRoleForCodePipeline role to Systems Manager Automation.

Here is the content of the CodePipelineStartAutomation policy. Replace the values in red with the ARN of the Automation document you created in step 2 and save the contents locally in a file named codePipelineStartAutomation.json. For more information about Systems Manager resource ARNs, see Resources in the AWS Systems Manager User Guide.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "ssm:StartAutomationExecution",
            "Resource": "arn:aws:ssm:eu-west-1:0123456789012:automation-definition/SuspendCodePipeline:$DEFAULT"
        }
    ]
}

Here is the content of the CodePipelineAutomationPassRole policy. Replace the values in red with the ARN of the IAM role you created in step 3 and save the contents of the policy in a file named codePipelineAutomationPassRole.json.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "iam:PassRole",
            "Resource": "arn:aws:iam::0123456789012:role/AutomationExecutionRoleForCodePipeline"
        }
    ]
}

Copy and save the contents of the trust relationship with EventBridge in a file named eventbridgeAssumeRolepolicy.json.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "events.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Use the following command to create the IAM role. Make a note of the ARN.

aws iam create-role \
--role-name EventBridgeInvocationRole \
--assume-role-policy-document file://eventbridgeAssumeRolepolicy.json

Attach the policies to the role.

aws iam put-role-policy \
--role-name EventBridgeInvocationRole \
--policy-name CodePipelineStartAutomation \
--policy-document file://codePipelineStartAutomation.json
aws iam put-role-policy \
--role-name EventBridgeInvocationRole \
--policy-name CodePipelineAutomationPassRole \
--policy-document file://codePipelineAutomationPassRole.json

Step 5: Create an EventBridge rule to monitor the state of the calendar and use the Automation document as a target for this rule

 Next, create an EventBridge rule that watches for the event where the PublicHolidays calendar changes to the  CLOSED state. Replace the ARN in red with the ARN of the PublicHolidays calendar created in step 1.

aws events put-rule --name MonitoringClosedCalendarState \
--event-pattern "{\"source\":[\"aws.ssm\"],\"detail-type\":[\"Calendar State Change\"],\"resources\":[\"arn:aws:ssm:eu-west-1:0123456789012:document\/PublicHolidays\"],\"detail\":{\"state\":[\"CLOSED\"]}}" \
--region eu-west-1

Now add the SuspendCodePipeline Automation document you created in step 3 as a target. To pass parameters to this document, you can use the Input transformer feature in EventBridge to define multiple JSON paths from the event pattern text and store those values as a variable. These variables are then passed as an input to the event target, which is the SuspendCodePipeline Automation document.

The following is the resulting JSON output of a sample event pattern when a Change Calendar entry switches to the CLOSED state.

{
  "version": "0",
  "id": "a36ee795-9844-7e8b-02c8-a737651f54f1",
  "detail-type": "Calendar State Change",
  "source": "aws.ssm",
  "account": "0123456789012",
  "time": "2020-09-17T18:03:01Z",
  "region": "eu-west-1",
  "resources": ["arn:aws:ssm:eu-west-1:0123456789012:document/PublicHolidays"],
  "detail": {
    "state": "CLOSED",
    "atTime": "2020-12-25T00:00:00Z",
    "nextTransitionTime": "2020-12-26T00:00:00Z ",
    "event": "New Year’s Day"
  }
}

From this event JSON, the value of event is stored as the variable reason. In the Input transformer JSON,  InputPath is used to define variables and InputTemplate is used to pass these variables to the target Automation document:

 

"InputTransformer": {
      "InputPathsMap": {
        "reason": "$.detail.event"
      },
      "InputTemplate": "{\"AutomationAssumeRole\":[\"arn:aws:iam::0123456789012:role/AutomationExecutionRoleForCodePipeline\"],\"PipelineName\":[\"myPipeline\"],\"StageName\":[\"Deploy\"],\"TransitionType\":[\"Inbound\"],\"DesiredState\":[\"Disable\"],\"Reason\":[\"<reason>\"]}"

To add the target to the rule, use the put-targets AWS CLI command. To pass in the configuration for the target, copy and save the following content in a file named targetdetails.json. Replace the values in red with values for your environment.

[
    {
        "Id": "Target",
        "Arn": "arn:aws:ssm:eu-west-1:0123456789012:automation-definition/SuspendCodePipeline:$DEFAULT",
        "RoleArn": "arn:aws:iam::0123456789012:role/EventBridgeInvocationRole",
        "InputTransformer": {
            "InputPathsMap": {
                "reason": "$.detail.event"
            },
            "InputTemplate": "{\"AutomationAssumeRole\":[\"arn:aws:iam::0123456789012:role\/AutomationExecutionRoleForCodePipeline\"],\"PipelineName\":[\"myPipeline\"],\"StageName\":[\"Deploy\"],\"TransitionType\":[\"Inbound\"],\"DesiredState\":[\"Disable\"],\"Reason\":[<reason>]}"
        }
    }
]

Run the following command to create a target for the MonitoringClosedCalendarState event.

aws events put-targets --targets \
file://targetdetails.json \
--rule MonitoringClosedCalendarState \
--region eu-west-1

Step 6: (Optional) Create an EventBridge rule to monitor the calendar state and trigger the Automation document to start the pipeline when the calendar state reverts to default

The Automation document you created in step 2 can also be used to start the pipeline after the state of the Change Calendar returns to the default.

Create an EventBridge rule that monitors the OPEN state and triggers the Automation document.

aws events put-rule --name MonitoringOpenCalendarState \
--event-pattern "{\"source\":[\"aws.ssm\"],\"detail-type\":[\"Calendar State Change\"],\"resources\":[\"arn:aws:ssm:eu-west-1:0123456789012:document\/PublicHolidays\"],\"detail\":{\"state\":[\"OPEN\"]}}" \
--region eu-west-1

This Automation document enables or disables the pipeline based on the value of the DesiredState input parameter. To suspend the pipeline transition, the value is set to Disable. Now the input value to the parameter should be set to Enable. Here are the contents of  the targetdetails.json file. Replace the values in red with values for your environment.

[
    {
        "Id": "Target",
        "Arn": "arn:aws:ssm:eu-west-1:0123456789012:automation-definition/SuspendCodePipeline:$DEFAULT",
        "RoleArn": "arn:aws:iam::0133456789012:role/EventBridgeInvocationRole",
        "InputTransformer": {
            "InputPathsMap": {
                "reason": "$.detail.event"
            },
            "InputTemplate": "{\"AutomationAssumeRole\":[\"arn:aws:iam::0123456789012:role\/AutomationExecutionRoleForCodePipeline\"],\"PipelineName\":[\"myPipeline\"],\"StageName\":[\"Deploy\"],\"TransitionType\":[\"Inbound\"],\"DesiredState\":[\"Enable\"]}"
        }
    }
]

Execute the following command to create a target for the MonitoringOpenCalendarState event.

aws events put-targets --targets \
file://targetdetails.json \
--rule MonitoringOpenCalendarState \
--region eu-west-1

Test the solution

  1. In the left navigation pane of the AWS Systems Manager console, choose Change Calendar.
  2. Navigate to the PublicHolidays calendar entry.
  3. On the calendar entry’s details page, choose Create event.
  4. On the Create scheduled event page, in Event details, enter a display name for your event (for example, TestingCodePipelineBlog).
  • In Event start date and Event end date, enter or choose a day in the format MM/DD/YYYY to start the event, and then enter a time on the specified day in the format hh:mm:ss (hours, minutes, and seconds) to start the event.
  1. In Schedule time zone, choose a time zone that applies to the start and end times of the event.
  1. Choose Create scheduled event and wait for the event to start.
  2. In the left navigation pane, choose Automation and wait for the automation execution to complete.
  3. Open the AWS CodePipeline console and navigate to the pipeline. You’ll see that the stage transition has been disabled, as shown in Figure 2.

 Note: Pipeline execution IDs may vary.

The myPipeline pipeline displays Source and Deploy stages, both of which have a status of Succeeded. The arrow between the stages is grayed-out and the Enable transition button appears next to it.

Cleanup

After you test this solution, delete the pipeline and other resources to avoid continuing charges to your account.

To delete the pipeline

If you used the CloudFormation stack to create the pipeline, follow the instructions in . Deleting the stack will delete the resources. You must delete all of the objects in the S3 buckets first.

If you created the pipeline manually, follow the steps in Clean up resources.

To delete the other resources

To delete the EventBridge rules, follow the instructions in Deleting or Disabling an EventBridge Rule. To delete the IAM role, see Deleting roles or instance profiles. To delete the Change Calendar entry, follow the instructions in Delete a Change Calendar entry.

To delete the Automation document, use the delete-document CLI command.

Conclusion

In this blog post, I showed you how to create public calendars using Systems Manager Change Calendar, suspend the pipeline during any business-critical public events, and re-enable after the calendar state reverts to the default. You might want to set up a calendar entry to allow changes only on weekdays or during work hours to ensure administrator support is available to troubleshoot failed actions or deployments. You also might want to deploy event-specific marketing or promotional campaigns in an automated way through a Change Calendar entry and an Automation runbook.

For more information, see the Using AWS Systems Manager Change Calendar to prevent changes during critical events blog post, Automation walkthroughs, and Working with runbooks.

 

About the Authors


Saud ul Khalid is a Cloud Support Engineer in AWS Premium Support. He specializes in AWS Systems Manager, EC2 Image Builder and Amazon EC2 Linux. In his role, he enjoys helping customers solve complex issues and building automated workflows. Outside work, he loves to cook food for his family and friends, watch movies and go on hiking adventures with his friends.

 

 


Erik Weber is a World-wide Specialist Solutions Architect for AWS Management & Governance services. He specializes in AWS Systems Manager and AWS Config. Outside of work, Erik has a passion for hiking, cooking, and biking.