cfn-flow 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9b11eb9cc9a03afce395768b4e29f5b59764646f
4
+ data.tar.gz: 18fa02cf2b9834e8ab4c870d23ecb548dfd7a064
5
+ SHA512:
6
+ metadata.gz: 81a7ce12e778b4b5beb6fd566143900ec6a9803b19f56a9b4b4700dd65d4128a792ec9f48be6a3741c329f24ad5a7c3023a2288c4a80014c836a86348910266a
7
+ data.tar.gz: 01491f61b53acc8ece495146a7e8b9864f96dd897ec7fc4465ce97910d773ba88fd04da9952abd9db7a1314eeb3630ae533276b465b0b67866354163166b9365
data/README.md ADDED
@@ -0,0 +1,133 @@
1
+ # cfn-flow
2
+ An opinionated command-line workflow for developing AWS CloudFormation templates. Track template changes in git and upload versioned releases to AWS S3.
3
+
4
+ #### Opinions
5
+
6
+ 1. *Optimize for onboarding.* The workflow should be simple to learn & understand.
7
+ 2. *Optimize for happiness.* The workflow should be easy and enjoyable to use.
8
+ 3. *Auditable history.* Know who changed what when. Leverage git for auditing.
9
+ 4. *Immutable releases.* The code in a release never changes.
10
+
11
+ ## Installation
12
+
13
+ Via [rubygems](https://rubygems.org/gems/cfn-flow):
14
+ ```
15
+ gem install cfn-flow
16
+ ```
17
+
18
+ The `git` command is also needed.
19
+
20
+ ## Usage
21
+
22
+ Poke around:
23
+ ```
24
+ cfn-flow help
25
+ ```
26
+
27
+ #### Dev mode (default)
28
+
29
+ Dev mode allows you to quickly test template changes.
30
+ `cfn-flow` validates all templates and uploads them to your personal prefix, overwriting existing templates.
31
+
32
+ Dev mode does not verify that your local changes are
33
+ committed to git (as opposed to release mode).
34
+
35
+ You should use dev mode for testing & verifying changes in non-production stacks.
36
+
37
+ ```
38
+ # Set a personal name to prefix your templates.
39
+ export CFN_FLOW_DEV_NAME=aaron
40
+
41
+ # Validate and upload all CloudFormation templates in your working directory to
42
+ s3://my-bucket/dev/aaron/*
43
+ # NB that this overwrites existing templates in your CFN_FLOW_DEV_NAME
44
+ namespace.
45
+
46
+ cfn-init
47
+ ```
48
+
49
+ You can launch or update test stacks using your dev template path to quickly test your
50
+ template changes.
51
+
52
+ #### Release mode
53
+
54
+ Release mode publishes your templates to a versioned S3 path, and pushes a git
55
+ tag of the version.
56
+
57
+ ```
58
+ # uploads templates to `s3://my-bucket/release/1.0.0/*` and pushes a 1.0.0 git
59
+ tag
60
+ cfn-flow --release 1.0.0
61
+ ```
62
+
63
+ Release mode ensures there are no uncommitted changes in your git working
64
+ directory, and pushes a `1.0.0` git tag.
65
+
66
+ Inspecting the differences between releases is possible using `git log` and `git
67
+ diff`.
68
+
69
+ ## Configuration
70
+
71
+ You can configure cfn-flow defaults by creating a `cfn-flow.yml` file in same
72
+ directory you run `cfn-flow` (presumably the root of your project).
73
+
74
+ Settings in the configuration file are overridden by environment variables. And
75
+ environment variables are overridden by command line arguments.
76
+
77
+ ```
78
+ # cfn-flow.yml in the root of your project
79
+ # All options in this config can be overridden with command line arguments
80
+ ---
81
+ # S3 bucket where templates are uploaded. No default.
82
+ # Override with CFN_FLOW_BUCKET
83
+ bucket: 'my-s3-bucket'
84
+
85
+ # S3 path prefix. Default: none
86
+ # Override with CFN_FLOW_TO
87
+ to: my/project/prefix
88
+
89
+ # Local path in which to recursively search for templates. Default: .
90
+ # Override with CFN_FLOW_FROM
91
+ from: my/local/prefix
92
+
93
+ # AWS Region
94
+ # Override with AWS_REGION
95
+ region: us-east-1 # AWS region
96
+ ```
97
+
98
+ #### AWS credentials
99
+
100
+ AWS credentials can only be set using the
101
+ `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables; or by
102
+ using an EC2 instance's IAM role.
103
+
104
+
105
+ ## Sweet Features
106
+
107
+ #### YAML > JSON
108
+
109
+ `cfn-flow` lets you write templates in either JSON or
110
+ [YAML](http://www.yaml.org). YAML is a superset of JSON that allows a terser,
111
+ less cluttered syntax, inline comments, and code re-use with variables. YAML
112
+ templates are transparently converted to JSON when uploaded to S3 for use in
113
+ CloudFormation stacks.
114
+
115
+ #### Use versions in nested stack template URLs
116
+
117
+ `cfn-flow` works great with [nested stack
118
+ resources](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stack.html). Use [Fn::Join](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-join.html) to construct the `TemplateURL` from a parameter:
119
+
120
+ ```
121
+ {
122
+ "Type" : "AWS::CloudFormation::Stack",
123
+ "Properties" : {
124
+ "TemplateURL" : {
125
+ "Fn::Join" : [ ":",r
126
+ [ "https://s3.amazonaws.com/my-bucket", {"Ref": "prefix"}, "my-template.json" ]
127
+ ]
128
+ }
129
+ }
130
+ }
131
+ ```
132
+
133
+ While testing, set the `prefix` parameter to dev prefix like `dev/aaron`. When you're confident your changes work, release them with cfn-flow and change the `prefix` parameter to `release/1.0.0` for production.
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require "rubygems"
2
+ require "bundler/setup"
3
+ require 'rake/testtask'
4
+
5
+ namespace :test do
6
+ Rake::TestTask.new(:units) do |t|
7
+ t.pattern = "spec/*_spec.rb"
8
+ end
9
+
10
+ Rake::TestTask.new(:integration) do |t|
11
+ t.pattern = "spec/integration/*_spec.rb"
12
+ end
13
+ end
14
+
15
+ desc 'Run tests'
16
+ task :test => %w[test:units test:integration]
17
+
18
+ task :default => :test
data/bin/cfn-flow ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'cfn-flow'
3
+ CfnFlow.start
@@ -0,0 +1,59 @@
1
+ class CfnFlow::Template
2
+ attr_reader :from, :prefix, :bucket
3
+ def initialize(from:, prefix:, bucket:)
4
+ @from, @prefix, @bucket = from, prefix, bucket
5
+ end
6
+
7
+ def yaml?
8
+ from.end_with?('.yml')
9
+ end
10
+
11
+ def json?
12
+ ! yaml?
13
+ end
14
+
15
+ # Determine if this file is a CFN template
16
+ def is_cfn_template?
17
+ from_data.is_a?(Hash) && from_data.key?('Resources')
18
+ end
19
+
20
+ def validate!
21
+ cfn.validate_template(template_body: to_json)
22
+ end
23
+
24
+ def key
25
+ # Replace leading './' in from, rename *.yml to *.json
26
+ File.join(prefix, from.sub(/\A\.\//, '').sub(/\.yml\Z/, '.json'))
27
+ end
28
+
29
+ def upload!
30
+ s3_object.put(body: to_json)
31
+ end
32
+
33
+ def url
34
+ s3_object.public_url
35
+ end
36
+
37
+ def from_data
38
+ # We *could* load JSON as YAML, but that would generate confusing errors
39
+ # in the case of a JSON syntax error.
40
+ @from_data ||= yaml? ? YAML.load_file(from) : MultiJson.load(File.read(from))
41
+ rescue
42
+ puts "Error loading #{from}"
43
+ raise $!
44
+ end
45
+
46
+ def to_json
47
+ @to_json ||= MultiJson.dump(from_data, pretty: true)
48
+ end
49
+
50
+ private
51
+ def cfn
52
+ @cfn ||= Aws::CloudFormation::Client.new
53
+ end
54
+
55
+ def s3_object
56
+ @s3_object ||= Aws::S3::Object.new(bucket, key)
57
+ end
58
+
59
+ end
@@ -0,0 +1,3 @@
1
+ module CfnFlow
2
+ VERSION = '0.0.1'
3
+ end
data/lib/cfn-flow.rb ADDED
@@ -0,0 +1,143 @@
1
+ require 'thor'
2
+ require 'aws-sdk'
3
+ require 'multi_json'
4
+ require 'yaml'
5
+
6
+ class CfnFlow < Thor
7
+ class GitError < StandardError; end
8
+
9
+ require 'cfn-flow/template'
10
+ def self.shared_options
11
+
12
+ method_option :bucket, type: :string, desc: 'S3 bucket for templates'
13
+ method_option :to, type: :string, desc: 'S3 path prefix for templates'
14
+ method_option :from, type: :string, desc: 'Local source directory for templates'
15
+ method_option 'dev-name', type: :string, desc: 'Personal development prefix'
16
+ method_option :region, type: :string, desc: 'AWS Region'
17
+
18
+ method_option :verbose, type: :boolean, desc: 'Verbose output', default: false
19
+ end
20
+
21
+ no_commands do
22
+ def load_config
23
+ defaults = { 'from' => '.' }
24
+ file_config = begin
25
+ YAML.load_file('./cfn-flow.yml')
26
+ rescue Errno::ENOENT
27
+ {}
28
+ end
29
+ env_config = {
30
+ 'bucket' => ENV['CFN_FLOW_BUCKET'],
31
+ 'to' => ENV['CFN_FLOW_TO'],
32
+ 'from' => ENV['CFN_FLOW_FROM'],
33
+ 'dev-name' => ENV['CFN_FLOW_DEV_NAME'],
34
+ 'region' => ENV['AWS_REGION']
35
+ }.delete_if {|_,v| v.nil?}
36
+
37
+ # Env vars override config file. Command args override env vars.
38
+ self.options = defaults.merge(file_config).merge(env_config).merge(options)
39
+
40
+ # Ensure region env var is set for AWS client
41
+ ENV['AWS_REGION'] = options['region']
42
+
43
+ # TODO: validate required options are present
44
+ end
45
+
46
+ shared_options
47
+ def load_templates
48
+ load_config
49
+ glob = File.join(options['from'], '**/*.{yml,json,template}')
50
+
51
+ @templates = Dir.glob(glob).map { |path|
52
+ CfnFlow::Template.new(from: path, bucket: options['bucket'], prefix: prefix)
53
+ }.select! {|t|
54
+ verbose "Checking file #{t.from}... "
55
+ if t.is_cfn_template?
56
+ verbose "loaded"
57
+ true
58
+ else
59
+ verbose "skipped."
60
+ false
61
+ end
62
+ }
63
+ end
64
+ end
65
+
66
+ desc :validate, 'Validates templates'
67
+ shared_options
68
+ def validate
69
+ load_templates
70
+ @templates.each do |t|
71
+ begin
72
+ verbose "Validating #{t.from}... "
73
+ t.validate!
74
+ verbose "valid."
75
+ rescue Aws::CloudFormation::Errors::ValidationError
76
+ say "Error validating #{t.from}. Message:"
77
+ say $!.message
78
+ end
79
+ end
80
+ end
81
+
82
+ desc :upload, 'Validate & upload templates to the CFN_DEV_FLOW_NAME prefix'
83
+ shared_options
84
+ method_option :release, type: :string, desc: 'Upload & tag release'
85
+ def upload
86
+ tag_release if options['release']
87
+
88
+ validate
89
+ @templates.each do |t|
90
+ verbose "Uploading #{t.from} to #{t.url}"
91
+ t.upload!
92
+ end
93
+
94
+ push_release if options['release']
95
+
96
+ end
97
+ default_task :upload
98
+
99
+ private
100
+ def verbose(msg)
101
+ say msg if options['verbose']
102
+ end
103
+
104
+ def prefix
105
+ # Add the release or dev name to the prefix
106
+ parts = []
107
+ parts << options['prefix'] unless options['prefix'].empty?
108
+ if options['release']
109
+ parts += [ 'release', options['release'] ]
110
+ else
111
+ parts += [ 'dev', options['dev-name'] ]
112
+ end
113
+ File.join(*parts)
114
+ end
115
+
116
+ def tag_release
117
+ # Check git status
118
+ unless `git status -s`.empty?
119
+ git_error "Git working directory is not clean. Please commit or reset changes in order to release."
120
+ end
121
+ unless $?.success?
122
+ git_error "Error running `git status`"
123
+ end
124
+
125
+ say "Tagging release #{options['release']}"
126
+ `git tag -a -m #{options['release']}, #{options['release']}`
127
+ unless $?.success?
128
+ git_error "Error tagging release."
129
+ end
130
+ end
131
+
132
+ def push_tag
133
+ `git push origin #{options['release']}`
134
+ unless $?.success?
135
+ git_error "Error pushing tag to origin."
136
+ end
137
+ end
138
+
139
+ def git_error(message)
140
+ say message, :red
141
+ raise GitError.new(message)
142
+ end
143
+ end
data/spec/sqs.template ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "AWSTemplateFormatVersion" : "2010-09-09",
3
+
4
+ "Description" : "AWS CloudFormation Sample Template SQS_With_CloudWatch_Alarms: Sample template showing how to create an SQS queue with AWS CloudWatch alarms on queue depth. **WARNING** This template creates an Amazon SQS Queue and one or more Amazon CloudWatch alarms. You will be billed for the AWS resources used if you create a stack from this template.",
5
+
6
+ "Parameters" : {
7
+ "AlarmEMail": {
8
+ "Description": "EMail address to notify if there are any operational issues",
9
+ "Type": "String",
10
+ "AllowedPattern": "([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)",
11
+ "ConstraintDescription": "must be a valid email address."
12
+ }
13
+ },
14
+
15
+ "Resources" : {
16
+ "MyQueue" : {
17
+ "Type" : "AWS::SQS::Queue",
18
+ "Properties" : {
19
+ }
20
+ },
21
+
22
+ "AlarmTopic": {
23
+ "Type": "AWS::SNS::Topic",
24
+ "Properties": {
25
+ "Subscription": [{
26
+ "Endpoint": { "Ref": "AlarmEMail" },
27
+ "Protocol": "email"
28
+ }]
29
+ }
30
+ },
31
+
32
+ "QueueDepthAlarm": {
33
+ "Type": "AWS::CloudWatch::Alarm",
34
+ "Properties": {
35
+ "AlarmDescription": "Alarm if queue depth grows beyond 10 messages",
36
+ "Namespace": "AWS/SQS",
37
+ "MetricName": "ApproximateNumberOfMessagesVisible",
38
+ "Dimensions": [{
39
+ "Name": "QueueName",
40
+ "Value" : { "Fn::GetAtt" : ["MyQueue", "QueueName"] }
41
+ }],
42
+ "Statistic": "Sum",
43
+ "Period": "300",
44
+ "EvaluationPeriods": "1",
45
+ "Threshold": "10",
46
+ "ComparisonOperator": "GreaterThanThreshold",
47
+ "AlarmActions": [{ "Ref": "AlarmTopic" }],
48
+ "InsufficientDataActions": [{ "Ref": "AlarmTopic" }]
49
+ }
50
+ }
51
+ },
52
+ "Outputs" : {
53
+ "QueueURL" : {
54
+ "Description" : "URL of newly created SQS Queue",
55
+ "Value" : { "Ref" : "MyQueue" }
56
+ },
57
+ "QueueARN" : {
58
+ "Description" : "ARN of newly created SQS Queue",
59
+ "Value" : { "Fn::GetAtt" : ["MyQueue", "Arn"]}
60
+ },
61
+ "QueueName" : {
62
+ "Description" : "Name newly created SQS Queue",
63
+ "Value" : { "Fn::GetAtt" : ["MyQueue", "QueueName"]}
64
+ }
65
+ }
66
+ }
data/spec/sqs.yml ADDED
@@ -0,0 +1,62 @@
1
+ ---
2
+ AWSTemplateFormatVersion: '2010-09-09'
3
+ Description: 'AWS CloudFormation Sample Template SQS_With_CloudWatch_Alarms: Sample
4
+ template showing how to create an SQS queue with AWS CloudWatch alarms on queue
5
+ depth. **WARNING** This template creates an Amazon SQS Queue and one or more Amazon
6
+ CloudWatch alarms. You will be billed for the AWS resources used if you create a
7
+ stack from this template.'
8
+ Parameters:
9
+ AlarmEMail:
10
+ Description: EMail address to notify if there are any operational issues
11
+ Type: String
12
+ AllowedPattern: "([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)"
13
+ ConstraintDescription: must be a valid email address.
14
+ Resources:
15
+ MyQueue:
16
+ Type: AWS::SQS::Queue
17
+ Properties: {}
18
+ AlarmTopic:
19
+ Type: AWS::SNS::Topic
20
+ Properties:
21
+ Subscription:
22
+ - Endpoint:
23
+ Ref: AlarmEMail
24
+ Protocol: email
25
+ QueueDepthAlarm:
26
+ Type: AWS::CloudWatch::Alarm
27
+ Properties:
28
+ AlarmDescription: Alarm if queue depth grows beyond 10 messages
29
+ Namespace: AWS/SQS
30
+ MetricName: ApproximateNumberOfMessagesVisible
31
+ Dimensions:
32
+ - Name: QueueName
33
+ Value:
34
+ Fn::GetAtt:
35
+ - MyQueue
36
+ - QueueName
37
+ Statistic: Sum
38
+ Period: '300'
39
+ EvaluationPeriods: '1'
40
+ Threshold: '10'
41
+ ComparisonOperator: GreaterThanThreshold
42
+ AlarmActions:
43
+ - Ref: AlarmTopic
44
+ InsufficientDataActions:
45
+ - Ref: AlarmTopic
46
+ Outputs:
47
+ QueueURL:
48
+ Description: URL of newly created SQS Queue
49
+ Value:
50
+ Ref: MyQueue
51
+ QueueARN:
52
+ Description: ARN of newly created SQS Queue
53
+ Value:
54
+ Fn::GetAtt:
55
+ - MyQueue
56
+ - Arn
57
+ QueueName:
58
+ Description: Name newly created SQS Queue
59
+ Value:
60
+ Fn::GetAtt:
61
+ - MyQueue
62
+ - QueueName
metadata ADDED
@@ -0,0 +1,139 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cfn-flow
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Aaron Suggs
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-01-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 2.0.20.pre
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 2.0.20.pre
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.18'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.18'
41
+ - !ruby/object:Gem::Dependency
42
+ name: multi_json
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: appraisal
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: An opinionate worflow for AWS CloudFormation
98
+ email: aaron@ktheory.com
99
+ executables:
100
+ - cfn-flow
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - README.md
105
+ - Rakefile
106
+ - bin/cfn-flow
107
+ - lib/cfn-flow.rb
108
+ - lib/cfn-flow/template.rb
109
+ - lib/cfn-flow/version.rb
110
+ - spec/sqs.template
111
+ - spec/sqs.yml
112
+ homepage: http://github.com/kickstarter/cfn-flow
113
+ licenses:
114
+ - MIT
115
+ metadata: {}
116
+ post_install_message:
117
+ rdoc_options:
118
+ - "--charset=UTF-8"
119
+ require_paths:
120
+ - lib
121
+ required_ruby_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: 2.0.0
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ requirements: []
132
+ rubyforge_project:
133
+ rubygems_version: 2.4.5
134
+ signing_key:
135
+ specification_version: 4
136
+ summary: A CLI for CloudFormation templates
137
+ test_files:
138
+ - spec/sqs.template
139
+ - spec/sqs.yml