beaker-docker 1.4.0 → 2.0.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/.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])
|