cloudformula 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +1 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +206 -0
  9. data/Rakefile +1 -0
  10. data/bin/cloudformula +12 -0
  11. data/cloudformula.gemspec +26 -0
  12. data/integration_tests/.ruby-gemset +1 -0
  13. data/integration_tests/.ruby-version +1 -0
  14. data/integration_tests/Gemfile +3 -0
  15. data/integration_tests/README.md +33 -0
  16. data/integration_tests/fixtures/minimal_cf_template.json +30 -0
  17. data/integration_tests/fixtures/minimal_cf_template_update.json +54 -0
  18. data/integration_tests/stack_create_update.rb +26 -0
  19. data/lib/cloudformula/cli.rb +113 -0
  20. data/lib/cloudformula/cloud_formation.rb +54 -0
  21. data/lib/cloudformula/help/create.txt +12 -0
  22. data/lib/cloudformula/help/generate.txt +10 -0
  23. data/lib/cloudformula/help/top.txt +20 -0
  24. data/lib/cloudformula/help/update.txt +12 -0
  25. data/lib/cloudformula/json_erb.rb +42 -0
  26. data/lib/cloudformula/string.rb +11 -0
  27. data/lib/cloudformula/template.rb +99 -0
  28. data/lib/cloudformula/validator.rb +172 -0
  29. data/lib/cloudformula/version.rb +3 -0
  30. data/lib/cloudformula.rb +23 -0
  31. data/spec/cloud_formula_spec.rb +18 -0
  32. data/spec/cloudformula/cloud_formation_spec.rb +55 -0
  33. data/spec/cloudformula/template_spec.rb +303 -0
  34. data/spec/fixtures/_partial.json.erb +1 -0
  35. data/spec/fixtures/with_custom_erb_validations.erb +4 -0
  36. data/spec/fixtures/with_erb_parameters.erb +3 -0
  37. data/spec/fixtures/with_erb_parameters.json.erb +4 -0
  38. data/spec/fixtures/with_erb_parameters_and_stack_options.json.erb +5 -0
  39. data/spec/fixtures/with_erb_parameters_answer.json +4 -0
  40. data/spec/fixtures/with_erb_parameters_answer.txt +3 -0
  41. data/spec/fixtures/with_erb_parameters_escaped_answer.json +4 -0
  42. data/spec/fixtures/with_erb_validations.json.erb +53 -0
  43. data/spec/fixtures/with_partial.json.erb +5 -0
  44. data/spec/fixtures/with_partial_answer.json +5 -0
  45. data/spec/fixtures/with_raw.json.erb +3 -0
  46. data/spec/fixtures/with_raw_answer.json +3 -0
  47. data/spec/fixtures/with_stack_options.json.erb +4 -0
  48. data/spec/fixtures/without_erb_parameters.erb +1 -0
  49. data/spec/fixtures/without_erb_parameters.json.erb +3 -0
  50. data/spec/spec_helper.rb +11 -0
  51. 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
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .idea/
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
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
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cloudformula.gemspec
4
+ gemspec
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,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'cloudformula', :path => '../'
@@ -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
@@ -0,0 +1,10 @@
1
+ NAME
2
+ cloudformula generate
3
+
4
+ DESCRIPTION
5
+ Dynamically generates an AWS CloudFormation template and outputs the result.
6
+
7
+ USAGE
8
+ cloudformula generate --template '<template>' --parameters '<params>'
9
+
10
+ OPTIONS