cfndsl 0.17.5 → 1.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rubocop.yml +4 -4
  4. data/.travis.yml +6 -1
  5. data/CHANGELOG.md +32 -34
  6. data/README.md +68 -21
  7. data/Rakefile +25 -3
  8. data/TODO.md +18 -0
  9. data/UPGRADING.md +22 -0
  10. data/cfndsl.gemspec +3 -3
  11. data/exe/cfndsl +5 -0
  12. data/lib/cfndsl.rb +2 -116
  13. data/lib/cfndsl/aws/cloud_formation_template.rb +8 -1
  14. data/lib/cfndsl/aws/patches/000_sam.spec.json +574 -0
  15. data/lib/cfndsl/aws/patches/100_sam.spec_DeploymentPreference_patch.json +64 -0
  16. data/lib/cfndsl/aws/patches/500_Cognito_IdentityPoolRoleAttachment_patches.json +25 -0
  17. data/lib/cfndsl/aws/patches/500_IoT1Click_patch_PlacementTemplate_DeviceTemplates.json +20 -0
  18. data/lib/cfndsl/aws/patches/500_SAM_Serverless_Function_S3Event_Events_patch.json +16 -0
  19. data/lib/cfndsl/aws/patches/500_SAM_Serverless_Function_S3Location_Version_patch.json +16 -0
  20. data/lib/cfndsl/aws/patches/500_SSM_AssociationName_patch.json +16 -0
  21. data/lib/cfndsl/aws/patches/500_VPCEndpoint_patch.json +17 -0
  22. data/lib/cfndsl/aws/patches/510_ElasticSearch_Domain_patches.json +15 -0
  23. data/lib/cfndsl/aws/patches/600_RefKinds_patch.json +3654 -0
  24. data/lib/cfndsl/aws/patches/700_SAM_Serverless_Function_InlineCode_patch.json +20 -0
  25. data/lib/cfndsl/aws/patches/800_List_types_patch.json +115 -0
  26. data/lib/cfndsl/aws/resource_specification.json +35809 -11627
  27. data/lib/cfndsl/aws/types.rb +3 -3
  28. data/lib/cfndsl/cfnlego.rb +34 -0
  29. data/lib/{cfnlego → cfndsl/cfnlego}/cloudformation.erb +0 -0
  30. data/lib/{cfnlego → cfndsl/cfnlego}/cloudformation.rb +0 -0
  31. data/lib/{cfnlego → cfndsl/cfnlego}/resource.rb +3 -8
  32. data/lib/cfndsl/cloudformation.rb +107 -0
  33. data/lib/cfndsl/conditions.rb +11 -1
  34. data/lib/cfndsl/creation_policy.rb +1 -1
  35. data/lib/cfndsl/deep_merge.rb +4 -0
  36. data/lib/cfndsl/external_parameters.rb +4 -13
  37. data/lib/cfndsl/globals.rb +48 -9
  38. data/lib/cfndsl/jsonable.rb +22 -60
  39. data/lib/cfndsl/mappings.rb +1 -1
  40. data/lib/cfndsl/module.rb +16 -5
  41. data/lib/cfndsl/orchestration_template.rb +185 -83
  42. data/lib/cfndsl/outputs.rb +5 -1
  43. data/lib/cfndsl/parameters.rb +1 -1
  44. data/lib/cfndsl/plurals.rb +12 -1
  45. data/lib/cfndsl/properties.rb +1 -1
  46. data/lib/cfndsl/rake_task.rb +206 -12
  47. data/lib/cfndsl/ref_check.rb +19 -11
  48. data/lib/cfndsl/resources.rb +6 -19
  49. data/lib/cfndsl/rules.rb +1 -1
  50. data/lib/cfndsl/runner.rb +143 -0
  51. data/lib/cfndsl/specification.rb +80 -95
  52. data/lib/cfndsl/types.rb +205 -91
  53. data/lib/cfndsl/update_policy.rb +1 -1
  54. data/lib/cfndsl/version.rb +1 -1
  55. data/sample/autoscale.rb +0 -1
  56. data/sample/autoscale2.rb +0 -1
  57. data/sample/config_service.rb +2 -2
  58. data/sample/t1.rb +1 -1
  59. data/sample/vpc_example.rb +1 -1
  60. data/sample/vpc_with_vpn_example.rb +1 -1
  61. data/spec/aws/list_type_patches_spec.rb +35 -0
  62. data/spec/aws/nested_arrays_spec.rb +155 -3
  63. data/spec/aws/serverless_spec.rb +0 -2
  64. data/spec/cfndsl_spec.rb +94 -78
  65. data/spec/cli_spec.rb +16 -54
  66. data/spec/cloud_formation_template_spec.rb +233 -0
  67. data/spec/condition_spec.rb +24 -0
  68. data/spec/direct_ruby_spec.rb +19 -0
  69. data/spec/external_parameters_spec.rb +2 -15
  70. data/spec/fixtures/condition-assertion.json +1 -0
  71. data/spec/fixtures/test.rb +2 -1
  72. data/spec/generate_spec.rb +4 -2
  73. data/spec/resources_spec.rb +0 -7
  74. data/spec/spec_helper.rb +2 -7
  75. data/spec/support/shared_examples/orchestration_template.rb +15 -2
  76. data/spec/types_definition_spec.rb +3 -6
  77. metadata +52 -23
  78. data/bin/cfndsl +0 -160
  79. data/lib/cfndsl/errors.rb +0 -31
  80. data/lib/cfndsl/os/heat_template.rb +0 -18
  81. data/lib/cfndsl/os/types.rb +0 -14
  82. data/lib/cfndsl/os/types.yaml +0 -2423
  83. data/lib/cfndsl/patches.rb +0 -226
  84. data/lib/cfnlego.rb +0 -44
  85. data/spec/fixtures/heattest.rb +0 -24
  86. data/spec/heat_template_spec.rb +0 -7
@@ -1,28 +1,225 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rake'
4
3
  require 'rake/tasklib'
4
+ require_relative 'globals'
5
+ require_relative 'specification'
5
6
 
6
- require 'cfndsl'
7
+ # Monkey patch Rake
8
+ module Rake
9
+ # Default verbosity to false
10
+ def self.verbose?
11
+ verbose && verbose != FileUtilsExt::DEFAULT
12
+ end
13
+ end
7
14
 
8
15
  module CfnDsl
9
- # Rake Task
16
+ # Rake TaskLib
17
+ # rubocop:disable Metrics/ClassLength
10
18
  class RakeTask < Rake::TaskLib
19
+ # @deprecated pre 1.x rake task generator
11
20
  attr_accessor :cfndsl_opts
12
21
 
22
+ # Creates Cloudformation generation tasks
23
+ #
24
+ # @example
25
+ # directory 'tmp'
26
+ #
27
+ # namespace :samples do
28
+ #
29
+ # CfnDsl::RakeTask.new do |t|
30
+ # t.specification(file: 'tmp/cloudformation_resources.json')
31
+ #
32
+ # desc 'Generate CloudFormation Json'
33
+ # t.json(name: :json, files: ["sample/*.rb"], pathmap: 'tmp/%f.json', pretty: true, extras: FileList.new('sample/*.yaml') )
34
+ #
35
+ # t.yaml(name: :yaml, files: 'sample/t1.rb', pathmap: 'tmp/%f.yaml', extras: '%X.yaml')
36
+ # end
37
+ # end
13
38
  def initialize(name = nil)
39
+ @tasks = []
40
+ @spec_task = nil
41
+
42
+ last_desc = ::Rake.application.last_description
43
+ desc nil
44
+
14
45
  yield self if block_given?
15
46
 
16
- desc 'Generate Cloudformation' unless ::Rake.application.last_description
17
- task(name || :generate) do |_t, _args|
18
- cfndsl_opts[:files].each do |opts|
19
- generate(opts)
20
- end
47
+ if cfndsl_opts
48
+ desc last_desc if last_desc
49
+ task(name || :generate) { |_t, _args| cfndsl_opts[:files].each { |opts| generate(opts) } }
50
+ else
51
+ define_base_tasks(name || :generate, last_desc)
52
+ end
53
+ end
54
+
55
+ # Use a custom specification file
56
+ #
57
+ # This specification file will be used for any generation tasks
58
+ #
59
+ # Creates a file task to download from upstream source, with an optional additional named task that depends on it.
60
+ #
61
+ # The minimum required version can be specified with the version parameter, which can be overriden by invoking
62
+ # rake with the 'cfn_spec_task' argument
63
+ #
64
+ # @example
65
+ # CfnDsl::RakeTask.new() do |t|
66
+ # t.specification(name: :update_spec, file: 'tmp/cloudformation_resources.json', version: '6.3'0)
67
+ # end
68
+ #
69
+ # # rake update_spec
70
+ # # >> Will ensure 'tmp/cloudformation_resources.json' exists and is at least 6.3.0
71
+ # # rake update_spec[latest]
72
+ # # >> Will always try to download the latest available spec
73
+ #
74
+ #
75
+ # @param [String] file the number of the specification file to use
76
+ # @param [Symbol] name A pretty name for the task to update this file from upstream source
77
+ # @param [String] version The minimum version required, and the default version used when downloading
78
+ # if not specified and not overriden by 'cfn_spec_version' rake task argument
79
+ # then any existing file is considered sufficient, and 'latest' is the version used for downloading
80
+ #
81
+ # @todo Add capability to provide a user spec/patches dir
82
+ def specification(name: nil, file:, version: nil)
83
+ if name
84
+ desc 'Update Resource Specification' unless ::Rake.application.last_description
85
+ task name, [:cfn_spec_version] => file
86
+ end
87
+
88
+ @spec_task = file(file, :cfn_spec_version) do |t, args|
89
+ update_specification(t.name, args.with_defaults(cfn_spec_version: version)[:cfn_spec_version])
90
+ end
91
+ @spec_task.define_singleton_method(:needed?) { true } # We always need to check
92
+ self
93
+ end
94
+
95
+ # Convert DSL sources to json
96
+ #
97
+ # Generates file tasks for each of the matching file in the files FileList. Each task is dependant on the source,
98
+ # the specification file, and the required extras files such that if the timestamp of any of these is earlier
99
+ # the target file will be regenerated.
100
+ #
101
+ # Finally task <name> is generated that depends on all the generated targets.
102
+ #
103
+ # @param [Symbol|String] name the name of a task that is dependant on all the files being converted
104
+ # @param [Rake::FileList | Array<String> ] files source file list
105
+ # @param [String] pathmap expression to map source files to target files
106
+ # @param [Rake::Filelist|Array<String>] extras a list of files to load as external parameters
107
+ # Note String values containing '%' are treated as a pathmap from the source
108
+ # The resulting generated FileList is used as a dependency for generation so any entries not
109
+ # containing '*' MUST exist.
110
+ # @param [Boolean] pretty use JSON.pretty_generate
111
+ # @param [Hash] json_opts, other options to pass to JSON generator
112
+ # rubocop:disable Metrics/ParameterLists
113
+ def json(name:, files:, pathmap:, pretty: false, extras: [], **json_opts)
114
+ json_method = pretty ? :pretty_generate : :generate
115
+ generate_model_tasks(name: name, files: files, pathmap: pathmap, extras: extras) do |model, f|
116
+ f.write(JSON.send(json_method, model, **json_opts))
21
117
  end
118
+ self
22
119
  end
23
120
 
121
+ # Convert DSL sources to yaml
122
+ #
123
+ # Generates file tasks for each of the matching file in the files FileList. Each task is dependant on the source,
124
+ # the specification file, and the required extras files such that if the timestamp of any of these is earlier
125
+ # the target file will be regenerated.
126
+ #
127
+ # Finally task <name> is generated that depends on all the generated targets.
128
+ # @param [Symbol|String] name the name of a task that is dependant on all the files being converted
129
+ # @param [Rake::FileList | Array<String> ] files source file list
130
+ # @param [String] pathmap expression to map source files to target files
131
+ # @param [Rake::Filelist|Array<String>] extras a list of files to load as external parameters
132
+ # Note String values containing '%' are treated as a pathmap from the source
133
+ # The resulting generated FileList is used as a dependency for generation so any entries not
134
+ # containing '*' MUST exist.
135
+ # @param [Hash] yaml_opts, other options to pass to YAML generator
136
+ def yaml(name:, files:, pathmap:, extras: [], **yaml_opts)
137
+ generate_model_tasks(name: name, files: files, pathmap: pathmap, extras: extras) do |model, f|
138
+ YAML.dump(model, f, **yaml_opts)
139
+ end
140
+ self
141
+ end
142
+ # rubocop:enable Metrics/ParameterLists
143
+
24
144
  private
25
145
 
146
+ attr_reader :tasks, :spec_task
147
+
148
+ def define_base_tasks(name, description)
149
+ return if tasks.empty?
150
+
151
+ desc description || 'Generate Cloudformation'
152
+ task(name || :generate, [:cfn_spec_version] => tasks)
153
+
154
+ load_spec = task load_spec_types: [spec_task].compact do |_t, args|
155
+ CfnDsl.specification_file = spec_task&.name || CfnDsl::LOCAL_SPEC_FILE
156
+ require_relative 'cloudformation'
157
+ CfnDsl::CloudFormationTemplate.check_types(file: spec_task&.name, version: @updated_version || args[:cfn_spec_version])
158
+ verbose&.puts "Using Cloudformation types generated from #{CfnDsl::CloudFormationTemplate.template_types.values_at('File', 'Version').join(' ')}"
159
+ end
160
+ load_spec.define_singleton_method(:timestamp) { Rake::EARLY } # Prevent this task from causing generation tasks.
161
+
162
+ tasks.unshift(load_spec)
163
+ end
164
+
165
+ def update_specification(file, minimum_version)
166
+ if CfnDsl::Specification.update_required?(version: minimum_version, file: file)
167
+
168
+ safe_update_embedded_spec if file == CfnDsl::LOCAL_SPEC_FILE
169
+
170
+ result = CfnDsl.update_specification_file(file: file, version: minimum_version)
171
+ puts "Specification #{result[:file]} updated to version #{result[:version]}"
172
+ @updated_version = result[:version]
173
+ elsif minimum_version
174
+ verbose&.puts "Specification #{file} is already >= #{minimum_version}"
175
+ end
176
+ end
177
+
178
+ def safe_update_embedded_spec
179
+ return if File.fnmatch?(File.join(File.dirname(File.expand_path(Rake.application.rakefile)), '*'), CfnDsl::LOCAL_SPEC_FILE)
180
+
181
+ raise Error, 'Refusing to update CloudFormation spec embedded in cfndsl gem'
182
+ end
183
+
184
+ def generate_model_tasks(name:, files:, pathmap:, extras:)
185
+ files = Rake::FileList.new(*files) unless files.is_a?(Rake::FileList)
186
+
187
+ tasks << task(name, [:cfn_spec_version] => files.pathmap(pathmap))
188
+
189
+ files.each do |source|
190
+ matched_extras = build_extras_filelist(source, extras)
191
+
192
+ file source.pathmap(pathmap) => [source, :load_spec_types, matched_extras] do |task|
193
+ eval_extras = matched_extras.map { |e| [:yaml, e] } # eval treats yaml and json same
194
+ puts "Generating Cloudformation for #{source} to #{task.name}"
195
+ model = CfnDsl.eval_file_with_extras(source, eval_extras, verbose)
196
+ File.open(task.name, 'w') { |f| yield model, f }
197
+ end
198
+ end
199
+
200
+ self
201
+ end
202
+
203
+ def build_extras_filelist(source, extras)
204
+ extras = [extras] unless extras.respond_to?(:each)
205
+ extras.each_with_object(Rake::FileList.new) do |extra, result|
206
+ case extra
207
+ when Rake::FileList
208
+ result.add(extra)
209
+ when Array
210
+ result.add(*extra)
211
+ when /%/
212
+ result.add(source.pathmap(extra))
213
+ else
214
+ result.add(extra)
215
+ end
216
+ end
217
+ end
218
+
219
+ def verbose
220
+ (Rake.verbose? || cfndsl_opts&.fetch(:verbose, false)) && STDERR
221
+ end
222
+
26
223
  def generate(opts)
27
224
  log(opts)
28
225
  outputter(opts) do |output|
@@ -55,12 +252,9 @@ module CfnDsl
55
252
  cfndsl_opts.fetch(:extras, [])
56
253
  end
57
254
 
58
- def verbose
59
- cfndsl_opts[:verbose] && STDERR
60
- end
61
-
62
255
  def file_output(path)
63
256
  File.open(File.expand_path(path), 'w') { |f| yield f }
64
257
  end
65
258
  end
259
+ # rubocop:enable Metrics/ClassLength
66
260
  end
@@ -3,26 +3,34 @@
3
3
  # This module defines some methods for walking the reference tree
4
4
  # of various objects.
5
5
  module RefCheck
6
- # Build up a set of references.
7
- def build_references(refs)
8
- raise 'Circular reference' if @_visited
6
+ class SelfReference < StandardError
7
+ end
9
8
 
10
- @_visited = true
9
+ class NullReference < StandardError
10
+ end
11
11
 
12
- if respond_to?(:all_refs)
13
- all_refs.each do |ref|
14
- refs[ref.to_s] = 1
12
+ # Build up a set of references.
13
+ # rubocop:disable Metrics/PerceivedComplexity
14
+ def build_references(refs = [], origin = nil, method = :all_refs)
15
+ if respond_to?(method)
16
+ send(method).each do |ref|
17
+ raise SelfReference, "#{origin} references itself at #{to_json}" if origin && ref.to_s == origin
18
+ raise NullReference, "#{origin} contains null value reference at #{to_json}" if origin && ref.nil?
19
+
20
+ refs << ref
15
21
  end
16
22
  end
17
23
 
18
24
  ref_children.each do |elem|
19
- elem.build_references(refs) if elem.respond_to?(:build_references)
20
- end
25
+ # Nulls are not permitted in Cloudformation templates.
26
+ raise NullReference, "#{origin} contains null value reference at #{to_json}" if origin && elem.nil?
21
27
 
22
- @_visited = nil
28
+ elem.build_references(refs, origin, method) if elem.respond_to?(:build_references)
29
+ end
23
30
 
24
31
  refs
25
32
  end
33
+ # rubocop:enable Metrics/PerceivedComplexity
26
34
 
27
35
  def ref_children
28
36
  []
@@ -37,7 +45,7 @@ class Array
37
45
  end
38
46
  end
39
47
 
40
- # Mixin to Array
48
+ # Mixin to Hash
41
49
  class Hash
42
50
  include RefCheck
43
51
  def ref_children
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'cfndsl/jsonable'
4
- require 'cfndsl/properties'
5
- require 'cfndsl/update_policy'
3
+ require_relative 'jsonable'
6
4
 
7
5
  module CfnDsl
8
6
  # Handles Resource objects
@@ -10,10 +8,6 @@ module CfnDsl
10
8
  dsl_attr_setter :Type, :DependsOn, :DeletionPolicy, :Condition, :Metadata
11
9
  dsl_content_object :Property, :UpdatePolicy, :CreationPolicy
12
10
 
13
- def addTag(name, value, propagate = nil)
14
- add_tag(name, value, propagate)
15
- end
16
-
17
11
  def add_tag(name, value, propagate = nil)
18
12
  send(:Tag) do
19
13
  Key name
@@ -22,19 +16,12 @@ module CfnDsl
22
16
  end
23
17
  end
24
18
 
25
- def all_refs
26
- refs = []
27
- if @DependsOn
28
- if @DependsOn.respond_to?(:each)
29
- @DependsOn.each do |dep|
30
- refs.push dep
31
- end
32
- end
33
-
34
- refs.push @DependsOn if @DependsOn.instance_of?(String)
35
- end
19
+ def condition_refs
20
+ [@Condition].flatten.compact.map(&:to_s)
21
+ end
36
22
 
37
- refs
23
+ def all_refs
24
+ [@DependsOn].flatten.compact.map(&:to_s)
38
25
  end
39
26
  end
40
27
  end
data/lib/cfndsl/rules.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'cfndsl/jsonable'
3
+ require_relative 'jsonable'
4
4
 
5
5
  module CfnDsl
6
6
  # Handles Rule objects
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+ require 'json'
5
+ require_relative 'globals'
6
+ # Defer require of other capabilities (particularly loading dynamic Types) until required
7
+
8
+ module CfnDsl
9
+ # Runner class to handle commandline invocation
10
+ class Runner
11
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
12
+ def self.invoke!
13
+ options = {}
14
+
15
+ optparse = OptionParser.new do |opts|
16
+ opts.version = CfnDsl::VERSION
17
+ opts.banner = 'Usage: cfndsl [options] FILE'
18
+
19
+ # Define the options, and what they do
20
+ options[:output] = '-'
21
+ opts.on('-o', '--output FILE', 'Write output to file') do |file|
22
+ options[:output] = file
23
+ end
24
+
25
+ options[:extras] = []
26
+ opts.on('-y', '--yaml FILE', 'Import yaml file as local variables') do |file|
27
+ options[:extras].push([:yaml, File.expand_path(file)])
28
+ end
29
+
30
+ opts.on('-j', '--json FILE', 'Import json file as local variables') do |file|
31
+ options[:extras].push([:json, File.expand_path(file)])
32
+ end
33
+
34
+ opts.on('-p', '--pretty', 'Pretty-format output JSON') do
35
+ options[:pretty] = true
36
+ end
37
+
38
+ opts.on('-f', '--format FORMAT', 'Specify the output format (JSON default)') do |format|
39
+ raise "Format #{format} not supported" unless %w[json yaml].include? format
40
+
41
+ options[:outformat] = format
42
+ end
43
+
44
+ opts.on('-D', '--define "VARIABLE=VALUE"', 'Directly set local VARIABLE as VALUE') do |file|
45
+ options[:extras].push([:raw, file])
46
+ end
47
+
48
+ options[:verbose] = false
49
+ opts.on('-v', '--verbose', 'Turn on verbose ouptut') do
50
+ options[:verbose] = true
51
+ end
52
+
53
+ opts.on('-m', '--disable-deep-merge', 'Disable deep merging of yaml') do
54
+ CfnDsl.disable_deep_merge
55
+ end
56
+
57
+ # TODO: Support options to add a spec/patches dir
58
+ opts.on('-s', '--specification-file FILE', 'Location of Cloudformation Resource Specification file') do |file|
59
+ CfnDsl.specification_file File.expand_path(file)
60
+ end
61
+
62
+ opts.on('-u', '--update-specification [VERSION]', 'Update the Resource Specification file to latest, or specific version') do |file|
63
+ options[:spec_version] = file || 'latest'
64
+ options[:update_spec] = true
65
+ end
66
+
67
+ opts.on('-g', '--generate RESOURCE_TYPE,RESOURCE_LOGICAL_NAME', 'Add resource type and logical name') do |r|
68
+ options[:lego] = true
69
+ options[:resources] << r
70
+ end
71
+
72
+ opts.on('-a', '--assetversion', 'Print out the specification version') do
73
+ options[:assetversion] = true
74
+ end
75
+
76
+ opts.on('-l', '--list', 'List supported resources') do
77
+ require_relative 'cfnlego'
78
+ puts Cfnlego.Resources.sort
79
+ exit
80
+ end
81
+
82
+ # This displays the help screen, all programs are
83
+ # assumed to have this option.
84
+ opts.on('-h', '--help', 'Display this screen') do
85
+ puts opts
86
+ exit
87
+ end
88
+ end
89
+
90
+ optparse.parse!
91
+
92
+ if options[:update_spec]
93
+ warn 'Updating specification file'
94
+ result = CfnDsl.update_specification_file(version: options[:spec_version])
95
+ warn "Specification #{result[:version]} successfully written to #{result[:file]}"
96
+ end
97
+
98
+ if options[:assetversion]
99
+ spec_file = JSON.parse File.read(CfnDsl.specification_file)
100
+ warn spec_file['ResourceSpecificationVersion']
101
+ end
102
+
103
+ if options[:lego]
104
+ require 'cfnlego'
105
+ puts Cfnlego.run(options)
106
+ exit
107
+ end
108
+
109
+ if ARGV.empty?
110
+ if options[:update_spec] || options[:assetversion]
111
+ exit 0
112
+ else
113
+ puts optparse.help
114
+ exit 1
115
+ end
116
+ end
117
+
118
+ filename = File.expand_path(ARGV[0])
119
+ verbose = options[:verbose] && STDERR
120
+
121
+ verbose.puts "Using specification file #{CfnDsl.specification_file}" if verbose
122
+
123
+ require_relative 'cloudformation'
124
+ model = CfnDsl.eval_file_with_extras(filename, options[:extras], verbose)
125
+
126
+ output = STDOUT
127
+ if options[:output] != '-'
128
+ verbose.puts("Writing to #{options[:output]}") if verbose
129
+ output = File.open(File.expand_path(options[:output]), 'w')
130
+ elsif verbose
131
+ verbose.puts('Writing to STDOUT')
132
+ end
133
+
134
+ if options[:outformat] == 'yaml'
135
+ data = model.to_json
136
+ output.puts JSON.parse(data).to_yaml
137
+ else
138
+ output.puts options[:pretty] ? JSON.pretty_generate(model) : model.to_json
139
+ end
140
+ end
141
+ end
142
+ end
143
+ # rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity