kitchen-sparkleformation 0.1.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 92ae337395999426b1a24e36f5b4e6f8902de8ba
4
+ data.tar.gz: 0b581f423224aaa77e2ab0759e01a327bb17d5f9
5
+ SHA512:
6
+ metadata.gz: dbc1291477d25c3ebeaae2a714d57ce2d32c372eda6e5de763830022ee77e3dd14b43edc32afa9a22e93d32c31bec0c84def1aacb9feb844454c25271f2ff2a3
7
+ data.tar.gz: 4a2e32b7f48d23c947b2caa2c181aaa043bb5a686d9a8ca828dab71e92830c47385e63e9637e77e9ad225777a27cffa2078077be85ac0096f2e4f7b5f3570f5c
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
@@ -0,0 +1,39 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ kitchen-sparkleformation (0.1)
5
+ aws-sdk (>= 2, < 3)
6
+ sparkle_formation (>= 3, < 4)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ attribute_struct (0.3.4)
12
+ bogo (>= 0.1.31, < 0.3.0)
13
+ aws-sdk (2.9.14)
14
+ aws-sdk-resources (= 2.9.14)
15
+ aws-sdk-core (2.9.14)
16
+ aws-sigv4 (~> 1.0)
17
+ jmespath (~> 1.0)
18
+ aws-sdk-resources (2.9.14)
19
+ aws-sdk-core (= 2.9.14)
20
+ aws-sigv4 (1.0.0)
21
+ bogo (0.2.8)
22
+ hashie
23
+ multi_json
24
+ hashie (3.5.5)
25
+ jmespath (1.3.1)
26
+ multi_json (1.12.1)
27
+ sparkle_formation (3.0.20)
28
+ attribute_struct (>= 0.3.0, < 0.5)
29
+ bogo
30
+ multi_json
31
+
32
+ PLATFORMS
33
+ ruby
34
+
35
+ DEPENDENCIES
36
+ kitchen-sparkleformation!
37
+
38
+ BUNDLED WITH
39
+ 1.12.5
@@ -0,0 +1,59 @@
1
+ # SparkleFormation driver for test-kitchen
2
+
3
+ ## Installation
4
+
5
+ Put this in your Gemfile:
6
+ ```ruby
7
+ gem 'kitchen-sparkleformation', git: 'https://github.com/devkid/kitchen-sparkleformation'
8
+ ```
9
+
10
+ and run:
11
+ ```
12
+ $ bundle
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ `.kitchen.yml` configuration:
18
+
19
+ ```yaml
20
+ driver:
21
+ name: sparkleformation
22
+ stack_name: kitchen-test
23
+ stack_name_random_suffix: true
24
+ sparkle_packs:
25
+ - some_sparkle_pack
26
+ sparkle_template: my_template_name
27
+ upload_template: true
28
+ s3_region: us-west-1
29
+ s3_bucket: some-bucket
30
+ s3_path: cloudformation/kitchen-tmp
31
+ cf_params:
32
+ some_parameter: some_value
33
+ cf_options:
34
+ :disable_rollback: true
35
+ ```
36
+
37
+ ## Configuration
38
+
39
+ | Option | Description | Default Value | Required? |
40
+ |----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------|----------------------------------|
41
+ | `stack_name` | The name of the CloudFormation stack to create. | | yes |
42
+ | `stack_name_random_suffix` | If true, append a random suffix string to the given stack name. | `false` | |
43
+ | `sparkle_path` | The path to a directory containing your SparkleFormation template files. | `'sparkleformation'` | |
44
+ | `sparkle_template` | The name of the SparkleFormation template to use. | | yes |
45
+ | `sparkle_state` | A hash of compile time parameters that are passed to SparkleFormation. | `{}` | |
46
+ | `sparkle_packs` | Array of SparklePacks to load before compiling the template. | `[]` | |
47
+ | `upload_template` | If true, upload the template to S3. Requires `s3_region`, `s3_bucket`. | `false` | `true` if using nested templates |
48
+ | `s3_region` | Region of given S3 bucket. | | if `upload_template` is true |
49
+ | `s3_bucket` | Name of an S3 bucket where templates are uploaded to. | | if `upload_template` is true |
50
+ | `s3_path` | Path in the S3 bucket where templates are uploaded to. | | if `upload_template` is true |
51
+ | `hostname_output` | Extract the hostname of an instance from the value of the given output. | | no |
52
+ | `hostname_resource` | The stack resource to use to extract the hostname from. | | no |
53
+ | `hostname_attribute` | The attribute of a the given stack resource to extract the hostname from. Can be `'<physical_resource_id>'` to use the Physical ID of the given stack resource (e.g. Route53 record). | | no |
54
+ | `cf_params` | Runtime parameters to pass to CloudFormation stack. | `{}` | no |
55
+ | `cf_options` | Additional options to pass to CloudFormation stack creation. See [here](http://docs.aws.amazon.com/sdkforruby/api/Aws/CloudFormation/Client.html#create_stack-instance_method). | `{}` | no |
56
+
57
+ If neither `hostname_output` nor `hostname_resource` is given, the first EC2 instance in the stack is used to extract the hostname.
58
+
59
+ If `hostname_attribute` is set to something other than `'<physical_resource_id>'`, the given `hostname_resource` must be an EC2 instance. See [here](http://docs.aws.amazon.com/sdkforruby/api/Aws/EC2/Client.html#describe_instances-instance_method) for a list of available attributes. By default, `private_ip_address` is used.
@@ -0,0 +1,13 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'kitchen-sparkleformation'
3
+ s.version = "0.1.0"
4
+ s.licenses = %w(GPL-2.0)
5
+ s.homepage = 'https://github.com/devkid/kitchen-sparkleformation'
6
+ s.summary = 'Kitchen driver for SparkleFormation / CloudFormation'
7
+ s.description = 'Kitchen driver for creating CloudFormation stacks using SparkleFormation templates'
8
+ s.authors = ['Alfred Krohmer']
9
+ s.email = 'devkid@gmx.net'
10
+ s.files = %w(README.md kitchen-sparkleformation.gemspec Gemfile Gemfile.lock) + Dir['lib/**/*']
11
+ s.add_runtime_dependency 'sparkle_formation', '>= 3', '< 4'
12
+ s.add_runtime_dependency 'aws-sdk', '>= 2', '< 3'
13
+ end
@@ -0,0 +1,184 @@
1
+ require 'aws-sdk'
2
+ require 'sparkle_formation'
3
+ require 'securerandom'
4
+
5
+ module Kitchen
6
+ module Driver
7
+ class Sparkleformation < Kitchen::Driver::Base
8
+ default_config :stack_name_random_suffix, false
9
+ default_config :sparkle_path, 'sparkleformation'
10
+ default_config :sparkle_state, {}
11
+ default_config :sparkle_packs, []
12
+ default_config :upload_template, false
13
+ default_config :cf_options, {}
14
+ default_config :cf_params, {}
15
+ default_config :hostname_output, nil
16
+ default_config :hostname_resource, nil
17
+ default_config :hostname_attribute, nil
18
+
19
+ def initialize(config)
20
+ init_config config
21
+ @cf = Aws::CloudFormation::Client.new
22
+ end
23
+
24
+ def create(state)
25
+ unless state[:stack_id].nil?
26
+ # will fail if the stack does not exist:
27
+ @stack_desc = @cf.describe_stacks(stack_name: state[:stack_id]).stacks.first
28
+
29
+ case @stack_desc.stack_status
30
+ when 'CREATE_IN_PROGRESS'
31
+ @cf.wait_until :stack_create_complete, stack_name: state[:stack_id]
32
+ when 'CREATE_COMPLETE'
33
+ else
34
+ raise "Invalid stack state: #{@stack_desc.stack_status}"
35
+ end
36
+
37
+ if state[:hostname].nil?
38
+ state[:hostname] = hostname(state[:stack_id])
39
+ end
40
+
41
+ return
42
+ end
43
+
44
+ # generate stack name
45
+ @stack_name = config[:stack_name]
46
+ if config[:stack_name_random_suffix]
47
+ @stack_name << "-#{SecureRandom.hex(6)}"
48
+ end
49
+
50
+ json = generate_cloudformation_template
51
+
52
+ if config[:upload_template]
53
+ url = upload_template @stack_name, json
54
+ end
55
+
56
+ # build options for call to create_stack
57
+ stack_options = {
58
+ stack_name: @stack_name,
59
+ parameters: config[:cf_params].map { |k, v| { parameter_key: SparkleFormation.camel(k), parameter_value: v } }
60
+ }
61
+ if config[:upload_template]
62
+ stack_options[:template_url] = url
63
+ else
64
+ stack_options[:template_body] = json.to_json
65
+ end
66
+ stack_options.merge! config[:cf_options]
67
+
68
+ info 'Triggering CloudFormation stack creation'
69
+ state[:stack_id] = @cf.create_stack(stack_options).stack_id
70
+
71
+ info "Waiting for stack #{state[:stack_id]} to reach state CREATE_COMPLETE..."
72
+ @cf.wait_until :stack_create_complete, stack_name: state[:stack_id]
73
+ info 'Stack creation finished'
74
+
75
+ state[:hostname] = hostname(state[:stack_id])
76
+ end
77
+
78
+ def destroy(state)
79
+ return if state[:stack_id].nil?
80
+
81
+ info 'Triggering CloudFormation stack deletion'
82
+ @cf.delete_stack stack_name: state[:stack_id]
83
+
84
+ info 'Waiting for stack to reach state DELETE_COMPLETE...'
85
+ @cf.wait_until :stack_delete_complete, stack_name: state[:stack_id]
86
+
87
+ info 'Stack deletion finished'
88
+ end
89
+
90
+ private
91
+
92
+ def generate_cloudformation_template
93
+ info 'Generating CloudFormation template'
94
+
95
+ SparkleFormation.sparkle_path = config[:sparkle_path]
96
+
97
+ formation = SparkleFormation.compile(config[:sparkle_template], :sparkle)
98
+ config[:sparkle_packs].each do |pack|
99
+ require pack
100
+ formation.sparkle.add_sparkle SparkleFormation::SparklePack.new name: pack
101
+ end
102
+ formation.compile state: config[:sparkle_state]
103
+
104
+ # nesting
105
+ formation.apply_nesting do |stack_name, nested_stack_sfn, original_stack_resource|
106
+ unless config[:upload_template]
107
+ raise 'Nested stacks require template upload'
108
+ end
109
+
110
+ info "Generating nested stack template for #{stack_name}"
111
+ dump = nested_stack_sfn.compile.dump!
112
+
113
+ url = upload_template "#{@stack_name}-#{stack_name}", dump
114
+
115
+ # update original stack
116
+ original_stack_resource.properties.delete!(:stack)
117
+ original_stack_resource.properties.set!('TemplateURL', url)
118
+ end
119
+
120
+ formation.dump
121
+ end
122
+
123
+ def upload_template(stack_name, json)
124
+ raise 's3_region not given' if config[:s3_region].nil?
125
+ raise 's3_bucket not given' if config[:s3_bucket].nil?
126
+ path = "#{config[:s3_path]}/#{stack_name}.json"
127
+ info "Uploading CloudFormation template to s3://#{config[:s3_bucket]}/#{path}"
128
+ s3_client = Aws::S3::Client.new(region: config[:s3_region])
129
+ bucket = Aws::S3::Resource.new(client: s3_client).bucket(config[:s3_bucket])
130
+ bucket.put_object(
131
+ key: path,
132
+ body: json.to_json
133
+ ).public_url
134
+ end
135
+
136
+ def stack_resources(stack_id)
137
+ resources = []
138
+ next_token = nil
139
+ loop do
140
+ response = @cf.list_stack_resources(stack_name: stack_id, next_token: next_token)
141
+ resources += response.stack_resource_summaries
142
+ break if (next_token = response.next_token).nil?
143
+ end
144
+ resources
145
+ end
146
+
147
+ def hostname(stack_id)
148
+ unless config[:hostname_output].nil?
149
+ @stack_desc ||= @cf.describe_stacks(stack_name: state[:stack_id]).stacks.first
150
+ output = @stack_desc.outputs.find { |o| o.output_key == config[:hostname_output] }
151
+ raise "Output »#{config[:hostname_output]}« not found in stack" if output.nil?
152
+ return output.output_value
153
+ end
154
+
155
+ resources = stack_resources stack_id
156
+
157
+ if config[:hostname_resource].nil?
158
+ resource = resources.find { |r| r.resource_type == 'AWS::EC2::Instance' }
159
+ raise 'Stack does not contain an EC2 instance' if resource.nil?
160
+ else
161
+ resource = resources.find { |r| r.logical_resource_id == config[:hostname_resource] }
162
+ raise "Resource »#{config[:hostname_resource]}« not found in stack" if resource.nil?
163
+ end
164
+
165
+ if config[:hostname_attribute] == '<physical_resource_id>'
166
+ return resource.physical_resource_id
167
+ end
168
+
169
+ unless resource.resource_type == 'AWS::EC2::Instance'
170
+ raise "Resource »#{config[:hostname_resource]}« is not of type AWS::EC2::Instance"
171
+ end
172
+
173
+ ec2 = Aws::EC2::Client.new
174
+ instance = ec2.describe_instances(instance_ids: [resource.physical_resource_id]).reservations.first.instances.first
175
+
176
+ if config[:hostname_attribute].nil?
177
+ instance.private_ip_address
178
+ else
179
+ instance.send(config[:hostname_attribute].to_sym)
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kitchen-sparkleformation
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Alfred Krohmer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-05-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sparkle_formation
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '4'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '3'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '4'
33
+ - !ruby/object:Gem::Dependency
34
+ name: aws-sdk
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '2'
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '3'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '2'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '3'
53
+ description: Kitchen driver for creating CloudFormation stacks using SparkleFormation
54
+ templates
55
+ email: devkid@gmx.net
56
+ executables: []
57
+ extensions: []
58
+ extra_rdoc_files: []
59
+ files:
60
+ - Gemfile
61
+ - Gemfile.lock
62
+ - README.md
63
+ - kitchen-sparkleformation.gemspec
64
+ - lib/kitchen/driver/sparkleformation.rb
65
+ homepage: https://github.com/devkid/kitchen-sparkleformation
66
+ licenses:
67
+ - GPL-2.0
68
+ metadata: {}
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubyforge_project:
85
+ rubygems_version: 2.6.10
86
+ signing_key:
87
+ specification_version: 4
88
+ summary: Kitchen driver for SparkleFormation / CloudFormation
89
+ test_files: []