AWS CDK L3 construct for managing SSM Documents.
CloudFormation's support for SSM Documents currently is lacking updating functionality. Instead of updating a document, CFN will replace it. The old document is destroyed and a new one is created with a different name. This is problematic because:
- When names potentially change, you cannot directly reference a document
- Old versions are permanently lost
This construct provides document support in a way you'd expect it:
- Changes on documents will cerate new versions
- Versions cannot be deleted
import cdk = require('@aws-cdk/core');
import { Document } from 'cdk-ssm-document';
import fs = require('fs');
import path = require('path');
export class TestStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props: cdk.StackProps) {
super(scope, id, props);
const file = path.join(__dirname, '../documents/hello-world.yml');
new Document(this, 'SSM-Document-HelloWorld', {
name: 'HelloWorld',
content: fs.readFileSync(file).toString(),
});
}
}
import cdk = require('@aws-cdk/core');
import { Document } from 'cdk-ssm-document';
import fs = require('fs');
import path = require('path');
export class TestStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props: cdk.StackProps) {
super(scope, id, props);
new Document(this, 'SSM-Document-HelloWorld', {
name: 'HelloWorld',
content: {
schemaVersion: '2.2',
description: 'Echo Hello World!',
parameters: {
text: {
default: 'Hello World!',
description: 'Text to echo',
type: 'String',
},
},
mainSteps: [
{
name: 'echo',
action: 'aws:runShellScript',
inputs: {
runCommand: [
'echo "{{text}}"',
],
},
precondition: {
StringEquals: [
'platformType',
'Linux',
],
},
},
],
},
});
}
}
import cdk = require('@aws-cdk/core');
import { Document } from 'cdk-ssm-document';
import fs = require('fs');
import path = require('path');
export class TestStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props: cdk.StackProps) {
super(scope, id, props);
const dir = path.join(__dirname, '../documents');
const files = fs.readdirSync(dir);
for (const i in files) {
const name = files[i];
const shortName = name.split('.').slice(0, -1).join('.'); // removes file extension
const file = `${dir}/${name}`;
new Document(this, `SSM-Document-${shortName}`, {
name: shortName,
content: fs.readFileSync(file).toString(),
});
}
}
}
If you're still not convinced to use the AWS CDK, you can still use the Lambda as a custom resource in your CFN template. Here is how:
-
Create a zip file for the Lambda:
To create a zip from the Lambda source run:
lambda/build
This will generate the file
lambda/code.zip
. -
Upload the Lambda function:
Upload this zip file to an S3 bucket via cli, Console or however you like.
Example via cli:
aws s3 cp lambda/code.zip s3://example-bucket/code.zip
-
Deploy a CloudFormation stack utilizing the zip as a custom resource provider:
Example CloudFormation template:
--- AWSTemplateFormatVersion: "2010-09-09" Resources: SSMDocExecutionRole: Type: AWS::IAM::Role Properties: RoleName: CFN-Resource-Custom-SSM-Document AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole - Ref: SSMDocExecutionPolicy SSMDocExecutionPolicy: Type: AWS::IAM::ManagedPolicy Properties: ManagedPolicyName: CFN-Resource-Custom-SSM-Document PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - ssm:ListDocuments - ssm:ListTagsForResource Resource: "*" - Effect: Allow Action: - ssm:CreateDocument - ssm:AddTagsToResource Resource: "*" Condition: StringEquals: aws:RequestTag/CreatedBy: CFN::Resource::Custom::SSM-Document - Effect: Allow Action: - ssm:DeleteDocument - ssm:DescribeDocument - ssm:GetDocument - ssm:ListDocumentVersions - ssm:ModifyDocumentPermission - ssm:UpdateDocument - ssm:UpdateDocumentDefaultVersion - ssm:AddTagsToResource - ssm:RemoveTagsFromResource Resource: "*" Condition: StringEquals: aws:ResourceTag/CreatedBy: CFN::Resource::Custom::SSM-Document SSMDocFunction: Type: AWS::Lambda::Function Properties: FunctionName: CFN-Resource-Custom-SSM-Document-Manager Code: S3Bucket: example-bucket S3Key: code.zip Handler: index.handler Runtime: nodejs10.x Timeout: 3 Role: !GetAtt SSMDocExecutionRole.Arn MyDocument: Type: Custom::SSM-Document Properties: Name: MyDocument ServiceToken: !GetAtt SSMDocFunction.Arn StackName: !Ref AWS::StackName UpdateDefaultVersion: true # default: true Content: schemaVersion: "2.2" description: Echo Hello World! parameters: text: type: String description: Text to echo default: Hello World! mainSteps: - name: echo action: aws:runShellScript inputs: runCommand: - echo "{{text}}" precondition: StringEquals: - platformType - Linux DocumentType: Command # default: Command TargetType: / # default: / Tags: CreatedBy: CFN::Resource::Custom::SSM-Document # required, see above policy conditions