Class: Cfhighlander::Util::CloudFormation
- Inherits:
-
Object
- Object
- Cfhighlander::Util::CloudFormation
- Defined in:
- lib/util/cloudformation.util.rb
Class Method Summary collapse
- .collect_output_values(template) ⇒ Object
- .collect_replacements(component, template, output_values) ⇒ Object
- .find_outval_refs(tree, outval_refs = []) ⇒ Object
- .flatten_key_names(component, template) ⇒ Object
- .flatten_namespace(element_type, component, template) ⇒ Object
- .flattenCloudformation(args = {}) ⇒ Object
- .inline_elements(element_name, component, template) ⇒ Object
- .inline_resources(component, template) ⇒ Object
-
.node_replace(tree, search, replacement) ⇒ Object
if hash is treated as collection of tree structures where each key in Hash is root of the tree and value is subtree replace hash subtree with another subtree.
- .process_replacements(component, template, component_replacements) ⇒ Object
- .remove_inlined_component_stacks(component, template) ⇒ Object
-
.rename_condition(tree, search, replacement) ⇒ Object
rename cloudformation condition in cfn model.
-
.rename_mapping(tree, search, replacement) ⇒ Object
rename cloudformation mapping in cfn model.
-
.rename_resource(tree, search, replacement) ⇒ Object
rename cloudformation resource in model.
-
.value_replace(tree, search, replacement) ⇒ Object
Replace single value in tree structure (represented) either as Hash or Array with another value.
Class Method Details
.collect_output_values(template) ⇒ Object
328 329 330 331 332 333 334 335 336 337 338 339 |
# File 'lib/util/cloudformation.util.rb', line 328 def self.collect_output_values(template) output_vals = {} template.subcomponents.each do |sub_component| # we collect outputs only from inlined components model = sub_component.component_loaded.cfn_model_raw model['Outputs'].each do |name, value| output_vals[sub_component.component_loaded.name] = {} unless output_vals.key? sub_component.component_loaded.name output_vals[sub_component.component_loaded.name][name] = value['Value'] end if model.key? 'Outputs' end return output_vals end |
.collect_replacements(component, template, output_values) ⇒ Object
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'lib/util/cloudformation.util.rb', line 240 def self.collect_replacements(component, template, output_values) replacements = {} # collect replacements for inlined components template.subcomponents.each do |sub_component| next unless sub_component.inlined component_loaded = sub_component.component_loaded replacements[component_loaded.name] = [] sub_stack_def = component.cfn_model_raw['Resources'][sub_component.cfn_name] next unless sub_stack_def['Properties'].key? 'Parameters' params = sub_stack_def['Properties']['Parameters'] params.each do |param_name, param_value| # if param value is hash, we may find output values # these should be replaced with inlined values if param_value.is_a? Hash outval_refs = find_outval_refs(param_value) outval_refs.each do |out_ref| # replacement only takes place if # source component is inlined as well # if source component is not inlined # it's output won't be collected source_sub_component = template.subcomponents.find {|sc| sc.component_loaded.name == out_ref[:component]} # if source component is not inlined we can replacement as-is next unless source_sub_component.inlined search = { 'Fn::GetAtt' => [ out_ref[:component], "Outputs.#{out_ref[:outputName]}" ] } replacement = output_values[out_ref[:component]][out_ref[:outputName]] if param_value == search param_value = replacement else # parameter value may be deeper in the structure, e.g. # member of Fn::If intrinsic function node_replace( param_value, search, replacement ) end if output_values.key? out_ref[:component] end end replacements[component_loaded.name] << { search: { 'Ref' => param_name }, replace: param_value } end end # collect replacements to be performed on parameters of non-inlined components # that are referencing inlined components replacements[component.name] = [] template.subcomponents.each do |sub_component| next if sub_component.inlined sub_stack_def = component.cfn_model_raw['Resources'][sub_component.cfn_name] next unless sub_stack_def['Properties'].key? 'Parameters' params = sub_stack_def['Properties']['Parameters'] params.each do |param_name, param_value| if param_value.is_a? Hash outval_refs = find_outval_refs(param_value) # component is NOT inlined and has out references to components that MAY be inlined outval_refs.each do |out_ref| component_name = out_ref[:component] ref_sub_component = template.subcomponents.find {|sc| sc.name == component_name} if ref_sub_component.nil? raise Cfhighlander::Error, "unable to find outputs from component #{component_name} reference by parameters in component #{sub_component.name}" end if ref_sub_component.inlined # out refs here need to be replaced with actual values replacement = output_values[out_ref[:component]][out_ref[:outputName]] replacements[component.name] << { search: { 'Fn::GetAtt' => [component_name, "Outputs.#{out_ref[:outputName]}"] }, replace: replacement } end end end end end return replacements end |
.find_outval_refs(tree, outval_refs = []) ⇒ Object
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/util/cloudformation.util.rb', line 214 def self.find_outval_refs(tree, outval_refs = []) tree.each do |key, val| # if we have located get att, it may be output value if key == 'Fn::GetAtt' if val.is_a? Array and val.size == 2 if val[1].start_with? 'Outputs.' component = val[0] output = val[1].split('.')[1] outval_refs << { component: component, outputName: output } end end elsif val.is_a? Hash or val.is_a? Array # however we may also find output deeper in the tree # example being FnIf(condition, out1, out2) find_outval_refs(val, outval_refs) end end if tree.is_a? Hash tree.each do |element| find_outval_refs(element, outval_refs) end if tree.is_a? Array return outval_refs end |
.flatten_key_names(component, template) ⇒ Object
460 461 462 463 464 465 466 467 468 469 |
# File 'lib/util/cloudformation.util.rb', line 460 def self.flatten_key_names(component, template) flatten_namespace('Conditions', component, template) Debug.debug_dump_cfn(template, 'flat.conditions') flatten_namespace('Mappings', component, template) Debug.debug_dump_cfn(template, 'flat.mappings') flatten_namespace('Resources', component, template) Debug.debug_dump_cfn(template, 'flat.resources') end |
.flatten_namespace(element_type, component, template) ⇒ Object
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/util/cloudformation.util.rb', line 52 def self.flatten_namespace(element_type, component, template) if component.cfn_model_raw.key? element_type keys_taken = component.cfn_model_raw[element_type].keys else keys_taken = [] end template.subcomponents.each do |sub_component| next unless sub_component.inlined model = sub_component.component_loaded.cfn_model_raw model[element_type].keys.each do |key| if keys_taken.include? key candidate = "#{sub_component.component_loaded.name}#{key}" counter = 1 while keys_taken.include? candidate candidate = "#{sub_component.component_loaded.name}#{key}#{counter}" counter = counter + 1 end actual_key = candidate # we need to replace all as # resources can reference conditions # outputs can and will reference resources model[element_type][actual_key] = model[element_type][key] model[element_type].delete(key) case element_type when 'Resources' rename_resource(model, key, actual_key) when 'Mappings' rename_mapping(model, key, actual_key) when 'Conditions' rename_condition(model, key, actual_key) when 'Outputs' # outputs are not effecting anything within the same template end keys_taken << actual_key else keys_taken << key end end if model.key? element_type end end |
.flattenCloudformation(args = {}) ⇒ Object
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/util/cloudformation.util.rb', line 12 def self.flattenCloudformation(args = {}) component = args.fetch(:component) template = component.highlander_dsl # make sure all mappings, resources and conditions # are named uniquely in all of the templates flatten_key_names(component, template) Debug.debug_dump_cfn(template, 'namespace_flat') # collect output values output_values = collect_output_values(template) Debug.debug_dump(output_values, 'outputs') # collect referenced parameters and convert to replacements component_replacements = collect_replacements(component, template, output_values) Debug.debug_dump(component_replacements, 'replacements') # apply replacements in referenced templates process_replacements(component, template, component_replacements) Debug.debug_dump_cfn(template, 'transformed') # inline all of the resources inline_resources(component, template) # remove substacks remove_inlined_component_stacks(component, template) # return inlined model return component.cfn_model_raw end |
.inline_elements(element_name, component, template) ⇒ Object
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/util/cloudformation.util.rb', line 106 def self.inline_elements(element_name, component, template) parent_model = component.cfn_model_raw template.subcomponents.each do |sub_component| next unless sub_component.inlined model = sub_component.component_loaded.cfn_model_raw model[element_name].each do |resource, value| if sub_component.conditional # If the resource already has a conditon we need to combine it with the stack condition if element_name == 'Conditions' value = { "Fn::And" => [{"Condition" => sub_component.condition}, value]} end # Adds the condition to the inlined resource if it doesn't already have a condition if element_name == 'Resources' || element_name == 'Outputs' value['Condition'] = sub_component.condition unless value.has_key?('Condition') end end # effective extraction of child resource into parent # allows for line components to use - or _ in the component name # and still generate valid references safe_resource_name = resource.gsub('-','').gsub('_','') unless element_name == 'Outputs' && resource.end_with?('CfTemplateUrl') parent_model[element_name] = {} unless parent_model.key? element_name parent_model[element_name][safe_resource_name] = value end end if model.key? element_name end end |
.inline_resources(component, template) ⇒ Object
93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/util/cloudformation.util.rb', line 93 def self.inline_resources(component, template) inline_elements('Conditions', component, template) inline_elements('Mappings', component, template) inline_elements('Resources', component, template) # outputs are renamed AFTER all of the other processing # has been done, as outputs are referenced. Only # outputs of inlined components are renamed flatten_namespace('Outputs', component, template) Debug.debug_dump_cfn(template, 'flat.outputs') inline_elements('Outputs', component, template) end |
.node_replace(tree, search, replacement) ⇒ Object
if hash is treated as collection of tree structures where each key in Hash is root of the tree and value is subtree replace hash subtree with another subtree
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 |
# File 'lib/util/cloudformation.util.rb', line 346 def self.node_replace(tree, search, replacement) if tree.is_a? Hash tree.each do |root, subtree| if subtree == search tree[root] = replacement elsif subtree.is_a? Hash or subtree.is_a? Array node_replace(subtree, search, replacement) end end elsif tree.is_a? Array tree.each do |element| if element == search tree[tree.index element] = replacement elsif element.is_a? Hash or element.is_a? Array node_replace(element, search, replacement) end end end end |
.process_replacements(component, template, component_replacements) ⇒ Object
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/util/cloudformation.util.rb', line 134 def self.process_replacements(component, template, component_replacements) # replacement processing is done from least to most dependant component dependency_sorted_subcomponents = template.subcomponents.sort {|sc1, sc2| sc1_params = component.cfn_model_raw['Resources'][sc1.cfn_name]['Properties']['Parameters'] sc2_params = component.cfn_model_raw['Resources'][sc2.cfn_name]['Properties']['Parameters'] outval_refs_sc1 = find_outval_refs(sc1_params) outval_refs_sc2 = find_outval_refs(sc2_params) # if sc1 is dependant on sc2, # sc2 param outval refs should have sc1 output # and vice versa sc1_depends_sc2 = if outval_refs_sc1.find{|oref| oref[:component] == sc2.cfn_name}.nil? then false else true end sc2_depends_sc1 = if outval_refs_sc2.find{|oref| oref[:component] == sc1.cfn_name}.nil? then false else true end if (sc1_depends_sc2 and sc2_depends_sc1) raise StandardError, "Components #{sc1.cfn_name} and #{sc2.cfn_name} have circular dependency!!" end if sc1_depends_sc2 then +1 elsif sc2_depends_sc1 then -1 else 0 end } # process replacements in order from least dependant to # most dependant dependency_sorted_subcomponents.each_with_index do |sub_component, index| next unless sub_component.inlined component_name = sub_component.component_loaded.name if component_replacements.key? component_name if sub_component.component_loaded.cfn_model_raw.key? 'Outputs' outputs_apriori = duplicate(sub_component.component_loaded.cfn_model_raw['Outputs']) else outputs_apriori = {} end component_replacements[component_name].each do |replacement| node_replace( sub_component.component_loaded.cfn_model_raw, replacement[:search], replacement[:replace] ) # some of the component outputs may be changed and thus replacements need be updated end iteration_index = 2 outputs_apriori.each do |out_name, out_value| value_after_transform = sub_component.component_loaded.cfn_model_raw['Outputs'][out_name] # value of the output was changed by replacement unless out_value == value_after_transform # for all downstream dependant components propagated_update_index = index + 1 while propagated_update_index < dependency_sorted_subcomponents.size pc_name = dependency_sorted_subcomponents[propagated_update_index].component_loaded.name component_replacements[pc_name].each do |replacement| # replacements for dependant component needs to be updated as well replace = replacement[:replace] if out_value['Value'] == replace replacement[:replace] = value_after_transform['Value'] else node_replace(replacement[:replace], out_value['Value'], value_after_transform['Value']) end end if component_replacements.include? pc_name propagated_update_index += 1 end Debug.debug_dump(component_replacements, "replacements.#{iteration_index}") iteration_index += 1 end end end end # process replacements on component itself component_replacements[component.name].each do |replacement| node_replace(component.cfn_model_raw, replacement[:search], replacement[:replace]) end end |
.remove_inlined_component_stacks(component, template) ⇒ Object
44 45 46 47 48 49 50 |
# File 'lib/util/cloudformation.util.rb', line 44 def self.remove_inlined_component_stacks(component, template) model = component.cfn_model_raw template.subcomponents.each do |sub_component| next unless sub_component.inlined model['Resources'].delete(sub_component.cfn_name) end end |
.rename_condition(tree, search, replacement) ⇒ Object
rename cloudformation condition in cfn model
409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 |
# File 'lib/util/cloudformation.util.rb', line 409 def self.rename_condition(tree, search, replacement) # conditions can be referenced by Fn::If and Condition => cond tree.keys.each do |k| v = tree[k] if k == 'Fn::If' and v[0] == search tree[k] = [replacement, v[1], v[2]] end if k == 'Condition' and v == search tree[k] = replacement end if v.is_a? Array or v.is_a? Hash rename_condition(v, search, replacement) end end if tree.is_a? Hash tree.each do |element| rename_condition(element, search, replacement) end if tree.is_a? Array end |
.rename_mapping(tree, search, replacement) ⇒ Object
rename cloudformation mapping in cfn model
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 |
# File 'lib/util/cloudformation.util.rb', line 390 def self.rename_mapping(tree, search, replacement) tree.keys.each do |k| v = tree[k] if k == 'Fn::FindInMap' and v[0] == search tree[k] = [replacement, v[1], v[2]] end if v.is_a? Array or v.is_a? Hash rename_mapping(v, search, replacement) end end if tree.is_a? Hash tree.each do |element| rename_mapping(element, search, replacement) end if tree.is_a? Array end |
.rename_resource(tree, search, replacement) ⇒ Object
rename cloudformation resource in model
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 |
# File 'lib/util/cloudformation.util.rb', line 367 def self.rename_resource(tree, search, replacement) tree.keys.each do |k| v = tree[k] if k == 'Ref' and v == search tree[k] = replacement end if k == 'Fn::GetAtt' and v[0] == search tree[k] = [replacement, v[1]] end if v.is_a? Array or v.is_a? Hash rename_resource(v, search, replacement) end end if tree.is_a? Hash tree.each do |element| rename_resource(element, search, replacement) end if tree.is_a? Array end |
.value_replace(tree, search, replacement) ⇒ Object
Replace single value in tree structure (represented) either as Hash or Array with another value
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 |
# File 'lib/util/cloudformation.util.rb', line 434 def self.value_replace(tree, search, replacement) if tree.is_a? Hash tree.keys.each do |root| subtree = tree[root] if root == search tree[replacement] = subtree tree.delete(root) end if subtree == search tree[root] = replacement end if subtree.is_a? Hash or subtree.is_a? Array value_replace(subtree, search, replacement) end end elsif tree.is_a? Array tree.each do |element| if element == search tree[tree.index element] = replacement elsif element.is_a? Hash or element.is_a? Array value_replace(element, search, replacement) end end end end |