cfhighlander 0.7.0.alpha.1544216666 → 0.8.0.alpha.1550191162

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: 82637e017954b006eaffddbb6b2358a7416e625823f1a5ca1c13e2e363945f86
4
- data.tar.gz: 5fca2570d5d86a16989c079b790e02e15d7cc10cfad7f9584316edd27ad8f466
3
+ metadata.gz: d104f29b677e85d2bc647c37b07afcb0d7dc085e97d03845a5e6fefb197ac36c
4
+ data.tar.gz: 440af326438f2ea24845afc1eda9dfe938509c5b7140580a4e46e2a7fbd351ee
5
5
  SHA512:
6
- metadata.gz: fca8e6e40cfb6730fa4bba2d084cc2645732fce96f0314537380197f7e9165be5c55f80e1b8915cbc3901b050d8d18fc0b9bd234c4d52063a041836b45666cb8
7
- data.tar.gz: be3db85a83ccefb1373a36353584fc1e148878ab6c95822a11b8295bc12e8db8e38164d8302f211498e8f640984ebaffcd509a45c7fc1afdc184cb0015d2e019
6
+ metadata.gz: b6df11c0ee32110b5b4157caee01343e6f3ca3bb744bb373bc85118ca015ce5c45a26cda164b691b287e10ee36e1ba2e1b1be35f1dc24bfbdd6d116e47231797
7
+ data.tar.gz: 5af50e51cc21cea03f6ed502ff9d37e0e2199dd1bd43ff3cfd676182b709c4c83e75af5fae2ecdd4ceb13851a0b0d4fc50eabef149ac6bd793d65cf1b6f0e3f5
data/README.md CHANGED
@@ -77,7 +77,7 @@ end
77
77
 
78
78
  ```
79
79
 
80
- ... compile the template with ...
80
+ ... compile the template with ...
81
81
 
82
82
  ```shell
83
83
  cfcompile application
@@ -120,8 +120,8 @@ used in conjuction with ECS Cluster
120
120
  - [rds-postgres](https://github.com/theonestack/hl-component-rds-postgres) - RDS Component for Postgres engine
121
121
  - [aurora-mysql](https://github.com/theonestack/hl-component-aurora-mysql) - Aurora component for MySQL engine
122
122
  - [aurora-postgres](https://github.com/theonestack/hl-component-aurora-postgres) - Aurora component for Postgres engine
123
- - [elasticache-memcache](https://github.com/theonestack/hl-component-elasticache-memcache) - Aws Elasticache - Memcache engine
124
- - [elasticache-memcache](https://github.com/theonestack/hl-component-elasticache-redis) - Aws Elasticache - Redis engine
123
+ - [elasticache-memcache](https://github.com/theonestack/hl-component-elasticache-memcache) - Aws Elasticache - Memcache engine
124
+ - [elasticache-memcache](https://github.com/theonestack/hl-component-elasticache-redis) - Aws Elasticache - Redis engine
125
125
  - [asg](https://github.com/theonestack/hl-component-asg) - AutoScalingGroup component
126
126
  - [cognito](https://github.com/theonestack/hl-component-cognito) - Cognito user pools, custom domain names and clients
127
127
 
@@ -134,7 +134,7 @@ From shell, command below will generate cloudformation for given component in `o
134
134
  cfcompile component_name
135
135
  ```
136
136
 
137
- Or from outer cfhighlander template, just pull component using `Component` DSL statement
137
+ Or from outer cfhighlander template, just pull component using `Component` DSL statement
138
138
 
139
139
  ```ruby
140
140
  CfhighlanderTemplate do
@@ -264,9 +264,9 @@ via CLI (`--dstbucket`, `--dstprefix`, `-v`). Default distribution bucket and pr
264
264
  for more details on this statements. Version defaults to `latest` if not explicitly given using `-v` switch
265
265
 
266
266
  If no distribution options is given using mentioned CLI options or DSL statements,
267
- bucket will be automatically created for you. Bucket name defaults to
267
+ bucket will be automatically created for you. Bucket name defaults to
268
268
  `$ACCOUNT.$REGION.cfhighlander.templates`, with `/published-templates`
269
- prefix.
269
+ prefix.
270
270
 
271
271
  *cfpublish* command will give you quick launch CloudFirmation stack URL to assist
272
272
  you in creating your stack:
@@ -710,7 +710,7 @@ This is also default render mode - if no render mode is specified `Substack` wil
710
710
 
711
711
  `Inline` - places all defined resources from inner component in outer component cloudformation template. Resources,
712
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.
713
+ process in order to assure unique names.
714
714
 
715
715
  There are some limitations when using inline components - Inlined component parameters, having values as outputs from another component (inlined or not)
716
716
  can't be referenced in component conditions. However, conditions referencing mapping values or parameters passed as mapping values,
@@ -722,13 +722,13 @@ are allowed.
722
722
  by referencing Zone Name (rather than ZoneId), meaning there is no explicit dependency between the resources. When both components
723
723
  are rendered as substack, implicit dependency is created if there is at least one output from component A passed as parameter
724
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.
725
+ may be halted, as record set is being created/deleted before prior the record set.
726
726
 
727
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
728
  when rendering inner components inlined.
729
729
 
730
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.
731
+ will be referenced as substack in example below.
732
732
 
733
733
  ```ruby
734
734
  CfhighlanderTemplate do
@@ -760,6 +760,58 @@ available in cfhighlander templates of all components.
760
760
  `CFHIGHLANDER_WORKDIR` - defaults to $PWD, determines location of 'out' folder where all of the
761
761
  generated files are placed
762
762
 
763
- `CFHIGHLANDER_AWS_RETRY_LIMIT` - defaults to 10. Number of retries for AWS SDK before giving up.
763
+ `CFHIGHLANDER_AWS_RETRY_LIMIT` - defaults to 10. Number of retries for AWS SDK before giving up.
764
764
  AWS SDK uses exponential backoff to make the API calls
765
-
765
+
766
+
767
+
768
+ ## Testing components
769
+
770
+ Tests are designed for testing different configuration options on components.
771
+ They can be defined as `my_test.test.yaml` files in the `tests/` directory.
772
+ Each test file represents a single test configuration which is then compiled
773
+ and validated against AWS cloudformation api.
774
+
775
+ ### Metadata
776
+
777
+ test metadata needs to defined as bellow with at least a `name:`. Other key:values are for documentation.
778
+
779
+ ```yaml
780
+ test_metadata:
781
+ type: config
782
+ name: queues with config overrides
783
+ description: Create 2 queues with name and override available config
784
+ ```
785
+
786
+ ### Running Test
787
+
788
+ ```bash
789
+ $ cfhighlander cftest [component] [options]
790
+ ```
791
+
792
+ ```bash
793
+ Usage:
794
+ cfhighlander cftest component[@version] -f, --format=FORMAT
795
+
796
+ Options:
797
+ -d, [--directory=DIRECTORY] # Tests directory
798
+ # Default: tests
799
+ -t, [--tests=one two three] # Point to specific test files using the relative path
800
+ [--dstbucket=DSTBUCKET] # Distribution S3 bucket
801
+ [--dstprefix=DSTPREFIX] # Distribution S3 prefix
802
+ -f, --format=FORMAT # CloudFormation templates output format
803
+ # Default: yaml
804
+ # Possible values: yaml, json
805
+ [--validate], [--no-validate] # Optionally validate template
806
+ # Default: true
807
+ -q, [--quiet], [--no-quiet] # Silently agree on user prompts (e.g. Package lambda command)
808
+ # Default: true
809
+ -r, [--report=REPORT] # report output format in reports directory
810
+ # Possible values: json, xml
811
+
812
+ Test Highlander component with test case config
813
+ ```
814
+
815
+ ### Reports
816
+
817
+ By default test will print the output to stdout. You can output to a file with a format of xml or json using the `-r` option
data/bin/cfhighlander.rb CHANGED
@@ -10,16 +10,24 @@
10
10
  require 'thor'
11
11
  require 'rubygems'
12
12
  require 'aws-sdk-core'
13
+ require_relative '../lib/cfhighlander.version'
13
14
  require_relative '../lib/cfhighlander.compiler'
14
15
  require_relative '../lib/cfhighlander.factory'
15
16
  require_relative '../lib/cfhighlander.publisher'
16
17
  require_relative '../lib/cfhighlander.validator'
18
+ require_relative '../lib/cfhighlander.tests'
17
19
  require_relative '../hl_ext/aws_helper'
18
20
 
19
21
  class HighlanderCli < Thor
20
22
 
21
23
  package_name "cfhighlander"
22
24
 
25
+ map %w[--version -v] => :__print_version
26
+ desc "--version, -v", "print the version"
27
+ def __print_version
28
+ puts Cfhighlander::VERSION
29
+ end
30
+
23
31
  desc 'configcompile component[@version]', 'Compile Highlander components configuration'
24
32
 
25
33
  def configcompile(template_name)
@@ -121,7 +129,7 @@ class HighlanderCli < Thor
121
129
 
122
130
  def cfpublish(component_name)
123
131
  compiler = cfcompile(component_name, true)
124
- publisher = Cfhighlander::Publisher::ComponentPublisher.new(compiler.component, false)
132
+ publisher = Cfhighlander::Publisher::ComponentPublisher.new(compiler.component, false, options[:format])
125
133
  publisher.publishFiles(compiler.cfn_template_paths + compiler.lambda_src_paths)
126
134
 
127
135
  puts "\n\nUse following url to launch CloudFormation stack\n\n#{publisher.getLaunchStackUrl}\n\n"
@@ -152,14 +160,116 @@ class HighlanderCli < Thor
152
160
  component.distribution_prefix = distribution_prefix unless distribution_prefix.nil?
153
161
  component.load
154
162
 
155
- publisher = Cfhighlander::Publisher::ComponentPublisher.new(component, true)
163
+ publisher = Cfhighlander::Publisher::ComponentPublisher.new(component, true, 'yaml')
156
164
  publisher.publishComponent
157
165
  end
158
166
 
167
+ desc 'cftest component[@version]', 'Test Highlander component with test case config'
168
+ method_option :directory, :type => :string, :required => false, :default => 'tests', :aliases => "-d",
169
+ :desc => 'Tests directory'
170
+ method_option :tests, :type => :array, :required => false, :aliases => "-t",
171
+ :desc => 'Point to specific test files using the relative path'
172
+ method_option :dstbucket, :type => :string, :required => false, :default => nil,
173
+ :desc => 'Distribution S3 bucket'
174
+ method_option :dstprefix, :type => :string, :required => false, :default => nil,
175
+ :desc => 'Distribution S3 prefix'
176
+ method_option :format, :type => :string, :required => true, :default => 'yaml', :aliases => "-f",
177
+ :enum => %w(yaml json), :desc => 'CloudFormation templates output format'
178
+ method_option :validate, :type => :boolean, :default => true,
179
+ :desc => 'Optionally validate template'
180
+ method_option :quiet, :type => :boolean, :default => true, :aliases => '-q',
181
+ :desc => 'Silently agree on user prompts (e.g. Package lambda command)'
182
+ method_option :report, :type => :string, :aliases => '-r', :enum => ['json','xml'],
183
+ :desc => 'report output format in reports directory'
184
+
185
+ def cftest(component_name = nil, autogenerate_dist = false)
186
+
187
+ tests_start = Time.now
188
+
189
+ if component_name.nil?
190
+ candidates = Dir["*.cfhighlander.rb"]
191
+ if candidates.size == 0
192
+ self.help('cftest')
193
+ exit -1
194
+ else
195
+ component_name = candidates[0].gsub('.cfhighlander.rb','')
196
+ end
197
+ end
198
+
199
+ tests = CfHighlander::Tests.new(component_name,options)
200
+ tests.timestamp = Time.now.strftime('%Y-%m-%dT%H:%M:%S.%L%z')
201
+ tests.get_cases
202
+
203
+ tests.cases.each do |test|
204
+ test_name = test[:metadata]['name']
205
+ component = build_component(options, component_name, test[:config])
206
+ failure = false
207
+
208
+ if component.highlander_dsl.distribution_bucket.nil? or component.highlander_dsl.distribution_prefix.nil?
209
+ component.distribution_bucket="#{aws_account_id()}.#{aws_current_region()}.cfhighlander.templates" if component.distribution_bucket.nil?
210
+ component.distribution_prefix="published-templates/#{component.name}" if component.distribution_prefix.nil?
211
+ puts "INFO: Reloading component, as auto-generated distribution settings are being applied..."
212
+ component.load
213
+ end if autogenerate_dist
214
+
215
+ # compile cloud formation
216
+ component_compiler = Cfhighlander::Compiler::ComponentCompiler.new(component)
217
+ component_compiler.cfn_output_location = "#{ENV['CFHIGHLANDER_WORKDIR']}/out/tests/#{test_name.gsub(' ','_')}"
218
+ component_compiler.silent_mode = options[:quiet]
219
+ out_format = options[:format]
220
+
221
+ start = Time.now
222
+ begin
223
+ component_compiler.compileCloudFormation out_format
224
+ rescue => e
225
+ failure = {message: e.message, type: 'Cfhighlander::Compiler::ComponentCompiler'}
226
+ end
227
+ finish = Time.now
228
+
229
+ tests.report << {
230
+ name: test_name,
231
+ test: test[:file],
232
+ type: 'compile',
233
+ failure: failure,
234
+ time: (finish - start).to_s
235
+ }
236
+ next if failure
237
+
238
+ if options[:validate]
239
+ start = Time.now
240
+ begin
241
+ component_validator = Cfhighlander::Cloudformation::Validator.new(component)
242
+ component_validator.validate(component_compiler.cfn_template_paths, out_format)
243
+ rescue Aws::CloudFormation::Errors::ValidationError => e
244
+ failure = {message: e.message, type: 'Cfhighlander::Cloudformation::Validator'}
245
+ end
246
+ finish = Time.now
247
+ end
248
+
249
+ tests.report << {
250
+ name: test_name,
251
+ test: test[:file],
252
+ type: 'Validation',
253
+ failure: failure,
254
+ time: (finish - start).to_s
255
+ }
256
+
257
+ component_compiler
258
+ end
259
+
260
+ tests_finish = Time.now
261
+ tests.time = (tests_finish - tests_start)
262
+
263
+ tests.generate_report(options[:report]) if options[:report]
264
+ tests.print_results
265
+ exit tests.exit_code
266
+
267
+ end
268
+
159
269
  end
160
270
 
161
271
  # build component from passed cli options
162
- def build_component(options, template_name)
272
+ def build_component(options, template_name, config=nil)
163
273
 
164
274
  component_version = options[:version]
165
275
  distribution_bucket = options[:dstbucket]
@@ -169,6 +279,7 @@ def build_component(options, template_name)
169
279
  component_loader = Cfhighlander::Factory::ComponentFactory.new
170
280
  component = component_loader.loadComponentFromTemplate(template_name)
171
281
  component.version = component_version unless component_version.nil?
282
+ component.config = config unless config.nil?
172
283
  component.distribution_bucket = distribution_bucket unless distribution_bucket.nil?
173
284
  component.distribution_prefix = distribution_prefix unless distribution_prefix.nil?
174
285
  component.load
@@ -89,7 +89,8 @@ module Cfhighlander
89
89
  cfn_template = renderer.result(OpenStruct.new({
90
90
  'dsl' => dsl,
91
91
  'component_cfndsl' => component_cfndsl,
92
- 'component_requires' => (@@global_extensions_paths + @component.cfndsl_ext_files)
92
+ 'component_requires' => (@@global_extensions_paths + @component.cfndsl_ext_files),
93
+ 'distribution_format' => out_format
93
94
  }).instance_eval {binding})
94
95
 
95
96
  # write to output file
@@ -129,7 +130,7 @@ module Cfhighlander
129
130
  dsl = @component.highlander_dsl
130
131
 
131
132
  # create out dir if not there
132
- @cfn_output_location = "#{@workdir}/out/#{format}"
133
+ @cfn_output_location = "#{@workdir}/out/#{format}" if @cfn_output_location.nil?
133
134
  output_dir = @cfn_output_location
134
135
  FileUtils.mkdir_p(output_dir) unless Dir.exist?(output_dir)
135
136
 
@@ -522,4 +522,4 @@ end
522
522
  def HighlanderComponent(&block)
523
523
  STDERR.puts("DEPRECATED: #{@template.template_name}: use CfhighlanderTemplate instead of HighlanderComponent")
524
524
  CfhighlanderTemplate(&block)
525
- end
525
+ end
@@ -7,9 +7,10 @@ module Cfhighlander
7
7
 
8
8
  class ComponentPublisher
9
9
 
10
- def initialize(component, cleanup)
10
+ def initialize(component, cleanup, template_format)
11
11
  @component = component
12
12
  @cleanup_destination = cleanup
13
+ @template_format = template_format
13
14
  end
14
15
 
15
16
  def publishComponent
@@ -44,7 +45,7 @@ module Cfhighlander
44
45
  template_url = "https://#{@component.highlander_dsl.distribution_bucket}.s3.amazonaws.com/"
45
46
  template_url += @component.highlander_dsl.distribution_prefix + "/"
46
47
  template_url += @component.highlander_dsl.version
47
- template_url += "/#{@component.name}.compiled.yaml"
48
+ template_url += "/#{@component.name}.compiled.#{@template_format}"
48
49
  return template_url
49
50
  end
50
51
 
@@ -0,0 +1,114 @@
1
+ require 'yaml'
2
+ require 'json'
3
+
4
+ module CfHighlander
5
+ class Tests
6
+
7
+ attr_accessor :cases,
8
+ :report,
9
+ :exit_code,
10
+ :report_path,
11
+ :time,
12
+ :timestamp
13
+
14
+ def initialize(component_name,options)
15
+ @component_name = component_name
16
+ @tests_dir = options[:directory]
17
+ @test_files = options[:tests] || Dir["#{@tests_dir}/*.test.yaml"]
18
+ @report_path = "reports"
19
+ @debug = false
20
+ @cases = []
21
+ @report = []
22
+ @time = nil
23
+ @timestamp = nil
24
+ @exit_code = 0
25
+ end
26
+
27
+ def get_cases
28
+ @test_files.each do |file|
29
+ test_case = load_test_case(file)
30
+ @cases << { metadata: test_case['test_metadata'], file: file, config: test_case }
31
+ end
32
+ end
33
+
34
+ def load_test_case(file)
35
+ begin
36
+ YAML.load(File.read(file))
37
+ rescue Errno::ENOENT => e
38
+ abort "No test file found for #{file}"
39
+ end
40
+ end
41
+
42
+ def print_results
43
+ failures = @report.select { |k,v| k[:failure] }
44
+ puts "\n\s\s============================"
45
+ puts "\s\s# CfHighlander Tests #"
46
+ puts "\s\s============================\n\n"
47
+ puts "\s\sPass: #{cases.size - failures.size}"
48
+ puts "\s\sFail: #{failures.size}"
49
+ puts "\s\sTime: #{@time}\n\n"
50
+
51
+ if failures.any?
52
+ @exit_code = 1
53
+ puts "\s\s=========Failures========="
54
+ failures.each do |failure|
55
+ puts "\s\sName: #{failure[:name]}"
56
+ puts "\s\sTest: #{failure[:test]}"
57
+ puts "\s\sType: #{failure[:type]}"
58
+ puts "\s\sMessage: #{failure[:failure][:message]}"
59
+ puts "\s\s-------------------------"
60
+ end
61
+ end
62
+ end
63
+
64
+
65
+ def report_dir
66
+ FileUtils.mkdir_p(@report_path) unless Dir.exist?(@report_path)
67
+ end
68
+
69
+ def generate_report(type)
70
+ report_dir
71
+ failures = @report.select { |k,v| k[:message] }
72
+ report = {}
73
+ report[:component] = @component_name
74
+ report[:tests] = cases.size.to_s
75
+ report[:pass] = (cases.size - failures.size).to_s
76
+ report[:failures] = failures.size.to_s
77
+ report[:time] = @time.to_s
78
+ report[:timestamp] = @timestamp.to_s
79
+ report[:testcases] = @report
80
+ case type
81
+ when 'json'
82
+ report_json(report)
83
+ when 'xml'
84
+ report_xml(report)
85
+ end
86
+ end
87
+
88
+ def report_xml(report)
89
+ testsuite = report.map { |k,v| "#{k}=\"#{v}\"" if k != :testcases }
90
+ File.open("reports/report.xml","w") do |f|
91
+ f.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
92
+ f.write("<testsuite #{testsuite.join(' ')}>\n")
93
+ report[:testcases].each do |test|
94
+ testcase = test.map { |k,v| "#{k}=\"#{v}\"" if k != :failure }
95
+ if test[:failure]
96
+ f.write("\t<testcase #{testcase.join(' ')}>\n")
97
+ f.write("\t\t<failure message=\"#{test[:failure][:message]}\" type=\"#{test[:failure][:type]}\"/>\n")
98
+ f.write("\t</testcase>\n")
99
+ else
100
+ f.write("\t<testcase #{testcase.join(' ')}/>\n")
101
+ end
102
+ end
103
+ f.write("</testsuite>")
104
+ end
105
+ end
106
+
107
+ def report_json(report)
108
+ File.open("reports/report.json","w") do |f|
109
+ f.write(JSON.pretty_generate(report))
110
+ end
111
+ end
112
+
113
+ end
114
+ end
@@ -0,0 +1,3 @@
1
+ module Cfhighlander
2
+ VERSION="0.8.0".freeze
3
+ end
@@ -68,4 +68,11 @@ CloudFormation do
68
68
  <% for @condition in dsl.conditions %> Condition('<%= @condition.name %>', <%= @condition.expression %>)
69
69
  <% end %>
70
70
  Description '<%= dsl.description %>'
71
+
72
+ Output('CfTemplateUrl') {
73
+ Value("<%=dsl.distribute_url%>/<%=dsl.name%>.compiled.<%=distribution_format%>")
74
+ }
75
+ Output('CfTemplateVersion') {
76
+ Value("<%=dsl.version%>")
77
+ }
71
78
  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.7.0.alpha.1544216666
4
+ version: 0.8.0.alpha.1550191162
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-12-07 00:00:00.000000000 Z
13
+ date: 2019-02-15 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: highline
@@ -273,7 +273,9 @@ files:
273
273
  - lib/cfhighlander.model.component.rb
274
274
  - lib/cfhighlander.model.templatemeta.rb
275
275
  - lib/cfhighlander.publisher.rb
276
+ - lib/cfhighlander.tests.rb
276
277
  - lib/cfhighlander.validator.rb
278
+ - lib/cfhighlander.version.rb
277
279
  - lib/util/cloudformation.util.rb
278
280
  - lib/util/debug.util.rb
279
281
  - lib/util/zip.util.rb
@@ -297,8 +299,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
297
299
  - !ruby/object:Gem::Version
298
300
  version: 1.3.1
299
301
  requirements: []
300
- rubyforge_project:
301
- rubygems_version: 2.7.8
302
+ rubygems_version: 3.0.2
302
303
  signing_key:
303
304
  specification_version: 4
304
305
  summary: DSL on top of cfndsl. Manage libraries of cloudformation components