kitchen-fog 0.6.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: 6b40bf2e82fa79545da6b55c68430f7578a83557
4
+ data.tar.gz: 8cd22ed93fbe59e67fb76da948685ccb56c03767
5
+ SHA512:
6
+ metadata.gz: bdfe511e6a307c5e56217dca30c88f082498ea9eb0cf36b36761cb0104650696c23ab3caae9a9448f2472c64719883486ef4df6979ac402d1644603c7b0e7149
7
+ data.tar.gz: 5c21fa33874143f44585b3801ab8cf84d105be44adca84c9eff6012dd94777a8c4a01ab46e2a393a8cfc521f45813e988f445f70c6d46e8dfa4dce0b795f8e6e
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .kitchen
19
+ *.sw?
20
+ *~
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+
3
+ gemfile:
4
+ - Gemfile
5
+
6
+ rvm:
7
+ - 1.9.2
8
+ - 1.9.3
9
+ - 2.0.0
10
+
11
+ # vim: ai et ts=2 sts=2 sw=2 ft=yaml
@@ -0,0 +1,53 @@
1
+ # 0.2.0 / 2013-10-02
2
+
3
+ ### New Features
4
+
5
+ * Generalize kitchen-openstack to work with other providers
6
+
7
+ ### Improvements
8
+
9
+ * PR [#19][] - Don't assume `public` and `private` network names exist
10
+ * PR [#19][] - Make IPv4 or IPv6 configurable instead of relying on Fog to pick
11
+
12
+ ### Bug Fixes
13
+
14
+ * PR [#20][] - Limit generated hostnames to 64 characters
15
+
16
+ # 0.4.0 / 2013-06-06
17
+
18
+ ### New Features
19
+
20
+ * PR [#12][] - Support `openstack_network_name` option; via [@saketoba][]
21
+ * PR [#11][] - Support `ssh_key` option; via [@saketoba][]
22
+
23
+ # 0.2.0 / 2013-05-11
24
+
25
+ ### Bug Fixes
26
+
27
+ * PR [#7][] - `disable_ssl_validation` wasn't being respected on destroy
28
+
29
+ ### New Features
30
+
31
+ * PR [#10][] - Support optional `openstack_region` and `openstack_service_name`
32
+ * PR [#2][] - Support `key_name:` option; via [@stevendanna][]
33
+
34
+ ### Improvements
35
+
36
+ * PR [#7][] - Clean up/refactor to pass style checks
37
+ * PR [#9][] - Add some (probably overkill) RSpec tests
38
+
39
+ # 0.1.0 / 2013-03-12
40
+
41
+ * Initial release! Woo!
42
+
43
+ [#20]: https://github.com/RoboticCheese/kitchen-openstack/pull/20
44
+ [#19]: https://github.com/RoboticCheese/kitchen-openstack/pull/19
45
+ [#12]: https://github.com/RoboticCheese/kitchen-openstack/pull/12
46
+ [#11]: https://github.com/RoboticCheese/kitchen-openstack/pull/11
47
+ [#10]: https://github.com/RoboticCheese/kitchen-openstack/pull/10
48
+ [#9]: https://github.com/RoboticCheese/kitchen-openstack/pull/9
49
+ [#7]: https://github.com/RoboticCheese/kitchen-openstack/pull/7
50
+ [#2]: https://github.com/RoboticCheese/kitchen-openstack/pull/2
51
+
52
+ [@saketoba]: https://github.com/saketoba
53
+ [@stevendanna]: https://github.com/stevendanna
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in kitchen-fog.gemspec
4
+ gemspec
5
+
6
+ # vim: ai et ts=2 sts=2 sw=2 ft=ruby
@@ -0,0 +1,15 @@
1
+ Author:: Jonathan Hartman (<j@p4nt5.com>)
2
+
3
+ Copyright (c) 2013 Jonathan Hartman
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
@@ -0,0 +1,71 @@
1
+ [![Build Status](https://travis-ci.org/TerryHowe/kitchen-fog.png?branch=master)](https://travis-ci.org/TerryHowe/kitchen-fog) [![Code Climate](https://codeclimate.com/github/TerryHowe/kitchen-fog.png)](https://codeclimate.com/github/TerryHowe/kitchen-fog)
2
+
3
+ # Kitchen::Fog
4
+
5
+ A Fog Nova driver for Test Kitchen 1.0!
6
+
7
+ Generalized from [Jonathan Hartman](https://github.com/RoboticCheese)'s awesome work on an [OpenStack driver](https://github.com/RoboticCheese/kitchen-openstack) which is shamelessly copied from [Fletcher Nichol](https://github.com/fnichol)'s
8
+ awesome work on an [EC2 driver](https://github.com/opscode/kitchen-ec2).
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ gem 'kitchen-fog'
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install kitchen-fog
23
+
24
+ ## Usage
25
+
26
+ Provide, at a minimum, the required driver options in your `.kitchen.yml` file. The authentication and server_create sections are specific to the provider:
27
+
28
+ driver_plugin: fog
29
+ driver_config:
30
+ authentication:
31
+ provider: 'hp'
32
+ hp_access_key: 'username'
33
+ hp_secret_key: 'password'
34
+ hp_auth_uri: 'https://region-a.geo-1.identity.hpcloudsvc.com:35357/v2.0/tokens'
35
+ hp_tenant_id: 'project/tenant id'
36
+ hp_avl_zone: 'region-b.geo-1'
37
+ version: v2
38
+ hp_use_upass_auth_style: true
39
+ server_create:
40
+ flavor_id: '103'
41
+ image_id: '8c096c29-a666-4b82-99c4-c77dc70cfb40'
42
+ networks: [ '76abe0b1-581a-4698-b200-a2e890f4eb8d' ]
43
+ network: '76abe0b1-581a-4698-b200-a2e890f4eb8d'
44
+ require_chef_omnibus: latest (if you'll be using Chef)
45
+
46
+ By default, a unique server name will be generated and the current user's SSH
47
+ key will be used, though that behavior can be overridden with additional
48
+ options:
49
+
50
+ name: [A UNIQUE SERVER NAME]
51
+ ssh_key: [PATH TO YOUR PRIVATE SSH KEY]
52
+ public_key_path: [PATH TO YOUR SSH PUBLIC KEY]
53
+ username: [SSH USER]
54
+ port: [SSH PORT]
55
+ key_name: [SSH KEY NAME]
56
+
57
+ If a key\_name is provided it will be used instead of any
58
+ public\_key\_path that is specified.
59
+
60
+ disable_ssl_validation: true
61
+
62
+ Only disable SSL cert validation if you absolutely know what you are doing,
63
+ but are stuck with a deployment without valid SSL certs.
64
+
65
+ ## Contributing
66
+
67
+ 1. Fork it
68
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
69
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
70
+ 4. Push to the branch (`git push origin my-new-feature`)
71
+ 5. Create new Pull Request
@@ -0,0 +1,25 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'tailor/rake_task'
3
+ require 'cane/rake_task'
4
+ require 'rspec/core/rake_task'
5
+
6
+ desc 'Run Cane to check quality metrics'
7
+ Cane::RakeTask.new
8
+
9
+ desc 'Run Tailor to lint check code'
10
+ Tailor::RakeTask.new do |task|
11
+ task.file_set '**/**/*.rb'
12
+ end
13
+
14
+ desc 'Display LOC stats'
15
+ task :loc do
16
+ puts "\n## LOC Stats"
17
+ sh 'countloc -r lib/kitchen'
18
+ end
19
+
20
+ desc 'Run RSpec unit tests'
21
+ RSpec::Core::RakeTask.new(:spec)
22
+
23
+ task :default => [ :cane, :tailor, :loc, :spec ]
24
+
25
+ # vim: ai et ts=2 sts=2 sw=2 ft=ruby
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'kitchen/driver/fog_version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'kitchen-fog'
8
+ spec.version = Kitchen::Driver::FOG_VERSION
9
+ spec.authors = ['Jonathan Hartman']
10
+ spec.email = ['j@p4nt5.com']
11
+ spec.description = %q{A Test Kitchen Fog Nova driver}
12
+ spec.summary = spec.description
13
+ spec.homepage = 'https://github.com/TerryHowe/kitchen-fog'
14
+ spec.license = 'Apache'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'test-kitchen', '~> 1.0.0.beta'
22
+ spec.add_dependency 'fog', '~> 1.15'
23
+
24
+ spec.add_development_dependency 'bundler'
25
+ spec.add_development_dependency 'rake'
26
+ spec.add_development_dependency 'tailor'
27
+ spec.add_development_dependency 'cane'
28
+ spec.add_development_dependency 'countloc'
29
+ spec.add_development_dependency 'rspec'
30
+ end
31
+
32
+ # vim: ai et ts=2 sts=2 sw=2 ft=ruby
@@ -0,0 +1,169 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Jonathan Hartman (<j@p4nt5.com>)
4
+ #
5
+ # Copyright (C) 2013, Jonathan Hartman
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require 'benchmark'
20
+ require 'fog'
21
+ require 'kitchen'
22
+ require 'etc'
23
+ require 'ipaddr'
24
+ require 'socket'
25
+
26
+ module Kitchen
27
+ module Driver
28
+ # Fog driver for Kitchen.
29
+ #
30
+ # @author Jonathan Hartman <j@p4nt5.com>
31
+ class Fog < Kitchen::Driver::SSHBase
32
+ default_config :name, nil
33
+ default_config :public_key_path, File.expand_path('~/.ssh/id_dsa.pub')
34
+ default_config :username, 'root'
35
+ default_config :port, '22'
36
+ default_config :use_ipv6, false
37
+ default_config :network_name, nil
38
+
39
+ def create(state)
40
+ config[:name] ||= generate_name(instance.name)
41
+ config[:disable_ssl_validation] and disable_ssl_validation
42
+ server = create_server(state)
43
+ unless config[:floating_ip_create].nil?
44
+ create_floating_ip(server)
45
+ else
46
+ state[:hostname] = get_ip(server)
47
+ end
48
+ wait_for_sshd(state[:hostname]) ; puts '(ssh ready)'
49
+ unless config[:ssh_key] or config[:key_name]
50
+ do_ssh_setup(state, config, server)
51
+ end
52
+ rescue ::Fog::Errors::Error, Excon::Errors::Error => ex
53
+ raise ActionFailed, ex.message
54
+ end
55
+
56
+ def destroy(state)
57
+ return if state[:server_id].nil?
58
+
59
+ config[:disable_ssl_validation] and disable_ssl_validation
60
+ server = compute.servers.get(state[:server_id])
61
+ server.destroy unless server.nil?
62
+ info "Fog instance <#{state[:server_id]}> destroyed."
63
+ state.delete(:server_id)
64
+ state.delete(:hostname)
65
+ end
66
+
67
+ private
68
+
69
+ def compute
70
+ ::Fog::Compute.new(config[:authentication].dup)
71
+ end
72
+
73
+ def network
74
+ authentication = config[:authentication].dup
75
+ authentication.delete(:version)
76
+ ::Fog::Network.new(authentication)
77
+ end
78
+
79
+ def create_server(state)
80
+ server_def = config[:server_create] || {}
81
+ server_def = server_def.dup
82
+ server_def[:name] = config[:name]
83
+ # Can't use the Fog bootstrap and/or setup methods here; they require a
84
+ # public IP address that can't be guaranteed to exist across all
85
+ # OpenStack deployments (e.g. TryStack ARM only has private IPs).
86
+ server = compute.servers.create(server_def)
87
+ state[:server_id] = server.id
88
+ info "Fog instance <#{state[:server_id]}> created."
89
+ server.wait_for { print '.'; ready? } ; puts "\n(server ready)"
90
+ server
91
+ end
92
+
93
+ def create_floating_ip(server)
94
+ hsh = config[:floating_ip_create].dup
95
+ floater = network.create_floating_ip(hsh[:floating_network_id], hsh)
96
+ floating_id = floater.body['floatingip']['id']
97
+ state[:hostname] = floater.body['floatingip']['floating_ip_address']
98
+ port = network.ports(:filters => { :device_id => server.id }).first
99
+ network.associate_floating_ip(floating_id, port.id)
100
+ end
101
+
102
+ def generate_name(base)
103
+ # Generate what should be a unique server name
104
+ sep = '-'
105
+ pieces = [
106
+ base,
107
+ Etc.getlogin,
108
+ Socket.gethostname,
109
+ Array.new(8) { rand(36).to_s(36) }.join
110
+ ]
111
+ until pieces.join(sep).length <= 64 do
112
+ if pieces[2].length > 24
113
+ pieces[2] = pieces[2][0..-2]
114
+ elsif pieces[1].length > 16
115
+ pieces[1] = pieces[1][0..-2]
116
+ elsif pieces[0].length > 16
117
+ pieces[0] = pieces[0][0..-2]
118
+ end
119
+ end
120
+ pieces.join sep
121
+ end
122
+
123
+ def get_ip(server)
124
+ if config[:network_name]
125
+ debug "Using configured network: #{config[:network_name]}"
126
+ return server.addresses[config[:network_name]].first['addr']
127
+ end
128
+ begin
129
+ pub, priv = server.public_ip_addresses, server.private_ip_addresses
130
+ rescue Exception => e
131
+ # See Fog issue: https://github.com/fog/fog/issues/2160
132
+ addrs = server.addresses
133
+ addrs['public'] and pub = addrs['public'].map { |i| i['addr'] }
134
+ addrs['private'] and priv = addrs['private'].map { |i| i['addr'] }
135
+ end
136
+ pub, priv = parse_ips(pub, priv)
137
+ pub.first || priv.first || raise(ActionFailed, 'Could not find an IP')
138
+ end
139
+
140
+ def parse_ips(pub, priv)
141
+ pub, priv = Array(pub), Array(priv)
142
+ if config[:use_ipv6]
143
+ [pub, priv].each { |n| n.select! { |i| IPAddr.new(i).ipv6? } }
144
+ else
145
+ [pub, priv].each { |n| n.select! { |i| IPAddr.new(i).ipv4? } }
146
+ end
147
+ return pub, priv
148
+ end
149
+
150
+ def do_ssh_setup(state, config, server)
151
+ ssh = ::Fog::SSH.new(state[:hostname], config[:username],
152
+ { :password => server.password })
153
+ pub_key = open(config[:public_key_path]).read
154
+ ssh.run([
155
+ %{mkdir .ssh},
156
+ %{echo "#{pub_key}" >> ~/.ssh/authorized_keys},
157
+ %{passwd -l #{config[:username]}}
158
+ ])
159
+ end
160
+
161
+ def disable_ssl_validation
162
+ require 'excon'
163
+ Excon.defaults[:ssl_verify_peer] = false
164
+ end
165
+ end
166
+ end
167
+ end
168
+
169
+ # vim: ai et ts=2 sts=2 sw=2 ft=ruby
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Jonathan Hartman (<j@p4nt5.com>)
4
+ #
5
+ # Copyright (C) 2013, Jonathan Hartman
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ module Kitchen
20
+ module Driver
21
+ # Version string for Fog Kitchen driver
22
+ FOG_VERSION = '0.6.0'
23
+ end
24
+ end
25
+
26
+ # vim: ai et ts=2 sts=2 sw=2 ft=ruby
@@ -0,0 +1,593 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Jonathan Hartman (<j@p4nt5.com>)
4
+ #
5
+ # Copyright (C) 2013, Jonathan Hartman
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require 'logger'
20
+ require 'stringio'
21
+ require 'rspec'
22
+ require 'kitchen'
23
+ require_relative '../../spec_helper'
24
+
25
+ describe Kitchen::Driver::Fog do
26
+ let(:logged_output) { StringIO.new }
27
+ let(:logger) { Logger.new(logged_output) }
28
+ let(:config) { Hash.new }
29
+ let(:state) { Hash.new }
30
+
31
+ let(:instance) do
32
+ double(:name => 'potatoes', :logger => logger, :to_str => 'instance')
33
+ end
34
+
35
+ let(:driver) do
36
+ d = Kitchen::Driver::Fog.new(config)
37
+ d.instance = instance
38
+ d
39
+ end
40
+
41
+ describe '#initialize'do
42
+ context 'default options' do
43
+ it 'defaults to no name' do
44
+ expect(driver[:name]).to eq(nil)
45
+ end
46
+
47
+ it 'defaults to local user\'s SSH public key' do
48
+ expect(driver[:public_key_path]).to eq(File.expand_path(
49
+ '~/.ssh/id_dsa.pub'))
50
+ end
51
+
52
+ it 'defaults to SSH with root user on port 22' do
53
+ expect(driver[:username]).to eq('root')
54
+ expect(driver[:port]).to eq('22')
55
+ end
56
+
57
+ it 'defaults to use ipv4' do
58
+ expect(driver[:use_ipv6]).to eq(false)
59
+ end
60
+
61
+ it 'defaults to no network name' do
62
+ expect(driver[:network_name]).to eq(nil)
63
+ end
64
+ end
65
+
66
+ context 'overridden options' do
67
+ let(:config) do
68
+ {
69
+ :authentication => {
70
+ :openstack_tenant => 'that_one',
71
+ :openstack_region => 'atlantis',
72
+ :openstack_service_name => 'the_service',
73
+ },
74
+ :server_create => {
75
+ :image_ref => '22',
76
+ :flavor_ref => '33',
77
+ },
78
+ :public_key_path => '/tmp',
79
+ :username => 'admin',
80
+ :port => '2222',
81
+ :name => 'puppy',
82
+ :ssh_key => '/path/to/id_rsa'
83
+ }
84
+ end
85
+
86
+ it 'uses all the overridden options' do
87
+ drv = driver
88
+ config.each do |k, v|
89
+ expect(drv[k]).to eq(v)
90
+ end
91
+ end
92
+
93
+ it 'SSH with user-specified private key' do
94
+ expect(driver[:ssh_key]).to eq('/path/to/id_rsa')
95
+ end
96
+ end
97
+ end
98
+
99
+ describe '#create' do
100
+ let(:server) do
101
+ double(:id => 'test123', :wait_for => true,
102
+ :public_ip_addresses => %w{1.2.3.4})
103
+ end
104
+ let(:driver) do
105
+ d = Kitchen::Driver::Fog.new(config)
106
+ d.instance = instance
107
+ d.stub(:generate_name).with('potatoes').and_return('a_monkey!')
108
+ d.stub(:create_server).and_return(server)
109
+ d.stub(:wait_for_sshd).with('1.2.3.4').and_return(true)
110
+ d.stub(:get_ip).and_return('1.2.3.4')
111
+ d.stub(:do_ssh_setup).and_return(true)
112
+ d
113
+ end
114
+
115
+ context 'required options provided' do
116
+ let(:config) do
117
+ {
118
+ :authentication => {
119
+ :openstack_username => 'hello',
120
+ :openstack_api_key => 'world',
121
+ :openstack_auth_url => 'http:',
122
+ :openstack_tenant => 'www'
123
+ },
124
+ :server_create => {
125
+ :flavorRef => 101,
126
+ :imageRef => 222
127
+ }
128
+ }
129
+ end
130
+
131
+ it 'generates a server name in the absence of one' do
132
+ driver.create(state)
133
+ expect(driver[:name]).to eq('a_monkey!')
134
+ end
135
+
136
+ it 'gets a proper hostname (IP)' do
137
+ driver.create(state)
138
+ expect(state[:hostname]).to eq('1.2.3.4')
139
+ end
140
+
141
+ it 'does not disable SSL validation' do
142
+ driver.should_not_receive(:disable_ssl_validation)
143
+ driver.create(state)
144
+ end
145
+ end
146
+
147
+ context 'SSL validation disabled' do
148
+ let(:config) { { :disable_ssl_validation => true } }
149
+
150
+ it 'disables SSL cert validation' do
151
+ driver.should_receive(:disable_ssl_validation)
152
+ driver.create(state)
153
+ end
154
+ end
155
+ end
156
+
157
+ describe '#destroy' do
158
+ let(:server_id) { '12345' }
159
+ let(:hostname) { 'example.com' }
160
+ let(:state) { { :server_id => server_id, :hostname => hostname } }
161
+ let(:server) { double(:nil? => false, :destroy => true) }
162
+ let(:servers) { double(:get => server) }
163
+ let(:compute) { double(:servers => servers) }
164
+
165
+ let(:driver) do
166
+ d = Kitchen::Driver::Fog.new(config)
167
+ d.instance = instance
168
+ d.stub(:compute).and_return(compute)
169
+ d
170
+ end
171
+
172
+ context 'a live server that needs to be destroyed' do
173
+ it 'destroys the server' do
174
+ state.should_receive(:delete).with(:server_id)
175
+ state.should_receive(:delete).with(:hostname)
176
+ driver.destroy(state)
177
+ end
178
+
179
+ it 'does not disable SSL cert validation' do
180
+ driver.should_not_receive(:disable_ssl_validation)
181
+ driver.destroy(state)
182
+ end
183
+ end
184
+
185
+ context 'no server ID present' do
186
+ let(:state) { Hash.new }
187
+
188
+ it 'does nothing' do
189
+ driver.stub(:compute)
190
+ driver.should_not_receive(:compute)
191
+ state.should_not_receive(:delete)
192
+ driver.destroy(state)
193
+ end
194
+ end
195
+
196
+ context 'a server that was already destroyed' do
197
+ let(:servers) do
198
+ s = double('servers')
199
+ s.stub(:get).with('12345').and_return(nil)
200
+ s
201
+ end
202
+ let(:compute) { double(:servers => servers) }
203
+ let(:driver) do
204
+ d = Kitchen::Driver::Fog.new(config)
205
+ d.instance = instance
206
+ d.stub(:compute).and_return(compute)
207
+ d
208
+ end
209
+
210
+ it 'does not try to destroy the server again' do
211
+ allow_message_expectations_on_nil
212
+ driver.destroy(state)
213
+ end
214
+ end
215
+
216
+ context 'SSL validation disabled' do
217
+ let(:config) { { :disable_ssl_validation => true } }
218
+
219
+ it 'disables SSL cert validation' do
220
+ driver.should_receive(:disable_ssl_validation)
221
+ driver.destroy(state)
222
+ end
223
+ end
224
+ end
225
+
226
+ describe '#compute' do
227
+ let(:config) do
228
+ {
229
+ :authentication => {
230
+ :openstack_username => 'monkey',
231
+ :openstack_api_key => 'potato',
232
+ :openstack_auth_url => 'http:',
233
+ :openstack_tenant => 'link',
234
+ :openstack_region => 'ord',
235
+ :openstack_service_name => 'the_service'
236
+ }
237
+ }
238
+ end
239
+
240
+ context 'all requirements provided' do
241
+ it 'creates a new compute connection' do
242
+ Fog::Compute.stub(:new) { |arg| arg }
243
+ res = config.merge({ :provider => 'fog' })
244
+ expect(driver.send(:compute)).to eq(res[:authentication])
245
+ end
246
+ end
247
+
248
+ context 'only an API key provided' do
249
+ let(:config) { { :authentication => { :openstack_api_key => '1234' } } }
250
+
251
+ it 'raises an error' do
252
+ expect { driver.send(:compute) }.to raise_error(ArgumentError)
253
+ end
254
+ end
255
+
256
+ context 'only a username provided' do
257
+ let(:config) do
258
+ { :authentication => { :openstack_username => 'monkey' } }
259
+ end
260
+
261
+ it 'raises an error' do
262
+ expect { driver.send(:compute) }.to raise_error(ArgumentError)
263
+ end
264
+ end
265
+ end
266
+
267
+ describe '#create_server' do
268
+ let(:config) do
269
+ {
270
+ :name => 'hello',
271
+ :image_ref => 'there',
272
+ :flavor_ref => 'captain',
273
+ :public_key_path => 'tarpals'
274
+ }
275
+ end
276
+ let(:server) do
277
+ double(:id => 'test222', :wait_for => true,
278
+ :public_ip_addresses => %w{1.2.3.4})
279
+ end
280
+ let(:servers) do
281
+ s = double('servers')
282
+ s.stub(:create).and_return(server)
283
+ s
284
+ end
285
+ let(:compute) { double(:servers => servers) }
286
+ let(:driver) do
287
+ d = Kitchen::Driver::Fog.new(config)
288
+ d.instance = instance
289
+ d.stub(:compute).and_return(compute)
290
+ d
291
+ end
292
+
293
+ context 'a default config' do
294
+ before(:each) { @config = config.dup }
295
+
296
+ it 'creates the server using a compute connection' do
297
+ state = {}
298
+ expect(driver.send(:create_server, state)).to eq(server)
299
+ expect(state).to eq({ :server_id => 'test222' })
300
+ end
301
+ end
302
+
303
+ context 'a provided public key path' do
304
+ let(:config) do
305
+ {
306
+ :name => 'hello',
307
+ :image_ref => 'there',
308
+ :flavor_ref => 'captain',
309
+ :public_key_path => 'tarpals'
310
+ }
311
+ end
312
+ before(:each) { @config = config.dup }
313
+
314
+ it 'passes that public key path to Fog' do
315
+ state = {}
316
+ expect(driver.send(:create_server, state)).to eq(server)
317
+ expect(state).to eq({ :server_id => 'test222' })
318
+ end
319
+ end
320
+
321
+ context 'a provided key name' do
322
+ let(:config) do
323
+ {
324
+ :name => 'hello',
325
+ :image_ref => 'there',
326
+ :flavor_ref => 'captain',
327
+ :public_key_path => 'montgomery',
328
+ :key_name => 'tarpals'
329
+ }
330
+ end
331
+ before(:each) { @config = config.dup }
332
+
333
+ it 'passes that key name to Fog' do
334
+ state = {}
335
+ expect(driver.send(:create_server, state)).to eq(server)
336
+ expect(state).to eq({ :server_id => 'test222' })
337
+ end
338
+ end
339
+ end
340
+
341
+ describe '#generate_name' do
342
+ before(:each) do
343
+ Etc.stub(:getlogin).and_return('user')
344
+ Socket.stub(:gethostname).and_return('host')
345
+ end
346
+
347
+ it 'generates a name' do
348
+ expect(driver.send(:generate_name, 'monkey')).to match(
349
+ /^monkey-user-host-/)
350
+ end
351
+
352
+ context 'local node with a long hostname' do
353
+ before(:each) do
354
+ Socket.unstub(:gethostname)
355
+ Socket.stub(:gethostname).and_return('ab.c' * 20)
356
+ end
357
+
358
+ it 'limits the generated name to 64-characters' do
359
+ expect(driver.send(:generate_name, 'long').length).to eq(64)
360
+ end
361
+ end
362
+ end
363
+
364
+ describe '#get_ip' do
365
+ let(:addresses) { nil }
366
+ let(:public_ip_addresses) { nil }
367
+ let(:private_ip_addresses) { nil }
368
+ let(:parsed_ips) { [[], []] }
369
+ let(:driver) do
370
+ d = Kitchen::Driver::Fog.new(config)
371
+ d.instance = instance
372
+ d.stub(:parse_ips).and_return(parsed_ips)
373
+ d
374
+ end
375
+ let(:server) do
376
+ double(:addresses => addresses,
377
+ :public_ip_addresses => public_ip_addresses,
378
+ :private_ip_addresses => private_ip_addresses)
379
+ end
380
+
381
+ context 'both public and private IPs' do
382
+ let(:public_ip_addresses) { %w{1::1 1.2.3.4} }
383
+ let(:private_ip_addresses) { %w{5.5.5.5} }
384
+ let(:parsed_ips) { [%w{1.2.3.4}, %w{5.5.5.5}] }
385
+
386
+ it 'returns a public IPv4 address' do
387
+ expect(driver.send(:get_ip, server)).to eq('1.2.3.4')
388
+ end
389
+ end
390
+
391
+ context 'only public IPs' do
392
+ let(:public_ip_addresses) { %w{4.3.2.1 2::1} }
393
+ let(:parsed_ips) { [%w{4.3.2.1}, []] }
394
+
395
+ it 'returns a public IPv4 address' do
396
+ expect(driver.send(:get_ip, server)).to eq('4.3.2.1')
397
+ end
398
+ end
399
+
400
+ context 'only private IPs' do
401
+ let(:private_ip_addresses) { %w{3::1 5.5.5.5} }
402
+ let(:parsed_ips) { [[], %w{5.5.5.5}] }
403
+
404
+ it 'returns a private IPv4 address' do
405
+ expect(driver.send(:get_ip, server)).to eq('5.5.5.5')
406
+ end
407
+ end
408
+
409
+ context 'IPs in user-defined network group' do
410
+ let(:config) { { :network_name => 'mynetwork' } }
411
+ let(:addresses) do
412
+ {
413
+ 'mynetwork' => [
414
+ { 'addr' => '7.7.7.7' },
415
+ { 'addr' => '4::1' }
416
+ ]
417
+ }
418
+ end
419
+
420
+ it 'returns a IPv4 address in user-defined network group' do
421
+ expect(driver.send(:get_ip, server)).to eq('7.7.7.7')
422
+ end
423
+ end
424
+
425
+ context 'an Fog deployment without the floating IP extension' do
426
+ let(:server) do
427
+ s = double('server')
428
+ s.stub(:addresses).and_return(addresses)
429
+ s.stub(:public_ip_addresses).and_raise(
430
+ Fog::Compute::OpenStack::NotFound)
431
+ s.stub(:private_ip_addresses).and_raise(
432
+ Fog::Compute::OpenStack::NotFound)
433
+ s
434
+ end
435
+
436
+ context 'both public and private IPs in the addresses hash' do
437
+ let(:addresses) do
438
+ {
439
+ 'public' => [{ 'addr' => '6.6.6.6' }, { 'addr' => '7.7.7.7' }],
440
+ 'private' => [{ 'addr' => '8.8.8.8' }, { 'addr' => '9.9.9.9' }]
441
+ }
442
+ end
443
+ let(:parsed_ips) { [%w{6.6.6.6 7.7.7.7}, %w{8.8.8.8 9.9.9.9}] }
444
+
445
+ it 'selects the first public IP' do
446
+ expect(driver.send(:get_ip, server)).to eq('6.6.6.6')
447
+ end
448
+ end
449
+
450
+ context 'only public IPs in the address hash' do
451
+ let(:addresses) do
452
+ { 'public' => [{ 'addr' => '6.6.6.6' }, { 'addr' => '7.7.7.7' }] }
453
+ end
454
+ let(:parsed_ips) { [%w{6.6.6.6 7.7.7.7}, []] }
455
+
456
+ it 'selects the first public IP' do
457
+ expect(driver.send(:get_ip, server)).to eq('6.6.6.6')
458
+ end
459
+ end
460
+
461
+ context 'only private IPs in the address hash' do
462
+ let(:addresses) do
463
+ { 'private' => [{ 'addr' => '8.8.8.8' }, { 'addr' => '9.9.9.9' }] }
464
+ end
465
+ let(:parsed_ips) { [[], %w{8.8.8.8 9.9.9.9}] }
466
+
467
+ it 'selects the first private IP' do
468
+ expect(driver.send(:get_ip, server)).to eq('8.8.8.8')
469
+ end
470
+ end
471
+ end
472
+
473
+ context 'no IP addresses whatsoever' do
474
+ it 'raises an exception' do
475
+ expect { driver.send(:get_ip, server) }.to raise_error
476
+ end
477
+ end
478
+ end
479
+
480
+ describe '#parse_ips' do
481
+ let(:pub_v4) { %w{1.1.1.1 2.2.2.2} }
482
+ let(:pub_v6) { %w{1::1 2::2} }
483
+ let(:priv_v4) { %w{3.3.3.3 4.4.4.4} }
484
+ let(:priv_v6) { %w{3::3 4::4} }
485
+ let(:pub) { pub_v4 + pub_v6 }
486
+ let(:priv) { priv_v4 + priv_v6 }
487
+
488
+ context 'both public and private IPs' do
489
+ context 'IPv4 (default)' do
490
+ it 'returns only the v4 IPs' do
491
+ expect(driver.send(:parse_ips, pub, priv)).to eq([pub_v4, priv_v4])
492
+ end
493
+ end
494
+
495
+ context 'IPv6' do
496
+ let(:config) { { :use_ipv6 => true } }
497
+
498
+ it 'returns only the v6 IPs' do
499
+ expect(driver.send(:parse_ips, pub, priv)).to eq([pub_v6, priv_v6])
500
+ end
501
+ end
502
+ end
503
+
504
+ context 'only public IPs' do
505
+ let(:priv) { nil }
506
+
507
+ context 'IPv4 (default)' do
508
+ it 'returns only the v4 IPs' do
509
+ expect(driver.send(:parse_ips, pub, priv)).to eq([pub_v4, []])
510
+ end
511
+ end
512
+
513
+ context 'IPv6' do
514
+ let(:config) { { :use_ipv6 => true } }
515
+
516
+ it 'returns only the v6 IPs' do
517
+ expect(driver.send(:parse_ips, pub, priv)).to eq([pub_v6, []])
518
+ end
519
+ end
520
+ end
521
+
522
+ context 'only private IPs' do
523
+ let(:pub) { nil }
524
+
525
+ context 'IPv4 (default)' do
526
+ it 'returns only the v4 IPs' do
527
+ expect(driver.send(:parse_ips, pub, priv)).to eq([[], priv_v4])
528
+ end
529
+ end
530
+
531
+ context 'IPv6' do
532
+ let(:config) { { :use_ipv6 => true } }
533
+
534
+ it 'returns only the v6 IPs' do
535
+ expect(driver.send(:parse_ips, pub, priv)).to eq([[], priv_v6])
536
+ end
537
+ end
538
+ end
539
+
540
+ context 'no IPs whatsoever' do
541
+ let(:pub) { nil }
542
+ let(:priv) { nil }
543
+
544
+ context 'IPv4 (default)' do
545
+ it 'returns empty lists' do
546
+ expect(driver.send(:parse_ips, pub, priv)).to eq([[], []])
547
+ end
548
+ end
549
+
550
+ context 'IPv6' do
551
+ let(:config) { { :use_ipv6 => true } }
552
+
553
+ it 'returns empty lists' do
554
+ expect(driver.send(:parse_ips, nil, nil)).to eq([[], []])
555
+ end
556
+ end
557
+ end
558
+ end
559
+
560
+ describe '#do_ssh_setup' do
561
+ let(:server) { double(:password => 'aloha') }
562
+ let(:state) { { :hostname => 'host' } }
563
+ let(:read) { double(:read => 'a_key') }
564
+ let(:ssh) do
565
+ s = double('ssh')
566
+ s.stub(:run) { |args| args }
567
+ s
568
+ end
569
+
570
+ it 'opens an SSH session to the server' do
571
+ Fog::SSH.stub(:new).with('host', 'root',
572
+ { :password => 'aloha' }).and_return(ssh)
573
+ driver.stub(:open).with(File.expand_path(
574
+ '~/.ssh/id_dsa.pub')).and_return(read)
575
+ read.stub(:read).and_return('a_key')
576
+ res = driver.send(:do_ssh_setup, state, config, server)
577
+ expected = [
578
+ 'mkdir .ssh',
579
+ 'echo "a_key" >> ~/.ssh/authorized_keys',
580
+ 'passwd -l root'
581
+ ]
582
+ expect(res).to eq(expected)
583
+ end
584
+ end
585
+
586
+ describe '#disable_ssl_validation' do
587
+ it 'turns off Excon SSL cert validation' do
588
+ expect(driver.send(:disable_ssl_validation)).to eq(false)
589
+ end
590
+ end
591
+ end
592
+
593
+ # vim: ai et ts=2 sts=2 sw=2 ft=ruby
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Jonathan Hartman (<j@p4nt5.com>)
4
+ #
5
+ # Copyright (C) 2013, Jonathan Hartman
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require 'rspec'
20
+ require_relative '../lib/kitchen/driver/fog'
21
+
22
+ # vim: ai et ts=2 sts=2 sw=2 ft=ruby
metadata ADDED
@@ -0,0 +1,170 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kitchen-fog
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.6.0
5
+ platform: ruby
6
+ authors:
7
+ - Jonathan Hartman
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-10-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: test-kitchen
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 1.0.0.beta
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 1.0.0.beta
27
+ - !ruby/object:Gem::Dependency
28
+ name: fog
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.15'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.15'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: tailor
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: cane
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: countloc
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ! '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: A Test Kitchen Fog Nova driver
126
+ email:
127
+ - j@p4nt5.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - .gitignore
133
+ - .travis.yml
134
+ - CHANGELOG.md
135
+ - Gemfile
136
+ - LICENSE.txt
137
+ - README.md
138
+ - Rakefile
139
+ - kitchen-fog.gemspec
140
+ - lib/kitchen/driver/fog.rb
141
+ - lib/kitchen/driver/fog_version.rb
142
+ - spec/kitchen/driver/openstack_spec.rb
143
+ - spec/spec_helper.rb
144
+ homepage: https://github.com/TerryHowe/kitchen-fog
145
+ licenses:
146
+ - Apache
147
+ metadata: {}
148
+ post_install_message:
149
+ rdoc_options: []
150
+ require_paths:
151
+ - lib
152
+ required_ruby_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ! '>='
155
+ - !ruby/object:Gem::Version
156
+ version: '0'
157
+ required_rubygems_version: !ruby/object:Gem::Requirement
158
+ requirements:
159
+ - - ! '>='
160
+ - !ruby/object:Gem::Version
161
+ version: '0'
162
+ requirements: []
163
+ rubyforge_project:
164
+ rubygems_version: 2.1.5
165
+ signing_key:
166
+ specification_version: 4
167
+ summary: A Test Kitchen Fog Nova driver
168
+ test_files:
169
+ - spec/kitchen/driver/openstack_spec.rb
170
+ - spec/spec_helper.rb