cfhighlander 0.2.1.alpha.34 → 0.2.1.alpha.43

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 57345cb7494a630f82ccc0be2eab077f1362a5d5bf3bea22619ad17e43f7297c
4
- data.tar.gz: aefc4bebf4b709dd299c5f7a79c266674a4697f9ef64bd1b87d11ea0c031ec30
3
+ metadata.gz: b0076c7a6d8d8d5dcfe21a9c57601f659014cc64dadfc78c890b78ac98aaee7e
4
+ data.tar.gz: d9688f728ef4edd875f8603b8733c153a95270a1650b0555a706cf9d0f7f5a6b
5
5
  SHA512:
6
- metadata.gz: cea97fa74dcf7bec3d87d559499a674cc1a4546502c7a14f589f6bbfef73dbe74a1f8d2ebb21d7f0ee69438fd35b222590d01f3e35d95e922430ea2d2f09ccc0
7
- data.tar.gz: d16b3eaab44a8a08754768106de95c7d412c8a449902f4ca9279e536422f164c9667deb95f74fcb5c012f4b34e8a2a46bdc435cfe6db9ead84830c89fb3b702b
6
+ metadata.gz: 928880bc75f3bc9665ab84c7bbd8d50fe8e310d520365e065c1c036cbdc8c67195e37059e608e4340dd018943965f322361287a057a3450771905997648960ad
7
+ data.tar.gz: 7ba9c66b2e655044c62c7b6db12d42451e46e5c5239206b879b1951a69472c1fe883b491efbfdcd26fb38a850663abcf7e673150bdf61ec5e5ed85d383c3c490
data/README.md CHANGED
@@ -5,20 +5,23 @@
5
5
  Cfhighlander is DSL processor that enables composition and orchestration of Amazon CloudFormation templates
6
6
  written using [CfnDsl](https://github.com/cfndsl/cfndsl) in an abstract way. It tries to tackle problem of merging multiple templates into master
7
7
  template in an elegant way, so higher degree of template reuse can be achieved. It does so by formalising commonly
8
- used patterns via DSL statements. For an example, passing output of one stack into other stack is achieved using
9
- `OutputParam` highlander DSL statement, rather than wiring this parameters manually in cfndsl templates. For this example to
8
+ used cfndsl template composition patterns via DSL statements. For an example, passing output of one stack into other stack is done
9
+ automatically if parameter and output names are the same,
10
+ rather than wiring them manually in 'master' cfndsl template. For this example to
10
11
  work, parent component will have to pull in both component rendering output values, and component pulling them in
11
- as parameters. It also enables it's user to build component library, where components can be distributed to s3, and
12
- consequentially references to them resolved.
12
+ as parameters. Cfhighlander allows for greater reuse of component templates by allowing references to templates
13
+ via git urls, and s3 urls. [Theonestack](https://github.com/theonestack) has several prebuilt component templates to use,
14
+ with repositories from this org being one of the default sources for searching component templates.
13
15
 
14
- Highlander DSL produces CloudFormation templates in 3 phases
16
+ Highlander DSL produces CloudFormation templates in 4 phases
15
17
 
16
18
  - Processing referenced component's configuration and resolving configuration exports
19
+ - Wiring parameters from components to their inner components
17
20
  - Producing [CfnDsl](https://github.com/cfndsl/cfndsl) templates for all components and subcomponents as intermediary
18
21
  step
19
22
  - Producing resulting CloudFormation templates using configuration and templates generated in two previous phases.
20
23
 
21
- Each phase above is executable as stand-alone through CLI, making development of Highlander templates easier by enabling
24
+ Each phase (aside from parameter wiring) above is executable as stand-alone through CLI, making development of Highlander templates easier by enabling
22
25
  debugging of produced configuration and cfndsl templates.
23
26
 
24
27
 
@@ -169,7 +172,7 @@ This configuration level overrides component's own config file.
169
172
  - Outer component explicit configuration. You can pass `config` named parameter to `Component` statement, such as
170
173
 
171
174
  ```ruby
172
- HighlanderComponent do
175
+ CfhighlanderTemplate do
173
176
 
174
177
  # ...
175
178
  # some dsl code
@@ -257,7 +260,7 @@ Parameters block is used to define CloudFormation template parameters, and metad
257
260
  are wired with outer or sibling components.
258
261
 
259
262
  ```ruby
260
- HighlanderComponent do
263
+ CfhighlanderTemplate do
261
264
  Parameters do
262
265
  ##
263
266
  ## parameter definitions here
@@ -270,81 +273,117 @@ Parameter block supports following parameters
270
273
 
271
274
  #### ComponentParam
272
275
 
273
- `ComponentParam` - Component parameter takes name and default value. It defines component parameter
274
- that is not auto-wired in any way with outer component. This parameter will either use default value, or value
275
- explicitly passed from outer component.
276
+ `ComponentParam` - Component parameter exposes parameter to be wired from outer component. Cfhighlander's
277
+ autowiring mechanism will try and find any stack outputs from other components defined by outer components with name
278
+ matching. If there is no explicit value provided, or autowired from outputs, parameter will be propagated to outer component.
279
+
280
+ Propagated parameter will be prefixed with component name **if it is not defined as global parameter**. Otherwise,
281
+ parameter name is kept in full.
282
+
283
+ Example below demonstrates 3 different ways of providing parameter values from outer to inner component.
284
+
285
+ - Provide value explicitly
286
+ - Provide value explicitly as output of another component
287
+ - Autowire value from output of another component with the same name
288
+ - Propagate parameter to outer component
276
289
 
277
290
  ```ruby
278
291
 
279
- # Inner Component
280
- HighlanderComponent do
292
+ # Inner Component 1
293
+ CfhighlanderTemplate do
281
294
  Name 's3'
282
295
  Parameters do
283
296
  ComponentParam 'BucketName','highlander.example.com.au'
297
+ ComponentParam 'BucketName2',''
298
+ ComponentParam 'BucketName3',''
299
+ ComponentParam 'BucketName4','', isGlobal: false # default value is false
300
+ ComponentParam 'BucketName5','', isGlobal: true
284
301
  end
302
+
285
303
  end
304
+
286
305
  ```
287
306
 
288
307
  ```ruby
289
- # Outer component
290
- HighlanderComponent do
291
- # instantiate inner component with name and template
292
- Component template:'s3',
293
- name:'s3',
294
- parameters:{'BucketName' => 'outer.example.com.au'}
308
+ # Inner Component 2
309
+ CfhighlanderTemplate do
310
+ Name 'nameproducer'
311
+
312
+ # has output 'bucket name defined in cfdnsl
295
313
  end
296
- ```
297
314
 
298
- #### StackParam
299
315
 
300
- `StackParam` - Stack parameter bubbles up to it's outer component. Outer component will either define top level parameter
301
- with same name as inner component parameter (if parameter is defined as global), or it will be prefixed with inner component name.
316
+ # -- contents of cfndsl
317
+ CloudFormation do
302
318
 
319
+ Condition 'AlwaysFalse', FnEquals('true','false')
320
+ S3_Bucket :resourcetovalidateproperly do
321
+ Condition 'AlwaysFalse'
322
+ end
303
323
 
304
- ```ruby
305
- # Outer component
306
- HighlanderComponent do
307
- Component template:'s3',name:'s3'
324
+ Output('BucketName') do
325
+ Value('highlanderbucketautowired.example.com.au')
326
+ end
308
327
  end
328
+
329
+
309
330
  ```
310
331
 
311
332
  ```ruby
312
- # Inner component
313
- HighlanderComponent do
314
- Name 's3'
315
- Parameters do
316
- StackParam 'EnvironmentName','dev', isGlobal:true
317
- StackParam 'BucketName','highlander.example.com.au', isGlobal:false
318
- end
333
+ # Outer component
334
+ CfhighlanderTemplate do
335
+ Component 'nameproducer'
336
+ Component 's3' do
337
+ parameter name: 'BucketName2', value: 'nameproducer.BucketName'
338
+ parameter name: 'BucketName3', value: 'mybucket.example.cfhighlander.org'
339
+ end
319
340
  end
341
+
320
342
  ```
321
343
 
322
344
 
323
- Example above translates to following cfndsl template in outer component
345
+ Example above translates to following wiring of parameters in cfndsl template
324
346
  ```ruby
325
347
  CloudFormation do
326
348
 
327
- Parameter('EnvironmentName') do
328
- Type 'String'
329
- Default ''
330
- end
331
-
332
- Parameter('s3BucketName') do
333
- Type 'String'
334
- Default 'highlander.example.com.au'
349
+ # Parameter that was propagated
350
+ Parameter('s3BucketName4') do
351
+ Type 'String'
352
+ Default ''
353
+ NoEcho false
335
354
  end
336
-
337
- CloudFormation_Stack('s3') do
338
- TemplateURL 'https://distributionbucket/dist/latest/s3.yaml'
339
- Parameters ({
340
-
341
- 'EnvironmentName' => Ref('EnvironmentName'),
342
-
343
- 'BucketName' => Ref('s3BucketName'),
344
-
345
- })
355
+
356
+ Parameter('BucketName5') do
357
+ Type 'String'
358
+ Default ''
359
+ NoEcho false
346
360
  end
361
+
362
+ CloudFormation_Stack('s3') do
363
+ TemplateURL './s3.compiled.yaml'
364
+ Parameters ({
365
+
366
+ # Paramater that was auto-wired
367
+ 'BucketName' => {"Fn::GetAtt":["nameproducer","Outputs.BucketName"]},
368
+
369
+ # Parameter that was explicitly wired as output param from another component
370
+ 'BucketName2' => {"Fn::GetAtt":["nameproducer","Outputs.BucketName"]},
371
+
372
+ # Paramater that was explicitly provided
373
+ 'BucketName3' => 'mybucket.example.cfhighlander.org',
374
+
375
+ # Reference to parameter that was propagated. isGlobal: false when defining
376
+ # parameter, so parameter name is prefixed with component name
377
+ 'BucketName4' => {"Ref":"s3BucketName4"},
378
+
379
+ # Reference to parameter that was propagated. isGlobal: true when defining
380
+ # parameter, so parameter name is not prefixed, but rather propagated as-is
381
+ 'BucketName5' => {"Ref":"BucketName5"},
382
+
383
+ })
384
+ end
347
385
  end
386
+
348
387
  ```
349
388
 
350
389
 
@@ -358,7 +397,7 @@ This DSL statements takes a full body, as Mapping name, Map key, and value key n
358
397
 
359
398
  ```ruby
360
399
  # Inner component
361
- HighlanderComponent do
400
+ CfhighlanderTemplate do
362
401
  Name 's3'
363
402
  Parameters do
364
403
  MappingParam 'BucketName' do
@@ -370,17 +409,13 @@ end
370
409
  ```
371
410
 
372
411
 
373
- #### OutputParam
374
-
375
- TBD
376
-
377
412
  ### DependsOn
378
413
 
379
414
  `DependsOn` - this will include any globally exported libraries from given
380
415
  template. E.g.
381
416
 
382
417
  ```ruby
383
- HighlanderComponent do
418
+ CfhighlanderTemplate do
384
419
  Name 's3'
385
420
  DependsOn 'vpc@1.0.3'
386
421
  end
@@ -398,7 +433,46 @@ so extension methods can be consumed within cfndsl template.
398
433
  #### Referencing
399
434
 
400
435
 
401
- ## Finding and loading components
436
+ ## Finding templates and creating components
437
+
438
+
439
+ Templates are located by default in following locations
440
+
441
+ - `$WD`
442
+ - `$WD/$componentname`
443
+ - `$WD/components/$componentname`
444
+ - `~/.cfhighlander/components/componentname/componentversion`
445
+ - `https://github.com/cfhighlander/theonestack/hl-component-$componentname` on `master` branch
446
+
447
+ Location of component templates can be given as git/github repo:
448
+
449
+ ```ruby
450
+
451
+ CfhighlanderTemplate do
452
+
453
+ # pulls directly from master branch of https://github.com/theonestack/hl-component-vpc
454
+ Component name: 'vpc0', template: 'vpc'
455
+
456
+ # specify branch github.com: or github: work. You specify branch with hash
457
+ Component name: 'vpc1', template: 'github:theonestack/hl-component-vpc#master'
458
+
459
+ # you can use git over ssh
460
+ # Component name: 'vpc2', template: 'git:git@github.com:theonestack/hl-component-vpc.git'
461
+
462
+ # use git over https
463
+ Component name: 'vpc3', template: 'git:https://github.com/theonestack/hl-component-sns.git'
464
+
465
+ # specify .snapshot to always clone fresh copy
466
+ Component name: 'vpc4', template: 'git:https://github.com/theonestack/hl-component-sns.git#master.snapshot'
467
+
468
+ # by default, if not found locally, highlander will search for https://github.com/theonestack/component-$componentname
469
+ # in v${version} branch (or tag for that matter)
470
+ Component name: 'vpc5', template: 'vpc@1.0.4'
471
+
472
+ end
473
+
474
+ ```
475
+
402
476
 
403
477
  ## Rendering CloudFormation templates
404
478
 
@@ -43,13 +43,15 @@ module Cfhighlander
43
43
  @lambdas_processed = false
44
44
  @silent_mode = false
45
45
  @lambda_src_paths = []
46
+ @config_yaml_path = nil
47
+ @cfn_model = nil
46
48
 
47
49
  if @@global_extensions_paths.empty?
48
50
  global_extensions_folder = "#{File.dirname(__FILE__)}/../cfndsl_ext"
49
51
  Dir["#{global_extensions_folder}/*.rb"].each { |f| @@global_extensions_paths << f }
50
52
  end
51
53
 
52
- @component.highlander_dsl.components.each do |sub_component|
54
+ @component.highlander_dsl.subcomponents.each do |sub_component|
53
55
  sub_component_compiler = Cfhighlander::Compiler::ComponentCompiler.new(sub_component.component_loaded)
54
56
  sub_component_compiler.component_name = sub_component.name
55
57
  @sub_components << sub_component_compiler
@@ -67,7 +69,7 @@ module Cfhighlander
67
69
  dsl = @component.highlander_dsl
68
70
  component_cfndsl = @component.cfndsl_content
69
71
 
70
- @component.highlander_dsl.components.each { |sc|
72
+ @component.highlander_dsl.subcomponents.each { |sc|
71
73
  sc.distribution_format = out_format
72
74
  }
73
75
 
@@ -100,33 +102,38 @@ module Cfhighlander
100
102
 
101
103
  end
102
104
 
103
- def compileCloudFormation(format = 'yaml')
104
-
105
-
105
+ def evaluateCloudFormation(format = 'yaml')
106
106
  #compile cfndsl templates first
107
107
  compileCfnDsl format unless @cfndsl_compiled
108
108
 
109
+ # write config
110
+ cfndsl_opts = []
111
+ cfndsl_opts.push([:yaml, @config_yaml_path])
112
+
113
+ # grab cfndsl model
114
+ model = CfnDsl.eval_file_with_extras(@cfndsl_compiled_path, cfndsl_opts, false)
115
+ @cfn_model = model
116
+ return model
117
+ end
118
+
119
+ def compileCloudFormation(format = 'yaml')
120
+
109
121
  dsl = @component.highlander_dsl
110
- component_cfndsl = @component.cfndsl_content
111
122
 
112
123
  # create out dir if not there
113
124
  @cfn_output_location = "#{@workdir}/out/#{format}"
114
125
  output_dir = @cfn_output_location
115
126
  FileUtils.mkdir_p(output_dir) unless Dir.exist?(output_dir)
116
127
 
117
- # write config
118
- config_yaml_path = writeConfig
119
-
120
128
 
121
129
  # compile templates
122
- output_path = "#{output_dir}/#{component_name}.compiled.#{format}"
130
+ output_path = "#{output_dir}/#{@component_name}.compiled.#{format}"
123
131
  @cfn_template_paths << output_path
124
132
  # configure cfndsl
125
- cfndsl_opts = []
126
- cfndsl_opts.push([:yaml, config_yaml_path])
133
+
127
134
 
128
135
  # grab cfndsl model
129
- model = CfnDsl.eval_file_with_extras(@cfndsl_compiled_path, cfndsl_opts, false)
136
+ model = evaluateCloudFormation
130
137
 
131
138
  # write resulting cloud formation template
132
139
  if format == 'json'
@@ -165,7 +172,8 @@ module Cfhighlander
165
172
  end
166
173
  end
167
174
  @config_written = true
168
- config_yaml_path
175
+ @config_yaml_path = config_yaml_path
176
+ return @config_yaml_path
169
177
  end
170
178
 
171
179
  def processLambdas()
@@ -3,6 +3,8 @@ require_relative './cfhighlander.dsl.base'
3
3
  module Cfhighlander
4
4
 
5
5
  module Dsl
6
+
7
+ # dsl statements
6
8
  class Parameters < DslBase
7
9
 
8
10
  attr_accessor :param_list
@@ -12,7 +14,7 @@ module Cfhighlander
12
14
  end
13
15
 
14
16
  def addParam(param)
15
- existing_param = @param_list.find { |p| p.name == param.name }
17
+ existing_param = @param_list.find {|p| p.name == param.name}
16
18
  if not existing_param.nil?
17
19
  puts "Parameter being overwritten. Updating parameter #{param.name} with new definition..."
18
20
  @param_list[@param_list.index(existing_param)] = param
@@ -21,62 +23,55 @@ module Cfhighlander
21
23
  end
22
24
  end
23
25
 
24
- def StackParam(name, defaultValue='', isGlobal: false, noEcho: false)
25
- param = StackParam.new(name, 'String', defaultValue)
26
- param.is_global = isGlobal
27
- param.config = @config
28
- param.no_echo = noEcho
29
- addParam param
26
+ def StackParam(name, defaultValue = '', isGlobal: false, noEcho: false, type: 'String')
27
+ STDERR.puts "DEPRECATED: StackParam #{name} - Use ComponentParam instead"
28
+ ComponentParam(name, defaultValue, isGlobal: isGlobal, noEcho: noEcho, type: type)
29
+ end
30
+
31
+ def OutputParam(component:, name:, isGlobal: false, noEcho: false, type: 'String')
32
+ STDERR.puts ("DEPRECATED: OutputParam #{name} - Use ComponentParam instead. Outputut params are " +
33
+ "autorwired by name only, with component disregarded")
34
+ param = ComponentParam(name, '', isGlobal: isGlobal, noEcho: noEcho, type: type)
35
+ param.provided_value = "#{component}.#{name}"
30
36
  end
31
37
 
32
- def ComponentParam(name, defaultValue='')
33
- param = ComponentParam.new(name, 'String', defaultValue)
38
+ def ComponentParam(name, defaultValue = '', isGlobal: false, noEcho: false, type: 'String', allowedValues: nil)
39
+ param = Parameter.new(name, type, defaultValue, noEcho, isGlobal, allowedValues)
34
40
  param.config = @config
35
41
  addParam param
42
+ return param
36
43
  end
37
44
 
38
- def MappingParam(name, defaultValue='', &block)
45
+ def MappingParam(name, defaultValue = '', &block)
39
46
  param = MappingParam.new(name, 'String', defaultValue)
40
47
  param.config = @config
41
48
  param.instance_eval(&block)
42
49
  addParam param
43
50
  end
44
51
 
45
- def OutputParam(component:, name:, default: '')
46
- param = OutputParam.new(component, name, default)
47
- param.config = @config
48
- addParam param
49
- end
52
+
50
53
  end
51
54
 
55
+ # model classes
52
56
  class Parameter < DslBase
53
- attr_accessor :name, :type, :default_value, :no_echo
54
-
55
- def initialize(name, type, defaultValue, noEcho = false)
57
+ attr_accessor :name,
58
+ :type,
59
+ :default_value,
60
+ :no_echo,
61
+ :is_global,
62
+ :provided_value,
63
+ :allowed_values
64
+
65
+ def initialize(name, type, defaultValue, noEcho = false, isGlobal = false, allowed_values = nil)
56
66
  @no_echo = noEcho
57
67
  @name = name
58
68
  @type = type
59
69
  @default_value = defaultValue
70
+ @is_global = isGlobal
71
+ @allowed_values = allowed_values
72
+ @provided_value = nil
60
73
  end
61
- end
62
-
63
- class StackParam < Parameter
64
- attr_accessor :is_global
65
- end
66
74
 
67
- class ComponentParam < Parameter
68
-
69
- end
70
-
71
- class OutputParam < Parameter
72
- attr_accessor :component
73
-
74
- def initialize(component, name, default)
75
- @component = component
76
- @name = name
77
- @default_value = default
78
- @type = 'String'
79
- end
80
75
  end
81
76
 
82
77
  class MappingParam < Parameter
@@ -1,6 +1,7 @@
1
1
  require_relative './cfhighlander.helper'
2
2
  require_relative './cfhighlander.dsl.base'
3
3
  require_relative './cfhighlander.factory'
4
+ require 'cfndsl'
4
5
 
5
6
  module Cfhighlander
6
7
 
@@ -15,7 +16,6 @@ module Cfhighlander
15
16
 
16
17
  end
17
18
 
18
-
19
19
  class Subcomponent < DslBase
20
20
 
21
21
  attr_accessor :name,
@@ -100,9 +100,6 @@ module Cfhighlander
100
100
  end
101
101
 
102
102
  def load(component_config_override = {})
103
- # check for component config on parent
104
- parent = @parent
105
-
106
103
  # Highest priority is DSL defined configuration
107
104
  component_config_override.extend @config
108
105
 
@@ -111,9 +108,13 @@ module Cfhighlander
111
108
  @component_loaded.load @component_config_override
112
109
  end
113
110
 
111
+ def parameter(name:, value:)
112
+ @param_values[name] = value
113
+ end
114
+
114
115
  # Parameters should be lazy loaded, that is late-binding should happen once
115
116
  # all parameters and mappings are known
116
- def load_parameters
117
+ def resolve_parameter_values(available_outputs)
117
118
  component_dsl = @component_loaded.highlander_dsl
118
119
  component_dsl.parameters.param_list.each do |component_param|
119
120
  param = Cfhighlander::Dsl::SubcomponentParameter.new
@@ -121,7 +122,8 @@ module Cfhighlander
121
122
  param.cfndsl_value = SubcomponentParamValueResolver.resolveValue(
122
123
  @parent,
123
124
  self,
124
- component_param)
125
+ component_param,
126
+ available_outputs)
125
127
  @parameters << param
126
128
  end
127
129
  end
@@ -129,40 +131,77 @@ module Cfhighlander
129
131
  end
130
132
 
131
133
  class SubcomponentParamValueResolver
132
- def self.resolveValue(component, sub_component, param)
133
-
134
- puts("Resolving parameter #{component.name} -> #{sub_component.name}.#{param.name}")
134
+ def self.resolveValue(component, sub_component, param, available_outputs)
135
+
136
+ print("INFO Resolving parameter #{component.name} -> #{sub_component.name}.#{param.name}: ")
137
+
138
+ # rule 0: this rule is here for legacy reasons and OutputParam. It should be deprecated
139
+ # once all hl-components- repos remove any references to OutputParam
140
+ if not param.provided_value.nil?
141
+ component_name = param.provided_value.split('.')[0]
142
+ output_name = param.provided_value.split('.')[1]
143
+ source_component = component.subcomponents.find {|c| c.name == component_name}
144
+ if source_component.nil?
145
+ source_component = component.subcomponents.find {|c| c.component_loaded.template.template_name == component_name}
146
+ end
147
+ return CfnDsl::Fn.new('GetAtt', [
148
+ source_component.name,
149
+ "Outputs.#{output_name}"
150
+ ]).to_json
151
+ end
135
152
 
136
- # check if there are values defined on component itself
153
+ # rule 1: check if there are values defined on component itself
137
154
  if sub_component.param_values.key?(param.name)
138
- return Cfhighlander::Helper.parameter_cfndsl_value(sub_component.param_values[param.name])
155
+ puts " parameter value provided "
156
+
157
+ param_value = sub_component.param_values[param.name]
158
+ if param_value.is_a? String and param_value.include? '.'
159
+ source_component_name = param_value.split('.')[0]
160
+ source_output = param_value.split('.')[1]
161
+ source_component = component.subcomponents.find {|sc| sc.name == source_component_name}
162
+ # if source component exists
163
+ if not source_component.nil?
164
+ if source_component_name == sub_component.name
165
+ STDERR.puts "WARNING: Parameter value on component #{source_component_name} references component itself: #{param_value}"
166
+ else
167
+ return CfnDsl::Fn.new('GetAtt', [
168
+ source_component_name,
169
+ "Outputs.#{source_output}"
170
+ ]).to_json
171
+ end
172
+ else
173
+ return Cfhighlander::Helper.parameter_cfndsl_value(param_value)
174
+ end
175
+ else
176
+ return Cfhighlander::Helper.parameter_cfndsl_value(sub_component.param_values[param.name])
177
+ end
139
178
  end
140
179
 
141
- if param.class == Cfhighlander::Dsl::StackParam
142
- return self.resolveStackParamValue(component, sub_component, param)
143
- elsif param.class == Cfhighlander::Dsl::ComponentParam
144
- return self.resolveComponentParamValue(component, sub_component, param)
145
- elsif param.class == Cfhighlander::Dsl::MappingParam
180
+ # rule 1.1 mapping parameters are handled differently.
181
+ # TODO wire mapping parameters outside of component
182
+ if param.class == Cfhighlander::Dsl::MappingParam
183
+ puts " mapping parameter"
146
184
  return self.resolveMappingParamValue(component, sub_component, param)
147
- elsif param.class == Cfhighlander::Dsl::OutputParam
148
- return self.resolveOutputParamValue(component, sub_component, param)
149
- else
150
- raise "#{param.class} not resolvable to parameter value"
151
185
  end
152
- end
153
186
 
154
- def self.resolveStackParamValue(component, sub_component, param)
155
- param_name = param.is_global ? param.name : "#{sub_component.name}#{param.name}"
156
- return "Ref('#{param_name}')"
157
- end
187
+ # rule #2: match output values from other components
188
+ # by parameter name
189
+ if available_outputs.key? param.name
190
+ component_name = available_outputs[param.name].component.name
191
+ puts " resolved as output of #{component_name}"
192
+ return CfnDsl::Fn.new('GetAtt', [
193
+ component_name,
194
+ "Outputs.#{param.name}"
195
+ ]).to_json
196
+ end
197
+
198
+ # by default bubble parameter and resolve as reference on upper level
199
+ propagated_param = param.clone
200
+ propagated_param.name = "#{sub_component.name}#{param.name}" unless param.is_global
201
+ component.parameters.addParam propagated_param
202
+ puts " no autowiring candidates, propagate parameter to parent"
203
+ return CfnDsl::RefDefinition.new(propagated_param.name).to_json
158
204
 
159
- def self.resolveComponentParamValue(component, sub_component, param)
160
- # check component config for param value
161
- # TODO
162
- # check stack config for param value
163
- # TODO
164
- # return default value
165
- return "'#{param.default_value}'"
166
205
  end
167
206
 
168
207
  def self.resolveMappingParamValue(component, sub_component, param)
@@ -207,37 +246,9 @@ module Cfhighlander
207
246
  return "''"
208
247
  end
209
248
 
210
- return value
211
-
212
-
213
249
  return value
214
250
  end
215
251
 
216
- def self.resolveOutputParamValue(component, sub_component, param)
217
- component_name = param.component
218
- resource_name = nil
219
- if not sub_component.export_config.nil?
220
- if sub_component.export_config.key? component_name
221
- resource_name = sub_component.export_config[component_name]
222
- end
223
- end
224
-
225
- if resource_name.nil?
226
- # find by component
227
- resource = component.components.find {|c| c.name == component_name}
228
- resource_name = resource.name unless resource.nil?
229
- if resource_name.nil?
230
- resource = component.components.find {|c| c.template == component_name}
231
- resource_name = resource.name unless resource.nil?
232
- end
233
- end
234
-
235
- if resource_name.nil?
236
- raise "#{sub_component.name}.Params.#{param.name}: Failed to resolve OutputParam '#{param.name}' with source '#{component_name}'. Component not found!"
237
- end
238
-
239
- return "FnGetAtt('#{resource_name}','Outputs.#{param.name}')"
240
- end
241
252
  end
242
253
 
243
254
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  extensions_folder = "#{File.dirname(__FILE__)}/../hl_ext"
4
4
 
5
- Dir["#{extensions_folder}/*.rb"].each { |f|
5
+ Dir["#{extensions_folder}/*.rb"].each {|f|
6
6
  require f
7
7
  }
8
8
 
@@ -10,7 +10,7 @@ Dir["#{extensions_folder}/*.rb"].each { |f|
10
10
 
11
11
  require_relative './cfhighlander.dsl.base'
12
12
  require_relative './cfhighlander.dsl.params'
13
- require_relative './cfhighlander.dsl.component'
13
+ require_relative './cfhighlander.dsl.subcomponent'
14
14
 
15
15
  module Cfhighlander
16
16
 
@@ -21,7 +21,7 @@ module Cfhighlander
21
21
  attr_accessor :mappings,
22
22
  :parameters,
23
23
  :name,
24
- :components,
24
+ :subcomponents,
25
25
  :version,
26
26
  :distribute_url,
27
27
  :distribution_bucket,
@@ -32,7 +32,7 @@ module Cfhighlander
32
32
 
33
33
  def initialize
34
34
  @mappings = []
35
- @components = []
35
+ @subcomponents = []
36
36
  @config = { 'mappings' => {}, 'component_version' => 'latest' }
37
37
  @component_configs = {}
38
38
  @version = 'latest'
@@ -74,7 +74,7 @@ module Cfhighlander
74
74
 
75
75
  def DynamicMappings(providerName)
76
76
  maps = mappings_provider_maps(providerName, self.config)
77
- maps.each { |name, map| addMapping(name, map) } unless maps.nil?
77
+ maps.each {|name, map| addMapping(name, map)} unless maps.nil?
78
78
  end
79
79
 
80
80
  def DependsOn(template)
@@ -82,8 +82,20 @@ module Cfhighlander
82
82
  end
83
83
 
84
84
 
85
- def Component(template:, name: template, param_values: {}, config: {}, export_config: {}, &block)
85
+ def Component(template_arg = nil, template: nil,
86
+ name: template,
87
+ param_values: {},
88
+ config: {},
89
+ export_config: {}, &block)
86
90
  puts "INFO: Requested subcomponent #{name} with template #{template}"
91
+ if ((not template_arg.nil?) and template.nil?)
92
+ template = template_arg
93
+ elsif (template_arg.nil? and template.nil?)
94
+ raise 'Component must be defined with template name at minimum,' +
95
+ ' passed as first argument, or template: named arguent'
96
+ end
97
+
98
+ name = template if name.nil?
87
99
 
88
100
  # load component
89
101
  component = Cfhighlander::Dsl::Subcomponent.new(self,
@@ -94,11 +106,12 @@ module Cfhighlander
94
106
  config,
95
107
  export_config
96
108
  )
109
+ component.instance_eval(&block) unless block.nil?
97
110
  component.distribute_bucket = @distribution_bucket unless @distribution_bucket.nil?
98
111
  component.distribute_prefix = @distribution_prefix unless @distribution_prefix.nil?
99
112
  component.version = @version unless @version.nil?
100
113
  @component_configs[name] = config
101
- @components << component
114
+ @subcomponents << component
102
115
  end
103
116
 
104
117
  def ComponentVersion(version)
@@ -151,8 +164,8 @@ module Cfhighlander
151
164
  def loadComponents()
152
165
 
153
166
  # empty config overrides to start with
154
- @config_overrides = Hash[@components.collect { |c| [c.name, {'nested_component' => true}] }]
155
- @named_components = Hash[@components.collect { |c| [c.name, c] }]
167
+ @config_overrides = Hash[@subcomponents.collect {|c| [c.name, { 'nested_component' => true }]}]
168
+ @named_components = Hash[@subcomponents.collect {|c| [c.name, c]}]
156
169
 
157
170
  # populate overrides with master config defined overrides
158
171
  load_configfile_component_config
@@ -174,7 +187,7 @@ module Cfhighlander
174
187
 
175
188
 
176
189
  # load components and extract parent stack parameters and mappings
177
- @components.each { |component|
190
+ @subcomponents.each do |component|
178
191
 
179
192
  component.load @config_overrides[component.name]
180
193
  # add all of it's stack parameters unless same template has been already processed
@@ -184,19 +197,6 @@ module Cfhighlander
184
197
  .parameters
185
198
  .param_list.each do |param|
186
199
 
187
- # add stack parameters
188
- if param.class == Cfhighlander::Dsl::StackParam
189
- # sub-component stack param becomes top-level component param
190
- param_name = param.is_global ? param.name : "#{component.name}#{param.name}"
191
- stack_param = Cfhighlander::Dsl::ComponentParam.new(
192
- param_name,
193
- param.type,
194
- param.default_value,
195
- param.no_echo
196
- )
197
- @parameters.addParam stack_param
198
- end unless component.param_values.key? param.name
199
-
200
200
  # for map parameters add maps
201
201
  if param.class == Cfhighlander::Dsl::MappingParam
202
202
  if not param.mapProvider.nil?
@@ -215,9 +215,21 @@ module Cfhighlander
215
215
 
216
216
  end
217
217
 
218
- # late bind parameter values, once mappings and top level params are extracted
219
- component.load_parameters
220
- }
218
+ component.component_loaded.eval_cfndsl()
219
+ end
220
+
221
+ # in 2nd pass, load parameters
222
+ # 2nd pass is required as all of cfndsl models need to be populated
223
+ available_outputs = {}
224
+ @subcomponents.each do |c|
225
+ c.component_loaded.outputs.each do |out|
226
+ available_outputs[out.name] = out
227
+ end
228
+ end
229
+
230
+ @subcomponents.each do |component|
231
+ component.resolve_parameter_values available_outputs
232
+ end
221
233
 
222
234
  @dependson_components_templates.each do |template|
223
235
  component = Cfhighlander::Dsl::Subcomponent.new(self,
@@ -232,7 +244,7 @@ module Cfhighlander
232
244
  end
233
245
 
234
246
  def load_extension_exports
235
- @components.each do |c|
247
+ @subcomponents.each do |c|
236
248
  component = c.component_loaded
237
249
  config = component.config
238
250
  if ((config.key? 'lib_export') and (config['lib_export'].key? 'global'))
@@ -241,7 +253,7 @@ module Cfhighlander
241
253
  if global_export_config.key? 'cfndsl'
242
254
  global_export_config['cfndsl'].each do |exported_extension|
243
255
  extension_file_path = "#{component.component_dir}/ext/cfndsl/#{exported_extension}.rb"
244
- @components.each do |cr|
256
+ @subcomponents.each do |cr|
245
257
  cr.component_loaded.cfndsl_ext_files << extension_file_path unless cr == c
246
258
  end
247
259
  end
@@ -252,14 +264,14 @@ module Cfhighlander
252
264
  end
253
265
 
254
266
  def apply_config_overrides
255
- @config_overrides.each { |component_name, component_override|
267
+ @config_overrides.each {|component_name, component_override|
256
268
  @named_components[component_name].component_loaded.config.extend(component_override)
257
269
  }
258
270
  end
259
271
 
260
272
  def load_configfile_component_config
261
273
  if (@config.key? 'components')
262
- @config['components'].each { |component_name, component_config|
274
+ @config['components'].each {|component_name, component_config|
263
275
  if component_config.key?('config')
264
276
  if @config_overrides.key? component_name
265
277
  @config_overrides[component_name].extend(component_config['config'])
@@ -274,28 +286,28 @@ module Cfhighlander
274
286
  def apply_config_exports
275
287
  # first export from master to all children
276
288
  if ((@config.key? 'config_export') and (@config['config_export']['global']))
277
- @config['config_export']['global'].each { |global_export_key|
289
+ @config['config_export']['global'].each {|global_export_key|
278
290
  if @config.key? global_export_key
279
- @config_overrides.each { |cname, co|
291
+ @config_overrides.each {|cname, co|
280
292
  co[global_export_key] = @config[global_export_key]
281
293
  }
282
294
  end
283
295
  }
284
296
  end
285
297
 
286
- @components.each { |component|
298
+ @subcomponents.each {|component|
287
299
  cl = component.component_loaded
288
300
  if ((not cl.config.nil?) and (cl.config.key? 'config_export'))
289
301
 
290
302
  # global config
291
303
  if cl.config['config_export'].key? 'global'
292
- cl.config['config_export']['global'].each { |global_export_key|
304
+ cl.config['config_export']['global'].each {|global_export_key|
293
305
 
294
306
  # global config is exported to parent and every component
295
307
  if cl.config.key? global_export_key
296
308
 
297
309
  # cname is for component name, co for component override
298
- @config_overrides.each { |cname, co|
310
+ @config_overrides.each {|cname, co|
299
311
 
300
312
  # if templates are different e.g don't export from vpc to vpc
301
313
  config_receiver_component = @named_components[cname]
@@ -321,7 +333,7 @@ module Cfhighlander
321
333
  end
322
334
 
323
335
  if cl.config['config_export'].key? 'component'
324
- cl.config['config_export']['component'].each { |component_name, export_keys|
336
+ cl.config['config_export']['component'].each {|component_name, export_keys|
325
337
  # check if there is configuration of export from this component
326
338
  # and if there is export configuration for given component name
327
339
 
@@ -330,7 +342,7 @@ module Cfhighlander
330
342
  if @config_overrides.key? component.export_config[component_name]
331
343
  # override the config
332
344
  real_component_name = component.export_config[component_name]
333
- export_keys.each { |export_component_key|
345
+ export_keys.each {|export_component_key|
334
346
  puts("Exporting config for key=#{export_component_key} from #{component.name} to #{real_component_name}")
335
347
  if not @config_overrides[real_component_name].key? export_component_key
336
348
  @config_overrides[real_component_name][export_component_key] = {}
@@ -341,7 +353,7 @@ module Cfhighlander
341
353
  STDERR.puts("Trying to export configuration for non-existant component #{component.export_config[component_name]}")
342
354
  end
343
355
  elsif @config_overrides.key? component_name
344
- export_keys.each { |export_component_key|
356
+ export_keys.each {|export_component_key|
345
357
  puts("Exporting config for key=#{export_component_key} from #{component.name} to #{component_name}")
346
358
  if not @config_overrides[component_name].key? export_component_key
347
359
  @config_overrides[component_name][export_component_key] = {}
@@ -361,7 +373,7 @@ module Cfhighlander
361
373
  end
362
374
 
363
375
  def load_explicit_component_config
364
- @component_configs.each { |component_name, component_config|
376
+ @component_configs.each {|component_name, component_config|
365
377
  @config_overrides[component_name].extend(component_config)
366
378
  }
367
379
  end
@@ -384,7 +396,7 @@ module Cfhighlander
384
396
  if not (@distribution_bucket.nil? or @distribution_prefix.nil?)
385
397
  @distribute_url = "https://#{@distribution_bucket}.s3.amazonaws.com/#{@distribution_prefix}"
386
398
  @distribute_url = "#{@distribute_url}/#{@version}" unless @version.nil?
387
- @components.each { |component|
399
+ @subcomponents.each {|component|
388
400
  component.distribute_bucket = @distribution_bucket unless @distribution_bucket.nil?
389
401
  component.distribute_prefix = @distribution_prefix unless @distribution_prefix.nil?
390
402
  component.version = @version unless @version.nil?
@@ -399,7 +411,7 @@ module Cfhighlander
399
411
 
400
412
  end
401
413
 
402
- def HighlanderComponent(&block)
414
+ def CfhighlanderTemplate(&block)
403
415
  instance = Cfhighlander::Dsl::HighlanderTemplate.new
404
416
 
405
417
  puts "Processing higlander component #{@name}\n\tLocation:#{@highlander_dsl_path}" +
@@ -435,5 +447,11 @@ def HighlanderComponent(&block)
435
447
  end
436
448
 
437
449
  def CfhighlanderComponent(&block)
438
- HighlanderComponent(&block)
450
+ STDERR.puts("DEPRECATED: #{@template.template_name}: use CfhighlanderTemplate instead of CfhighlanderComponent")
451
+ CfhighlanderTemplate(&block)
452
+ end
453
+
454
+ def HighlanderComponent(&block)
455
+ STDERR.puts("DEPRECATED: #{@template.template_name}: use CfhighlanderTemplate instead of HighlanderComponent")
456
+ CfhighlanderTemplate(&block)
439
457
  end
@@ -1,4 +1,4 @@
1
- require_relative './cfhighlander.dsl'
1
+ require_relative './cfhighlander.dsl.template'
2
2
  require_relative './cfhighlander.factory.templatefinder'
3
3
  require_relative './cfhighlander.model.component'
4
4
  require 'fileutils'
@@ -11,7 +11,7 @@ module Cfhighlander
11
11
  end
12
12
 
13
13
  if value.class == Hash
14
- return value.to_s
14
+ return value.to_json
15
15
  end
16
16
 
17
17
  return "'#{value}'"
@@ -22,6 +22,8 @@ module Cfhighlander
22
22
  :cfndsl_ext_files,
23
23
  :lambda_src_files
24
24
 
25
+ attr_reader :cfn_model, :outputs
26
+
25
27
  def initialize(template_meta, component_name)
26
28
  @template = template_meta
27
29
  @name = component_name
@@ -69,7 +71,7 @@ module Cfhighlander
69
71
  STDERR.puts "DEPRECATED: #{legacy_cfhighlander_path} - Use *.cfhiglander.rb"
70
72
  @highlander_dsl_path = legacy_cfhighlander_path
71
73
  else
72
- @highlander_dsl_path = "#{@component_dir}/#{@template.template_name}.cfhighlander.rb"
74
+ @highlander_dsl_path = "#{@component_dir}/#{@template.template_name}.cfhighlander.rb"
73
75
  end
74
76
 
75
77
  @cfndsl_path = "#{@component_dir}/#{@template.template_name}.cfndsl.rb"
@@ -88,10 +90,7 @@ module Cfhighlander
88
90
  @config['component_name'] = @name
89
91
  @config['template_name'] = @template.template_name
90
92
  @config['template_version'] = @template.template_version
91
- # allow override of components
92
- # config_override.each do |key, value|
93
- # @config[key] = value
94
- # end unless config_override.nil?
93
+
95
94
 
96
95
  Dir[candidate_mappings_path].each do |mapping_file|
97
96
  mappings = YAML.load(File.read(mapping_file))
@@ -145,6 +144,31 @@ module Cfhighlander
145
144
 
146
145
  loadDepandantExt()
147
146
  end
147
+
148
+ # evaluates cfndsl with current config
149
+ def eval_cfndsl
150
+ compiler = Cfhighlander::Compiler::ComponentCompiler.new self
151
+ @cfn_model = compiler.evaluateCloudFormation().as_json
152
+ @outputs = (
153
+ if @cfn_model.key? 'Outputs'
154
+ then
155
+ @cfn_model['Outputs'].map {|k, v| ComponentOutput.new self, k, v}
156
+ else
157
+ []
158
+ end
159
+ )
160
+ end
161
+ end
162
+
163
+ class ComponentOutput
164
+
165
+ attr_reader :component, :name, :value
166
+
167
+ def initialize(component, name, value)
168
+ @component = component
169
+ @name = name
170
+ @value = value
171
+ end
148
172
  end
149
173
 
150
174
  end
@@ -8,7 +8,7 @@ CloudFormation do
8
8
  <% end %>
9
9
 
10
10
  # render subcomponents
11
- <% for @component in dsl.components %>
11
+ <% for @component in dsl.subcomponents %>
12
12
  CloudFormation_Stack('<%= @component.name.gsub('-','').gsub('_','') %>') do
13
13
  TemplateURL '<%= @component.distribution_url %>'
14
14
  Parameters ({
@@ -38,6 +38,9 @@ CloudFormation do
38
38
  <% unless @param.no_echo.nil? %>
39
39
  NoEcho <%= @param.no_echo.to_s %>
40
40
  <% end %>
41
+ <% unless @param.allowed_values.nil? %>
42
+ AllowedValues <%= @param.allowed_values.to_s %>
43
+ <% end %>
41
44
  end
42
45
  <% end %>
43
46
 
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.2.1.alpha.34
4
+ version: 0.2.1.alpha.43
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nikola Tosic
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-06-05 00:00:00.000000000 Z
12
+ date: 2018-06-07 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: highline
@@ -245,9 +245,9 @@ files:
245
245
  - hl_ext/vpc.rb
246
246
  - lib/cfhighlander.compiler.rb
247
247
  - lib/cfhighlander.dsl.base.rb
248
- - lib/cfhighlander.dsl.component.rb
249
248
  - lib/cfhighlander.dsl.params.rb
250
- - lib/cfhighlander.dsl.rb
249
+ - lib/cfhighlander.dsl.subcomponent.rb
250
+ - lib/cfhighlander.dsl.template.rb
251
251
  - lib/cfhighlander.factory.rb
252
252
  - lib/cfhighlander.factory.templatefinder.rb
253
253
  - lib/cfhighlander.helper.rb