aws-cfn-dsl 0.6.0 → 0.7.0

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.
@@ -1,4 +1,3 @@
1
- require 'slop'
2
1
  require "aws/cfn/dsl/base"
3
2
 
4
3
  module Aws
@@ -8,25 +7,11 @@ module Aws
8
7
 
9
8
  def run
10
9
 
11
- @opts = Slop.parse(help: true) do
12
- on :j, :template=, 'The template to convert', as: String, argument: true
13
- on :o, :output=, 'The directory to output the DSL to.', as: String, argument: true
14
- on :O, :overwrite, 'Overwrite existing generated source files. (HINT: Think twice ...)', { as: String, optional_argument: true, default: 'off', match: %r/0|1|yes|no|on|off|enable|disable|set|unset|true|false|raw/i }
15
- end
16
-
17
- @config[:overwrite] = if @opts[:overwrite].downcase.match %r'^(1|true|on|yes|enable|set)$'
18
- true
19
- else
20
- false
21
- end
22
-
23
- unless @opts[:template]
24
- abort! @opts
25
- end
10
+ parse_options
26
11
 
27
12
  load_template(@opts[:template])
28
13
 
29
- save_dsl(@opts[:output])
14
+ save_dsl(@opts[:directory])
30
15
 
31
16
  end
32
17
  end
@@ -0,0 +1,52 @@
1
+ module Aws
2
+ module Cfn
3
+ module Dsl
4
+ module DSL
5
+
6
+ # Attempt to figure out what fragment of the template we have. This is imprecise and can't
7
+ # detect Mappings and Outputs sections reliably, so it doesn't attempt to.
8
+ def detect_type(val)
9
+ if val.is_a?(Hash) && val['AWSTemplateFormatVersion']
10
+ :template
11
+ elsif val.is_a?(Hash) && /^(String|Number)$/ =~ val['Type']
12
+ :parameter
13
+ elsif val.is_a?(Hash) && val['Type']
14
+ :resource
15
+ elsif val.is_a?(Hash) && val.values.all? { |v| detect_type(v) == :parameter }
16
+ :parameters
17
+ elsif val.is_a?(Hash) && val.values.all? { |v| detect_type(v) == :resource }
18
+ :resources
19
+ end
20
+ end
21
+
22
+ def add_brick(subdir,name)
23
+ if @config[:directory]
24
+ #file = rb_file(subdir, name).gsub(%r'^#{@config[:directory]}/','')
25
+ #writeln " file '#{file}'"
26
+ s = subdir.downcase.gsub(%r's$','')
27
+ writeln " #{s} '#{name}'"
28
+ end
29
+ end
30
+
31
+ def rb_file(subdir, name)
32
+ path = File.join(@config[:directory], subdir)
33
+ unless File.directory? path
34
+ Dir.mkdir path
35
+ end
36
+ file = File.join(path, "#{name}.rb")
37
+ end
38
+
39
+ def module_name(parts=-1)
40
+ name = self.class.to_s.split("::")
41
+ name[0..parts-1].join('::')
42
+ end
43
+
44
+
45
+ def self.included(includer)
46
+
47
+ end
48
+
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,42 @@
1
+ module Aws
2
+ module Cfn
3
+ module Dsl
4
+ module Maintainer
5
+
6
+ def maintainer(parts=-1)
7
+ "maintainer: #{module_name parts}"
8
+ end
9
+
10
+ def maintainer_comment(indent=' ')
11
+ "#{indent}# WARNING: This code is generated. Your changes may be overwritten!\n" +
12
+ "#{indent}# Remove this message and/or set the 'maintainer: <author name>' when you need your changes to survive.\n" +
13
+ "#{indent}# Abscence of the 'maintainer: ' will be considered conscent to overwrite.\n" +
14
+ "#{indent}# #{maintainer 3}\n" +
15
+ "#\n"
16
+ end
17
+
18
+ def print_maintainer(indent=' ')
19
+ writeln maintainer_comment(indent)
20
+ end
21
+
22
+ def i_am_maintainer(file)
23
+ # mod = module_name 2
24
+ if File.exists?(file)
25
+ src = IO.read(file)
26
+ mtc = src.match(%r'#{maintainer 2}')
27
+ iam = (not mtc.nil? or src.match(%r'#\s*maintainer:').nil?)
28
+ ovr = @config[:overwrite]
29
+ iam or ovr
30
+ else
31
+ true
32
+ end
33
+ end
34
+
35
+ def self.included(includer)
36
+
37
+ end
38
+
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,126 @@
1
+ require 'slop'
2
+ module Aws
3
+ module Cfn
4
+ module Dsl
5
+ module Options
6
+ attr_reader :opts
7
+ attr_reader :config
8
+
9
+ def setup_options
10
+ @on_off_regex = %r/0|1|yes|no|on|off|enable|disable|set|unset|true|false|raw/i
11
+ @format_regex = %r/ruby|rb|yaml|yml|json|js/i
12
+ @on_yes_regex = %r'^(1|true|on|yes|enable|set)$'
13
+
14
+ @opts = Slop.new(help: true) do
15
+ on :t, :template=, 'The template', as: String
16
+ on :d, :directory=, 'The directory with template components.', as: String
17
+
18
+ on :l, :log_level=, "Logging level. [#{::Logging::LEVELS.keys.join('|')}]", {as: String,
19
+ default: 'step',
20
+ match: %r/#{::Logging::LEVELS.keys.join('|')}/i}
21
+
22
+ on :n, :functions=, 'Enable function use.', { as: String,
23
+ default: 'off',
24
+ match: @on_off_regex } do |_|
25
+ me = @options.select { |o|
26
+ o.long == 'functions'
27
+ }[0]
28
+ me.config[:default] = 'on'
29
+ end
30
+ on :x, :expandedpaths, 'Show expanded paths in output', {as: String,
31
+ optional_argument: true,
32
+ default: 'off',
33
+ match: @on_off_regex } do |objects|
34
+ me = @options.select { |o|
35
+ o.long == 'expandedpaths'
36
+ }[0]
37
+ me.config[:default] = 'on'
38
+ end
39
+ on :O, :overwrite, 'Overwrite existing generated source files. (HINT: Think twice ...)', {as: String,
40
+ optional_argument: true,
41
+ default: 'off',
42
+ match: @on_off_regex } do |objects|
43
+ me = @options.select { |o|
44
+ o.long == 'overwrite'
45
+ }[0]
46
+ me.config[:default] = 'on'
47
+ end
48
+ on :force, 'Continue processing and ignore warnings', {as: String,
49
+ optional_argument: true,
50
+ default: 'off',
51
+ match: @on_off_regex } do |objects|
52
+ me = @options.select { |o|
53
+ o.long == 'force'
54
+ }[0]
55
+ me.config[:default] = 'on'
56
+ end
57
+ end
58
+
59
+ end
60
+
61
+ def parse_options
62
+
63
+ setup_options
64
+
65
+ @opts.parse!
66
+
67
+ unless @opts[:directory]
68
+ puts @opts
69
+ abort! "Missing required option --directory"
70
+ end
71
+
72
+ unless @opts[:template]
73
+ puts @opts
74
+ abort! "Missing required option --template"
75
+ end
76
+
77
+ setup_config
78
+
79
+ end
80
+
81
+ def setup_config
82
+
83
+ [:overwrite, :functions, :force, :expandedpaths ].each { |cfg|
84
+ @config[cfg] = (not @opts[cfg].downcase.match(@on_yes_regex).nil?)
85
+ }
86
+
87
+ @opts.options.each{ |opt|
88
+ key = opt.long.to_sym
89
+ unless @config.has_key?(key)
90
+ @config[key] = opt.value unless opt.value.nil?
91
+ end
92
+ }
93
+
94
+ lcs = ::Logging::ColorScheme.new( 'compiler', :levels => {
95
+ :trace => :blue,
96
+ :debug => :cyan,
97
+ :info => :green,
98
+ :step => :green,
99
+ :warn => :yellow,
100
+ :error => :red,
101
+ :fatal => :red,
102
+ :todo => :purple,
103
+ })
104
+ scheme = lcs.scheme
105
+ scheme['trace'] = "\e[38;5;33m"
106
+ scheme['fatal'] = "\e[38;5;89m"
107
+ scheme['todo'] = "\e[38;5;55m"
108
+ lcs.scheme scheme
109
+ @config[:log_opts] = lambda{|mlll| {
110
+ :pattern => "%#{mlll}l: %m %C\n",
111
+ :date_pattern => '%Y-%m-%d %H:%M:%S',
112
+ :color_scheme => 'compiler'
113
+ }
114
+ }
115
+ @config[:log_level] ||= :info
116
+ @logger = getLogger(@config)
117
+ end
118
+
119
+ def self.included(includer)
120
+
121
+ end
122
+
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,53 @@
1
+ module Aws
2
+ module Cfn
3
+ module Dsl
4
+ module Output
5
+
6
+ def write(*s)
7
+ if s.is_a?(Array)
8
+ s = s.join('')
9
+ end
10
+ if @output.size > 0
11
+ @output[0].write s
12
+ else
13
+ print s
14
+ end
15
+ end
16
+
17
+ def writeln(s='')
18
+ if @output.size > 0
19
+ @output[0].puts s
20
+ else
21
+ puts s
22
+ end
23
+ end
24
+
25
+ def open_output(subdir,name)
26
+ if @config[:directory]
27
+ file = rb_file(subdir, name)
28
+ if i_am_maintainer(file)
29
+ @output.unshift File.open(file, 'w')
30
+ true
31
+ else
32
+ false
33
+ end
34
+ else
35
+ true
36
+ end
37
+ end
38
+
39
+ def close_output()
40
+ if @config[:directory] and @output.size > 0
41
+ fp = @output.shift
42
+ fp.close
43
+ end
44
+ end
45
+
46
+ def self.included(includer)
47
+
48
+ end
49
+
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,296 @@
1
+ module Aws
2
+ module Cfn
3
+ module Dsl
4
+ module PrettyPrint
5
+
6
+ def pprint(val)
7
+ logStep "Pretty print ..."
8
+ case detect_type(val)
9
+ when :template
10
+ pprint_cfn_template(val)
11
+ when :parameter
12
+ pprint_cfn_section 'parameter', 'TODO', val, 'Parameters'
13
+ when :resource
14
+ pprint_cfn_resource 'TODO', val
15
+ when :parameters
16
+ val.each { |k, v| pprint_cfn_section 'parameter', k, v, 'Parameters' }
17
+ when :resources
18
+ val.each { |k, v| pprint_cfn_resource k, v }
19
+ else
20
+ pprint_value(val, '')
21
+ end
22
+ end
23
+
24
+ def pprint_cfn_template(tpl)
25
+ file = File.join(@config[:directory],File.basename(@config[:template].gsub(%r'\.(json|yaml|js|yaml)$'i, '.rb')))
26
+ filn = if @config[:expandedpaths]
27
+ File.expand_path(file)
28
+ else
29
+ file
30
+ end
31
+ file = File.basename(@config[:template].gsub(%r'\.(json|yaml|js|yaml)$'i, '.rb'))
32
+ # noinspection RubyParenthesesAroundConditionInspection
33
+ if (iam = open_output('', file.gsub(%r'\.(json|yaml|js|yml|rb)'i, '')))
34
+ logStep "Saving #{filn}"
35
+ writeln "#!/usr/bin/env ruby"
36
+ print_maintainer('')
37
+ writeln
38
+ if @config[:directory]
39
+ writeln "$:.unshift(File.dirname(__FILE__))"
40
+ # noinspection RubyExpressionInStringInspection
41
+ writeln '$:.unshift File.absolute_path("#{File.dirname(__FILE__)}/../lib")'
42
+ end
43
+ writeln "require 'bundler/setup'"
44
+ writeln "require 'aws/cfn/dsl/template'"
45
+ #writeln "require 'cloudformation-ruby-dsl/spotprice'"
46
+ #writeln "require 'cloudformation-ruby-dsl/table'"
47
+ writeln
48
+ writeln "template do"
49
+ writeln
50
+ tpl.each do |section, v|
51
+ case section
52
+ when 'Parameters'
53
+ when 'Mappings'
54
+ when 'Resources'
55
+ when 'Outputs'
56
+ else
57
+ write " value #{fmt_key(section)} => "
58
+ pprint_value v, ' '
59
+ writeln
60
+ writeln
61
+ end
62
+ end
63
+ else
64
+ @logger.warn "Not overwriting template: '#{file}'"
65
+ end
66
+ %w(Mappings Parameters Resources Outputs).each do |section|
67
+ writeln " # #{section}" if iam
68
+ v = tpl[section]
69
+ case section
70
+ when 'Parameters'
71
+ v.each { |name, options| pprint_cfn_section 'parameter', name, options, 'Parameters', iam }
72
+ when 'Mappings'
73
+ v.each { |name, options| pprint_cfn_section 'mapping', name, options, 'Mappings', iam }
74
+ when 'Resources'
75
+ v.each { |name, options| pprint_cfn_resource name, options, iam }
76
+ when 'Outputs'
77
+ v.each { |name, options| pprint_cfn_section 'output', name, options, 'Outputs', iam }
78
+ else
79
+ abort! "Internal Error: Unexpected section '#{section}'"
80
+ end
81
+ writeln if iam
82
+ end
83
+ writeln "end.exec!" if iam
84
+ end
85
+
86
+
87
+ def prelude_code(indent=' ')
88
+ "scope = Aws::Cfn::Compiler.binding[File.basename(File.dirname(__FILE__))][File.basename(__FILE__, '.rb')]\n"+
89
+ "template = scope[:template]\n"+
90
+ "\n"+
91
+ "# noinspection RubyStringKeysInHashInspection\n"+
92
+ "template." +
93
+ ""
94
+ end
95
+
96
+ def print_with_wrapper(code,indent=' ')
97
+ write prelude_code(indent)+code.gsub(%r'^\s+','')
98
+ end
99
+
100
+ def pprint_cfn_section(section, name, options, subdir, brick=true)
101
+ filn = rb_file(subdir, name)
102
+ filn = File.expand_path(filn) if @config[:expandedpaths]
103
+ if open_output(subdir,name)
104
+ @logger.info "Pretty print #{section} '#{name}' to '#{filn}'"
105
+ print_maintainer ''
106
+ print_with_wrapper "#{section} #{fmt_string(name)}"
107
+ indent = ' ' + (' ' * section.length) + ' '
108
+ hang = true
109
+ options.each do |k, v|
110
+ if hang
111
+ writeln ','
112
+ hang = false
113
+ end
114
+ write indent, fmt_key(k), " => "
115
+ pprint_value v, indent
116
+ hang = true
117
+ end
118
+ writeln
119
+ writeln
120
+ close_output
121
+ add_brick(subdir,name) if brick
122
+ else
123
+ @logger.warn "NOT overwriting existing source file '#{filn}'"
124
+ end
125
+ end
126
+
127
+ def pprint_cfn_resource(name, options, brick=true)
128
+ subdir = 'Resources'
129
+ filn = rb_file(subdir, name)
130
+ filn = File.expand_path(filn) if @config[:expandedpaths]
131
+ if open_output(subdir,name)
132
+ @logger.info "Pretty print resource '#{name}' to '#{filn}'"
133
+ print_maintainer ''
134
+ print_with_wrapper "resource #{fmt_string(name)}"
135
+ indent = ' '
136
+ hang = true
137
+ options.each do |k, v|
138
+ if hang
139
+ writeln ','
140
+ hang = false
141
+ end
142
+
143
+ case k
144
+ when /^(Metadata|Properties)$/
145
+ write "#{indent}#{fmt_key(k)} => "
146
+ pprint_value options[k], indent
147
+ hang = true
148
+ else
149
+ write "#{indent}#{fmt_key(k)} => "
150
+ write "#{fmt(v)}"
151
+ hang = true
152
+ end
153
+ end
154
+ writeln
155
+ close_output
156
+ add_brick(subdir,name) if brick
157
+ else
158
+ @logger.warn "NOT overwriting existing source file '#{filn}'"
159
+ end
160
+ end
161
+
162
+ def pprint_value(val, indent)
163
+ # Prefer to write the value on a single line if it's reasonable to do so
164
+ single_line = is_single_line(val) || is_single_line_hack(val)
165
+ if single_line && !is_multi_line_hack(val)
166
+ s = fmt(val)
167
+ if s.length < 120 || is_single_line_hack(val)
168
+ write s
169
+ return
170
+ end
171
+ end
172
+
173
+ # Print the value across multiple lines
174
+ if val.is_a?(Hash)
175
+ writeln "{"
176
+ val.each do |k, v|
177
+ write "#{indent} #{fmt_key(k)} => "
178
+ pprint_value v, indent + ' '
179
+ writeln ","
180
+ end
181
+ write "#{indent}}"
182
+
183
+ elsif val.is_a?(Array)
184
+ writeln "["
185
+ val.each do |v|
186
+ write "#{indent} "
187
+ pprint_value v, indent + ' '
188
+ writeln ","
189
+ end
190
+ write "#{indent}]"
191
+
192
+ elsif val.is_a?(FnCall) && val.multiline && @config[:functions] != 'raw'
193
+ write val.name, "("
194
+ args = val.arguments
195
+ sep = ''
196
+ sub_indent = indent + ' '
197
+ if val.name == 'join' && args.length > 1
198
+ pprint_value args[0], indent + ' '
199
+ args = args[1..-1]
200
+ sep = ','
201
+ sub_indent = indent + ' '
202
+ end
203
+ unless args.empty?
204
+ args.each do |v|
205
+ writeln sep
206
+ write sub_indent
207
+ pprint_value v, sub_indent
208
+ sep = ','
209
+ end
210
+ if val.name == 'join' && args.length > 1
211
+ write ","
212
+ end
213
+ writeln
214
+ write indent
215
+ end
216
+ write ")"
217
+
218
+ else
219
+ write fmt(val)
220
+ end
221
+ end
222
+
223
+ def is_single_line(val)
224
+ if val.is_a?(Hash)
225
+ is_single_line(val.values)
226
+ elsif val.is_a?(Array)
227
+ val.empty? ||
228
+ (val.length == 1 && is_single_line(val[0]) && !val[0].is_a?(Hash)) ||
229
+ val.all? { |v| v.is_a?(String) }
230
+ else
231
+ true
232
+ end
233
+ end
234
+
235
+ # Emo-specific hacks to force the desired output formatting
236
+ def is_single_line_hack(val)
237
+ is_array_of_strings_hack(val)
238
+ end
239
+
240
+ # Emo-specific hacks to force the desired output formatting
241
+ def is_multi_line_hack(val)
242
+ val.is_a?(Hash) && val['email']
243
+ end
244
+
245
+ # Emo-specific hacks to force the desired output formatting
246
+ def is_array_of_strings_hack(val)
247
+ val.is_a?(Array) && val.all? { |v| v.is_a?(String) } && val.grep(/\s/).empty? && (
248
+ val.include?('autoscaling:EC2_INSTANCE_LAUNCH') ||
249
+ val.include?('m1.small')
250
+ )
251
+ end
252
+
253
+ def fmt(val)
254
+ if val == {}
255
+ '{}'
256
+ elsif val == []
257
+ '[]'
258
+ elsif val.is_a?(Hash)
259
+ '{ ' + (val.map { |k,v| fmt_key(k) + ' => ' + fmt(v) }).join(', ') + ' }'
260
+ elsif val.is_a?(Array) && is_array_of_strings_hack(val)
261
+ '%w(' + val.join(' ') + ')'
262
+ elsif val.is_a?(Array)
263
+ '[ ' + (val.map { |v| fmt(v) }).join(', ') + ' ]'
264
+ elsif val.is_a?(FnCall) && val.arguments.empty?
265
+ val.name
266
+ elsif val.is_a?(FnCall)
267
+ val.name + '(' + (val.arguments.map { |v| fmt(v) }).join(', ') + ')'
268
+ elsif val.is_a?(String)
269
+ fmt_string(val)
270
+ elsif val == nil
271
+ 'null'
272
+ else
273
+ val.to_s # number, boolean
274
+ end
275
+ end
276
+
277
+ def fmt_key(s)
278
+ ':' + (/^[a-zA-Z_]\w+$/ =~ s ? s : fmt_string(s)) # returns a symbol like :Foo or :'us-east-1'
279
+ end
280
+
281
+ def fmt_string(s)
282
+ if /[^ -~]/ =~ s
283
+ s.dump # contains, non-ascii or control char, return double-quoted string
284
+ else
285
+ '\'' + s.gsub(/([\\'])/, '\\\\\1') + '\'' # return single-quoted string, escape \ and '
286
+ end
287
+ end
288
+
289
+ def self.included(includer)
290
+
291
+ end
292
+
293
+ end
294
+ end
295
+ end
296
+ end