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