beaker-docker 0.7.0 → 0.8.3

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