cfn-flow 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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