cfn-flow 0.7.0 → 0.8.0

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 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