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.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +39 -0
- data/README.md +59 -0
- data/kitchen-sparkleformation.gemspec +13 -0
- data/lib/kitchen/driver/sparkleformation.rb +184 -0
- metadata +89 -0
checksums.yaml
ADDED
|
@@ -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
data/Gemfile.lock
ADDED
|
@@ -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
|
data/README.md
ADDED
|
@@ -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: []
|