kitchen-nodes-lobatoa 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitattributes +3 -0
- data/.gitignore +20 -0
- data/.kitchen.travis.yml +37 -0
- data/.kitchen.yml +48 -0
- data/.rspec +1 -0
- data/.rubocop.yml +9 -0
- data/.travis.yml +22 -0
- data/Berksfile +7 -0
- data/CHANGELOG.md +124 -0
- data/Gemfile +11 -0
- data/LICENSE +15 -0
- data/README.md +154 -0
- data/Rakefile +24 -0
- data/kitchen-nodes.gemspec +31 -0
- data/lib/kitchen/provisioner/finder.rb +25 -0
- data/lib/kitchen/provisioner/finder/ssh.rb +107 -0
- data/lib/kitchen/provisioner/finder/winrm.rb +49 -0
- data/lib/kitchen/provisioner/nodes.rb +164 -0
- data/lib/kitchen/provisioner/nodes_version.rb +24 -0
- data/lib/kitchen/provisioner/run_list_expansion_from_kitchen.rb +38 -0
- data/spec/unit/nodes_spec.rb +285 -0
- data/spec/unit/stubs/ifconfig.txt +36 -0
- data/spec/unit/stubs/ip.txt +15 -0
- data/test/fixtures/roles/test_json_role.json +10 -0
- data/test/fixtures/roles/test_ruby_role.rb +2 -0
- data/test/integration/cookbooks/node-tests/libraries/helper.rb +18 -0
- data/test/integration/cookbooks/node-tests/metadata.rb +4 -0
- data/test/integration/cookbooks/node-tests/recipes/node1.rb +0 -0
- data/test/integration/cookbooks/node-tests/recipes/node2.rb +10 -0
- data/test/integration/node1/serverspec/default_spec.rb +0 -0
- data/test/integration/node2/serverspec/Gemfile +3 -0
- data/test/integration/node2/serverspec/default_spec.rb +69 -0
- metadata +219 -0
@@ -0,0 +1,285 @@
|
|
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
|
+
require 'winrm'
|
10
|
+
|
11
|
+
# rubocop:disable Metrics/BlockLength
|
12
|
+
describe Kitchen::Provisioner::Nodes do
|
13
|
+
let(:config) do
|
14
|
+
{
|
15
|
+
test_base_path: '/b',
|
16
|
+
kitchen_root: '/r',
|
17
|
+
run_list: ['recipe[cookbook::default]'],
|
18
|
+
attributes: { att_key: 'att_val' },
|
19
|
+
client_rb: { environment: 'my_env' },
|
20
|
+
reset_node_files: false
|
21
|
+
}
|
22
|
+
end
|
23
|
+
let(:instance) do
|
24
|
+
double(
|
25
|
+
'instance',
|
26
|
+
name: 'test_suite',
|
27
|
+
suite: suite,
|
28
|
+
platform: platform,
|
29
|
+
transport: transport,
|
30
|
+
driver: Kitchen::Driver::Dummy.new
|
31
|
+
)
|
32
|
+
end
|
33
|
+
let(:transport) { Kitchen::Transport::Ssh.new }
|
34
|
+
let(:platform) { double('platform', os_type: nil, name: 'ubuntu') }
|
35
|
+
let(:suite) { double('suite', name: 'suite') }
|
36
|
+
let(:state) { { hostname: '192.168.1.10' } }
|
37
|
+
let(:node) { JSON.parse(File.read(subject.node_file), symbolize_names: true) }
|
38
|
+
|
39
|
+
before do
|
40
|
+
FakeFS.activate!
|
41
|
+
FileUtils.mkdir_p(config[:test_base_path])
|
42
|
+
allow_any_instance_of(Kitchen::StateFile)
|
43
|
+
.to receive(:read).and_return(state)
|
44
|
+
allow(transport).to receive(:connection)
|
45
|
+
.and_return(Kitchen::Transport::Base::Connection.new)
|
46
|
+
allow_any_instance_of(Kitchen::Transport::Base::Connection)
|
47
|
+
.to receive(:node_execute).with('hostname -f')
|
48
|
+
.and_return("fakehostname\n\n")
|
49
|
+
end
|
50
|
+
after do
|
51
|
+
FakeFS.deactivate!
|
52
|
+
FakeFS::FileSystem.clear
|
53
|
+
end
|
54
|
+
|
55
|
+
subject { Kitchen::Provisioner::Nodes.new(config).finalize_config!(instance) }
|
56
|
+
|
57
|
+
describe '#create_node' do
|
58
|
+
context 'node file does not exist' do
|
59
|
+
before do
|
60
|
+
allow(Dir).to receive(:exist?).and_return(false)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'creates node' do
|
64
|
+
subject.create_node
|
65
|
+
|
66
|
+
expect(File).to exist(subject.node_file)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
context 'node file exists' do
|
70
|
+
before do
|
71
|
+
allow(Dir).to receive(:exist?).and_return(true)
|
72
|
+
expect(File).to receive(:exist?).with(subject.node_file).and_return(true)
|
73
|
+
end
|
74
|
+
|
75
|
+
context 'config[:reset_node_files] = false' do
|
76
|
+
it 'does not update the node file' do
|
77
|
+
expect(File).not_to receive(:open)
|
78
|
+
|
79
|
+
subject.create_node
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'config[:reset_node_files] = true' do
|
84
|
+
before do
|
85
|
+
config[:reset_node_files] = true
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'updates the node file' do
|
89
|
+
expect(File).to receive(:open)
|
90
|
+
|
91
|
+
subject.create_node
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'sets the id' do
|
98
|
+
subject.create_node
|
99
|
+
|
100
|
+
expect(node[:id]).to eq instance.name
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'sets the environment' do
|
104
|
+
subject.create_node
|
105
|
+
|
106
|
+
expect(node[:chef_environment]).to eq config[:client_rb][:environment]
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'sets the runlist' do
|
110
|
+
subject.create_node
|
111
|
+
|
112
|
+
expect(node[:run_list]).to eq config[:run_list]
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'expands the runlist' do
|
116
|
+
subject.create_node
|
117
|
+
|
118
|
+
expect(node[:automatic][:recipes]).to eq ['cookbook::default']
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'sets the named_runlist' do
|
122
|
+
subject.create_node
|
123
|
+
|
124
|
+
expect(node[:named_run_list]).to eq config[:named_run_list]
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'sets the normal attributes' do
|
128
|
+
subject.create_node
|
129
|
+
|
130
|
+
expect(node[:normal]).to eq config[:attributes]
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'sets the ip address' do
|
134
|
+
subject.create_node
|
135
|
+
|
136
|
+
expect(node[:automatic][:ipaddress]).to eq state[:hostname]
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'sets the fqdn' do
|
140
|
+
subject.create_node
|
141
|
+
|
142
|
+
expect(node[:automatic][:fqdn]).to eq 'fakehostname'
|
143
|
+
end
|
144
|
+
|
145
|
+
context 'cannot obtain fqdn' do
|
146
|
+
before do
|
147
|
+
allow_any_instance_of(Kitchen::Transport::Base::Connection)
|
148
|
+
.to receive(:node_execute).with('hostname -f')
|
149
|
+
.and_raise(Kitchen::Transport::TransportFailed.new(''))
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'sets the fqdn to nil' do
|
153
|
+
subject.create_node
|
154
|
+
expect(node[:automatic][:fqdn]).to be_nil
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
context 'no environment explicitly set' do
|
159
|
+
before { config.delete(:client_rb) }
|
160
|
+
|
161
|
+
it 'sets the environment' do
|
162
|
+
subject.create_node
|
163
|
+
|
164
|
+
expect(node[:chef_environment]).to eq '_default'
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
context 'instance is localhost' do
|
169
|
+
let(:state) { { hostname: '127.0.0.1' } }
|
170
|
+
let(:machine_ips) { ['192.168.1.1', '192.168.1.2', '192.168.1.3'] }
|
171
|
+
|
172
|
+
before do
|
173
|
+
allow_any_instance_of(Net::Ping::External).to receive(:ping)
|
174
|
+
.and_return(true)
|
175
|
+
end
|
176
|
+
|
177
|
+
context 'cannot find an ip' do
|
178
|
+
let(:ifconfig_response) do
|
179
|
+
FakeFS.deactivate!
|
180
|
+
template = File.read('spec/unit/stubs/ifconfig.txt')
|
181
|
+
FakeFS.activate!
|
182
|
+
template.gsub!('', machine_ips[0])
|
183
|
+
template.gsub!('', machine_ips[1])
|
184
|
+
template.gsub!('', machine_ips[2])
|
185
|
+
end
|
186
|
+
let(:transport) { Kitchen::Transport::Ssh.new }
|
187
|
+
|
188
|
+
before do
|
189
|
+
allow_any_instance_of(Kitchen::Transport::Base::Connection)
|
190
|
+
.to receive(:node_execute).and_return(ifconfig_response)
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'fails' do
|
194
|
+
expect { subject.create_node }.to raise_error('Unable to retrieve IPs')
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
context 'platform is windows' do
|
199
|
+
let(:transport) { Kitchen::Transport::Winrm.new }
|
200
|
+
|
201
|
+
before do
|
202
|
+
data = WinRM::Output.new
|
203
|
+
data << { stdout: "\r\n" }
|
204
|
+
machine_ips.map { |ip| data << { stdout: "IPv4 Address .: #{ip}\r\n" } }
|
205
|
+
allow_any_instance_of(Kitchen::Transport::Base::Connection).to(
|
206
|
+
receive(:node_execute).and_return(data)
|
207
|
+
)
|
208
|
+
allow(platform).to receive(:name).and_return('windows')
|
209
|
+
end
|
210
|
+
|
211
|
+
it 'sets the ip address to the first reachable IP' do
|
212
|
+
subject.create_node
|
213
|
+
|
214
|
+
expect(node[:automatic][:ipaddress]).to eq machine_ips.first
|
215
|
+
end
|
216
|
+
|
217
|
+
context 'only the last ip is reachable' do
|
218
|
+
before do
|
219
|
+
allow_any_instance_of(Net::Ping::TCP).to receive(:ping)
|
220
|
+
.and_return(false)
|
221
|
+
allow_any_instance_of(Net::Ping::External).to receive(:ping)
|
222
|
+
.and_return(false)
|
223
|
+
allow_any_instance_of(Net::Ping::External).to receive(:ping)
|
224
|
+
.with(machine_ips.last).and_return(true)
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'sets the ip address to the last IP' do
|
228
|
+
subject.create_node
|
229
|
+
|
230
|
+
expect(node[:automatic][:ipaddress]).to eq machine_ips.last
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
context 'platform is *nix' do
|
236
|
+
let(:ifconfig_response) do
|
237
|
+
FakeFS.deactivate!
|
238
|
+
template = File.read('spec/unit/stubs/ifconfig.txt')
|
239
|
+
FakeFS.activate!
|
240
|
+
template.gsub!('1.1.1.1', machine_ips[0])
|
241
|
+
template.gsub!('2.2.2.2', machine_ips[1])
|
242
|
+
template.gsub!('3.3.3.3', machine_ips[2])
|
243
|
+
end
|
244
|
+
let(:transport) { Kitchen::Transport::Ssh.new }
|
245
|
+
|
246
|
+
before do
|
247
|
+
allow_any_instance_of(Kitchen::Transport::Base::Connection)
|
248
|
+
.to receive(:node_execute).and_return(ifconfig_response)
|
249
|
+
end
|
250
|
+
|
251
|
+
it 'sets the ip address to the RUNNING IP that is not localhost' do
|
252
|
+
subject.create_node
|
253
|
+
|
254
|
+
expect(node[:automatic][:ipaddress]).to eq machine_ips[1]
|
255
|
+
end
|
256
|
+
|
257
|
+
context 'ifconfig not supported' do
|
258
|
+
let(:ip_response) do
|
259
|
+
FakeFS.deactivate!
|
260
|
+
template = File.read('spec/unit/stubs/ip.txt')
|
261
|
+
FakeFS.activate!
|
262
|
+
template.gsub!('1.1.1.1', machine_ips[0])
|
263
|
+
template.gsub!('2.2.2.2', machine_ips[1])
|
264
|
+
template.gsub!('3.3.3.3', machine_ips[2])
|
265
|
+
end
|
266
|
+
|
267
|
+
before do
|
268
|
+
allow_any_instance_of(Kitchen::Transport::Base::Connection)
|
269
|
+
.to receive(:node_execute).with('/sbin/ifconfig -a')
|
270
|
+
.and_raise(Kitchen::Transport::TransportFailed.new(''))
|
271
|
+
|
272
|
+
allow_any_instance_of(Kitchen::Transport::Base::Connection)
|
273
|
+
.to receive(:node_execute).with('/sbin/ip -4 addr show')
|
274
|
+
.and_return(ip_response)
|
275
|
+
end
|
276
|
+
|
277
|
+
it 'sets the ip address to the connected IP that is not localhost' do
|
278
|
+
subject.create_node
|
279
|
+
|
280
|
+
expect(node[:automatic][:ipaddress]).to eq machine_ips[0]
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
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
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
def search_for_nodes(query, timeout = 120)
|
4
|
+
nodes = []
|
5
|
+
Timeout.timeout(timeout) do
|
6
|
+
nodes = search(:node, query)
|
7
|
+
until nodes.count > 0 && nodes[0].key?('ipaddress')
|
8
|
+
sleep 5
|
9
|
+
nodes = search(:node, query)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
if nodes.count.zero? || !nodes[0].key?('ipaddress')
|
14
|
+
raise "Unable to find any nodes meeting the search criteria '#{query}'!"
|
15
|
+
end
|
16
|
+
|
17
|
+
nodes
|
18
|
+
end
|
File without changes
|
@@ -0,0 +1,10 @@
|
|
1
|
+
first_node = search_for_nodes(
|
2
|
+
"run_list:*node1* AND platform:#{node['platform']}"
|
3
|
+
)
|
4
|
+
|
5
|
+
ruby_block 'Save first attributes' do
|
6
|
+
block do
|
7
|
+
parent = File.join(ENV['TEMP'] || '/tmp', 'kitchen')
|
8
|
+
IO.write(File.join(parent, 'other_node.json'), first_node[0].to_json)
|
9
|
+
end
|
10
|
+
end
|
File without changes
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'serverspec'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
if RUBY_PLATFORM =~ /mingw/
|
5
|
+
set :backend, :cmd
|
6
|
+
set :os, family: 'windows'
|
7
|
+
else
|
8
|
+
set :backend, :exec
|
9
|
+
end
|
10
|
+
|
11
|
+
describe 'other node' do
|
12
|
+
let(:node) do
|
13
|
+
JSON.parse(
|
14
|
+
IO.read(File.join(ENV['TEMP'] || '/tmp', 'kitchen/other_node.json'))
|
15
|
+
)
|
16
|
+
end
|
17
|
+
let(:ip) { node['automatic']['ipaddress'] }
|
18
|
+
let(:fqdn) { node['automatic']['fqdn'] }
|
19
|
+
let(:connection) do
|
20
|
+
if RUBY_PLATFORM =~ /mingw/
|
21
|
+
require 'winrm'
|
22
|
+
::WinRM::Connection.new(
|
23
|
+
endpoint: "http://#{ip}:5985/wsman",
|
24
|
+
user: 'vagrant',
|
25
|
+
password: 'vagrant'
|
26
|
+
).shell(:cmd)
|
27
|
+
else
|
28
|
+
Net::SSH.start(
|
29
|
+
ip,
|
30
|
+
'vagrant',
|
31
|
+
password: 'vagrant',
|
32
|
+
paranoid: false
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'has an non localhost ip' do
|
38
|
+
expect(ip).not_to eq('127.0.0.1')
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'has a valid ip' do
|
42
|
+
expect(ip).to match(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Skip this test on the 2008 box bc its not sysprepped....
|
46
|
+
unless ENV['computername'] =~ /VAGRANT\-2008R2/i
|
47
|
+
describe command('hostname') do
|
48
|
+
its(:stdout) { should_not match(/#{Regexp.quote(fqdn)}/) }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if RUBY_PLATFORM =~ /mingw/
|
53
|
+
it 'has a computername matching node fqdn' do
|
54
|
+
expect(connection.run('hostname').stdout.chomp).to eq(fqdn)
|
55
|
+
end
|
56
|
+
else
|
57
|
+
it 'has a computername matching node fqdn' do
|
58
|
+
connection.open_channel do |channel|
|
59
|
+
channel.request_pty
|
60
|
+
channel.exec('hostname') do |_ch, _success|
|
61
|
+
channel.on_data do |_ch, data|
|
62
|
+
expect(data.chomp).to eq(fqdn)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
connection.loop
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|