kitchen-linode 0.10.0 → 0.11.0

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