aws-cfn-dsl 0.6.0 → 0.7.0

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