beaker-docker 0.7.1 → 0.8.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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