cfn-model 0.4.29 → 0.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5c60ef54532b89cab5abaf2c3bf9381a7e9e0529f1ee9d1a516941e4ec9e9393
4
- data.tar.gz: e77b5257bf99294ddc85ec54763e475be9a5b9ad4e4895e88cb552a7ea302a99
3
+ metadata.gz: '0912463527f6c2239a20d71bd81ce4d7bd8915d44ffb4dd975d684bc57f36555'
4
+ data.tar.gz: e0943893a06458b5b49b3cc92a73c5570ebae24a1e5b5458b52e456043f401d4
5
5
  SHA512:
6
- metadata.gz: 1713962ad868efd3d92c97e5eff4a9e6300113afa35cf94f7ac5cd00cc7824c98ba8a9206616cd130a0e939d00e9ec44cbb422af2a0449674712c606b17383be
7
- data.tar.gz: e9ba5783d5b37bec3edf86ec7f194c6bbf60bbcb310a87d14ce6e0b03b2b135f8abd565666b8a5ca8403659ca46b17cfb154643d71910ce7e8b04be8bc510e58
6
+ metadata.gz: e56dff8cbc449c62012d268e470500ae4bd946cfb45b8292e86a6ff50375659f76caafe5c9043ccb3727a5d59ca51c90efb80407c3bc065aeb922ed5909b8900
7
+ data.tar.gz: 3547645ee5d45b27a7679c805f9f57492174245f15782a6d6abf59d7f67dec8f8ba22305ec31c3412bda6605cac728f6ea3bb75cc80affb0e52ac998acdcd259
@@ -9,16 +9,39 @@ require 'cfn-model/parser/parser_error'
9
9
  # references yet... in the meantime pile things up here and hope a pattern becomes
10
10
  # clear
11
11
  module References
12
- def self.resolve_value(cfn_model, value)
12
+ def self.unsupported_passthru?(value)
13
+ value.has_key?('Fn::GetAtt') || value.has_key?('Fn::ImportValue') || value.has_key?('Fn::Transform') || value.has_key?('Fn::Cidr')
14
+ end
13
15
 
16
+ def self.resolve_value(cfn_model, value)
14
17
  if value.is_a? Hash
15
18
  if value.has_key?('Ref')
16
19
  resolve_reference(cfn_model, value)
17
20
  elsif value.has_key?('Fn::FindInMap')
18
21
  resolve_map(cfn_model, value)
19
- else
22
+ elsif value.has_key?('Fn::If')
23
+ resolve_if(cfn_model, value)
24
+ elsif value.has_key?('Fn::Sub')
25
+ resolve_sub(cfn_model, value)
26
+ elsif value.has_key?('Fn::GetAZs')
27
+ resolve_getazs(cfn_model, value)
28
+ elsif value.has_key?('Fn::Split')
29
+ resolve_split(cfn_model, value)
30
+ elsif value.has_key?('Fn::Join')
31
+ resolve_join(cfn_model, value)
32
+ elsif value.has_key?('Fn::Base64')
33
+ resolve_base64(cfn_model, value)
34
+ elsif value.has_key?('Fn::Select')
35
+ resolve_select(cfn_model, value)
36
+ elsif unsupported_passthru?(value)
20
37
  value
38
+ else # another mapping
39
+ value.map do |k,v|
40
+ [k, resolve_value(cfn_model, v)]
41
+ end.to_h
21
42
  end
43
+ elsif value.is_a? Array
44
+ value.map { |item| resolve_value(cfn_model, item) }
22
45
  else
23
46
  value
24
47
  end
@@ -96,6 +119,117 @@ module References
96
119
 
97
120
  private
98
121
 
122
+ def self.resolve_sub(cfn_model, expression)
123
+ if expression['Fn::Sub'].is_a? String
124
+ resolve_shorthand_sub(cfn_model, expression)
125
+ elsif expression['Fn::Sub'].is_a? Array
126
+ resolve_longform_sub(cfn_model, expression)
127
+ else
128
+ expression
129
+ end
130
+ end
131
+
132
+ def self.resolve_select(cfn_model, reference)
133
+ index = reference['Fn::Select'][0]
134
+ collection = References.resolve_value(cfn_model, reference['Fn::Select'][1])
135
+ if collection.is_a? Array
136
+ collection[index]
137
+ else
138
+ reference
139
+ end
140
+ end
141
+
142
+ def self.resolve_base64(cfn_model, reference)
143
+ References.resolve_value(cfn_model, reference['Fn::Base64'])
144
+ end
145
+
146
+ def self.resolve_join(cfn_model, reference)
147
+ delimiter = reference['Fn::Join'][0]
148
+ items = References.resolve_value(cfn_model, reference['Fn::Join'][1])
149
+ return reference unless items.is_a?(Array)
150
+ items.join(delimiter)
151
+ end
152
+
153
+ def self.resolve_split(cfn_model, reference)
154
+ delimiter = reference['Fn::Split'][0]
155
+ target_string = References.resolve_value(cfn_model, reference['Fn::Split'][1])
156
+ return reference unless target_string.is_a?(String)
157
+ target_string.split(delimiter)
158
+ end
159
+
160
+ def self.resolve_getazs(cfn_model, reference)
161
+ number_azs = References.resolve_value(cfn_model, { 'Ref' => 'AWS::NumberAZs' })
162
+ region = reference['Fn::GetAZs']
163
+ if region == '' || region == { 'Ref' => 'AWS::Region' }
164
+ region = References.resolve_value(cfn_model, { 'Ref' => 'AWS::Region' })
165
+ end
166
+ (('a'.ord)..('a'.ord+number_azs)).map do |az_number|
167
+ "#{region}#{az_number.chr}"
168
+ end
169
+ end
170
+
171
+ def self.strip_cfn_interpolation(reference)
172
+ reference[2..-2]
173
+ end
174
+
175
+ def self.references_in_sub(string_value)
176
+ # ignore ${!foo} as cfn interprets that as the literal ${foo}
177
+ references = string_value.scan /\${[^!].*?}/
178
+ references.map { |reference| strip_cfn_interpolation(reference) }
179
+ end
180
+
181
+ def self.resolvable_reference?(cfn_model, reference)
182
+ resolved_value = References.resolve_value(cfn_model, {'Ref'=>reference})
183
+ resolved_value != {'Ref'=>reference}
184
+ end
185
+
186
+ def self.resolve_shorthand_sub(cfn_model, expression)
187
+ string_value = expression['Fn::Sub']
188
+ subbed_string_value = string_value
189
+ has_unresolved_references = false
190
+ references_in_sub(string_value).each do |reference|
191
+ if resolvable_reference?(cfn_model, reference)
192
+ subbed_string_value = subbed_string_value.gsub(
193
+ "${#{reference}}",
194
+ References.resolve_value(cfn_model, {'Ref'=>reference})
195
+ )
196
+ end
197
+ end
198
+ subbed_string_value
199
+ end
200
+
201
+ def self.resolve_longform_sub(cfn_model, expression)
202
+ array_value = expression['Fn::Sub']
203
+ subbed_string_value = array_value[0]
204
+ substitution_mapping = array_value[1]
205
+ references_in_sub(subbed_string_value).each do |reference|
206
+ if substitution_mapping.has_key? reference
207
+ if References.resolve_value(cfn_model, substitution_mapping[reference]).is_a?(String)
208
+ subbed_string_value = subbed_string_value.gsub(
209
+ "${#{reference}}",
210
+ References.resolve_value(cfn_model, substitution_mapping[reference])
211
+ )
212
+ end
213
+ elsif resolvable_reference?(cfn_model, reference)
214
+ subbed_string_value = subbed_string_value.gsub(
215
+ "${#{reference}}",
216
+ References.resolve_value(cfn_model, {'Ref'=>reference})
217
+ )
218
+ end
219
+ end
220
+ subbed_string_value
221
+ end
222
+
223
+ def self.resolve_if(cfn_model, expression)
224
+ if_expression = expression['Fn::If']
225
+ condition_name = if_expression[0]
226
+ if cfn_model.conditions[condition_name]
227
+ resolve_value(cfn_model, if_expression[1])
228
+ else
229
+ resolve_value(cfn_model, if_expression[2])
230
+ end
231
+ end
232
+
99
233
  def self.logical_resource_id_from_get_att(attribute_spec, attr_to_retrieve=nil)
100
234
  if attribute_spec.is_a? Array
101
235
  if !attr_to_retrieve || attribute_spec[1] == attr_to_retrieve
@@ -44,6 +44,9 @@ class CfnParser
44
44
 
45
45
  apply_parameter_values(cfn_model, parameter_values_json)
46
46
 
47
+ # pass 2: tie together separate resources only where necessary to make life easier for rule logic
48
+ post_process_resource_model_elements cfn_model
49
+
47
50
  cfn_model
48
51
  end
49
52
 
@@ -87,8 +90,7 @@ class CfnParser
87
90
  transform_hash_into_parameters cfn_hash, cfn_model
88
91
  transform_hash_into_globals cfn_hash, cfn_model
89
92
 
90
- # pass 2: tie together separate resources only where necessary to make life easier for rule logic
91
- post_process_resource_model_elements cfn_model
93
+
92
94
 
93
95
  cfn_model
94
96
  end
@@ -279,7 +281,7 @@ class CfnParser
279
281
 
280
282
  def map_non_aws_resource_name_to_class_name(module_names)
281
283
  # this is a little hacky. we've been ignoring Custom so more for
282
- # backward compat. for Alexa and other transformed resources just jam the whole
284
+ # backward compat. for Alexa and other transformed resources just jam the whole
283
285
  # thing together
284
286
  if module_names.first == 'Custom'
285
287
  first_module_index = 1
@@ -306,7 +308,8 @@ class CfnParser
306
308
  else
307
309
  custom_resource_class_name = map_non_aws_resource_name_to_class_name(module_names)
308
310
  begin
309
- resource_class = Object.const_get custom_resource_class_name
311
+ custom_class = Object.const_get custom_resource_class_name
312
+ resource_class = custom_class if custom_class.is_a?(ModelElement)
310
313
  rescue NameError
311
314
  Object.const_set(custom_resource_class_name, resource_class)
312
315
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'cfn-model/model/iam_role'
4
4
  require 'cfn-model/model/policy'
5
+ require 'cfn-model/model/references'
5
6
  require_relative 'policy_document_parser'
6
7
 
7
8
  class IamGroupParser
@@ -12,8 +13,8 @@ class IamGroupParser
12
13
  next unless policy.has_key? 'PolicyName'
13
14
 
14
15
  new_policy = Policy.new
15
- new_policy.policy_name = policy['PolicyName']
16
- new_policy.policy_document = PolicyDocumentParser.new.parse(policy['PolicyDocument'])
16
+ new_policy.policy_name = References.resolve_value(cfn_model, policy['PolicyName'])
17
+ new_policy.policy_document = PolicyDocumentParser.new.parse(cfn_model, policy['PolicyDocument'])
17
18
  new_policy
18
19
  end.reject { |policy| policy.nil? }
19
20
  iam_group
@@ -2,20 +2,21 @@
2
2
 
3
3
  require 'cfn-model/model/iam_role'
4
4
  require 'cfn-model/model/policy'
5
+ require 'cfn-model/model/references'
5
6
  require_relative 'policy_document_parser'
6
7
 
7
8
  class IamRoleParser
8
9
  def parse(cfn_model:, resource:)
9
10
  iam_role = resource
10
11
 
11
- iam_role.assume_role_policy_document = PolicyDocumentParser.new.parse(iam_role.assumeRolePolicyDocument)
12
+ iam_role.assume_role_policy_document = PolicyDocumentParser.new.parse(cfn_model, iam_role.assumeRolePolicyDocument)
12
13
 
13
14
  iam_role.policy_objects = iam_role.policies.map do |policy|
14
15
  next unless policy.has_key? 'PolicyName'
15
16
 
16
17
  new_policy = Policy.new
17
- new_policy.policy_name = policy['PolicyName']
18
- new_policy.policy_document = PolicyDocumentParser.new.parse(policy['PolicyDocument'])
18
+ new_policy.policy_name = References.resolve_value(cfn_model, policy['PolicyName'])
19
+ new_policy.policy_document = PolicyDocumentParser.new.parse(cfn_model, policy['PolicyDocument'])
19
20
  new_policy
20
21
  end.reject { |policy| policy.nil? }
21
22
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'cfn-model/model/policy_document'
4
4
  require 'cfn-model/model/policy'
5
+ require 'cfn-model/model/references'
5
6
  require_relative 'policy_document_parser'
6
7
 
7
8
  class IamUserParser
@@ -12,8 +13,8 @@ class IamUserParser
12
13
  next unless policy.has_key? 'PolicyName'
13
14
 
14
15
  new_policy = Policy.new
15
- new_policy.policy_name = policy['PolicyName']
16
- new_policy.policy_document = PolicyDocumentParser.new.parse(policy['PolicyDocument'])
16
+ new_policy.policy_name = References.resolve_value(cfn_model, policy['PolicyName'])
17
+ new_policy.policy_document = PolicyDocumentParser.new.parse(cfn_model, policy['PolicyDocument'])
17
18
  new_policy
18
19
  end.reject { |policy| policy.nil? }
19
20
 
@@ -22,8 +23,8 @@ class IamUserParser
22
23
  user_to_group_additions = cfn_model.resources_by_type 'AWS::IAM::UserToGroupAddition'
23
24
  user_to_group_additions.each do |user_to_group_addition|
24
25
 
25
- if user_to_group_addition_has_username(user_to_group_addition.users,iam_user)
26
- iam_user.group_names << user_to_group_addition.groupName
26
+ if user_to_group_addition_has_username(user_to_group_addition.users, iam_user)
27
+ iam_user.group_names << References.resolve_value(cfn_model, user_to_group_addition.groupName)
27
28
 
28
29
  # we need to figure out the story on resolving Refs i think for this to be real
29
30
  end
@@ -9,7 +9,7 @@ class KmsKeyParser
9
9
  kms_key = resource
10
10
 
11
11
  new_policy = Policy.new
12
- new_policy.policy_document = PolicyDocumentParser.new.parse(kms_key.keyPolicy)
12
+ new_policy.policy_document = PolicyDocumentParser.new.parse(cfn_model, kms_key.keyPolicy)
13
13
  kms_key.key_policy = new_policy
14
14
 
15
15
  kms_key
@@ -43,7 +43,8 @@ class ParameterSubstitution
43
43
  'AWS::AccountId' => '111111111111',
44
44
  'AWS::Region' => 'us-east-1',
45
45
  'AWS::StackId' => 'arn:aws:cloudformation:us-east-1:111111111111:stack/stackname/51af3dc0-da77-11e4-872e-1234567db123',
46
- 'AWS::StackName' => 'stackname'
46
+ 'AWS::StackName' => 'stackname',
47
+ 'AWS::NumberAZs' => 2
47
48
  }
48
49
  pseudo_function_defaults.each do |function_name, default_value|
49
50
  parameter = Parameter.new
@@ -1,16 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'cfn-model/model/iam_policy'
4
+ require 'cfn-model/model/references'
5
+
4
6
  require 'cfn-model/model/policy_document'
5
7
 
6
8
  class PolicyDocumentParser
7
- def parse(raw_policy_document)
9
+ def parse(cfn_model, raw_policy_document)
8
10
  policy_document = PolicyDocument.new
9
11
 
10
- policy_document.version = raw_policy_document['Version']
12
+ policy_document.version = References.resolve_value(cfn_model, raw_policy_document['Version'])
11
13
 
12
14
  policy_document.statements = streamline_array(raw_policy_document['Statement']) do |statement|
13
- parse_statement statement
15
+ parse_statement cfn_model, statement
14
16
  end
15
17
 
16
18
  policy_document
@@ -18,17 +20,17 @@ class PolicyDocumentParser
18
20
 
19
21
  private
20
22
 
21
- def parse_statement(raw_statement)
23
+ def parse_statement(cfn_model, raw_statement)
22
24
  statement = Statement.new
23
- statement.effect = raw_statement['Effect']
24
- statement.sid = raw_statement['Sid']
25
- statement.condition = raw_statement['Condition']
26
- statement.actions = streamline_array(raw_statement['Action'])
27
- statement.not_actions = streamline_array(raw_statement['NotAction'])
28
- statement.resources = streamline_array(raw_statement['Resource'])
29
- statement.not_resources = streamline_array(raw_statement['NotResource'])
30
- statement.principal = raw_statement['Principal']
31
- statement.not_principal = raw_statement['NotPrincipal']
25
+ statement.effect = References.resolve_value(cfn_model, raw_statement['Effect'])
26
+ statement.sid = References.resolve_value(cfn_model, raw_statement['Sid'])
27
+ statement.condition = References.resolve_value(cfn_model, raw_statement['Condition'])
28
+ statement.actions = References.resolve_value(cfn_model, streamline_array(raw_statement['Action']))
29
+ statement.not_actions = References.resolve_value(cfn_model, streamline_array(raw_statement['NotAction']))
30
+ statement.resources = References.resolve_value(cfn_model, streamline_array(raw_statement['Resource']))
31
+ statement.not_resources = References.resolve_value(cfn_model, streamline_array(raw_statement['NotResource']))
32
+ statement.principal = References.resolve_value(cfn_model, raw_statement['Principal'])
33
+ statement.not_principal = References.resolve_value(cfn_model, raw_statement['NotPrincipal'])
32
34
  statement
33
35
  end
34
36
 
@@ -38,7 +38,7 @@ class SecurityGroupParser
38
38
  ingress_object = AWS::EC2::SecurityGroupIngress.new cfn_model
39
39
  ingress.each do |k, v|
40
40
  silently_fail do
41
- ingress_object.send("#{initialLower(k)}=", v)
41
+ ingress_object.send("#{initialLower(k)}=", References.resolve_value(cfn_model, v))
42
42
  mapped_at_least_one_attribute = true
43
43
  end
44
44
  end
@@ -59,7 +59,7 @@ class SecurityGroupParser
59
59
  egress.each do |k, v|
60
60
  next if k.match /::/
61
61
  silently_fail do
62
- egress_object.send("#{initialLower(k)}=", v)
62
+ egress_object.send("#{initialLower(k)}=", References.resolve_value(cfn_model, v))
63
63
  mapped_at_least_one_attribute = true
64
64
  end
65
65
 
@@ -6,7 +6,7 @@ require_relative 'policy_document_parser'
6
6
 
7
7
  class WithPolicyDocumentParser
8
8
  def parse(cfn_model:, resource:)
9
- resource.policy_document = PolicyDocumentParser.new.parse(resource.policyDocument)
9
+ resource.policy_document = PolicyDocumentParser.new.parse(cfn_model, resource.policyDocument)
10
10
  resource
11
11
  end
12
12
  end
@@ -112,8 +112,14 @@ class CfnModel
112
112
  resource_name,
113
113
  with_line_numbers)
114
114
 
115
- cfn_hash['Resources'][resource_name] = lambda_function lambda_fn_params
116
-
115
+ cfn_hash['Resources'][resource_name] = lambda_function(
116
+ handler: lambda_fn_params[:handler],
117
+ code_bucket: lambda_fn_params[:code_bucket],
118
+ code_key: lambda_fn_params[:code_key],
119
+ role: lambda_fn_params[:role],
120
+ runtime: lambda_fn_params[:runtime],
121
+ with_line_numbers: lambda_fn_params[:with_line_numbers]
122
+ )
117
123
  unless serverless_function['Properties']['Role']
118
124
  cfn_hash['Resources'][resource_name + 'Role'] = function_role(serverless_function,
119
125
  resource_name,
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cfn-model
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.29
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Kascic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-01 00:00:00.000000000 Z
11
+ date: 2020-10-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop
@@ -165,7 +165,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
165
165
  - !ruby/object:Gem::Version
166
166
  version: '0'
167
167
  requirements: []
168
- rubygems_version: 3.1.2
168
+ rubygems_version: 3.1.4
169
169
  signing_key:
170
170
  specification_version: 4
171
171
  summary: cfn-model