kitchen-sparkleformation 0.1.0

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