cfn-flow 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8e00390b3fd3c7698003efd85d99fc576e885ba3
4
- data.tar.gz: 881bb3936515feb3db9801637767c13db1b2ab9e
3
+ metadata.gz: 96e7a912fc803e4d51267f48e138ae273455d424
4
+ data.tar.gz: 0827e20cf4a86e42063c55648d50531522bdc769
5
5
  SHA512:
6
- metadata.gz: 511ada328d1a675b712cb20ad14d94e01a1940fedfb5da0e196609d9dfd2845a155c09932115c1858a13c831684d1f03d316106d2bdb87ccc28f8b0c906c6eed
7
- data.tar.gz: b3e5baf4a020bc72e6ada19e6e16ae758a678e3c3aded9973ced8bd6bc84b71d3ee576035cd9f35ee8d1c3bdfea1da4a44dd91f65f53b09d450a62ef5c2d43a3
6
+ metadata.gz: e20d070594f969140f946c1f2cc37ae777bbc795ef846a375d9e064c5078912810d8200018bca251ad0a73bbd6c9437f453fe71915c3875a12a15526ef6ac9b6
7
+ data.tar.gz: f0eb7fb958048dc13915255067efdb45e7d06941a50e291a0949aa90f6627f616bc0bc8b3de31ed0236224def48651a974130b68917e58cc992260af74bebdaf
data/README.md CHANGED
@@ -1,17 +1,21 @@
1
1
  # cfn-flow
2
- An opinionated command-line workflow for developing [AWS CloudFormation](https://aws.amazon.com/cloudformation/) templates and deploying stacks.
2
+ `cfn-flow` is an command-line tool for developing [AWS CloudFormation](https://aws.amazon.com/cloudformation/) templates and deploying stacks.
3
3
 
4
- Track template changes in git and publish versioned releases to AWS S3.
5
-
6
- Deploy stacks using a standard, reliable process with extensible
7
- configuration in git.
4
+ It provides a *simple*, *standard*, and *flexible* process for using CloudFormation, ideal for DevOps-style organizations.
8
5
 
9
6
  #### Opinions
10
7
 
11
- 1. *Optimize for onboarding.* The workflow should be simple to learn & understand.
12
- 2. *Optimize for happiness.* The workflow should be easy and enjoyable to use.
13
- 3. *Auditable changes.* Know who changed what when. Leverage git change history.
14
- 4. *Immutable releases.* The code in a release never changes.
8
+ `cfn-flow` introduces a consist, convenient workflow that encourages good template organization
9
+ and deploy practices.
10
+
11
+ 1. *Optimize for happiness.* The workflow should be easy and enjoyable to use.
12
+ 2. *Optimize for onboarding.* The workflow should be simple to learn & understand.
13
+ 3. *Auditable changes.* Know who changed what when. Leverage git history.
14
+ 4. *Immutable releases.* The code in a release never changes. To make a change,
15
+ launch a new stack.
16
+
17
+ The features & implementation of `cfn-flow` itself must also be simple. This follows the Unix philosophy of "[worse is
18
+ better](http://www.jwz.org/doc/worse-is-better.html)". `cfn-flow` values a simple design and implementation, and being composable with other workflows over handling every edge case out of the box.
15
19
 
16
20
  ## Installation
17
21
 
@@ -24,8 +28,8 @@ The `git` command is also needed.
24
28
 
25
29
  ## Usage
26
30
 
27
- Poke around:
28
31
  ```
32
+ # Get help
29
33
  cfn-flow help
30
34
 
31
35
  cfn-flow help COMMAND
@@ -33,11 +37,74 @@ cfn-flow help COMMAND
33
37
  cfn-flow help deploy
34
38
  ```
35
39
 
36
- Launching a CloudFormation stack:
40
+ Launch a CloudFormation stack:
37
41
  ```
38
42
  cfn-flow deploy production
39
43
  ```
40
44
 
45
+ ## How it works
46
+
47
+ `cfn-flow` works from a directory containing a `cfn-flow.yml` config file, and a CloudFormation template.
48
+ Presumably your app code is in the same directory, but it doesn't have to be.
49
+
50
+ There are two key concepts for `cfn-flow`: **services** and **environments**.
51
+
52
+ #### Services
53
+
54
+ A service is a name for your project and comprises a set of resources that
55
+ change together. Each service has it's own `cfn-flow.yml` config file. A service
56
+ can be instantiated as several distinct environments.
57
+
58
+ For example, a `WebApp` service could have a CloudFormation template that
59
+ creates an ELB, LaunchConfig, and AutoScalingGroup resources.
60
+
61
+ All the resources in a service change together. Deploying the `WebApp`
62
+ service to an environment will create a new ELB, LaunchConfig, and AutoScalingGroup.
63
+
64
+ Resources that *do not* change across deploys are not part of the service (from
65
+ `cfn-flow`'s perspective).
66
+ Say all `WebApp` EC2 servers connect to a long-running RDS database. That
67
+ database is not part of the cfn-flow service because it should re-used across
68
+ deploys. The database is a *backing resource* the service uses; not part
69
+ of the service itself.
70
+
71
+ #### Environments
72
+
73
+ An environment is an particular instantiation of a service. For example, you
74
+ could deploy your `WebApp` service to both a `development` and `production` environment.
75
+
76
+ `cfn-flow` is designed to support arbitrary environments like git supports
77
+ arbitrary branches.
78
+
79
+ Then `CFN_FLOW_ENVIRONMENT` environment variable can be used in
80
+ `cfn-flow.yml` to use the environment in your template parameters.
81
+
82
+ #### Deploying
83
+
84
+ Deployments consist of launching a *new stack* in a particular environment, then
85
+ shutting down the old stack. For example:
86
+
87
+ ```
88
+ cfn-flow deploy ENVIRONMENT --cleanup
89
+ ```
90
+
91
+ This follows the [red/black](http://techblog.netflix.com/2013/08/deploying-netflix-api.html)
92
+ or [blue/green](http://martinfowler.com/bliki/BlueGreenDeployment.html)
93
+ deployment pattern.
94
+
95
+ After verifying the new stack is working correctly, the deployer is expected to
96
+ delete the old stack.
97
+
98
+ To roll back a bad deploy, simply delete the *new* stack, while the *old*
99
+ stack is running.
100
+
101
+ Although CloudFormation supports updating existing stacks, `cfn-flow` prefers
102
+ launching immutable stacks. Stack updates are more difficult to test than new stacks; and there's less chance of a deployment error disrupting or breaking important resources.
103
+
104
+ #### AWS credentials
105
+
106
+ Set your AWS credentials so they can be found by the AWS SDK for Ruby ([details here](http://docs.aws.amazon.com/AWSSdkDocsRuby/latest/DeveloperGuide/set-up-creds.html)), e.g. using the `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables.
107
+
41
108
  ## Configuration
42
109
 
43
110
  `cfn-flow` looks for `./cfn-flow.yml` for stack and template configuration.
@@ -55,6 +122,7 @@ stack:
55
122
  # Stack name uses embedded ruby to support dynamic values
56
123
  stack_name: MyService-<%= Time.now.to_i %>
57
124
  # Required: *either* template_url or template_body
125
+ # NB: template_body is a local path to the template
58
126
  template_body: path/to/template.json
59
127
  # Alternatively:
60
128
  # template_url: https://MyS3Bucket.s3.amazonaws.com/MyPrefix/release/abc123/template.json
@@ -81,8 +149,16 @@ templates:
81
149
  bucket: MyS3Bucket
82
150
  s3_prefix: 'My/S3/Prefix'
83
151
 
152
+ ##
153
+ # Stacks
154
+ #
155
+ # These are the arguments passed when launching a new stack.
156
+ # It's nearly identical to the create_stack args in the ruby sdk, except
157
+ # parameters and tags are hashes. See http://amzn.to/1M0nBuq
158
+
84
159
  stack:
85
160
  stack_name: MyService-<%= Time.now.to_i %>
161
+ # NB: template_body is a local path to the template
86
162
  template_body: path/to/template.yml
87
163
  template_url: http://...
88
164
  parameters:
@@ -97,6 +173,8 @@ stack:
97
173
  stack_policy_body: "StackPolicyBody",
98
174
  stack_policy_url: "StackPolicyURL",
99
175
  tags:
176
+ # Whatever you want.
177
+ # Note that `cfn-flow` automatically adds two tags: `CfnFlowService` and `CfnFlowEnvironment`
100
178
  TagKey: TagValue
101
179
  # Who launched this stack
102
180
  Deployer: <%= ENV['USER'] %>
@@ -104,121 +182,145 @@ stack:
104
182
  BillingType: <%= ENV['CFN_FLOW_ENVIRONMENT'] == 'production' ? 'production' : 'development' %>
105
183
  ```
106
184
 
107
- #### Dev mode (default)
185
+ ### UX improvements:
108
186
 
109
- Dev mode allows you to quickly test template changes.
110
- `cfn-flow` validates all templates and uploads them to your personal prefix, overwriting existing templates.
187
+ `cfn-flow` includes a few developer-friendly features:
111
188
 
112
- Dev mode does not verify that your local changes are
113
- committed to git (as opposed to release mode).
189
+ #### YAML > JSON
114
190
 
115
- You should use dev mode for testing & verifying changes in non-production stacks.
191
+ `cfn-flow` lets you write templates in either JSON or
192
+ [YAML](http://www.yaml.org). YAML is a superset of JSON that allows a terser,
193
+ less cluttered syntax, inline comments, and code re-use with anchors (like
194
+ variables). YAML templates are transparently converted to JSON when uploaded to
195
+ S3 for use in CloudFormation stacks.
116
196
 
117
- ```
118
- # Set a personal name to prefix your templates.
119
- export CFN_FLOW_DEV_NAME=aaron
197
+ Note that you can use JSON snippets inside YAML templates. JSON is always valid
198
+ YAML.
199
+
200
+ #### Embedded ruby in `cfn-flow.yml`
120
201
 
121
- # Validate and upload all CloudFormation templates in your working directory to
122
- s3://my-bucket/dev/aaron/*
123
- # NB that this overwrites existing templates in your CFN_FLOW_DEV_NAME
124
- namespace.
202
+ To allow dynamic/programatic attributes, use
203
+ [ERB](https://en.wikipedia.org/wiki/ERuby) in `cfn-flow.yml`. For example:
125
204
 
126
- cfn-flow
205
+ ```yaml
206
+ stack:
207
+ name: my-stack-<%= Time.now.to_i %>
208
+ ...
209
+ parameters:
210
+ git_sha: <%= `git rev-parse --verify HEAD`.chomp %>
127
211
  ```
128
212
 
129
- You can launch or update test stacks using your dev template path to quickly test your
130
- template changes.
213
+ ## Usage
214
+
215
+ ### Working with stacks
216
+
217
+ `cfn-flow` automatically sets two tags on any stack it launches:
218
+
219
+ Name | Example value
220
+ --- | ---
221
+ CfnFlowService | `myapp`
222
+ CfnFlowEnvironment | `production`
131
223
 
132
- #### Release mode
224
+ These tags let `cfn-flow` associate stacks back to services & environments.
133
225
 
134
- Release mode publishes your templates to a versioned S3 path, and pushes a git
135
- tag of the version.
226
+ #### `cfn-flow deploy ENVIRONMENT`
227
+
228
+ Launches a stack in ENVIRONMENT. E.g. `cfn-flow deploy production`
229
+
230
+ Add the `--cleanup` option to be prompted to shut down other stacks in the environment.
231
+
232
+ #### `cfn-flow list ENVIRONMENT`
233
+
234
+ Show running stacks for ENVIRONMENT.
136
235
 
137
236
  ```
138
- # uploads templates to `s3://my-bucket/release/<git sha>/*`
139
- tag
140
- cfn-flow --release
237
+ $ cfn-flow list production
238
+
239
+ myapp-production-aaa (CREATE_COMPLETE)
240
+ myapp-production-bbb (CREATE_FAILED)
141
241
  ```
142
242
 
143
- Release mode ensures there are no uncommitted changes in your git working
144
- directory.
243
+ #### `cfn-flow delete STACK`
145
244
 
146
- Inspecting the differences between releases is possible using `git log` and `git
147
- diff`.
245
+ Deletes a stack.
148
246
 
149
- ## Configuration
247
+ ```
248
+ $ cfn-flow delete myapp-production-aaa
249
+ ```
150
250
 
151
- You can configure cfn-flow defaults by creating a `cfn-flow.yml` file in same
152
- directory you run `cfn-flow` (presumably the root of your project).
251
+ #### `cfn-flow show STACK`
153
252
 
154
- Settings in the configuration file are overridden by environment variables. And
155
- environment variables are overridden by command line arguments.
253
+ Show the status of STACK.
156
254
 
157
- ```
158
- # cfn-flow.yml in the root of your project
159
- # You can specify an alternative path by setting the CFN_FLOW_CONFIG environment
160
- # variable.
161
- #
162
- # All options in this config can be overridden with command line arguments
163
- ---
164
- # S3 bucket where templates are uploaded. No default.
165
- # Override with CFN_FLOW_BUCKET env var
166
- bucket: 'my-s3-bucket'
255
+ #### `cfn-flow events STACK`
256
+
257
+ List events for STACK
167
258
 
168
- # S3 path prefix. Default: none
169
- # Override with CFN_FLOW_TO env var
170
- to: my/project/prefix
259
+ Use the `--tail` option to poll for new events until the stack status is no
260
+ longer `*_IN_PROGRESS`
171
261
 
172
- # Local path in which to recursively search for templates. Default: .
173
- # Override with CFN_FLOW_FROM env var
174
- from: my/local/prefix
262
+ ### Common workflows
263
+
264
+ #### Deploying to production
175
265
 
176
- # AWS Region
177
- # Override with AWS_REGION env var
178
- region: us-east-1 # AWS region
179
266
  ```
267
+ # Launch a new stack for the current git commit
268
+ $ cfn-flow deploy production
269
+ Launching stack myapp-production-abc123
270
+ # ... wait for it to be ready
180
271
 
181
- #### AWS credentials
272
+ # See the other stacks
273
+ $ cfn-deploy list production
182
274
 
183
- AWS credentials can only be set using the
184
- `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` environment variables; or by
185
- using an EC2 instance's IAM role.
275
+ myapp-production-abc123 CREATE_COMPLETE
276
+ myapp-production-xyz987 CREATE_COMPLETE
186
277
 
278
+ # Shut down the old stack
279
+ $ cfn-flow delete myapp-production-xyz987
280
+ ```
187
281
 
188
- ## Sweet Features
282
+ ### Launching a development environment
189
283
 
190
- #### YAML > JSON
284
+ Launch a new stack for `myenv` environment
191
285
 
192
- `cfn-flow` lets you write templates in either JSON or
193
- [YAML](http://www.yaml.org). YAML is a superset of JSON that allows a terser,
194
- less cluttered syntax, inline comments, and code re-use with variables. YAML
195
- templates are transparently converted to JSON when uploaded to S3 for use in
196
- CloudFormation stacks.
286
+ ```
287
+ cfn-flow deploy myenv
288
+ ```
197
289
 
198
- #### Use versions in nested stack template URLs
290
+ ### Working with templates
199
291
 
200
- `cfn-flow` works great with [nested stack
201
- 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:
292
+ #### `cfn-flow validate`
202
293
 
203
294
  ```
204
- {
205
- "Type" : "AWS::CloudFormation::Stack",
206
- "Properties" : {
207
- "TemplateURL" : {
208
- "Fn::Join" : [ ":",
209
- [ "https://s3.amazonaws.com/my-bucket", {"Ref": "prefix"}, "my-template.json" ]
210
- ]
211
- }
212
- }
213
- }
295
+ # Runs validate-template on all templates.
296
+ # returns an error on any failure.
297
+ # does not persist to S3
298
+
299
+ $ cfn-flow validate path/to/template.yml
214
300
  ```
215
301
 
216
- While testing, set the `prefix` parameter to a dev prefix like `dev/aaron`. When you're confident your changes work, release them with cfn-flow and change the `prefix` parameter to `release/<git sha>` for production.
302
+ #### `cfn-flow publish`
303
+
304
+ Publish templates to S3 with immutable release names, or overwrite "dev names"
305
+ for quicker testing. *This is only needed if you want to use nested stack resources.*
217
306
 
218
- #### Continuous integration
307
+ ```
308
+ $ cfn-flow publish path/to/template.yml
309
+ # validates & uploads templates to dev path
310
+ # Env var CFN_FLOW_DEV_NAME=aaron
311
+ # E.g. https://mybucket.s3.amazonaws.com/myprefix/dev/aaron/mytemplate.yml
312
+
313
+ $ cfn-flow upload --release
314
+ # validates & uploads templates for current git sha
315
+ # E.g. https://mybucket.s3.amazonaws.com/myprefix/deadbeef/mytemplate.yml
316
+
317
+ $ cfn-flow upload --release=v1.0.0
318
+ # Upload templates for an arbitrary release name
319
+ # E.g. https://mybucket.s3.amazonaws.com/myprefix/v1.0.0/mytemplate.yml
320
+ ```
219
321
 
220
- #### Github commit status
322
+ ## License
221
323
 
222
- #### Minimal AWS credentials
324
+ Copyright Kickstarter, Inc.
223
325
 
224
- TODO: example IAM policy
326
+ Released under an [MIT License](http://opensource.org/licenses/MIT).
data/lib/cfn-flow.rb CHANGED
@@ -1,139 +1 @@
1
- require 'thor'
2
- require 'aws-sdk'
3
- require 'multi_json'
4
- require 'yaml'
5
- require 'erb'
6
-
7
- module CfnFlow
8
- class << self
9
-
10
- ##
11
- # Configuration
12
- def config_path
13
- ENV['CFN_FLOW_CONFIG_PATH'] || 'cfn-flow.yml'
14
- end
15
-
16
- def load_config
17
- @config = YAML.load(
18
- ERB.new( File.read(config_path) ).result(binding)
19
- )
20
- # TODO: Validate config?
21
- end
22
-
23
- def config_loaded?
24
- @config.is_a? Hash
25
- end
26
-
27
- def config
28
- load_config unless config_loaded?
29
- @config
30
- end
31
-
32
- def service
33
- unless config.key?('service')
34
- raise Thor::Error.new("No service name in #{config_path}. Add 'service: my_app_name'.")
35
- end
36
- config['service']
37
- end
38
-
39
- def stack_params(environment)
40
- unless config['stack'].is_a? Hash
41
- raise Thor::Error.new("No stack defined in #{config_path}. Add 'stack: ...'.")
42
- end
43
-
44
- # Dup & symbolize keys
45
- params = config['stack'].map{|k,v| [k.to_sym, v]}.to_h
46
-
47
- # Expand params
48
- if params[:parameters].is_a? Hash
49
- expanded_params = params[:parameters].map do |key,value|
50
- { parameter_key: key, parameter_value: value }
51
- end
52
- params[:parameters] = expanded_params
53
- end
54
-
55
- # Expand tags
56
- if params[:tags].is_a? Hash
57
- tags = params[:tags].map do |key, value|
58
- {key: key, value: value}
59
- end
60
-
61
- params[:tags] = tags
62
- end
63
-
64
- # Append CfnFlow tags
65
- params[:tags] ||= []
66
- params[:tags] << { key: 'CfnFlowService', value: service }
67
- params[:tags] << { key: 'CfnFlowEnvironment', value: environment }
68
-
69
- # Expand template body
70
- if params[:template_body].is_a? String
71
- begin
72
- body = CfnFlow::Template.new(params[:template_body]).to_json
73
- params[:template_body] = body
74
- rescue CfnFlow::Template::Error
75
- # Do nothing
76
- end
77
- end
78
-
79
- params
80
- end
81
-
82
- def template_s3_bucket
83
- unless config['templates'].is_a?(Hash) && config['templates']['s3_bucket']
84
- raise Thor::Error.new("No s3_bucket defined for templates in #{config_path}. Add 'templates: { s3_bucket: ... }'.")
85
- end
86
-
87
- config['templates']['s3_bucket']
88
- end
89
-
90
- def template_s3_prefix
91
- unless config['templates'].is_a?(Hash)
92
- raise Thor::Error.new("No templates defined in #{config_path}. Add 'templates: ... '.")
93
- end
94
-
95
- # Ok for this to be ''
96
- config['templates']['s3_prefix']
97
- end
98
-
99
- ##
100
- # Aws Clients
101
- def cfn_client
102
- @cfn_client ||= Aws::CloudFormation::Client.new(region: config[:region] || ENV['AWS_REGION'])
103
- end
104
-
105
- def cfn_resource
106
- # NB: increase default retry limit to avoid throttling errors iterating over stacks.
107
- # See https://github.com/aws/aws-sdk-ruby/issues/705
108
- @cfn_resource ||= Aws::CloudFormation::Resource.new(
109
- region: config[:region] || ENV['AWS_REGION'],
110
- retry_limit: 10
111
- )
112
- end
113
-
114
- # Clear aws sdk clients & config (for tests)
115
- def clear!
116
- @config = @cfn_client = @cfn_resource = nil
117
- end
118
-
119
- # Exit with status code = 1 when raising a Thor::Error
120
- # Override thor default
121
- def exit_on_failure?
122
- if instance_variable_defined?(:@exit_on_failure)
123
- @exit_on_failure
124
- else
125
- true
126
- end
127
- end
128
-
129
- def exit_on_failure=(value)
130
- @exit_on_failure = value
131
- end
132
- end
133
- end
134
-
135
- require 'cfn-flow/template'
136
- require 'cfn-flow/git'
137
- require 'cfn-flow/event_presenter'
138
- require 'cfn-flow/cli'
139
- require 'cfn-flow/version'
1
+ require 'cfn_flow'
data/lib/cfn_flow.rb ADDED
@@ -0,0 +1,138 @@
1
+ require 'thor'
2
+ require 'aws-sdk'
3
+ require 'multi_json'
4
+ require 'yaml'
5
+ require 'erb'
6
+
7
+ module CfnFlow
8
+ class << self
9
+
10
+ ##
11
+ # Configuration
12
+ def config_path
13
+ ENV['CFN_FLOW_CONFIG_PATH'] || 'cfn-flow.yml'
14
+ end
15
+
16
+ def load_config
17
+ @config = YAML.load(
18
+ ERB.new( File.read(config_path) ).result(binding)
19
+ )
20
+ end
21
+
22
+ def config_loaded?
23
+ @config.is_a? Hash
24
+ end
25
+
26
+ def config
27
+ load_config unless config_loaded?
28
+ @config
29
+ end
30
+
31
+ def service
32
+ unless config.key?('service')
33
+ raise Thor::Error.new("No service name in #{config_path}. Add 'service: my_app_name'.")
34
+ end
35
+ config['service']
36
+ end
37
+
38
+ def stack_params(environment)
39
+ unless config['stack'].is_a? Hash
40
+ raise Thor::Error.new("No stack defined in #{config_path}. Add 'stack: ...'.")
41
+ end
42
+
43
+ # Dup & symbolize keys
44
+ params = config['stack'].map{|k,v| [k.to_sym, v]}.to_h
45
+
46
+ # Expand params
47
+ if params[:parameters].is_a? Hash
48
+ expanded_params = params[:parameters].map do |key,value|
49
+ { parameter_key: key, parameter_value: value }
50
+ end
51
+ params[:parameters] = expanded_params
52
+ end
53
+
54
+ # Expand tags
55
+ if params[:tags].is_a? Hash
56
+ tags = params[:tags].map do |key, value|
57
+ {key: key, value: value}
58
+ end
59
+
60
+ params[:tags] = tags
61
+ end
62
+
63
+ # Append CfnFlow tags
64
+ params[:tags] ||= []
65
+ params[:tags] << { key: 'CfnFlowService', value: service }
66
+ params[:tags] << { key: 'CfnFlowEnvironment', value: environment }
67
+
68
+ # Expand template body
69
+ if params[:template_body].is_a? String
70
+ begin
71
+ body = CfnFlow::Template.new(params[:template_body]).to_json
72
+ params[:template_body] = body
73
+ rescue CfnFlow::Template::Error
74
+ # Do nothing
75
+ end
76
+ end
77
+
78
+ params
79
+ end
80
+
81
+ def template_s3_bucket
82
+ unless config['templates'].is_a?(Hash) && config['templates']['s3_bucket']
83
+ raise Thor::Error.new("No s3_bucket defined for templates in #{config_path}. Add 'templates: { s3_bucket: ... }'.")
84
+ end
85
+
86
+ config['templates']['s3_bucket']
87
+ end
88
+
89
+ def template_s3_prefix
90
+ unless config['templates'].is_a?(Hash)
91
+ raise Thor::Error.new("No templates defined in #{config_path}. Add 'templates: ... '.")
92
+ end
93
+
94
+ # Ok for this to be ''
95
+ config['templates']['s3_prefix']
96
+ end
97
+
98
+ ##
99
+ # Aws Clients
100
+ def cfn_client
101
+ @cfn_client ||= Aws::CloudFormation::Client.new(region: config['region'] || ENV['AWS_REGION'])
102
+ end
103
+
104
+ def cfn_resource
105
+ # NB: increase default retry limit to avoid throttling errors iterating over stacks.
106
+ # See https://github.com/aws/aws-sdk-ruby/issues/705
107
+ @cfn_resource ||= Aws::CloudFormation::Resource.new(
108
+ region: config['region'] || ENV['AWS_REGION'],
109
+ retry_limit: 10
110
+ )
111
+ end
112
+
113
+ # Clear aws sdk clients & config (for tests)
114
+ def clear!
115
+ @config = @cfn_client = @cfn_resource = nil
116
+ end
117
+
118
+ # Exit with status code = 1 when raising a Thor::Error
119
+ # Override thor default
120
+ def exit_on_failure?
121
+ if instance_variable_defined?(:@exit_on_failure)
122
+ @exit_on_failure
123
+ else
124
+ true
125
+ end
126
+ end
127
+
128
+ def exit_on_failure=(value)
129
+ @exit_on_failure = value
130
+ end
131
+ end
132
+ end
133
+
134
+ require 'cfn_flow/template'
135
+ require 'cfn_flow/git'
136
+ require 'cfn_flow/event_presenter'
137
+ require 'cfn_flow/cli'
138
+ require 'cfn_flow/version'
@@ -28,7 +28,7 @@ module CfnFlow
28
28
 
29
29
  desc 'publish TEMPLATE [...]', 'Validate & upload templates'
30
30
  method_option 'dev-name', type: :string, desc: 'Personal development prefix'
31
- method_option :release, type: :string, desc: 'Upload release', lazy_default: CfnFlow::Git.sha
31
+ method_option :release, type: :string, desc: 'Upload release', lazy_default: true
32
32
  method_option :verbose, type: :boolean, desc: 'Verbose output', default: false
33
33
  def publish(*templates)
34
34
  if templates.empty?
@@ -36,8 +36,6 @@ module CfnFlow
36
36
  end
37
37
 
38
38
  validate(*templates)
39
- # TODO: check git is clean before releasing
40
- #CfnFlow::Git.check_status if options['release']
41
39
 
42
40
  release = publish_release
43
41
  templates.each do |path|
@@ -95,12 +93,12 @@ module CfnFlow
95
93
 
96
94
  return if stacks.empty?
97
95
 
98
- table_header = options['no-header'] ? [] : [['NAME', 'ENVIRONMENT', 'STATUS']]
96
+ table_header = options['no-header'] ? [] : [['NAME', 'ENVIRONMENT', 'STATUS', 'CREATED']]
99
97
  table_data = stacks.map do |s|
100
98
  env_tag = s.tags.detect {|tag| tag.key == 'CfnFlowEnvironment'}
101
99
  env = env_tag ? env_tag.value : 'NONE'
102
100
 
103
- [ s.name, env, s.stack_status ]
101
+ [ s.name, env, s.stack_status, s.creation_time ]
104
102
  end
105
103
 
106
104
  print_table(table_header + table_data)
@@ -143,6 +141,14 @@ module CfnFlow
143
141
  end
144
142
  end
145
143
 
144
+ ##
145
+ # Version command
146
+ desc "version", "Prints the version information"
147
+ def version
148
+ say CfnFlow::VERSION
149
+ end
150
+ map %w(-v --version) => :version
151
+
146
152
  private
147
153
  def find_stack_in_service(name)
148
154
  stack = CfnFlow.cfn_resource.stack(name).load
@@ -164,7 +170,8 @@ module CfnFlow
164
170
  def publish_release
165
171
  # Add the release or dev name to the prefix
166
172
  if options[:release]
167
- 'release/' + options[:release]
173
+ release = options[:release] == true ? CfnFlow::Git.sha : options[:release]
174
+ 'release/' + release
168
175
  elsif options['dev-name']
169
176
  'dev/' + options['dev-name']
170
177
  elsif ENV['CFN_FLOW_DEV_NAME']
File without changes
File without changes
File without changes
@@ -0,0 +1,3 @@
1
+ module CfnFlow
2
+ VERSION = '0.8.0'
3
+ end
@@ -68,10 +68,18 @@ describe 'CfnFlow::CLI' do
68
68
  out.must_match("dev/#{name}")
69
69
  end
70
70
 
71
- it 'can take a release argument' do
72
- release = 'v2.0'
73
- out, _ = capture_io { cli.start [:publish, template, '--release', release] }
74
- out.must_match CfnFlow::Template.new(template).url("release/#{release}")
71
+ describe 'with --release' do
72
+ it 'defaults to git sha' do
73
+ sha = CfnFlow::Git.sha
74
+ out, _ = capture_io { cli.start [:publish, template, '--release'] }
75
+ out.must_match CfnFlow::Template.new(template).url("release/#{sha}")
76
+ end
77
+
78
+ it 'can take a value' do
79
+ release = 'v2.0'
80
+ out, _ = capture_io { cli.start [:publish, template, '--release', release] }
81
+ out.must_match CfnFlow::Template.new(template).url("release/#{release}")
82
+ end
75
83
  end
76
84
 
77
85
  it 'can fail with malformed templates' do
@@ -182,13 +190,13 @@ describe 'CfnFlow::CLI' do
182
190
  end
183
191
  it 'should print the stack' do
184
192
  out, err = capture_io { cli.start [:list] }
185
- out.must_match(/mystack\s+production\s+CREATE_COMPLETE/)
193
+ out.must_match(/mystack\s+production\s+CREATE_COMPLETE\s+#{memo_now.utc}/)
186
194
  err.must_equal ''
187
195
  end
188
196
 
189
197
  it 'should print the header' do
190
198
  out, _ = capture_io { cli.start [:list] }
191
- out.must_match(/NAME\s+ENVIRONMENT\s+STATUS/)
199
+ out.must_match(/NAME\s+ENVIRONMENT\s+STATUS\s+CREATED/)
192
200
  end
193
201
 
194
202
  it 'should print stacks when passed an environment' do
@@ -213,7 +221,7 @@ describe 'CfnFlow::CLI' do
213
221
  stacks: [
214
222
  { stack_name: "mystack",
215
223
  stack_status: 'CREATE_COMPLETE',
216
- creation_time: Time.now,
224
+ creation_time: memo_now,
217
225
  tags: [
218
226
  {key: 'CfnFlowService', value: 'none-such-service'},
219
227
  {key: 'CfnFlowEnvironment', value: 'production'}
@@ -380,4 +388,22 @@ describe 'CfnFlow::CLI' do
380
388
  end
381
389
  end
382
390
 
391
+ describe '#version' do
392
+ let(:version) { CfnFlow::VERSION + "\n" }
393
+ it 'prints the version' do
394
+ out, _ = capture_io { cli.start [:version] }
395
+ out.must_equal version
396
+ end
397
+
398
+ it 'handles -v argument' do
399
+ out, _ = capture_io { cli.start ['-v'] }
400
+ out.must_equal version
401
+ end
402
+
403
+ it 'handles --version argument' do
404
+ out, _ = capture_io { cli.start ['--version'] }
405
+ out.must_equal version
406
+ end
407
+
408
+ end
383
409
  end
File without changes
File without changes
@@ -135,7 +135,7 @@ describe 'CfnFlow' do
135
135
 
136
136
  it 'can be overridden with config' do
137
137
  ENV['AWS_REGION'] = 'env-region'
138
- subject.instance_variable_set(:@config, {region: 'config-region' })
138
+ subject.instance_variable_set(:@config, {'region' => 'config-region' })
139
139
  subject.cfn_client.config.region.must_equal 'config-region'
140
140
  end
141
141
  end
@@ -159,7 +159,7 @@ describe 'CfnFlow' do
159
159
 
160
160
  it 'can be overridden with config' do
161
161
  ENV['AWS_REGION'] = 'env-region'
162
- subject.instance_variable_set(:@config, {region: 'config-region' })
162
+ subject.instance_variable_set(:@config, {'region' => 'config-region' })
163
163
  subject.cfn_client.config.region.must_equal 'config-region'
164
164
  end
165
165
  end
@@ -1,6 +1,5 @@
1
1
  ---
2
2
  service: cfn-flow-specs
3
- region: us-east-1
4
3
  templates:
5
4
  s3_bucket: test-bucket
6
5
  s3_prefix: test-prefix
data/spec/helper.rb CHANGED
@@ -38,11 +38,15 @@ class Minitest::Spec
38
38
  Aws.config.delete(:cloudformation)
39
39
  end
40
40
 
41
+ def memo_now
42
+ @now = Time.now
43
+ end
44
+
41
45
  def stub_stack_data(attrs = {})
42
46
  {
43
47
  stack_name: "mystack",
44
48
  stack_status: 'CREATE_COMPLETE',
45
- creation_time: Time.now,
49
+ creation_time: memo_now,
46
50
  tags: [
47
51
  {key: 'CfnFlowService', value: CfnFlow.service},
48
52
  {key: 'CfnFlowEnvironment', value: 'production'}
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cfn-flow
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Suggs
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-04 00:00:00.000000000 Z
11
+ date: 2015-09-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk
@@ -92,15 +92,16 @@ files:
92
92
  - bin/cfn-flow
93
93
  - bin/rake
94
94
  - lib/cfn-flow.rb
95
- - lib/cfn-flow/cli.rb
96
- - lib/cfn-flow/event_presenter.rb
97
- - lib/cfn-flow/git.rb
98
- - lib/cfn-flow/template.rb
99
- - lib/cfn-flow/version.rb
100
- - spec/cfn-flow/cli_spec.rb
101
- - spec/cfn-flow/event_presenter_spec.rb
102
- - spec/cfn-flow/template_spec.rb
103
- - spec/cfn-flow_spec.rb
95
+ - lib/cfn_flow.rb
96
+ - lib/cfn_flow/cli.rb
97
+ - lib/cfn_flow/event_presenter.rb
98
+ - lib/cfn_flow/git.rb
99
+ - lib/cfn_flow/template.rb
100
+ - lib/cfn_flow/version.rb
101
+ - spec/cfn_flow/cli_spec.rb
102
+ - spec/cfn_flow/event_presenter_spec.rb
103
+ - spec/cfn_flow/template_spec.rb
104
+ - spec/cfn_flow_spec.rb
104
105
  - spec/data/cfn-flow.yml
105
106
  - spec/data/invalid.json
106
107
  - spec/data/invalid.yml
@@ -133,10 +134,10 @@ signing_key:
133
134
  specification_version: 4
134
135
  summary: A CLI for CloudFormation templates
135
136
  test_files:
136
- - spec/cfn-flow/cli_spec.rb
137
- - spec/cfn-flow/event_presenter_spec.rb
138
- - spec/cfn-flow/template_spec.rb
139
- - spec/cfn-flow_spec.rb
137
+ - spec/cfn_flow/cli_spec.rb
138
+ - spec/cfn_flow/event_presenter_spec.rb
139
+ - spec/cfn_flow/template_spec.rb
140
+ - spec/cfn_flow_spec.rb
140
141
  - spec/data/cfn-flow.yml
141
142
  - spec/data/invalid.json
142
143
  - spec/data/invalid.yml
@@ -1,3 +0,0 @@
1
- module CfnFlow
2
- VERSION = '0.7.0'
3
- end