kitchen-linode 0.10.0 → 0.11.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.
data/.travis.yml CHANGED
@@ -1,11 +1,11 @@
1
1
  language: ruby
2
2
 
3
3
  rvm:
4
- - 2.0.0
5
- - 1.9.3
6
- - 1.9.2
7
- - ruby-head
4
+ - 2.3.0
5
+ - 2.1.5
8
6
 
9
- matrix:
10
- allow_failures:
11
- - rvm: ruby-head
7
+ script: "bundle exec rspec"
8
+
9
+ addons:
10
+ code_climate:
11
+ repo_token: e6d4bb7235b740943569bfe50196b620353970884a75f466d11cd37a2fc250f6
data/Gemfile CHANGED
@@ -1,6 +1,3 @@
1
1
  source "http://rubygems.org"
2
2
 
3
3
  gemspec
4
-
5
- gem 'rspec', '~> 3.0'
6
-
data/README.md CHANGED
@@ -1,19 +1,29 @@
1
1
  # <a name="title"></a> Kitchen::Linode
2
+ [![Gem](https://img.shields.io/gem/v/kitchen-linode.svg)](https://rubygems.org/gems/kitchen-linode)
3
+ [![Gem](https://img.shields.io/gem/dt/kitchen-linode.svg)](https://rubygems.org/gems/kitchen-linode)
4
+ [![Gem](https://img.shields.io/gem/dtv/kitchen-linode.svg)](https://rubygems.org/gems/kitchen-linode)
5
+ [![Code Climate](https://codeclimate.com/github/ssplatt/kitchen-linode/badges/gpa.svg)](https://codeclimate.com/github/ssplatt/kitchen-linode)
6
+ [![Test Coverage](https://codeclimate.com/github/ssplatt/kitchen-linode/badges/coverage.svg)](https://codeclimate.com/github/ssplatt/kitchen-linode/coverage)
7
+ [![Build Status](https://travis-ci.org/ssplatt/kitchen-linode.svg?branch=master)](https://travis-ci.org/ssplatt/kitchen-linode)
2
8
 
3
- A Test Kitchen Driver for Linode.
9
+ A Test Kitchen Driver for [Linode](http://www.linode.com).
10
+
11
+ [![asciicast](https://asciinema.org/a/44348.png)](https://asciinema.org/a/44348)
4
12
 
5
13
  ## <a name="requirements"></a> Requirements
6
14
 
7
- **TODO:** document any software or library prerequisites that are required to
8
- use this driver. Implement the `#verify_dependencies` method in your Driver
9
- class to enforce these requirements in code, if possible.
15
+ Requires [Test Kitchen](http://kitchen-ci.org) and a [Linode](http://www.linode.com) account.
16
+ ```
17
+ gem install test-kitchen
18
+ ```
10
19
 
11
20
  ## <a name="installation"></a> Installation and Setup
12
21
 
13
- Install the gem file:
22
+ The gem file is hosted at [RubyGems](https://rubygems.org/gems/kitchen-linode). To install the gem file, run:
14
23
  ```
15
24
  gem install kitchen-linode
16
25
  ```
26
+ Or, install with bundler if you have a Gemfile
17
27
  Please read the [Driver usage][driver_usage] page for more details.
18
28
 
19
29
  ## <a name="config"></a> Configuration
@@ -21,12 +31,12 @@ Please read the [Driver usage][driver_usage] page for more details.
21
31
  For many of these, you can specify an ID number, a full name, or a partial name that will try to match something in the list but may not match exactly what you want.
22
32
  ```
23
33
  LINODE_API_KEY Linode API Key environment variable, default: nil
24
- :username ssh user name, default: 'root'
34
+ :username ssh user name, default: "root"
25
35
  :password password for user, default: randomly generated hash
26
- :image Linux distribution, default: "Debian 8.1"
36
+ :image Linux distribution, default: "Debian 8"
27
37
  :data_center data center, default: "Atlanta"
28
38
  :flavor linode type/amount of RAM, default: "Linode 1024"
29
- :payment_terms if you happen to have legacy default: 1
39
+ :payment_terms if you happen to have legacy, default: 1
30
40
  :kernel Linux kernel, default: "Latest 64 bit"
31
41
  :private_key_path Location of your private key file, default: "~/.ssh/id_rsa"
32
42
  :public_key_path Location of your public key file, default: "~/.ssh/id_rsa.pub"
@@ -65,7 +75,7 @@ then you're ready to run `kitchen test` or `kitchen converge`
65
75
  ```
66
76
  $ kitchen test
67
77
  ```
68
- If you want to create a second yaml config; one for using Vagrant locally but a second to use the Linode driver when run on your CI server, create a config with a name like `.kitchen-ci.yml`:
78
+ If you want to create a second yaml config; one for using Vagrant locally but another to use the Linode driver when run on your CI server, create a config with a name like `.kitchen-ci.yml`:
69
79
  ```
70
80
  ---
71
81
  driver:
@@ -89,9 +99,9 @@ Then you can run the second config by changing the KITCHEN_YAML environment vari
89
99
  ```
90
100
  $ KITCHEN_YAML="./.kitchen-ci.yml" kitchen test
91
101
  ```
92
- If you want to change any of the default settings, you can do it in the 'platforms' area:
102
+ If you want to change any of the default settings, you can do so in the 'platforms' area:
93
103
  ```
94
- ...
104
+ ...<snip>...
95
105
  platforms:
96
106
  - name: debian_jessie
97
107
  driver:
@@ -99,25 +109,9 @@ platforms:
99
109
  data_center: Dallas
100
110
  kernel: 4.0.2-x86_64-linode56
101
111
  image: Debian 7
102
- ...
112
+ ...<snip>...
103
113
  ```
104
114
 
105
- ### <a name="config-require-chef-omnibus"></a> require\_chef\_omnibus
106
-
107
- Determines whether or not a Chef [Omnibus package][chef_omnibus_dl] will be
108
- installed. There are several different behaviors available:
109
-
110
- * `true` - the latest release will be installed. Subsequent converges
111
- will skip re-installing if chef is present.
112
- * `latest` - the latest release will be installed. Subsequent converges
113
- will always re-install even if chef is present.
114
- * `<VERSION_STRING>` (ex: `10.24.0`) - the desired version string will
115
- be passed the the install.sh script. Subsequent converges will skip if
116
- the installed version and the desired version match.
117
- * `false` or `nil` - no chef is installed.
118
-
119
- The default value is unset, or `nil`.
120
-
121
115
  ## <a name="development"></a> Development
122
116
 
123
117
  * Source hosted at [GitHub][repo]
@@ -147,4 +141,3 @@ Apache 2.0 (see [LICENSE][license])
147
141
  [license]: https://github.com/ssplatt/kitchen-linode/blob/master/LICENSE
148
142
  [repo]: https://github.com/ssplatt/kitchen-linode
149
143
  [driver_usage]: http://docs.kitchen-ci.org/drivers/usage
150
- [chef_omnibus_dl]: http://www.getchef.com/chef/install/
@@ -23,4 +23,6 @@ Gem::Specification.new do |spec|
23
23
 
24
24
  spec.add_development_dependency 'bundler', '~> 1.3'
25
25
  spec.add_development_dependency 'rake', '~> 10.4'
26
+ spec.add_development_dependency 'rspec', '~> 3.4'
27
+ spec.add_development_dependency 'codeclimate-test-reporter', '~> 0.5'
26
28
  end
@@ -32,29 +32,36 @@ module Kitchen
32
32
 
33
33
  default_config :username, 'root'
34
34
  default_config :password, nil
35
+ default_config :server_name, nil
35
36
  default_config :image, 140
36
37
  default_config :data_center, 4
37
38
  default_config :flavor, 1
38
39
  default_config :payment_terms, 1
39
- default_config :ssh_key_name, nil
40
40
  default_config :kernel, 138
41
41
 
42
42
  default_config :sudo, true
43
- default_config :port, 22
44
43
  default_config :ssh_timeout, 600
45
44
 
46
- default_config :private_key, nil
47
- default_config :private_key_path, "~/.ssh/id_rsa"
48
- default_config :public_key, nil
49
- default_config :public_key_path, "~/.ssh/id_rsa.pub"
45
+ default_config :private_key_path do
46
+ %w(id_rsa).map do |k|
47
+ f = File.expand_path("~/.ssh/#{k}")
48
+ f if File.exist?(f)
49
+ end.compact.first
50
+ end
51
+ default_config :public_key_path do |driver|
52
+ driver[:private_key_path] + '.pub' if driver[:private_key_path]
53
+ end
50
54
 
51
55
  default_config :api_key, ENV['LINODE_API_KEY']
52
56
 
53
57
  required_config :api_key
58
+ required_config :private_key_path
59
+ required_config :public_key_path
54
60
 
55
61
  def create(state)
56
62
  # create and boot server
57
63
  config_server_name
64
+ set_password
58
65
 
59
66
  if state[:server_id]
60
67
  info "#{config[:server_name]} (#{state[:server_id]}) already exists."
@@ -72,7 +79,7 @@ module Kitchen
72
79
  info("Waiting for linode to boot...")
73
80
  server.wait_for { ready? }
74
81
  info("Linode <#{state[:server_id]}, #{state[:hostname]}> ready.")
75
- setup_ssh(server, state) if bourne_shell?
82
+ setup_ssh(state) if bourne_shell?
76
83
  rescue Fog::Errors::Error, Excon::Errors::Error => ex
77
84
  raise ActionFailed, ex.message
78
85
  end
@@ -94,26 +101,21 @@ module Kitchen
94
101
  Fog::Compute.new(:provider => 'Linode', :linode_api_key => config[:api_key])
95
102
  end
96
103
 
97
- def create_server
98
- if config[:password].nil?
99
- config[:password] = [*('a'..'z'),*('A'..'Z'),*('0'..'9')].shuffle[0,20].join
100
- end
101
-
102
- # set datacenter
104
+ def get_dc
103
105
  if config[:data_center].is_a? Integer
104
106
  data_center = compute.data_centers.find { |dc| dc.id == config[:data_center] }
105
107
  else
106
- data_center = compute.data_centers.find { |dc| dc.location == config[:data_center] }
107
- if data_center.nil?
108
- data_center = compute.data_centers.find { |dc| dc.location =~ /#{config[:data_center]}/ }
109
- end
108
+ data_center = compute.data_centers.find { |dc| dc.location =~ /#{config[:data_center]}/ }
110
109
  end
110
+
111
111
  if data_center.nil?
112
112
  fail(UserError, "No match for data_center: #{config[:data_center]}")
113
113
  end
114
114
  info "Got data center: #{data_center.location}..."
115
-
116
- # set flavor
115
+ return data_center
116
+ end
117
+
118
+ def get_flavor
117
119
  if config[:flavor].is_a? Integer
118
120
  if config[:flavor] < 1024
119
121
  flavor = compute.flavors.find { |f| f.id == config[:flavor] }
@@ -121,51 +123,48 @@ module Kitchen
121
123
  flavor = compute.flavors.find { |f| f.ram == config[:flavor] }
122
124
  end
123
125
  else
124
- flavor = compute.flavors.find { |f| f.name == config[:flavor] }
125
- if flavor.nil?
126
- flavor = compute.flavors.find { |f| f.name =~ /#{config[:flavor]}/ }
127
- end
126
+ flavor = compute.flavors.find { |f| f.name =~ /#{config[:flavor]}/ }
128
127
  end
128
+
129
129
  if flavor.nil?
130
130
  fail(UserError, "No match for flavor: #{config[:flavor]}")
131
131
  end
132
132
  info "Got flavor: #{flavor.name}..."
133
-
134
- # set image/distribution
133
+ return flavor
134
+ end
135
+
136
+ def get_image
135
137
  if config[:image].is_a? Integer
136
138
  image = compute.images.find { |i| i.id == config[:image] }
137
139
  else
138
- image = compute.images.find { |i| i.name == config[:image] }
139
- if image.nil?
140
- image = compute.images.find { |i| i.name =~ /#{config[:image]}/ }
141
- end
140
+ image = compute.images.find { |i| i.name =~ /#{config[:image]}/ }
142
141
  end
143
142
  if image.nil?
144
143
  fail(UserError, "No match for image: #{config[:image]}")
145
144
  end
146
145
  info "Got image: #{image.name}..."
147
-
148
- # set kernel
146
+ return image
147
+ end
148
+
149
+ def get_kernel
149
150
  if config[:kernel].is_a? Integer
150
151
  kernel = compute.kernels.find { |k| k.id == config[:kernel] }
151
152
  else
152
- kernel = compute.kernels.find { |k| k.name == config[:kernel] }
153
- if kernel.nil?
154
- kernel = compute.kernels.find { |k| k.name =~ /#{config[:kernel]}/ }
155
- end
153
+ kernel = compute.kernels.find { |k| k.name =~ /#{config[:kernel]}/ }
156
154
  end
157
155
  if kernel.nil?
158
156
  fail(UserError, "No match for kernel: #{config[:kernel]}")
159
157
  end
160
158
  info "Got kernel: #{kernel.name}..."
159
+ return kernel
160
+ end
161
+
162
+ def create_server
163
+ data_center = get_dc
164
+ flavor = get_flavor
165
+ image = get_image
166
+ kernel = get_kernel
161
167
 
162
- if config[:private_key_path]
163
- config[:private_key_path] = File.expand_path(config[:private_key_path])
164
- end
165
- if config[:public_key_path]
166
- config[:public_key_path] = File.expand_path(config[:public_key_path])
167
- end
168
-
169
168
  # submit new linode request
170
169
  compute.servers.create(
171
170
  :data_center => data_center,
@@ -179,7 +178,8 @@ module Kitchen
179
178
  )
180
179
  end
181
180
 
182
- def setup_ssh(server, state)
181
+ def setup_ssh(state)
182
+ set_ssh_keys
183
183
  state[:ssh_key] = config[:private_key_path]
184
184
  do_ssh_setup(state, config)
185
185
  end
@@ -220,6 +220,23 @@ module Kitchen
220
220
  return if config[:server_name]
221
221
  config[:server_name] = "kitchen_linode-#{rand.to_s.split('.')[1]}"
222
222
  end
223
+
224
+ # ensure a password is set
225
+ def set_password
226
+ if config[:password].nil?
227
+ config[:password] = [*('a'..'z'),*('A'..'Z'),*('0'..'9')].sample(15).join
228
+ end
229
+ end
230
+
231
+ # set ssh keys
232
+ def set_ssh_keys
233
+ if config[:private_key_path]
234
+ config[:private_key_path] = File.expand_path(config[:private_key_path])
235
+ end
236
+ if config[:public_key_path]
237
+ config[:public_key_path] = File.expand_path(config[:public_key_path])
238
+ end
239
+ end
223
240
  end
224
241
  end
225
242
  end
@@ -20,6 +20,6 @@ module Kitchen
20
20
 
21
21
  module Driver
22
22
  # Version string for Linode Kitchen driver
23
- LINODE_VERSION = "0.10.0"
23
+ LINODE_VERSION = "0.11.0"
24
24
  end
25
25
  end
@@ -0,0 +1,170 @@
1
+ # Encoding: UTF-8
2
+
3
+ require_relative '../../spec_helper'
4
+ require_relative '../../../lib/kitchen/driver/linode'
5
+
6
+ require 'logger'
7
+ require 'stringio'
8
+ require 'rspec'
9
+ require 'kitchen'
10
+ require 'kitchen/driver/linode'
11
+ require 'kitchen/provisioner/dummy'
12
+ require 'kitchen/transport/dummy'
13
+ require 'kitchen/verifier/dummy'
14
+ require 'fog'
15
+
16
+ describe Kitchen::Driver::Linode do
17
+ let(:logged_output) { StringIO.new }
18
+ let(:logger) { Logger.new(logged_output) }
19
+ let(:config) { Hash.new }
20
+ let(:state) { Hash.new }
21
+ let(:rsa) { File.expand_path('~/.ssh/id_rsa') }
22
+ let(:instance_name) { 'the_thing' }
23
+ let(:transport) { Kitchen::Transport::Dummy.new }
24
+ let(:platform) { Kitchen::Platform.new(name: 'fake_platform') }
25
+ let(:driver) { Kitchen::Driver::Linode.new(config) }
26
+
27
+ let(:instance) do
28
+ double(
29
+ name: instance_name,
30
+ transport: transport,
31
+ logger: logger,
32
+ platform: platform,
33
+ to_str: 'instance'
34
+ )
35
+ end
36
+
37
+ let(:driver) { described_class.new(config) }
38
+
39
+ before(:each) do
40
+ allow_any_instance_of(described_class).to receive(:instance)
41
+ .and_return(instance)
42
+ allow(File).to receive(:exist?).and_call_original
43
+ allow(File).to receive(:exist?).with(rsa).and_return(true)
44
+ end
45
+
46
+ describe '#finalize_config' do
47
+ before(:each) { allow(File).to receive(:exist?).and_return(false) }
48
+
49
+ context 'both private and public key info provided' do
50
+ let(:config) do
51
+ { private_key_path: '/tmp/key', public_key_path: '/tmp/key.pub' }
52
+ end
53
+
54
+ it 'raises no error' do
55
+ expect(driver.finalize_config!(instance)).to be
56
+ end
57
+ end
58
+ end
59
+
60
+ describe '#initialize' do
61
+ context 'default options' do
62
+ context 'only a RSA SSH key available for the user' do
63
+ before(:each) do
64
+ allow(File).to receive(:exist?).and_return(false)
65
+ allow(File).to receive(:exist?).with(rsa).and_return(true)
66
+ end
67
+
68
+ it 'uses the local user\'s RSA private key' do
69
+ expect(driver[:private_key_path]).to eq(rsa)
70
+ end
71
+
72
+ it 'uses the local user\'s RSA public key' do
73
+ expect(driver[:public_key_path]).to eq(rsa + '.pub')
74
+ end
75
+ end
76
+
77
+ nils = [
78
+ :server_name,
79
+ :password
80
+ ]
81
+ nils.each do |i|
82
+ it "defaults to no #{i}" do
83
+ expect(driver[i]).to eq(nil)
84
+ end
85
+ end
86
+
87
+ end
88
+ context 'overridden options' do
89
+ let(:config) do
90
+ {
91
+ image: 139,
92
+ data_center: 10,
93
+ flavor: 2,
94
+ kernel: 215,
95
+ username: 'someuser',
96
+ server_name: 'thisserver',
97
+ private_key_path: '/path/to/id_rsa',
98
+ public_key_path: '/path/to/id_rsa.pub',
99
+ password: 'somepassword'
100
+ }
101
+ end
102
+
103
+ it 'uses all the overridden options' do
104
+ drv = driver
105
+ config.each do |k, v|
106
+ expect(drv[k]).to eq(v)
107
+ end
108
+ end
109
+
110
+ it 'overrides server name prefix with explicit server name, if given' do
111
+ expect(driver[:server_name]).to eq(config[:server_name])
112
+ end
113
+ end
114
+ end
115
+
116
+ describe '#create' do
117
+ let(:server) do
118
+ double(id: 'test123', wait_for: true, public_ip_address: %w(1.2.3.4))
119
+ end
120
+ let(:driver) do
121
+ d = super()
122
+ allow(d).to receive(:create_server).and_return(server)
123
+ allow(d).to receive(:do_ssh_setup).and_return(true)
124
+ d
125
+ end
126
+
127
+ context 'when a server is already created' do
128
+ it 'does not create a new instance' do
129
+ state[:server_id] = '1'
130
+ expect(driver).not_to receive(:create_server)
131
+ driver.create(state)
132
+ end
133
+ end
134
+
135
+ context 'required options provided' do
136
+ let(:config) do
137
+ {
138
+ username: 'someuser',
139
+ api_key: 'somekey',
140
+ disable_ssl_validation: false
141
+ }
142
+ end
143
+ let(:server) do
144
+ double(id: 'test123', wait_for: true, public_ip_address: %w(1.2.3.4))
145
+ end
146
+
147
+ let(:driver) do
148
+ d = described_class.new(config)
149
+ allow(d).to receive(:create_server).and_return(server)
150
+ allow(server).to receive(:id).and_return('test123')
151
+
152
+ allow(server).to receive(:wait_for)
153
+ .with(an_instance_of(Fixnum)).and_yield
154
+ allow(d).to receive(:bourne_shell?).and_return(false)
155
+ d
156
+ end
157
+
158
+ it 'returns nil, but modifies the state' do
159
+ expect(driver.send(:create, state)).to eq(nil)
160
+ expect(state[:server_id]).to eq('test123')
161
+ end
162
+
163
+ it 'throws an Action error when trying to create_server' do
164
+ allow(driver).to receive(:create_server).and_raise(Fog::Errors::Error)
165
+ expect { driver.send(:create, state) }.to raise_error(Kitchen::ActionFailed)
166
+ end
167
+ end
168
+ end
169
+
170
+ end