cfhighlander 0.6.0.alpha.1538010880 → 0.6.0.alpha.1539120146

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: a1ca60058a558ba23ed08273fa7ec940794476f1fc9228d6077da3521c6d7b19
4
- data.tar.gz: 83b85f5799b9d48263174b5adc2ec5db6a6fde698a21e1b0698c36c1a4091ff7
3
+ metadata.gz: 4a73647965281a89f8bb2bc78e8282e81604f13b5aa06a48ab2ad0f7c21ec78b
4
+ data.tar.gz: 60a0df940c9a7675ec62de5f636144496bd88dad4ddfcaeae1e5c6f6669dca94
5
5
  SHA512:
6
- metadata.gz: 682f0fb7fe00e2ecb5315ec1d5e599591d8a9a8721d55bc40d96fdafbf0fbfadc227b2272aaf20c19802d27fde22c1215a2ef10987fdefb306d42c8ec65ccfe3
7
- data.tar.gz: 553b3e35fe13a125bf633da11486167f4946a5f7ea5cc51be92b9189ffb95ef4d614446e191fbbd5b7648952739337419435858b467cc54efd68c7e72939288d
6
+ metadata.gz: b743ab97b1cc2051d98f048630ee114a2ebd976b3acb14f14d9b989373bcbd0964b10e726330d12b569aa443504f052111bc0ad78febd61a54c8f26b5eb7ea10
7
+ data.tar.gz: 9a5de349f486e6c8cea90a1c7f7672ca178179815f489c16795714684da4dd8d0862a54bee672c083fc0a3d106d3bb5161443e23fb9bc3f06cb0cf8053f61b3c
data/README.md CHANGED
@@ -699,6 +699,46 @@ CfhighlanderTemplate do
699
699
  end
700
700
 
701
701
  ```
702
+ ## Render mode for components
703
+
704
+ Rendering component resources in resulting cloudformation stack is available in 2 modes. These modes
705
+ are controlled using `render` keyword of `Component` DSL statement
706
+
707
+ `Substack` - creates additional substack for cfhighlander component and points to it using [CloudFormation
708
+ Stack](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-stack.html) resource type
709
+ This is also default render mode - if no render mode is specified `Substack` will be used
710
+
711
+ `Inline` - places all defined resources from inner component in outer component cloudformation template. Resources,
712
+ Outputs, Conditions, Parameters and Mappings are all inlined - please note that some of the template elements may be renamed in this
713
+ process in order to assure unique names.
714
+
715
+ There are some limitations when using inline components - Inlined component parameters, having values as outputs from another component (inlined or not)
716
+ can't be referenced in component conditions. However, conditions referencing mapping values or parameters passed as mapping values,
717
+ are allowed.
718
+
719
+ **`SIDE EFFECTS`** Side effect of moving from substack based to fully inlined stack may be revealing some of the implicit dependencies within an environment
720
+
721
+ *Example:* Component A defines Hosted Zone, while component B defines Record Set for given hosted zone. Record set is defined
722
+ by referencing Zone Name (rather than ZoneId), meaning there is no explicit dependency between the resources. When both components
723
+ are rendered as substack, implicit dependency is created if there is at least one output from component A passed as parameter
724
+ to component B. Rendering components inlined removes this implicitly defined dependency, as a consequence stack deletion or creation
725
+ may be halted, as record set is being created/deleted before prior the record set.
726
+
727
+ **`WARNING`** Be aware of [resource, condition, parameter, output and mapping limits](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cloudformation-limits.html) on a single template
728
+ when rendering inner components inlined.
729
+
730
+ **`EXAMPLE`** All of the VPC resources will be rendered in outer component template, while bastion
731
+ will be referenced as substack in example below.
732
+
733
+ ```ruby
734
+ CfhighlanderTemplate do
735
+
736
+ Component template:'vpc@1.5.0', name: 'vpc', render: Inline
737
+ Component template:'bastion@1.2.0', name: 'bastion', render: Substack
738
+
739
+ end
740
+ ```
741
+
702
742
 
703
743
 
704
744
  ## Rendering CloudFormation templates
data/bin/cfhighlander.rb CHANGED
@@ -125,6 +125,7 @@ class HighlanderCli < Thor
125
125
  publisher.publishFiles(compiler.cfn_template_paths + compiler.lambda_src_paths)
126
126
 
127
127
  puts "\n\nUse following url to launch CloudFormation stack\n\n#{publisher.getLaunchStackUrl}\n\n"
128
+ puts "\n\nUse following template url to update the stack\n\n#{publisher.getTemplateUrl}\n\n"
128
129
 
129
130
  end
130
131
 
@@ -5,11 +5,11 @@ def mappings_provider(provider_name, is_legacy = false)
5
5
  return nil if provider_name.nil?
6
6
  provider = nil
7
7
  module_name = is_legacy ? 'Highlander': 'Cfhighlander'
8
- providers = Object.const_get(module_name).const_get('MapProviders')
9
8
  begin
9
+ providers = Object.const_get(module_name).const_get('MapProviders')
10
10
  providers.const_get(provider_name)
11
11
  rescue NameError => e
12
- if e.to_s.include? "uninitialized constant #{module_name}::MapProviders::"
12
+ if e.to_s.include? "uninitialized constant"
13
13
  return mappings_provider(provider_name, true) unless is_legacy
14
14
  return nil
15
15
  end
@@ -11,6 +11,7 @@ require 'net/https'
11
11
  require 'highline/import'
12
12
  require 'zip'
13
13
  require_relative './util/zip.util'
14
+ require_relative './util/cloudformation.util'
14
15
 
15
16
  module Cfhighlander
16
17
 
@@ -140,7 +141,22 @@ module Cfhighlander
140
141
 
141
142
 
142
143
  # grab cfndsl model
144
+
145
+ # 1st pass of cloudformation compiling - does the substacks normally
143
146
  model = evaluateCloudFormation
147
+ @component.set_cfndsl_model model
148
+
149
+ # compile sub-component templates
150
+ @sub_components.each do |sub_component|
151
+ sub_component.compileCloudFormation format
152
+ @cfn_template_paths += sub_component.cfn_template_paths
153
+ @lambda_src_paths += sub_component.lambda_src_paths
154
+ end
155
+
156
+ # 2nd pass will flatten any inlined components
157
+ model = Cfhighlander::Util::CloudFormation.flattenCloudformation(
158
+ component: @component
159
+ )
144
160
 
145
161
  # write resulting cloud formation template
146
162
  if format == 'json'
@@ -155,12 +171,8 @@ module Cfhighlander
155
171
  # `cfndsl #{@cfndsl_compiled_path} -p -f #{format} -o #{output_path} --disable-binding`
156
172
  puts "CloudFormation #{format.upcase} template for #{dsl.name} written to #{output_path}"
157
173
 
158
- # compile sub-component templates
159
- @sub_components.each do |sub_component|
160
- sub_component.compileCloudFormation format
161
- @cfn_template_paths += sub_component.cfn_template_paths
162
- @lambda_src_paths += sub_component.lambda_src_paths
163
- end
174
+
175
+
164
176
 
165
177
  end
166
178
 
@@ -35,6 +35,10 @@ module Cfhighlander
35
35
  return { 'Fn::Split' => [delimiter, source] }
36
36
  end
37
37
 
38
+ def FnJoin(glue, pieces)
39
+ return { 'Fn::Join' => [glue, pieces]}
40
+ end
41
+
38
42
  def FnSelect(index, list)
39
43
  return { 'Fn::Select' => [index, list] }
40
44
  end
@@ -28,13 +28,13 @@ module Cfhighlander
28
28
  :component_config_override,
29
29
  :export_config
30
30
 
31
-
32
31
  attr_reader :cfn_name,
33
32
  :conditional,
34
33
  :parent,
35
34
  :name,
36
35
  :template,
37
- :template_version
36
+ :template_version,
37
+ :inlined
38
38
 
39
39
  def initialize(parent,
40
40
  name,
@@ -45,6 +45,7 @@ module Cfhighlander
45
45
  export_config = {},
46
46
  conditional = false,
47
47
  enabled = true,
48
+ inline = false,
48
49
  distribution_format = 'yaml')
49
50
 
50
51
  @parent = parent
@@ -52,6 +53,7 @@ module Cfhighlander
52
53
  @export_config = export_config
53
54
  @component_sources = component_sources
54
55
  @conditional = conditional
56
+ @inlined = inline
55
57
 
56
58
  template_name = template
57
59
  template_version = 'latest'
@@ -227,7 +229,12 @@ module Cfhighlander
227
229
  # TODO wire mapping parameters outside of component
228
230
  if param.class == Cfhighlander::Dsl::MappingParam
229
231
  puts " mapping parameter"
230
- return self.resolveMappingParamValue(component, sub_component, param)
232
+ mapping_param_value = self.resolveMappingParamValue(component, sub_component, param)
233
+
234
+ # if mapping param is not resolved, e.g. mapping not provided
235
+ # parameters will bubble to parent component if not matched by outputs from
236
+ # other components
237
+ return mapping_param_value unless mapping_param_value.nil?
231
238
  end
232
239
 
233
240
  # rule #2: match output values from other components
@@ -287,11 +294,6 @@ module Cfhighlander
287
294
  key_name: key_name
288
295
  )
289
296
 
290
- if value.nil?
291
- return "'#{param.default_value}'" unless param.default_value.empty?
292
- return "''"
293
- end
294
-
295
297
  return value
296
298
  end
297
299
 
@@ -117,6 +117,7 @@ module Cfhighlander
117
117
  export_config: {},
118
118
  conditional: false,
119
119
  enabled: true,
120
+ render: Cfhighlander::Model::Component::Substack,
120
121
  &block)
121
122
  puts "INFO: Requested subcomponent #{name} with template #{template}"
122
123
  if ((not template_arg.nil?) and template.nil?)
@@ -137,7 +138,8 @@ module Cfhighlander
137
138
  config,
138
139
  export_config,
139
140
  conditional,
140
- enabled
141
+ enabled,
142
+ render == Cfhighlander::Model::Component::Inline
141
143
  )
142
144
  # component.instance_eval(&block) unless block.nil?
143
145
  @subcomponents_exec[name] = block unless block.nil?
@@ -482,9 +484,6 @@ def CfhighlanderTemplate(&block)
482
484
  unless @distribution_prefix.nil?
483
485
  instance.DistributionPrefix(@distribution_prefix)
484
486
  end
485
- unless @distribution_format.nil?
486
- instance.Forma
487
- end
488
487
 
489
488
  # process convention over configuration componentname.config.yaml files
490
489
  @potential_subcomponent_overrides.each do |name, config|
@@ -15,8 +15,9 @@ module Cfhighlander
15
15
  default_locations = [
16
16
  LOCAL_CFHIGHLANDER_CACHE_LOCATION,
17
17
  File.expand_path('components'),
18
- File.expand_path('.')
18
+ File.expand_path('.'),
19
19
  ]
20
+ default_locations << ENV['CFHIGHLANDER_WORKDIR'] if ENV.key? 'CFHIGHLANDER_WORKDIR'
20
21
  default_locations.each do |predefined_path|
21
22
  component_sources.unshift(predefined_path)
22
23
  end
@@ -6,6 +6,9 @@ module Cfhighlander
6
6
 
7
7
  class Component
8
8
 
9
+ Inline = 'inline'
10
+ Substack = 'substack'
11
+
9
12
  attr_accessor :component_dir,
10
13
  :config,
11
14
  :highlander_dsl_path,
@@ -26,12 +29,12 @@ module Cfhighlander
26
29
  :factory,
27
30
  :extended_component,
28
31
  :is_parent_component
29
-
30
- attr_reader :cfn_model,
31
- :outputs,
32
+ attr_reader :outputs,
32
33
  :factory,
33
34
  :extended_component,
34
- :potential_subcomponent_overrides
35
+ :potential_subcomponent_overrides,
36
+ :cfn_model,
37
+ :cfn_model_raw
35
38
 
36
39
 
37
40
  def initialize(template_meta, component_name, factory)
@@ -90,7 +93,7 @@ module Cfhighlander
90
93
  def loadDepandantExt()
91
94
  @highlander_dsl.dependson_components.each do |requirement|
92
95
  requirement.component_loaded.cfndsl_ext_files.each do |file|
93
- cfndsl_ext_files << file
96
+ @cfndsl_ext_files << file
94
97
  end
95
98
  end
96
99
  end
@@ -230,12 +233,17 @@ module Cfhighlander
230
233
  end
231
234
 
232
235
  # evaluates cfndsl with current config
236
+ def set_cfndsl_model(value)
237
+ @cfn_model = value.as_json
238
+ @cfn_model_raw = JSON.parse(@cfn_model.to_json)
239
+ end
233
240
  def eval_cfndsl
234
241
  compiler = Cfhighlander::Compiler::ComponentCompiler.new self
235
242
  # there is no need for processing lambda source code during cloudformation evaluation,
236
243
  # this version never gets published
237
244
  compiler.process_lambdas = false
238
245
  @cfn_model = compiler.evaluateCloudFormation().as_json
246
+ @cfn_model_raw = JSON.parse(@cfn_model.to_json)
239
247
  @outputs = (
240
248
  if @cfn_model.key? 'Outputs'
241
249
  then
@@ -40,12 +40,17 @@ module Cfhighlander
40
40
 
41
41
  end
42
42
 
43
- def getLaunchStackUrl
43
+ def getTemplateUrl
44
44
  template_url = "https://#{@component.highlander_dsl.distribution_bucket}.s3.amazonaws.com/"
45
45
  template_url += @component.highlander_dsl.distribution_prefix + "/"
46
46
  template_url += @component.highlander_dsl.version
47
- region = s3_bucket_region(@component.highlander_dsl.distribution_bucket)
48
47
  template_url += "/#{@component.name}.compiled.yaml"
48
+ return template_url
49
+ end
50
+
51
+ def getLaunchStackUrl
52
+ template_url = getTemplateUrl
53
+ region = s3_bucket_region(@component.highlander_dsl.distribution_bucket)
49
54
  return "https://console.aws.amazon.com/cloudformation/home?region=#{region}#/stacks/create/review?filter=active&templateURL=" +
50
55
  "#{URI::encode(template_url)}&stackName=#{@component.name}"
51
56
  end
@@ -7,7 +7,6 @@ module Cfhighlander
7
7
 
8
8
  module Cloudformation
9
9
 
10
-
11
10
  class Validator
12
11
 
13
12
  def initialize(component)
@@ -0,0 +1,450 @@
1
+ require_relative '../cfhighlander.model.component'
2
+ require_relative './debug.util'
3
+ require 'duplicate'
4
+
5
+ module Cfhighlander
6
+
7
+ module Util
8
+
9
+ class CloudFormation
10
+
11
+ def self.flattenCloudformation(args = {})
12
+
13
+ component = args.fetch(:component)
14
+ template = component.highlander_dsl
15
+
16
+ # make sure all mappings, resources and conditions
17
+ # are named uniquely in all of the templates
18
+ flatten_key_names(component, template)
19
+ Debug.debug_dump_cfn(template, 'namespace_flat')
20
+
21
+ # collect output values
22
+ output_values = collect_output_values(template)
23
+ Debug.debug_dump(output_values, 'outputs')
24
+
25
+ # collect referenced parameters and convert to replacements
26
+ component_replacements = collect_replacements(component, template, output_values)
27
+ Debug.debug_dump(component_replacements, 'replacements')
28
+
29
+ # apply replacements in referenced templates
30
+ process_replacements(component, template, component_replacements)
31
+ Debug.debug_dump_cfn(template, 'transformed')
32
+
33
+ # inline all of the resources
34
+ inline_resources(component, template)
35
+
36
+ # remove substacks
37
+ remove_inlined_component_stacks(component, template)
38
+
39
+ # return inlined model
40
+ return component.cfn_model_raw
41
+ end
42
+
43
+ def self.remove_inlined_component_stacks(component, template)
44
+ model = component.cfn_model_raw
45
+ template.subcomponents.each do |sub_component|
46
+ next unless sub_component.inlined
47
+ model['Resources'].delete(sub_component.component_loaded.name)
48
+ end
49
+ end
50
+
51
+ def self.flatten_namespace(element_type, component, template)
52
+ if component.cfn_model_raw.key? element_type
53
+ keys_taken = component.cfn_model_raw[element_type].keys
54
+ else
55
+ keys_taken = []
56
+ end
57
+ template.subcomponents.each do |sub_component|
58
+ next unless sub_component.inlined
59
+ model = sub_component.component_loaded.cfn_model_raw
60
+
61
+ model[element_type].keys.each do |key|
62
+ if keys_taken.include? key
63
+ candidate = "#{sub_component.component_loaded.name}#{key}"
64
+ counter = 1
65
+ while keys_taken.include? candidate
66
+ candidate = "#{sub_component.component_loaded.name}#{key}#{counter}"
67
+ counter = counter + 1
68
+ end
69
+ actual_key = candidate
70
+ # we need to replace all as
71
+ # resources can reference conditions
72
+ # outputs can and will reference resources
73
+ model[element_type][actual_key] = model[element_type][key]
74
+ model[element_type].delete(key)
75
+ case element_type
76
+ when 'Resources'
77
+ rename_resource(model, key, actual_key)
78
+ when 'Mappings'
79
+ rename_mapping(model, key, actual_key)
80
+ when 'Conditions'
81
+ rename_condition(model, key, actual_key)
82
+ when 'Outputs'
83
+ # outputs are not effecting anything within the same template
84
+ end
85
+ keys_taken << actual_key
86
+ else
87
+ keys_taken << key
88
+ end
89
+ end if model.key? element_type
90
+ end
91
+ end
92
+
93
+ def self.inline_resources(component, template)
94
+ inline_elements('Conditions', component, template)
95
+ inline_elements('Mappings', component, template)
96
+ inline_elements('Resources', component, template)
97
+ inline_elements('Outputs', component, template)
98
+ end
99
+
100
+ def self.inline_elements(element_name, component, template)
101
+ parent_model = component.cfn_model_raw
102
+ template.subcomponents.each do |sub_component|
103
+ next unless sub_component.inlined
104
+ model = sub_component.component_loaded.cfn_model_raw
105
+ model[element_name].each do |resource, value|
106
+ # effective extraction of child resource into parent
107
+ parent_model[element_name] = {} unless parent_model.key? element_name
108
+ parent_model[element_name][resource] = value
109
+ end if model.key? element_name
110
+ end
111
+ end
112
+
113
+ def self.process_replacements(component, template, component_replacements)
114
+
115
+ # replacement processing is done from least to most dependant component
116
+ dependency_sorted_subcomponents = template.subcomponents.sort {|sc1, sc2|
117
+ sc1_params = component.cfn_model_raw['Resources'][sc1.cfn_name]['Properties']['Parameters']
118
+ sc2_params = component.cfn_model_raw['Resources'][sc2.cfn_name]['Properties']['Parameters']
119
+ outval_refs_sc1 = find_outval_refs(sc1_params)
120
+ outval_refs_sc2 = find_outval_refs(sc2_params)
121
+
122
+ # if sc1 is dependant on sc2,
123
+ # sc2 param outval refs should have sc1 output
124
+ # and vice versa
125
+ sc1_depends_sc2 = if outval_refs_sc1.find{|oref| oref[:component] == sc2.cfn_name}.nil? then false else true end
126
+ sc2_depends_sc1 = if outval_refs_sc2.find{|oref| oref[:component] == sc1.cfn_name}.nil? then false else true end
127
+
128
+ if (sc1_depends_sc2 and sc2_depends_sc1)
129
+ raise StandardError, "Components #{sc1.cfn_name} and #{sc2.cfn_name} have circular dependency!!"
130
+ end
131
+ if sc1_depends_sc2 then
132
+ +1
133
+ elsif sc2_depends_sc1 then
134
+ -1
135
+ else
136
+ 0
137
+ end
138
+ }
139
+
140
+ # process replacements in order from least dependant to
141
+ # most dependant
142
+ dependency_sorted_subcomponents.each_with_index do |sub_component, index|
143
+ next unless sub_component.inlined
144
+ component_name = sub_component.component_loaded.name
145
+ if component_replacements.key? component_name
146
+ if sub_component.component_loaded.cfn_model_raw.key? 'Outputs'
147
+ outputs_apriori = duplicate(sub_component.component_loaded.cfn_model_raw['Outputs'])
148
+ else
149
+ outputs_apriori = {}
150
+ end
151
+ component_replacements[component_name].each do |replacement|
152
+ node_replace(
153
+ sub_component.component_loaded.cfn_model_raw,
154
+ replacement[:search],
155
+ replacement[:replace]
156
+ ) # some of the component outputs may be changed and thus replacements need be updated
157
+ end
158
+ iteration_index = 2
159
+ outputs_apriori.each do |out_name, out_value|
160
+ value_after_transform = sub_component.component_loaded.cfn_model_raw['Outputs'][out_name]
161
+ # value of the output was changed by replacement
162
+ unless out_value == value_after_transform
163
+ # for all downstream dependant components
164
+ propagated_update_index = index + 1
165
+ while propagated_update_index < dependency_sorted_subcomponents.size
166
+ pc_name = dependency_sorted_subcomponents[propagated_update_index].component_loaded.name
167
+ component_replacements[pc_name].each do |replacement|
168
+ # replacements for dependant component needs to be updated as well
169
+ replace = replacement[:replace]
170
+
171
+ if out_value['Value'] == replace
172
+ replacement[:replace] = value_after_transform['Value']
173
+ else
174
+ node_replace(replacement[:replace], out_value['Value'], value_after_transform['Value'])
175
+ end
176
+
177
+ end if component_replacements.include? pc_name
178
+ propagated_update_index += 1
179
+ end
180
+ Debug.debug_dump(component_replacements, "replacements.#{iteration_index}")
181
+ iteration_index += 1
182
+ end
183
+ end
184
+ end
185
+ end
186
+
187
+ # process replacements on component itself
188
+ component_replacements[component.name].each do |replacement|
189
+ node_replace(component.cfn_model_raw, replacement[:search], replacement[:replace])
190
+ end
191
+ end
192
+
193
+ def self.find_outval_refs(tree, outval_refs = [])
194
+ tree.each do |key, val|
195
+
196
+ # if we have located get att, it may be output value
197
+ if key == 'Fn::GetAtt'
198
+ if val.is_a? Array and val.size == 2
199
+ if val[1].start_with? 'Outputs.'
200
+ component = val[0]
201
+ output = val[1].split('.')[1]
202
+ outval_refs << { component: component, outputName: output }
203
+ end
204
+ end
205
+ elsif val.is_a? Hash or val.is_a? Array
206
+ # however we may also find output deeper in the tree
207
+ # example being FnIf(condition, out1, out2)
208
+ find_outval_refs(val, outval_refs)
209
+ end
210
+
211
+ end if tree.is_a? Hash
212
+
213
+ tree.each do |element|
214
+ find_outval_refs(element, outval_refs)
215
+ end if tree.is_a? Array
216
+ return outval_refs
217
+ end
218
+
219
+ def self.collect_replacements(component, template, output_values)
220
+ replacements = {}
221
+
222
+ # collect replacements for inlined components
223
+ template.subcomponents.each do |sub_component|
224
+ next unless sub_component.inlined
225
+ component_loaded = sub_component.component_loaded
226
+ replacements[component_loaded.name] = []
227
+ sub_stack_def = component.cfn_model_raw['Resources'][sub_component.cfn_name]
228
+ next unless sub_stack_def['Properties'].key? 'Parameters'
229
+ params = sub_stack_def['Properties']['Parameters']
230
+ params.each do |param_name, param_value|
231
+ # if param value is hash, we may find output values
232
+ # these should be replaced with inlined values
233
+ if param_value.is_a? Hash
234
+ outval_refs = find_outval_refs(param_value)
235
+ outval_refs.each do |out_ref|
236
+ # replacement only takes place if
237
+ # source component is inlined as well
238
+ # if source component is not inlined
239
+ # it's output won't be collected
240
+ source_sub_component = template.subcomponents.find {|sc| sc.component_loaded.name == out_ref[:component]}
241
+
242
+ # if source component is not inlined we can replacement as-is
243
+ next unless source_sub_component.inlined
244
+ search = { 'Fn::GetAtt' => [
245
+ out_ref[:component],
246
+ "Outputs.#{out_ref[:outputName]}"
247
+ ] }
248
+ replacement = output_values[out_ref[:component]][out_ref[:outputName]]
249
+ if param_value == search
250
+ param_value = replacement
251
+ else
252
+ # parameter value may be deeper in the structure, e.g.
253
+ # member of Fn::If intrinsic function
254
+ node_replace(
255
+ param_value,
256
+ search,
257
+ replacement
258
+ )
259
+ end if output_values.key? out_ref[:component]
260
+
261
+ end
262
+ end
263
+ replacements[component_loaded.name] << {
264
+ search: { 'Ref' => param_name },
265
+ replace: param_value
266
+ }
267
+ end
268
+
269
+ end
270
+
271
+ # collect replacements to be performed on parameters of non-inlined components
272
+ # that are referencing inlined components
273
+ replacements[component.name] = []
274
+ template.subcomponents.each do |sub_component|
275
+ next if sub_component.inlined
276
+ sub_stack_def = component.cfn_model_raw['Resources'][sub_component.cfn_name]
277
+ next unless sub_stack_def['Properties'].key? 'Parameters'
278
+ params = sub_stack_def['Properties']['Parameters']
279
+ params.each do |param_name, param_value|
280
+ if param_value.is_a? Hash
281
+ outval_refs = find_outval_refs(param_value)
282
+
283
+ # component is NOT inlined and has out references to components that MAY be inlined
284
+ outval_refs.each do |out_ref|
285
+ component_name = out_ref[:component]
286
+ ref_sub_component = template.subcomponents.find {|sc| sc.name == component_name}
287
+ if ref_sub_component.inlined
288
+ # out refs here need to be replaced with actual values
289
+ replacement = output_values[out_ref[:component]][out_ref[:outputName]]
290
+ replacements[component.name] << {
291
+ search: { 'Fn::GetAtt' => [component_name, "Outputs.#{out_ref[:outputName]}"] },
292
+ replace: replacement
293
+ }
294
+ end
295
+ end
296
+ end
297
+ end
298
+ end
299
+ return replacements
300
+ end
301
+
302
+ def self.collect_output_values(template)
303
+ output_vals = {}
304
+ template.subcomponents.each do |sub_component|
305
+ # we collect outputs only from inlined components
306
+ model = sub_component.component_loaded.cfn_model_raw
307
+ model['Outputs'].each do |name, value|
308
+ output_vals[sub_component.component_loaded.name] = {} unless output_vals.key? sub_component.component_loaded.name
309
+ output_vals[sub_component.component_loaded.name][name] = value['Value']
310
+ end if model.key? 'Outputs'
311
+ end
312
+ return output_vals
313
+ end
314
+
315
+ ## if hash is treated as
316
+ ## collection of tree structures
317
+ ## where each key in Hash is root of the tree
318
+ ## and value is subtree
319
+ ## replace hash subtree with another subtree
320
+ def self.node_replace(tree, search, replacement)
321
+ if tree.is_a? Hash
322
+ tree.each do |root, subtree|
323
+ if subtree == search
324
+ tree[root] = replacement
325
+ elsif subtree.is_a? Hash or subtree.is_a? Array
326
+ node_replace(subtree, search, replacement)
327
+ end
328
+ end
329
+ elsif tree.is_a? Array
330
+ tree.each do |element|
331
+ if element == search
332
+ tree[tree.index element] = replacement
333
+ elsif element.is_a? Hash or element.is_a? Array
334
+ node_replace(element, search, replacement)
335
+ end
336
+ end
337
+ end
338
+ end
339
+
340
+ # rename cloudformation resource in model
341
+ def self.rename_resource(tree, search, replacement)
342
+ tree.keys.each do |k|
343
+ v = tree[k]
344
+ if k == 'Ref' and v == search
345
+ tree[k] = replacement
346
+ end
347
+
348
+ if k == 'Fn::GetAtt' and v[0] == search
349
+ tree[k] = [replacement, v[1]]
350
+ end
351
+
352
+ if v.is_a? Array or v.is_a? Hash
353
+ rename_resource(v, search, replacement)
354
+ end
355
+ end if tree.is_a? Hash
356
+
357
+ tree.each do |element|
358
+ rename_resource(element, search, replacement)
359
+ end if tree.is_a? Array
360
+
361
+ end
362
+
363
+ # rename cloudformation mapping in cfn model
364
+ def self.rename_mapping(tree, search, replacement)
365
+ tree.keys.each do |k|
366
+ v = tree[k]
367
+ if k == 'Fn::FindInMap' and v[0] = search
368
+ tree[k] = [replacement, v[1], v[2]]
369
+ end
370
+
371
+ if v.is_a? Array or v.is_a? Hash
372
+ rename_mapping(v, search, replacement)
373
+ end
374
+ end if tree.is_a? Hash
375
+
376
+ tree.each do |element|
377
+ rename_mapping(element, search, replacement)
378
+ end if tree.is_a? Array
379
+
380
+ end
381
+
382
+ # rename cloudformation condition in cfn model
383
+ def self.rename_condition(tree, search, replacement)
384
+ # conditions can be referenced by Fn::If and Condition => cond
385
+ tree.keys.each do |k|
386
+ v = tree[k]
387
+ if k == 'Fn::If' and v[0] == search
388
+ tree[k] = [replacement, v[1], v[2]]
389
+ end
390
+
391
+ if k == 'Condition' and v == search
392
+ tree[k] = replacement
393
+ end
394
+
395
+ if v.is_a? Array or v.is_a? Hash
396
+ rename_condition(v, search, replacement)
397
+ end
398
+ end if tree.is_a? Hash
399
+
400
+ tree.each do |element|
401
+ rename_condition(element, search, replacement)
402
+ end if tree.is_a? Array
403
+
404
+ end
405
+
406
+ ## Replace single value in tree structure (represented)
407
+ # either as Hash or Array with another value
408
+ def self.value_replace(tree, search, replacement)
409
+ if tree.is_a? Hash
410
+ tree.keys.each do |root|
411
+ subtree = tree[root]
412
+ if root == search
413
+ tree[replacement] = subtree
414
+ tree.delete(root)
415
+ end
416
+ if subtree == search
417
+ tree[root] = replacement
418
+ end
419
+ if subtree.is_a? Hash or subtree.is_a? Array
420
+ value_replace(subtree, search, replacement)
421
+ end
422
+ end
423
+ elsif tree.is_a? Array
424
+ tree.each do |element|
425
+ if element == search
426
+ tree[tree.index element] = replacement
427
+ elsif element.is_a? Hash or element.is_a? Array
428
+ value_replace(element, search, replacement)
429
+ end
430
+ end
431
+ end
432
+ end
433
+
434
+ def self.flatten_key_names(component, template)
435
+ flatten_namespace('Conditions', component, template)
436
+ Debug.debug_dump_cfn(template, 'flat.conditions')
437
+
438
+ flatten_namespace('Mappings', component, template)
439
+ Debug.debug_dump_cfn(template, 'flat.mappings')
440
+
441
+ flatten_namespace('Resources', component, template)
442
+ Debug.debug_dump_cfn(template, 'flat.resources')
443
+
444
+ flatten_namespace('Outputs', component, template)
445
+ Debug.debug_dump_cfn(template, 'flat.outputs')
446
+ end
447
+
448
+ end
449
+ end
450
+ end
@@ -0,0 +1,37 @@
1
+ module Cfhighlander
2
+
3
+ module Util
4
+
5
+ class Debug
6
+
7
+ @@debug_folder_exists = false
8
+
9
+ def self.create_debug_folder
10
+ if ((ENV.key? 'CFHIGHLANDER_DEBUG') and (ENV['CFHIGHLANDER_DEBUG'] == '1'))
11
+ FileUtils.mkdir_p "#{ENV['CFHIGHLANDER_WORKDIR']}/out/debug/"
12
+ @@debug_folder_exists = true
13
+ end unless @@debug_folder_exists
14
+ end
15
+
16
+ def self.debug_dump_cfn(template, step_name)
17
+ create_debug_folder
18
+ if ((ENV.key? 'CFHIGHLANDER_DEBUG') and (ENV['CFHIGHLANDER_DEBUG'] == '1'))
19
+ template.subcomponents.each do |sub_component|
20
+ path = "#{ENV['CFHIGHLANDER_WORKDIR']}/out/debug/#{sub_component.cfn_name}_#{step_name}.yaml"
21
+ File.write(path, sub_component.component_loaded.cfn_model_raw.to_yaml)
22
+ end
23
+ end
24
+ end
25
+
26
+ def self.debug_dump(model, dump_name)
27
+ create_debug_folder
28
+ if ((ENV.key? 'CFHIGHLANDER_DEBUG') and (ENV['CFHIGHLANDER_DEBUG'] == '1'))
29
+ path = "#{ENV['CFHIGHLANDER_WORKDIR']}/out/debug/#{dump_name}.yaml"
30
+ File.write(path, model.to_yaml)
31
+ end
32
+ end
33
+
34
+
35
+ end
36
+ end
37
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cfhighlander
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0.alpha.1538010880
4
+ version: 0.6.0.alpha.1539120146
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nikola Tosic
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2018-09-27 00:00:00.000000000 Z
13
+ date: 2018-10-09 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: highline
@@ -212,6 +212,20 @@ dependencies:
212
212
  - - ">="
213
213
  - !ruby/object:Gem::Version
214
214
  version: 1.5.1
215
+ - !ruby/object:Gem::Dependency
216
+ name: duplicate
217
+ requirement: !ruby/object:Gem::Requirement
218
+ requirements:
219
+ - - "~>"
220
+ - !ruby/object:Gem::Version
221
+ version: '1.1'
222
+ type: :runtime
223
+ prerelease: false
224
+ version_requirements: !ruby/object:Gem::Requirement
225
+ requirements:
226
+ - - "~>"
227
+ - !ruby/object:Gem::Version
228
+ version: '1.1'
215
229
  - !ruby/object:Gem::Dependency
216
230
  name: rspec
217
231
  requirement: !ruby/object:Gem::Requirement
@@ -243,7 +257,6 @@ files:
243
257
  - cfndsl_ext/config/managed_policies.yaml
244
258
  - cfndsl_ext/iam_helper.rb
245
259
  - cfndsl_ext/lambda_helper.rb
246
- - cfndsl_ext/sg.rb
247
260
  - hl_ext/aws_helper.rb
248
261
  - hl_ext/common_helper.rb
249
262
  - hl_ext/mapping_helper.rb
@@ -261,6 +274,8 @@ files:
261
274
  - lib/cfhighlander.model.templatemeta.rb
262
275
  - lib/cfhighlander.publisher.rb
263
276
  - lib/cfhighlander.validator.rb
277
+ - lib/util/cloudformation.util.rb
278
+ - lib/util/debug.util.rb
264
279
  - lib/util/zip.util.rb
265
280
  - templates/cfndsl.component.template.erb
266
281
  homepage: https://github.com/theonestack/cfhighlander/blob/master/README.md
data/cfndsl_ext/sg.rb DELETED
@@ -1,53 +0,0 @@
1
- require 'netaddr'
2
-
3
- def sg_create_rules (x, ip_blocks={})
4
- rules = []
5
- x.each do | group |
6
- group['ips'].each do |ip|
7
- group['rules'].each do |rule|
8
- lookup_ips_for_sg(ip_blocks, ip).each do |cidr|
9
- rules << { IpProtocol: "#{rule['IpProtocol']}", FromPort: "#{rule['FromPort']}", ToPort: "#{rule['ToPort']}", CidrIp: cidr }
10
- end
11
- end
12
- end
13
- end
14
- return rules
15
- end
16
-
17
-
18
- def lookup_ips_for_sg (ips, ip_block_name={})
19
- cidr = []
20
- if ip_block_name == 'stack'
21
- cidr = [FnJoin( "", [ "10.", Ref('StackOctet'), ".", "0.0/16" ] )]
22
- elsif ips.has_key? ip_block_name
23
- ips[ip_block_name].each do |ip|
24
- if (ips.include?(ip) || ip_block_name == 'stack')
25
- cidr += lookup_ips_for_sg(ips, ip) unless ip == ip_block_name
26
- else
27
- if ip == 'stack'
28
- cidr << [FnJoin( "", [ "10.", Ref('StackOctet'), ".", "0.0/16" ] )]
29
- elsif(isCidr(ip))
30
- cidr << ip
31
- else
32
- STDERR.puts("WARN: ip #{ip} is not a valid CIDR. Ignoring IP")
33
- end
34
- end
35
- end
36
- else
37
- if isCidr(ip_block_name)
38
- cidr = [ip_block_name]
39
- else
40
- STDERR.puts("WARN: ip #{ip_block_name} is not a valid CIDR. Ignoring IP")
41
- end
42
- end
43
- cidr
44
- end
45
-
46
- def isCidr(block)
47
- begin
48
- NetAddr::CIDR.create(block)
49
- return block.include?('/')
50
- rescue NetAddr::ValidationError
51
- return false
52
- end
53
- end