cloudformula 1.1.2
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 +7 -0
- data/.gitignore +18 -0
- data/.rspec +1 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +206 -0
- data/Rakefile +1 -0
- data/bin/cloudformula +12 -0
- data/cloudformula.gemspec +26 -0
- data/integration_tests/.ruby-gemset +1 -0
- data/integration_tests/.ruby-version +1 -0
- data/integration_tests/Gemfile +3 -0
- data/integration_tests/README.md +33 -0
- data/integration_tests/fixtures/minimal_cf_template.json +30 -0
- data/integration_tests/fixtures/minimal_cf_template_update.json +54 -0
- data/integration_tests/stack_create_update.rb +26 -0
- data/lib/cloudformula/cli.rb +113 -0
- data/lib/cloudformula/cloud_formation.rb +54 -0
- data/lib/cloudformula/help/create.txt +12 -0
- data/lib/cloudformula/help/generate.txt +10 -0
- data/lib/cloudformula/help/top.txt +20 -0
- data/lib/cloudformula/help/update.txt +12 -0
- data/lib/cloudformula/json_erb.rb +42 -0
- data/lib/cloudformula/string.rb +11 -0
- data/lib/cloudformula/template.rb +99 -0
- data/lib/cloudformula/validator.rb +172 -0
- data/lib/cloudformula/version.rb +3 -0
- data/lib/cloudformula.rb +23 -0
- data/spec/cloud_formula_spec.rb +18 -0
- data/spec/cloudformula/cloud_formation_spec.rb +55 -0
- data/spec/cloudformula/template_spec.rb +303 -0
- data/spec/fixtures/_partial.json.erb +1 -0
- data/spec/fixtures/with_custom_erb_validations.erb +4 -0
- data/spec/fixtures/with_erb_parameters.erb +3 -0
- data/spec/fixtures/with_erb_parameters.json.erb +4 -0
- data/spec/fixtures/with_erb_parameters_and_stack_options.json.erb +5 -0
- data/spec/fixtures/with_erb_parameters_answer.json +4 -0
- data/spec/fixtures/with_erb_parameters_answer.txt +3 -0
- data/spec/fixtures/with_erb_parameters_escaped_answer.json +4 -0
- data/spec/fixtures/with_erb_validations.json.erb +53 -0
- data/spec/fixtures/with_partial.json.erb +5 -0
- data/spec/fixtures/with_partial_answer.json +5 -0
- data/spec/fixtures/with_raw.json.erb +3 -0
- data/spec/fixtures/with_raw_answer.json +3 -0
- data/spec/fixtures/with_stack_options.json.erb +4 -0
- data/spec/fixtures/without_erb_parameters.erb +1 -0
- data/spec/fixtures/without_erb_parameters.json.erb +3 -0
- data/spec/spec_helper.rb +11 -0
- metadata +185 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 24bc82972e9d49a352e41baa51b21706cd6542d5
|
4
|
+
data.tar.gz: dbb0c9f3404d9c5509ca5cb7d3ecc7b06a494336
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8cd142edaa41c09a9edbb576dba107de8d27e94c23ccec3afe6783437a42d876abb958544d57051d040b5f8902ee82a7ed1012fa4c9d6422247694beaf7f1602
|
7
|
+
data.tar.gz: b9093780e5397256d3481725703315e4ebcb21343ea98803575abecacb742a9f47b6ca813296ccee8cc8fe2d23bd9aabd3e7e3b5ed65963bd3ac217c55835df5
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
cloudformula
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.0.0-p195
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Kabam
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,206 @@
|
|
1
|
+
# CloudFormula
|
2
|
+
|
3
|
+
CloudFormula is a tool which generates and uploads CloudFormation templates from ERB templates.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'cloudformula'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install cloudformula
|
18
|
+
|
19
|
+
## API
|
20
|
+
|
21
|
+
[API Documentation](http://rubydoc.info/github/Kabam/cloudformula/master/frames)
|
22
|
+
|
23
|
+
## CLI
|
24
|
+
|
25
|
+
This gem includes a command-line interface which can generate templates, create and update CloudFormation stacks.
|
26
|
+
Type `cloudformula --help` in a console for documentation.
|
27
|
+
|
28
|
+
## Basic usage
|
29
|
+
|
30
|
+
Generate a template:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
template = CloudFormula::Template.new('/path/to/template', { :param1 => 'foo', :param2 => 'bar' })
|
34
|
+
template.generate
|
35
|
+
```
|
36
|
+
|
37
|
+
You can then pass the template to `create_stack`:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
stack = CloudFormula.create_stack('us-east-1', 'stack-name', template)
|
41
|
+
```
|
42
|
+
|
43
|
+
...or to `update_stack`:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
CloudFormula.update_stack('us-east-1', 'stack-name', template)
|
47
|
+
```
|
48
|
+
|
49
|
+
TODO - see api documentation
|
50
|
+
|
51
|
+
## Creating templates
|
52
|
+
|
53
|
+
Templates are Ruby ERB, which means you have the full power of Ruby available.
|
54
|
+
|
55
|
+
### Escaping content
|
56
|
+
|
57
|
+
Normally, variables output in your ERB templates using `<%= %>` will be automatically JSON-escaped. You can change
|
58
|
+
this behavior by using the `raw` helper. For instance:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
<%= raw @foo %>
|
62
|
+
```
|
63
|
+
|
64
|
+
Note this only works for Strings. If `@foo` is an object that will be implicitly converted to a String, it will still
|
65
|
+
be JSON-escaped.
|
66
|
+
|
67
|
+
### Partials
|
68
|
+
|
69
|
+
Basic partial functionality is available. You can include a partial inside a template as follows:
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
<%= render 'path/to/template.json.erb', { :param1 => 'value1', :param2 => 'value2', ... } %>
|
73
|
+
```
|
74
|
+
|
75
|
+
ERB variables are not automatically passed through to partials - you'll have to explicitly pass them.
|
76
|
+
|
77
|
+
### Validation
|
78
|
+
|
79
|
+
Templates are responsible for their own validations. There are several pre-defined helpers you can use for common
|
80
|
+
cases. For the sake of familiarity, we've partially reproduced the helpers in ActiveRecord. See the
|
81
|
+
[ActiveRecord Validation Helpers documentation](http://edgeguides.rubyonrails.org/active_record_validations.html#validation-helpers)
|
82
|
+
for information on how to use the helpers.
|
83
|
+
|
84
|
+
Use pre-defined helpers by creating a Hash named `@validations` in your ERB template. For example:
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
<%
|
88
|
+
@validations = {
|
89
|
+
:param1 => { :presence => true },
|
90
|
+
...
|
91
|
+
}
|
92
|
+
%>
|
93
|
+
{
|
94
|
+
"AWSTemplateFormatVersion" : "2010-09-09",
|
95
|
+
...
|
96
|
+
}
|
97
|
+
```
|
98
|
+
|
99
|
+
#### exclusion
|
100
|
+
|
101
|
+
Ensures a value is not in the exclusion list.
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
:exclusion => ['val1']
|
105
|
+
```
|
106
|
+
|
107
|
+
#### inclusion
|
108
|
+
|
109
|
+
Ensures a value is in the inclusion list
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
:inclusion => ['val1', 'val2']
|
113
|
+
```
|
114
|
+
|
115
|
+
#### length
|
116
|
+
|
117
|
+
Ensures value length
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
:length => {
|
121
|
+
:minimum => n,
|
122
|
+
:maximum => n,
|
123
|
+
:in|:within => x..y,
|
124
|
+
:is => n
|
125
|
+
}
|
126
|
+
```
|
127
|
+
|
128
|
+
#### format
|
129
|
+
|
130
|
+
Ensures a value matches the regex
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
:format => /regex/
|
134
|
+
```
|
135
|
+
|
136
|
+
#### numericality
|
137
|
+
|
138
|
+
Ensures numeric value constraints
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
:numericality => {
|
142
|
+
:only_integer => true|false,
|
143
|
+
:equal_to => n,
|
144
|
+
:greater_than => n,
|
145
|
+
:greater_than_or_equal_to => n,
|
146
|
+
:less_than => n,
|
147
|
+
:less_than_or_equal_to => n,
|
148
|
+
:even => true|false,
|
149
|
+
:odd => true|false
|
150
|
+
}
|
151
|
+
```
|
152
|
+
|
153
|
+
#### presence
|
154
|
+
|
155
|
+
Ensures a value exists and has a length greater than 0
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
:presence => true|false
|
159
|
+
```
|
160
|
+
|
161
|
+
#### Custom validations
|
162
|
+
|
163
|
+
You can also write custom validations by raising errors:
|
164
|
+
|
165
|
+
```ruby
|
166
|
+
<%
|
167
|
+
# Validate parameters
|
168
|
+
raise 'Required parameter "@foo" is missing.' if @foo.nil? || @foo.empty?
|
169
|
+
%>
|
170
|
+
```
|
171
|
+
|
172
|
+
### Reserved keywords
|
173
|
+
|
174
|
+
`parameters` and `source` are reserved keywords and cannot be used as ERB or CloudFormation parameter names.
|
175
|
+
|
176
|
+
### CloudFormation stack options
|
177
|
+
|
178
|
+
You can specify default stack options within a template. Refer to
|
179
|
+
[AWS::CloudFormation::StackCollection.create](http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/CloudFormation/StackCollection.html#create-instance_method)
|
180
|
+
at "Options Hash (options)" for a list of options you can set. Note that "parameters" will be overwritten by parameters
|
181
|
+
supplied to the create|update_stack methods.
|
182
|
+
|
183
|
+
Example of setting the default to not rollback the stack in case of failure:
|
184
|
+
```erb
|
185
|
+
<%
|
186
|
+
@stack_options = { :disable_rollback => true }
|
187
|
+
%>
|
188
|
+
{
|
189
|
+
...template...
|
190
|
+
}
|
191
|
+
```
|
192
|
+
|
193
|
+
You can override stack_options if needed by providing a Hash for the override_options parameter when calling
|
194
|
+
CloudFormula::CloudFormation#create_stack or CloudFormula::CloudFormation#update_stack
|
195
|
+
|
196
|
+
### Security concerns
|
197
|
+
|
198
|
+
This gem should not be used with untrusted templates or parameters.
|
199
|
+
|
200
|
+
## Contributing
|
201
|
+
|
202
|
+
1. Fork it
|
203
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
204
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
205
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
206
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/cloudformula
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
$LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
|
5
|
+
|
6
|
+
require 'cloudformula'
|
7
|
+
require 'cloudformula/cli'
|
8
|
+
|
9
|
+
args = ARGV.dup
|
10
|
+
ARGV.clear
|
11
|
+
|
12
|
+
CloudFormula::CLI.run(args)
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'cloudformula/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'cloudformula'
|
8
|
+
spec.version = CloudFormula::VERSION
|
9
|
+
spec.authors = ['Kabam']
|
10
|
+
spec.summary = 'CloudFormula is a tool which generates and uploads CloudFormation templates from ERB templates.'
|
11
|
+
spec.description = spec.summary
|
12
|
+
spec.homepage = 'https://github.com/Kabam/cloudformula'
|
13
|
+
spec.license = 'MIT'
|
14
|
+
|
15
|
+
spec.files = `git ls-files`.split($/)
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
|
20
|
+
spec.add_dependency 'aws-sdk', '~> 1.21.0'
|
21
|
+
spec.add_dependency 'trollop', '~> 2.0'
|
22
|
+
|
23
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
24
|
+
spec.add_development_dependency 'rake'
|
25
|
+
spec.add_development_dependency 'rspec', '~> 2.14'
|
26
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
cloudformula_integration
|
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.0.0-p195
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# CloudFormula Integration Tests
|
2
|
+
|
3
|
+
These are simple scripts that fully test functionality of the gem from a user / developer perspective.
|
4
|
+
|
5
|
+
## Setup
|
6
|
+
|
7
|
+
Provide your AWS security credentials via ENV:
|
8
|
+
|
9
|
+
```shell
|
10
|
+
export AWS_ACCESS_KEY_ID='...'
|
11
|
+
export AWS_SECRET_ACCESS_KEY='...'
|
12
|
+
```
|
13
|
+
|
14
|
+
Install gems:
|
15
|
+
|
16
|
+
```shell
|
17
|
+
cd integration_tests
|
18
|
+
bundle
|
19
|
+
```
|
20
|
+
|
21
|
+
## Running
|
22
|
+
|
23
|
+
Run all tests from the `integration_tests` directory. These tests may be packaged in the future, but for now they can
|
24
|
+
be run individually as follows.
|
25
|
+
|
26
|
+
```shell
|
27
|
+
ruby testfile.rb
|
28
|
+
```
|
29
|
+
|
30
|
+
### stack_create_update.rb
|
31
|
+
|
32
|
+
This test creates, updates, and deletes an AWS CloudFormation stack. If this test fails, you must manually delete
|
33
|
+
the CloudFormation stack "stackalong-cassidy" before attempting to run it again.
|
@@ -0,0 +1,30 @@
|
|
1
|
+
{
|
2
|
+
"AWSTemplateFormatVersion" : "2010-09-09",
|
3
|
+
"Description" : "Minimal stack for testing",
|
4
|
+
|
5
|
+
"Resources" : {
|
6
|
+
"CfnRole" : {
|
7
|
+
"Type" : "AWS::IAM::Role",
|
8
|
+
"Properties" : {
|
9
|
+
"AssumeRolePolicyDocument" : {
|
10
|
+
"Statement" : [ {
|
11
|
+
"Effect" : "Allow",
|
12
|
+
"Principal" : { "Service" : [ "ec2.amazonaws.com" ] },
|
13
|
+
"Action" : [ "sts:AssumeRole" ]
|
14
|
+
} ]
|
15
|
+
},
|
16
|
+
"Path" : "/",
|
17
|
+
"Policies" : [ {
|
18
|
+
"PolicyName" : "root",
|
19
|
+
"PolicyDocument" : {
|
20
|
+
"Statement" : [ {
|
21
|
+
"Effect" : "Allow",
|
22
|
+
"Action" : "cloudformation:DescribeStackResource",
|
23
|
+
"Resource" : "*"
|
24
|
+
} ]
|
25
|
+
}
|
26
|
+
} ]
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
@@ -0,0 +1,54 @@
|
|
1
|
+
{
|
2
|
+
"AWSTemplateFormatVersion" : "2010-09-09",
|
3
|
+
"Description" : "Minimal stack for testing",
|
4
|
+
|
5
|
+
"Resources" : {
|
6
|
+
"CfnRole" : {
|
7
|
+
"Type" : "AWS::IAM::Role",
|
8
|
+
"Properties" : {
|
9
|
+
"AssumeRolePolicyDocument" : {
|
10
|
+
"Statement" : [ {
|
11
|
+
"Effect" : "Allow",
|
12
|
+
"Principal" : { "Service" : [ "ec2.amazonaws.com" ] },
|
13
|
+
"Action" : [ "sts:AssumeRole" ]
|
14
|
+
} ]
|
15
|
+
},
|
16
|
+
"Path" : "/",
|
17
|
+
"Policies" : [ {
|
18
|
+
"PolicyName" : "root",
|
19
|
+
"PolicyDocument" : {
|
20
|
+
"Statement" : [ {
|
21
|
+
"Effect" : "Allow",
|
22
|
+
"Action" : "cloudformation:DescribeStackResource",
|
23
|
+
"Resource" : "*"
|
24
|
+
} ]
|
25
|
+
}
|
26
|
+
} ]
|
27
|
+
}
|
28
|
+
},
|
29
|
+
|
30
|
+
"CfnRole2" : {
|
31
|
+
"Type" : "AWS::IAM::Role",
|
32
|
+
"Properties" : {
|
33
|
+
"AssumeRolePolicyDocument" : {
|
34
|
+
"Statement" : [ {
|
35
|
+
"Effect" : "Allow",
|
36
|
+
"Principal" : { "Service" : [ "ec2.amazonaws.com" ] },
|
37
|
+
"Action" : [ "sts:AssumeRole" ]
|
38
|
+
} ]
|
39
|
+
},
|
40
|
+
"Path" : "/",
|
41
|
+
"Policies" : [ {
|
42
|
+
"PolicyName" : "root",
|
43
|
+
"PolicyDocument" : {
|
44
|
+
"Statement" : [ {
|
45
|
+
"Effect" : "Allow",
|
46
|
+
"Action" : "cloudformation:DescribeStackResource",
|
47
|
+
"Resource" : "*"
|
48
|
+
} ]
|
49
|
+
}
|
50
|
+
} ]
|
51
|
+
}
|
52
|
+
}
|
53
|
+
}
|
54
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'cloudformula'
|
3
|
+
|
4
|
+
template_path = File.expand_path('fixtures/minimal_cf_template.json', File.dirname(__FILE__))
|
5
|
+
template = CloudFormula::Template.new(template_path)
|
6
|
+
|
7
|
+
puts 'Creating...'
|
8
|
+
stack = CloudFormula.create_stack('us-west-2', 'stackalong-cassidy', template)
|
9
|
+
puts stack.inspect
|
10
|
+
|
11
|
+
puts 'Waiting for creation to complete...'
|
12
|
+
sleep(1.0) while stack.status == 'CREATE_IN_PROGRESS'
|
13
|
+
|
14
|
+
template_path = File.expand_path('fixtures/minimal_cf_template_update.json', File.dirname(__FILE__))
|
15
|
+
template = CloudFormula::Template.new(template_path)
|
16
|
+
|
17
|
+
puts 'Updating...'
|
18
|
+
CloudFormula.update_stack('us-west-2', 'stackalong-cassidy', template)
|
19
|
+
|
20
|
+
puts 'Waiting for update to complete...'
|
21
|
+
sleep(1.0) while stack.status == 'UPDATE_IN_PROGRESS' || stack.status == 'UPDATE_COMPLETE_CLEANUP_IN_PROGRESS'
|
22
|
+
|
23
|
+
puts 'Deleting...'
|
24
|
+
stack.delete
|
25
|
+
|
26
|
+
puts 'Done.'
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'trollop'
|
3
|
+
|
4
|
+
module CloudFormula
|
5
|
+
class CLI
|
6
|
+
def self.run(args)
|
7
|
+
parse_opts = parse_options args
|
8
|
+
subcommand = parse_opts[:subcommand]
|
9
|
+
opts = parse_opts[:opts]
|
10
|
+
|
11
|
+
template = CloudFormula.template opts[:template], parse_parameters(opts[:parameters])
|
12
|
+
|
13
|
+
begin
|
14
|
+
case subcommand
|
15
|
+
when 'generate'
|
16
|
+
puts template.generate
|
17
|
+
when 'create'
|
18
|
+
stack = CloudFormula.create_stack opts[:region], opts[:'stack-name'], template
|
19
|
+
puts stack.inspect
|
20
|
+
when 'update'
|
21
|
+
CloudFormula.update_stack opts[:region], opts[:'stack-name'], template
|
22
|
+
end
|
23
|
+
rescue Exception => e
|
24
|
+
puts "ERROR: #{e.message}\n\nStack trace:\n#{e.backtrace.join("\n from ")}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
# Parses and validates command-line arguments. Returns the subcommand and its options.
|
31
|
+
# @param [Array] args Command-line arguments (ARGV)
|
32
|
+
# @return [Hash] { :subcommand => "command", :opts => Hash } @see Trollop::Parser#parse for opts structure
|
33
|
+
def self.parse_options(args)
|
34
|
+
parse_top_level args
|
35
|
+
parse_subcommand args
|
36
|
+
end
|
37
|
+
|
38
|
+
# Top-level parser includes global help and subcommand pass-through
|
39
|
+
# @param [Array] args Command-line arguments (ARGV)
|
40
|
+
# @return [Hash] @see Trollop::Parser#parse
|
41
|
+
def self.parse_top_level(args)
|
42
|
+
Trollop::options args do
|
43
|
+
banner CloudFormula::CLI.banner(:top)
|
44
|
+
stop_on %w(generate create update)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Subcommand parser includes subcommand help and parameter validation
|
49
|
+
# @param [Array] args Command-line arguments (ARGV)
|
50
|
+
# @return [Hash] { :subcommand => "command", :opts => Hash } @see Trollop::Parser#parse for opts structure
|
51
|
+
def self.parse_subcommand(args)
|
52
|
+
cmd = args.shift # get the subcommand
|
53
|
+
Trollop::die "unknown command #{cmd.inspect}" unless ['generate', 'create', 'update'].include?(cmd)
|
54
|
+
cmd_opts = Trollop::options args do
|
55
|
+
banner CloudFormula::CLI.banner(cmd.to_sym)
|
56
|
+
CloudFormula::CLI.opt_definitions(cmd).each { |item| opt *item }
|
57
|
+
end
|
58
|
+
validate_global_opts cmd_opts
|
59
|
+
{ :subcommand => cmd, :opts => cmd_opts }
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns an array defining the command-line options
|
63
|
+
# Each item in the array can be passed directly to `opt` in a parser. Example:
|
64
|
+
# Trollop::options(args) do
|
65
|
+
# opt_definitions(subcommand).each { |item| opt *item }
|
66
|
+
# end
|
67
|
+
# @param [String] subcommand The definitions will be customized appropriately for this value.
|
68
|
+
# @return [Array]
|
69
|
+
def self.opt_definitions(subcommand)
|
70
|
+
defs = [
|
71
|
+
[:parameters, 'Parameters to supply to the template in JSON format. {"var1":"foo","var2":"bar"}', :type => :string],
|
72
|
+
[:template, 'Path to the .json or .json.erb template', :type => :string, :required => true]
|
73
|
+
]
|
74
|
+
case subcommand
|
75
|
+
when 'create', 'update'
|
76
|
+
defs += [
|
77
|
+
[:'stack-name', 'Name of the CloudFormation stack', :type => :string, :required => true],
|
78
|
+
[:region, 'AWS region. us-east-1, us-west-2, etc. Default us-east-1', :type => :string]
|
79
|
+
]
|
80
|
+
end
|
81
|
+
defs
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns the appropriate help banner for the given subcommand
|
85
|
+
# @param [Symbol] subcommand
|
86
|
+
# @return [String]
|
87
|
+
def self.banner(subcommand)
|
88
|
+
case subcommand
|
89
|
+
when :top, :generate, :create, :update
|
90
|
+
file = File.open(File.expand_path("#{subcommand}.txt", "#{File.dirname(__FILE__)}/help"), 'rb')
|
91
|
+
file.read
|
92
|
+
else
|
93
|
+
''
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Validates options required for all subcommands.
|
98
|
+
# Returns the parsed --parameters value as either a Hash or an Array
|
99
|
+
# @param [Hash] opts
|
100
|
+
# @return [Array|Hash] @see JSON.parse
|
101
|
+
def self.validate_global_opts(opts)
|
102
|
+
Trollop.die :template, "file #{opts[:template]} not found or cannot be read" unless File.file?(opts[:template]) && File.readable?(opts[:template])
|
103
|
+
parse_parameters opts[:parameters]
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.parse_parameters(parameters)
|
107
|
+
return {} if parameters.nil? || parameters.empty?
|
108
|
+
parsed = JSON.parse parameters, :symbolize_names => true rescue Trollop.die :parameters, 'must be valid JSON'
|
109
|
+
Trollop.die :parameters, 'must be a JSON hash' unless parsed.class == Hash
|
110
|
+
parsed
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
|
3
|
+
module CloudFormula
|
4
|
+
class CloudFormation
|
5
|
+
class << self
|
6
|
+
# Convenience method which creates a new CloudFormation stack.
|
7
|
+
#
|
8
|
+
# @param [String] region A valid AWS region, e.g. "us-east-1", "us-west-2", etc
|
9
|
+
# @param [String] stack_name
|
10
|
+
# @param [CloudFormula::Template] template
|
11
|
+
# @param [Hash] override_options Optional. Stack options to override. See README.md "CloudFormation stack options" for details
|
12
|
+
# @param [Hash] parameters Optional. Parameters to supply to the template.
|
13
|
+
# @param [String] aws_access_key_id Optional. If not supplied, AWS_ACCESS_KEY_ID must exist in ENV
|
14
|
+
# @param [String] aws_secret_key Optional. If not supplied, AWS_SECRET_ACCESS_KEY must exist in ENV
|
15
|
+
# @return [AWS::CloudFormation::Stack]
|
16
|
+
def create_stack(region, stack_name, template, override_options = {}, parameters = {}, aws_access_key_id = nil, aws_secret_key = nil)
|
17
|
+
validate template
|
18
|
+
cfm = get_cloud_formation region, aws_access_key_id, aws_secret_key
|
19
|
+
cfm.stacks.create(stack_name, template.generate, template.aws_options(override_options, parameters))
|
20
|
+
end
|
21
|
+
|
22
|
+
# Convenience method which updates an existing CloudFormation stack.
|
23
|
+
#
|
24
|
+
# @param [String] region A valid AWS region, e.g. "us-east-1", "us-west-2", etc
|
25
|
+
# @param [String] stack_name
|
26
|
+
# @param [CloudFormula::Template] template
|
27
|
+
# @param [Hash] parameters Optional. Parameters to supply to the template.
|
28
|
+
# @param [String] aws_access_key_id Optional. If not supplied, AWS_ACCESS_KEY_ID must exist in ENV
|
29
|
+
# @param [String] aws_secret_key Optional. If not supplied, AWS_SECRET_ACCESS_KEY must exist in ENV
|
30
|
+
# @return nil
|
31
|
+
def update_stack(region, stack_name, template, parameters = {}, aws_access_key_id = nil, aws_secret_key = nil)
|
32
|
+
validate template
|
33
|
+
cfm = get_cloud_formation region, aws_access_key_id, aws_secret_key
|
34
|
+
stack = cfm.stacks[stack_name]
|
35
|
+
stack.update({ :template => template.generate, :parameters => parameters })
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# @param [CloudFormula::Template] template
|
41
|
+
def validate(template)
|
42
|
+
raise ArgumentError.new("Invalid template: #{template.inspect}") if !template.is_a?(CloudFormula::Template)
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_cloud_formation(region, aws_access_key_id = nil, aws_secret_key = nil)
|
46
|
+
options = { :region => region }
|
47
|
+
options[:access_key_id] = aws_access_key_id unless aws_access_key_id.nil?
|
48
|
+
options[:secret_access_key] = aws_secret_key unless aws_secret_key.nil?
|
49
|
+
AWS::CloudFormation.new options
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
@@ -0,0 +1,12 @@
|
|
1
|
+
NAME
|
2
|
+
cloudformula create
|
3
|
+
|
4
|
+
DESCRIPTION
|
5
|
+
Dynamically generates an AWS CloudFormation template and uses it to create a
|
6
|
+
new CloudFormation stack.
|
7
|
+
|
8
|
+
USAGE
|
9
|
+
cloudformula create --stack-name '<stackname>' --template '<template>' \\
|
10
|
+
--parameters '<params>' [--region '<region>']
|
11
|
+
|
12
|
+
OPTIONS
|