cfhighlander 0.2.0.alpha.10

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e3e89ca96189f61bf33bee53483c80d21bc8b019046d0f88f53546187ffb8a3f
4
+ data.tar.gz: 570a0ecaf7861401432572603be85d3d2627309aeeeca96369b080540e79556b
5
+ SHA512:
6
+ metadata.gz: f2dd509569d44185bdfabafe04e20fa11efb4556f4e9bd33f5415bf8dee5faf3abcc35f53e155438af3f41595387e6719662ae21a0ed8a700fa08c04a88624ce
7
+ data.tar.gz: ddbff792cf60a90b573e281cc4671cd0ef94e842a01440710a960ef08bdcf3831da5be5ad9e3b7832213762ff2af1fa985a952f260449fb70921d97f3fa6388f
data/README.md ADDED
@@ -0,0 +1,411 @@
1
+ [![Build Status](https://travis-ci.com/theonestack/cfhighlander.svg?branch=develop)](https://travis-ci.com/theonestack/cfhighlander)
2
+
3
+ # Highlander
4
+
5
+ Highlander is DSL processor that enables composition and orchestration of Amazon CloudFormation templates
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
+ 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
10
+ 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.
13
+
14
+ Highlander DSL produces CloudFormation templates in 3 phases
15
+
16
+ - Processing referenced component's configuration and resolving configuration exports
17
+ - Producing [CfnDsl](https://github.com/cfndsl/cfndsl) templates for all components and subcomponents as intermediary
18
+ step
19
+ - Producing resulting CloudFormation templates using configuration and templates generated in two previous phases.
20
+
21
+ Each phase above is executable as stand-alone through CLI, making development of Highlander templates easier by enabling
22
+ debugging of produced configuration and cfndsl templates.
23
+
24
+
25
+ ## Highlander components
26
+
27
+ Highlander component is located on local file system or S3 location with following
28
+ files defining them
29
+
30
+ - Highlander DSL file (`$componentname.highlander.rb`)
31
+ - *(Optional)* Configuration files (`*.config.yaml`)
32
+ - *(Optional)* CfnDSL file (`componentname.cfnds.rb`)
33
+ - *(Optional)* Mappings YAML files `*.mappings.yaml` -
34
+ this file defines map used within component itself
35
+ - *(Optional)* Mappings extension file `componentname.mappings.rb` - see more under Mappings section
36
+ - *(Optional)* Ruby extensions consumed by cfndsl templates - placed in `ext/cfndsl/*.rb` - see more under
37
+ Extensions section
38
+
39
+ ## Terminology
40
+
41
+ **Component** is basic building block of highlander systems. Components have following roles
42
+
43
+ - Define (include) other components
44
+ - Define how their parameters are wired with other components (sibling and outer components)
45
+ - Define how their configuration affects other components
46
+ - Define sources of their inner components
47
+ - Define publish location for both component source code and compiled CloudFormation templates
48
+ - Define cfndsl template used for building CloudFormation resources
49
+
50
+
51
+ **Outer component** is component that defines other component via higlander dsl `Component` statement. Defined component
52
+ is called **inner component**. Components defined under same outer component are **sibling components**
53
+
54
+ ## Usage
55
+
56
+ You can either pull highlander classes in your own code, or more commonly use it via command line interface (cli).
57
+ For both ways, highlander is distributed as ruby gem
58
+
59
+
60
+ ```bash
61
+ $ gem install highlander
62
+ $ highlander help
63
+ highlander commands:
64
+ highlander cfcompile component[@version] -f, --format=FORMAT # Compile Highlander component to CloudFormation templates
65
+ highlander cfpublish component[@version] -f, --format=FORMAT # Publish CloudFormation template for component, and it' referenced subcomponents
66
+ highlander configcompile component[@version] # Compile Highlander components configuration
67
+ highlander dslcompile component[@version] -f, --format=FORMAT # Compile Highlander component configuration and create cfndsl templates
68
+ highlander help [COMMAND] # Describe available commands or one specific command
69
+ highlander publish component[@version] [-v published_version] # Publish CloudFormation template for component, and it' referenced subcomponents
70
+
71
+ ```
72
+ ### Working directory
73
+
74
+ All templates and configuration generated are placed in `$WORKDIR/out` directory. Optionally, you can alter working directory
75
+ via `HIGHLANDER_WORKDIR` environment variable.
76
+
77
+ ### Commands
78
+
79
+ To get full list of options for any of cli commands use `highlander help command_name` syntax
80
+
81
+ ```bash
82
+ $ highlander help publish
83
+ Usage:
84
+ highlander publish component[@version] [-v published_version]
85
+
86
+ Options:
87
+ [--dstbucket=DSTBUCKET] # Distribution S3 bucket
88
+ [--dstprefix=DSTPREFIX] # Distribution S3 prefix
89
+ -v, [--version=VERSION] # Distribution component version, defaults to latest
90
+
91
+ Publish CloudFormation template for component,
92
+ and it' referenced subcomponents
93
+
94
+ ```
95
+
96
+ #### Silent mode
97
+
98
+ Highlander DSL processor has built-in support for packaging and deploying AWS Lambda functions. Some of these lambda
99
+ functions may require shell command to be executed (e.g. pulling library dependencies) prior their packaging in ZIP archive format.
100
+ Such commands are potential security risk, as they allow execution of arbitrary code, so for this reason user agreement is required
101
+ e.g:
102
+
103
+ ```bash
104
+ Packaging AWS Lambda function logMessage...
105
+ Following code will be executed to generate lambda function logMessage:
106
+
107
+ pip install requests -t lib
108
+
109
+ Proceed (y/n)?
110
+ ```
111
+
112
+ In order to avoid user prompt pass `-q` or `--quiet` switch to CLI for commands that require Lambda packaging
113
+ (`dslcompile`, `cfcompile`, `cfpublish`)
114
+
115
+
116
+ #### cfcompile
117
+
118
+ *cfcompile* will produce cloudformation templates in specified format (defaults to yaml). You can optionally validate
119
+ produced template via `--validate` switch. Resulting templates will be placed in `$WORKDIR/out/$format`
120
+
121
+
122
+ #### cfpublish
123
+
124
+ *cfcompile* will produce cloudformation templates in specified format (defaults to yaml), and publish them to S3 location.
125
+ You can optionally validate produced template via `--validate` switch. Resulting templates will be placed in `$WORKDIR/out/$format`, and
126
+ published to `s3://$distributionBucket/$distributionPrefix/$distributionVersion`. All S3 path components can be controlled
127
+ via CLI (`--dstbucket`, `--dstprefix`, `-v`). Default distribution bucket and prefix can be also be controlled via DSL using
128
+ `DistributionBucket`, `DistributionBucket`, `DistributionPrefix` or `ComponentDistribution` statements. Check DSL specification
129
+ for more details on this statements. Version defaults to `latest` if not explicitly given using `-v` switch
130
+
131
+
132
+ #### configcompile
133
+
134
+ *configcompile* produces configuration yamls that are passed as external configuration when processing
135
+ cfndsl templates. Check component configuration section for more details.
136
+
137
+ #### dslcompile
138
+
139
+ *dslcompile* will produce intermediary cfndsl templates. This is useful for debugging highlander components
140
+
141
+ #### publish
142
+
143
+ *publish* command publishes highlander components source code to s3 location (compared to *cfpublish* which is publishing
144
+ compiled cloudformation templates). Same CLI / DSL options apply as for *cfpublish* command. Version defaults to `latest`
145
+
146
+
147
+
148
+ ## Component configuration
149
+
150
+ There are 4 levels of component configuration
151
+
152
+ - Component local config file `component.config.yaml` (lowest priority)
153
+ - Outer component configuration file, under `components` key, like
154
+
155
+
156
+ ```yaml
157
+
158
+ # some configuration values
159
+
160
+ components:
161
+ vpc:
162
+ config:
163
+ maximum_availibility_zones: 2
164
+
165
+ ```
166
+ This configuration level overrides component's own config file.
167
+
168
+
169
+ - Outer component explicit configuration. You can pass `config` named parameter to `Component` statement, such as
170
+
171
+ ```ruby
172
+ HighlanderComponent do
173
+
174
+ # ...
175
+ # some dsl code
176
+ # ...
177
+
178
+ Component template:'vpc@latest',config: {'maximum_availibility_zones' => 2}
179
+
180
+ end
181
+ ```
182
+ Configuration done this way will override any outer component config coming from configuration file
183
+
184
+
185
+ - Exported configuration from other components. If any component exports configuration using `config_export` configuration
186
+ key, it may alter configuration of other components. Globally exported configuration is defined under `global`, while
187
+ component-oriented configuration is exported under `component` key. E.g. following configuration will export global
188
+ configuration defining name of ecs cluster, and targeted to vpc component configuration, defining subnets
189
+
190
+ ```yaml
191
+ ecs_cluster_name: ApplicationCluster
192
+
193
+ subnets:
194
+ ecs_cluster:
195
+ name: ECSCluster
196
+ type: private
197
+ allocation: 20
198
+
199
+ config_export:
200
+ global:
201
+ - ecs_cluster_name
202
+
203
+ component:
204
+ vpc:
205
+ - subnets
206
+ ```
207
+
208
+ Configuration is exported **AFTER** component local config, and outer component configurations are loaded.
209
+ Outer component configuration takes priority over exported configuration, as this configuration is loaded once
210
+ more once component exported conifgurations are applied to appropriate components.
211
+
212
+ To change *NAME* of targeted component (e.g. from `vpc` to `vpc1`), you can use `export_config` named parameter on `Component` dsl method
213
+ In addition to configuration in inner component above, wiring of targeted component for export would be done like
214
+
215
+ ```ruby
216
+ Component name: 'vpc1', template: 'vpc'
217
+ Component name: 'ecs_cluster', template: 'ecs_cluster@latest', export_config: {'vpc' => 'vpc1'}
218
+ ```
219
+ ## CloudFormation mappings
220
+
221
+ [CloudFormation Mappings](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/mappings-section-structure.html)
222
+ section matches a key to a corresponding set of named values. Highlander allows you to define this mappings in two ways
223
+
224
+ 1. By using static maps defined through YAML files. Place `*.mappings.yaml` file alongside with highlander
225
+ template to define mappings this way. Mappings defined in a static way are automatically rendered withing CloudFormation
226
+ template E.g.
227
+
228
+ ```yaml
229
+ # Master component mappings
230
+ # envtype.mappings.yaml
231
+ EnvironmentType:
232
+ dev:
233
+ InstanceType: t2.medium
234
+ prod:
235
+ InstanceType: m4.medium
236
+ ```
237
+
238
+ 2. By defining mappings dynamically through Ruby code. Alongside with mappings, you can define default map name, and default
239
+ map key to be used when looking up value within this map. This mappings are usually rendered in outer component when inner
240
+ components pulls mapping value as parameter via `MappingParam` statement. Optionally, this mappings can be rendered within
241
+ component that defines them using `DynamicMappings` DSL statement.
242
+
243
+ ## Extensions
244
+
245
+ ### Cfndsl extensions
246
+
247
+ In order to make template more DRY, template developer may reuse ruby functions. It is possible to place
248
+ such functions in separate files. Any ruby files placed within `ext/cfndsl` directory will get automatically
249
+ included via Ruby `require` function in compiled Cfndsl template.
250
+
251
+ ## Component DSL
252
+
253
+
254
+ ### Parameters
255
+
256
+ Parameters block is used to define CloudFormation template parameters, and metadata on how they
257
+ are wired with outer or sibling components.
258
+
259
+ ```ruby
260
+ HighlanderComponent do
261
+ Parameters do
262
+ ##
263
+ ## parameter definitions here
264
+ ##
265
+ end
266
+ end
267
+ ```
268
+
269
+ Parameter block supports following parameters
270
+
271
+ #### ComponentParam
272
+
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
+
277
+ ```ruby
278
+
279
+ # Inner Component
280
+ HighlanderComponent do
281
+ Name 's3'
282
+ Parameters do
283
+ ComponentParam 'BucketName','highlander.example.com.au'
284
+ end
285
+ end
286
+ ```
287
+
288
+ ```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'}
295
+ end
296
+ ```
297
+
298
+ #### StackParam
299
+
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.
302
+
303
+
304
+ ```ruby
305
+ # Outer component
306
+ HighlanderComponent do
307
+ Component template:'s3',name:'s3'
308
+ end
309
+ ```
310
+
311
+ ```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
319
+ end
320
+ ```
321
+
322
+
323
+ Example above translates to following cfndsl template in outer component
324
+ ```ruby
325
+ CloudFormation do
326
+
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'
335
+ 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
+ })
346
+ end
347
+ end
348
+ ```
349
+
350
+
351
+ #### MappingParam
352
+
353
+ `MappingParam` - Mapping parameters value is passed as CloudFormation mapping lookup from outer component.
354
+ This DSL statements takes a full body, as Mapping name, Map key, and value key need to be specified. `key`,
355
+ `attribute` and `map` methods are used to specify these properties. Mapping parameters involve ruby code execution
356
+
357
+
358
+
359
+ ```ruby
360
+ # Inner component
361
+ HighlanderComponent do
362
+ Name 's3'
363
+ Parameters do
364
+ MappingParam 'BucketName' do
365
+ map 'AccountId'
366
+ attribute 'DnsDomain'
367
+ end
368
+ end
369
+ end
370
+ ```
371
+
372
+
373
+ #### OutputParam
374
+
375
+ TBD
376
+
377
+ ### DependsOn
378
+
379
+ `DependsOn` - this will include any globally exported libraries from given
380
+ template. E.g.
381
+
382
+ ```ruby
383
+ HighlanderComponent do
384
+ Name 's3'
385
+ DependsOn 'vpc@1.0.3'
386
+ end
387
+ ```
388
+
389
+ Will include any cfndsl libraries present and exported in vpc template
390
+ so extension methods can be consumed within cfndsl template.
391
+
392
+ ### LambdaFunctions
393
+
394
+ #### Packaging and publishing
395
+
396
+ #### Rendering
397
+
398
+ #### Referencing
399
+
400
+
401
+ ## Finding and loading components
402
+
403
+ ## Rendering CloudFormation templates
404
+
405
+
406
+
407
+ ## Global Extensions
408
+
409
+ Any extensions placed within `cfndsl_ext` folder will be
410
+ available in cfndsl templates of all components. Any extensions placed within `hl_ext` folder are
411
+ available in highlander templates of all components.
data/bin/cfhighlander ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative('./highlander')
data/bin/highlander.rb ADDED
@@ -0,0 +1,158 @@
1
+ ####
2
+ #### highlander publish [component_dir:-$PWD] (defaults to current dir) - (publishes highlander component)
3
+ #### highlander compile [component_dir:-$PWD] [--format json] - compile component to cloudformation template
4
+ #### highlander env create --name [stack_name] [component_name@version:-$PWD] create environment out of component (compile, deploy to s3, update)
5
+ #### highlander env update --name [stack_name] [component_name@version] update environment to component version (compile,deploy to s3, update)
6
+ #### highlander env delete --name [stack_name]
7
+ ####
8
+
9
+
10
+ require 'thor'
11
+ require 'rubygems'
12
+ require_relative '../lib//highlander.compiler'
13
+ require_relative '../lib/highlander.factory'
14
+ require_relative '../lib/highlander.publisher'
15
+ require_relative '../lib/highlander.validator'
16
+
17
+ class HighlanderCli < Thor
18
+
19
+
20
+
21
+ package_name "highlander"
22
+
23
+ desc 'configcompile component[@version]', 'Compile Highlander components configuration'
24
+
25
+ def configcompile(component_name)
26
+
27
+ # find and load component
28
+ component_loader = Highlander::Factory::ComponentFactory.new
29
+ component = component_loader.findComponent(component_name)
30
+ component.load
31
+
32
+ # compile cfndsl template
33
+ component_compiler = Highlander::Compiler::ComponentCompiler.new(component)
34
+ component_compiler.writeConfig(true)
35
+ end
36
+
37
+ desc 'dslcompile component[@version]', 'Compile Highlander component configuration and create cfndsl templates'
38
+ method_option :version, :type => :string, :required => false, :default => nil, :aliases => '-v',
39
+ :desc => 'Version to compile by which subcomponents are referenced'
40
+ method_option :dstbucket, :type => :string, :required => false, :default => nil,
41
+ :desc => 'Distribution S3 bucket'
42
+ method_option :dstprefix, :type => :string, :required => false, :default => nil,
43
+ :desc => 'Distribution S3 prefix'
44
+ method_option :format, :type => :string, :required => true, :default => 'yaml', :aliases => "-f",
45
+ :enum => %w(yaml json), :desc => 'CloudFormation templates output format'
46
+ method_option :quiet, :type => :boolean, :default => false, :aliases => '-q',
47
+ :desc => 'Silently agree on user prompts (e.g. Package lambda command)'
48
+
49
+ def dslcompile(component_name)
50
+ component = build_component(options, component_name)
51
+
52
+ # compile cfndsl template
53
+ component_compiler = Highlander::Compiler::ComponentCompiler.new(component)
54
+ component_compiler.silent_mode = options[:quiet]
55
+ out_format = options[:format]
56
+ component_compiler.compileCfnDsl out_format
57
+ end
58
+
59
+
60
+ desc 'cfcompile component[@version]', 'Compile Highlander component to CloudFormation templates'
61
+ method_option :version, :type => :string, :required => false, :default => nil, :aliases => '-v',
62
+ :desc => 'Version to compile by which subcomponents are referenced'
63
+ method_option :dstbucket, :type => :string, :required => false, :default => nil,
64
+ :desc => 'Distribution S3 bucket'
65
+ method_option :dstprefix, :type => :string, :required => false, :default => nil,
66
+ :desc => 'Distribution S3 prefix'
67
+ method_option :format, :type => :string, :required => true, :default => 'yaml', :aliases => "-f",
68
+ :enum => %w(yaml json), :desc => 'CloudFormation templates output format'
69
+ method_option :validate, :type => :boolean, :default => false,
70
+ :desc => 'Optionally validate template'
71
+ method_option :quiet, :type => :boolean, :default => false, :aliases => '-q',
72
+ :desc => 'Silently agree on user prompts (e.g. Package lambda command)'
73
+
74
+ def cfcompile(component_name)
75
+ component = build_component(options, component_name)
76
+
77
+ # compile cloud formation
78
+ component_compiler = Highlander::Compiler::ComponentCompiler.new(component)
79
+ component_compiler.silent_mode = options[:quiet]
80
+ out_format = options[:format]
81
+ component_compiler.compileCloudFormation out_format
82
+ if options[:validate]
83
+ component_validator = Highlander::Cloudformation::Validator.new(component)
84
+ component_validator.validate(component_compiler.cfn_template_paths, out_format)
85
+ end
86
+ component_compiler
87
+ end
88
+
89
+ desc 'cfpublish component[@version]', 'Publish CloudFormation template for component,
90
+ and it\' referenced subcomponents'
91
+ method_option :version, :type => :string, :required => false, :default => nil, :aliases => '-v',
92
+ :desc => 'Version to compile by which subcomponents are referenced'
93
+ method_option :dstbucket, :type => :string, :required => false, :default => nil,
94
+ :desc => 'Distribution S3 bucket'
95
+ method_option :dstprefix, :type => :string, :required => false, :default => nil,
96
+ :desc => 'Distribution S3 prefix'
97
+ method_option :format, :type => :string, :required => true, :default => 'yaml', :aliases => "-f",
98
+ :enum => %w(yaml json), :desc => 'CloudFormation templates output format'
99
+ method_option :validate, :type => :boolean, :default => false,
100
+ :desc => 'Optionally validate template'
101
+ method_option :quiet, :type => :boolean, :default => false, :aliases => '-q',
102
+ :desc => 'Silently agree on user prompts (e.g. Package lambda command)'
103
+
104
+ def cfpublish(component_name)
105
+ compiler = cfcompile(component_name)
106
+ publisher = Highlander::Publisher::Component.new(compiler.component, false)
107
+ publisher.publishFiles(compiler.cfn_template_paths + compiler.lambda_src_paths)
108
+ end
109
+
110
+
111
+ desc 'publish component[@version] [-v published_version]', 'Publish CloudFormation template for component,
112
+ and it\'s referenced subcomponents'
113
+ method_option :dstbucket, :type => :string, :required => false, :default => nil,
114
+ :desc => 'Distribution S3 bucket'
115
+ method_option :dstprefix, :type => :string, :required => false, :default => nil,
116
+ :desc => 'Distribution S3 prefix'
117
+ method_option :version, :type => :string, :required => false, :default => nil, :aliases => '-v',
118
+ :desc => 'Distribution component version, defaults to latest'
119
+
120
+ def publish(component_name)
121
+ component_version = options[:version]
122
+ distribution_bucket = options[:dstbucket]
123
+ distribution_prefix = options[:dstprefix]
124
+
125
+ # find and load component
126
+ component_loader = Highlander::Factory::ComponentFactory.new
127
+ component = component_loader.findComponent(component_name)
128
+ component.version = component_version
129
+ component.distribution_bucket = distribution_bucket unless distribution_bucket.nil?
130
+ component.distribution_prefix = distribution_prefix unless distribution_prefix.nil?
131
+ component.load
132
+
133
+ publisher = Highlander::Publisher::Component.new(component, true)
134
+ publisher.publishComponent
135
+ end
136
+
137
+ end
138
+
139
+ # build component from passed cli options
140
+ def build_component(options, component_name)
141
+ if ENV['HIGHLANDER_WORKDIR'].nil?
142
+ ENV['HIGHLANDER_WORKDIR'] = Dir.pwd
143
+ end
144
+ component_version = options[:version]
145
+ distribution_bucket = options[:dstbucket]
146
+ distribution_prefix = options[:dstprefix]
147
+
148
+ # find and load component
149
+ component_loader = Highlander::Factory::ComponentFactory.new
150
+ component = component_loader.findComponent(component_name)
151
+ component.version = component_version unless component_version.nil?
152
+ component.distribution_bucket = distribution_bucket unless distribution_bucket.nil?
153
+ component.distribution_prefix = distribution_prefix unless distribution_prefix.nil?
154
+ component.load
155
+ component
156
+ end
157
+
158
+ HighlanderCli.start