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