aws-cfn-compiler 0.3.1 → 0.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 98579909e2c94b50d6d0f5905160c7bb2e033bb9
4
- data.tar.gz: 4c5f6152afa7a9853c0a80ade457367124f65598
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ MzY5MDJkOWY3MWRlNGZhOTEzNDMxN2IzMjVlMmZhNmI3ZGE4YWYyMg==
5
+ data.tar.gz: !binary |-
6
+ MmI4Zjc4OGI3NmI0NzQzMTJmZTEyNTUxMjMzMDcxMmZjMDJiOTk5OQ==
5
7
  SHA512:
6
- metadata.gz: 0d37543235669ff4bfbdf7b6f17aaaaba20eb7375734f633902c49bd5df38aa5308f359f2e4ad82fbb9d8d2d8d4f8e56770a8347fc01c27dfdede867969daffc
7
- data.tar.gz: d342f24025ce2b7b2880d1870318b83cae302b9027e8ed6357cb4f35542146db853d195c0687716209b0f55efa958d812db05235db8917f82ab6039e8ca02d2f
8
+ metadata.gz: !binary |-
9
+ NWU0YTAyZTU1MDUxMWYxMGI1NDhhNTBlMzhmMmFiNTViMjhjZjBkYzM5MTMz
10
+ YzQwNzVlMjRmN2JkYmFlOTJhN2M4OWJkYzlmNjkzM2JjMGEzNTEyYzYxMGJk
11
+ ZWU4Y2NhNDQ0NGQ4NzBhMmRiN2Y5ZDc4ZGY2ZTE2MjViNDMxOGY=
12
+ data.tar.gz: !binary |-
13
+ M2QzYTdlMmEyZWI3NDUwODA1YmEzNDc4Nzc2Y2UyODU0MmFjZmQ5MmQzODUx
14
+ N2RlYjFkZDlkYWNjZjM3NGExZThkZmIwNDYyNWY0NjIwNmZhYmM4YWQxMDEx
15
+ NGQwNjkyYzhjMzYzMDZhMzlmZjQzNjM0ZjFlYTBlNTBiMTViNjU=
data/Gemfile CHANGED
@@ -2,3 +2,9 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in aws-cfn-compiler.gemspec
4
4
  gemspec
5
+
6
+ gem 'cloudformation-ruby-dsl', :path => '../cloudformation-ruby-dsl', :group => :development
7
+ gem 'aws-cfn-dsl', :path => '../aws-cfn-dsl', :group => :development
8
+ gem 'aws-cfn-decompiler', :path => '../aws-cfn-decompiler', :group => :development
9
+ gem 'bundler', "~> 1.6", :group => :development
10
+ gem 'rake', :group => :development
@@ -18,12 +18,14 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_dependency "awesome_print"
21
+ spec.add_dependency "awesome_print", '~> 1.2', '>= 1.2.0'
22
22
  spec.add_dependency "psych"
23
23
  spec.add_dependency "json"
24
- spec.add_dependency 'aws-cfn-dsl', '>= 0.0.1', '~> 0.0'
25
24
  spec.add_dependency "slop"
25
+ spec.add_dependency 'colorize'
26
+ spec.add_dependency 'inifile'
27
+ spec.add_dependency 'aws-cfn-dsl', '>= 0.2.0', '~> 0.2'
28
+ spec.add_dependency 'dldinternet-mixlib-cli', ">= 0.1.0", '~> 0.1'
29
+ spec.add_dependency 'dldinternet-mixlib-logging', '>= 0.1.6', '~> 0.1'
26
30
 
27
- spec.add_development_dependency 'bundler', "~> 1.6"
28
- spec.add_development_dependency 'rake'
29
31
  end
data/aws-cfn-compiler.iml CHANGED
@@ -3,26 +3,25 @@
3
3
  <component name="CompassSettings">
4
4
  <option name="compassSupportEnabled" value="true" />
5
5
  </component>
6
- <component name="FacetManager">
7
- <facet type="gem" name="Ruby Gem">
8
- <configuration>
9
- <option name="GEM_APP_ROOT_PATH" value="$MODULE_DIR$" />
10
- <option name="GEM_APP_TEST_PATH" value="" />
11
- <option name="GEM_APP_LIB_PATH" value="$MODULE_DIR$/lib" />
12
- </configuration>
13
- </facet>
14
- </component>
15
6
  <component name="NewModuleRootManager" inherit-compiler-output="true">
16
7
  <exclude-output />
17
8
  <content url="file://$MODULE_DIR$" />
18
- <orderEntry type="jdk" jdkName="RVM: ruby-1.9.3-p547 [aws-cfn-compile]" jdkType="RUBY_SDK" />
9
+ <orderEntry type="inheritedJdk" />
19
10
  <orderEntry type="sourceFolder" forTests="false" />
11
+ <orderEntry type="module" module-name="aws-cfn-decompiler" />
12
+ <orderEntry type="module" module-name="aws-cfn-dsl" />
13
+ <orderEntry type="module" module-name="cloudformation-ruby-dsl" />
20
14
  <orderEntry type="library" scope="PROVIDED" name="awesome_print (v1.2.0, RVM: ruby-1.9.3-p547 [aws-cfn-compile]) [gem]" level="application" />
21
- <orderEntry type="library" scope="PROVIDED" name="aws-cfn-dsl (v0.0.4, RVM: ruby-1.9.3-p547 [aws-cfn-compile]) [gem]" level="application" />
22
- <orderEntry type="library" scope="PROVIDED" name="bundler (v1.6.2, RVM: ruby-1.9.3-p547 [aws-cfn-compile]) [gem]" level="application" />
23
- <orderEntry type="library" scope="PROVIDED" name="cloudformation-ruby-dsl (v0.4.3, RVM: ruby-1.9.3-p547 [aws-cfn-compile]) [gem]" level="application" />
15
+ <orderEntry type="library" scope="PROVIDED" name="colorize (v0.7.3, RVM: ruby-1.9.3-p547 [aws-cfn-compile]) [gem]" level="application" />
24
16
  <orderEntry type="library" scope="PROVIDED" name="detabulator (v0.1.0, RVM: ruby-1.9.3-p547 [aws-cfn-compile]) [gem]" level="application" />
17
+ <orderEntry type="library" scope="PROVIDED" name="dldinternet-mixlib-cli (v0.1.0, RVM: ruby-1.9.3-p547 [aws-cfn-compile]) [gem]" level="application" />
18
+ <orderEntry type="library" scope="PROVIDED" name="dldinternet-mixlib-logging (v0.1.6, RVM: ruby-1.9.3-p547 [aws-cfn-compile]) [gem]" level="application" />
19
+ <orderEntry type="library" scope="PROVIDED" name="inifile (v2.0.2, RVM: ruby-1.9.3-p547 [aws-cfn-compile]) [gem]" level="application" />
25
20
  <orderEntry type="library" scope="PROVIDED" name="json (v1.8.1, RVM: ruby-1.9.3-p547 [aws-cfn-compile]) [gem]" level="application" />
21
+ <orderEntry type="library" scope="PROVIDED" name="little-plugger (v1.1.3, RVM: ruby-1.9.3-p547 [aws-cfn-compile]) [gem]" level="application" />
22
+ <orderEntry type="library" scope="PROVIDED" name="logging (v1.8.2, RVM: ruby-1.9.3-p547 [aws-cfn-compile]) [gem]" level="application" />
23
+ <orderEntry type="library" scope="PROVIDED" name="mixlib-cli (v1.5.0, RVM: ruby-1.9.3-p547 [aws-cfn-compile]) [gem]" level="application" />
24
+ <orderEntry type="library" scope="PROVIDED" name="multi_json (v1.10.1, RVM: ruby-1.9.3-p547 [aws-cfn-compile]) [gem]" level="application" />
26
25
  <orderEntry type="library" scope="PROVIDED" name="psych (v2.0.5, RVM: ruby-1.9.3-p547 [aws-cfn-compile]) [gem]" level="application" />
27
26
  <orderEntry type="library" scope="PROVIDED" name="rake (v10.3.2, RVM: ruby-1.9.3-p547 [aws-cfn-compile]) [gem]" level="application" />
28
27
  <orderEntry type="library" scope="PROVIDED" name="slop (v3.5.0, RVM: ruby-1.9.3-p547 [aws-cfn-compile]) [gem]" level="application" />
data/bin/cfn-compiler CHANGED
@@ -2,27 +2,30 @@
2
2
 
3
3
  require 'rubygems'
4
4
 
5
- # require 'safe_yaml'
6
- # SafeYAML::OPTIONS[:default_mode] = :safe
7
-
5
+ path = File.dirname(__FILE__)
6
+ if File.exists?("#{path}/../.gitignore")
7
+ %w(aws-cfn-decompiler aws-cfn-compiler cloudformation-ruby-dsl).each do |mod|
8
+ add_path = File.expand_path(File.join(path, "../../#{mod}", "lib"))
9
+ $:.unshift(add_path)
10
+ end
11
+ else
8
12
  # Borrowing from "whiches" gem ...
9
- cmd = File.basename(__FILE__, '.rb')
10
- exes = []
11
- exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
12
- ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
13
- exts.each { |ext|
14
- exe = File.join(path, "#{cmd}#{ext}")
15
- exes << exe if File.executable? exe
16
- }
17
- end
18
- path = if exes.size > 0
19
- File.dirname(exes[0])
20
- else
21
- File.dirname(__FILE__)
22
- end
13
+ cmd = File.basename(__FILE__, '.rb')
14
+ exes = []
15
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
16
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
17
+ exts.each { |ext|
18
+ exe = File.join(path, "#{cmd}#{ext}")
19
+ exes << exe if File.executable? exe
20
+ }
21
+ end
22
+ if exes.size > 0
23
+ path = File.dirname(exes[0])
24
+ end
23
25
 
26
+ end
24
27
  add_path = File.expand_path(File.join(path, "..", "lib"))
25
28
  $:.unshift(add_path)
26
29
 
27
30
  require 'aws/cfn/compiler'
28
- Aws::Cfn::Compiler::Main.new().run
31
+ Aws::Cfn::Compiler::Main.new.run
@@ -0,0 +1,328 @@
1
+
2
+ require 'json'
3
+ require 'ap'
4
+ require 'yaml'
5
+ require 'slop'
6
+ require 'aws/cfn/dsl/template'
7
+
8
+ module Aws
9
+ module Cfn
10
+ module Compiler
11
+ class Base
12
+ attr_accessor :items
13
+ attr_accessor :opts
14
+ attr_accessor :spec
15
+
16
+ require 'dldinternet/mixlib/logging'
17
+ include DLDInternet::Mixlib::Logging
18
+
19
+ def initialize
20
+ @items = {}
21
+ @config ||= {}
22
+ @config[:log_opts] = lambda{|mlll| {
23
+ :pattern => "%#{mlll}l: %m %C\n",
24
+ :date_pattern => '%Y-%m-%d %H:%M:%S',
25
+ }
26
+ }
27
+ @config[:log_level] = :step
28
+ @logger = getLogger(@config)
29
+ end
30
+
31
+ def validate(compiled)
32
+ raise 'No Resources!?' unless compiled['Resources']
33
+
34
+ # Mappings => Resources
35
+ maps = find_maps(compiled) #.select { |a| !(a =~ /^AWS::/) }
36
+ rscs = compiled['Resources'].keys
37
+ mpgs = compiled['Mappings'].nil? ? [] : compiled['Mappings'].keys
38
+ names = rscs+mpgs
39
+
40
+ unless (maps-names).empty?
41
+ @logger.error '!!! Unknown mappings !!!'
42
+ (maps-names).each do |name|
43
+ @logger.error " #{name}"
44
+ end
45
+ abort!
46
+ end
47
+ @logger.step ' Mappings validated'
48
+
49
+ # Parameters => Resources => Out@logger.step
50
+ refs = find_refs(compiled).select { |a,_| !(a =~ /^AWS::/) }
51
+ prms = compiled['Parameters'].keys rescue []
52
+ # outs = compiled['Outputs'].keys rescue []
53
+ names = rscs+prms
54
+
55
+ unless (refs.keys-names).empty?
56
+ @logger.error '!!! Unknown references !!!'
57
+ (refs.keys-names).each do |name|
58
+ @logger.error " #{name} from #{refs[name][0]}:#{refs[name][1]}"
59
+ end
60
+ abort!
61
+ end
62
+ @logger.step ' References validated'
63
+ end
64
+
65
+ def save(compiled, output_file)
66
+ begin
67
+ hash = {}
68
+ compiled.each do |item,value|
69
+ unless value.nil?
70
+ if (not value.is_a?(Hash)) or (value.count > 0)
71
+ hash[item] = value
72
+ end
73
+ end
74
+ end
75
+
76
+ File.open output_file, 'w' do |f|
77
+ f.write JSON.pretty_generate(hash, { indent: "\t", space: ' '})
78
+ end
79
+ @logger.step ' Compiled file written.'
80
+ rescue
81
+ @logger.error "!!! Could not write compiled file: #{$!}"
82
+ abort!
83
+ end
84
+ end
85
+
86
+ def load(spec=nil)
87
+ if spec
88
+ begin
89
+ abs = File.absolute_path(File.expand_path(spec))
90
+ unless File.exists?(abs)
91
+ abs = File.absolute_path(File.expand_path(File.join(@opts[:directory],spec)))
92
+ end
93
+ rescue
94
+ # pass
95
+ end
96
+ if File.exists?(abs)
97
+ raise "Unsupported specification file type: #{spec}=>#{abs}\n\tSupported types are: json,yaml,jts,yts\n" unless abs =~ /\.(json|ya?ml|jts|yts)\z/i
98
+
99
+ @logger.step "Loading specification #{abs}..."
100
+ spec = File.read(abs)
101
+
102
+ case File.extname(File.basename(abs)).downcase
103
+ when /json|jts/
104
+ @spec = JSON.parse(spec)
105
+ when /yaml|yts/
106
+ @spec = YAML.load(spec)
107
+ else
108
+ raise "Unsupported file type for specification: #{spec}"
109
+ end
110
+ # @spec = spec
111
+ else
112
+ raise "Unable to open specification: #{abs}"
113
+ end
114
+ @dsl ||= Aws::Cfn::Dsl::Template.new(@opts[:directory])
115
+ %w( Mappings Parameters Resources Outputs ).each do |dir|
116
+ load_dir(dir,@spec)
117
+ end
118
+ else
119
+ raise "No specification provided"
120
+ end
121
+ end
122
+
123
+ protected
124
+
125
+ def abort!
126
+ @logger.fatal '!!! Aborting !!!'
127
+ exit
128
+ end
129
+
130
+ def find_refs(hash, type='Reference', parent='')
131
+ h = {}
132
+ newparent = parent
133
+ if hash.is_a? Hash
134
+ hash.keys.collect do |key|
135
+ if %w{Mappings Parameters Resources Outputs}.include? key
136
+ type = key#.gsub(/s$/, '')
137
+ newparent = key
138
+ elsif %w{Mappings Parameters Resources Outputs}.include? parent
139
+ newparent = key
140
+ end
141
+ if %w{Ref}.include? key
142
+ h = { hash[key] => [type,newparent] }
143
+ elsif 'Fn::GetAtt' == key
144
+ h = { hash[key].first => [type,newparent] }
145
+ # elsif %w{SourceSecurityGroupName CacheSecurityGroupNames SecurityGroupNames}.include? key
146
+ # a = find_refs(hash[key],type,newparent)
147
+ # h = merge(h, a, *[type,newparent])
148
+ else
149
+ a = find_refs(hash[key],type,newparent)
150
+ h = merge(h, a, *[type,newparent])
151
+ end
152
+ end.flatten.compact.uniq
153
+ elsif hash.is_a? Array
154
+ a = hash.map{|i| find_refs(i,type,newparent) }
155
+ h = merge(h, a, type, *[type,newparent])
156
+ end
157
+ h
158
+ end
159
+
160
+ def merge(h, a, *type)
161
+ if a.is_a? Hash
162
+ if a.size > 0
163
+ h.merge! a
164
+ end
165
+ else
166
+ a.flatten.compact.uniq.map { |i|
167
+ if i.is_a? Hash
168
+ if i.size > 0
169
+ h.merge! i
170
+ h
171
+ end
172
+ else
173
+ h[i] = type
174
+ end
175
+ }
176
+ end
177
+ h
178
+ end
179
+
180
+ def find_maps(hash)
181
+ if hash.is_a? Hash
182
+ tr = []
183
+ hash.keys.collect do |key|
184
+ if 'Fn::FindInMap' == key
185
+ hash[key].first
186
+ else
187
+ find_maps(hash[key])
188
+ end
189
+ end.flatten.compact.uniq
190
+ elsif hash.is_a? Array
191
+ hash.collect{|a| find_maps(a)}.flatten.compact.uniq
192
+ end
193
+ end
194
+
195
+ # --------------------------------------------------------------------------------
196
+ def get_file_set(want, path, exts=[])
197
+ # Dir[File.join(@opts[:directory], "#{dir}.*")] | Dir[File.join(@opts[:directory], dir.to_s, "**", "*")]
198
+ raise "Bad call to #{self.class.name}.getPathSet: want == nil" unless want
199
+ @logger.debug "Look for #{want.ai} in #{[path]} with #{exts} extensions"
200
+ if exts.nil?
201
+ exts = @config[:precedence]
202
+ end
203
+ file_regex=%r/^(\S+)\.(#{exts.join('|')})$/
204
+ if exts.empty?
205
+ file_regex=%r/^(\S+)()$/
206
+ exts=['']
207
+ end
208
+ regex = "^(#{want.join('|')})$"
209
+ set = {}
210
+ abs = File.expand_path(path)
211
+ abs = path unless @config[:expandedpaths]
212
+ raise "Oops! Does '#{path}' directory exist?" unless File.directory?(abs)
213
+ #abs = File.realpath(abs)
214
+ begin
215
+ Dir.glob("#{abs}/*").each{ |f|
216
+ match = File.basename(f).match(file_regex)
217
+ if match
218
+ name = match[1]
219
+ ext = match[2]
220
+ set[ext] = {} unless set[ext]
221
+ @logger.trace "#{name} =~ #{regex}"
222
+ set[ext][name] = f if name.match(regex)
223
+ end
224
+ }
225
+ rescue RegexpError => e
226
+ raise ChopError.new "The regular expression attempting to match resources in '#{path}' is incorrect! #{e.message}"
227
+ end
228
+ @logger.debug "getPathSet set=#{set.ai}"
229
+ res = {}
230
+ # Iterate extension sets in increasing precedence order ...
231
+ # Survivor will be the most desireable version of the item
232
+ # i.e. the .rb environment, role, data bag, etc. will be preferred over the .json version
233
+ exts.each{ |e|
234
+ h = set[e]
235
+ if h
236
+ h.each{ |n,f|
237
+ @logger.warn "Ignoring #{File.basename(res[n])}" if res[n]
238
+ res[n] = f
239
+ }
240
+ else
241
+ @logger.info "'#{e}' set is empty! (No #{path}/*.#{e} files found using precedence #{exts})"
242
+ end
243
+ }
244
+ set = res
245
+ end
246
+
247
+ def load_dir(dir,spec=nil)
248
+ logStep "Loading #{dir}..."
249
+
250
+ if spec and spec[dir]
251
+ raise "No such directory: #{@opts[:directory]}" unless File.directory?(@opts[:directory])
252
+ set = []
253
+ if File.directory?(File.join(@opts[:directory], dir))
254
+ @items[dir] = {}
255
+ set = get_file_set([".*"], "#{@opts[:directory]}/#{dir}", @config[:precedence])
256
+ else
257
+ if File.directory?(File.join(@opts[:directory], dir.downcase))
258
+ @items[dir] = {}
259
+ set = get_file_set(['.*'], dir.downcase, @config[:precedence])
260
+ else
261
+ @logger.error " !! error: Cannot load bricks from #{File.join(@opts[:directory], dir)}"
262
+ abort!
263
+ end
264
+ end
265
+
266
+ item = {}
267
+ spec[dir].each do |base|
268
+ @logger.info "\tUsing #{dir}/#{base}"
269
+ if set[base]
270
+ if item.has_key?(base)
271
+ @logger.error " !! error: Duplicate item: #{dir}/#{base}"
272
+ abort!
273
+ end
274
+
275
+ filename = set[base]
276
+ unless filename =~ /\.(ru?by?|ya?ml|js(|on))\z/i
277
+ @logger.info "Brick not supported/ relevant: #{filename}"
278
+ next
279
+ end
280
+
281
+ begin
282
+ @logger.step " reading #{filename}"
283
+ content = File.read(filename)
284
+ next if content.size==0
285
+
286
+ if filename =~ /\.(rb|ruby)\z/i
287
+ eval "@dsl.#{content.gsub(%r'^\s+','')}"
288
+ unless @dsl.dict[dir.to_sym]
289
+ raise "Unable to expand #{filename} for #{dir}/#{base}"
290
+ end
291
+ item.merge! @dsl.dict[dir.to_sym]
292
+ elsif filename =~ /\.js(|on)\z/i
293
+ item.merge! JSON.parse(content)
294
+ elsif filename =~ /\.ya?ml\z/i
295
+ item.merge! YAML.load(content)
296
+ else
297
+ next
298
+ end
299
+
300
+ rescue
301
+ @logger.error " !! error: #{$!}"
302
+ abort!
303
+ end
304
+ else
305
+ @logger.error " !! error: #{dir}/#{base} not found!"
306
+ abort!
307
+ end
308
+ end
309
+ item.keys.each { |key|
310
+ if @items[dir].has_key?(key)
311
+ @logger.error " !! error: Duplicate item: #{dir}/#{key}"
312
+ abort!
313
+ end
314
+ }
315
+ @items[dir].merge! item
316
+
317
+ unless @items[dir].keys.count == spec[dir].count
318
+ @logger.error " !! error: Suspect that a #{dir} item was missed! \nRequested: #{spec[dir]}\n Found: #{@items[dir].keys}"
319
+ abort!
320
+ end
321
+ end
322
+
323
+ end
324
+
325
+ end
326
+ end
327
+ end
328
+ end
@@ -0,0 +1,68 @@
1
+
2
+ require 'json'
3
+ require 'ap'
4
+ require 'yaml'
5
+ require 'slop'
6
+
7
+ module Aws
8
+ module Cfn
9
+ module Compiler
10
+ class Main < Base
11
+
12
+ def run
13
+
14
+ @opts = Slop.parse(help: true) do
15
+ on :d, :directory=, 'The directory to look in', as: String
16
+ on :o, :output=, 'The JSON file to output', as: String
17
+ on :s, :specification=, 'The specification to use when selecting components. A JSON or YAML file or JSON object', as: String
18
+ on :f, :formatversion=, 'The AWS Template format version. Default 2010-09-09', as: String
19
+ on :p, :precedence=, 'The precedence of template component types. Default: rb,yaml,yml,json,js', as: String, default: 'rb,ruby,yaml,yml,json,js'
20
+ on :t, :description=, "The AWS Template description. Default: output basename or #{File.basename(__FILE__,'.rb')}", as: String
21
+ on :x, :expandedpaths=, 'Show expanded paths in output', as: String, default: 'off'
22
+ end
23
+
24
+ unless @opts[:directory]
25
+ puts @opts
26
+ exit
27
+ end
28
+
29
+ @config[:precedence] = @opts[:precedence].split(%r',+\s*').reverse
30
+ @config[:expandedpaths] = @opts[:expandedpaths].downcase.match %r'^(1|true|on|yes|enable|set)$'
31
+
32
+ load @opts[:specification]
33
+
34
+ desc = @opts[:output] ? File.basename(@opts[:output]).gsub(%r/\.(json|yaml)/, '') : File.basename(__FILE__,'.rb')
35
+ if @spec and @spec['Description']
36
+ desc = @spec['Description']
37
+ end
38
+ vers = '2010-09-09'
39
+ if @spec and @spec['AWSTemplateFormatVersion']
40
+ vers = @spec['AWSTemplateFormatVersion']
41
+ end
42
+ # noinspection RubyStringKeysInHashInspection
43
+ compiled = {
44
+ 'AWSTemplateFormatVersion' => (@opts[:formatversion].nil? ? vers : @opts[:formatversion]),
45
+ 'Description' => (@opts[:description].nil? ? desc : @opts[:description]),
46
+ 'Mappings' => @items['Mappings'],
47
+ 'Parameters' => @items['Parameters'],
48
+ 'Resources' => @items['Resources'],
49
+ 'Outputs' => @items['Outputs'],
50
+ }
51
+
52
+ @logger.step'Validating compiled file...'
53
+
54
+ validate(compiled)
55
+
56
+ output_file = @opts[:output] || 'compiled.json'
57
+ puts
58
+ @logger.step"Writing compiled file to #{output_file}..."
59
+ save(compiled, output_file)
60
+
61
+ puts
62
+ @logger.step'*** Compiled Successfully ***'
63
+ end
64
+
65
+ end
66
+ end
67
+ end
68
+ end
@@ -1,7 +1,7 @@
1
1
  module Aws
2
2
  module Cfn
3
3
  module Compiler
4
- VERSION = "0.3.1"
4
+ VERSION = "0.5.0"
5
5
  end
6
6
  end
7
7
  end
@@ -1,282 +1,4 @@
1
1
  require "aws/cfn/compiler/version"
2
+ require "aws/cfn/compiler/base"
3
+ require "aws/cfn/compiler/main"
2
4
 
3
- require 'json'
4
- require 'ap'
5
- require 'yaml'
6
- require 'slop'
7
-
8
- module Aws
9
- module Cfn
10
- module Compiler
11
- class Main
12
- attr_accessor :items
13
- attr_accessor :opts
14
- attr_accessor :spec
15
-
16
- def initialize
17
- @items = {}
18
- end
19
-
20
- def run
21
-
22
- @opts = Slop.parse(help: true) do
23
- on :d, :directory=, 'The directory to look in', as: String
24
- on :o, :output=, 'The JSON file to output', as: String
25
- on :s, :specification=, 'The specification to use when selecting components. A JSON or YAML file or JSON object', as: String
26
- on :f, :formatversion=, 'The AWS Template format version. Default 2010-09-09', as: String
27
- on :t, :description=, "The AWS Template description. Default: output basename or #{File.basename(__FILE__,'.rb')}", as: String
28
- end
29
-
30
- unless @opts[:directory]
31
- puts @opts
32
- exit
33
- end
34
-
35
- load @opts[:specification]
36
-
37
- desc = @opts[:output] ? File.basename(@opts[:output]).gsub(%r/\.(json|yaml)/, '') : File.basename(__FILE__,'.rb')
38
- if @spec and @spec['Description']
39
- desc = @spec['Description']
40
- end
41
- vers = '2010-09-09'
42
- if @spec and @spec['AWSTemplateFormatVersion']
43
- vers = @spec['AWSTemplateFormatVersion']
44
- end
45
- # noinspection RubyStringKeysInHashInspection
46
- compiled = {
47
- 'AWSTemplateFormatVersion' => (@opts[:formatversion].nil? ? vers : @opts[:formatversion]),
48
- 'Description' => (@opts[:description].nil? ? desc : @opts[:description]),
49
- 'Mappings' => @items['Mappings'],
50
- 'Parameters' => @items['Parameters'],
51
- 'Resources' => @items['Resources'],
52
- 'Outputs' => @items['Outputs'],
53
- }
54
-
55
- puts
56
- puts 'Validating compiled file...'
57
-
58
- validate(compiled)
59
-
60
- output_file = @opts[:output] || 'compiled.json'
61
- puts
62
- puts "Writing compiled file to #{output_file}..."
63
- save(compiled, output_file)
64
-
65
- puts
66
- puts '*** Compiled Successfully ***'
67
- end
68
-
69
- def validate(compiled)
70
- raise 'No Resources!?' unless compiled['Resources']
71
-
72
- # Mappings => Resources
73
- maps = find_maps(compiled) #.select { |a| !(a =~ /^AWS::/) }
74
- rscs = compiled['Resources'].keys
75
- mpgs = compiled['Mappings'].nil? ? [] : compiled['Mappings'].keys
76
- names = rscs+mpgs
77
-
78
- unless (maps-names).empty?
79
- puts '!!! Unknown mappings !!!'
80
- (maps-names).each do |name|
81
- puts " #{name}"
82
- end
83
- abort!
84
- end
85
- puts ' Mappings validated'
86
-
87
- # Parameters => Resources => Outputs
88
- refs = find_refs(compiled).select { |a,_| !(a =~ /^AWS::/) }
89
- prms = compiled['Parameters'].keys rescue []
90
- # outs = compiled['Outputs'].keys rescue []
91
- names = rscs+prms
92
-
93
- unless (refs.keys-names).empty?
94
- puts '!!! Unknown references !!!'
95
- (refs.keys-names).each do |name|
96
- puts " #{name} from #{refs[name][0]}:#{refs[name][1]}"
97
- end
98
- abort!
99
- end
100
- puts ' References validated'
101
- end
102
-
103
- def save(compiled, output_file)
104
- begin
105
- hash = {}
106
- compiled.each do |item,value|
107
- unless value.nil?
108
- if (not value.is_a?(Hash)) or (value.count > 0)
109
- hash[item] = value
110
- end
111
- end
112
- end
113
-
114
- File.open output_file, 'w' do |f|
115
- f.write JSON.pretty_generate(hash, { indent: "\t", space: ' '})
116
- end
117
- puts ' Compiled file written.'
118
- rescue
119
- puts "!!! Could not write compiled file: #{$!}"
120
- abort!
121
- end
122
- end
123
-
124
- def load(spec=nil)
125
- if spec
126
- begin
127
- abs = File.absolute_path(File.expand_path(spec))
128
- unless File.exists?(abs)
129
- abs = File.absolute_path(File.expand_path(File.join(@opts[:directory],spec)))
130
- end
131
- rescue
132
- # pass
133
- end
134
- if File.exists?(abs)
135
- raise "Unsupported specification file type: #{spec}=>#{abs}\n\tSupported types are: json,yaml,jts,yts\n" unless abs =~ /\.(json|ya?ml|jts|yts)\z/i
136
-
137
- puts "Loading specification #{abs}..."
138
- spec = File.read(abs)
139
-
140
- case File.extname(File.basename(abs)).downcase
141
- when /json|jts/
142
- @spec = JSON.parse(spec)
143
- when /yaml|yts/
144
- @spec = YAML.load(spec)
145
- else
146
- raise "Unsupported file type for specification: #{spec}"
147
- end
148
- # @spec = spec
149
- else
150
- raise "Unable to open specification: #{abs}"
151
- end
152
- end
153
- %w( Mappings Parameters Resources Outputs ).each do |dir|
154
- load_dir(dir,@spec)
155
- end
156
- end
157
-
158
- protected
159
-
160
- def abort!
161
- puts '!!! Aborting !!!'
162
- exit
163
- end
164
-
165
- def find_refs(hash, type='Reference', parent='')
166
- h = {}
167
- newparent = parent
168
- if hash.is_a? Hash
169
- hash.keys.collect do |key|
170
- if %w{Mappings Parameters Resources Outputs}.include? key
171
- type = key#.gsub(/s$/, '')
172
- newparent = key
173
- elsif %w{Mappings Parameters Resources Outputs}.include? parent
174
- newparent = key
175
- end
176
- if %w{Ref}.include? key
177
- h = { hash[key] => [type,newparent] }
178
- elsif 'Fn::GetAtt' == key
179
- h = { hash[key].first => [type,newparent] }
180
- # elsif %w{SourceSecurityGroupName CacheSecurityGroupNames SecurityGroupNames}.include? key
181
- # a = find_refs(hash[key],type,newparent)
182
- # h = merge(h, a, *[type,newparent])
183
- else
184
- a = find_refs(hash[key],type,newparent)
185
- h = merge(h, a, *[type,newparent])
186
- end
187
- end.flatten.compact.uniq
188
- elsif hash.is_a? Array
189
- a = hash.map{|i| find_refs(i,type,newparent) }
190
- h = merge(h, a, type, *[type,newparent])
191
- end
192
- h
193
- end
194
-
195
- def merge(h, a, *type)
196
- if a.is_a? Hash
197
- if a.size > 0
198
- h.merge! a
199
- end
200
- else
201
- a.flatten.compact.uniq.map { |i|
202
- if i.is_a? Hash
203
- if i.size > 0
204
- h.merge! i
205
- h
206
- end
207
- else
208
- h[i] = type
209
- end
210
- }
211
- end
212
- h
213
- end
214
-
215
- def find_maps(hash)
216
- if hash.is_a? Hash
217
- tr = []
218
- hash.keys.collect do |key|
219
- if 'Fn::FindInMap' == key
220
- hash[key].first
221
- else
222
- find_maps(hash[key])
223
- end
224
- end.flatten.compact.uniq
225
- elsif hash.is_a? Array
226
- hash.collect{|a| find_maps(a)}.flatten.compact.uniq
227
- end
228
- end
229
-
230
- def load_dir(dir,spec=nil)
231
- puts "Loading #{dir}..."
232
- raise "No such directory: #{@opts[:directory]}" unless File.directory?(@opts[:directory])
233
- set = []
234
- if File.directory?(File.join(@opts[:directory], dir))
235
- @items[dir] = {}
236
- set = get_file_set(dir)
237
- else
238
- if File.directory?(File.join(@opts[:directory], dir.downcase))
239
- @items[dir] = {}
240
- set = get_file_set(dir.downcase)
241
- end
242
- end
243
- set.collect do |filename|
244
- next unless filename =~ /\.(json|ya?ml)\z/i
245
- if spec and spec.has_key?(dir)
246
- base = File.basename(filename).gsub(%r/\.(rb|yaml)/, '')
247
- next if spec[dir].nil? # Edge case ... explicitly want NONE of these!
248
- next unless spec[dir].include?(base)
249
- puts "\tUsing #{dir}/#{base}"
250
- end
251
- begin
252
- puts " reading #{filename}"
253
- content = File.read(filename)
254
- next if content.size==0
255
-
256
- if filename =~ /\.json\z/i
257
- item = JSON.parse(content)
258
- elsif filename =~ /\.ya?ml\z/i
259
- item = YAML.load(content)
260
- else
261
- next
262
- end
263
- item.keys.each { |key| raise "Duplicate item: #{key}" if @items[dir].has_key?(key) }
264
- @items[dir].merge! item
265
- rescue
266
- puts " !! error: #{$!}"
267
- abort!
268
- end
269
- end
270
- if spec and spec[dir]
271
- raise "Suspect that a #{dir} item was missed! \nRequested: #{spec[dir]}\n Found: #{@items[dir].keys}" unless (@items[dir].keys.count == spec[dir].count)
272
- end
273
- end
274
-
275
- def get_file_set(dir)
276
- Dir[File.join(@opts[:directory], "#{dir}.*")] | Dir[File.join(@opts[:directory], dir.to_s, "**", "*")]
277
- end
278
-
279
- end
280
- end
281
- end
282
- end
metadata CHANGED
@@ -1,119 +1,165 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aws-cfn-compiler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - PKinney
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-06-26 00:00:00.000000000 Z
11
+ date: 2014-06-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: awesome_print
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - ~>
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '1.2'
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: 1.2.0
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - ">="
27
+ - - ~>
25
28
  - !ruby/object:Gem::Version
26
- version: '0'
29
+ version: '1.2'
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 1.2.0
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: psych
29
35
  requirement: !ruby/object:Gem::Requirement
30
36
  requirements:
31
- - - ">="
37
+ - - ! '>='
32
38
  - !ruby/object:Gem::Version
33
39
  version: '0'
34
40
  type: :runtime
35
41
  prerelease: false
36
42
  version_requirements: !ruby/object:Gem::Requirement
37
43
  requirements:
38
- - - ">="
44
+ - - ! '>='
39
45
  - !ruby/object:Gem::Version
40
46
  version: '0'
41
47
  - !ruby/object:Gem::Dependency
42
48
  name: json
43
49
  requirement: !ruby/object:Gem::Requirement
44
50
  requirements:
45
- - - ">="
51
+ - - ! '>='
46
52
  - !ruby/object:Gem::Version
47
53
  version: '0'
48
54
  type: :runtime
49
55
  prerelease: false
50
56
  version_requirements: !ruby/object:Gem::Requirement
51
57
  requirements:
52
- - - ">="
58
+ - - ! '>='
53
59
  - !ruby/object:Gem::Version
54
60
  version: '0'
55
61
  - !ruby/object:Gem::Dependency
56
- name: aws-cfn-dsl
62
+ name: slop
57
63
  requirement: !ruby/object:Gem::Requirement
58
64
  requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: 0.0.1
62
- - - "~>"
65
+ - - ! '>='
63
66
  - !ruby/object:Gem::Version
64
- version: '0.0'
67
+ version: '0'
65
68
  type: :runtime
66
69
  prerelease: false
67
70
  version_requirements: !ruby/object:Gem::Requirement
68
71
  requirements:
69
- - - ">="
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: colorize
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ! '>='
70
80
  - !ruby/object:Gem::Version
71
- version: 0.0.1
72
- - - "~>"
81
+ version: '0'
82
+ type: :runtime
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ! '>='
73
87
  - !ruby/object:Gem::Version
74
- version: '0.0'
88
+ version: '0'
75
89
  - !ruby/object:Gem::Dependency
76
- name: slop
90
+ name: inifile
77
91
  requirement: !ruby/object:Gem::Requirement
78
92
  requirements:
79
- - - ">="
93
+ - - ! '>='
80
94
  - !ruby/object:Gem::Version
81
95
  version: '0'
82
96
  type: :runtime
83
97
  prerelease: false
84
98
  version_requirements: !ruby/object:Gem::Requirement
85
99
  requirements:
86
- - - ">="
100
+ - - ! '>='
87
101
  - !ruby/object:Gem::Version
88
102
  version: '0'
89
103
  - !ruby/object:Gem::Dependency
90
- name: bundler
104
+ name: aws-cfn-dsl
91
105
  requirement: !ruby/object:Gem::Requirement
92
106
  requirements:
93
- - - "~>"
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: 0.2.0
110
+ - - ~>
94
111
  - !ruby/object:Gem::Version
95
- version: '1.6'
96
- type: :development
112
+ version: '0.2'
113
+ type: :runtime
97
114
  prerelease: false
98
115
  version_requirements: !ruby/object:Gem::Requirement
99
116
  requirements:
100
- - - "~>"
117
+ - - ! '>='
101
118
  - !ruby/object:Gem::Version
102
- version: '1.6'
119
+ version: 0.2.0
120
+ - - ~>
121
+ - !ruby/object:Gem::Version
122
+ version: '0.2'
103
123
  - !ruby/object:Gem::Dependency
104
- name: rake
124
+ name: dldinternet-mixlib-cli
105
125
  requirement: !ruby/object:Gem::Requirement
106
126
  requirements:
107
- - - ">="
127
+ - - ! '>='
108
128
  - !ruby/object:Gem::Version
109
- version: '0'
110
- type: :development
129
+ version: 0.1.0
130
+ - - ~>
131
+ - !ruby/object:Gem::Version
132
+ version: '0.1'
133
+ type: :runtime
111
134
  prerelease: false
112
135
  version_requirements: !ruby/object:Gem::Requirement
113
136
  requirements:
114
- - - ">="
137
+ - - ! '>='
115
138
  - !ruby/object:Gem::Version
116
- version: '0'
139
+ version: 0.1.0
140
+ - - ~>
141
+ - !ruby/object:Gem::Version
142
+ version: '0.1'
143
+ - !ruby/object:Gem::Dependency
144
+ name: dldinternet-mixlib-logging
145
+ requirement: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ! '>='
148
+ - !ruby/object:Gem::Version
149
+ version: 0.1.6
150
+ - - ~>
151
+ - !ruby/object:Gem::Version
152
+ version: '0.1'
153
+ type: :runtime
154
+ prerelease: false
155
+ version_requirements: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ! '>='
158
+ - !ruby/object:Gem::Version
159
+ version: 0.1.6
160
+ - - ~>
161
+ - !ruby/object:Gem::Version
162
+ version: '0.1'
117
163
  description: The idea is to create a folder structure to better manage pieces of a
118
164
  CloudFormation deployment. Additionally, writing in JSON is hard, so the compiler
119
165
  takes YAML files as well.
@@ -124,9 +170,9 @@ executables:
124
170
  extensions: []
125
171
  extra_rdoc_files: []
126
172
  files:
127
- - ".gitignore"
128
- - ".ruby-gemset"
129
- - ".ruby-version"
173
+ - .gitignore
174
+ - .ruby-gemset
175
+ - .ruby-version
130
176
  - Gemfile
131
177
  - LICENSE.txt
132
178
  - README.md
@@ -149,6 +195,8 @@ files:
149
195
  - example/resources/subnets.json
150
196
  - example/resources/vpc.json
151
197
  - lib/aws/cfn/compiler.rb
198
+ - lib/aws/cfn/compiler/base.rb
199
+ - lib/aws/cfn/compiler/main.rb
152
200
  - lib/aws/cfn/compiler/version.rb
153
201
  homepage: https://github.com/dldinternet/aws-cfn-compiler
154
202
  licenses:
@@ -160,12 +208,12 @@ require_paths:
160
208
  - lib
161
209
  required_ruby_version: !ruby/object:Gem::Requirement
162
210
  requirements:
163
- - - ">="
211
+ - - ! '>='
164
212
  - !ruby/object:Gem::Version
165
213
  version: '0'
166
214
  required_rubygems_version: !ruby/object:Gem::Requirement
167
215
  requirements:
168
- - - ">="
216
+ - - ! '>='
169
217
  - !ruby/object:Gem::Version
170
218
  version: '0'
171
219
  requirements: []