cloudformation-ruby-dsl-addedvars 1.2.4

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0e7c2c0a0a70a6a7fd805e721fdb1b1307486de2
4
+ data.tar.gz: b59977b05cd853d59c3377f610303f3e5c912730
5
+ SHA512:
6
+ metadata.gz: 4c0294a8955b1e53859009b65c78242c611ddd5b85bd277c51ecf9521024e24a17c01be0e7fc8965488550abfe815dc8d433ed5735a2a64e5814ba6377447033
7
+ data.tar.gz: 48e71fc4893c51545d744c425e5cb8032654f5337429a303b95dc841ef51cdcce6b9c982b56a47ac402d9fd5dced588a79da8d5c68949387a562b0fb29e285a7
@@ -0,0 +1,29 @@
1
+ *.swp
2
+ maestro.json
3
+ .rakeTasks
4
+ .DS_Store
5
+ target
6
+ *.iml
7
+ .idea/
8
+ *.swo
9
+ /manifests
10
+ /modules
11
+ cloudformation/expanded/
12
+ *.gem
13
+ *.rbc
14
+ .bundle
15
+ .config
16
+ .yardoc
17
+ Gemfile.lock
18
+ InstalledFiles
19
+ _yardoc
20
+ coverage
21
+ doc/
22
+ lib/bundler/man
23
+ pkg
24
+ rdoc
25
+ spec/reports
26
+ test/tmp
27
+ test/version_tmp
28
+ tmp
29
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in cloudformation-ruby-dsl-addedvars.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2015 Bazaarvoice, Inc.
2
+  
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+  
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+  
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/OWNERS ADDED
@@ -0,0 +1,4 @@
1
+ # Owners.
2
+
3
+ jonaf
4
+ temujin9
@@ -0,0 +1,125 @@
1
+ # cloudformation-ruby-dsl-addedvars
2
+
3
+ __TEMPORARY FORK__
4
+
5
+ This gem contains changes not yet suitable for the upstream project. This fork will be retired once a proper implementation of the changes is merged upstream. Discussion likely to continue [here](https://github.com/bazaarvoice/cloudformation-ruby-dsl/issues/77) and/or [here](https://github.com/bazaarvoice/cloudformation-ruby-dsl/pull/87).
6
+
7
+ __END TEMPORARY FORK NOTICE__
8
+
9
+
10
+ A Ruby DSL and helper utilities for building CloudFormation templates dynamically.
11
+
12
+ Written by [Bazaarvoice](http://www.bazaarvoice.com): see [the contributors page](https://github.com/bazaarvoice/cloudformation-ruby-dsl/graphs/contributors) and [the initial contributions](https://github.com/bazaarvoice/cloudformation-ruby-dsl/blob/master/initial_contributions.md) for more details.
13
+
14
+ ## Motivation
15
+
16
+ CloudFormation templates often contain repeated stanzas, information which must be loaded from external sources, and other functionality that would be easier handled as code, instead of configuration.
17
+
18
+ Consider when a userdata script needs to be added to a CloudFormation template. Traditionally, you would re-write the script by hand in a valid JSON format. Using the DSL, you can specify the file containing the script and generate the correct information at runtime.
19
+
20
+ :UserData => base64(interpolate(file('userdata.sh')))
21
+
22
+ Additionally, CloudFormation templates are just massive JSON documents, making general readability and reusability an issue. The DSL allows not only a cleaner format (and comments!), but will also allow the same DSL template to be reused as needed.
23
+
24
+ ## Installation
25
+
26
+ Run `gem install cloudformation-ruby-dsl-addedvars` to install system-wide.
27
+
28
+ To use in a specific project, add `gem 'cloudformation-ruby-dsl-addedvars'` to your Gemfile, and then run `bundle`.
29
+
30
+ ## Releasing
31
+
32
+ See [Releasing](docs/Releasing.md).
33
+
34
+ ## Contributing
35
+
36
+ See [Contributing](docs/Contributing.md).
37
+
38
+ ## Usage
39
+
40
+ To convert existing JSON templates to use the DSL, run
41
+
42
+ cfntemplate-to-ruby [EXISTING_CFN] > [NEW_NAME.rb]
43
+
44
+ You may need to preface this with `bundle exec` if you installed via Bundler.
45
+
46
+ Make the resulting file executable (`chmod +x [NEW_NAME.rb]`). It can respond to the following subcommands (which are listed if you run without parameters):
47
+ - `expand`: output the JSON template to the command line (takes optional `--nopretty` to minimize the output)
48
+ - `diff`: compare an existing stack with your template. Produces following exit codes:
49
+ ```
50
+ 0 - no differences, nothing to update
51
+ 1 - stack does not exist, template Validation error
52
+ 2 - there are differences between an existing stack and your template
53
+ ```
54
+ - `validate`: run validation against the stack definition
55
+ - `create`: create a new stack from the output
56
+ - `update`: update an existing stack from the output. Produces following exit codes:
57
+ ```
58
+ 0 - update finished successfully
59
+ 1 - no updates to perform, stack doesn't exist, unable to update immutable parameter or tag, AWS ServiceError exception
60
+ ```
61
+ - `cancel-update`: cancel updating a stack
62
+ - `delete`: delete a stack (with prompt)
63
+ - `describe`: get output of an existing stack and output it (takes optional `--nopretty` to minimize output)
64
+ - `describe-resource`: given two arguments: stack-name and logical-resource-id, get output from a stack concerning the specific resource (takes optional `--nopretty` to minimize output)
65
+ - `get-template`: get entire template output of an existing stack
66
+
67
+ Below are the various functions currently available in the DSL. See [the example script](examples/cloudformation-ruby-script.rb) for more usage information.
68
+
69
+ ### DSL Statements
70
+
71
+ Add the named object to the appropriate collection.
72
+ - `parameter(name, options)` (may be marked :Immutable, which will raise error on a later change)
73
+ - The special option key :UsePreviousValue can be set to preserve the parameter's existing value during stack updates (e.g. database passwords that won't be saved in revision control).
74
+ - `mapping(name, options)`
75
+ - `condition(name, conditions)`
76
+ - `resource(name, options)`
77
+ - `output(name, options)`
78
+
79
+ ### CloudFormation Function Calls
80
+
81
+ Invoke an intrinsic CloudFormation function.
82
+ - `base64(value)`
83
+ - `find_in_map(map, key, name)`
84
+ - `get_att(resource, attribute)`
85
+ - `get_azs(region)`
86
+ - `join(delim, *list)`
87
+ - `select(index, list)`
88
+ - `ref(name)`
89
+
90
+ Intrinsic conditionals are also supported, with some syntactic sugar.
91
+ - `fn_not(condition)`
92
+ - `fn_or(*condition_list)`
93
+ - `fn_and(*condition_list)`
94
+ - `fn_if(condition, value_if_true, value_if_false)`
95
+ - `equal(lhsOperand, rhsOperand)`
96
+ - `not_equal(lhsOperand, rhsOperand)`
97
+
98
+ Reference a CloudFormation pseudo parameter.
99
+ - `aws_account_id()`
100
+ - `aws_notification_arns()`
101
+ - `aws_no_value()`
102
+ - `aws_region()`
103
+ - `aws_stack_id()`
104
+ - `aws_stack_name()`
105
+
106
+ ### Utility Functions
107
+
108
+ Additional capabilities for file inclusion, etc.
109
+ - `tag(tag_name, tag_options_hash)`: add tags to the stack, which are inherited by all resources in that stack. `tag_options_hash` includes `:Value=>value` and `:Immutable=>true` properties. `tag(tag_value_hash)` is deprecated and will be removed in a future version.
110
+ - `file(name)`: return the named file as a string, for further use
111
+ - `load_from_file(filename)`: load the named file by a given type; currently handles YAML, JSON, and Ruby
112
+ - `interpolate(string)`: embed CFN references into a string (`{{ref('Service')}}`) for later interpretation by the CFN engine
113
+ - `Table.load(filename)`: load a table from the listed file, which can then be turned into mappings (via `get_map`)
114
+
115
+ ### Default Region
116
+
117
+ The tool defaults to region `us-east-1`. To change this set either `EC2_REGION` or `AWS_DEFAULT_REGION` in your environment.
118
+
119
+ ### Storing Command Line Options
120
+
121
+ The following (optional) variables can be set in your CloudFormation .rb file (before the template) to avoid needing to set them on the command-line:
122
+ - `$aws_profile`: A string setting the `~/.aws/credentials` profile to use
123
+ - `$aws_region`: A string setting the AWS region
124
+ - `$stack_name`: A string containing the stack name
125
+ - `$stack_params`: A hash containing parameter values, e.g. `{'EnvironmentName' => 'prod'}`
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ # Ruby shell to run bash patch file
3
+ system( File.join( File.dirname(__FILE__) + "/../share/aws-sdk-patch.sh " + ARGV.join( ) ) )
@@ -0,0 +1,345 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright 2013-2014 Bazaarvoice, Inc.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ unless RUBY_VERSION >= '1.9'
18
+ # Ruby 1.9 preserves order within Hash objects which avoid scrambling the template output.
19
+ $stderr.puts "This script requires ruby 1.9+. On OS/X use Homebrew to install ruby 1.9:"
20
+ $stderr.puts " brew install ruby"
21
+ exit(2)
22
+ end
23
+
24
+ require 'json'
25
+
26
+ def main(argv)
27
+ unless (argv & %w(-h --help -?)).empty?
28
+ $stderr.puts <<"EOF"
29
+ usage: #{$PROGRAM_NAME} [cloudformation-template.json] ...
30
+
31
+ Converts the specified CloudFormation JSON template or template fragment to
32
+ Ruby DSL syntax. Reads from stdin or from the specified json files. Note
33
+ that the input must be valid JSON.
34
+
35
+ Examples:
36
+
37
+ # Convert a JSON CloudFormation template to Ruby DSL syntax
38
+ #{$PROGRAM_NAME} my-template.json > my-template.rb
39
+ chmod +x my-template.rb
40
+
41
+ # Convert the JSON fragment in the clipboard to Ruby DSL syntax
42
+ pbpaste | #{$PROGRAM_NAME} | less
43
+
44
+ EOF
45
+ exit(2)
46
+ end
47
+ if argv.empty?
48
+ pprint(simplify(JSON.parse($stdin.read)))
49
+ else
50
+ argv.each do |filename|
51
+ pprint(simplify(JSON.parse(File.read(filename))))
52
+ end
53
+ end
54
+ # The user should make the resulting template executable w/chmod +x
55
+ end
56
+
57
+ class FnCall
58
+ attr_reader :name, :arguments, :multiline
59
+
60
+ def initialize(name, arguments, multiline = false)
61
+ @name = name
62
+ @arguments = arguments
63
+ @multiline = multiline
64
+ end
65
+
66
+ def to_s()
67
+ @name + "(" + @arguments.join(', ') + ")"
68
+ end
69
+ end
70
+
71
+ def pprint(val)
72
+ case detect_type(val)
73
+ when :template
74
+ pprint_cfn_template(val)
75
+ when :parameter
76
+ pprint_cfn_section 'parameter', 'TODO', val
77
+ when :resource
78
+ pprint_cfn_resource 'TODO', val
79
+ when :parameters
80
+ val.each { |k, v| pprint_cfn_section 'parameter', k, v }
81
+ when :resources
82
+ val.each { |k, v| pprint_cfn_resource k, v }
83
+ else
84
+ pprint_value(val, '')
85
+ end
86
+ end
87
+
88
+ # Attempt to figure out what fragment of the template we have. This is imprecise and can't
89
+ # detect Mappings and Outputs sections reliably, so it doesn't attempt to.
90
+ def detect_type(val)
91
+ if val.is_a?(Hash) && val['AWSTemplateFormatVersion']
92
+ :template
93
+ elsif val.is_a?(Hash) && /^(String|Number)$/ =~ val['Type']
94
+ :parameter
95
+ elsif val.is_a?(Hash) && val['Type']
96
+ :resource
97
+ elsif val.is_a?(Hash) && val.values.all? { |v| detect_type(v) == :parameter }
98
+ :parameters
99
+ elsif val.is_a?(Hash) && val.values.all? { |v| detect_type(v) == :resource }
100
+ :resources
101
+ end
102
+ end
103
+
104
+ def pprint_cfn_template(tpl)
105
+ puts "#!/usr/bin/env ruby"
106
+ puts
107
+ puts "require 'bundler/setup'"
108
+ puts "require 'cloudformation-ruby-dsl-addedvars/cfntemplate'"
109
+ puts "require 'cloudformation-ruby-dsl-addedvars/spotprice'"
110
+ puts "require 'cloudformation-ruby-dsl-addedvars/table'"
111
+ puts
112
+ puts "template do"
113
+ puts
114
+ tpl.each do |section, v|
115
+ case section
116
+ when 'Parameters'
117
+ v.each { |name, options| pprint_cfn_section 'parameter', name, options }
118
+ when 'Mappings'
119
+ v.each { |name, options| pprint_cfn_section 'mapping', name, options }
120
+ when 'Resources'
121
+ v.each { |name, options| pprint_cfn_resource name, options }
122
+ when 'Conditions'
123
+ v.each { |name, options| pprint_cfn_section 'condition', name, options }
124
+ when 'Outputs'
125
+ v.each { |name, options| pprint_cfn_section 'output', name, options }
126
+ else
127
+ print " value #{fmt_key(section)} => "
128
+ pprint_value v, ' '
129
+ puts
130
+ puts
131
+ end
132
+ end
133
+ puts "end.exec!"
134
+ end
135
+
136
+ def pprint_cfn_section(section, name, options)
137
+ print " #{section} #{fmt_string(name)}"
138
+ indent = ' ' + (' ' * section.length) + ' '
139
+ options.each do |k, v|
140
+ puts ","
141
+ print indent, fmt_key(k), " => "
142
+ pprint_value v, indent
143
+ end
144
+ puts
145
+ puts
146
+ end
147
+
148
+ def pprint_cfn_resource(name, options)
149
+ print " resource #{fmt_string(name)}"
150
+ indent = ' '
151
+ options.each do |k, v|
152
+ unless k == 'Properties'
153
+ print ", #{fmt_key(k)} => #{fmt(v)}"
154
+ end
155
+ end
156
+ if options.key?('Properties')
157
+ print ", #{fmt_key('Properties')} => "
158
+ pprint_value options['Properties'], indent
159
+ end
160
+ puts
161
+ puts
162
+ end
163
+
164
+ def pprint_value(val, indent)
165
+ # Prefer to print the value on a single line if it's reasonable to do so
166
+ single_line = is_single_line(val) || is_single_line_hack(val)
167
+ if single_line && !is_multi_line_hack(val)
168
+ s = fmt(val)
169
+ if s.length < 120 || is_single_line_hack(val)
170
+ print s
171
+ return
172
+ end
173
+ end
174
+
175
+ # Print the value across multiple lines
176
+ if val.is_a?(Hash)
177
+ puts "{"
178
+ val.each do |k, v|
179
+ print "#{indent} #{fmt_key(k)} => "
180
+ pprint_value v, indent + ' '
181
+ puts ","
182
+ end
183
+ print "#{indent}}"
184
+
185
+ elsif val.is_a?(Array)
186
+ puts "["
187
+ val.each do |v|
188
+ print "#{indent} "
189
+ pprint_value v, indent + ' '
190
+ puts ","
191
+ end
192
+ print "#{indent}]"
193
+
194
+ elsif val.is_a?(FnCall) && val.multiline
195
+ print val.name, "("
196
+ args = val.arguments
197
+ sep = ''
198
+ sub_indent = indent + ' '
199
+ if val.name == 'join' && args.length > 1
200
+ pprint_value args[0], indent + ' '
201
+ args = args[1..-1]
202
+ sep = ','
203
+ sub_indent = indent + ' '
204
+ end
205
+ unless args.empty?
206
+ args.each do |v|
207
+ puts sep
208
+ print sub_indent
209
+ pprint_value v, sub_indent
210
+ sep = ','
211
+ end
212
+ if val.name == 'join' && args.length > 1
213
+ print ","
214
+ end
215
+ puts
216
+ print indent
217
+ end
218
+ print ")"
219
+
220
+ else
221
+ print fmt(val)
222
+ end
223
+ end
224
+
225
+ def is_single_line(val)
226
+ if val.is_a?(Hash)
227
+ is_single_line(val.values)
228
+ elsif val.is_a?(Array)
229
+ val.empty? ||
230
+ (val.length == 1 && is_single_line(val[0]) && !val[0].is_a?(Hash)) ||
231
+ val.all? { |v| v.is_a?(String) }
232
+ else
233
+ true
234
+ end
235
+ end
236
+
237
+ # Emo-specific hacks to force the desired output formatting
238
+ def is_single_line_hack(val)
239
+ is_array_of_strings_hack(val)
240
+ end
241
+
242
+ # Emo-specific hacks to force the desired output formatting
243
+ def is_multi_line_hack(val)
244
+ val.is_a?(Hash) && val['email']
245
+ end
246
+
247
+ # Emo-specific hacks to force the desired output formatting
248
+ def is_array_of_strings_hack(val)
249
+ val.is_a?(Array) && val.all? { |v| v.is_a?(String) } && val.grep(/\s/).empty? && (
250
+ val.include?('autoscaling:EC2_INSTANCE_LAUNCH') ||
251
+ val.include?('m1.small')
252
+ )
253
+ end
254
+
255
+ def fmt(val)
256
+ if val == {}
257
+ '{}'
258
+ elsif val == []
259
+ '[]'
260
+ elsif val.is_a?(Hash)
261
+ '{ ' + (val.map { |k,v| fmt_key(k) + ' => ' + fmt(v) }).join(', ') + ' }'
262
+ elsif val.is_a?(Array) && is_array_of_strings_hack(val)
263
+ '%w(' + val.join(' ') + ')'
264
+ elsif val.is_a?(Array)
265
+ '[ ' + (val.map { |v| fmt(v) }).join(', ') + ' ]'
266
+ elsif val.is_a?(FnCall) && val.arguments.empty?
267
+ val.name
268
+ elsif val.is_a?(FnCall)
269
+ val.name + '(' + (val.arguments.map { |v| fmt(v) }).join(', ') + ')'
270
+ elsif val.is_a?(String)
271
+ fmt_string(val)
272
+ elsif val == nil
273
+ 'null'
274
+ else
275
+ val.to_s # number, boolean
276
+ end
277
+ end
278
+
279
+ def fmt_key(s)
280
+ ':' + (/^[a-zA-Z_]\w+$/ =~ s ? s : fmt_string(s)) # returns a symbol like :Foo or :'us-east-1'
281
+ end
282
+
283
+ def fmt_string(s)
284
+ if /[^ -~]/ =~ s
285
+ s.dump # contains, non-ascii or control char, return double-quoted string
286
+ else
287
+ '\'' + s.gsub(/([\\'])/, '\\\\\1') + '\'' # return single-quoted string, escape \ and '
288
+ end
289
+ end
290
+
291
+ def many(val)
292
+ if val.is_a?(Array)
293
+ val
294
+ else
295
+ [ val ]
296
+ end
297
+ end
298
+
299
+ def simplify(val)
300
+ if val.is_a?(Hash)
301
+ val = Hash[val.map { |k,v| [k, simplify(v)] }]
302
+ if val.length != 1
303
+ val
304
+ else
305
+ k, v = val.entries[0]
306
+ # CloudFormation functions
307
+ case
308
+ when k == 'Fn::Base64'
309
+ FnCall.new 'base64', [v], true
310
+ when k == 'Fn::FindInMap'
311
+ FnCall.new 'find_in_map', v
312
+ when k == 'Fn::GetAtt'
313
+ FnCall.new 'get_att', v
314
+ when k == 'Fn::GetAZs'
315
+ FnCall.new 'get_azs', v != '' ? [v] : []
316
+ when k == 'Fn::Join'
317
+ FnCall.new 'join', [v[0]] + many(v[1]), true
318
+ when k == 'Fn::Select'
319
+ FnCall.new 'select', v
320
+ when k == 'Ref' && v == 'AWS::AccountId'
321
+ FnCall.new 'aws_account_id', []
322
+ when k == 'Ref' && v == 'AWS::NotificationARNs'
323
+ FnCall.new 'aws_notification_arns', []
324
+ when k == 'Ref' && v == 'AWS::NoValue'
325
+ FnCall.new 'aws_no_value', []
326
+ when k == 'Ref' && v == 'AWS::Region'
327
+ FnCall.new 'aws_region', []
328
+ when k == 'Ref' && v == 'AWS::StackId'
329
+ FnCall.new 'aws_stack_id', []
330
+ when k == 'Ref' && v == 'AWS::StackName'
331
+ FnCall.new 'aws_stack_name', []
332
+ when k == 'Ref'
333
+ FnCall.new 'ref', [v]
334
+ else
335
+ val
336
+ end
337
+ end
338
+ elsif val.is_a?(Array)
339
+ val.map { |v| simplify(v) }
340
+ else
341
+ val
342
+ end
343
+ end
344
+
345
+ main(ARGV)