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