cfndsl 1.0.4 → 1.2.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.
@@ -14,7 +14,7 @@ module Cfnlego
14
14
  end
15
15
 
16
16
  def render
17
- erb = ERB.new(File.read(TEMPLATE), nil, '-')
17
+ erb = ERB.new(File.read(TEMPLATE), trim_mode: '-')
18
18
  erb.result(binding)
19
19
  end
20
20
  end
@@ -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('='))
@@ -87,7 +87,7 @@ module CfnDsl
87
87
  def FnSub(string, substitutions = nil)
88
88
  raise ArgumentError, 'The first argument passed to Fn::Sub must be a string' unless string.is_a? String
89
89
 
90
- refs = string.scan(FN_SUB_SCANNER).map(&:first)
90
+ refs = string.scan(FN_SUB_SCANNER).map(&:first).map { |r| r.split('.', 2).first }
91
91
 
92
92
  if substitutions
93
93
  raise ArgumentError, 'The second argument passed to Fn::Sub must be a Hash' unless substitutions.is_a? Hash
@@ -169,7 +169,7 @@ module CfnDsl
169
169
  def check_names
170
170
  return if instance_variable_get('@Resources').nil?
171
171
 
172
- instance_variable_get('@Resources').keys.each do |name|
172
+ instance_variable_get('@Resources').each_key do |name|
173
173
  next unless name !~ /\A\p{Alnum}+\z/
174
174
 
175
175
  warn "Resource name: #{name} is invalid"
@@ -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 = {
@@ -245,7 +245,7 @@ module CfnDsl
245
245
  end
246
246
  end
247
247
 
248
- # rubocop:disable Metrics/PerceivedComplexity
248
+ # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
249
249
  def _check_refs(container_name, method, source_containers)
250
250
  container = instance_variable_get("@#{container_name}s")
251
251
  return [] unless container
@@ -279,7 +279,7 @@ module CfnDsl
279
279
 
280
280
  invalids
281
281
  end
282
- # rubocop:enable Metrics/PerceivedComplexity
282
+ # rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
283
283
 
284
284
  def validate
285
285
  errors = check_refs || []
@@ -135,7 +135,8 @@ module CfnDsl
135
135
  # @param [Hash] yaml_opts, other options to pass to YAML generator
136
136
  def yaml(name:, files:, pathmap:, extras: [], **yaml_opts)
137
137
  generate_model_tasks(name: name, files: files, pathmap: pathmap, extras: extras) do |model, f|
138
- YAML.dump(model, f, **yaml_opts)
138
+ simple_model = JSON.parse(model.to_json) # convert model to a simple ruby object to avoid yaml tags
139
+ YAML.dump(simple_model, f, **yaml_opts)
139
140
  end
140
141
  self
141
142
  end
@@ -189,7 +190,7 @@ module CfnDsl
189
190
  files.each do |source|
190
191
  matched_extras = build_extras_filelist(source, extras)
191
192
 
192
- file source.pathmap(pathmap) => [source, :load_spec_types, matched_extras] do |task|
193
+ file source.pathmap(pathmap) => [source, :load_spec_types, *matched_extras] do |task|
193
194
  eval_extras = matched_extras.map { |e| [:yaml, e] } # eval treats yaml and json same
194
195
  puts "Generating Cloudformation for #{source} to #{task.name}"
195
196
  model = CfnDsl.eval_file_with_extras(source, eval_extras, verbose)
@@ -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/PerceivedComplexity, 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/PerceivedComplexity, 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
@@ -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!
@@ -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
@@ -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/PerceivedComplexity, 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|
@@ -54,7 +54,7 @@ module CfnDsl
54
54
  end
55
55
  # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/MethodLength
56
56
 
57
- # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/MethodLength
57
+ # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/MethodLength
58
58
  def self.extract_types(spec)
59
59
  primitive_types = {
60
60
  'String' => 'String',
@@ -117,9 +117,9 @@ module CfnDsl
117
117
  types
118
118
  end
119
119
  end
120
- # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/MethodLength
120
+ # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/MethodLength
121
121
 
122
- # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
122
+ # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/PerceivedComplexity
123
123
  def self.included(type_def)
124
124
  types_list = extract_from_resource_spec
125
125
  type_def.const_set('Types_Internal', types_list)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CfnDsl
4
- VERSION = '1.0.4'
4
+ VERSION = '1.2.0'
5
5
  end
@@ -215,9 +215,10 @@ module DeepMerge
215
215
  knockout_prefix = options[:knockout_prefix] || false
216
216
  di = options[:debug_indent] || ''
217
217
  if knockout_prefix && overwrite_unmergeable
218
- src_tmp = if source.is_a?(String) # remove knockout string from source before overwriting dest
218
+ src_tmp = case source
219
+ when String # remove knockout string from source before overwriting dest
219
220
  source.gsub(/^#{knockout_prefix}/, '')
220
- elsif source.is_a?(Array) # remove all knockout elements before overwriting dest
221
+ when Array # remove all knockout elements before overwriting dest
221
222
  source.delete_if { |ko_item| ko_item.is_a?(String) && ko_item.match(/^#{knockout_prefix}/) }
222
223
  else
223
224
  source
@@ -0,0 +1 @@
1
+ description: 5 machine cluster
@@ -1,2 +1 @@
1
- description: 5 machine cluster
2
1
  machines: 5
@@ -188,6 +188,26 @@ describe CfnDsl::CloudFormationTemplate do
188
188
  end
189
189
  end
190
190
 
191
+ it 'composes DependsOn' do
192
+ spec = self
193
+ subject.Resource('SomeResource') do
194
+ d = DependsOn('X')
195
+ spec.expect(d).to spec.eq('X') # start with a single value, stays a single value
196
+ d = DependsOn(%w[Y Z])
197
+ spec.expect(d).to spec.eq(%w[X Y Z]) # concatenates values
198
+ d = DependsOn('Y') # uniqeness
199
+ spec.expect(d).to spec.eq(%w[X Y Z])
200
+ end
201
+ expect(subject.to_json).to eq('{"AWSTemplateFormatVersion":"2010-09-09","Resources":{"SomeResource":{"DependsOn":["X","Y","Z"]}}}')
202
+ end
203
+
204
+ it 'supports single value DependsOn' do
205
+ subject.Resource('SomeResource') do
206
+ DependsOn(:ADependency)
207
+ end
208
+ expect(subject.to_json).to eq('{"AWSTemplateFormatVersion":"2010-09-09","Resources":{"SomeResource":{"DependsOn":"ADependency"}}}')
209
+ end
210
+
191
211
  context 'built-in functions' do
192
212
  it 'FnGetAtt' do
193
213
  func = subject.FnGetAtt('A', 'B')
@@ -40,10 +40,31 @@ describe CfnDsl::CloudFormationTemplate do
40
40
  expect(subject.validate).to equal(subject)
41
41
  end
42
42
 
43
+ it 'returns self if there are valid Fn::Sub references to other resources' do
44
+ tr = subject.Resource(:TestResource)
45
+ tr.Type('Custom-TestType')
46
+ tr.Property(:AProperty, tr.FnSub('prefix ${TestResource2}suffix'))
47
+
48
+ t2 = subject.Resource('TestResource2')
49
+ t2.Type('Custom-TestType')
50
+ expect(subject.validate).to equal(subject)
51
+ end
52
+
53
+ it 'returns self if there are valid Fn::Sub references to other resource attributes' do
54
+ tr = subject.Resource(:TestResource)
55
+ tr.Type('Custom-TestType')
56
+ tr.Property(:AProperty, tr.FnSub('prefix ${TestResource2.AnAttribute}suffix'))
57
+
58
+ t2 = subject.Resource('TestResource2')
59
+ t2.Type('Custom-TestType')
60
+ expect(subject.validate).to equal(subject)
61
+ end
62
+
43
63
  it 'returns self if there are valid DependsOn to other resources' do
44
64
  tr = subject.Resource(:TestResource)
45
65
  tr.Type('Custom-TestType')
46
66
  tr.DependsOn(:TestResource2)
67
+ tr.DependsOn(:TestResource2)
47
68
 
48
69
  t2 = subject.Resource('TestResource2')
49
70
  t2.Type('Custom-TestType')
@@ -119,6 +140,13 @@ describe CfnDsl::CloudFormationTemplate do
119
140
  expect { subject.validate }.to raise_error(CfnDsl::Error, /TestResource.*itself/i)
120
141
  end
121
142
 
143
+ it 'raises CfnDsl::Error if a resourcs references itself in Fn::Sub attribute expression' do
144
+ tr = subject.Resource(:TestResource)
145
+ tr.Type('Custom-TestType')
146
+ tr.Property(:AProperty, tr.FnSub('${TestResource.attr}'))
147
+ expect { subject.validate }.to raise_error(CfnDsl::Error, /TestResource.*itself/i)
148
+ end
149
+
122
150
  it 'raises CfnDsl::Error if there are cyclic DependsOn references' do
123
151
  tr = subject.Resource(:ResourceOne)
124
152
  tr.Type('Custom-TestType')
@@ -211,6 +239,12 @@ describe CfnDsl::CloudFormationTemplate do
211
239
  expect(subject.validate).to equal(subject)
212
240
  end
213
241
 
242
+ it 'returns self if there are valid Fn::Sub references to resource attributes' do
243
+ subject.Resource(:TestResource)
244
+ subject.Output('TestResourceOutput').Value(subject.FnSub('prefix ${TestResource.attr}suffix'))
245
+ expect(subject.validate).to equal(subject)
246
+ end
247
+
214
248
  it 'raises CfnDsl::Error if references a non existent condition' do
215
249
  subject.Output(:TestOutput).Condition('NoCondition')
216
250
  expect { subject.validate }.to raise_error(CfnDsl::Error, /TestOutput.*NoCondition/)
@@ -12,6 +12,7 @@ describe Cfnlego do
12
12
  output << '# http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-eip.html#cfn-ec2-eip-domain'
13
13
  output << "\n InstanceId String # http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-eip.html#cfn-ec2-eip-instanceid"
14
14
  output << "\n PublicIpv4Pool String # http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-eip.html#cfn-ec2-eip-publicipv4pool"
15
+ output << "\n Tags [List] # http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-eip.html#cfn-ec2-eip-tags"
15
16
  output << "\n end\nend\n"
16
17
  expect(template).to eq output
17
18
  end
@@ -102,6 +102,13 @@ shared_examples 'an orchestration template' do
102
102
  expect(plural_value).to eq([{ foo: 'bar' }])
103
103
  end
104
104
 
105
+ it 'allows array returning function for otherwise array value when singular name == plural name' do
106
+ security_group = described_class.type_module.const_get('AWS_EC2_SecurityGroup').new
107
+ security_group.Property('SecurityGroupIngress', security_group.FnFindInMap('x', 'y', 'z'))
108
+ plural_value = security_group.instance_variable_get('@Properties')['SecurityGroupIngress'].value
109
+ expect(plural_value.to_json).to eql('{"Fn::FindInMap":["x","y","z"]}')
110
+ end
111
+
105
112
  it 'sets the type of each resource correctly' do
106
113
  ec2_instance = subject.EC2_Instance(:foo)
107
114
  expect(ec2_instance.instance_variable_get('@Type')).to eq('AWS::EC2::Instance')
@@ -27,9 +27,10 @@ RSpec.describe 'Type Definitions' do
27
27
  it "#{name} has all property types defined" do
28
28
  type = type['Properties'] if type.is_a?(Hash) && type.key?('Properties')
29
29
  type = type.first if type.is_a?(Array)
30
- if type.is_a?(String)
30
+ case type
31
+ when String
31
32
  expect(types).to have_key(type)
32
- elsif type.is_a?(Hash)
33
+ when Hash
33
34
  type.values.flatten.each { |t| expect(types).to have_key(t) }
34
35
  else
35
36
  raise "A defined type should only be of the form String, Array or Hash, got #{type.class}"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cfndsl
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Steven Jack
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: exe
13
13
  cert_chain: []
14
- date: 2020-03-20 00:00:00.000000000 Z
14
+ date: 2020-07-29 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: bundler
@@ -70,6 +70,8 @@ files:
70
70
  - lib/cfndsl/aws/patches/000_sam.spec.json
71
71
  - lib/cfndsl/aws/patches/100_sam.spec_DeploymentPreference_patch.json
72
72
  - lib/cfndsl/aws/patches/200_Scrutinies_patch.json
73
+ - lib/cfndsl/aws/patches/500_BadTagsv13.0.0_patch.json
74
+ - lib/cfndsl/aws/patches/500_BadTagsv16.2.0_patch.json
73
75
  - lib/cfndsl/aws/patches/500_Cognito_IdentityPoolRoleAttachment_patches.json
74
76
  - lib/cfndsl/aws/patches/500_IoT1Click_patch_PlacementTemplate_DeviceTemplates.json
75
77
  - lib/cfndsl/aws/patches/500_NetworkAclEntry_patch.json
@@ -128,6 +130,7 @@ files:
128
130
  - sample/import.rb
129
131
  - sample/lambda.rb
130
132
  - sample/s3.rb
133
+ - sample/t1-extra.yaml
131
134
  - sample/t1.rb
132
135
  - sample/t1.yaml
133
136
  - sample/vpc_example.rb
@@ -182,15 +185,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
182
185
  requirements:
183
186
  - - "~>"
184
187
  - !ruby/object:Gem::Version
185
- version: '2.3'
188
+ version: '2.4'
186
189
  required_rubygems_version: !ruby/object:Gem::Requirement
187
190
  requirements:
188
191
  - - ">="
189
192
  - !ruby/object:Gem::Version
190
193
  version: '0'
191
194
  requirements: []
192
- rubyforge_project:
193
- rubygems_version: 2.7.7
195
+ rubygems_version: 3.0.8
194
196
  signing_key:
195
197
  specification_version: 4
196
198
  summary: AWS Cloudformation DSL