├── LICENSE ├── CountSnapshots.js ├── Notification.js ├── CopySnapshotToDR.js ├── DeleteOldSnapshots.js ├── TagSnapshotCopy.js ├── TagSnapshots.js ├── README.md ├── DR_RegionTemplate.yaml └── PrimaryRegionTemplate.yaml /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | -------------------------------------------------------------------------------- /CountSnapshots.js: -------------------------------------------------------------------------------- 1 | /* 2 | // Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // SPDX-License-Identifier: MIT-0. 4 | */ 5 | var AWS = require('aws-sdk'); 6 | var ec2 = new AWS.EC2(); 7 | 8 | exports.handler = (event, context, callback) => { 9 | 10 | console.log(JSON.stringify(event)); 11 | 12 | //Pull the volume id from the request 13 | var volume_id = event.originalVolumeId; 14 | 15 | console.log("volumeId: " + volume_id); 16 | var params = { 17 | Filters: [ 18 | { 19 | Name: 'tag:OriginalVolumeId', 20 | Values: [ 21 | volume_id 22 | ] 23 | }, 24 | { 25 | Name: "tag-key", 26 | Values: [ 27 | "AutomatedSnapName" 28 | ] 29 | } 30 | ] 31 | }; 32 | 33 | var numSnapshots; 34 | 35 | console.log("describeSnapshots parameters: " + params); 36 | ec2.describeSnapshots(params, function(err, data) { 37 | if (err) 38 | { 39 | console.log(err, err.stack); // an error occurred 40 | callback('Error in Finding Number of Snapshots'); 41 | } 42 | else 43 | { 44 | numSnapshots = data.Snapshots.length; 45 | console.log("Number of snapshots found = " + numSnapshots); 46 | data.numSnapshots = numSnapshots; 47 | 48 | //return the list of snapshots and the number of snapshots 49 | callback(null, data); 50 | } 51 | }); 52 | }; 53 | -------------------------------------------------------------------------------- /Notification.js: -------------------------------------------------------------------------------- 1 | /* 2 | // Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // SPDX-License-Identifier: MIT-0. 4 | */ 5 | var AWS = require('aws-sdk'); 6 | var sns = new AWS.SNS(); 7 | 8 | /* Lambda "main": Execution begins here */ 9 | exports.handler = function(event, context, callback) { 10 | console.log('Received event:', JSON.stringify(event, null, 2)); 11 | 12 | var messageInfo = buildErrorMessage(event.detail.source,event.errorMsg); 13 | sendSNSNotification(messageInfo.msg, messageInfo.sbj, callback); 14 | } 15 | 16 | /* 17 | * Send an email to an SNS Topic 18 | */ 19 | function buildErrorMessage(volumeid,errorMsg) { 20 | message = 'An error occurred when managing your snapshots for volumeid: ' + 21 | volumeid + '. ' + 22 | 'Please check the StepFunctions logs for failures. \n Error message: ' + 23 | errorMsg.Cause; 24 | subject = 'Snapshot Management Error'; 25 | return { 26 | msg: message, 27 | sbj: subject 28 | } 29 | } 30 | 31 | /* 32 | * Send an email to an SNS Topic 33 | */ 34 | function sendSNSNotification(message, subject, callback) { 35 | var params = { 36 | Message: message, /* required */ 37 | /* anotherKey: ... */ 38 | Subject: subject, 39 | TopicArn: process.env.notificationTopic 40 | }; 41 | console.log('Message to publish to SNS:' + message); 42 | sns.publish(params, function(err, data) { 43 | if (err) callback(err); // an error occurred 44 | else callback(null, data); // successful response 45 | }); 46 | } 47 | -------------------------------------------------------------------------------- /CopySnapshotToDR.js: -------------------------------------------------------------------------------- 1 | /* 2 | // Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // SPDX-License-Identifier: MIT-0. 4 | */ 5 | // Sample Lambda function to copy an EBS snapshot to a different region 6 | var AWS = require('aws-sdk'); 7 | var ec2 = new AWS.EC2(); 8 | 9 | exports.handler = (event, context, callback) => { 10 | 11 | //pull the destination region from the environment variables 12 | var destinationRegion = process.env.destRegion; 13 | 14 | //pull the source region from the source volume-id -> "arn:aws:ec2::region:volume/volume-id" 15 | var sourceRegion = event.detail.source.substring(event.detail.source.indexOf('::') + 2); 16 | sourceRegion = sourceRegion.substring(0,sourceRegion.indexOf(":")); 17 | console.log("source region: " + sourceRegion); 18 | 19 | // Get the EBS snapshot ID and volume ID from the CloudWatch event details 20 | var snapshotId = event.detail.snapshot_id.substring( 21 | event.detail.snapshot_id.indexOf('/') + 1); 22 | var volumeId = event.detail.source.substring( 23 | event.detail.source.indexOf('/') + 1) 24 | 25 | //Add information regarding the original volume to the description 26 | // so have access to it in DR region 27 | const description = `${snapshotId}**${volumeId}**${sourceRegion}`; 28 | console.log ("snapshotId:", snapshotId); 29 | 30 | // Load EC2 class and update the configuration to use destination region 31 | // to initiate the snapshot. 32 | // **Note copySnapshot commands are performed against the destination region 33 | AWS.config.update({region: destinationRegion}); 34 | var ec2 = new AWS.EC2(); 35 | 36 | // Prepare variables for ec2.modifySnapshotAttribute call 37 | const copySnapshotParams = { 38 | Description: description, 39 | DestinationRegion: destinationRegion, 40 | SourceRegion: sourceRegion, 41 | SourceSnapshotId: snapshotId 42 | }; 43 | 44 | // Execute the copy snapshot and log any errors 45 | ec2.copySnapshot(copySnapshotParams, (err, data) => { 46 | if (err) { 47 | const errorMessage = `Error copying snapshot ${snapshotId} to region ${destinationRegion}.`; 48 | console.log(errorMessage); 49 | console.log(err); 50 | callback(errorMessage); 51 | } else { 52 | const successMessage = `Successfully started copy of snapshot ${snapshotId} to region ${destinationRegion}.`; 53 | console.log(successMessage); 54 | console.log(data); 55 | callback(null, successMessage); 56 | } 57 | }); 58 | }; 59 | -------------------------------------------------------------------------------- /DeleteOldSnapshots.js: -------------------------------------------------------------------------------- 1 | /* 2 | // Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // SPDX-License-Identifier: MIT-0 4 | */ 5 | var AWS = require('aws-sdk'); 6 | var ec2 = new AWS.EC2(); 7 | 8 | exports.handler = (event, context, callback) => { 9 | 10 | //Pull the list of Snapshots from the event 11 | var snapshots = event.snapshotList.Snapshots; 12 | 13 | /* 14 | Need to ensure the list of snapshots are sorted by date in descending order 15 | 16 | return positive number is a is older than b 17 | return negative if a is more recent than b 18 | return 0 if equivalent 19 | */ 20 | snapshots.sort(function(a, b) { 21 | var dateA = new Date(a.StartTime); //convert to date 22 | var dateB = new Date(b.StartTime); // convert to date 23 | if (dateA < dateB) { 24 | return 1; 25 | } 26 | if (dateA > dateB) { 27 | return -1; 28 | } 29 | 30 | // dates must be equal 31 | console.log("[WARN] - Snapshot dates are equal - may be issue with snapshot creation"); 32 | return 0; 33 | }); 34 | 35 | var numSnapshotsDeleted = 0; 36 | var retention = parseInt(process.env.retentionPeriod); 37 | var deletedSnapshots = []; 38 | 39 | if((retention - event.snapshotList.numSnapshots) < 0) 40 | { 41 | //Snapshots are sorted in descending order 42 | var snapshotsToDelete = snapshots.slice(retention - event.snapshotList.numSnapshots); 43 | console.log("Snapshots to Delete: " + JSON.stringify(snapshotsToDelete)); 44 | 45 | //delete the snapshots 46 | snapshotsToDelete.forEach(function(snapshot) { 47 | var params = { 48 | SnapshotId: snapshot.SnapshotId 49 | }; 50 | console.log("deleting snapshot:" + snapshot.SnapshotId); 51 | deletedSnapshots.push(ec2.deleteSnapshot(params).promise()); 52 | }); 53 | 54 | Promise.all(deletedSnapshots).then(values => { 55 | console.log("Number of deleted snapshots: " + values.length); 56 | callback(null, values.length); 57 | }, reason => { 58 | console.log("[ERROR] - Error Deleting snapshot"); 59 | callback("[ERROR]-Error Deleting snapshot"); 60 | }); 61 | } 62 | else 63 | { 64 | //you shouldn't be here! 65 | var msg = "[ERROR] - You shouldn't be here, check that your retention " + 66 | "period defined in the environment variable matches the value for the retention defined in your state machine."; 67 | console.log(msg); 68 | callback(msg); 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /TagSnapshotCopy.js: -------------------------------------------------------------------------------- 1 | /* 2 | // Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // SPDX-License-Identifier: MIT-0. 4 | */ 5 | var AWS = require('aws-sdk'); 6 | var ec2 = new AWS.EC2(); 7 | 8 | exports.handler = (event, context, callback) => { 9 | 10 | // pull snapshot id 11 | var snapshotId = event.detail.snapshot_id.substring(event.detail.snapshot_id.indexOf('/') + 1); 12 | var params = { 13 | SnapshotIds: [snapshotId] 14 | }; 15 | 16 | console.log(params); 17 | ec2.describeSnapshots(params, function(err, data) { 18 | if (err) 19 | { 20 | console.log(err, err.stack); // an error occurred 21 | callback('[ERROR] - Error in Describing Snapshot: ' + snapshotId); 22 | } 23 | else 24 | { 25 | var snapshot = data.Snapshots[0]; 26 | var description = snapshot.Description; 27 | var originRegionDataArray = description.split("**"); 28 | 29 | 30 | /* 31 | If the description is not formatted: snapshot**volume**region 32 | then the snapshot did not originate from the Step Function state machine 33 | in the primary region, so we'll make the assumption it falls outside 34 | of this backup regimen and skip it 35 | Format defined in file: CopySnapshotToDR.js 36 | */ 37 | if(originRegionDataArray.length !=3){ 38 | callback(null, "VOLUME_NOT_TAGGED"); 39 | } 40 | var originalVolumeId = originRegionDataArray[1]; 41 | var originalRegion = originRegionDataArray[2]; 42 | console.log(originalVolumeId); 43 | console.log(originalRegion); 44 | 45 | tagSnapshot(event,snapshotId,originalVolumeId, originalRegion, callback); 46 | } 47 | }); 48 | }; 49 | 50 | function tagSnapshot(event, snapshotId, originalVolumeId, originalRegion, callback) 51 | { 52 | var name = originalVolumeId + '_' + event.detail.startTime; 53 | var tagParams = { 54 | Resources: [ 55 | snapshotId 56 | ], 57 | Tags: [ 58 | { 59 | Key: "AutomatedSnapName", 60 | Value: name 61 | }, 62 | { 63 | Key: "OriginalVolumeId", 64 | Value: originalVolumeId 65 | }, 66 | { 67 | Key: "OriginalRegion", 68 | Value: originalRegion 69 | } 70 | ] 71 | }; 72 | console.log(tagParams); 73 | ec2.createTags(tagParams, function(err, data) { 74 | if (err) 75 | { 76 | console.log("[ERROR]" + err, err.stack); // an error occurred 77 | callback(err); 78 | } 79 | else 80 | { 81 | console.log(data); // successful response 82 | callback(null, originalVolumeId); 83 | } 84 | }); 85 | }; 86 | -------------------------------------------------------------------------------- /TagSnapshots.js: -------------------------------------------------------------------------------- 1 | /* 2 | // Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | // SPDX-License-Identifier: MIT-0. 4 | */ 5 | var AWS = require('aws-sdk'); 6 | var ec2 = new AWS.EC2(); 7 | 8 | exports.handler = (event, context, callback) => { 9 | 10 | console.log(JSON.stringify(event)); 11 | 12 | var volumeId = event.detail.source.substring(event.detail.source.indexOf('/') + 1); 13 | var snapshotId = event.detail.snapshot_id.substring(event.detail.snapshot_id.indexOf('/') + 1); 14 | var name = volumeId + '_' + event.detail.startTime; 15 | 16 | console.log('volume Id: ' + volumeId); 17 | console.log('snapshot Id: ' + snapshotId); 18 | 19 | var params = { 20 | Resources: [ 21 | snapshotId 22 | ], 23 | Tags: [{ 24 | Key: "AutomatedSnapName", 25 | Value: name 26 | }, 27 | { 28 | Key: "OriginalVolumeId", 29 | Value: volumeId 30 | } 31 | ] 32 | }; 33 | 34 | determineValidTags(volumeId, callback, function() { 35 | ec2.createTags(params, function(err, data) { 36 | if (err) { 37 | console.log(err, err.stack); // an error occurred 38 | callback(err); 39 | } else { 40 | console.log(data); // successful response 41 | callback(null, volumeId); 42 | } 43 | }); 44 | }); 45 | }; 46 | 47 | function determineValidTags(volumeId, lambdaCallback, callback) { 48 | 49 | /* 50 | If a tagKey environment variable exists, then that indicates we should 51 | only perform the backup procedures on snapshots with that tagKey 52 | */ 53 | var tagToInclude = process.env.tagKey; 54 | if (tagToInclude != null && tagToInclude != "none") { 55 | //Pull the description of the volume 56 | var params = { 57 | VolumeIds: [volumeId], 58 | Filters: [{ 59 | Name: "tag-key", 60 | Values: [ 61 | tagToInclude 62 | ] 63 | }] 64 | }; 65 | 66 | console.log("describeVolumes parameters: " + params); 67 | ec2.describeVolumes(params, function(err, data) { 68 | if (err) { 69 | console.log(err, err.stack); // an error occurred 70 | lambdaCallback('Error in Finding Original Volume details for: ' + volumeId); 71 | } else { 72 | var numVolumes = data.Volumes.length; 73 | console.log("Number of volumes found = " + numVolumes); 74 | 75 | // This indicates that the tagKey environment variable was specified 76 | // AND the current snapshot did NOT originate from a volume that contained 77 | // that tag, so we will move on 78 | if (numVolumes == 0) { 79 | console.log("The volume does not contain the specified tag: " + tagToInclude + 80 | ". Exiting the function."); 81 | lambdaCallback(null, "VOLUME_NOT_TAGGED"); 82 | } 83 | 84 | callback(); 85 | } 86 | }); 87 | } 88 | else { 89 | callback(); 90 | } 91 | }; 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aws-step-functions-ebs-snapshot-mgmt 2 | 3 | Example architecture for integrating AWS Step Functions and Amazon CloudWatch Events. 4 | 5 | The Snapshot Management Application ([diagram](https://s3-eu-west-1.amazonaws.com/step-functions-ref-archs-eu-west-1/DRandBackupArchDiagram.pdf)) demonstrates how to use [AWS Step Functions](https://aws.amazon.com/step-functions/) in conjunction with [Amazon CloudWatch Events](https://aws.amazon.com/cloudwatch/details/#events), [AWS Lambda](http://aws.amazon.com/lambda/), and [AWS CloudFormation](https://aws.amazon.com/cloudformation/) to build a serverless solution for [Amazon Elastic Block Store](https://aws.amazon.com/ebs/) (EBS) snapshot lifecycle management. 6 | 7 | The application assumes that you already are using something to schedule the creation of snapshots for your EBS volumes, and the reference architecture initiates once those snapshots are completed. We'll setup an Amazon CloudWatch Event that will trigger on the completion of the snapshot creation. The target for the CloudWatch event is an AWS Step Functions state machine. The state machine coordinates different steps in the EBS snapshot management, including deleting snapshots past the retention period specified, and copying snapshots to a Disaster Recovery (DR) region. We deploy another state machine in the DR region that performs similar steps for the snapshots that are copied into the DR region. 8 | 9 | This repository contains sample code for all the AWS Lambda functions that the AWS Step Functions state machines invoke, and AWS Serverless Application Model (SAM) templates for deploying the Lambda functions and the state machines. The Amazon CloudWatch Events rules matching EBS snapshot events to target state machines can be created using a manual creation via the CloudWatch Events console or the AWS Command Line Interface (CLI). 10 | 11 | 12 | ## Deploying the example 13 | 14 | Because the example involves resources in two regions, the primary region and DR region, there are two stacks that are needed to launch the application. 15 | 16 | The first stack is executed in the primary region. The **Launch Stack** button below will launch the template for the primary region in the eu-west-1 (Ireland) region in your account: 17 | 18 | [![Launch EBS Snapshot Management into Ireland with CloudFormation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/images/cloudformation-launch-stack-button.png)](https://console.aws.amazon.com/cloudformation/home?region=eu-west-1#/stacks/new?stackName=step-functions-ebs-mgmt-primary&templateURL=https://s3-eu-west-1.amazonaws.com/step-functions-ref-archs-eu-west-1/PrimaryRegionTemplateV2.json) 19 | 20 | The second stack is executed in the DR region. The **Launch Stack** button below will launch the template for the DR region in the us-east-2 (Ohio) region in your account: 21 | 22 | [![Launch EBS Snapshot Management into Ohio with CloudFormation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/images/cloudformation-launch-stack-button.png)](https://console.aws.amazon.com/cloudformation/home?region=us-east-2#/stacks/new?stackName=step-functions-ebs-mgmt-dr&templateURL=https://s3.us-east-2.amazonaws.com/step-functions-ref-archs-east-2/DRRegionTemplateV2.json) 23 | 24 | After the stack is successfully created, you can test the configuration by following the instructions in the **Testing the example** section. 25 | 26 | 27 | ## Configuration Options 28 | 29 | There are a few different configuration options for controlling the Snapshot Management architecture. 30 | 31 | 1. **Configure Notifications for Failures**: Whenever a failure is detected in the state machine execution (or AWS Lambda functions it invokes) the state machine executes a Lambda function that sends a notification to an [Amazon Simple Notification Service](https://aws.amazon.com/sns/) (SNS) topic. To receive emails when a failure occurs, [add an email subscription](http://docs.aws.amazon.com/sns/latest/dg/SubscribeTopic.html) to the **SnapshotMgmtTopic**. 32 | 33 | 2. **Configure Volumes to Include**: If you only want certain volumes to be included in the snapshot management workflow, you can specify a Tag key that a volume must have in order for it to be included. If a Tag key isn't specified, then the 34 | snapshot management will take place for all snapshot creations. If you would like to specify a Tag key, either: 35 | - After the CloudFormation stack has completed in your primary region. Follow 36 | these steps to modify it: 37 | - Go to **Services** -> **Lambda** 38 | - Select the **TagSnapshots** function 39 | - On the **Code** tab, scroll to the bottom and in the **Environment Variables** 40 | section, fill in the tagKey environment variable with the value of your tag key 41 | you want to perform snapshot management for. 42 | - Edit the PrimaryRegionTemplate.yaml prior to deployment (if you are following the steps in the section that describes **How to customize and run the architecture in your account**). 43 | You will modify the tagKeyValue Default value in that file. 44 | ``` 45 | tagKeyValue: 46 | Description: 'The value for the key tag that you want all volumes to have for the snapshot management to apply.' 47 | Type: 'String' 48 | Default: 'none' 49 | ``` 50 | 51 | ## Testing the example 52 | 53 | The application can be tested by performing the following steps: 54 | 55 | 1. Login to the **AWS Management Console**. 56 | 2. In the upper right hand corner, choose the primary region (Ireland). 57 | 3. From the **Services** menu in the top left, choose Amazon EC2. 58 | 4. Click **Volumes** from the menu on the left side. 59 | 5. If you do not already have a volume, [create a volume](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-creating-volume.html). 60 | 6. Select the volume you want to snapshot, select **Create Snapshot** from the **Actions** drop-down menu. 61 | 62 | This will start the state machine. You can see the completion of the state machine by choosing AWS **Step Functions** from the **Services** menu. Choose the **SnapshotMgmtStateMachine-** from the list of state machines. This displays a list of executions. Click on an execution to see the details of the state machine execution. You can also switch to the DR region (Ohio) in the upper right corner and see the execution of the DR region state machine. 63 | 64 | ## Cleaning up the example resources 65 | 66 | To remove all resources created by this example, do the following: 67 | 68 | 1. Delete the **AWS CloudFormation** stacks in the primary and DR regions. 69 | 70 | ## How to customize and run the architecture in your account 71 | 72 | ### Note: This assumes you have the AWS CLI installed and configured 73 | 74 | First clone the repo: 75 | ``` 76 | # Clone it from github 77 | git clone https://github.com/awslabs/serverless-stepfunctions-ebs-snapshots.git 78 | ``` 79 | 80 | Make the edits you want to make. For instance, if you want to modify the DR region (i.e. not use Ohio), then in the PrimaryRegionTemplate.yaml file, edit the default value for the DRRegion parameter to the region you would prefer to use: 81 | 82 | ``` 83 | DRRegion: 84 | Description: 'The DR region where snapshots will be copied (This should be a different region from the region you are running this CloudFormation stack in.' 85 | Type: 'String' 86 | Default: 'us-east-2' 87 | ``` 88 | 89 | In the following commands you'll need to replace the following: 90 | **** - Replace with primary region (i.e. us-east-1, etc.) 91 | **** - Replace with DR region (i.e. us-east-1, etc.) 92 | **** - Replace with a globally unique bucket name for staging code in your primary region 93 | **** - Replace with a globally unique bucket name for staging code in your DR region 94 | 95 | Also, ensure that you have the latest CLI installed because the updates for the AWS Serverless Application Model (SAM) are needed for the next section. 96 | 97 | These first commands create an Amazon S3 bucket for staging your AWS Lambda function zips, then package the code and upload it, then deploy the AWS CloudFormation stack. 98 | ``` 99 | # Create an S3 bucket for staging your code in the primary region 100 | aws s3api create-bucket --bucket --region --create-bucket-configuration LocationConstraint= 101 | # If your primary region is us-east-1 run this command instead 102 | aws s3api create-bucket --bucket --region 103 | 104 | aws cloudformation package --template-file PrimaryRegionTemplate.yaml --s3-bucket --output-template-file tempPrimary.yaml --region 105 | 106 | aws cloudformation deploy --template-file tempPrimary.yaml --stack-name PrimaryRegionSnapshotManagement --capabilities CAPABILITY_IAM --region 107 | ``` 108 | 109 | These next set of commands perform the same actions for the DR region. 110 | ``` 111 | # Create an S3 bucket for staging your code in the primary region by running 112 | aws s3api create-bucket --bucket --region --create-bucket-configuration LocationConstraint= 113 | # If your DR region is us-east-1 run this command instead 114 | aws s3api create-bucket --bucket --region 115 | 116 | aws cloudformation package --template-file DR_RegionTemplate.yaml --s3-bucket --output-template-file tempDR.yaml --region 117 | 118 | aws cloudformation deploy --template-file tempDR.yaml --stack-name DRRegionSnapshotManagement --capabilities CAPABILITY_IAM --region 119 | ``` 120 | At this point the stacks will be updated and you can begin creating snapshots. 121 | -------------------------------------------------------------------------------- /DR_RegionTemplate.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: 'AWS::Serverless-2016-10-31' 3 | Description: '' 4 | Parameters: 5 | Retention: 6 | Description: 'The number of snapshots you want to keep per volume in your DR Region.' 7 | Type: 'String' 8 | Default: '1' 9 | FunctionNameSuffix: 10 | Description: 'Suffix to append to the Lambda functions.' 11 | Type: 'String' 12 | Default: '' 13 | Resources: 14 | StatesExecutionRole: 15 | Type: "AWS::IAM::Role" 16 | Properties: 17 | AssumeRolePolicyDocument: 18 | Version: "2012-10-17" 19 | Statement: 20 | - Effect: Allow 21 | Principal: 22 | Service: !Sub states.${AWS::Region}.amazonaws.com 23 | Action: "sts:AssumeRole" 24 | Path: "/" 25 | Policies: 26 | - PolicyName: StatesExecutionPolicy 27 | PolicyDocument: 28 | Version: "2012-10-17" 29 | Statement: 30 | - Effect: Allow 31 | Action: 32 | - "lambda:InvokeFunction" 33 | Resource: "*" 34 | ExecuteStateMachineRole: 35 | Type: "AWS::IAM::Role" 36 | Properties: 37 | AssumeRolePolicyDocument: 38 | Version: "2012-10-17" 39 | Statement: 40 | - 41 | Sid: "AllowCWEServiceToAssumeRole" 42 | Effect: "Allow" 43 | Action: 44 | - "sts:AssumeRole" 45 | Principal: 46 | Service: 47 | - "events.amazonaws.com" 48 | Path: "/" 49 | Policies: 50 | - 51 | PolicyName: "ExecuteStateMachine" 52 | PolicyDocument: 53 | Version: "2012-10-17" 54 | Statement: 55 | - 56 | Effect: "Allow" 57 | Action: 58 | - "states:StartExecution" 59 | Resource: "*" 60 | SNSNotificationIAMRole: 61 | Type: "AWS::IAM::Role" 62 | Properties: 63 | AssumeRolePolicyDocument: 64 | Version: "2012-10-17" 65 | Statement: 66 | - 67 | Sid: "AllowLambdaServiceToAssumeRole" 68 | Effect: "Allow" 69 | Action: 70 | - "sts:AssumeRole" 71 | Principal: 72 | Service: 73 | - "lambda.amazonaws.com" 74 | Path: "/" 75 | Policies: 76 | - 77 | PolicyName: "SNSNotification" 78 | PolicyDocument: 79 | Version: "2012-10-17" 80 | Statement: 81 | - 82 | Effect: "Allow" 83 | Action: 84 | - "sns:Publish" 85 | Resource: !Ref NotificationTopic 86 | - 87 | PolicyName: "WriteToCWLogs" 88 | PolicyDocument: 89 | Version: "2012-10-17" 90 | Statement: 91 | - 92 | Effect: "Allow" 93 | Action: 94 | - "logs:CreateLogStream" 95 | - "logs:CreateLogGroup" 96 | - "logs:PutLogEvents" 97 | Resource: "*" 98 | SnapshotManagementIAMRole: 99 | Type: "AWS::IAM::Role" 100 | Properties: 101 | AssumeRolePolicyDocument: 102 | Version: "2012-10-17" 103 | Statement: 104 | - 105 | Sid: "AllowLambdaServiceToAssumeRole" 106 | Effect: "Allow" 107 | Action: 108 | - "sts:AssumeRole" 109 | Principal: 110 | Service: 111 | - "lambda.amazonaws.com" 112 | Path: "/" 113 | Policies: 114 | - 115 | PolicyName: "SnapshotDescribeCopyPolicy" 116 | PolicyDocument: 117 | Version: "2012-10-17" 118 | Statement: 119 | - 120 | Effect: "Allow" 121 | Action: 122 | - "ec2:describeSnapshots" 123 | - "ec2:copySnapshot" 124 | - "ec2:createTags" 125 | Resource: "*" 126 | - 127 | PolicyName: "WriteToCWLogs" 128 | PolicyDocument: 129 | Version: "2012-10-17" 130 | Statement: 131 | - 132 | Effect: "Allow" 133 | Action: 134 | - "logs:CreateLogStream" 135 | - "logs:CreateLogGroup" 136 | - "logs:PutLogEvents" 137 | Resource: "*" 138 | SnapshotDeletionIAMRole: 139 | Type: "AWS::IAM::Role" 140 | Properties: 141 | AssumeRolePolicyDocument: 142 | Version: "2012-10-17" 143 | Statement: 144 | - 145 | Sid: "AllowLambdaServiceToAssumeRole" 146 | Effect: "Allow" 147 | Action: 148 | - "sts:AssumeRole" 149 | Principal: 150 | Service: 151 | - "lambda.amazonaws.com" 152 | Path: "/" 153 | Policies: 154 | - 155 | PolicyName: "SnapshotDeletionPolicy" 156 | PolicyDocument: 157 | Version: "2012-10-17" 158 | Statement: 159 | - 160 | Effect: "Allow" 161 | Action: "ec2:deleteSnapshot" 162 | Resource: "*" 163 | - 164 | PolicyName: "WriteToCWLogs" 165 | PolicyDocument: 166 | Version: "2012-10-17" 167 | Statement: 168 | - 169 | Effect: "Allow" 170 | Action: 171 | - "logs:CreateLogStream" 172 | - "logs:CreateLogGroup" 173 | - "logs:PutLogEvents" 174 | Resource: "*" 175 | 176 | TagSnapshotCopy: 177 | Type: 'AWS::Serverless::Function' 178 | Properties: 179 | Handler: TagSnapshotCopy.handler 180 | Runtime: nodejs4.3 181 | FunctionName: !Sub "TagSnapshotCopy-${FunctionNameSuffix}" 182 | CodeUri: . 183 | Role: !GetAtt SnapshotManagementIAMRole.Arn 184 | Description: >- 185 | Tags any new snapshot copies created 186 | MemorySize: 128 187 | Timeout: 15 188 | CountSnapshots: 189 | Type: 'AWS::Serverless::Function' 190 | Properties: 191 | Handler: CountSnapshots.handler 192 | Runtime: nodejs4.3 193 | FunctionName: !Sub "CountSnapshots-${FunctionNameSuffix}" 194 | CodeUri: . 195 | Role: !GetAtt SnapshotManagementIAMRole.Arn 196 | Description: >- 197 | Count how many current snapshots exist for the volume ID and return the snapshot data 198 | MemorySize: 128 199 | Timeout: 15 200 | DeleteOldSnapshots: 201 | Type: 'AWS::Serverless::Function' 202 | Properties: 203 | Handler: DeleteOldSnapshots.handler 204 | Runtime: nodejs4.3 205 | FunctionName: !Sub "DeleteOldSnapshots-${FunctionNameSuffix}" 206 | CodeUri: . 207 | Role: !GetAtt SnapshotDeletionIAMRole.Arn 208 | Description: >- 209 | Deletes any snapshots beyond the retention period. 210 | MemorySize: 128 211 | Timeout: 15 212 | Environment: 213 | Variables: 214 | retentionPeriod: !Ref Retention 215 | NotifyUser: 216 | Type: 'AWS::Serverless::Function' 217 | Properties: 218 | Handler: Notification.handler 219 | Runtime: nodejs4.3 220 | FunctionName: !Sub "Notification-${FunctionNameSuffix}" 221 | CodeUri: . 222 | Role: !GetAtt SNSNotificationIAMRole.Arn 223 | Description: >- 224 | Puts notification to topic regarding errors in snapshot management flow 225 | MemorySize: 128 226 | Timeout: 15 227 | Environment: 228 | Variables: 229 | notificationTopic: !Ref NotificationTopic 230 | NotificationTopic: 231 | Type: "AWS::SNS::Topic" 232 | Properties: 233 | TopicName: "SnapshotMgmtTopic" 234 | SnapshotMgmtStateMachine: 235 | Type: 'AWS::StepFunctions::StateMachine' 236 | Properties: 237 | DefinitionString: !Sub 238 | |- 239 | { 240 | "StartAt": "TagSnapshot", 241 | "States": { 242 | "TagSnapshot": { 243 | "Type": "Task", 244 | "Resource": "${TagSnapshotCopy.Arn}", 245 | "OutputPath": "$", 246 | "ResultPath": "$.originalVolumeId", 247 | "TimeoutSeconds": 15, 248 | "Catch": [ 249 | { 250 | "ErrorEquals": ["States.ALL"], 251 | "ResultPath": "$.errorMsg", 252 | "Next": "NotifyUser" 253 | } 254 | ], 255 | "Next": "EvaluateContinue" 256 | }, 257 | "EvaluateContinue": { 258 | "Type": "Choice", 259 | "Choices": [ 260 | { 261 | "StringEquals": "VOLUME_NOT_TAGGED", 262 | "Variable": "$.originalVolumeId", 263 | "Next": "SuccessState" 264 | } 265 | ], 266 | "Default": "RetrieveNumberOfSnapshots" 267 | }, 268 | "RetrieveNumberOfSnapshots": { 269 | "Type": "Task", 270 | "Resource": "${CountSnapshots.Arn}", 271 | "OutputPath": "$", 272 | "ResultPath": "$.snapshotList", 273 | "TimeoutSeconds": 20, 274 | "Catch": [ 275 | { 276 | "ErrorEquals": ["States.ALL"], 277 | "ResultPath": "$.errorMsg", 278 | "Next": "NotifyUser" 279 | } 280 | ], 281 | "Next": "EvaluateSnapshotDeletion" 282 | }, 283 | "EvaluateSnapshotDeletion": { 284 | "Type": "Choice", 285 | "Choices": [ 286 | { 287 | "NumericGreaterThan": ${Retention}, 288 | "Variable": "$.snapshotList.numSnapshots", 289 | "Next": "CleanupSnapshotsPastRetention" 290 | } 291 | ], 292 | "Default": "SuccessState" 293 | }, 294 | "CleanupSnapshotsPastRetention": { 295 | "Type": "Task", 296 | "Resource": "${DeleteOldSnapshots.Arn}", 297 | "TimeoutSeconds": 60, 298 | "Catch": [ 299 | { 300 | "ErrorEquals": ["States.ALL"], 301 | "ResultPath": "$.errorMsg", 302 | "Next": "NotifyUser" 303 | } 304 | ], 305 | "End": true 306 | }, 307 | "NotifyUser": { 308 | "Type": "Task", 309 | "Resource": "${NotifyUser.Arn}", 310 | "TimeoutSeconds": 60, 311 | "End": true 312 | }, 313 | "SuccessState" : { 314 | "Type" : "Succeed", 315 | "OutputPath": "$" 316 | } 317 | } 318 | } 319 | 320 | RoleArn: !GetAtt StatesExecutionRole.Arn 321 | ExecuteStateMachineRole: 322 | Type: "AWS::IAM::Role" 323 | Properties: 324 | AssumeRolePolicyDocument: 325 | Version: "2012-10-17" 326 | Statement: 327 | - 328 | Sid: "AllowCWEServiceToAssumeRole" 329 | Effect: "Allow" 330 | Action: 331 | - "sts:AssumeRole" 332 | Principal: 333 | Service: 334 | - "events.amazonaws.com" 335 | Path: "/" 336 | Policies: 337 | - 338 | PolicyName: "ExecuteStateMachine" 339 | PolicyDocument: 340 | Version: "2012-10-17" 341 | Statement: 342 | - 343 | Effect: "Allow" 344 | Action: 345 | - "states:StartExecution" 346 | Resource: "*" 347 | CloudWatchEventRule: 348 | Type: "AWS::Events::Rule" 349 | Properties: 350 | Description: "Rule to invoke snapshot mgmt state machine upon snapshot completion" 351 | EventPattern: 352 | source: 353 | - "aws.ec2" 354 | detail-type: 355 | - "EBS Snapshot Notification" 356 | detail: 357 | event: 358 | - "copySnapshot" 359 | Name: "EBS_Snapshot_Mgmt" 360 | State: "ENABLED" 361 | Targets: 362 | - 363 | Arn: !Ref SnapshotMgmtStateMachine 364 | Id: SFN_Target 365 | RoleArn: !GetAtt ExecuteStateMachineRole.Arn 366 | Outputs: 367 | StateMachineArn: 368 | Description: ARN for the Step Functions state machine 369 | Value: !Ref SnapshotMgmtStateMachine 370 | CWETriggerRoleArn: 371 | Description: ARN for the IAM role to execute the state machine 372 | Value: !GetAtt ExecuteStateMachineRole.Arn 373 | -------------------------------------------------------------------------------- /PrimaryRegionTemplate.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: 'AWS::Serverless-2016-10-31' 3 | Description: '' 4 | Parameters: 5 | tagKeyValue: 6 | Description: 'The value for the key tag that you want all volumes to have for the snapshot managment to apply.' 7 | Type: 'String' 8 | Default: 'none' 9 | DRRegion: 10 | Description: 'The DR region where snapshots will be copied (This should be a different region from the region you are running this CloudFormation stack in.' 11 | Type: 'String' 12 | Default: 'us-east-2' 13 | Retention: 14 | Description: 'The number of snapshots you want to keep per volume.' 15 | Type: 'String' 16 | Default: '7' 17 | FunctionNameSuffix: 18 | Description: 'Suffix to append to the Lambda functions.' 19 | Type: 'String' 20 | Default: '' 21 | Resources: 22 | StatesExecutionRole: 23 | Type: "AWS::IAM::Role" 24 | Properties: 25 | AssumeRolePolicyDocument: 26 | Version: "2012-10-17" 27 | Statement: 28 | - Effect: Allow 29 | Principal: 30 | Service: !Sub states.${AWS::Region}.amazonaws.com 31 | Action: "sts:AssumeRole" 32 | Path: "/" 33 | Policies: 34 | - PolicyName: StatesExecutionPolicy 35 | PolicyDocument: 36 | Version: "2012-10-17" 37 | Statement: 38 | - Effect: Allow 39 | Action: 40 | - "lambda:InvokeFunction" 41 | Resource: "*" 42 | ExecuteStateMachineRole: 43 | Type: "AWS::IAM::Role" 44 | Properties: 45 | AssumeRolePolicyDocument: 46 | Version: "2012-10-17" 47 | Statement: 48 | - 49 | Sid: "AllowCWEServiceToAssumeRole" 50 | Effect: "Allow" 51 | Action: 52 | - "sts:AssumeRole" 53 | Principal: 54 | Service: 55 | - "events.amazonaws.com" 56 | Path: "/" 57 | Policies: 58 | - 59 | PolicyName: "ExecuteStateMachine" 60 | PolicyDocument: 61 | Version: "2012-10-17" 62 | Statement: 63 | - 64 | Effect: "Allow" 65 | Action: 66 | - "states:StartExecution" 67 | Resource: "*" 68 | SNSNotificationIAMRole: 69 | Type: "AWS::IAM::Role" 70 | Properties: 71 | AssumeRolePolicyDocument: 72 | Version: "2012-10-17" 73 | Statement: 74 | - 75 | Sid: "AllowLambdaServiceToAssumeRole" 76 | Effect: "Allow" 77 | Action: 78 | - "sts:AssumeRole" 79 | Principal: 80 | Service: 81 | - "lambda.amazonaws.com" 82 | Path: "/" 83 | Policies: 84 | - 85 | PolicyName: "SNSNotification" 86 | PolicyDocument: 87 | Version: "2012-10-17" 88 | Statement: 89 | - 90 | Effect: "Allow" 91 | Action: 92 | - "sns:Publish" 93 | Resource: !Ref NotificationTopic 94 | - 95 | PolicyName: "WriteToCWLogs" 96 | PolicyDocument: 97 | Version: "2012-10-17" 98 | Statement: 99 | - 100 | Effect: "Allow" 101 | Action: 102 | - "logs:CreateLogStream" 103 | - "logs:CreateLogGroup" 104 | - "logs:PutLogEvents" 105 | Resource: "*" 106 | SnapshotManagementIAMRole: 107 | Type: "AWS::IAM::Role" 108 | Properties: 109 | AssumeRolePolicyDocument: 110 | Version: "2012-10-17" 111 | Statement: 112 | - 113 | Sid: "AllowLambdaServiceToAssumeRole" 114 | Effect: "Allow" 115 | Action: 116 | - "sts:AssumeRole" 117 | Principal: 118 | Service: 119 | - "lambda.amazonaws.com" 120 | Path: "/" 121 | Policies: 122 | - 123 | PolicyName: "SnapshotDescribeCopyPolicy" 124 | PolicyDocument: 125 | Version: "2012-10-17" 126 | Statement: 127 | - 128 | Effect: "Allow" 129 | Action: 130 | - "ec2:describeSnapshots" 131 | - "ec2:copySnapshot" 132 | - "ec2:createTags" 133 | - "ec2:describeVolumes" 134 | Resource: "*" 135 | - 136 | PolicyName: "WriteToCWLogs" 137 | PolicyDocument: 138 | Version: "2012-10-17" 139 | Statement: 140 | - 141 | Effect: "Allow" 142 | Action: 143 | - "logs:CreateLogStream" 144 | - "logs:CreateLogGroup" 145 | - "logs:PutLogEvents" 146 | Resource: "*" 147 | SnapshotDeletionIAMRole: 148 | Type: "AWS::IAM::Role" 149 | Properties: 150 | AssumeRolePolicyDocument: 151 | Version: "2012-10-17" 152 | Statement: 153 | - 154 | Sid: "AllowLambdaServiceToAssumeRole" 155 | Effect: "Allow" 156 | Action: 157 | - "sts:AssumeRole" 158 | Principal: 159 | Service: 160 | - "lambda.amazonaws.com" 161 | Path: "/" 162 | Policies: 163 | - 164 | PolicyName: "SnapshotDeletionPolicy" 165 | PolicyDocument: 166 | Version: "2012-10-17" 167 | Statement: 168 | - 169 | Effect: "Allow" 170 | Action: "ec2:deleteSnapshot" 171 | Resource: "*" 172 | - 173 | PolicyName: "WriteToCWLogs" 174 | PolicyDocument: 175 | Version: "2012-10-17" 176 | Statement: 177 | - 178 | Effect: "Allow" 179 | Action: 180 | - "logs:CreateLogStream" 181 | - "logs:CreateLogGroup" 182 | - "logs:PutLogEvents" 183 | Resource: "*" 184 | 185 | TagSnapshots: 186 | Type: 'AWS::Serverless::Function' 187 | Properties: 188 | Handler: TagSnapshots.handler 189 | Runtime: nodejs4.3 190 | FunctionName: !Sub "TagSnapshots-${FunctionNameSuffix}" 191 | CodeUri: . 192 | Role: !GetAtt SnapshotManagementIAMRole.Arn 193 | Description: >- 194 | Tags any new snapshots created 195 | MemorySize: 128 196 | Timeout: 15 197 | Environment: 198 | Variables: 199 | tagKey: !Ref tagKeyValue 200 | CountSnapshots: 201 | Type: 'AWS::Serverless::Function' 202 | Properties: 203 | Handler: CountSnapshots.handler 204 | Runtime: nodejs4.3 205 | FunctionName: !Sub "CountSnapshots-${FunctionNameSuffix}" 206 | CodeUri: . 207 | Role: !GetAtt SnapshotManagementIAMRole.Arn 208 | Description: >- 209 | Count how many current snapshots exist for the volume ID and return the snapshot data 210 | MemorySize: 128 211 | Timeout: 15 212 | Environment: 213 | Variables: 214 | tagKey: !Ref tagKeyValue 215 | CopySnapshotToDRRegion: 216 | Type: 'AWS::Serverless::Function' 217 | Properties: 218 | Handler: CopySnapshotToDR.handler 219 | Runtime: nodejs4.3 220 | FunctionName: !Sub "CopySnapshotToDRRegion-${FunctionNameSuffix}" 221 | CodeUri: . 222 | Role: !GetAtt SnapshotManagementIAMRole.Arn 223 | Description: >- 224 | Copy the most recent snapshot to the DR region 225 | MemorySize: 128 226 | Timeout: 15 227 | Environment: 228 | Variables: 229 | destRegion: !Ref DRRegion 230 | DeleteOldSnapshots: 231 | Type: 'AWS::Serverless::Function' 232 | Properties: 233 | Handler: DeleteOldSnapshots.handler 234 | Runtime: nodejs4.3 235 | FunctionName: !Sub "DeleteOldSnapshots-${FunctionNameSuffix}" 236 | CodeUri: . 237 | Role: !GetAtt SnapshotDeletionIAMRole.Arn 238 | Description: >- 239 | Deletes any snapshots beyond the retention period. 240 | MemorySize: 128 241 | Timeout: 15 242 | Environment: 243 | Variables: 244 | retentionPeriod: !Ref Retention 245 | NotifyUser: 246 | Type: 'AWS::Serverless::Function' 247 | Properties: 248 | Handler: Notification.handler 249 | Runtime: nodejs4.3 250 | FunctionName: !Sub "Notification-${FunctionNameSuffix}" 251 | CodeUri: . 252 | Role: !GetAtt SNSNotificationIAMRole.Arn 253 | Description: >- 254 | Puts notification to topic regarding errors in snapshot management flow 255 | MemorySize: 128 256 | Timeout: 15 257 | Environment: 258 | Variables: 259 | notificationTopic: !Ref NotificationTopic 260 | NotificationTopic: 261 | Type: "AWS::SNS::Topic" 262 | Properties: 263 | TopicName: "SnapshotMgmtTopic" 264 | SnapshotMgmtStateMachine: 265 | Type: 'AWS::StepFunctions::StateMachine' 266 | Properties: 267 | DefinitionString: !Sub 268 | |- 269 | { 270 | "StartAt": "TagSnapshot", 271 | "States": { 272 | "TagSnapshot": { 273 | "Type": "Task", 274 | "Resource": "${TagSnapshots.Arn}", 275 | "OutputPath": "$", 276 | "ResultPath": "$.originalVolumeId", 277 | "TimeoutSeconds": 60, 278 | "Next": "EvaluateContinue", 279 | "Catch": [ 280 | { 281 | "ErrorEquals": ["States.ALL"], 282 | "ResultPath": "$.errorMsg", 283 | "Next": "NotifyUser" 284 | } 285 | ] 286 | }, 287 | "EvaluateContinue": { 288 | "Type": "Choice", 289 | "Choices": [ 290 | { 291 | "StringEquals": "VOLUME_NOT_TAGGED", 292 | "Variable": "$.originalVolumeId", 293 | "Next": "SuccessState" 294 | } 295 | ], 296 | "Default": "RetrieveNumberOfSnapshots" 297 | }, 298 | "RetrieveNumberOfSnapshots": { 299 | "Type": "Task", 300 | "Resource": "${CountSnapshots.Arn}", 301 | "OutputPath": "$", 302 | "ResultPath": "$.snapshotList", 303 | "TimeoutSeconds": 60, 304 | "Next": "ManageSnapshots", 305 | "Catch": [ 306 | { 307 | "ErrorEquals": ["States.ALL"], 308 | "ResultPath": "$.errorMsg", 309 | "Next": "NotifyUser" 310 | } 311 | ] 312 | }, 313 | "ManageSnapshots": { 314 | "Type": "Parallel", 315 | "Branches": [ 316 | { 317 | "StartAt": "CopyToDRRegion", 318 | "States": { 319 | "CopyToDRRegion": { 320 | "Type": "Task", 321 | "Resource":"${CopySnapshotToDRRegion.Arn}", 322 | "TimeoutSeconds": 10, 323 | "Catch": [ 324 | { 325 | "ErrorEquals": ["States.ALL"], 326 | "ResultPath": "$.errorMsg", 327 | "Next": "NotifyUserInParallelBranch1" 328 | } 329 | ], 330 | "End": true 331 | }, 332 | "NotifyUserInParallelBranch1": { 333 | "Type": "Task", 334 | "Resource": "${NotifyUser.Arn}", 335 | "TimeoutSeconds": 60, 336 | "End": true 337 | } 338 | } 339 | }, 340 | { 341 | "StartAt": "EvaluateSnapshotDeletion", 342 | "States": { 343 | "EvaluateSnapshotDeletion": { 344 | "Type": "Choice", 345 | "Choices": [ 346 | { 347 | "NumericGreaterThan": ${Retention}, 348 | "Variable": "$.snapshotList.numSnapshots", 349 | "Next": "CleanupSnapshotsPastRetention" 350 | } 351 | ], 352 | "Default": "SuccessStateInParallel" 353 | }, 354 | "CleanupSnapshotsPastRetention": { 355 | "Type": "Task", 356 | "Resource": "${DeleteOldSnapshots.Arn}", 357 | "TimeoutSeconds": 60, 358 | "Catch": [ 359 | { 360 | "ErrorEquals": ["States.ALL"], 361 | "ResultPath": "$.errorMsg", 362 | "Next": "NotifyUserInParallelBranch2" 363 | } 364 | ], 365 | "End": true 366 | }, 367 | "NotifyUserInParallelBranch2": { 368 | "Type": "Task", 369 | "Resource": "${NotifyUser.Arn}", 370 | "TimeoutSeconds": 60, 371 | "End": true 372 | }, 373 | "SuccessStateInParallel" : { 374 | "Type" : "Succeed", 375 | "OutputPath": "$" 376 | } 377 | } 378 | } 379 | ], 380 | "Next": "SuccessState" 381 | }, 382 | "NotifyUser": { 383 | "Type": "Task", 384 | "Resource": "${NotifyUser.Arn}", 385 | "TimeoutSeconds": 60, 386 | "End": true 387 | }, 388 | "SuccessState": { 389 | "Type": "Succeed" 390 | } 391 | } 392 | } 393 | 394 | RoleArn: !GetAtt StatesExecutionRole.Arn 395 | ExecuteStateMachineRole: 396 | Type: "AWS::IAM::Role" 397 | Properties: 398 | AssumeRolePolicyDocument: 399 | Version: "2012-10-17" 400 | Statement: 401 | - 402 | Sid: "AllowCWEServiceToAssumeRole" 403 | Effect: "Allow" 404 | Action: 405 | - "sts:AssumeRole" 406 | Principal: 407 | Service: 408 | - "events.amazonaws.com" 409 | Path: "/" 410 | Policies: 411 | - 412 | PolicyName: "ExecuteStateMachine" 413 | PolicyDocument: 414 | Version: "2012-10-17" 415 | Statement: 416 | - 417 | Effect: "Allow" 418 | Action: 419 | - "states:StartExecution" 420 | Resource: "*" 421 | CloudWatchEventRule: 422 | Type: "AWS::Events::Rule" 423 | Properties: 424 | Description: "Rule to invoke snapshot mgmt state machine upon snapshot completion" 425 | EventPattern: 426 | source: 427 | - "aws.ec2" 428 | detail-type: 429 | - "EBS Snapshot Notification" 430 | detail: 431 | event: 432 | - "createSnapshot" 433 | Name: "EBS_Snapshot_Mgmt" 434 | State: "ENABLED" 435 | Targets: 436 | - 437 | Arn: !Ref SnapshotMgmtStateMachine 438 | Id: SFN_Target 439 | RoleArn: !GetAtt ExecuteStateMachineRole.Arn 440 | Outputs: 441 | StateMachineArn: 442 | Description: ARN for the Step Functions state machine 443 | Value: !Ref SnapshotMgmtStateMachine 444 | CWETriggerRoleArn: 445 | Description: ARN for the IAM role to execute the state machine 446 | Value: !GetAtt ExecuteStateMachineRole.Arn 447 | --------------------------------------------------------------------------------