cfhighlander 0.2.0.alpha.10

Sign up to get free protection for your applications and to get access to all the features.
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