cfn-flow 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +148 -48
- data/lib/cfn_flow.rb +7 -36
- data/lib/cfn_flow/cached_stack.rb +32 -0
- data/lib/cfn_flow/stack_params.rb +71 -0
- data/lib/cfn_flow/version.rb +1 -1
- data/spec/cfn_flow/cached_stack_spec.rb +71 -0
- data/spec/cfn_flow/stack_params_spec.rb +114 -0
- data/spec/cfn_flow_spec.rb +3 -24
- metadata +10 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d70dd0718f3ff8dd77315c1478efa29bde5b9411
|
4
|
+
data.tar.gz: a15a7d6ee25f1d91908d7d437cde77443f8e1a24
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 50a5acc781978e22d1a8513d90b14609331de3951b93d51988c7e92c777842d7e7288e1f9dfe830fd9b84f6fad31d7bdaca36a64d5c51784eba2f39d1d78935d
|
7
|
+
data.tar.gz: b412de302fb2bab1ee786f9910354e656f4b60796a172e54705012502cf581847770244672afeec8d0ef13cdfebde09ae7808a455f4ea928bf9a52af557a4d24
|
data/README.md
CHANGED
@@ -1,22 +1,56 @@
|
|
1
1
|
# cfn-flow
|
2
|
-
`cfn-flow` is
|
2
|
+
`cfn-flow` is a command-line tool for developing [AWS CloudFormation](https://aws.amazon.com/cloudformation/) templates and deploying stacks.
|
3
3
|
|
4
4
|
It provides a *simple*, *standard*, and *flexible* process for using CloudFormation, ideal for DevOps-style organizations.
|
5
5
|
|
6
|
-
|
6
|
+
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
7
|
+
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
8
|
+
## Table of Contents
|
9
|
+
|
10
|
+
- [Opinions](#opinions)
|
11
|
+
- [Installation](#installation)
|
12
|
+
- [Key concepts](#key-concepts)
|
13
|
+
- [Services](#services)
|
14
|
+
- [Environments](#environments)
|
15
|
+
- [Deploying](#deploying)
|
16
|
+
- [AWS credentials](#aws-credentials)
|
17
|
+
- [Configuration](#configuration)
|
18
|
+
- [UX improvements](#ux-improvements)
|
19
|
+
- [YAML > JSON](#yaml--json)
|
20
|
+
- [Embedded ruby in `cfn-flow.yml`](#embedded-ruby-in-cfn-flowyml)
|
21
|
+
- [Usage](#usage)
|
22
|
+
- [Working with stacks](#working-with-stacks)
|
23
|
+
- [Deploy (launch) a stack](#deploy-launch-a-stack)
|
24
|
+
- [List stacks for your service or environment](#list-stacks-for-your-service-or-environment)
|
25
|
+
- [Inspect a stack](#inspect-a-stack)
|
26
|
+
- [Show stack events](#show-stack-events)
|
27
|
+
- [Delete a stack](#delete-a-stack)
|
28
|
+
- [Common workflows](#common-workflows)
|
29
|
+
- [Deploying to production](#deploying-to-production)
|
30
|
+
- [Launching a development environment](#launching-a-development-environment)
|
31
|
+
- [Working with templates](#working-with-templates)
|
32
|
+
- [Validate templates](#validate-templates)
|
33
|
+
- [Publish templates to S3](#publish-templates-to-s3)
|
34
|
+
- [License](#license)
|
35
|
+
|
36
|
+
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
37
|
+
|
38
|
+
## Opinions
|
7
39
|
|
8
40
|
`cfn-flow` introduces a consist, convenient workflow that encourages good template organization
|
9
41
|
and deploy practices.
|
10
42
|
|
11
|
-
1. *Optimize for happiness.* The workflow should be easy
|
12
|
-
2. *Optimize for onboarding.* The workflow should be simple to learn &
|
13
|
-
3. *Auditable changes.* Know who changed what when. Leverage git history.
|
43
|
+
1. *Optimize for happiness.* The workflow should be easy & enjoyable to use.
|
44
|
+
2. *Optimize for onboarding.* The workflow should be simple to learn, understand, & debug.
|
45
|
+
3. *Auditable changes.* Know who changed what when & why. Leverage git history.
|
14
46
|
4. *Immutable releases.* The code in a release never changes. To make a change,
|
15
47
|
launch a new stack.
|
16
48
|
|
17
49
|
The features & implementation of `cfn-flow` itself must also be simple. This follows the Unix philosophy of "[worse is
|
18
50
|
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.
|
19
51
|
|
52
|
+
See [this introductory blog post](https://www.kickstarter.com/backing-and-hacking/introducing-cfn-flow-a-practical-workflow-for-aws-cloudformation) for our motivation behind `cfn-flow`.
|
53
|
+
|
20
54
|
## Installation
|
21
55
|
|
22
56
|
Via [rubygems](https://rubygems.org/gems/cfn-flow):
|
@@ -26,23 +60,7 @@ gem install cfn-flow
|
|
26
60
|
|
27
61
|
The `git` command is also needed.
|
28
62
|
|
29
|
-
##
|
30
|
-
|
31
|
-
```
|
32
|
-
# Get help
|
33
|
-
cfn-flow help
|
34
|
-
|
35
|
-
cfn-flow help COMMAND
|
36
|
-
# E.g.:
|
37
|
-
cfn-flow help deploy
|
38
|
-
```
|
39
|
-
|
40
|
-
Launch a CloudFormation stack:
|
41
|
-
```
|
42
|
-
cfn-flow deploy production
|
43
|
-
```
|
44
|
-
|
45
|
-
## How it works
|
63
|
+
## Key concepts
|
46
64
|
|
47
65
|
`cfn-flow` works from a directory containing a `cfn-flow.yml` config file, and a CloudFormation template.
|
48
66
|
Presumably your app code is in the same directory, but it doesn't have to be.
|
@@ -51,8 +69,8 @@ There are two key concepts for `cfn-flow`: **services** and **environments**.
|
|
51
69
|
|
52
70
|
#### Services
|
53
71
|
|
54
|
-
A service
|
55
|
-
|
72
|
+
A service comprises a set of resources that change together.
|
73
|
+
Each service has its own `cfn-flow.yml` config file. A service
|
56
74
|
can be instantiated as several distinct environments.
|
57
75
|
|
58
76
|
For example, a `WebApp` service could have a CloudFormation template that
|
@@ -64,7 +82,7 @@ service to an environment will create a new ELB, LaunchConfig, and AutoScalingGr
|
|
64
82
|
Resources that *do not* change across deploys are not part of the service (from
|
65
83
|
`cfn-flow`'s perspective).
|
66
84
|
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
|
85
|
+
database is not part of the cfn-flow service because it is re-used across
|
68
86
|
deploys. The database is a *backing resource* the service uses; not part
|
69
87
|
of the service itself.
|
70
88
|
|
@@ -76,8 +94,9 @@ could deploy your `WebApp` service to both a `development` and `production` envi
|
|
76
94
|
`cfn-flow` is designed to support arbitrary environments like git supports
|
77
95
|
arbitrary branches.
|
78
96
|
|
79
|
-
|
80
|
-
`cfn-flow.yml` to use the environment in your template parameters.
|
97
|
+
**Pro tip:** Use the `CFN_FLOW_ENVIRONMENT` environment variable in
|
98
|
+
`cfn-flow.yml` config to use the environment in your template parameters.
|
99
|
+
See [Configuration](#configuration) for examples.
|
81
100
|
|
82
101
|
#### Deploying
|
83
102
|
|
@@ -131,7 +150,6 @@ stack:
|
|
131
150
|
And here's a maximal config file:
|
132
151
|
|
133
152
|
```yaml
|
134
|
-
---
|
135
153
|
# Example cfn-flow.yml
|
136
154
|
|
137
155
|
service: MyService
|
@@ -157,7 +175,8 @@ templates:
|
|
157
175
|
# parameters and tags are hashes. See http://amzn.to/1M0nBuq
|
158
176
|
|
159
177
|
stack:
|
160
|
-
|
178
|
+
# Use the CFN_FLOW_ENVIRONMENT var & git sha in stack name
|
179
|
+
stack_name: MyService-<%= ENV['CFN_FLOW_ENVIRONMENT'] %>-<%= `git rev-parse --short HEAD`.chomp %>
|
161
180
|
# NB: template_body is a local path to the template
|
162
181
|
template_body: path/to/template.yml
|
163
182
|
template_url: http://...
|
@@ -165,6 +184,21 @@ stack:
|
|
165
184
|
# Your parameters, e.g.:
|
166
185
|
vpcid: vpc-1234
|
167
186
|
ami: ami-abcd
|
187
|
+
|
188
|
+
##
|
189
|
+
# Use outputs from other stacks
|
190
|
+
|
191
|
+
# This set the `load_balancer` parameter to the value of the
|
192
|
+
# `elbname` output of `my-elb-stack`
|
193
|
+
load_balancer:
|
194
|
+
stack: my-elb-stack
|
195
|
+
output: elbname
|
196
|
+
|
197
|
+
# If you don't specify the output name, it's assumed to be same
|
198
|
+
# as the parameter key:
|
199
|
+
ssh_security_group:
|
200
|
+
stack: my-bastion-stack
|
201
|
+
|
168
202
|
disable_rollback: true,
|
169
203
|
timeout_in_minutes: 1,
|
170
204
|
notification_arns: ["NotificationARN"],
|
@@ -182,7 +216,7 @@ stack:
|
|
182
216
|
BillingType: <%= ENV['CFN_FLOW_ENVIRONMENT'] == 'production' ? 'production' : 'development' %>
|
183
217
|
```
|
184
218
|
|
185
|
-
|
219
|
+
## UX improvements
|
186
220
|
|
187
221
|
`cfn-flow` includes a few developer-friendly features:
|
188
222
|
|
@@ -199,7 +233,7 @@ YAML.
|
|
199
233
|
|
200
234
|
#### Embedded ruby in `cfn-flow.yml`
|
201
235
|
|
202
|
-
To allow dynamic/
|
236
|
+
To allow dynamic/programmatic attributes, use
|
203
237
|
[ERB](https://en.wikipedia.org/wiki/ERuby) in `cfn-flow.yml`. For example:
|
204
238
|
|
205
239
|
```yaml
|
@@ -210,8 +244,41 @@ stack:
|
|
210
244
|
git_sha: <%= `git rev-parse --verify HEAD`.chomp %>
|
211
245
|
```
|
212
246
|
|
247
|
+
#### Use stack outputs as parameters
|
248
|
+
`cfn-flow` lets you easily reference stack outputs as parameters for new stacks.
|
249
|
+
|
250
|
+
```yaml
|
251
|
+
# cfn-flow.yml
|
252
|
+
stack:
|
253
|
+
parameters:
|
254
|
+
# Set my-param to the `my-param` output of `another-stack`
|
255
|
+
my-param:
|
256
|
+
stack: another-stack
|
257
|
+
|
258
|
+
# Set my-param to the `my-output` output of `another-stack`
|
259
|
+
my-param:
|
260
|
+
stack: another-stack
|
261
|
+
output: my-output
|
262
|
+
```
|
263
|
+
|
213
264
|
## Usage
|
214
265
|
|
266
|
+
Getting help:
|
267
|
+
|
268
|
+
```
|
269
|
+
# Get help
|
270
|
+
cfn-flow help
|
271
|
+
|
272
|
+
cfn-flow help COMMAND
|
273
|
+
# E.g.:
|
274
|
+
cfn-flow help deploy
|
275
|
+
```
|
276
|
+
|
277
|
+
Launch a CloudFormation stack:
|
278
|
+
```
|
279
|
+
cfn-flow deploy production
|
280
|
+
```
|
281
|
+
|
215
282
|
### Working with stacks
|
216
283
|
|
217
284
|
`cfn-flow` automatically sets two tags on any stack it launches:
|
@@ -223,42 +290,64 @@ CfnFlowEnvironment | `production`
|
|
223
290
|
|
224
291
|
These tags let `cfn-flow` associate stacks back to services & environments.
|
225
292
|
|
226
|
-
####
|
293
|
+
#### Deploy (launch) a stack
|
294
|
+
|
295
|
+
```
|
296
|
+
cfn-flow deploy ENVIRONMENT
|
297
|
+
```
|
227
298
|
|
228
299
|
Launches a stack in ENVIRONMENT. E.g. `cfn-flow deploy production`
|
229
300
|
|
230
301
|
Add the `--cleanup` option to be prompted to shut down other stacks in the environment.
|
231
302
|
|
232
|
-
####
|
303
|
+
#### List stacks for your service or environment
|
233
304
|
|
234
|
-
|
305
|
+
```
|
306
|
+
cfn-flow list [ENVIRONMENT]
|
307
|
+
```
|
308
|
+
|
309
|
+
Show all stacks running in your service, or just in an ENVIRONMENT.
|
235
310
|
|
236
311
|
```
|
312
|
+
# For example:
|
237
313
|
$ cfn-flow list production
|
238
314
|
|
239
315
|
myapp-production-aaa (CREATE_COMPLETE)
|
240
316
|
myapp-production-bbb (CREATE_FAILED)
|
241
317
|
```
|
242
318
|
|
243
|
-
####
|
244
|
-
|
245
|
-
Deletes a stack.
|
319
|
+
#### Inspect a stack
|
246
320
|
|
247
321
|
```
|
248
|
-
|
322
|
+
cfn-flow show STACK
|
249
323
|
```
|
250
324
|
|
251
|
-
#### `cfn-flow show STACK`
|
252
|
-
|
253
325
|
Show the status of STACK.
|
254
326
|
|
255
|
-
####
|
327
|
+
#### Show stack events
|
328
|
+
|
329
|
+
```
|
330
|
+
cfn-flow events STACK
|
331
|
+
```
|
256
332
|
|
257
333
|
List events for STACK
|
258
334
|
|
259
|
-
Use the `--
|
335
|
+
Use the `--poll` option to poll for new events until the stack status is no
|
260
336
|
longer `*_IN_PROGRESS`
|
261
337
|
|
338
|
+
#### Delete a stack
|
339
|
+
|
340
|
+
```
|
341
|
+
cfn-flow delete STACK
|
342
|
+
```
|
343
|
+
|
344
|
+
Deletes a stack.
|
345
|
+
|
346
|
+
```
|
347
|
+
# For example:
|
348
|
+
$ cfn-flow delete myapp-production-aaa
|
349
|
+
```
|
350
|
+
|
262
351
|
### Common workflows
|
263
352
|
|
264
353
|
#### Deploying to production
|
@@ -289,22 +378,33 @@ cfn-flow deploy myenv
|
|
289
378
|
|
290
379
|
### Working with templates
|
291
380
|
|
292
|
-
####
|
381
|
+
#### Validate templates
|
293
382
|
|
294
383
|
```
|
295
|
-
|
296
|
-
|
297
|
-
# does not persist to S3
|
384
|
+
cfn-flow validate TEMPLATE [...]
|
385
|
+
```
|
298
386
|
|
387
|
+
Validates CloudFormation templates; does not persist to S3.
|
388
|
+
|
389
|
+
```
|
390
|
+
# For example:
|
299
391
|
$ cfn-flow validate path/to/template.yml
|
300
392
|
```
|
301
393
|
|
302
|
-
####
|
394
|
+
#### Publish templates to S3
|
395
|
+
|
396
|
+
```
|
397
|
+
cfn-flow publish TEMPLATE [...]
|
398
|
+
```
|
303
399
|
|
304
400
|
Publish templates to S3 with immutable release names, or overwrite "dev names"
|
305
|
-
for quicker testing.
|
401
|
+
for quicker testing.
|
402
|
+
|
403
|
+
**Note:** Publishing to S3 is only needed if you want to use [nested stack resources](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stack.html),
|
404
|
+
(that is, stacks that lainclude other stacks).
|
306
405
|
|
307
406
|
```
|
407
|
+
# For example:
|
308
408
|
$ cfn-flow publish path/to/template.yml
|
309
409
|
# validates & uploads templates to dev path
|
310
410
|
# Env var CFN_FLOW_DEV_NAME=aaron
|
data/lib/cfn_flow.rb
CHANGED
@@ -39,43 +39,11 @@ module CfnFlow
|
|
39
39
|
unless config['stack'].is_a? Hash
|
40
40
|
raise Thor::Error.new("No stack defined in #{config_path}. Add 'stack: ...'.")
|
41
41
|
end
|
42
|
+
params = StackParams.expanded(config['stack'])
|
42
43
|
|
43
|
-
|
44
|
-
|
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
|
44
|
+
params.
|
45
|
+
add_tag('CfnFlowService' => service).
|
46
|
+
add_tag('CfnFlowEnvironment' => environment)
|
79
47
|
end
|
80
48
|
|
81
49
|
def template_s3_bucket
|
@@ -113,6 +81,7 @@ module CfnFlow
|
|
113
81
|
# Clear aws sdk clients & config (for tests)
|
114
82
|
def clear!
|
115
83
|
@config = @cfn_client = @cfn_resource = nil
|
84
|
+
CachedStack.stack_cache.clear
|
116
85
|
end
|
117
86
|
|
118
87
|
# Exit with status code = 1 when raising a Thor::Error
|
@@ -131,6 +100,8 @@ module CfnFlow
|
|
131
100
|
end
|
132
101
|
end
|
133
102
|
|
103
|
+
require 'cfn_flow/cached_stack'
|
104
|
+
require 'cfn_flow/stack_params'
|
134
105
|
require 'cfn_flow/template'
|
135
106
|
require 'cfn_flow/git'
|
136
107
|
require 'cfn_flow/event_presenter'
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module CfnFlow
|
2
|
+
class CachedStack
|
3
|
+
|
4
|
+
class MissingOutput < StandardError; end
|
5
|
+
|
6
|
+
def self.stack_cache
|
7
|
+
@stack_cache ||= {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.get_output(stack:, output:)
|
11
|
+
new(stack).output(output)
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :stack_name
|
15
|
+
|
16
|
+
def initialize(stack_name)
|
17
|
+
@stack_name = stack_name
|
18
|
+
end
|
19
|
+
|
20
|
+
def output(name)
|
21
|
+
output = stack_cache.outputs.detect{|out| out.output_key == name }
|
22
|
+
unless output
|
23
|
+
raise MissingOutput.new("Can't find outpout #{name} for stack #{stack_name}")
|
24
|
+
end
|
25
|
+
output.output_value
|
26
|
+
end
|
27
|
+
|
28
|
+
def stack_cache
|
29
|
+
self.class.stack_cache[stack_name] ||= CfnFlow.cfn_resource.stack(stack_name).load
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module CfnFlow
|
2
|
+
# Extend hash with some special behavior to generate the
|
3
|
+
# style of hash aws-sdk expects
|
4
|
+
class StackParams < Hash
|
5
|
+
|
6
|
+
def self.expanded(hash)
|
7
|
+
self[hash].
|
8
|
+
with_symbolized_keys.
|
9
|
+
with_expanded_parameters.
|
10
|
+
with_expanded_tags.
|
11
|
+
with_expanded_template_body
|
12
|
+
end
|
13
|
+
|
14
|
+
def with_symbolized_keys
|
15
|
+
self.inject(StackParams.new) do |accum, pair|
|
16
|
+
key, value = pair
|
17
|
+
accum.merge(key.to_sym => value)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def with_expanded_parameters
|
22
|
+
return self unless self[:parameters].is_a? Hash
|
23
|
+
|
24
|
+
expanded_params = self[:parameters].map do |key,value|
|
25
|
+
{ parameter_key: key, parameter_value: fetch_value(key, value) }
|
26
|
+
end
|
27
|
+
|
28
|
+
self.merge(parameters: expanded_params)
|
29
|
+
end
|
30
|
+
|
31
|
+
def with_expanded_tags
|
32
|
+
return self unless self[:tags].is_a? Hash
|
33
|
+
|
34
|
+
tags = self[:tags].map do |key, value|
|
35
|
+
{key: key, value: value}
|
36
|
+
end
|
37
|
+
|
38
|
+
self.merge(tags: tags)
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_tag(hash)
|
42
|
+
new_tags = hash.map do |k,v|
|
43
|
+
{key: k, value: v }
|
44
|
+
end
|
45
|
+
tags = (self[:tags] || []) + new_tags
|
46
|
+
self.merge(tags: tags)
|
47
|
+
end
|
48
|
+
|
49
|
+
def with_expanded_template_body
|
50
|
+
return self unless self[:template_body].is_a? String
|
51
|
+
body = CfnFlow::Template.new(self[:template_body]).to_json
|
52
|
+
self.merge(template_body: body)
|
53
|
+
rescue CfnFlow::Template::Error
|
54
|
+
# Do nothing
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
def fetch_value(key, value)
|
59
|
+
# Dereference stack output params
|
60
|
+
if value.is_a?(Hash) && value.key?('stack')
|
61
|
+
stack_name = value['stack']
|
62
|
+
stack_output_name = value['output'] || key
|
63
|
+
|
64
|
+
value = CachedStack.get_output(stack: stack_name, output: stack_output_name)
|
65
|
+
else
|
66
|
+
value
|
67
|
+
end
|
68
|
+
end
|
69
|
+
private :fetch_value
|
70
|
+
end
|
71
|
+
end
|
data/lib/cfn_flow/version.rb
CHANGED
@@ -0,0 +1,71 @@
|
|
1
|
+
require_relative '../helper'
|
2
|
+
|
3
|
+
describe 'CfnFlow::CachedStack' do
|
4
|
+
subject { CfnFlow::CachedStack }
|
5
|
+
|
6
|
+
describe '.stack_cache' do
|
7
|
+
it 'defaults to a hash' do
|
8
|
+
subject.stack_cache.must_equal({})
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '.get_output' do
|
13
|
+
let(:output_value) { 'myvalue' }
|
14
|
+
|
15
|
+
before do
|
16
|
+
Aws.config[:cloudformation]= {
|
17
|
+
stub_responses: {
|
18
|
+
describe_stacks: { stacks: [ stub_stack_data.merge(outputs: [{ output_key: "myoutput", output_value: output_value } ]) ] }
|
19
|
+
}
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'returns the output' do
|
24
|
+
subject.get_output(stack: 'mystack', output: 'myoutput').must_equal output_value
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'has required kwargs' do
|
28
|
+
-> { subject.get_output }.must_raise(ArgumentError)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'an instance' do
|
33
|
+
subject { CfnFlow::CachedStack.new('mystack') }
|
34
|
+
let(:output_value) { 'myvalue' }
|
35
|
+
|
36
|
+
before do
|
37
|
+
Aws.config[:cloudformation]= {
|
38
|
+
stub_responses: {
|
39
|
+
describe_stacks: { stacks: [ stub_stack_data.merge(outputs: [{ output_key: "myoutput", output_value: output_value } ]) ] }
|
40
|
+
}
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should return the output value" do
|
45
|
+
subject.output('myoutput').must_equal output_value
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "with a missing output" do
|
49
|
+
it "should raise an error" do
|
50
|
+
-> { subject.output("no-such-output") }.must_raise(CfnFlow::CachedStack::MissingOutput)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "with a missing stack" do
|
55
|
+
|
56
|
+
subject { CfnFlow::CachedStack.new('no-such-stack') }
|
57
|
+
before do
|
58
|
+
Aws.config[:cloudformation]= {
|
59
|
+
stub_responses: {
|
60
|
+
describe_stacks: 'ValidationError'
|
61
|
+
}
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should raise an error" do
|
66
|
+
-> { subject.output('blah') }.must_raise(Aws::CloudFormation::Errors::ValidationError)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require_relative '../helper'
|
2
|
+
|
3
|
+
describe 'CfnFlow::StackParams' do
|
4
|
+
subject { CfnFlow::StackParams }
|
5
|
+
|
6
|
+
it 'should be a hash' do
|
7
|
+
subject.new.must_be_kind_of Hash
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '.expanded' do
|
11
|
+
it "returns a StackParams hash" do
|
12
|
+
subject.expanded({}).must_be_kind_of subject
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#with_symbolized_keys' do
|
17
|
+
it 'works' do
|
18
|
+
subject[{'foo' => 1, :bar => true}].with_symbolized_keys.must_equal({foo: 1, bar: true})
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#with_expanded_parameters' do
|
23
|
+
it 'reformats parameters hash to array of hashes' do
|
24
|
+
hash = {
|
25
|
+
parameters: { 'k1' => 'v1', 'k2' => 'v2' }
|
26
|
+
}
|
27
|
+
|
28
|
+
expected = {
|
29
|
+
parameters: [
|
30
|
+
{parameter_key: 'k1', parameter_value: 'v1'},
|
31
|
+
{parameter_key: 'k2', parameter_value: 'v2'}
|
32
|
+
]
|
33
|
+
}
|
34
|
+
|
35
|
+
subject[hash].with_expanded_parameters.must_equal expected
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'with stack outputs' do
|
39
|
+
let(:output_key) { 'my-output-key' }
|
40
|
+
let(:output_value) { 'my-output-value' }
|
41
|
+
|
42
|
+
before do
|
43
|
+
Aws.config[:cloudformation]= {
|
44
|
+
stub_responses: {
|
45
|
+
describe_stacks: { stacks: [ stub_stack_data.merge(outputs: [{ output_key: output_key, output_value: output_value } ]) ] }
|
46
|
+
}
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'fetches stack outputs with explicit output key' do
|
51
|
+
hash = {
|
52
|
+
parameters: {
|
53
|
+
'my-key' => { 'stack' => 'my-stack', 'output' => output_key}
|
54
|
+
}
|
55
|
+
}
|
56
|
+
expected = {
|
57
|
+
parameters: [ {parameter_key: 'my-key', parameter_value: output_value} ]
|
58
|
+
}
|
59
|
+
|
60
|
+
subject[hash].with_expanded_parameters.must_equal expected
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'fetches stack outputs with implicit output key' do
|
64
|
+
hash = {
|
65
|
+
parameters: {
|
66
|
+
output_key => { 'stack' => 'my-stack'}
|
67
|
+
}
|
68
|
+
}
|
69
|
+
expected = {
|
70
|
+
parameters: [ {parameter_key: output_key, parameter_value: output_value} ]
|
71
|
+
}
|
72
|
+
|
73
|
+
subject[hash].with_expanded_parameters.must_equal expected
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '#with_expanded_tags' do
|
79
|
+
it 'expands tags hash to array of hashes' do
|
80
|
+
hash = {tags: {'k' => 'v'} }
|
81
|
+
expected = {tags: [{key: 'k', value: 'v'}]}
|
82
|
+
subject[hash].with_expanded_tags.must_equal expected
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe '#add_tag' do
|
87
|
+
it 'sets an empty tag hash' do
|
88
|
+
subject.new.add_tag('k' => 'v').must_equal({tags: [{key: 'k', value: 'v'}]})
|
89
|
+
|
90
|
+
end
|
91
|
+
it 'appends to existing tag hash' do
|
92
|
+
orig = subject[{tags: [{key: 'k1', value: 'v1'}] }]
|
93
|
+
expected = {tags: [{key: 'k1', value: 'v1'}, {key: 'k2', value: 'v2'}] }
|
94
|
+
|
95
|
+
orig.add_tag('k2' => 'v2').must_equal expected
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe '#with_expanded_template_body' do
|
101
|
+
it 'does not expand invalid templates' do
|
102
|
+
hash = { template_body: 'spec/data/invalid.yml' }
|
103
|
+
subject[hash].with_expanded_template_body.must_equal hash
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'expands valid template paths' do
|
107
|
+
template_path = 'spec/data/sqs.template'
|
108
|
+
result = subject[template_body: template_path].with_expanded_template_body
|
109
|
+
|
110
|
+
result.must_equal({template_body: CfnFlow::Template.new(template_path).to_json})
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
data/spec/cfn_flow_spec.rb
CHANGED
@@ -50,22 +50,9 @@ describe 'CfnFlow' do
|
|
50
50
|
error.message.must_match 'No stack defined'
|
51
51
|
end
|
52
52
|
|
53
|
-
it('
|
54
|
-
|
55
|
-
subject.
|
56
|
-
subject.stack_params('env')[:parameters].must_equal [ { parameter_key: 'ami', parameter_value: 'ami-12345' } ]
|
57
|
-
end
|
58
|
-
|
59
|
-
it('expands tags') do
|
60
|
-
stack = {'tags' => {'Deployer' => 'Aaron' } }
|
61
|
-
subject.instance_variable_set(:@config, {'service' => 'myservice', 'stack' => stack})
|
62
|
-
expected = [
|
63
|
-
{ key: 'Deployer', value: 'Aaron' },
|
64
|
-
{ key: 'CfnFlowService', value: 'myservice' },
|
65
|
-
{ key: 'CfnFlowEnvironment', value: 'env' }
|
66
|
-
]
|
67
|
-
|
68
|
-
subject.stack_params('env')[:tags].must_equal expected
|
53
|
+
it('returns a StackParams hash') do
|
54
|
+
subject.instance_variable_set(:@config, {'service' => 'myservice', 'stack' => {}})
|
55
|
+
subject.stack_params('env').must_be_kind_of CfnFlow::StackParams
|
69
56
|
end
|
70
57
|
|
71
58
|
it 'appends CfnFlow tags' do
|
@@ -77,14 +64,6 @@ describe 'CfnFlow' do
|
|
77
64
|
|
78
65
|
subject.stack_params('env')[:tags].must_equal expected
|
79
66
|
end
|
80
|
-
|
81
|
-
it 'expands template body' do
|
82
|
-
template_path = 'spec/data/sqs.template'
|
83
|
-
stack = {'template_body' => template_path}
|
84
|
-
subject.instance_variable_set(:@config, {'service' => 'myservice', 'stack' => stack})
|
85
|
-
subject.stack_params('env')[:template_body].must_equal CfnFlow::Template.new(template_path).to_json
|
86
|
-
end
|
87
|
-
|
88
67
|
end
|
89
68
|
|
90
69
|
describe '.template_s3_bucket' do
|
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.
|
4
|
+
version: 0.9.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-
|
11
|
+
date: 2015-10-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk
|
@@ -80,7 +80,7 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
-
description:
|
83
|
+
description: A practical worflow for AWS CloudFormation
|
84
84
|
email: aaron@ktheory.com
|
85
85
|
executables:
|
86
86
|
- cfn-flow
|
@@ -93,13 +93,17 @@ files:
|
|
93
93
|
- bin/rake
|
94
94
|
- lib/cfn-flow.rb
|
95
95
|
- lib/cfn_flow.rb
|
96
|
+
- lib/cfn_flow/cached_stack.rb
|
96
97
|
- lib/cfn_flow/cli.rb
|
97
98
|
- lib/cfn_flow/event_presenter.rb
|
98
99
|
- lib/cfn_flow/git.rb
|
100
|
+
- lib/cfn_flow/stack_params.rb
|
99
101
|
- lib/cfn_flow/template.rb
|
100
102
|
- lib/cfn_flow/version.rb
|
103
|
+
- spec/cfn_flow/cached_stack_spec.rb
|
101
104
|
- spec/cfn_flow/cli_spec.rb
|
102
105
|
- spec/cfn_flow/event_presenter_spec.rb
|
106
|
+
- spec/cfn_flow/stack_params_spec.rb
|
103
107
|
- spec/cfn_flow/template_spec.rb
|
104
108
|
- spec/cfn_flow_spec.rb
|
105
109
|
- spec/data/cfn-flow.yml
|
@@ -129,13 +133,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
129
133
|
version: '0'
|
130
134
|
requirements: []
|
131
135
|
rubyforge_project:
|
132
|
-
rubygems_version: 2.4.5
|
136
|
+
rubygems_version: 2.4.5.1
|
133
137
|
signing_key:
|
134
138
|
specification_version: 4
|
135
139
|
summary: A CLI for CloudFormation templates
|
136
140
|
test_files:
|
141
|
+
- spec/cfn_flow/cached_stack_spec.rb
|
137
142
|
- spec/cfn_flow/cli_spec.rb
|
138
143
|
- spec/cfn_flow/event_presenter_spec.rb
|
144
|
+
- spec/cfn_flow/stack_params_spec.rb
|
139
145
|
- spec/cfn_flow/template_spec.rb
|
140
146
|
- spec/cfn_flow_spec.rb
|
141
147
|
- spec/data/cfn-flow.yml
|