kitchen-nodes-fqdn 0.4.1
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/.gitignore +17 -0
- data/.rubocop.yml +2 -0
- data/.travis.yml +11 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +3 -0
- data/LICENSE +15 -0
- data/README.md +142 -0
- data/Rakefile +11 -0
- data/kitchen-nodes.gemspec +30 -0
- data/lib/kitchen/provisioner/ip_finder.rb +18 -0
- data/lib/kitchen/provisioner/ip_finder/ssh.rb +97 -0
- data/lib/kitchen/provisioner/ip_finder/winrm.rb +35 -0
- data/lib/kitchen/provisioner/nodes.rb +110 -0
- data/lib/kitchen/provisioner/nodes_version.rb +24 -0
- data/spec/unit/nodes_spec.rb +210 -0
- data/spec/unit/stubs/ifconfig.txt +36 -0
- data/spec/unit/stubs/ip.txt +15 -0
- metadata +178 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d0f9f88c511094a729079500e92c7d9dfe693977
|
4
|
+
data.tar.gz: 6d1f9c7880fceb77c8ac872b727f9edd6e65a7be
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e5d82650f62fad201c711b1d858487428ef1861c493d41b1002918799192c850ea1c662c469dd55cc48161e8d54eaa7f4d0c8a0a42bbb3811105fc5f53a58399
|
7
|
+
data.tar.gz: 2c12c15d84dbbbfa19db155f09f605278e758cc7cfa1f40ac9f3bc73769ea21baf56251de0c723417f0b7712402fa8542c53e36333514f2a085c177dda3dc745
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Author:: Matt Wrock (<matt@mattwrock.com>)
|
2
|
+
|
3
|
+
Copyright (C) 2015, Matt Wrock
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
# <a name="title"></a> Kitchen::Nodes
|
2
|
+
|
3
|
+
A Test Kitchen Provisioner that generates searchable Nodes.
|
4
|
+
|
5
|
+
The nodes provisioner extends the `chef-zero` provisioner along with all of its functionality and configuration. `chef-zero` can support chef searches by querying against node data stored in json files inside of the kitchen `nodes` folder. The `kitchen-nodes` plugin automatically generates a node file when a test instance is provisioned by test-kitchen.
|
6
|
+
|
7
|
+
### Example nodes:
|
8
|
+
|
9
|
+
```
|
10
|
+
{
|
11
|
+
"id": "server-community-ubuntu-1204",
|
12
|
+
"automatic": {
|
13
|
+
"ipaddress": "172.17.0.8",
|
14
|
+
"platform": "ubuntu"
|
15
|
+
},
|
16
|
+
"normal": {
|
17
|
+
"attr1": "val1"
|
18
|
+
}
|
19
|
+
"run_list": [
|
20
|
+
"recipe[apt]",
|
21
|
+
"recipe[couchbase-tests::ipaddress]",
|
22
|
+
"recipe[couchbase::server]",
|
23
|
+
"recipe[export-node]"
|
24
|
+
]
|
25
|
+
}
|
26
|
+
|
27
|
+
{
|
28
|
+
"id": "second-node-ubuntu-1204",
|
29
|
+
"automatic": {
|
30
|
+
"ipaddress": "172.17.0.9",
|
31
|
+
"platform": "ubuntu"
|
32
|
+
},
|
33
|
+
"run_list": [
|
34
|
+
"recipe[apt]",
|
35
|
+
"recipe[couchbase-tests::ipaddress]",
|
36
|
+
"recipe[couchbase-tests::default]",
|
37
|
+
"recipe[export-node]"
|
38
|
+
]
|
39
|
+
}
|
40
|
+
```
|
41
|
+
|
42
|
+
The node data includes the node id based on the test-kitchen suite name, the run list assigned to the node, the normal attributes included in the `.kitchen.yml` file, the externally reachable ip address and the platform of the test instance os.
|
43
|
+
|
44
|
+
## <a name="installation"></a> Installation
|
45
|
+
|
46
|
+
```
|
47
|
+
gem install kitchen-nodes
|
48
|
+
```
|
49
|
+
|
50
|
+
## <a name="config"></a> Configuration
|
51
|
+
|
52
|
+
Use `nodes` instead of `chef-zero` for the kitchen provisioner name.
|
53
|
+
|
54
|
+
```
|
55
|
+
provisioner:
|
56
|
+
name: nodes
|
57
|
+
```
|
58
|
+
|
59
|
+
## <a name="Usage"></a> Usage
|
60
|
+
|
61
|
+
Using `kitchen-nodes` one can expect all previously converged nodes to be represented in a node file and be searchable. For example consider this scenario looking for a primary node in a cluster in order to add a node to join:
|
62
|
+
|
63
|
+
```
|
64
|
+
require 'timeout'
|
65
|
+
|
66
|
+
def search_for_nodes(query, timeout = 120)
|
67
|
+
nodes = []
|
68
|
+
Timeout::timeout(timeout) do
|
69
|
+
nodes = search(:node, query)
|
70
|
+
until nodes.count > 0 && nodes[0].has_key?('ipaddress')
|
71
|
+
sleep 5
|
72
|
+
nodes = search(:node, query)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
if nodes.count == 0 || !nodes[0].has_key?('ipaddress')
|
77
|
+
raise "Unable to find nodes!"
|
78
|
+
end
|
79
|
+
|
80
|
+
nodes
|
81
|
+
end
|
82
|
+
|
83
|
+
primary = search_for_nodes("run_list:*couchbase??server* AND platform:#{node['platform']}")
|
84
|
+
node.normal["couchbase-tests"]["primary_ip"] = primary[0]['ipaddress']
|
85
|
+
|
86
|
+
```
|
87
|
+
### <a name="vagrant"></a> Using with Vagrant
|
88
|
+
|
89
|
+
When using kitchen-nodes with the vagrant driver, make sure you add the following to your `driver_config`:
|
90
|
+
|
91
|
+
```
|
92
|
+
network:
|
93
|
+
- ["private_network", { type: "dhcp" }]
|
94
|
+
```
|
95
|
+
|
96
|
+
This will add an additional non-NAT NIC to your vagrant box with an IP reachable from the host and other test nodes.
|
97
|
+
|
98
|
+
### <a name="virtualbox"></a> Why is my ohai `ipaddress` different from my node ipaddress on vagrant with VirtualBox?
|
99
|
+
|
100
|
+
Ohai will pick up the localhost ipaddress on vagrant boxes using virtualbox. To reset the `node["ipaddress"]` to the reachable ip, add `hurry-up-and-test::set_non_nat_vbox_ip` to the top of your `run_list`.
|
101
|
+
|
102
|
+
```
|
103
|
+
suites:
|
104
|
+
- name: my-suite
|
105
|
+
run_list:
|
106
|
+
- recipe[hurry-up-and-test::set_non_nat_vbox_ip]
|
107
|
+
- recipe[cookbook-under-test]
|
108
|
+
```
|
109
|
+
|
110
|
+
You can add this even if you do not use virtualbox and the recipe will do nothing.
|
111
|
+
|
112
|
+
|
113
|
+
## <a name="development"></a> Development
|
114
|
+
|
115
|
+
* Source hosted at [GitHub][repo]
|
116
|
+
* Report issues/questions/feature requests on [GitHub Issues][issues]
|
117
|
+
|
118
|
+
Pull requests are very welcome! Make sure your patches are well tested.
|
119
|
+
Ideally create a topic branch for every separate change you make. For
|
120
|
+
example:
|
121
|
+
|
122
|
+
1. Fork the repo
|
123
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
124
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
125
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
126
|
+
5. Create new Pull Request
|
127
|
+
|
128
|
+
## <a name="authors"></a> Authors
|
129
|
+
|
130
|
+
Created and maintained by [Matt Wrock][author] (<matt@mattwrock.com>)
|
131
|
+
|
132
|
+
## <a name="license"></a> License
|
133
|
+
|
134
|
+
Apache 2.0 (see [LICENSE][license])
|
135
|
+
|
136
|
+
|
137
|
+
[author]: https://github.com/mwrock
|
138
|
+
[issues]: https://github.com/mwrock/kitchen-nodes/issues
|
139
|
+
[license]: https://github.com/mwrock/kitchen-nodes/blob/master/LICENSE
|
140
|
+
[repo]: https://github.com/mwrock/kitchen-nodes
|
141
|
+
[driver_usage]: http://docs.kitchen-ci.org/drivers/usage
|
142
|
+
[chef_omnibus_dl]: http://www.getchef.com/chef/install/
|
data/Rakefile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'kitchen/provisioner/nodes_version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'kitchen-nodes-fqdn'
|
8
|
+
spec.version = Kitchen::Provisioner::NODES_VERSION
|
9
|
+
spec.authors = ['Matt Wrock', 'Marcin Cabaj']
|
10
|
+
spec.email = ['matt@mattwrock.com', 'mcabaj@gmail.com']
|
11
|
+
spec.description = 'A Test Kitchen Provisioner for Chef Nodes'
|
12
|
+
spec.summary = spec.description
|
13
|
+
spec.homepage = ''
|
14
|
+
spec.license = 'Apache 2.0'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = []
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_dependency 'net-ping'
|
22
|
+
spec.add_dependency 'win32-security'
|
23
|
+
spec.add_dependency 'test-kitchen', '~> 1.4'
|
24
|
+
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
26
|
+
spec.add_development_dependency 'fakefs', '~> 0.4'
|
27
|
+
spec.add_development_dependency 'rake'
|
28
|
+
spec.add_development_dependency 'rspec', '~> 3.2'
|
29
|
+
spec.add_development_dependency 'rubocop', '~> 0.29'
|
30
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Kitchen
|
2
|
+
module Provisioner
|
3
|
+
# Locates active IPs that are not localhost
|
4
|
+
# there are separate implementations for
|
5
|
+
# different kitchen transports
|
6
|
+
module IpFinder
|
7
|
+
def self.for_transport(transport, state)
|
8
|
+
transport_string = transport.class.name.split('::').last
|
9
|
+
require("kitchen/provisioner/ip_finder/#{transport_string.downcase}")
|
10
|
+
|
11
|
+
connection = transport.connection(state)
|
12
|
+
klass = const_get(transport_string)
|
13
|
+
object = klass.new(connection)
|
14
|
+
object
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Kitchen
|
2
|
+
module Transport
|
3
|
+
class Ssh < Kitchen::Transport::Base
|
4
|
+
# Monkey patch of test-kitchen ssh transport
|
5
|
+
# that returns stdout
|
6
|
+
class Connection < Kitchen::Transport::Base::Connection
|
7
|
+
def node_execute(command, &block)
|
8
|
+
return if command.nil?
|
9
|
+
out, exit_code = node_execute_with_exit_code(command, &block)
|
10
|
+
|
11
|
+
if exit_code != 0
|
12
|
+
fail Transport::SshFailed,
|
13
|
+
"SSH exited (#{exit_code}) for command: [#{command}]"
|
14
|
+
end
|
15
|
+
out
|
16
|
+
rescue Net::SSH::Exception => ex
|
17
|
+
raise SshFailed, "SSH command failed (#{ex.message})"
|
18
|
+
end
|
19
|
+
|
20
|
+
# rubocop:disable Metrics/AbcSize
|
21
|
+
def node_execute_with_exit_code(command)
|
22
|
+
exit_code = nil
|
23
|
+
out = []
|
24
|
+
session.open_channel do |channel|
|
25
|
+
channel.request_pty
|
26
|
+
channel.exec(command) do |_ch, _success|
|
27
|
+
channel.on_data do |_ch, data|
|
28
|
+
out << data
|
29
|
+
yield data if block_given?
|
30
|
+
end
|
31
|
+
|
32
|
+
channel.on_extended_data do |_ch, _type, data|
|
33
|
+
out << data
|
34
|
+
yield data if block_given?
|
35
|
+
end
|
36
|
+
|
37
|
+
channel.on_request('exit-status') do |_ch, data|
|
38
|
+
exit_code = data.read_long
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
session.loop
|
43
|
+
[out.join("\n"), exit_code]
|
44
|
+
end
|
45
|
+
# rubocop:enable Metrics/AbcSize
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
module Provisioner
|
51
|
+
module IpFinder
|
52
|
+
# SSH implementation for returning active non-localhost IPs
|
53
|
+
class Ssh
|
54
|
+
IP4REGEX = /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/
|
55
|
+
def initialize(connection)
|
56
|
+
@connection = connection
|
57
|
+
end
|
58
|
+
|
59
|
+
def find_ips
|
60
|
+
ips = []
|
61
|
+
(0..5).each do
|
62
|
+
begin
|
63
|
+
ips = run_ifconfig
|
64
|
+
rescue Kitchen::Transport::TransportFailed
|
65
|
+
ips = run_ip_addr
|
66
|
+
end
|
67
|
+
return ips unless ips.empty?
|
68
|
+
sleep 0.5
|
69
|
+
end
|
70
|
+
ips
|
71
|
+
end
|
72
|
+
|
73
|
+
def run_ifconfig
|
74
|
+
response = @connection.node_execute('/sbin/ifconfig -a')
|
75
|
+
ips = []
|
76
|
+
response.split(/^\S+/).each do |device|
|
77
|
+
next if !device.include?('RUNNING') || device.include?('LOOPBACK')
|
78
|
+
ips << IP4REGEX.match(device)[1]
|
79
|
+
end
|
80
|
+
ips.compact
|
81
|
+
end
|
82
|
+
|
83
|
+
def run_ip_addr
|
84
|
+
response = @connection.node_execute('/sbin/ip -4 addr show')
|
85
|
+
ips = []
|
86
|
+
response.split(/^[0-9]+: /).each do |device|
|
87
|
+
next if device.include?('LOOPBACK') || device.include?('NO-CARRIER')
|
88
|
+
next if device == ''
|
89
|
+
found_ips = IP4REGEX.match(device)
|
90
|
+
ips << IP4REGEX.match(device)[1] unless found_ips.nil?
|
91
|
+
end
|
92
|
+
ips.compact
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Kitchen
|
2
|
+
module Transport
|
3
|
+
class Winrm < Kitchen::Transport::Base
|
4
|
+
# Monkey patch of test-kitchen winrm transport
|
5
|
+
# that returns stdout
|
6
|
+
class Connection < Kitchen::Transport::Base::Connection
|
7
|
+
def node_execute(command, &block)
|
8
|
+
session.run_powershell_script(command, &block)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module Provisioner
|
15
|
+
module IpFinder
|
16
|
+
# WinRM implementation for returning active non-localhost IPs
|
17
|
+
class Winrm
|
18
|
+
def initialize(connection)
|
19
|
+
@connection = connection
|
20
|
+
end
|
21
|
+
|
22
|
+
def find_ips
|
23
|
+
out = @connection.node_execute(
|
24
|
+
'Get-NetIPConfiguration | % { $_.ipv4address.IPAddress }')
|
25
|
+
data = []
|
26
|
+
out[:data].each do |out_data|
|
27
|
+
stdout = out_data[:stdout]
|
28
|
+
data << stdout.chomp unless stdout.nil?
|
29
|
+
end
|
30
|
+
data
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Author:: Matt Wrock (<matt@mattwrock.com>)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2015, Matt Wrock
|
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 'kitchen'
|
20
|
+
require 'kitchen/provisioner/chef_zero'
|
21
|
+
require 'kitchen/provisioner/ip_finder'
|
22
|
+
require 'net/ping'
|
23
|
+
|
24
|
+
module Kitchen
|
25
|
+
module Provisioner
|
26
|
+
# Nodes provisioner for Kitchen.
|
27
|
+
#
|
28
|
+
# @author Matt Wrock <matt@mattwrock.com>
|
29
|
+
class Nodes < ChefZero
|
30
|
+
def create_sandbox
|
31
|
+
FileUtils.rm(node_file) if File.exist?(node_file)
|
32
|
+
create_node
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
def create_node
|
37
|
+
FileUtils.mkdir_p(node_dir) unless Dir.exist?(node_dir)
|
38
|
+
template_to_write = node_template
|
39
|
+
File.open(node_file, 'w') do |out|
|
40
|
+
out << JSON.pretty_generate(template_to_write)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def ipaddress
|
45
|
+
state = Kitchen::StateFile.new(
|
46
|
+
config[:kitchen_root],
|
47
|
+
instance.name
|
48
|
+
).read
|
49
|
+
|
50
|
+
if %w(127.0.0.1 localhost).include?(state[:hostname])
|
51
|
+
return get_reachable_guest_address(state)
|
52
|
+
end
|
53
|
+
state[:hostname]
|
54
|
+
end
|
55
|
+
|
56
|
+
def chef_environment
|
57
|
+
env = '_default'
|
58
|
+
if config[:client_rb] && config[:client_rb][:environment]
|
59
|
+
env = config[:client_rb][:environment]
|
60
|
+
end
|
61
|
+
env
|
62
|
+
end
|
63
|
+
|
64
|
+
def node_template
|
65
|
+
state = Kitchen::StateFile.new(
|
66
|
+
config[:kitchen_root],
|
67
|
+
instance.name
|
68
|
+
).read
|
69
|
+
state[:username] = instance.driver[:username]
|
70
|
+
state[:password] = instance.driver[:password]
|
71
|
+
{
|
72
|
+
id: instance.name,
|
73
|
+
chef_environment: chef_environment,
|
74
|
+
automatic: {
|
75
|
+
ipaddress: ipaddress,
|
76
|
+
platform: instance.platform.name.split('-')[0].downcase,
|
77
|
+
fqdn: instance.transport.connection(state).node_execute('hostname -f')[/(\w|\.)+/]
|
78
|
+
},
|
79
|
+
normal: config[:attributes],
|
80
|
+
run_list: config[:run_list]
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
def node_dir
|
85
|
+
File.join(config[:test_base_path], 'nodes')
|
86
|
+
end
|
87
|
+
|
88
|
+
def node_file
|
89
|
+
File.join(node_dir, "#{instance.name}.json")
|
90
|
+
end
|
91
|
+
|
92
|
+
def get_reachable_guest_address(state)
|
93
|
+
active_ips(instance.transport, state).each do |address|
|
94
|
+
next if address == '127.0.0.1'
|
95
|
+
return address if Net::Ping::External.new.ping(address)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def active_ips(transport, state)
|
100
|
+
# inject creds into state for legacy drivers
|
101
|
+
[:username, :password].each do |prop|
|
102
|
+
state[prop] = instance.driver[prop] if instance.driver[prop]
|
103
|
+
end
|
104
|
+
ips = IpFinder.for_transport(transport, state).find_ips
|
105
|
+
fail 'Unable to retrieve IPs' if ips.empty?
|
106
|
+
ips
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Author:: Matt Wrock (<matt@mattwrock.com>)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2015, Matt Wrock
|
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
|
+
# Version string for Nodes Kitchen driver
|
21
|
+
module Provisioner
|
22
|
+
NODES_VERSION = '0.4.1'
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
require 'erb'
|
2
|
+
require 'fakefs/safe'
|
3
|
+
require 'kitchen'
|
4
|
+
require 'kitchen/driver/dummy'
|
5
|
+
require 'kitchen/provisioner/nodes'
|
6
|
+
require 'kitchen/transport/dummy'
|
7
|
+
require 'kitchen/transport/winrm'
|
8
|
+
require 'kitchen/transport/ssh'
|
9
|
+
|
10
|
+
describe Kitchen::Provisioner::Nodes do
|
11
|
+
let(:config) do
|
12
|
+
{
|
13
|
+
test_base_path: '/b',
|
14
|
+
kitchen_root: '/r',
|
15
|
+
run_list: 'cookbook:recipe',
|
16
|
+
attributes: { att_key: 'att_val' },
|
17
|
+
client_rb: { environment: 'my_env' }
|
18
|
+
}
|
19
|
+
end
|
20
|
+
let(:instance) do
|
21
|
+
double(
|
22
|
+
'instance',
|
23
|
+
name: 'test_suite',
|
24
|
+
suite: suite,
|
25
|
+
platform: platform,
|
26
|
+
transport: transport,
|
27
|
+
driver: Kitchen::Driver::Dummy.new
|
28
|
+
)
|
29
|
+
end
|
30
|
+
let(:transport) { Kitchen::Transport::Dummy.new }
|
31
|
+
let(:platform) { double('platform', os_type: nil, name: 'ubuntu') }
|
32
|
+
let(:suite) { double('suite', name: 'suite') }
|
33
|
+
let(:state) { { hostname: '192.168.1.10' } }
|
34
|
+
let(:node) { JSON.parse(File.read(subject.node_file), symbolize_names: true) }
|
35
|
+
|
36
|
+
before do
|
37
|
+
FakeFS.activate!
|
38
|
+
FileUtils.mkdir_p(config[:test_base_path])
|
39
|
+
allow_any_instance_of(Kitchen::StateFile)
|
40
|
+
.to receive(:read).and_return(state)
|
41
|
+
end
|
42
|
+
after do
|
43
|
+
FakeFS.deactivate!
|
44
|
+
FakeFS::FileSystem.clear
|
45
|
+
end
|
46
|
+
|
47
|
+
subject { Kitchen::Provisioner::Nodes.new(config).finalize_config!(instance) }
|
48
|
+
|
49
|
+
it 'creates node' do
|
50
|
+
subject.create_node
|
51
|
+
|
52
|
+
expect(File).to exist(subject.node_file)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'sets the id' do
|
56
|
+
subject.create_node
|
57
|
+
|
58
|
+
expect(node[:id]).to eq instance.name
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'sets the environment' do
|
62
|
+
subject.create_node
|
63
|
+
|
64
|
+
expect(node[:chef_environment]).to eq config[:client_rb][:environment]
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'sets the runlist' do
|
68
|
+
subject.create_node
|
69
|
+
|
70
|
+
expect(node[:run_list]).to eq config[:run_list]
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'sets the normal attributes' do
|
74
|
+
subject.create_node
|
75
|
+
|
76
|
+
expect(node[:normal]).to eq config[:attributes]
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'sets the ip address' do
|
80
|
+
subject.create_node
|
81
|
+
|
82
|
+
expect(node[:automatic][:ipaddress]).to eq state[:hostname]
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'no environment explicitly set' do
|
86
|
+
before { config.delete(:client_rb) }
|
87
|
+
|
88
|
+
it 'sets the environment' do
|
89
|
+
subject.create_node
|
90
|
+
|
91
|
+
expect(node[:chef_environment]).to eq '_default'
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'instance is localhost' do
|
96
|
+
let(:state) { { hostname: '127.0.0.1' } }
|
97
|
+
let(:machine_ips) { ['192.168.1.1', '192.168.1.2', '192.168.1.3'] }
|
98
|
+
|
99
|
+
before do
|
100
|
+
allow_any_instance_of(Net::Ping::External).to receive(:ping)
|
101
|
+
.and_return(true)
|
102
|
+
allow(transport).to receive(:connection)
|
103
|
+
.and_return(Kitchen::Transport::Base::Connection.new)
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'cannot find an ip' do
|
107
|
+
let(:ifconfig_response) do
|
108
|
+
FakeFS.deactivate!
|
109
|
+
template = File.read('spec/unit/stubs/ifconfig.txt')
|
110
|
+
FakeFS.activate!
|
111
|
+
template.gsub!('', machine_ips[0])
|
112
|
+
template.gsub!('', machine_ips[1])
|
113
|
+
template.gsub!('', machine_ips[2])
|
114
|
+
end
|
115
|
+
let(:transport) { Kitchen::Transport::Ssh.new }
|
116
|
+
|
117
|
+
before do
|
118
|
+
allow_any_instance_of(Kitchen::Transport::Base::Connection)
|
119
|
+
.to receive(:node_execute).and_return(ifconfig_response)
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'fails' do
|
123
|
+
expect { subject.create_node }.to raise_error('Unable to retrieve IPs')
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context 'platform is windows' do
|
128
|
+
let(:transport) { Kitchen::Transport::Winrm.new }
|
129
|
+
|
130
|
+
before do
|
131
|
+
data = machine_ips.map { |ip| { stdout: "#{ip}\r\n" } }
|
132
|
+
allow_any_instance_of(Kitchen::Transport::Base::Connection).to(
|
133
|
+
receive(:node_execute).and_return(data: data)
|
134
|
+
)
|
135
|
+
allow(platform).to receive(:name).and_return('windows')
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'sets the ip address to the first reachable IP' do
|
139
|
+
subject.create_node
|
140
|
+
|
141
|
+
expect(node[:automatic][:ipaddress]).to eq machine_ips.first
|
142
|
+
end
|
143
|
+
|
144
|
+
context 'only the last ip is reachable' do
|
145
|
+
before do
|
146
|
+
allow_any_instance_of(Net::Ping::External).to receive(:ping)
|
147
|
+
.and_return(false)
|
148
|
+
allow_any_instance_of(Net::Ping::External).to receive(:ping)
|
149
|
+
.with(machine_ips.last).and_return(true)
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'sets the ip address to the last IP' do
|
153
|
+
subject.create_node
|
154
|
+
|
155
|
+
expect(node[:automatic][:ipaddress]).to eq machine_ips.last
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
context 'platform is *nix' do
|
161
|
+
let(:ifconfig_response) do
|
162
|
+
FakeFS.deactivate!
|
163
|
+
template = File.read('spec/unit/stubs/ifconfig.txt')
|
164
|
+
FakeFS.activate!
|
165
|
+
template.gsub!('1.1.1.1', machine_ips[0])
|
166
|
+
template.gsub!('2.2.2.2', machine_ips[1])
|
167
|
+
template.gsub!('3.3.3.3', machine_ips[2])
|
168
|
+
end
|
169
|
+
let(:transport) { Kitchen::Transport::Ssh.new }
|
170
|
+
|
171
|
+
before do
|
172
|
+
allow_any_instance_of(Kitchen::Transport::Base::Connection)
|
173
|
+
.to receive(:node_execute).and_return(ifconfig_response)
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'sets the ip address to the RUNNING IP that is not localhost' do
|
177
|
+
subject.create_node
|
178
|
+
|
179
|
+
expect(node[:automatic][:ipaddress]).to eq machine_ips[1]
|
180
|
+
end
|
181
|
+
|
182
|
+
context 'ifconfig not supported' do
|
183
|
+
let(:ip_response) do
|
184
|
+
FakeFS.deactivate!
|
185
|
+
template = File.read('spec/unit/stubs/ip.txt')
|
186
|
+
FakeFS.activate!
|
187
|
+
template.gsub!('1.1.1.1', machine_ips[0])
|
188
|
+
template.gsub!('2.2.2.2', machine_ips[1])
|
189
|
+
template.gsub!('3.3.3.3', machine_ips[2])
|
190
|
+
end
|
191
|
+
|
192
|
+
before do
|
193
|
+
allow_any_instance_of(Kitchen::Transport::Base::Connection)
|
194
|
+
.to receive(:node_execute).with('/sbin/ifconfig -a')
|
195
|
+
.and_raise(Kitchen::Transport::TransportFailed.new(''))
|
196
|
+
|
197
|
+
allow_any_instance_of(Kitchen::Transport::Base::Connection)
|
198
|
+
.to receive(:node_execute).with('/sbin/ip -4 addr show')
|
199
|
+
.and_return(ip_response)
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'sets the ip address to the connected IP that is not localhost' do
|
203
|
+
subject.create_node
|
204
|
+
|
205
|
+
expect(node[:automatic][:ipaddress]).to eq machine_ips[0]
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
bash: warning: setlocale: LC_ALL: cannot change locale (en_US)
|
2
|
+
docker0 Link encap:Ethernet HWaddr 56:84:7a:fe:97:99
|
3
|
+
inet addr:1.1.1.1 Bcast:0.0.0.0 Mask:255.255.0.0
|
4
|
+
UP BROADCAST MULTICAST MTU:1500 Metric:1
|
5
|
+
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
|
6
|
+
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
|
7
|
+
collisions:0 txqueuelen:0
|
8
|
+
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
|
9
|
+
|
10
|
+
eth0 Link encap:Ethernet HWaddr 08:00:27:88:0c:a6
|
11
|
+
inet addr:2.2.2.2 Bcast:10.0.2.255 Mask:255.255.255.0
|
12
|
+
inet6 addr: fe80::a00:27ff:fe88:ca6/64 Scope:Link
|
13
|
+
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
|
14
|
+
RX packets:10262 errors:0 dropped:0 overruns:0 frame:0
|
15
|
+
TX packets:7470 errors:0 dropped:0 overruns:0 carrier:0
|
16
|
+
collisions:0 txqueuelen:1000
|
17
|
+
RX bytes:1497781 (1.4 MB) TX bytes:1701791 (1.7 MB)
|
18
|
+
|
19
|
+
lo Link encap:Local Loopback
|
20
|
+
inet addr:127.0.0.1 Mask:255.0.0.0
|
21
|
+
inet6 addr: ::1/128 Scope:Host
|
22
|
+
UP LOOPBACK RUNNING MTU:65536 Metric:1
|
23
|
+
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
|
24
|
+
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
|
25
|
+
collisions:0 txqueuelen:0
|
26
|
+
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
|
27
|
+
|
28
|
+
# The following represents the format of ifconfig from CentOS 7.1
|
29
|
+
enp0s8: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
|
30
|
+
inet 3.3.3.3 netmask 255.255.255.0 broadcast 3.3.3.0
|
31
|
+
inet6 fe80::a00:27ff:fe5e:e9b0 prefixlen 64 scopeid 0x20<link>
|
32
|
+
ether 08:00:27:5e:e9:b0 txqueuelen 1000 (Ethernet)
|
33
|
+
RX packets 7961 bytes 823710 (804.4 KiB)
|
34
|
+
RX errors 0 dropped 0 overruns 0 frame 0
|
35
|
+
TX packets 263 bytes 50868 (49.6 KiB)
|
36
|
+
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
|
@@ -0,0 +1,15 @@
|
|
1
|
+
bash: warning: setlocale: LC_ALL: cannot change locale (en_US)
|
2
|
+
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default
|
3
|
+
inet 127.0.0.1/8 scope host lo
|
4
|
+
valid_lft forever preferred_lft forever
|
5
|
+
3: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
|
6
|
+
inet 1.1.1.1/24 brd 192.168.1.255 scope global wlan0
|
7
|
+
valid_lft forever preferred_lft forever
|
8
|
+
5: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
|
9
|
+
inet 2.2.2.2/16 scope global docker0
|
10
|
+
valid_lft forever preferred_lft forever
|
11
|
+
# The following represents the format of ipaddr from CentOS 7.1 (which is the same in this case, but
|
12
|
+
# adding for completeness)
|
13
|
+
6: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
|
14
|
+
inet 3.3.3.3/24 brd 3.3.3.255 scope global dynamic enp0s8
|
15
|
+
valid_lft 966sec preferred_lft 966sec
|
metadata
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: kitchen-nodes-fqdn
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matt Wrock
|
8
|
+
- Marcin Cabaj
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2015-09-30 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: net-ping
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: win32-security
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: test-kitchen
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '1.4'
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '1.4'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: bundler
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '1.3'
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '1.3'
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: fakefs
|
72
|
+
requirement: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0.4'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0.4'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: rake
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: rspec
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - "~>"
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '3.2'
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - "~>"
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '3.2'
|
112
|
+
- !ruby/object:Gem::Dependency
|
113
|
+
name: rubocop
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - "~>"
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0.29'
|
119
|
+
type: :development
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - "~>"
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0.29'
|
126
|
+
description: A Test Kitchen Provisioner for Chef Nodes
|
127
|
+
email:
|
128
|
+
- matt@mattwrock.com
|
129
|
+
- mcabaj@gmail.com
|
130
|
+
executables: []
|
131
|
+
extensions: []
|
132
|
+
extra_rdoc_files: []
|
133
|
+
files:
|
134
|
+
- ".gitignore"
|
135
|
+
- ".rubocop.yml"
|
136
|
+
- ".travis.yml"
|
137
|
+
- CHANGELOG.md
|
138
|
+
- Gemfile
|
139
|
+
- LICENSE
|
140
|
+
- README.md
|
141
|
+
- Rakefile
|
142
|
+
- kitchen-nodes.gemspec
|
143
|
+
- lib/kitchen/provisioner/ip_finder.rb
|
144
|
+
- lib/kitchen/provisioner/ip_finder/ssh.rb
|
145
|
+
- lib/kitchen/provisioner/ip_finder/winrm.rb
|
146
|
+
- lib/kitchen/provisioner/nodes.rb
|
147
|
+
- lib/kitchen/provisioner/nodes_version.rb
|
148
|
+
- spec/unit/nodes_spec.rb
|
149
|
+
- spec/unit/stubs/ifconfig.txt
|
150
|
+
- spec/unit/stubs/ip.txt
|
151
|
+
homepage: ''
|
152
|
+
licenses:
|
153
|
+
- Apache 2.0
|
154
|
+
metadata: {}
|
155
|
+
post_install_message:
|
156
|
+
rdoc_options: []
|
157
|
+
require_paths:
|
158
|
+
- lib
|
159
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
160
|
+
requirements:
|
161
|
+
- - ">="
|
162
|
+
- !ruby/object:Gem::Version
|
163
|
+
version: '0'
|
164
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - ">="
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0'
|
169
|
+
requirements: []
|
170
|
+
rubyforge_project:
|
171
|
+
rubygems_version: 2.4.5.1
|
172
|
+
signing_key:
|
173
|
+
specification_version: 4
|
174
|
+
summary: A Test Kitchen Provisioner for Chef Nodes
|
175
|
+
test_files:
|
176
|
+
- spec/unit/nodes_spec.rb
|
177
|
+
- spec/unit/stubs/ifconfig.txt
|
178
|
+
- spec/unit/stubs/ip.txt
|