beaker-docker 0.6.0 → 0.8.2
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 +5 -5
- data/.github/dependabot.yml +8 -0
- data/.github/workflows/release.yml +24 -0
- data/.github/workflows/test.yml +105 -0
- data/Gemfile.local +5 -0
- data/README.md +94 -13
- data/Rakefile +8 -4
- data/acceptance/config/nodes/hosts.yaml +21 -14
- data/acceptance/tests/00_default_spec.rb +10 -0
- data/beaker-docker.gemspec +3 -8
- data/lib/beaker-docker/version.rb +1 -1
- data/lib/beaker/hypervisor/docker.rb +158 -51
- data/spec/beaker/hypervisor/docker_spec.rb +509 -479
- metadata +16 -12
@@ -1,14 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'fakefs/spec_helpers'
|
3
3
|
|
4
|
-
# fake the docker-api
|
5
|
-
module Docker
|
6
|
-
class Image
|
7
|
-
end
|
8
|
-
class Container
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
4
|
module Beaker
|
13
5
|
platforms = [
|
14
6
|
"ubuntu-14.04-x86_64",
|
@@ -20,6 +12,8 @@ module Beaker
|
|
20
12
|
]
|
21
13
|
|
22
14
|
describe Docker do
|
15
|
+
require 'docker'
|
16
|
+
|
23
17
|
let(:hosts) {
|
24
18
|
the_hosts = make_hosts
|
25
19
|
the_hosts[2]['dockeropts'] = {
|
@@ -60,14 +54,15 @@ module Beaker
|
|
60
54
|
image
|
61
55
|
end
|
62
56
|
|
63
|
-
let(:
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
57
|
+
let(:container_mode) do
|
58
|
+
'rootless'
|
59
|
+
end
|
60
|
+
|
61
|
+
let(:container_config) do
|
62
|
+
conf = {
|
63
|
+
'HostConfig' => {
|
64
|
+
'NetworkMode' => 'slirp4netns'
|
65
|
+
},
|
71
66
|
'NetworkSettings' => {
|
72
67
|
'IPAddress' => '192.0.2.1',
|
73
68
|
'Ports' => {
|
@@ -80,7 +75,24 @@ module Beaker
|
|
80
75
|
},
|
81
76
|
'Gateway' => '192.0.2.254'
|
82
77
|
}
|
83
|
-
}
|
78
|
+
}
|
79
|
+
|
80
|
+
unless container_mode == 'rootless'
|
81
|
+
conf['HostConfig']['NetworkMode'] = 'bridge'
|
82
|
+
end
|
83
|
+
|
84
|
+
conf
|
85
|
+
end
|
86
|
+
|
87
|
+
let(:container) do
|
88
|
+
container = double('Docker::Container')
|
89
|
+
allow( container ).to receive(:id).and_return('abcdef')
|
90
|
+
allow( container ).to receive(:start)
|
91
|
+
allow( container ).to receive(:stats)
|
92
|
+
allow( container ).to receive(:info).and_return(
|
93
|
+
*(0..2).map { |index| { 'Names' => ["/spec-container-#{index}"] } }
|
94
|
+
)
|
95
|
+
allow( container ).to receive(:json).and_return(container_config)
|
84
96
|
allow( container ).to receive(:kill)
|
85
97
|
allow( container ).to receive(:delete)
|
86
98
|
allow( container ).to receive(:exec)
|
@@ -88,603 +100,621 @@ module Beaker
|
|
88
100
|
end
|
89
101
|
|
90
102
|
let (:docker) { ::Beaker::Docker.new( hosts, options ) }
|
103
|
+
|
91
104
|
let(:docker_options) { nil }
|
105
|
+
|
92
106
|
let (:version) { {"ApiVersion"=>"1.18", "Arch"=>"amd64", "GitCommit"=>"4749651", "GoVersion"=>"go1.4.2", "KernelVersion"=>"3.16.0-37-generic", "Os"=>"linux", "Version"=>"1.6.0"} }
|
93
107
|
|
94
|
-
|
95
|
-
#
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
allow( ::Docker ).to receive(:logger=)
|
101
|
-
allow( ::Docker ).to receive(:version).and_return(version)
|
102
|
-
allow( ::Docker::Image ).to receive(:build).and_return(image)
|
103
|
-
allow( ::Docker::Image ).to receive(:create).and_return(image)
|
104
|
-
allow( ::Docker::Container ).to receive(:create).and_return(container)
|
105
|
-
allow_any_instance_of( ::Docker::Container ).to receive(:start)
|
106
|
-
end
|
108
|
+
context 'with connection failure' do
|
109
|
+
describe '#initialize' do
|
110
|
+
before :each do
|
111
|
+
require 'excon'
|
112
|
+
expect( ::Docker ).to receive(:version).and_raise(Excon::Errors::SocketError.new( StandardError.new('oops') )).exactly(4).times
|
113
|
+
end
|
107
114
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
expect { docker }.to raise_error(RuntimeError, /Docker instance not connectable./)
|
115
|
-
expect { docker }.to raise_error(RuntimeError, /Check your DOCKER_HOST variable has been set/)
|
116
|
-
expect { docker }.to raise_error(RuntimeError, /If you are on OSX or Windows, you might not have Docker Machine setup correctly/)
|
117
|
-
expect { docker }.to raise_error(RuntimeError, /Error was: oops/)
|
115
|
+
it 'should fail when docker not present' do
|
116
|
+
expect { docker }.to raise_error(RuntimeError, /Docker instance not connectable/)
|
117
|
+
expect { docker }.to raise_error(RuntimeError, /Check your DOCKER_HOST variable has been set/)
|
118
|
+
expect { docker }.to raise_error(RuntimeError, /If you are on OSX or Windows, you might not have Docker Machine setup correctly/)
|
119
|
+
expect { docker }.to raise_error(RuntimeError, /Error was: oops/)
|
120
|
+
end
|
118
121
|
end
|
119
122
|
end
|
120
123
|
|
121
|
-
|
124
|
+
|
125
|
+
context 'with a working connection' do
|
122
126
|
before :each do
|
123
|
-
|
124
|
-
|
127
|
+
# Stub out all of the docker-api gem. we should never really call it
|
128
|
+
# from these tests
|
129
|
+
allow_any_instance_of( ::Beaker::Docker ).to receive(:require).with('docker')
|
130
|
+
allow( ::Docker ).to receive(:options).and_return(docker_options)
|
131
|
+
allow( ::Docker ).to receive(:options=)
|
132
|
+
allow( ::Docker ).to receive(:logger=)
|
133
|
+
allow( ::Docker ).to receive(:version).and_return(version)
|
134
|
+
allow( ::Docker::Image ).to receive(:build).and_return(image)
|
135
|
+
allow( ::Docker::Image ).to receive(:create).and_return(image)
|
136
|
+
allow( ::Docker::Container ).to receive(:create).and_return(container)
|
137
|
+
allow_any_instance_of( ::Docker::Container ).to receive(:start)
|
138
|
+
end
|
139
|
+
|
140
|
+
describe '#initialize' do
|
141
|
+
it 'should require the docker gem' do
|
142
|
+
expect_any_instance_of( ::Beaker::Docker ).to receive(:require).with('docker').once
|
125
143
|
|
126
|
-
|
127
|
-
|
144
|
+
docker
|
145
|
+
end
|
128
146
|
|
129
|
-
|
130
|
-
|
147
|
+
it 'should fail when the gem is absent' do
|
148
|
+
allow_any_instance_of( ::Beaker::Docker ).to receive(:require).with('docker').and_raise(LoadError)
|
149
|
+
expect { docker }.to raise_error(LoadError)
|
150
|
+
end
|
131
151
|
|
132
|
-
|
133
|
-
|
134
|
-
expect { docker }.to raise_error(LoadError)
|
135
|
-
end
|
152
|
+
it 'should set Docker options' do
|
153
|
+
expect( ::Docker ).to receive(:options=).with({:write_timeout => 300, :read_timeout => 300}).once
|
136
154
|
|
137
|
-
|
138
|
-
|
155
|
+
docker
|
156
|
+
end
|
139
157
|
|
140
|
-
|
141
|
-
|
158
|
+
context 'when Docker options are already set' do
|
159
|
+
let(:docker_options) {{:write_timeout => 600, :foo => :bar}}
|
142
160
|
|
143
|
-
|
144
|
-
|
161
|
+
it 'should not override Docker options' do
|
162
|
+
expect( ::Docker ).to receive(:options=).with({:write_timeout => 600, :read_timeout => 300, :foo => :bar}).once
|
145
163
|
|
146
|
-
|
147
|
-
|
164
|
+
docker
|
165
|
+
end
|
166
|
+
end
|
148
167
|
|
168
|
+
it 'should check the Docker gem can work with the api' do
|
149
169
|
docker
|
150
170
|
end
|
151
|
-
end
|
152
171
|
|
153
|
-
|
154
|
-
|
172
|
+
it 'should hook the Beaker logger into the Docker one' do
|
173
|
+
expect( ::Docker ).to receive(:logger=).with(logger)
|
155
174
|
|
156
|
-
|
175
|
+
docker
|
176
|
+
end
|
157
177
|
end
|
158
178
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
179
|
+
describe '#install_ssh_components' do
|
180
|
+
let(:test_container) { double('container') }
|
181
|
+
let(:host) {hosts[0]}
|
182
|
+
before :each do
|
183
|
+
allow( docker ).to receive(:dockerfile_for)
|
184
|
+
end
|
165
185
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
186
|
+
platforms.each do |platform|
|
187
|
+
it 'should call exec at least twice' do
|
188
|
+
host['platform'] = platform
|
189
|
+
expect(test_container).to receive(:exec).at_least(:twice)
|
190
|
+
docker.install_ssh_components(test_container, host)
|
191
|
+
end
|
192
|
+
end
|
173
193
|
|
174
|
-
|
175
|
-
|
176
|
-
host['platform'] = platform
|
194
|
+
it 'should accept alpine as valid platform' do
|
195
|
+
host['platform'] = 'alpine-3.8-x86_64'
|
177
196
|
expect(test_container).to receive(:exec).at_least(:twice)
|
178
197
|
docker.install_ssh_components(test_container, host)
|
179
198
|
end
|
180
|
-
end
|
181
199
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
200
|
+
it 'should raise an error with an unsupported platform' do
|
201
|
+
host['platform'] = 'boogeyman-2000-x86_64'
|
202
|
+
expect{docker.install_ssh_components(test_container, host)}.to raise_error(RuntimeError, /boogeyman/)
|
203
|
+
end
|
186
204
|
end
|
187
205
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
end
|
206
|
+
describe '#provision' do
|
207
|
+
before :each do
|
208
|
+
allow( docker ).to receive(:dockerfile_for)
|
209
|
+
end
|
193
210
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
211
|
+
context 'when the host has "tag" defined' do
|
212
|
+
before :each do
|
213
|
+
hosts.each do |host|
|
214
|
+
host['tag'] = 'my_tag'
|
215
|
+
end
|
216
|
+
end
|
199
217
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
host['tag'] = 'my_tag'
|
218
|
+
it 'will tag the image with the value of the tag' do
|
219
|
+
expect( image ).to receive(:tag).with({:repo => 'my_tag'}).exactly(3).times
|
220
|
+
docker.provision
|
204
221
|
end
|
205
222
|
end
|
206
223
|
|
207
|
-
|
208
|
-
expect( image ).to receive(:tag).with({:repo => 'my_tag'}).exactly(3).times
|
209
|
-
docker.provision
|
210
|
-
end
|
211
|
-
end
|
224
|
+
context 'when the host has "use_image_entry_point" set to true on the host' do
|
212
225
|
|
213
|
-
|
226
|
+
before :each do
|
227
|
+
hosts.each do |host|
|
228
|
+
host['use_image_entry_point'] = true
|
229
|
+
end
|
230
|
+
end
|
214
231
|
|
215
|
-
|
216
|
-
|
217
|
-
|
232
|
+
it 'should not call #dockerfile_for but run methods necessary for ssh installation' do
|
233
|
+
expect( docker ).not_to receive(:dockerfile_for)
|
234
|
+
expect( docker ).to receive(:install_ssh_components).exactly(3).times #once per host
|
235
|
+
expect( docker ).to receive(:fix_ssh).exactly(3).times #once per host
|
236
|
+
docker.provision
|
218
237
|
end
|
219
238
|
end
|
220
239
|
|
221
|
-
|
222
|
-
expect( docker ).not_to receive(:dockerfile_for)
|
223
|
-
expect( docker ).to receive(:install_ssh_components).exactly(3).times #once per host
|
224
|
-
expect( docker ).to receive(:fix_ssh).exactly(3).times #once per host
|
225
|
-
docker.provision
|
226
|
-
end
|
227
|
-
end
|
240
|
+
context 'when the host has a "dockerfile" for the host' do
|
228
241
|
|
229
|
-
|
242
|
+
before :each do
|
243
|
+
allow( docker ).to receive(:buildargs_for).and_return('buildargs')
|
244
|
+
hosts.each do |host|
|
245
|
+
host['dockerfile'] = 'mydockerfile'
|
246
|
+
end
|
247
|
+
end
|
230
248
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
249
|
+
it 'should not call #dockerfile_for but run methods necessary for ssh installation' do
|
250
|
+
allow( File ).to receive(:exist?).with('mydockerfile').and_return(true)
|
251
|
+
allow( ::Docker::Image ).to receive(:build_from_dir).with("/", hash_including(:rm => true, :buildargs => 'buildargs')).and_return(image)
|
252
|
+
expect( docker ).not_to receive(:dockerfile_for)
|
253
|
+
expect( docker ).to receive(:install_ssh_components).exactly(3).times #once per host
|
254
|
+
expect( docker ).to receive(:fix_ssh).exactly(3).times #once per host
|
255
|
+
docker.provision
|
235
256
|
end
|
236
257
|
end
|
237
258
|
|
238
|
-
it 'should
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
259
|
+
it 'should call image create for hosts when use_image_as_is is defined' do
|
260
|
+
hosts.each do |host|
|
261
|
+
host['use_image_as_is'] = true
|
262
|
+
expect( docker ).not_to receive(:install_ssh_components)
|
263
|
+
expect( docker ).not_to receive(:fix_ssh)
|
264
|
+
expect( ::Docker::Image ).to receive(:create).with('fromImage' => host['image']) #once per host
|
265
|
+
expect( ::Docker::Image ).not_to receive(:build)
|
266
|
+
expect( ::Docker::Image ).not_to receive(:build_from_dir)
|
267
|
+
end
|
247
268
|
|
248
|
-
|
249
|
-
hosts.each do |host|
|
250
|
-
host['use_image_as_is'] = true
|
251
|
-
expect( docker ).not_to receive(:install_ssh_components)
|
252
|
-
expect( docker ).not_to receive(:fix_ssh)
|
253
|
-
expect( ::Docker::Image ).to receive(:create).with('fromImage' => host['image']) #once per host
|
254
|
-
expect( ::Docker::Image ).not_to receive(:build)
|
255
|
-
expect( ::Docker::Image ).not_to receive(:build_from_dir)
|
269
|
+
docker.provision
|
256
270
|
end
|
257
271
|
|
258
|
-
|
259
|
-
|
272
|
+
it 'should call dockerfile_for with all the hosts' do
|
273
|
+
hosts.each do |host|
|
274
|
+
expect( docker ).not_to receive(:install_ssh_components)
|
275
|
+
expect( docker ).not_to receive(:fix_ssh)
|
276
|
+
expect( docker ).to receive(:dockerfile_for).with(host).and_return('')
|
277
|
+
end
|
260
278
|
|
261
|
-
|
262
|
-
hosts.each do |host|
|
263
|
-
expect( docker ).not_to receive(:install_ssh_components)
|
264
|
-
expect( docker ).not_to receive(:fix_ssh)
|
265
|
-
expect( docker ).to receive(:dockerfile_for).with(host).and_return('')
|
279
|
+
docker.provision
|
266
280
|
end
|
267
281
|
|
268
|
-
|
269
|
-
|
282
|
+
it 'should pass the Dockerfile on to Docker::Image.create' do
|
283
|
+
allow( docker ).to receive(:dockerfile_for).and_return('special testing value')
|
284
|
+
expect( ::Docker::Image ).to receive(:build).with('special testing value', { :rm => true, :buildargs => '{}' })
|
270
285
|
|
271
|
-
|
272
|
-
|
273
|
-
expect( ::Docker::Image ).to receive(:build).with('special testing value', { :rm => true, :buildargs => '{}' })
|
274
|
-
|
275
|
-
docker.provision
|
276
|
-
end
|
286
|
+
docker.provision
|
287
|
+
end
|
277
288
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
289
|
+
it 'should pass the buildargs from ENV DOCKER_BUILDARGS on to Docker::Image.create' do
|
290
|
+
allow( docker ).to receive(:dockerfile_for).and_return('special testing value')
|
291
|
+
ENV['DOCKER_BUILDARGS'] = 'HTTP_PROXY=http://1.1.1.1:3128'
|
292
|
+
expect( ::Docker::Image ).to receive(:build).with('special testing value', { :rm => true, :buildargs => "{\"HTTP_PROXY\":\"http://1.1.1.1:3128\"}" })
|
282
293
|
|
283
|
-
|
284
|
-
|
294
|
+
docker.provision
|
295
|
+
end
|
285
296
|
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
297
|
+
it 'should create a container based on the Image (identified by image.id)' do
|
298
|
+
hosts.each_with_index do |host,index|
|
299
|
+
expect( ::Docker::Container ).to receive(:create).with({
|
300
|
+
'Image' => image.id,
|
301
|
+
'Hostname' => host.name,
|
302
|
+
'HostConfig' => {
|
303
|
+
'PortBindings' => {
|
304
|
+
'22/tcp' => [{ 'HostPort' => /\b\d{4}\b/, 'HostIp' => '0.0.0.0'}]
|
305
|
+
},
|
306
|
+
'Privileged' => true,
|
307
|
+
'PublishAllPorts' => true,
|
308
|
+
'RestartPolicy' => {
|
309
|
+
'Name' => 'always'
|
310
|
+
}
|
294
311
|
},
|
295
|
-
'
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
}
|
301
|
-
|
302
|
-
'one' => 1,
|
303
|
-
'two' => 2,
|
304
|
-
},
|
305
|
-
}).with(hash_excluding('name'))
|
306
|
-
end
|
312
|
+
'Labels' => {
|
313
|
+
'one' => (index == 2 ? 3 : 1),
|
314
|
+
'two' => (index == 2 ? 4 : 2),
|
315
|
+
},
|
316
|
+
'name' => /\Abeaker-/
|
317
|
+
})
|
318
|
+
end
|
307
319
|
|
308
|
-
|
309
|
-
|
320
|
+
docker.provision
|
321
|
+
end
|
310
322
|
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
323
|
+
it 'should pass the multiple buildargs from ENV DOCKER_BUILDARGS on to Docker::Image.create' do
|
324
|
+
allow( docker ).to receive(:dockerfile_for).and_return('special testing value')
|
325
|
+
ENV['DOCKER_BUILDARGS'] = 'HTTP_PROXY=http://1.1.1.1:3128 HTTPS_PROXY=https://1.1.1.1:3129'
|
326
|
+
expect( ::Docker::Image ).to receive(:build).with('special testing value', { :rm => true, :buildargs => "{\"HTTP_PROXY\":\"http://1.1.1.1:3128\",\"HTTPS_PROXY\":\"https://1.1.1.1:3129\"}" })
|
315
327
|
|
316
|
-
|
317
|
-
|
328
|
+
docker.provision
|
329
|
+
end
|
318
330
|
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
331
|
+
it 'should create a container based on the Image (identified by image.id)' do
|
332
|
+
hosts.each_with_index do |host,index|
|
333
|
+
expect( ::Docker::Container ).to receive(:create).with({
|
334
|
+
'Image' => image.id,
|
335
|
+
'Hostname' => host.name,
|
336
|
+
'HostConfig' => {
|
337
|
+
'PortBindings' => {
|
338
|
+
'22/tcp' => [{ 'HostPort' => /\b\d{4}\b/, 'HostIp' => '0.0.0.0'}]
|
339
|
+
},
|
340
|
+
'PublishAllPorts' => true,
|
341
|
+
'Privileged' => true,
|
342
|
+
'RestartPolicy' => {
|
343
|
+
'Name' => 'always'
|
344
|
+
}
|
327
345
|
},
|
328
|
-
'
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
}
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
},
|
338
|
-
}).with(hash_excluding('name'))
|
346
|
+
'Labels' => {
|
347
|
+
'one' => (index == 2 ? 3 : 1),
|
348
|
+
'two' => (index == 2 ? 4 : 2),
|
349
|
+
},
|
350
|
+
'name' => /\Abeaker-/
|
351
|
+
})
|
352
|
+
end
|
353
|
+
|
354
|
+
docker.provision
|
339
355
|
end
|
340
356
|
|
341
|
-
|
342
|
-
|
357
|
+
it 'should create a named container based on the Image (identified by image.id)' do
|
358
|
+
hosts.each_with_index do |host, index|
|
359
|
+
container_name = "spec-container-#{index}"
|
360
|
+
host['docker_container_name'] = container_name
|
343
361
|
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
362
|
+
allow(::Docker::Container).to receive(:all).and_return([])
|
363
|
+
expect( ::Docker::Container ).to receive(:create).with({
|
364
|
+
'Image' => image.id,
|
365
|
+
'Hostname' => host.name,
|
366
|
+
'name' => container_name,
|
367
|
+
'HostConfig' => {
|
368
|
+
'PortBindings' => {
|
369
|
+
'22/tcp' => [{ 'HostPort' => /\b\d{4}\b/, 'HostIp' => '0.0.0.0'}]
|
370
|
+
},
|
371
|
+
'PublishAllPorts' => true,
|
372
|
+
'Privileged' => true,
|
373
|
+
'RestartPolicy' => {
|
374
|
+
'Name' => 'always'
|
375
|
+
}
|
357
376
|
},
|
358
|
-
'
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
'Labels' => {
|
365
|
-
'one' => (index == 2 ? 3 : 1),
|
366
|
-
'two' => (index == 2 ? 4 : 2),
|
367
|
-
},
|
368
|
-
})
|
369
|
-
end
|
377
|
+
'Labels' => {
|
378
|
+
'one' => (index == 2 ? 3 : 1),
|
379
|
+
'two' => (index == 2 ? 4 : 2),
|
380
|
+
},
|
381
|
+
})
|
382
|
+
end
|
370
383
|
|
371
|
-
|
372
|
-
|
384
|
+
docker.provision
|
385
|
+
end
|
373
386
|
|
374
387
|
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
},
|
387
|
-
'mount3' => {
|
388
|
-
'host_path' => '/different_folder',
|
389
|
-
'container_path' => '/different_mount',
|
390
|
-
'opts' => 'rw',
|
391
|
-
},
|
392
|
-
'mount4' => {
|
393
|
-
'host_path' => './',
|
394
|
-
'container_path' => '/relative_mount',
|
395
|
-
},
|
396
|
-
'mount5' => {
|
397
|
-
'host_path' => 'local_folder',
|
398
|
-
'container_path' => '/another_relative_mount',
|
399
|
-
}
|
400
|
-
}
|
401
|
-
|
402
|
-
expect( ::Docker::Container ).to receive(:create).with({
|
403
|
-
'Image' => image.id,
|
404
|
-
'Hostname' => host.name,
|
405
|
-
'HostConfig' => {
|
406
|
-
'Binds' => [
|
407
|
-
'/source_folder:/mount_point',
|
408
|
-
'/another_folder:/another_mount:ro',
|
409
|
-
'/different_folder:/different_mount:rw',
|
410
|
-
"#{File.expand_path('./')}:/relative_mount",
|
411
|
-
"#{File.expand_path('local_folder')}:/another_relative_mount",
|
412
|
-
],
|
413
|
-
'PortBindings' => {
|
414
|
-
'22/tcp' => [{ 'HostPort' => /\b\d{4}\b/, 'HostIp' => '0.0.0.0'}]
|
388
|
+
it 'should create a container with volumes bound' do
|
389
|
+
hosts.each_with_index do |host, index|
|
390
|
+
host['mount_folders'] = {
|
391
|
+
'mount1' => {
|
392
|
+
'host_path' => '/source_folder',
|
393
|
+
'container_path' => '/mount_point',
|
394
|
+
},
|
395
|
+
'mount2' => {
|
396
|
+
'host_path' => '/another_folder',
|
397
|
+
'container_path' => '/another_mount',
|
398
|
+
'opts' => 'ro',
|
415
399
|
},
|
416
|
-
'
|
417
|
-
|
418
|
-
|
419
|
-
'
|
400
|
+
'mount3' => {
|
401
|
+
'host_path' => '/different_folder',
|
402
|
+
'container_path' => '/different_mount',
|
403
|
+
'opts' => 'rw',
|
404
|
+
},
|
405
|
+
'mount4' => {
|
406
|
+
'host_path' => './',
|
407
|
+
'container_path' => '/relative_mount',
|
408
|
+
},
|
409
|
+
'mount5' => {
|
410
|
+
'host_path' => 'local_folder',
|
411
|
+
'container_path' => '/another_relative_mount',
|
420
412
|
}
|
421
|
-
}
|
422
|
-
'Labels' => {
|
423
|
-
'one' => (index == 2 ? 3 : 1),
|
424
|
-
'two' => (index == 2 ? 4 : 2),
|
425
|
-
},
|
426
|
-
})
|
427
|
-
end
|
413
|
+
}
|
428
414
|
|
429
|
-
|
430
|
-
|
415
|
+
expect( ::Docker::Container ).to receive(:create).with({
|
416
|
+
'Image' => image.id,
|
417
|
+
'Hostname' => host.name,
|
418
|
+
'HostConfig' => {
|
419
|
+
'Binds' => [
|
420
|
+
'/source_folder:/mount_point:z',
|
421
|
+
'/another_folder:/another_mount:ro',
|
422
|
+
'/different_folder:/different_mount:rw',
|
423
|
+
"#{File.expand_path('./')}:/relative_mount:z",
|
424
|
+
"#{File.expand_path('local_folder')}:/another_relative_mount:z",
|
425
|
+
],
|
426
|
+
'PortBindings' => {
|
427
|
+
'22/tcp' => [{ 'HostPort' => /\b\d{4}\b/, 'HostIp' => '0.0.0.0'}]
|
428
|
+
},
|
429
|
+
'PublishAllPorts' => true,
|
430
|
+
'Privileged' => true,
|
431
|
+
'RestartPolicy' => {
|
432
|
+
'Name' => 'always'
|
433
|
+
}
|
434
|
+
},
|
435
|
+
'Labels' => {
|
436
|
+
'one' => (index == 2 ? 3 : 1),
|
437
|
+
'two' => (index == 2 ? 4 : 2),
|
438
|
+
},
|
439
|
+
'name' => /\Abeaker-/
|
440
|
+
})
|
441
|
+
end
|
431
442
|
|
432
|
-
|
433
|
-
|
434
|
-
host['docker_cap_add'] = ['NET_ADMIN', 'SYS_ADMIN']
|
443
|
+
docker.provision
|
444
|
+
end
|
435
445
|
|
436
|
-
|
437
|
-
|
438
|
-
'
|
439
|
-
|
440
|
-
|
441
|
-
|
446
|
+
it 'should create a container with capabilities added' do
|
447
|
+
hosts.each_with_index do |host, index|
|
448
|
+
host['docker_cap_add'] = ['NET_ADMIN', 'SYS_ADMIN']
|
449
|
+
|
450
|
+
expect( ::Docker::Container ).to receive(:create).with({
|
451
|
+
'Image' => image.id,
|
452
|
+
'Hostname' => host.name,
|
453
|
+
'HostConfig' => {
|
454
|
+
'PortBindings' => {
|
455
|
+
'22/tcp' => [{ 'HostPort' => /\b\d{4}\b/, 'HostIp' => '0.0.0.0'}]
|
456
|
+
},
|
457
|
+
'PublishAllPorts' => true,
|
458
|
+
'RestartPolicy' => {
|
459
|
+
'Name' => 'always'
|
460
|
+
},
|
461
|
+
'CapAdd' => ['NET_ADMIN', 'SYS_ADMIN']
|
442
462
|
},
|
443
|
-
'
|
444
|
-
|
445
|
-
|
446
|
-
'Name' => 'always'
|
463
|
+
'Labels' => {
|
464
|
+
'one' => (index == 2 ? 3 : 1),
|
465
|
+
'two' => (index == 2 ? 4 : 2),
|
447
466
|
},
|
448
|
-
'
|
449
|
-
}
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
},
|
454
|
-
})
|
467
|
+
'name' => /\Abeaker-/
|
468
|
+
})
|
469
|
+
end
|
470
|
+
|
471
|
+
docker.provision
|
455
472
|
end
|
456
473
|
|
457
|
-
|
458
|
-
|
474
|
+
it 'should start the container' do
|
475
|
+
expect( container ).to receive(:start)
|
459
476
|
|
460
|
-
|
461
|
-
|
477
|
+
docker.provision
|
478
|
+
end
|
462
479
|
|
463
|
-
|
464
|
-
|
480
|
+
context "connecting to ssh" do
|
481
|
+
context "rootless" do
|
482
|
+
before { @docker_host = ENV['DOCKER_HOST'] }
|
483
|
+
after { ENV['DOCKER_HOST'] = @docker_host }
|
484
|
+
|
485
|
+
it 'should expose port 22 to beaker' do
|
486
|
+
ENV['DOCKER_HOST'] = nil
|
487
|
+
docker.provision
|
488
|
+
|
489
|
+
expect( hosts[0]['ip'] ).to be === '127.0.1.1'
|
490
|
+
expect( hosts[0]['port'] ).to be === 8022
|
491
|
+
end
|
492
|
+
|
493
|
+
it 'should expose port 22 to beaker when using DOCKER_HOST' do
|
494
|
+
ENV['DOCKER_HOST'] = "tcp://192.0.2.2:2375"
|
495
|
+
docker.provision
|
496
|
+
|
497
|
+
expect( hosts[0]['ip'] ).to be === '192.0.2.2'
|
498
|
+
expect( hosts[0]['port'] ).to be === 8022
|
499
|
+
end
|
500
|
+
|
501
|
+
it 'should have ssh agent forwarding enabled' do
|
502
|
+
ENV['DOCKER_HOST'] = nil
|
503
|
+
docker.provision
|
504
|
+
|
505
|
+
expect( hosts[0]['ip'] ).to be === '127.0.1.1'
|
506
|
+
expect( hosts[0]['port'] ).to be === 8022
|
507
|
+
expect( hosts[0]['ssh'][:password] ).to be === 'root'
|
508
|
+
expect( hosts[0]['ssh'][:port] ).to be === 8022
|
509
|
+
expect( hosts[0]['ssh'][:forward_agent] ).to be === true
|
510
|
+
end
|
511
|
+
|
512
|
+
it 'should connect to gateway ip' do
|
513
|
+
FakeFS do
|
514
|
+
File.open('/.dockerenv', 'w') { }
|
515
|
+
docker.provision
|
516
|
+
|
517
|
+
expect( hosts[0]['ip'] ).to be === '192.0.2.254'
|
518
|
+
expect( hosts[0]['port'] ).to be === 8022
|
519
|
+
end
|
520
|
+
end
|
521
|
+
end
|
465
522
|
|
466
|
-
|
467
|
-
|
468
|
-
|
523
|
+
context 'rootful' do
|
524
|
+
before { @docker_host = ENV['DOCKER_HOST'] }
|
525
|
+
after { ENV['DOCKER_HOST'] = @docker_host }
|
469
526
|
|
470
|
-
|
471
|
-
|
472
|
-
|
527
|
+
let(:container_mode) do
|
528
|
+
'rootful'
|
529
|
+
end
|
473
530
|
|
474
|
-
|
475
|
-
|
476
|
-
|
531
|
+
it 'should expose port 22 to beaker' do
|
532
|
+
ENV['DOCKER_HOST'] = nil
|
533
|
+
docker.provision
|
477
534
|
|
478
|
-
|
479
|
-
|
480
|
-
|
535
|
+
expect( hosts[0]['ip'] ).to be === '192.0.2.1'
|
536
|
+
expect( hosts[0]['port'] ).to be === '22'
|
537
|
+
end
|
538
|
+
end
|
481
539
|
|
482
|
-
expect( hosts[0]['ip'] ).to be === '192.0.2.2'
|
483
|
-
expect( hosts[0]['port'] ).to be === 8022
|
484
540
|
end
|
485
541
|
|
486
|
-
it
|
542
|
+
it "should generate a new /etc/hosts file referencing each host" do
|
487
543
|
ENV['DOCKER_HOST'] = nil
|
488
544
|
docker.provision
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
expect( hosts[0]['ssh'][:password] ).to be === 'root'
|
493
|
-
expect( hosts[0]['ssh'][:port] ).to be === 8022
|
494
|
-
expect( hosts[0]['ssh'][:forward_agent] ).to be === true
|
495
|
-
end
|
496
|
-
|
497
|
-
it 'should connect to gateway ip' do
|
498
|
-
FakeFS do
|
499
|
-
File.open('/.dockerenv', 'w') { }
|
500
|
-
docker.provision
|
501
|
-
|
502
|
-
expect( hosts[0]['ip'] ).to be === '192.0.2.254'
|
503
|
-
expect( hosts[0]['port'] ).to be === 8022
|
545
|
+
hosts.each do |host|
|
546
|
+
expect( docker ).to receive( :get_domain_name ).with( host ).and_return( 'labs.lan' )
|
547
|
+
expect( docker ).to receive( :set_etc_hosts ).with( host, "127.0.0.1\tlocalhost localhost.localdomain\n192.0.2.1\tvm1.labs.lan vm1\n192.0.2.1\tvm2.labs.lan vm2\n192.0.2.1\tvm3.labs.lan vm3\n" ).once
|
504
548
|
end
|
549
|
+
docker.hack_etc_hosts( hosts, options )
|
505
550
|
end
|
506
551
|
|
507
|
-
|
552
|
+
it 'should record the image and container for later' do
|
553
|
+
docker.provision
|
508
554
|
|
509
|
-
|
510
|
-
|
511
|
-
docker.provision
|
512
|
-
hosts.each do |host|
|
513
|
-
expect( docker ).to receive( :get_domain_name ).with( host ).and_return( 'labs.lan' )
|
514
|
-
expect( docker ).to receive( :set_etc_hosts ).with( host, "127.0.0.1\tlocalhost localhost.localdomain\n192.0.2.1\tvm1.labs.lan vm1\n192.0.2.1\tvm2.labs.lan vm2\n192.0.2.1\tvm3.labs.lan vm3\n" ).once
|
555
|
+
expect( hosts[0]['docker_image_id'] ).to be === image.id
|
556
|
+
expect( hosts[0]['docker_container_id'] ).to be === container.id
|
515
557
|
end
|
516
|
-
docker.hack_etc_hosts( hosts, options )
|
517
|
-
end
|
518
|
-
|
519
|
-
it 'should record the image and container for later' do
|
520
|
-
docker.provision
|
521
558
|
|
522
|
-
|
523
|
-
|
524
|
-
|
559
|
+
context 'provision=false' do
|
560
|
+
let(:options) {{
|
561
|
+
:logger => logger,
|
562
|
+
:forward_ssh_agent => true,
|
563
|
+
:provision => false
|
564
|
+
}}
|
525
565
|
|
526
|
-
context 'provision=false' do
|
527
|
-
let(:options) {{
|
528
|
-
:logger => logger,
|
529
|
-
:forward_ssh_agent => true,
|
530
|
-
:provision => false
|
531
|
-
}}
|
532
566
|
|
567
|
+
it 'should fix ssh' do
|
568
|
+
hosts.each_with_index do |host, index|
|
569
|
+
container_name = "spec-container-#{index}"
|
570
|
+
host['docker_container_name'] = container_name
|
533
571
|
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
expect( ::Docker::Container ).to receive(:all).and_return([container])
|
540
|
-
expect(docker).to receive(:fix_ssh).exactly(1).times
|
572
|
+
expect( ::Docker::Container ).to receive(:all).and_return([container])
|
573
|
+
expect(docker).to receive(:fix_ssh).exactly(1).times
|
574
|
+
end
|
575
|
+
docker.provision
|
541
576
|
end
|
542
|
-
docker.provision
|
543
|
-
end
|
544
577
|
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
578
|
+
it 'should not create a container if a named one already exists' do
|
579
|
+
hosts.each_with_index do |host, index|
|
580
|
+
container_name = "spec-container-#{index}"
|
581
|
+
host['docker_container_name'] = container_name
|
549
582
|
|
550
|
-
|
551
|
-
|
552
|
-
|
583
|
+
expect( ::Docker::Container ).to receive(:all).and_return([container])
|
584
|
+
expect( ::Docker::Container ).not_to receive(:create)
|
585
|
+
end
|
553
586
|
|
554
|
-
|
587
|
+
docker.provision
|
588
|
+
end
|
555
589
|
end
|
556
590
|
end
|
557
|
-
end
|
558
591
|
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
end
|
568
|
-
|
569
|
-
it 'should stop the containers' do
|
570
|
-
allow( docker ).to receive( :sleep ).and_return(true)
|
571
|
-
expect( container ).to receive(:kill)
|
572
|
-
docker.cleanup
|
573
|
-
end
|
592
|
+
describe '#cleanup' do
|
593
|
+
before :each do
|
594
|
+
# get into a state where there's something to clean
|
595
|
+
allow( ::Docker::Container ).to receive(:all).and_return([container])
|
596
|
+
allow( ::Docker::Image ).to receive(:remove).with(image.id)
|
597
|
+
allow( docker ).to receive(:dockerfile_for)
|
598
|
+
docker.provision
|
599
|
+
end
|
574
600
|
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
601
|
+
it 'should stop the containers' do
|
602
|
+
allow( docker ).to receive( :sleep ).and_return(true)
|
603
|
+
expect( container ).to receive(:kill)
|
604
|
+
docker.cleanup
|
605
|
+
end
|
580
606
|
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
607
|
+
it 'should delete the containers' do
|
608
|
+
allow( docker ).to receive( :sleep ).and_return(true)
|
609
|
+
expect( container ).to receive(:delete)
|
610
|
+
docker.cleanup
|
611
|
+
end
|
586
612
|
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
613
|
+
it 'should delete the images' do
|
614
|
+
allow( docker ).to receive( :sleep ).and_return(true)
|
615
|
+
expect( ::Docker::Image ).to receive(:remove).with(image.id)
|
616
|
+
docker.cleanup
|
591
617
|
end
|
592
|
-
expect( ::Docker::Image ).to_not receive(:remove)
|
593
|
-
docker.cleanup
|
594
|
-
end
|
595
618
|
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
619
|
+
it 'should not delete the image if docker_preserve_image is set to true' do
|
620
|
+
allow( docker ).to receive( :sleep ).and_return(true)
|
621
|
+
hosts.each do |host|
|
622
|
+
host['docker_preserve_image']=true
|
623
|
+
end
|
624
|
+
expect( ::Docker::Image ).to_not receive(:remove)
|
625
|
+
docker.cleanup
|
600
626
|
end
|
601
|
-
expect( ::Docker::Image ).to receive(:remove).with(image.id)
|
602
|
-
docker.cleanup
|
603
|
-
end
|
604
627
|
|
605
|
-
|
628
|
+
it 'should delete the image if docker_preserve_image is set to false' do
|
629
|
+
allow( docker ).to receive( :sleep ).and_return(true)
|
630
|
+
hosts.each do |host|
|
631
|
+
host['docker_preserve_image']=false
|
632
|
+
end
|
633
|
+
expect( ::Docker::Image ).to receive(:remove).with(image.id)
|
634
|
+
docker.cleanup
|
635
|
+
end
|
606
636
|
|
607
|
-
describe '#dockerfile_for' do
|
608
|
-
FakeFS.deactivate!
|
609
|
-
before :each do
|
610
|
-
allow( ::Docker ).to receive(:validate_version!)
|
611
|
-
end
|
612
|
-
it 'should raise on an unsupported platform' do
|
613
|
-
expect { docker.send(:dockerfile_for, {'platform' => 'a_sidewalk', 'image' => 'foobar' }) }.to raise_error(/platform a_sidewalk not yet supported/)
|
614
637
|
end
|
615
638
|
|
616
|
-
|
639
|
+
describe '#dockerfile_for' do
|
617
640
|
FakeFS.deactivate!
|
618
|
-
|
619
|
-
|
620
|
-
'platform' => platform,
|
621
|
-
'image' => 'foobar',
|
622
|
-
})
|
623
|
-
expect( dockerfile ).to be =~ /ENV container docker/
|
641
|
+
it 'should raise on an unsupported platform' do
|
642
|
+
expect { docker.send(:dockerfile_for, {'platform' => 'a_sidewalk', 'image' => 'foobar' }) }.to raise_error(/platform a_sidewalk not yet supported/)
|
624
643
|
end
|
625
|
-
end
|
626
644
|
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
]
|
638
|
-
})
|
645
|
+
it 'should set "ENV container docker"' do
|
646
|
+
FakeFS.deactivate!
|
647
|
+
platforms.each do |platform|
|
648
|
+
dockerfile = docker.send(:dockerfile_for, {
|
649
|
+
'platform' => platform,
|
650
|
+
'image' => 'foobar',
|
651
|
+
})
|
652
|
+
expect( dockerfile ).to be =~ /ENV container docker/
|
653
|
+
end
|
654
|
+
end
|
639
655
|
|
640
|
-
|
656
|
+
it 'should add docker_image_commands as RUN statements' do
|
657
|
+
FakeFS.deactivate!
|
658
|
+
platforms.each do |platform|
|
659
|
+
dockerfile = docker.send(:dockerfile_for, {
|
660
|
+
'platform' => platform,
|
661
|
+
'image' => 'foobar',
|
662
|
+
'docker_image_commands' => [
|
663
|
+
'special one',
|
664
|
+
'special two',
|
665
|
+
'special three',
|
666
|
+
]
|
667
|
+
})
|
668
|
+
|
669
|
+
expect( dockerfile ).to be =~ /RUN special one\nRUN special two\nRUN special three/
|
670
|
+
end
|
641
671
|
end
|
642
|
-
end
|
643
672
|
|
644
|
-
|
645
|
-
|
646
|
-
|
673
|
+
it 'should add docker_image_entrypoint' do
|
674
|
+
FakeFS.deactivate!
|
675
|
+
platforms.each do |platform|
|
676
|
+
dockerfile = docker.send(:dockerfile_for, {
|
677
|
+
'platform' => platform,
|
678
|
+
'image' => 'foobar',
|
679
|
+
'docker_image_entrypoint' => '/bin/bash'
|
680
|
+
})
|
681
|
+
|
682
|
+
expect( dockerfile ).to be =~ %r{ENTRYPOINT /bin/bash}
|
683
|
+
end
|
684
|
+
end
|
685
|
+
|
686
|
+
it 'should use zypper on sles' do
|
687
|
+
FakeFS.deactivate!
|
647
688
|
dockerfile = docker.send(:dockerfile_for, {
|
648
|
-
'platform' =>
|
689
|
+
'platform' => 'sles-12-x86_64',
|
649
690
|
'image' => 'foobar',
|
650
|
-
'docker_image_entrypoint' => '/bin/bash'
|
651
691
|
})
|
652
692
|
|
653
|
-
expect( dockerfile ).to be =~
|
693
|
+
expect( dockerfile ).to be =~ /RUN zypper -n in openssh/
|
654
694
|
end
|
655
|
-
end
|
656
695
|
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
696
|
+
(22..29).to_a.each do | fedora_release |
|
697
|
+
it "should use dnf on fedora #{fedora_release}" do
|
698
|
+
FakeFS.deactivate!
|
699
|
+
dockerfile = docker.send(:dockerfile_for, {
|
700
|
+
'platform' => "fedora-#{fedora_release}-x86_64",
|
701
|
+
'image' => 'foobar',
|
702
|
+
})
|
663
703
|
|
664
|
-
|
665
|
-
|
704
|
+
expect( dockerfile ).to be =~ /RUN dnf install -y sudo/
|
705
|
+
end
|
706
|
+
end
|
666
707
|
|
667
|
-
|
668
|
-
it "should use dnf on fedora #{fedora_release}" do
|
708
|
+
it 'should use pacman on archlinux' do
|
669
709
|
FakeFS.deactivate!
|
670
710
|
dockerfile = docker.send(:dockerfile_for, {
|
671
|
-
'platform' =>
|
711
|
+
'platform' => 'archlinux-current-x86_64',
|
672
712
|
'image' => 'foobar',
|
673
713
|
})
|
674
714
|
|
675
|
-
expect( dockerfile ).to be =~ /RUN
|
715
|
+
expect( dockerfile ).to be =~ /RUN pacman -S --noconfirm openssh/
|
676
716
|
end
|
677
717
|
end
|
678
|
-
|
679
|
-
it 'should use pacman on archlinux' do
|
680
|
-
FakeFS.deactivate!
|
681
|
-
dockerfile = docker.send(:dockerfile_for, {
|
682
|
-
'platform' => 'archlinux-current-x86_64',
|
683
|
-
'image' => 'foobar',
|
684
|
-
})
|
685
|
-
|
686
|
-
expect( dockerfile ).to be =~ /RUN pacman -S --noconfirm openssh/
|
687
|
-
end
|
688
718
|
end
|
689
719
|
end
|
690
720
|
end
|