cfndsl 1.1.0 → 1.3.2

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.
data/README.md CHANGED
@@ -6,25 +6,23 @@ cfndsl
6
6
  [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/cfndsl/cfndsl?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Join the chat at https://gitter.im/cfndsl/cfndsl](https://badges.gitter.im/cfndsl/cfndsl.svg)](https://gitter.im/cfndsl/cfndsl?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
7
7
 
8
8
  [AWS Cloudformation](http://docs.amazonwebservices.com/AWSCloudFormation/latest/UserGuide/GettingStarted.html) templates are an incredibly powerful way to build
9
- sets of resources in Amazon's AWS environment. Unfortunately, because
10
- they are specified in JSON, they are also difficult to write and
11
- maintain:
9
+ sets of resources in Amazon's AWS environment.
10
+ Unfortunately, because they are specified in JSON or YAML, they are also difficult to write and maintain:
12
11
 
13
- * JSON does not allow comments
12
+ * JSON does not allow comments, although CloudFormation now supports YAML
14
13
 
15
- * All structures are JSON, so it is sometimes easy for a person
16
- reading a template to get lost.
14
+ * JSON/YAML templates do not allow re-usable logic
17
15
 
18
- * References and internal functions have a particularly unpleasant syntax.
16
+ * It is easy for a person reading a template to miss the association and dependencies between entries
19
17
 
18
+ * References and internal functions have a particularly unpleasant syntax.
20
19
 
21
- The cnfdsl gem provides a simple DSL that allows you to write equivalent
22
- templates in a more friendly language and generate the correct json
23
- templates by running ruby.
20
+ The cnfdsl gem provides a DSL that allows you to write templates using friendly Ruby logic to generate and validate
21
+ cloudformation templates.
24
22
 
25
23
  ## Getting Started
26
24
 
27
- ruby version > 2.4 is required to run cfndsl, you should look at using rbenv example for installing with rbenv
25
+ ruby version > 2.7 is required to run cfndsl, you should look at using rbenv example for installing with rbenv
28
26
 
29
27
  rbenv exec gem install cfndsl
30
28
 
data/Rakefile CHANGED
@@ -73,6 +73,8 @@ task :bump, :type do |_, args|
73
73
 
74
74
  GitHubChangelogGenerator::RakeTask.new :changelog do |config|
75
75
  config.future_release = version
76
+ config.user = 'cfndsl'
77
+ config.project = 'cfndsl'
76
78
  end
77
79
 
78
80
  puts "Bumping gem from version #{CfnDsl::VERSION} to #{version} as a '#{type.capitalize}' release"
@@ -81,7 +83,7 @@ task :bump, :type do |_, args|
81
83
  Rake::Task[:changelog].execute
82
84
 
83
85
  contents = File.read version_path
84
- updated_contents = contents.gsub(/'[0-9\.]+'/, "'#{version}'")
86
+ updated_contents = contents.gsub(/'[0-9.]+'/, "'#{version}'")
85
87
  File.write(version_path, updated_contents)
86
88
 
87
89
  puts 'Commiting version update'
data/cfndsl.gemspec CHANGED
@@ -17,10 +17,10 @@ Gem::Specification.new do |s|
17
17
  s.test_files = s.files.grep(%r{^(test|spec|features)/})
18
18
  s.require_paths = ['lib']
19
19
  s.bindir = 'exe'
20
- s.required_ruby_version = '~> 2.4'
20
+ s.required_ruby_version = '>= 2.6'
21
21
 
22
22
  s.executables << 'cfndsl'
23
23
 
24
- s.add_development_dependency 'bundler', '~> 2.1'
24
+ s.add_development_dependency 'bundler', '~> 2.2'
25
25
  s.add_runtime_dependency 'hana', '~> 1.3'
26
26
  end
@@ -0,0 +1,23 @@
1
+ {
2
+ "broken": "16.2.0",
3
+ "ResourceTypes": {
4
+ "AWS::IoT::ProvisioningTemplate": {
5
+ "patch": {
6
+ "operations": [
7
+ {
8
+ "op": "replace",
9
+ "path": "/Properties/Tags",
10
+ "value": {
11
+ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iot-provisioningtemplate.html#cfn-iot-provisioningtemplate-tags",
12
+ "UpdateType": "Mutable",
13
+ "Required": false,
14
+ "Type": "List",
15
+ "ItemType": "Tag"
16
+ }
17
+ }
18
+ ]
19
+ }
20
+ }
21
+ }
22
+ }
23
+
@@ -0,0 +1,41 @@
1
+ {
2
+ "broken": "22.0.0",
3
+ "ResourceTypes": {
4
+ "AWS::SageMaker::Device": {
5
+ "patch": {
6
+ "operations": [
7
+ {
8
+ "op": "replace",
9
+ "path": "/Properties/Tags",
10
+ "value": {
11
+ "Type": "List",
12
+ "ItemType": "Tag",
13
+ "Required": false,
14
+ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-device.html#cfn-sagemaker-device-tags",
15
+ "UpdateType": "Mutable"
16
+ }
17
+ }
18
+ ],
19
+ "description": "Fix SageMaker Device to be List of Tag"
20
+ }
21
+ },
22
+ "AWS::SageMaker::DeviceFleet": {
23
+ "patch": {
24
+ "operations": [
25
+ {
26
+ "op": "replace",
27
+ "path": "/Properties/Tags",
28
+ "value": {
29
+ "Type": "List",
30
+ "ItemType": "Tag",
31
+ "Required": false,
32
+ "Documentation": "http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sagemaker-device.html#cfn-sagemaker-device-tags",
33
+ "UpdateType": "Mutable"
34
+ }
35
+ }
36
+ ],
37
+ "description": "Fix SageMaker Device to be List of Tag"
38
+ }
39
+ }
40
+ }
41
+ }
@@ -43,9 +43,10 @@ module CfnDsl
43
43
  params.load_file file
44
44
  when :raw
45
45
  file_parts = file.split('=')
46
- if file_parts[1].downcase == 'true'
46
+ case file_parts[1].downcase
47
+ when 'true'
47
48
  params.set_param(file_parts[0], true)
48
- elsif file_parts[1].downcase == 'false'
49
+ when 'false'
49
50
  params.set_param(file_parts[0], false)
50
51
  else
51
52
  params.set_param(*file.split('='))
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'version'
4
+ require 'fileutils'
4
5
 
5
6
  # Global variables to adjust CfnDsl behavior
6
7
  module CfnDsl
@@ -136,12 +136,13 @@ module CfnDsl
136
136
  check_names
137
137
  hash = {}
138
138
  instance_variables.each do |var|
139
- name = var[1..-1]
139
+ name = var[1..]
140
140
 
141
- if name =~ /^__/
141
+ case name
142
+ when /^__/
142
143
  # if a variable starts with double underscore, strip one off
143
- name = name[1..-1]
144
- elsif name =~ /^_/
144
+ name = name[1..]
145
+ when /^_/
145
146
  # Hide variables that start with single underscore
146
147
  name = nil
147
148
  end
@@ -22,7 +22,7 @@ module CfnDsl
22
22
  # Handles the overall template object
23
23
  # rubocop:disable Metrics/ClassLength
24
24
  class OrchestrationTemplate < JSONable
25
- dsl_attr_setter :AWSTemplateFormatVersion, :Description, :Metadata, :Transform
25
+ dsl_attr_setter :AWSTemplateFormatVersion, :Description, :Metadata, :Transform, :Hooks
26
26
  dsl_content_object :Condition, :Parameter, :Output, :Resource, :Mapping, :Rule
27
27
 
28
28
  GLOBAL_REFS = {
@@ -83,7 +83,6 @@ module CfnDsl
83
83
  resource_name
84
84
  end
85
85
 
86
- # rubocop:disable Metrics/PerceivedComplexity
87
86
  def create_array_property_def(resource, pname, pclass, info)
88
87
  singular_name = CfnDsl::Plurals.singularize pname
89
88
  plural_name = singular_name == pname ? CfnDsl::Plurals.pluralize(pname) : pname
@@ -111,7 +110,6 @@ module CfnDsl
111
110
  # Singular form understands concatenation and Fn::If property
112
111
  create_singular_property_def(resource, pname, pclass, singular_name) if singular_name
113
112
  end
114
- # rubocop:enable Metrics/PerceivedComplexity
115
113
 
116
114
  def create_resource_accessor(accessor, resource, type)
117
115
  class_eval do
@@ -245,7 +243,7 @@ module CfnDsl
245
243
  end
246
244
  end
247
245
 
248
- # rubocop:disable Metrics/PerceivedComplexity
246
+ # rubocop:disable Metrics/CyclomaticComplexity
249
247
  def _check_refs(container_name, method, source_containers)
250
248
  container = instance_variable_get("@#{container_name}s")
251
249
  return [] unless container
@@ -279,7 +277,7 @@ module CfnDsl
279
277
 
280
278
  invalids
281
279
  end
282
- # rubocop:enable Metrics/PerceivedComplexity
280
+ # rubocop:enable Metrics/CyclomaticComplexity
283
281
 
284
282
  def validate
285
283
  errors = check_refs || []
@@ -20,7 +20,7 @@ module CfnDsl
20
20
  @singles = @plurals.invert
21
21
 
22
22
  def pluralize(name)
23
- @plurals.fetch(name.to_s) { |key| key + 's' }
23
+ @plurals.fetch(name.to_s) { |key| "#{key}s" }
24
24
  end
25
25
 
26
26
  def singularize(name)
@@ -29,7 +29,7 @@ module CfnDsl
29
29
  when /List$/
30
30
  key[0..-5]
31
31
  when /ies$/
32
- key[0..-4] + 'y'
32
+ "#{key[0..-4]}y"
33
33
  when /s$/
34
34
  key[0..-2]
35
35
  else
@@ -79,7 +79,7 @@ module CfnDsl
79
79
  # then any existing file is considered sufficient, and 'latest' is the version used for downloading
80
80
  #
81
81
  # @todo Add capability to provide a user spec/patches dir
82
- def specification(name: nil, file:, version: nil)
82
+ def specification(file:, name: nil, version: nil)
83
83
  if name
84
84
  desc 'Update Resource Specification' unless ::Rake.application.last_description
85
85
  task name, [:cfn_spec_version] => file
@@ -218,7 +218,7 @@ module CfnDsl
218
218
  end
219
219
 
220
220
  def verbose
221
- (Rake.verbose? || cfndsl_opts&.fetch(:verbose, false)) && STDERR
221
+ (Rake.verbose? || cfndsl_opts&.fetch(:verbose, false)) && $stderr
222
222
  end
223
223
 
224
224
  def generate(opts)
@@ -238,8 +238,8 @@ module CfnDsl
238
238
  verbose&.puts("Writing to #{type}")
239
239
  end
240
240
 
241
- def outputter(opts)
242
- opts[:output].nil? ? yield(STDOUT) : file_output(opts[:output]) { |f| yield f }
241
+ def outputter(opts, &block)
242
+ opts[:output].nil? ? yield($stdout) : file_output(opts[:output], &block)
243
243
  end
244
244
 
245
245
  def model(filename)
@@ -253,8 +253,8 @@ module CfnDsl
253
253
  cfndsl_opts.fetch(:extras, [])
254
254
  end
255
255
 
256
- def file_output(path)
257
- File.open(File.expand_path(path), 'w') { |f| yield f }
256
+ def file_output(path, &block)
257
+ File.open(File.expand_path(path), 'w', &block)
258
258
  end
259
259
  end
260
260
  # rubocop:enable Metrics/ClassLength
@@ -10,7 +10,7 @@ module RefCheck
10
10
  end
11
11
 
12
12
  # Build up a set of references.
13
- # rubocop:disable Metrics/PerceivedComplexity
13
+ # rubocop:disable Metrics/CyclomaticComplexity
14
14
  def build_references(refs = [], origin = nil, method = :all_refs)
15
15
  if respond_to?(method)
16
16
  send(method).each do |ref|
@@ -30,7 +30,7 @@ module RefCheck
30
30
 
31
31
  refs
32
32
  end
33
- # rubocop:enable Metrics/PerceivedComplexity
33
+ # rubocop:enable Metrics/CyclomaticComplexity
34
34
 
35
35
  def ref_children
36
36
  []
@@ -5,7 +5,7 @@ require_relative 'jsonable'
5
5
  module CfnDsl
6
6
  # Handles Resource objects
7
7
  class ResourceDefinition < JSONable
8
- dsl_attr_setter :Type, :DependsOn, :DeletionPolicy, :Condition, :Metadata
8
+ dsl_attr_setter :Type, :UpdateReplacePolicy, :DeletionPolicy, :Condition, :Metadata
9
9
  dsl_content_object :Property, :UpdatePolicy, :CreationPolicy
10
10
 
11
11
  def add_tag(name, value, propagate = nil)
@@ -16,6 +16,23 @@ module CfnDsl
16
16
  end
17
17
  end
18
18
 
19
+ # DependsOn can be a single value or a list
20
+ def DependsOn(value)
21
+ case @DependsOn
22
+ when nil
23
+ @DependsOn = value
24
+ when Array
25
+ @DependsOn << value
26
+ else
27
+ @DependsOn = [@DependsOn, value]
28
+ end
29
+ if @DependsOn.is_a?(Array)
30
+ @DependsOn.flatten!
31
+ @DependsOn.uniq!
32
+ end
33
+ @DependsOn
34
+ end
35
+
19
36
  def condition_refs
20
37
  [@Condition].flatten.compact.map(&:to_s)
21
38
  end
data/lib/cfndsl/runner.rb CHANGED
@@ -7,7 +7,6 @@ require_relative 'globals'
7
7
 
8
8
  module CfnDsl
9
9
  # Runner class to handle commandline invocation
10
- # rubocop:disable Metrics/ClassLength
11
10
  class Runner
12
11
  # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
13
12
  def self.invoke!
@@ -118,14 +117,14 @@ module CfnDsl
118
117
  end
119
118
 
120
119
  filename = File.expand_path(ARGV[0])
121
- verbose = options[:verbose] && STDERR
120
+ verbose = options[:verbose] && $stderr
122
121
 
123
122
  verbose.puts "Using specification file #{CfnDsl.specification_file}" if verbose
124
123
 
125
124
  require_relative 'cloudformation'
126
125
  model = CfnDsl.eval_file_with_extras(filename, options[:extras], verbose)
127
126
 
128
- output = STDOUT
127
+ output = $stdout
129
128
  if options[:output] != '-'
130
129
  verbose.puts("Writing to #{options[:output]}") if verbose
131
130
  output = File.open(File.expand_path(options[:output]), 'w')
@@ -143,4 +142,3 @@ module CfnDsl
143
142
  end
144
143
  # rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
145
144
  end
146
- # rubocop:enable Metrics/ClassLength
@@ -70,20 +70,18 @@ module CfnDsl
70
70
  next unless %w[ResourceTypes PropertyTypes].include?(top_level_type)
71
71
 
72
72
  patches.each_pair do |property_type_name, patch_details|
73
- begin
74
- applies_to = spec[top_level_type]
75
- unless property_type_name == 'patch'
76
- # Patch applies within a specific property type
77
- applies_to = applies_to[property_type_name]
78
- patch_details = patch_details['patch']
79
- end
80
-
81
- Hana::Patch.new(patch_details['operations']).apply(applies_to) if patch_required?(patch_details)
82
- rescue Hana::Patch::MissingTargetException => e
83
- raise "Failed specification patch #{top_level_type} #{property_type_name} from #{from_file}" if fail_patches
84
-
85
- warn "Ignoring failed specification patch #{top_level_type} #{property_type_name} from #{from_file} - #{e.class.name}:#{e.message}"
73
+ applies_to = spec[top_level_type]
74
+ unless property_type_name == 'patch'
75
+ # Patch applies within a specific property type
76
+ applies_to = applies_to[property_type_name]
77
+ patch_details = patch_details['patch']
86
78
  end
79
+
80
+ Hana::Patch.new(patch_details['operations']).apply(applies_to) if patch_required?(patch_details)
81
+ rescue Hana::Patch::MissingTargetException => e
82
+ raise "Failed specification patch #{top_level_type} #{property_type_name} from #{from_file}" if fail_patches
83
+
84
+ warn "Ignoring failed specification patch #{top_level_type} #{property_type_name} from #{from_file} - #{e.class.name}:#{e.message}"
87
85
  end
88
86
  end
89
87
  end
data/lib/cfndsl/types.rb CHANGED
@@ -16,7 +16,7 @@ module CfnDsl
16
16
  { 'Resources' => resources, 'Types' => types, 'Version' => spec.version, 'File' => spec.file }
17
17
  end
18
18
 
19
- # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/MethodLength
19
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity
20
20
  def self.extract_resources(spec)
21
21
  spec.each_with_object({}) do |(resource_name, resource_info), resources|
22
22
  properties = resource_info['Properties'].each_with_object({}) do |(property_name, property_info), extracted|
@@ -43,7 +43,8 @@ module CfnDsl
43
43
  # resource name for uniqueness and connection
44
44
  property_type = resource_name.split('::').join + property_info['Type']
45
45
  else
46
- warn "could not extract resource type from #{resource_name}"
46
+ warn "could not extract resource type for property #{property_name} from #{resource_name}, assuming Json"
47
+ property_type = 'Json'
47
48
  end
48
49
  extracted[property_name] = property_type
49
50
  extracted
@@ -52,9 +53,9 @@ module CfnDsl
52
53
  resources
53
54
  end
54
55
  end
55
- # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/MethodLength
56
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
56
57
 
57
- # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/MethodLength
58
+ # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/MethodLength
58
59
  def self.extract_types(spec)
59
60
  primitive_types = {
60
61
  'String' => 'String',
@@ -99,6 +100,8 @@ module CfnDsl
99
100
  nested_prop_type =
100
101
  if nested_prop_info['ItemType'] == 'Tag'
101
102
  ['Tag']
103
+ elsif (nested_prop_info['ItemType'] == 'Json') && (nested_prop_info['Type'] == 'List')
104
+ ['Json']
102
105
  else
103
106
  Array(root_resource_name + nested_prop_info['ItemType'])
104
107
  end
@@ -106,7 +109,8 @@ module CfnDsl
106
109
  elsif nested_prop_info['Type']
107
110
  nested_prop_type = root_resource_name + nested_prop_info['Type']
108
111
  else
109
- warn "could not extract property type from #{property_name}"
112
+ warn "could not extract property type for #{nested_prop_name} from #{property_name}, assuming Json"
113
+ nested_prop_type = 'Json'
110
114
  p nested_prop_info
111
115
  end
112
116
  extracted[nested_prop_name] = nested_prop_type
@@ -117,9 +121,9 @@ module CfnDsl
117
121
  types
118
122
  end
119
123
  end
120
- # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/MethodLength
124
+ # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/MethodLength
121
125
 
122
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
126
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity
123
127
  def self.included(type_def)
124
128
  types_list = extract_from_resource_spec
125
129
  type_def.const_set('Types_Internal', types_list)