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.
- checksums.yaml +7 -0
- data/.gitignore +29 -0
- data/Gemfile +4 -0
- data/LICENSE +13 -0
- data/OWNERS +4 -0
- data/README.md +125 -0
- data/Rakefile +1 -0
- data/bin/aws-sdk-patch +3 -0
- data/bin/cfntemplate-to-ruby +345 -0
- data/cloudformation-ruby-dsl-addedvars.gemspec +42 -0
- data/docs/Contributing.md +21 -0
- data/docs/Releasing.md +20 -0
- data/examples/cloudformation-ruby-script.rb +232 -0
- data/examples/maps/domains.txt +4 -0
- data/examples/maps/map.json +9 -0
- data/examples/maps/map.rb +5 -0
- data/examples/maps/map.yaml +5 -0
- data/examples/maps/more_maps/map1.json +8 -0
- data/examples/maps/more_maps/map2.json +8 -0
- data/examples/maps/more_maps/map3.json +8 -0
- data/examples/maps/table.txt +5 -0
- data/examples/maps/vpc.txt +25 -0
- data/examples/userdata.sh +4 -0
- data/initial_contributions.md +5 -0
- data/lib/cloudformation-ruby-dsl-addedvars.rb +1 -0
- data/lib/cloudformation-ruby-dsl-addedvars/cfntemplate.rb +595 -0
- data/lib/cloudformation-ruby-dsl-addedvars/dsl.rb +270 -0
- data/lib/cloudformation-ruby-dsl-addedvars/spotprice.rb +50 -0
- data/lib/cloudformation-ruby-dsl-addedvars/table.rb +123 -0
- data/lib/cloudformation-ruby-dsl-addedvars/version.rb +21 -0
- data/share/aws-sdk-patch.sh +108 -0
- metadata +192 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitignore
ADDED
@@ -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
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
data/README.md
ADDED
@@ -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'}`
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/aws-sdk-patch
ADDED
@@ -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)
|