beaker-docker 0.7.1 → 0.8.4

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.
@@ -1,3 +1,3 @@
1
1
  module BeakerDocker
2
- VERSION = '0.7.1'
2
+ VERSION = '0.8.4'
3
3
  end
@@ -19,19 +19,25 @@ module Beaker
19
19
  default_docker_options = { :write_timeout => 300, :read_timeout => 300 }.merge(::Docker.options || {})
20
20
  # Merge docker options from the entry in hosts file
21
21
  ::Docker.options = default_docker_options.merge(@options[:docker_options] || {})
22
- # assert that the docker-api gem can talk to your docker
23
- # enpoint. Will raise if there is a version mismatch
22
+
23
+ # Ensure that we can correctly communicate with the docker API
24
24
  begin
25
- ::Docker.validate_version!
25
+ @docker_version = ::Docker.version
26
26
  rescue Excon::Errors::SocketError => e
27
- raise "Docker instance not connectable.\nError was: #{e}\nCheck your DOCKER_HOST variable has been set\nIf you are on OSX or Windows, you might not have Docker Machine setup correctly: https://docs.docker.com/machine/\n"
27
+ raise <<~ERRMSG
28
+ Docker instance not connectable
29
+ Error was: #{e}
30
+ * Check your DOCKER_HOST variable has been set
31
+ * If you are on OSX or Windows, you might not have Docker Machine setup correctly: https://docs.docker.com/machine/
32
+ * If you are using rootless podman, you might need to set up your local socket and service
33
+ ERRMSG
28
34
  end
29
35
 
30
36
  # Pass on all the logging from docker-api to the beaker logger instance
31
37
  ::Docker.logger = @logger
32
38
 
33
39
  # Find out what kind of remote instance we are talking against
34
- if ::Docker.version['Version'] =~ /swarm/
40
+ if @docker_version['Version'] =~ /swarm/
35
41
  @docker_type = 'swarm'
36
42
  unless ENV['DOCKER_REGISTRY']
37
43
  raise "Using Swarm with beaker requires a private registry. Please setup the private registry and set the 'DOCKER_REGISTRY' env var"
@@ -41,10 +47,21 @@ module Beaker
41
47
  else
42
48
  @docker_type = 'docker'
43
49
  end
44
-
45
50
  end
46
51
 
47
52
  def install_and_run_ssh(host)
53
+ def host.enable_root_login(host,opts)
54
+ logger.debug("Root login already enabled for #{host}")
55
+ end
56
+
57
+ # If the container is running ssh as its init process then this method
58
+ # will cause issues.
59
+ if host[:docker_cmd] =~ /sshd/
60
+ def host.ssh_service_restart
61
+ self[:docker_container].exec(%w(kill -1 1))
62
+ end
63
+ end
64
+
48
65
  host['dockerfile'] || host['use_image_entry_point']
49
66
  end
50
67
 
@@ -62,7 +79,6 @@ module Beaker
62
79
  '22/tcp' => [{ 'HostPort' => rand.to_s[2..5], 'HostIp' => '0.0.0.0'}]
63
80
  },
64
81
  'PublishAllPorts' => true,
65
- 'Privileged' => true,
66
82
  'RestartPolicy' => {
67
83
  'Name' => 'always'
68
84
  }
@@ -109,6 +125,49 @@ module Beaker
109
125
  { rm: true, buildargs: buildargs_for(host) })
110
126
  end
111
127
 
128
+ # Find out where the ssh port is from the container
129
+ # When running on swarm DOCKER_HOST points to the swarm manager so we have to get the
130
+ # IP of the swarm slave via the container data
131
+ # When we are talking to a normal docker instance DOCKER_HOST can point to a remote docker instance.
132
+ def get_ssh_connection_info(container)
133
+ ssh_connection_info = {
134
+ ip: nil,
135
+ port: nil
136
+ }
137
+
138
+ container_json = container.json
139
+ network_settings = container_json['NetworkSettings']
140
+ host_config = container_json['HostConfig']
141
+
142
+ ip = nil
143
+ port = nil
144
+ # Talking against a remote docker host which is a normal docker host
145
+ if @docker_type == 'docker' && ENV['DOCKER_HOST'] && !ENV.fetch('DOCKER_HOST','').include?(':///')
146
+ ip = URI.parse(ENV['DOCKER_HOST']).host
147
+ else
148
+ # Swarm or local docker host
149
+ if in_container?
150
+ gw = network_settings['Gateway']
151
+ ip = gw unless (gw.nil? || gw.empty?)
152
+ else
153
+ port22 = network_settings.dig('Ports','22/tcp')
154
+ ip = port22[0]["HostIp"] if port22
155
+ port = port22[0]['HostPort'] if port22
156
+ end
157
+ end
158
+
159
+ if host_config['NetworkMode'] != 'slirp4netns' && network_settings['IPAddress'] && !network_settings['IPAddress'].empty?
160
+ ip = network_settings['IPAddress'] if ip.nil?
161
+ else
162
+ port22 = network_settings.dig('Ports','22/tcp')
163
+ port = port22[0]['HostPort'] if port22
164
+ end
165
+
166
+ ssh_connection_info[:ip] = (ip == '0.0.0.0') ? '127.0.0.1' : ip
167
+ ssh_connection_info[:port] = port || '22'
168
+ ssh_connection_info
169
+ end
170
+
112
171
  def provision
113
172
  @logger.notify "Provisioning docker"
114
173
 
@@ -134,6 +193,8 @@ module Beaker
134
193
  image_name = image.id
135
194
  end
136
195
 
196
+ ### BEGIN CONTAINER OPTIONS MANGLING ###
197
+
137
198
  container_opts = get_container_opts(host, image_name)
138
199
  if host['dockeropts'] || @options[:dockeropts]
139
200
  dockeropts = host['dockeropts'] ? host['dockeropts'] : @options[:dockeropts]
@@ -156,7 +217,12 @@ module Beaker
156
217
  host_path = "/" + host_path.gsub(/^.\:/, host_path[/^(.)/].downcase)
157
218
  end
158
219
  a = [ host_path, mount['container_path'] ]
159
- a << mount['opts'] if mount.has_key?('opts')
220
+ if mount.has_key?('opts')
221
+ a << mount['opts'] if mount.has_key?('opts')
222
+ else
223
+ a << mount['opts'] = 'z'
224
+ end
225
+
160
226
  a.join(':')
161
227
  end
162
228
  end
@@ -165,16 +231,47 @@ module Beaker
165
231
  container_opts['Env'] = host['docker_env']
166
232
  end
167
233
 
234
+ # Fixup privileges
235
+ #
236
+ # If the user has specified CAPs, then we cannot be privileged
237
+ #
238
+ # If the user has not specified CAPs, we will default to privileged for
239
+ # compatibility with worst practice
168
240
  if host['docker_cap_add']
169
241
  container_opts['HostConfig']['CapAdd'] = host['docker_cap_add']
242
+ container_opts['HostConfig'].delete('Privileged')
243
+ else
244
+ container_opts['HostConfig']['Privileged'] = container_opts['HostConfig']['Privileged'].nil? ? true : container_opts['HostConfig']['Privileged']
170
245
  end
171
246
 
172
247
  if host['docker_container_name']
173
248
  container_opts['name'] = host['docker_container_name']
249
+ else
250
+ container_opts['name'] = ['beaker', host.name, SecureRandom.uuid.split('-').last].join('-')
174
251
  end
175
252
 
253
+ ### END CONTAINER OPTIONS MANGLING ###
254
+
176
255
  @logger.debug("Creating container from image #{image_name}")
177
- container = ::Docker::Container.create(container_opts)
256
+
257
+ ok=false
258
+ retries=0
259
+ while(!ok && (retries < 5))
260
+ container = ::Docker::Container.create(container_opts)
261
+
262
+ ssh_info = get_ssh_connection_info(container)
263
+ if ::Docker.rootless? && ssh_info[:ip] == '127.0.0.1' && (ssh_info[:port].to_i < 1024)
264
+ @logger.debug("#{host} was given a port less than 1024 but you are connecting to a rootless instance, retrying")
265
+
266
+ container.delete(force: true)
267
+ container = nil
268
+
269
+ retries+=1
270
+ next
271
+ end
272
+
273
+ ok=true
274
+ end
178
275
  else
179
276
  host['use_existing_container'] = true
180
277
  end
@@ -189,36 +286,36 @@ module Beaker
189
286
  @logger.debug("Starting container #{container.id}")
190
287
  container.start
191
288
 
289
+ begin
290
+ container.stats
291
+ rescue StandardError => e
292
+ container.delete(force: true)
293
+ raise "Container '#{container.id}' in a bad state: #{e}"
294
+ end
295
+
296
+ # Preserve the ability to talk directly to the underlying API
297
+ #
298
+ # You can use any method defined by the docker-api gem on this object
299
+ # https://github.com/swipely/docker-api
300
+ host[:docker_container] = container
301
+
192
302
  if install_and_run_ssh(host)
193
303
  @logger.notify("Installing ssh components and starting ssh daemon in #{host} container")
194
304
  install_ssh_components(container, host)
195
305
  # run fixssh to configure and start the ssh service
196
306
  fix_ssh(container, host)
197
307
  end
198
- # Find out where the ssh port is from the container
199
- # When running on swarm DOCKER_HOST points to the swarm manager so we have to get the
200
- # IP of the swarm slave via the container data
201
- # When we are talking to a normal docker instance DOCKER_HOST can point to a remote docker instance.
202
-
203
- # Talking against a remote docker host which is a normal docker host
204
- if @docker_type == 'docker' && ENV['DOCKER_HOST']
205
- ip = URI.parse(ENV['DOCKER_HOST']).host
206
- else
207
- # Swarm or local docker host
208
- if in_container?
209
- ip = container.json["NetworkSettings"]["Gateway"]
210
- else
211
- ip = container.json["NetworkSettings"]["Ports"]["22/tcp"][0]["HostIp"]
212
- end
213
- end
214
308
 
215
- @logger.info("Using docker server at #{ip}")
216
- port = container.json["NetworkSettings"]["Ports"]["22/tcp"][0]["HostPort"]
309
+ ssh_connection_info = get_ssh_connection_info(container)
310
+
311
+ ip = ssh_connection_info[:ip]
312
+ port = ssh_connection_info[:port]
313
+
314
+ @logger.info("Using container connection at #{ip}:#{port}")
217
315
 
218
316
  forward_ssh_agent = @options[:forward_ssh_agent] || false
219
317
 
220
- # Update host metadata
221
- host['ip'] = ip
318
+ host['ip'] = ip
222
319
  host['port'] = port
223
320
  host['ssh'] = {
224
321
  :password => root_password,
@@ -227,15 +324,17 @@ module Beaker
227
324
  :auth_methods => ['password', 'publickey', 'hostbased', 'keyboard-interactive']
228
325
  }
229
326
 
230
- @logger.debug("node available as ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@#{ip} -p #{port}")
327
+ @logger.debug("node available as ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@#{ip} -p #{port}")
231
328
  host['docker_container_id'] = container.id
232
329
  host['docker_image_id'] = image.id
233
330
  host['vm_ip'] = container.json["NetworkSettings"]["IPAddress"].to_s
234
331
 
332
+ def host.reboot
333
+ @logger.warn("Rebooting containers is ineffective...ignoring")
334
+ end
235
335
  end
236
336
 
237
337
  hack_etc_hosts @hosts, @options
238
-
239
338
  end
240
339
 
241
340
  # This sideloads sshd after a container starts
@@ -244,19 +343,23 @@ module Beaker
244
343
  when /ubuntu/, /debian/
245
344
  container.exec(%w(apt-get update))
246
345
  container.exec(%w(apt-get install -y openssh-server openssh-client))
346
+ container.exec(%w(sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/*))
247
347
  when /cumulus/
248
348
  container.exec(%w(apt-get update))
249
349
  container.exec(%w(apt-get install -y openssh-server openssh-client))
350
+ container.exec(%w(sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/*))
250
351
  when /fedora-(2[2-9])/
251
352
  container.exec(%w(dnf clean all))
252
353
  container.exec(%w(dnf install -y sudo openssh-server openssh-clients))
253
354
  container.exec(%w(ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key))
254
355
  container.exec(%w(ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key))
356
+ container.exec(%w(sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/*))
255
357
  when /^el-/, /centos/, /fedora/, /redhat/, /eos/
256
358
  container.exec(%w(yum clean all))
257
359
  container.exec(%w(yum install -y sudo openssh-server openssh-clients))
258
360
  container.exec(%w(ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key))
259
361
  container.exec(%w(ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key))
362
+ container.exec(%w(sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/*))
260
363
  when /opensuse/, /sles/
261
364
  container.exec(%w(zypper -n in openssh))
262
365
  container.exec(%w(ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key))
@@ -301,7 +404,7 @@ module Beaker
301
404
  end
302
405
  @logger.debug("delete container #{container.id}")
303
406
  begin
304
- container.delete
407
+ container.delete(force: true)
305
408
  rescue Excon::Errors::ClientError => e
306
409
  @logger.warn("deletion of container #{container.id} failed: #{e.response.body}")
307
410
  end
@@ -372,71 +475,76 @@ module Beaker
372
475
  case host['platform']
373
476
  when /ubuntu/, /debian/
374
477
  service_name = "ssh"
375
- dockerfile += <<-EOF
478
+ dockerfile += <<~EOF
376
479
  RUN apt-get update
377
480
  RUN apt-get install -y openssh-server openssh-client #{Beaker::HostPrebuiltSteps::DEBIAN_PACKAGES.join(' ')}
378
- EOF
481
+ RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/*
482
+ EOF
379
483
  when /cumulus/
380
- dockerfile += <<-EOF
484
+ dockerfile += <<~EOF
381
485
  RUN apt-get update
382
486
  RUN apt-get install -y openssh-server openssh-client #{Beaker::HostPrebuiltSteps::CUMULUS_PACKAGES.join(' ')}
383
- EOF
487
+ EOF
384
488
  when /fedora-(2[2-9])/
385
- dockerfile += <<-EOF
489
+ dockerfile += <<~EOF
386
490
  RUN dnf clean all
387
491
  RUN dnf install -y sudo openssh-server openssh-clients #{Beaker::HostPrebuiltSteps::UNIX_PACKAGES.join(' ')}
388
492
  RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key
389
493
  RUN ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key
390
- EOF
494
+ RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/*
495
+ EOF
391
496
  when /el-8/
392
- dockerfile += <<-EOF
393
- RUN yum clean all
394
- RUN yum install -y sudo openssh-server openssh-clients #{Beaker::HostPrebuiltSteps::RHEL8_PACKAGES.join(' ')}
497
+ dockerfile += <<~EOF
498
+ RUN dnf clean all
499
+ RUN dnf install -y sudo openssh-server openssh-clients #{Beaker::HostPrebuiltSteps::RHEL8_PACKAGES.join(' ')}
395
500
  RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key
396
501
  RUN ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key
397
- EOF
502
+ RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/*
503
+ EOF
398
504
  when /^el-/, /centos/, /fedora/, /redhat/, /eos/
399
- dockerfile += <<-EOF
505
+ dockerfile += <<~EOF
400
506
  RUN yum clean all
401
507
  RUN yum install -y sudo openssh-server openssh-clients #{Beaker::HostPrebuiltSteps::UNIX_PACKAGES.join(' ')}
402
508
  RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key
403
509
  RUN ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key
404
- EOF
510
+ RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/*
511
+ EOF
405
512
  when /opensuse/, /sles/
406
- dockerfile += <<-EOF
513
+ dockerfile += <<~EOF
407
514
  RUN zypper -n in openssh #{Beaker::HostPrebuiltSteps::SLES_PACKAGES.join(' ')}
408
515
  RUN ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key
409
516
  RUN ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key
410
517
  RUN sed -ri 's/^#?UsePAM .*/UsePAM no/' /etc/ssh/sshd_config
411
- EOF
518
+ RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/*
519
+ EOF
412
520
  when /archlinux/
413
- dockerfile += <<-EOF
521
+ dockerfile += <<~EOF
414
522
  RUN pacman --noconfirm -Sy archlinux-keyring
415
523
  RUN pacman --noconfirm -Syu
416
524
  RUN pacman -S --noconfirm openssh #{Beaker::HostPrebuiltSteps::ARCHLINUX_PACKAGES.join(' ')}
417
525
  RUN ssh-keygen -A
418
526
  RUN sed -ri 's/^#?UsePAM .*/UsePAM no/' /etc/ssh/sshd_config
419
527
  RUN systemctl enable sshd
420
- EOF
528
+ EOF
421
529
  else
422
530
  # TODO add more platform steps here
423
531
  raise "platform #{host['platform']} not yet supported on docker"
424
532
  end
425
533
 
426
534
  # Make sshd directory, set root password
427
- dockerfile += <<-EOF
535
+ dockerfile += <<~EOF
428
536
  RUN mkdir -p /var/run/sshd
429
537
  RUN echo root:#{root_password} | chpasswd
430
- EOF
538
+ EOF
431
539
 
432
540
  # Configure sshd service to allowroot login using password
433
541
  # Also, disable reverse DNS lookups to prevent every. single. ssh
434
542
  # operation taking 30 seconds while the lookup times out.
435
- dockerfile += <<-EOF
543
+ dockerfile += <<~EOF
436
544
  RUN sed -ri 's/^#?PermitRootLogin .*/PermitRootLogin yes/' /etc/ssh/sshd_config
437
545
  RUN sed -ri 's/^#?PasswordAuthentication .*/PasswordAuthentication yes/' /etc/ssh/sshd_config
438
546
  RUN sed -ri 's/^#?UseDNS .*/UseDNS no/' /etc/ssh/sshd_config
439
- EOF
547
+ EOF
440
548
 
441
549
 
442
550
  # Any extra commands specified for the host
@@ -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(:container) do
64
- container = double('Docker::Container')
65
- allow( container ).to receive(:id).and_return('abcdef')
66
- allow( container ).to receive(:start)
67
- allow( container ).to receive(:info).and_return(
68
- *(0..2).map { |index| { 'Names' => ["/spec-container-#{index}"] } }
69
- )
70
- allow( container ).to receive(:json).and_return({
57
+ let(:container_mode) do
58
+ 'rootless'
59
+ end
60
+
61
+ let(:container_config) do
62
+ conf = {
63
+ 'HostConfig' => {
64
+ 'NetworkMode' => 'slirp4netns'
65
+ },
71
66
  'NetworkSettings' => {
72
67
  'IPAddress' => '192.0.2.1',
73
68
  'Ports' => {
@@ -80,7 +75,24 @@ module Beaker
80
75
  },
81
76
  'Gateway' => '192.0.2.254'
82
77
  }
83
- })
78
+ }
79
+
80
+ unless container_mode == 'rootless'
81
+ conf['HostConfig']['NetworkMode'] = 'bridge'
82
+ end
83
+
84
+ conf
85
+ end
86
+
87
+ let(:container) do
88
+ container = double('Docker::Container')
89
+ allow( container ).to receive(:id).and_return('abcdef')
90
+ allow( container ).to receive(:start)
91
+ allow( container ).to receive(:stats)
92
+ allow( container ).to receive(:info).and_return(
93
+ *(0..2).map { |index| { 'Names' => ["/spec-container-#{index}"] } }
94
+ )
95
+ allow( container ).to receive(:json).and_return(container_config)
84
96
  allow( container ).to receive(:kill)
85
97
  allow( container ).to receive(:delete)
86
98
  allow( container ).to receive(:exec)
@@ -88,603 +100,625 @@ module Beaker
88
100
  end
89
101
 
90
102
  let (:docker) { ::Beaker::Docker.new( hosts, options ) }
103
+
91
104
  let(:docker_options) { nil }
105
+
92
106
  let (:version) { {"ApiVersion"=>"1.18", "Arch"=>"amd64", "GitCommit"=>"4749651", "GoVersion"=>"go1.4.2", "KernelVersion"=>"3.16.0-37-generic", "Os"=>"linux", "Version"=>"1.6.0"} }
93
107
 
94
108
  before :each do
95
- # Stub out all of the docker-api gem. we should never really call it
96
- # from these tests
97
- allow_any_instance_of( ::Beaker::Docker ).to receive(:require).with('docker')
98
- allow( ::Docker ).to receive(:options).and_return(docker_options)
99
- allow( ::Docker ).to receive(:options=)
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)
109
+ allow(::Docker).to receive(:rootless?).and_return(true)
106
110
  end
107
111
 
108
- describe '#initialize, failure to validate' do
109
- before :each do
110
- require 'excon'
111
- allow( ::Docker ).to receive(:validate_version!).and_raise(Excon::Errors::SocketError.new( StandardError.new('oops') ))
112
- end
113
- it 'should fail when docker not present' do
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/)
112
+ context 'with connection failure' do
113
+ describe '#initialize' do
114
+ before :each do
115
+ require 'excon'
116
+ expect( ::Docker ).to receive(:version).and_raise(Excon::Errors::SocketError.new( StandardError.new('oops') )).exactly(4).times
117
+ end
118
+
119
+ it 'should fail when docker not present' do
120
+ expect { docker }.to raise_error(RuntimeError, /Docker instance not connectable/)
121
+ expect { docker }.to raise_error(RuntimeError, /Check your DOCKER_HOST variable has been set/)
122
+ expect { docker }.to raise_error(RuntimeError, /If you are on OSX or Windows, you might not have Docker Machine setup correctly/)
123
+ expect { docker }.to raise_error(RuntimeError, /Error was: oops/)
124
+ end
118
125
  end
119
126
  end
120
127
 
121
- describe '#initialize' do
128
+
129
+ context 'with a working connection' do
122
130
  before :each do
123
- allow( ::Docker ).to receive(:validate_version!)
124
- end
131
+ # Stub out all of the docker-api gem. we should never really call it
132
+ # from these tests
133
+ allow_any_instance_of( ::Beaker::Docker ).to receive(:require).with('docker')
134
+ allow( ::Docker ).to receive(:options).and_return(docker_options)
135
+ allow( ::Docker ).to receive(:options=)
136
+ allow( ::Docker ).to receive(:logger=)
137
+ allow( ::Docker ).to receive(:version).and_return(version)
138
+ allow( ::Docker::Image ).to receive(:build).and_return(image)
139
+ allow( ::Docker::Image ).to receive(:create).and_return(image)
140
+ allow( ::Docker::Container ).to receive(:create).and_return(container)
141
+ allow_any_instance_of( ::Docker::Container ).to receive(:start)
142
+ end
143
+
144
+ describe '#initialize' do
145
+ it 'should require the docker gem' do
146
+ expect_any_instance_of( ::Beaker::Docker ).to receive(:require).with('docker').once
125
147
 
126
- it 'should require the docker gem' do
127
- expect_any_instance_of( ::Beaker::Docker ).to receive(:require).with('docker').once
148
+ docker
149
+ end
128
150
 
129
- docker
130
- end
151
+ it 'should fail when the gem is absent' do
152
+ allow_any_instance_of( ::Beaker::Docker ).to receive(:require).with('docker').and_raise(LoadError)
153
+ expect { docker }.to raise_error(LoadError)
154
+ end
131
155
 
132
- it 'should fail when the gem is absent' do
133
- allow_any_instance_of( ::Beaker::Docker ).to receive(:require).with('docker').and_raise(LoadError)
134
- expect { docker }.to raise_error(LoadError)
135
- end
156
+ it 'should set Docker options' do
157
+ expect( ::Docker ).to receive(:options=).with({:write_timeout => 300, :read_timeout => 300}).once
136
158
 
137
- it 'should set Docker options' do
138
- expect( ::Docker ).to receive(:options=).with({:write_timeout => 300, :read_timeout => 300}).once
159
+ docker
160
+ end
139
161
 
140
- docker
141
- end
162
+ context 'when Docker options are already set' do
163
+ let(:docker_options) {{:write_timeout => 600, :foo => :bar}}
142
164
 
143
- context 'when Docker options are already set' do
144
- let(:docker_options) {{:write_timeout => 600, :foo => :bar}}
165
+ it 'should not override Docker options' do
166
+ expect( ::Docker ).to receive(:options=).with({:write_timeout => 600, :read_timeout => 300, :foo => :bar}).once
145
167
 
146
- it 'should not override Docker options' do
147
- expect( ::Docker ).to receive(:options=).with({:write_timeout => 600, :read_timeout => 300, :foo => :bar}).once
168
+ docker
169
+ end
170
+ end
148
171
 
172
+ it 'should check the Docker gem can work with the api' do
149
173
  docker
150
174
  end
151
- end
152
175
 
153
- it 'should check the Docker gem can work with the api' do
154
- expect( ::Docker ).to receive(:validate_version!).once
176
+ it 'should hook the Beaker logger into the Docker one' do
177
+ expect( ::Docker ).to receive(:logger=).with(logger)
155
178
 
156
- docker
179
+ docker
180
+ end
157
181
  end
158
182
 
159
- it 'should hook the Beaker logger into the Docker one' do
160
- expect( ::Docker ).to receive(:logger=).with(logger)
161
-
162
- docker
163
- end
164
- end
183
+ describe '#install_ssh_components' do
184
+ let(:test_container) { double('container') }
185
+ let(:host) {hosts[0]}
186
+ before :each do
187
+ allow( docker ).to receive(:dockerfile_for)
188
+ end
165
189
 
166
- describe '#install_ssh_components' do
167
- let(:test_container) { double('container') }
168
- let(:host) {hosts[0]}
169
- before :each do
170
- allow( ::Docker ).to receive(:validate_version!)
171
- allow( docker ).to receive(:dockerfile_for)
172
- end
190
+ platforms.each do |platform|
191
+ it 'should call exec at least twice' do
192
+ host['platform'] = platform
193
+ expect(test_container).to receive(:exec).at_least(:twice)
194
+ docker.install_ssh_components(test_container, host)
195
+ end
196
+ end
173
197
 
174
- platforms.each do |platform|
175
- it 'should call exec at least twice' do
176
- host['platform'] = platform
198
+ it 'should accept alpine as valid platform' do
199
+ host['platform'] = 'alpine-3.8-x86_64'
177
200
  expect(test_container).to receive(:exec).at_least(:twice)
178
201
  docker.install_ssh_components(test_container, host)
179
202
  end
180
- end
181
203
 
182
- it 'should accept alpine as valid platform' do
183
- host['platform'] = 'alpine-3.8-x86_64'
184
- expect(test_container).to receive(:exec).at_least(:twice)
185
- docker.install_ssh_components(test_container, host)
204
+ it 'should raise an error with an unsupported platform' do
205
+ host['platform'] = 'boogeyman-2000-x86_64'
206
+ expect{docker.install_ssh_components(test_container, host)}.to raise_error(RuntimeError, /boogeyman/)
207
+ end
186
208
  end
187
209
 
188
- it 'should raise an error with an unsupported platform' do
189
- host['platform'] = 'boogeyman-2000-x86_64'
190
- expect{docker.install_ssh_components(test_container, host)}.to raise_error(RuntimeError, /boogeyman/)
191
- end
192
- end
210
+ describe '#provision' do
211
+ before :each do
212
+ allow( docker ).to receive(:dockerfile_for)
213
+ end
193
214
 
194
- describe '#provision' do
195
- before :each do
196
- allow( ::Docker ).to receive(:validate_version!)
197
- allow( docker ).to receive(:dockerfile_for)
198
- end
215
+ context 'when the host has "tag" defined' do
216
+ before :each do
217
+ hosts.each do |host|
218
+ host['tag'] = 'my_tag'
219
+ end
220
+ end
199
221
 
200
- context 'when the host has "tag" defined' do
201
- before :each do
202
- hosts.each do |host|
203
- host['tag'] = 'my_tag'
222
+ it 'will tag the image with the value of the tag' do
223
+ expect( image ).to receive(:tag).with({:repo => 'my_tag'}).exactly(3).times
224
+ docker.provision
204
225
  end
205
226
  end
206
227
 
207
- it 'will tag the image with the value of the tag' do
208
- expect( image ).to receive(:tag).with({:repo => 'my_tag'}).exactly(3).times
209
- docker.provision
210
- end
211
- end
228
+ context 'when the host has "use_image_entry_point" set to true on the host' do
212
229
 
213
- context 'when the host has "use_image_entry_point" set to true on the host' do
230
+ before :each do
231
+ hosts.each do |host|
232
+ host['use_image_entry_point'] = true
233
+ end
234
+ end
214
235
 
215
- before :each do
216
- hosts.each do |host|
217
- host['use_image_entry_point'] = true
236
+ it 'should not call #dockerfile_for but run methods necessary for ssh installation' do
237
+ expect( docker ).not_to receive(:dockerfile_for)
238
+ expect( docker ).to receive(:install_ssh_components).exactly(3).times #once per host
239
+ expect( docker ).to receive(:fix_ssh).exactly(3).times #once per host
240
+ docker.provision
218
241
  end
219
242
  end
220
243
 
221
- it 'should not call #dockerfile_for but run methods necessary for ssh installation' do
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
244
+ context 'when the host has a "dockerfile" for the host' do
228
245
 
229
- context 'when the host has a "dockerfile" for the host' do
246
+ before :each do
247
+ allow( docker ).to receive(:buildargs_for).and_return('buildargs')
248
+ hosts.each do |host|
249
+ host['dockerfile'] = 'mydockerfile'
250
+ end
251
+ end
230
252
 
231
- before :each do
232
- allow( docker ).to receive(:buildargs_for).and_return('buildargs')
233
- hosts.each do |host|
234
- host['dockerfile'] = 'mydockerfile'
253
+ it 'should not call #dockerfile_for but run methods necessary for ssh installation' do
254
+ allow( File ).to receive(:exist?).with('mydockerfile').and_return(true)
255
+ allow( ::Docker::Image ).to receive(:build_from_dir).with("/", hash_including(:rm => true, :buildargs => 'buildargs')).and_return(image)
256
+ expect( docker ).not_to receive(:dockerfile_for)
257
+ expect( docker ).to receive(:install_ssh_components).exactly(3).times #once per host
258
+ expect( docker ).to receive(:fix_ssh).exactly(3).times #once per host
259
+ docker.provision
235
260
  end
236
261
  end
237
262
 
238
- it 'should not call #dockerfile_for but run methods necessary for ssh installation' do
239
- allow( File ).to receive(:exist?).with('mydockerfile').and_return(true)
240
- allow( ::Docker::Image ).to receive(:build_from_dir).with("/", hash_including(:rm => true, :buildargs => 'buildargs')).and_return(image)
241
- expect( docker ).not_to receive(:dockerfile_for)
242
- expect( docker ).to receive(:install_ssh_components).exactly(3).times #once per host
243
- expect( docker ).to receive(:fix_ssh).exactly(3).times #once per host
244
- docker.provision
245
- end
246
- end
263
+ it 'should call image create for hosts when use_image_as_is is defined' do
264
+ hosts.each do |host|
265
+ host['use_image_as_is'] = true
266
+ expect( docker ).not_to receive(:install_ssh_components)
267
+ expect( docker ).not_to receive(:fix_ssh)
268
+ expect( ::Docker::Image ).to receive(:create).with('fromImage' => host['image']) #once per host
269
+ expect( ::Docker::Image ).not_to receive(:build)
270
+ expect( ::Docker::Image ).not_to receive(:build_from_dir)
271
+ end
247
272
 
248
- it 'should call image create for hosts when use_image_as_is is defined' do
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)
273
+ docker.provision
256
274
  end
257
275
 
258
- docker.provision
259
- end
276
+ it 'should call dockerfile_for with all the hosts' do
277
+ hosts.each do |host|
278
+ expect( docker ).not_to receive(:install_ssh_components)
279
+ expect( docker ).not_to receive(:fix_ssh)
280
+ expect( docker ).to receive(:dockerfile_for).with(host).and_return('')
281
+ end
260
282
 
261
- it 'should call dockerfile_for with all the hosts' do
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('')
283
+ docker.provision
266
284
  end
267
285
 
268
- docker.provision
269
- end
286
+ it 'should pass the Dockerfile on to Docker::Image.create' do
287
+ allow( docker ).to receive(:dockerfile_for).and_return('special testing value')
288
+ expect( ::Docker::Image ).to receive(:build).with('special testing value', { :rm => true, :buildargs => '{}' })
270
289
 
271
- it 'should pass the Dockerfile on to Docker::Image.create' do
272
- allow( docker ).to receive(:dockerfile_for).and_return('special testing value')
273
- expect( ::Docker::Image ).to receive(:build).with('special testing value', { :rm => true, :buildargs => '{}' })
274
-
275
- docker.provision
276
- end
290
+ docker.provision
291
+ end
277
292
 
278
- it 'should pass the buildargs from ENV DOCKER_BUILDARGS on to Docker::Image.create' do
279
- allow( docker ).to receive(:dockerfile_for).and_return('special testing value')
280
- ENV['DOCKER_BUILDARGS'] = 'HTTP_PROXY=http://1.1.1.1:3128'
281
- expect( ::Docker::Image ).to receive(:build).with('special testing value', { :rm => true, :buildargs => "{\"HTTP_PROXY\":\"http://1.1.1.1:3128\"}" })
293
+ it 'should pass the buildargs from ENV DOCKER_BUILDARGS on to Docker::Image.create' do
294
+ allow( docker ).to receive(:dockerfile_for).and_return('special testing value')
295
+ ENV['DOCKER_BUILDARGS'] = 'HTTP_PROXY=http://1.1.1.1:3128'
296
+ expect( ::Docker::Image ).to receive(:build).with('special testing value', { :rm => true, :buildargs => "{\"HTTP_PROXY\":\"http://1.1.1.1:3128\"}" })
282
297
 
283
- docker.provision
284
- end
298
+ docker.provision
299
+ end
285
300
 
286
- it 'should create a container based on the Image (identified by image.id)' do
287
- hosts.each do |host|
288
- expect( ::Docker::Container ).to receive(:create).with({
289
- 'Image' => image.id,
290
- 'Hostname' => host.name,
291
- 'HostConfig' => {
292
- 'PortBindings' => {
293
- '22/tcp' => [{ 'HostPort' => /\b\d{4}\b/, 'HostIp' => '0.0.0.0'}]
301
+ it 'should create a container based on the Image (identified by image.id)' do
302
+ hosts.each_with_index do |host,index|
303
+ expect( ::Docker::Container ).to receive(:create).with({
304
+ 'Image' => image.id,
305
+ 'Hostname' => host.name,
306
+ 'HostConfig' => {
307
+ 'PortBindings' => {
308
+ '22/tcp' => [{ 'HostPort' => /\b\d{4}\b/, 'HostIp' => '0.0.0.0'}]
309
+ },
310
+ 'Privileged' => true,
311
+ 'PublishAllPorts' => true,
312
+ 'RestartPolicy' => {
313
+ 'Name' => 'always'
314
+ }
294
315
  },
295
- 'PublishAllPorts' => true,
296
- 'Privileged' => true,
297
- 'RestartPolicy' => {
298
- 'Name' => 'always'
299
- }
300
- },
301
- 'Labels' => {
302
- 'one' => 1,
303
- 'two' => 2,
304
- },
305
- }).with(hash_excluding('name'))
306
- end
316
+ 'Labels' => {
317
+ 'one' => (index == 2 ? 3 : 1),
318
+ 'two' => (index == 2 ? 4 : 2),
319
+ },
320
+ 'name' => /\Abeaker-/
321
+ })
322
+ end
307
323
 
308
- docker.provision
309
- end
324
+ docker.provision
325
+ end
310
326
 
311
- it 'should pass the multiple buildargs from ENV DOCKER_BUILDARGS on to Docker::Image.create' do
312
- allow( docker ).to receive(:dockerfile_for).and_return('special testing value')
313
- ENV['DOCKER_BUILDARGS'] = 'HTTP_PROXY=http://1.1.1.1:3128 HTTPS_PROXY=https://1.1.1.1:3129'
314
- 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\"}" })
327
+ it 'should pass the multiple buildargs from ENV DOCKER_BUILDARGS on to Docker::Image.create' do
328
+ allow( docker ).to receive(:dockerfile_for).and_return('special testing value')
329
+ ENV['DOCKER_BUILDARGS'] = 'HTTP_PROXY=http://1.1.1.1:3128 HTTPS_PROXY=https://1.1.1.1:3129'
330
+ 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
331
 
316
- docker.provision
317
- end
332
+ docker.provision
333
+ end
318
334
 
319
- it 'should create a container based on the Image (identified by image.id)' do
320
- hosts.each do |host|
321
- expect( ::Docker::Container ).to receive(:create).with({
322
- 'Image' => image.id,
323
- 'Hostname' => host.name,
324
- 'HostConfig' => {
325
- 'PortBindings' => {
326
- '22/tcp' => [{ 'HostPort' => /\b\d{4}\b/, 'HostIp' => '0.0.0.0'}]
335
+ it 'should create a container based on the Image (identified by image.id)' do
336
+ hosts.each_with_index do |host,index|
337
+ expect( ::Docker::Container ).to receive(:create).with({
338
+ 'Image' => image.id,
339
+ 'Hostname' => host.name,
340
+ 'HostConfig' => {
341
+ 'PortBindings' => {
342
+ '22/tcp' => [{ 'HostPort' => /\b\d{4}\b/, 'HostIp' => '0.0.0.0'}]
343
+ },
344
+ 'PublishAllPorts' => true,
345
+ 'Privileged' => true,
346
+ 'RestartPolicy' => {
347
+ 'Name' => 'always'
348
+ }
327
349
  },
328
- 'PublishAllPorts' => true,
329
- 'Privileged' => true,
330
- 'RestartPolicy' => {
331
- 'Name' => 'always'
332
- }
333
- },
334
- 'Labels' => {
335
- 'one' => 1,
336
- 'two' => 2,
337
- },
338
- }).with(hash_excluding('name'))
350
+ 'Labels' => {
351
+ 'one' => (index == 2 ? 3 : 1),
352
+ 'two' => (index == 2 ? 4 : 2),
353
+ },
354
+ 'name' => /\Abeaker-/
355
+ })
356
+ end
357
+
358
+ docker.provision
339
359
  end
340
360
 
341
- docker.provision
342
- end
361
+ it 'should create a named container based on the Image (identified by image.id)' do
362
+ hosts.each_with_index do |host, index|
363
+ container_name = "spec-container-#{index}"
364
+ host['docker_container_name'] = container_name
343
365
 
344
- it 'should create a named container based on the Image (identified by image.id)' do
345
- hosts.each_with_index do |host, index|
346
- container_name = "spec-container-#{index}"
347
- host['docker_container_name'] = container_name
348
-
349
- allow(::Docker::Container).to receive(:all).and_return([])
350
- expect( ::Docker::Container ).to receive(:create).with({
351
- 'Image' => image.id,
352
- 'Hostname' => host.name,
353
- 'name' => container_name,
354
- 'HostConfig' => {
355
- 'PortBindings' => {
356
- '22/tcp' => [{ 'HostPort' => /\b\d{4}\b/, 'HostIp' => '0.0.0.0'}]
366
+ allow(::Docker::Container).to receive(:all).and_return([])
367
+ expect( ::Docker::Container ).to receive(:create).with({
368
+ 'Image' => image.id,
369
+ 'Hostname' => host.name,
370
+ 'name' => container_name,
371
+ 'HostConfig' => {
372
+ 'PortBindings' => {
373
+ '22/tcp' => [{ 'HostPort' => /\b\d{4}\b/, 'HostIp' => '0.0.0.0'}]
374
+ },
375
+ 'PublishAllPorts' => true,
376
+ 'Privileged' => true,
377
+ 'RestartPolicy' => {
378
+ 'Name' => 'always'
379
+ }
357
380
  },
358
- 'PublishAllPorts' => true,
359
- 'Privileged' => true,
360
- 'RestartPolicy' => {
361
- 'Name' => 'always'
362
- }
363
- },
364
- 'Labels' => {
365
- 'one' => (index == 2 ? 3 : 1),
366
- 'two' => (index == 2 ? 4 : 2),
367
- },
368
- })
369
- end
381
+ 'Labels' => {
382
+ 'one' => (index == 2 ? 3 : 1),
383
+ 'two' => (index == 2 ? 4 : 2),
384
+ },
385
+ })
386
+ end
370
387
 
371
- docker.provision
372
- end
388
+ docker.provision
389
+ end
373
390
 
374
391
 
375
- it 'should create a container with volumes bound' do
376
- hosts.each_with_index do |host, index|
377
- host['mount_folders'] = {
378
- 'mount1' => {
379
- 'host_path' => '/source_folder',
380
- 'container_path' => '/mount_point',
381
- },
382
- 'mount2' => {
383
- 'host_path' => '/another_folder',
384
- 'container_path' => '/another_mount',
385
- 'opts' => 'ro',
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'}]
392
+ it 'should create a container with volumes bound' do
393
+ hosts.each_with_index do |host, index|
394
+ host['mount_folders'] = {
395
+ 'mount1' => {
396
+ 'host_path' => '/source_folder',
397
+ 'container_path' => '/mount_point',
398
+ },
399
+ 'mount2' => {
400
+ 'host_path' => '/another_folder',
401
+ 'container_path' => '/another_mount',
402
+ 'opts' => 'ro',
415
403
  },
416
- 'PublishAllPorts' => true,
417
- 'Privileged' => true,
418
- 'RestartPolicy' => {
419
- 'Name' => 'always'
404
+ 'mount3' => {
405
+ 'host_path' => '/different_folder',
406
+ 'container_path' => '/different_mount',
407
+ 'opts' => 'rw',
408
+ },
409
+ 'mount4' => {
410
+ 'host_path' => './',
411
+ 'container_path' => '/relative_mount',
412
+ },
413
+ 'mount5' => {
414
+ 'host_path' => 'local_folder',
415
+ 'container_path' => '/another_relative_mount',
420
416
  }
421
- },
422
- 'Labels' => {
423
- 'one' => (index == 2 ? 3 : 1),
424
- 'two' => (index == 2 ? 4 : 2),
425
- },
426
- })
427
- end
417
+ }
428
418
 
429
- docker.provision
430
- end
419
+ expect( ::Docker::Container ).to receive(:create).with({
420
+ 'Image' => image.id,
421
+ 'Hostname' => host.name,
422
+ 'HostConfig' => {
423
+ 'Binds' => [
424
+ '/source_folder:/mount_point:z',
425
+ '/another_folder:/another_mount:ro',
426
+ '/different_folder:/different_mount:rw',
427
+ "#{File.expand_path('./')}:/relative_mount:z",
428
+ "#{File.expand_path('local_folder')}:/another_relative_mount:z",
429
+ ],
430
+ 'PortBindings' => {
431
+ '22/tcp' => [{ 'HostPort' => /\b\d{4}\b/, 'HostIp' => '0.0.0.0'}]
432
+ },
433
+ 'PublishAllPorts' => true,
434
+ 'Privileged' => true,
435
+ 'RestartPolicy' => {
436
+ 'Name' => 'always'
437
+ }
438
+ },
439
+ 'Labels' => {
440
+ 'one' => (index == 2 ? 3 : 1),
441
+ 'two' => (index == 2 ? 4 : 2),
442
+ },
443
+ 'name' => /\Abeaker-/
444
+ })
445
+ end
431
446
 
432
- it 'should create a container with capabilities added' do
433
- hosts.each_with_index do |host, index|
434
- host['docker_cap_add'] = ['NET_ADMIN', 'SYS_ADMIN']
447
+ docker.provision
448
+ end
435
449
 
436
- expect( ::Docker::Container ).to receive(:create).with({
437
- 'Image' => image.id,
438
- 'Hostname' => host.name,
439
- 'HostConfig' => {
440
- 'PortBindings' => {
441
- '22/tcp' => [{ 'HostPort' => /\b\d{4}\b/, 'HostIp' => '0.0.0.0'}]
450
+ it 'should create a container with capabilities added' do
451
+ hosts.each_with_index do |host, index|
452
+ host['docker_cap_add'] = ['NET_ADMIN', 'SYS_ADMIN']
453
+
454
+ expect( ::Docker::Container ).to receive(:create).with({
455
+ 'Image' => image.id,
456
+ 'Hostname' => host.name,
457
+ 'HostConfig' => {
458
+ 'PortBindings' => {
459
+ '22/tcp' => [{ 'HostPort' => /\b\d{4}\b/, 'HostIp' => '0.0.0.0'}]
460
+ },
461
+ 'PublishAllPorts' => true,
462
+ 'RestartPolicy' => {
463
+ 'Name' => 'always'
464
+ },
465
+ 'CapAdd' => ['NET_ADMIN', 'SYS_ADMIN']
442
466
  },
443
- 'PublishAllPorts' => true,
444
- 'Privileged' => true,
445
- 'RestartPolicy' => {
446
- 'Name' => 'always'
467
+ 'Labels' => {
468
+ 'one' => (index == 2 ? 3 : 1),
469
+ 'two' => (index == 2 ? 4 : 2),
447
470
  },
448
- 'CapAdd' => ['NET_ADMIN', 'SYS_ADMIN']
449
- },
450
- 'Labels' => {
451
- 'one' => (index == 2 ? 3 : 1),
452
- 'two' => (index == 2 ? 4 : 2),
453
- },
454
- })
471
+ 'name' => /\Abeaker-/
472
+ })
473
+ end
474
+
475
+ docker.provision
455
476
  end
456
477
 
457
- docker.provision
458
- end
478
+ it 'should start the container' do
479
+ expect( container ).to receive(:start)
459
480
 
460
- it 'should start the container' do
461
- expect( container ).to receive(:start)
481
+ docker.provision
482
+ end
462
483
 
463
- docker.provision
464
- end
484
+ context "connecting to ssh" do
485
+ context "rootless" do
486
+ before { @docker_host = ENV['DOCKER_HOST'] }
487
+ after { ENV['DOCKER_HOST'] = @docker_host }
488
+
489
+ it 'should expose port 22 to beaker' do
490
+ ENV['DOCKER_HOST'] = nil
491
+ docker.provision
492
+
493
+ expect( hosts[0]['ip'] ).to be === '127.0.1.1'
494
+ expect( hosts[0]['port'] ).to be === 8022
495
+ end
496
+
497
+ it 'should expose port 22 to beaker when using DOCKER_HOST' do
498
+ ENV['DOCKER_HOST'] = "tcp://192.0.2.2:2375"
499
+ docker.provision
500
+
501
+ expect( hosts[0]['ip'] ).to be === '192.0.2.2'
502
+ expect( hosts[0]['port'] ).to be === 8022
503
+ end
504
+
505
+ it 'should have ssh agent forwarding enabled' do
506
+ ENV['DOCKER_HOST'] = nil
507
+ docker.provision
508
+
509
+ expect( hosts[0]['ip'] ).to be === '127.0.1.1'
510
+ expect( hosts[0]['port'] ).to be === 8022
511
+ expect( hosts[0]['ssh'][:password] ).to be === 'root'
512
+ expect( hosts[0]['ssh'][:port] ).to be === 8022
513
+ expect( hosts[0]['ssh'][:forward_agent] ).to be === true
514
+ end
515
+
516
+ it 'should connect to gateway ip' do
517
+ FakeFS do
518
+ File.open('/.dockerenv', 'w') { }
519
+ docker.provision
520
+
521
+ expect( hosts[0]['ip'] ).to be === '192.0.2.254'
522
+ expect( hosts[0]['port'] ).to be === 8022
523
+ end
524
+ end
525
+ end
465
526
 
466
- context "connecting to ssh" do
467
- before { @docker_host = ENV['DOCKER_HOST'] }
468
- after { ENV['DOCKER_HOST'] = @docker_host }
527
+ context 'rootful' do
528
+ before { @docker_host = ENV['DOCKER_HOST'] }
529
+ after { ENV['DOCKER_HOST'] = @docker_host }
469
530
 
470
- it 'should expose port 22 to beaker' do
471
- ENV['DOCKER_HOST'] = nil
472
- docker.provision
531
+ let(:container_mode) do
532
+ 'rootful'
533
+ end
473
534
 
474
- expect( hosts[0]['ip'] ).to be === '127.0.1.1'
475
- expect( hosts[0]['port'] ).to be === 8022
476
- end
535
+ it 'should expose port 22 to beaker' do
536
+ ENV['DOCKER_HOST'] = nil
537
+ docker.provision
477
538
 
478
- it 'should expose port 22 to beaker when using DOCKER_HOST' do
479
- ENV['DOCKER_HOST'] = "tcp://192.0.2.2:2375"
480
- docker.provision
539
+ expect( hosts[0]['ip'] ).to be === '127.0.1.1'
540
+ expect( hosts[0]['port'] ).to be === 8022
541
+ end
542
+ end
481
543
 
482
- expect( hosts[0]['ip'] ).to be === '192.0.2.2'
483
- expect( hosts[0]['port'] ).to be === 8022
484
544
  end
485
545
 
486
- it 'should have ssh agent forwarding enabled' do
546
+ it "should generate a new /etc/hosts file referencing each host" do
487
547
  ENV['DOCKER_HOST'] = nil
488
548
  docker.provision
489
-
490
- expect( hosts[0]['ip'] ).to be === '127.0.1.1'
491
- expect( hosts[0]['port'] ).to be === 8022
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
549
+ hosts.each do |host|
550
+ expect( docker ).to receive( :get_domain_name ).with( host ).and_return( 'labs.lan' )
551
+ 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
552
  end
553
+ docker.hack_etc_hosts( hosts, options )
505
554
  end
506
555
 
507
- end
556
+ it 'should record the image and container for later' do
557
+ docker.provision
508
558
 
509
- it "should generate a new /etc/hosts file referencing each host" do
510
- ENV['DOCKER_HOST'] = nil
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
559
+ expect( hosts[0]['docker_image_id'] ).to be === image.id
560
+ expect( hosts[0]['docker_container_id'] ).to be === container.id
515
561
  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
562
 
522
- expect( hosts[0]['docker_image_id'] ).to be === image.id
523
- expect( hosts[0]['docker_container_id'] ).to be === container.id
524
- end
563
+ context 'provision=false' do
564
+ let(:options) {{
565
+ :logger => logger,
566
+ :forward_ssh_agent => true,
567
+ :provision => false
568
+ }}
525
569
 
526
- context 'provision=false' do
527
- let(:options) {{
528
- :logger => logger,
529
- :forward_ssh_agent => true,
530
- :provision => false
531
- }}
532
570
 
571
+ it 'should fix ssh' do
572
+ hosts.each_with_index do |host, index|
573
+ container_name = "spec-container-#{index}"
574
+ host['docker_container_name'] = container_name
533
575
 
534
- it 'should fix ssh' do
535
- hosts.each_with_index do |host, index|
536
- container_name = "spec-container-#{index}"
537
- host['docker_container_name'] = container_name
538
-
539
- expect( ::Docker::Container ).to receive(:all).and_return([container])
540
- expect(docker).to receive(:fix_ssh).exactly(1).times
576
+ expect( ::Docker::Container ).to receive(:all).and_return([container])
577
+ expect(docker).to receive(:fix_ssh).exactly(1).times
578
+ end
579
+ docker.provision
541
580
  end
542
- docker.provision
543
- end
544
581
 
545
- it 'should not create a container if a named one already exists' do
546
- hosts.each_with_index do |host, index|
547
- container_name = "spec-container-#{index}"
548
- host['docker_container_name'] = container_name
582
+ it 'should not create a container if a named one already exists' do
583
+ hosts.each_with_index do |host, index|
584
+ container_name = "spec-container-#{index}"
585
+ host['docker_container_name'] = container_name
549
586
 
550
- expect( ::Docker::Container ).to receive(:all).and_return([container])
551
- expect( ::Docker::Container ).not_to receive(:create)
552
- end
587
+ expect( ::Docker::Container ).to receive(:all).and_return([container])
588
+ expect( ::Docker::Container ).not_to receive(:create)
589
+ end
553
590
 
554
- docker.provision
591
+ docker.provision
592
+ end
555
593
  end
556
594
  end
557
- end
558
595
 
559
- describe '#cleanup' do
560
- before :each do
561
- # get into a state where there's something to clean
562
- allow( ::Docker ).to receive(:validate_version!)
563
- allow( ::Docker::Container ).to receive(:all).and_return([container])
564
- allow( ::Docker::Image ).to receive(:remove).with(image.id)
565
- allow( docker ).to receive(:dockerfile_for)
566
- docker.provision
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
596
+ describe '#cleanup' do
597
+ before :each do
598
+ # get into a state where there's something to clean
599
+ allow( ::Docker::Container ).to receive(:all).and_return([container])
600
+ allow( ::Docker::Image ).to receive(:remove).with(image.id)
601
+ allow( docker ).to receive(:dockerfile_for)
602
+ docker.provision
603
+ end
574
604
 
575
- it 'should delete the containers' do
576
- allow( docker ).to receive( :sleep ).and_return(true)
577
- expect( container ).to receive(:delete)
578
- docker.cleanup
579
- end
605
+ it 'should stop the containers' do
606
+ allow( docker ).to receive( :sleep ).and_return(true)
607
+ expect( container ).to receive(:kill)
608
+ docker.cleanup
609
+ end
580
610
 
581
- it 'should delete the images' do
582
- allow( docker ).to receive( :sleep ).and_return(true)
583
- expect( ::Docker::Image ).to receive(:remove).with(image.id)
584
- docker.cleanup
585
- end
611
+ it 'should delete the containers' do
612
+ allow( docker ).to receive( :sleep ).and_return(true)
613
+ expect( container ).to receive(:delete)
614
+ docker.cleanup
615
+ end
586
616
 
587
- it 'should not delete the image if docker_preserve_image is set to true' do
588
- allow( docker ).to receive( :sleep ).and_return(true)
589
- hosts.each do |host|
590
- host['docker_preserve_image']=true
617
+ it 'should delete the images' do
618
+ allow( docker ).to receive( :sleep ).and_return(true)
619
+ expect( ::Docker::Image ).to receive(:remove).with(image.id)
620
+ docker.cleanup
591
621
  end
592
- expect( ::Docker::Image ).to_not receive(:remove)
593
- docker.cleanup
594
- end
595
622
 
596
- it 'should delete the image if docker_preserve_image is set to false' do
597
- allow( docker ).to receive( :sleep ).and_return(true)
598
- hosts.each do |host|
599
- host['docker_preserve_image']=false
623
+ it 'should not delete the image if docker_preserve_image is set to true' do
624
+ allow( docker ).to receive( :sleep ).and_return(true)
625
+ hosts.each do |host|
626
+ host['docker_preserve_image']=true
627
+ end
628
+ expect( ::Docker::Image ).to_not receive(:remove)
629
+ docker.cleanup
600
630
  end
601
- expect( ::Docker::Image ).to receive(:remove).with(image.id)
602
- docker.cleanup
603
- end
604
631
 
605
- end
632
+ it 'should delete the image if docker_preserve_image is set to false' do
633
+ allow( docker ).to receive( :sleep ).and_return(true)
634
+ hosts.each do |host|
635
+ host['docker_preserve_image']=false
636
+ end
637
+ expect( ::Docker::Image ).to receive(:remove).with(image.id)
638
+ docker.cleanup
639
+ end
606
640
 
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
641
  end
615
642
 
616
- it 'should set "ENV container docker"' do
643
+ describe '#dockerfile_for' do
617
644
  FakeFS.deactivate!
618
- platforms.each do |platform|
619
- dockerfile = docker.send(:dockerfile_for, {
620
- 'platform' => platform,
621
- 'image' => 'foobar',
622
- })
623
- expect( dockerfile ).to be =~ /ENV container docker/
645
+ it 'should raise on an unsupported platform' do
646
+ expect { docker.send(:dockerfile_for, {'platform' => 'a_sidewalk', 'image' => 'foobar' }) }.to raise_error(/platform a_sidewalk not yet supported/)
624
647
  end
625
- end
626
648
 
627
- it 'should add docker_image_commands as RUN statements' do
628
- FakeFS.deactivate!
629
- platforms.each do |platform|
630
- dockerfile = docker.send(:dockerfile_for, {
631
- 'platform' => platform,
632
- 'image' => 'foobar',
633
- 'docker_image_commands' => [
634
- 'special one',
635
- 'special two',
636
- 'special three',
637
- ]
638
- })
649
+ it 'should set "ENV container docker"' do
650
+ FakeFS.deactivate!
651
+ platforms.each do |platform|
652
+ dockerfile = docker.send(:dockerfile_for, {
653
+ 'platform' => platform,
654
+ 'image' => 'foobar',
655
+ })
656
+ expect( dockerfile ).to be =~ /ENV container docker/
657
+ end
658
+ end
639
659
 
640
- expect( dockerfile ).to be =~ /RUN special one\nRUN special two\nRUN special three/
660
+ it 'should add docker_image_commands as RUN statements' do
661
+ FakeFS.deactivate!
662
+ platforms.each do |platform|
663
+ dockerfile = docker.send(:dockerfile_for, {
664
+ 'platform' => platform,
665
+ 'image' => 'foobar',
666
+ 'docker_image_commands' => [
667
+ 'special one',
668
+ 'special two',
669
+ 'special three',
670
+ ]
671
+ })
672
+
673
+ expect( dockerfile ).to be =~ /RUN special one\nRUN special two\nRUN special three/
674
+ end
641
675
  end
642
- end
643
676
 
644
- it 'should add docker_image_entrypoint' do
645
- FakeFS.deactivate!
646
- platforms.each do |platform|
677
+ it 'should add docker_image_entrypoint' do
678
+ FakeFS.deactivate!
679
+ platforms.each do |platform|
680
+ dockerfile = docker.send(:dockerfile_for, {
681
+ 'platform' => platform,
682
+ 'image' => 'foobar',
683
+ 'docker_image_entrypoint' => '/bin/bash'
684
+ })
685
+
686
+ expect( dockerfile ).to be =~ %r{ENTRYPOINT /bin/bash}
687
+ end
688
+ end
689
+
690
+ it 'should use zypper on sles' do
691
+ FakeFS.deactivate!
647
692
  dockerfile = docker.send(:dockerfile_for, {
648
- 'platform' => platform,
693
+ 'platform' => 'sles-12-x86_64',
649
694
  'image' => 'foobar',
650
- 'docker_image_entrypoint' => '/bin/bash'
651
695
  })
652
696
 
653
- expect( dockerfile ).to be =~ %r{ENTRYPOINT /bin/bash}
697
+ expect( dockerfile ).to be =~ /RUN zypper -n in openssh/
654
698
  end
655
- end
656
699
 
657
- it 'should use zypper on sles' do
658
- FakeFS.deactivate!
659
- dockerfile = docker.send(:dockerfile_for, {
660
- 'platform' => 'sles-12-x86_64',
661
- 'image' => 'foobar',
662
- })
700
+ (22..29).to_a.each do | fedora_release |
701
+ it "should use dnf on fedora #{fedora_release}" do
702
+ FakeFS.deactivate!
703
+ dockerfile = docker.send(:dockerfile_for, {
704
+ 'platform' => "fedora-#{fedora_release}-x86_64",
705
+ 'image' => 'foobar',
706
+ })
663
707
 
664
- expect( dockerfile ).to be =~ /RUN zypper -n in openssh/
665
- end
708
+ expect( dockerfile ).to be =~ /RUN dnf install -y sudo/
709
+ end
710
+ end
666
711
 
667
- (22..29).to_a.each do | fedora_release |
668
- it "should use dnf on fedora #{fedora_release}" do
712
+ it 'should use pacman on archlinux' do
669
713
  FakeFS.deactivate!
670
714
  dockerfile = docker.send(:dockerfile_for, {
671
- 'platform' => "fedora-#{fedora_release}-x86_64",
715
+ 'platform' => 'archlinux-current-x86_64',
672
716
  'image' => 'foobar',
673
717
  })
674
718
 
675
- expect( dockerfile ).to be =~ /RUN dnf install -y sudo/
719
+ expect( dockerfile ).to be =~ /RUN pacman -S --noconfirm openssh/
676
720
  end
677
721
  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
722
  end
689
723
  end
690
724
  end