kitchen-docker 2.9.0 → 2.10.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,68 @@
1
+ #
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+
14
+ require 'kitchen'
15
+ require 'kitchen/configurable'
16
+ require_relative 'cli_helper'
17
+ require_relative 'container_helper'
18
+
19
+ module Kitchen
20
+ module Docker
21
+ module Helpers
22
+ module ImageHelper
23
+ include Configurable
24
+ include Kitchen::Docker::Helpers::CliHelper
25
+ include Kitchen::Docker::Helpers::ContainerHelper
26
+
27
+ def parse_image_id(output)
28
+ output.each_line do |line|
29
+ if line =~ /image id|build successful|successfully built/i
30
+ return line.split(/\s+/).last
31
+ end
32
+ end
33
+ raise ActionFailed, 'Could not parse Docker build output for image ID'
34
+ end
35
+
36
+ def remove_image(state)
37
+ image_id = state[:image_id]
38
+ docker_command("rmi #{image_id}")
39
+ end
40
+
41
+ def build_image(state, dockerfile)
42
+ cmd = 'build'
43
+ cmd << ' --no-cache' unless config[:use_cache]
44
+ extra_build_options = config_to_options(config[:build_options])
45
+ cmd << " #{extra_build_options}" unless extra_build_options.empty?
46
+ dockerfile_contents = dockerfile
47
+ build_context = config[:build_context] ? '.' : '-'
48
+ file = Tempfile.new('Dockerfile-kitchen', Dir.pwd)
49
+ output = begin
50
+ file.write(dockerfile)
51
+ file.close
52
+ docker_command("#{cmd} -f #{Shellwords.escape(dockerfile_path(file))} #{build_context}",
53
+ input: dockerfile_contents)
54
+ ensure
55
+ file.close unless file.closed?
56
+ file.unlink
57
+ end
58
+
59
+ parse_image_id(output)
60
+ end
61
+
62
+ def image_exists?(state)
63
+ state[:image_id] && !!docker_command("inspect --type=image #{state[:image_id]}") rescue false
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,40 @@
1
+ #
2
+ # Licensed under the Apache License, Version 2.0 (the "License");
3
+ # you may not use this file except in compliance with the License.
4
+ # You may obtain a copy of the License at
5
+ #
6
+ # http://www.apache.org/licenses/LICENSE-2.0
7
+ #
8
+ # Unless required by applicable law or agreed to in writing, software
9
+ # distributed under the License is distributed on an "AS IS" BASIS,
10
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ # See the License for the specific language governing permissions and
12
+ # limitations under the License.
13
+
14
+ # This helper should be removed when the kitchen-inspec gem has been updated to include these runner options
15
+ begin
16
+ require 'kitchen/verifier/inspec'
17
+
18
+ # Add runner options for Docker transport for kitchen-inspec gem
19
+ module Kitchen
20
+ module Docker
21
+ module Helpers
22
+ module InspecHelper
23
+ Kitchen::Verifier::Inspec.class_eval do
24
+ def runner_options_for_docker(config_data)
25
+ opts = {
26
+ 'backend' => 'docker',
27
+ 'logger' => logger,
28
+ 'host' => config_data[:container_id],
29
+ }
30
+ logger.debug "Connect to Container: #{opts['host']}"
31
+ opts
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ rescue LoadError => e
39
+ logger.debug("[Docker] kitchen-inspec gem not found for InSpec verifier. #{e}")
40
+ end
@@ -1,457 +1,164 @@
1
- # -*- encoding: utf-8 -*-
2
- #
3
- # Copyright (C) 2014, Sean Porter
4
- #
5
- # Licensed under the Apache License, Version 2.0 (the "License");
6
- # you may not use this file except in compliance with the License.
7
- # You may obtain a copy of the License at
8
- #
9
- # http://www.apache.org/licenses/LICENSE-2.0
10
- #
11
- # Unless required by applicable law or agreed to in writing, software
12
- # distributed under the License is distributed on an "AS IS" BASIS,
13
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- # See the License for the specific language governing permissions and
15
- # limitations under the License.
16
-
17
- require 'kitchen'
18
- require 'json'
19
- require 'securerandom'
20
- require 'uri'
21
- require 'net/ssh'
22
- require 'tempfile'
23
- require 'shellwords'
24
- require 'base64'
25
-
26
- require 'kitchen/driver/base'
27
-
28
- require_relative './docker/erb'
29
-
30
- module Kitchen
31
- module Driver
32
- # Docker driver for Kitchen.
33
- #
34
- # @author Sean Porter <portertech@gmail.com>
35
- class Docker < Kitchen::Driver::Base
36
- include ShellOut
37
-
38
- default_config :binary, 'docker'
39
- default_config :socket, ENV['DOCKER_HOST'] || 'unix:///var/run/docker.sock'
40
- default_config :privileged, false
41
- default_config :cap_add, nil
42
- default_config :cap_drop, nil
43
- default_config :security_opt, nil
44
- default_config :use_cache, true
45
- default_config :remove_images, false
46
- default_config :run_command, '/usr/sbin/sshd -D -o UseDNS=no -o UsePAM=no -o PasswordAuthentication=yes ' +
47
- '-o UsePrivilegeSeparation=no -o PidFile=/tmp/sshd.pid'
48
- default_config :username, 'kitchen'
49
- default_config :tls, false
50
- default_config :tls_verify, false
51
- default_config :tls_cacert, nil
52
- default_config :tls_cert, nil
53
- default_config :tls_key, nil
54
- default_config :publish_all, false
55
- default_config :wait_for_sshd, true
56
- default_config :private_key, File.join(Dir.pwd, '.kitchen', 'docker_id_rsa')
57
- default_config :public_key, File.join(Dir.pwd, '.kitchen', 'docker_id_rsa.pub')
58
- default_config :build_options, nil
59
- default_config :run_options, nil
60
- default_config :use_internal_docker_network, false
61
-
62
- default_config :use_sudo, false
63
-
64
- default_config :image do |driver|
65
- driver.default_image
66
- end
67
-
68
- default_config :platform do |driver|
69
- driver.default_platform
70
- end
71
-
72
- default_config :disable_upstart, true
73
-
74
- default_config :build_context do |driver|
75
- !driver.remote_socket?
76
- end
77
-
78
- default_config :instance_name do |driver|
79
- # Borrowed from kitchen-rackspace
80
- [
81
- driver.instance.name.gsub(/\W/, ''),
82
- (Etc.getlogin || 'nologin').gsub(/\W/, ''),
83
- Socket.gethostname.gsub(/\W/, '')[0..20],
84
- Array.new(8) { rand(36).to_s(36) }.join
85
- ].join('-')
86
- end
87
-
88
- MUTEX_FOR_SSH_KEYS = Mutex.new
89
-
90
- def verify_dependencies
91
- run_command("#{config[:binary]} >> #{dev_null} 2>&1", quiet: true, use_sudo: config[:use_sudo])
92
- rescue
93
- raise UserError,
94
- 'You must first install the Docker CLI tool https://www.docker.com/get-started'
95
- end
96
-
97
- def dev_null
98
- case RbConfig::CONFIG["host_os"]
99
- when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
100
- "NUL"
101
- else
102
- "/dev/null"
103
- end
104
- end
105
-
106
- def default_image
107
- platform, release = instance.platform.name.split('-')
108
- if platform == 'centos' && release
109
- release = 'centos' + release.split('.').first
110
- end
111
- release ? [platform, release].join(':') : platform
112
- end
113
-
114
- def default_platform
115
- instance.platform.name.split('-').first
116
- end
117
-
118
- def create(state)
119
- generate_keys
120
- state[:username] = config[:username]
121
- state[:ssh_key] = config[:private_key]
122
- state[:image_id] = build_image(state) unless state[:image_id]
123
- state[:container_id] = run_container(state) unless state[:container_id]
124
- state[:hostname] = 'localhost'
125
- if remote_socket?
126
- state[:hostname] = socket_uri.host
127
- elsif config[:use_internal_docker_network]
128
- state[:hostname] = container_ip(state)
129
- end
130
- state[:port] = container_ssh_port(state)
131
- if config[:wait_for_sshd]
132
- instance.transport.connection(state) do |conn|
133
- conn.wait_until_ready
134
- end
135
- end
136
- end
137
-
138
- def destroy(state)
139
- rm_container(state) if container_exists?(state)
140
- if config[:remove_images] && state[:image_id]
141
- rm_image(state) if image_exists?(state)
142
- end
143
- end
144
-
145
- def remote_socket?
146
- config[:socket] ? socket_uri.scheme == 'tcp' : false
147
- end
148
-
149
- protected
150
-
151
- def socket_uri
152
- URI.parse(config[:socket])
153
- end
154
-
155
- def docker_command(cmd, options={})
156
- docker = config[:binary].dup
157
- docker << " -H #{config[:socket]}" if config[:socket]
158
- docker << " --tls" if config[:tls]
159
- docker << " --tlsverify" if config[:tls_verify]
160
- docker << " --tlscacert=#{config[:tls_cacert]}" if config[:tls_cacert]
161
- docker << " --tlscert=#{config[:tls_cert]}" if config[:tls_cert]
162
- docker << " --tlskey=#{config[:tls_key]}" if config[:tls_key]
163
- run_command("#{docker} #{cmd}", options.merge({
164
- quiet: !logger.debug?,
165
- use_sudo: config[:use_sudo],
166
- log_subject: Thor::Util.snake_case(self.class.to_s),
167
- }))
168
- end
169
-
170
- def generate_keys
171
- MUTEX_FOR_SSH_KEYS.synchronize do
172
- if !File.exist?(config[:public_key]) || !File.exist?(config[:private_key])
173
- private_key = OpenSSL::PKey::RSA.new(2048)
174
- blobbed_key = Base64.encode64(private_key.to_blob).gsub("\n", '')
175
- public_key = "ssh-rsa #{blobbed_key} kitchen_docker_key"
176
- File.open(config[:private_key], 'w') do |file|
177
- file.write(private_key)
178
- file.chmod(0600)
179
- end
180
- File.open(config[:public_key], 'w') do |file|
181
- file.write(public_key)
182
- file.chmod(0600)
183
- end
184
- end
185
- end
186
- end
187
-
188
- def build_dockerfile
189
- from = "FROM #{config[:image]}"
190
-
191
- env_variables = ''
192
- if config[:http_proxy]
193
- env_variables << "ENV http_proxy #{config[:http_proxy]}\n"
194
- env_variables << "ENV HTTP_PROXY #{config[:http_proxy]}\n"
195
- end
196
-
197
- if config[:https_proxy]
198
- env_variables << "ENV https_proxy #{config[:https_proxy]}\n"
199
- env_variables << "ENV HTTPS_PROXY #{config[:https_proxy]}\n"
200
- end
201
-
202
- if config[:no_proxy]
203
- env_variables << "ENV no_proxy #{config[:no_proxy]}\n"
204
- env_variables << "ENV NO_PROXY #{config[:no_proxy]}\n"
205
- end
206
-
207
- platform = case config[:platform]
208
- when 'debian', 'ubuntu'
209
- disable_upstart = <<-eos
210
- RUN [ ! -f "/sbin/initctl" ] || dpkg-divert --local --rename --add /sbin/initctl && ln -sf /bin/true /sbin/initctl
211
- eos
212
- packages = <<-eos
213
- ENV DEBIAN_FRONTEND noninteractive
214
- ENV container docker
215
- RUN apt-get update
216
- RUN apt-get install -y sudo openssh-server curl lsb-release
217
- eos
218
- config[:disable_upstart] ? disable_upstart + packages : packages
219
- when 'rhel', 'centos', 'oraclelinux', 'amazonlinux'
220
- <<-eos
221
- ENV container docker
222
- RUN yum clean all
223
- RUN yum install -y sudo openssh-server openssh-clients which curl
224
- RUN [ -f "/etc/ssh/ssh_host_rsa_key" ] || ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key -N ''
225
- RUN [ -f "/etc/ssh/ssh_host_dsa_key" ] || ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key -N ''
226
- eos
227
- when 'fedora'
228
- <<-eos
229
- ENV container docker
230
- RUN dnf clean all
231
- RUN dnf install -y sudo openssh-server openssh-clients which curl
232
- RUN [ -f "/etc/ssh/ssh_host_rsa_key" ] || ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key -N ''
233
- RUN [ -f "/etc/ssh/ssh_host_dsa_key" ] || ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key -N ''
234
- eos
235
- when 'opensuse/tumbleweed', 'opensuse/leap', 'opensuse', 'sles'
236
- <<-eos
237
- ENV container docker
238
- RUN zypper install -y sudo openssh which curl
239
- RUN [ -f "/etc/ssh/ssh_host_rsa_key" ] || ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key -N ''
240
- RUN [ -f "/etc/ssh/ssh_host_dsa_key" ] || ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key -N ''
241
- eos
242
- when 'arch'
243
- # See https://bugs.archlinux.org/task/47052 for why we
244
- # blank out limits.conf.
245
- <<-eos
246
- RUN pacman --noconfirm -Sy archlinux-keyring
247
- RUN pacman-db-upgrade
248
- RUN pacman --noconfirm -Syu openssl openssh sudo curl
249
- RUN [ -f "/etc/ssh/ssh_host_rsa_key" ] || ssh-keygen -A -t rsa -f /etc/ssh/ssh_host_rsa_key
250
- RUN [ -f "/etc/ssh/ssh_host_dsa_key" ] || ssh-keygen -A -t dsa -f /etc/ssh/ssh_host_dsa_key
251
- RUN echo >/etc/security/limits.conf
252
- eos
253
- when 'gentoo'
254
- <<-eos
255
- RUN emerge --sync
256
- RUN emerge net-misc/openssh app-admin/sudo
257
- RUN [ -f "/etc/ssh/ssh_host_rsa_key" ] || ssh-keygen -A -t rsa -f /etc/ssh/ssh_host_rsa_key
258
- RUN [ -f "/etc/ssh/ssh_host_dsa_key" ] || ssh-keygen -A -t dsa -f /etc/ssh/ssh_host_dsa_key
259
- eos
260
- when 'gentoo-paludis'
261
- <<-eos
262
- RUN cave sync
263
- RUN cave resolve -zx net-misc/openssh app-admin/sudo
264
- RUN [ -f "/etc/ssh/ssh_host_rsa_key" ] || ssh-keygen -A -t rsa -f /etc/ssh/ssh_host_rsa_key
265
- RUN [ -f "/etc/ssh/ssh_host_dsa_key" ] || ssh-keygen -A -t dsa -f /etc/ssh/ssh_host_dsa_key
266
- eos
267
- else
268
- raise ActionFailed,
269
- "Unknown platform '#{config[:platform]}'"
270
- end
271
-
272
- username = config[:username]
273
- public_key = IO.read(config[:public_key]).strip
274
- homedir = username == 'root' ? '/root' : "/home/#{username}"
275
-
276
- base = <<-eos
277
- RUN if ! getent passwd #{username}; then \
278
- useradd -d #{homedir} -m -s /bin/bash -p '*' #{username}; \
279
- fi
280
- RUN echo "#{username} ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
281
- RUN echo "Defaults !requiretty" >> /etc/sudoers
282
- RUN mkdir -p #{homedir}/.ssh
283
- RUN chown -R #{username} #{homedir}/.ssh
284
- RUN chmod 0700 #{homedir}/.ssh
285
- RUN touch #{homedir}/.ssh/authorized_keys
286
- RUN chown #{username} #{homedir}/.ssh/authorized_keys
287
- RUN chmod 0600 #{homedir}/.ssh/authorized_keys
288
- RUN mkdir -p /run/sshd
289
- eos
290
- custom = ''
291
- Array(config[:provision_command]).each do |cmd|
292
- custom << "RUN #{cmd}\n"
293
- end
294
- ssh_key = "RUN echo #{Shellwords.escape(public_key)} >> #{homedir}/.ssh/authorized_keys"
295
- # Empty string to ensure the file ends with a newline.
296
- [from, env_variables, platform, base, custom, ssh_key, ''].join("\n")
297
- end
298
-
299
- def dockerfile
300
- if config[:dockerfile]
301
- template = IO.read(File.expand_path(config[:dockerfile]))
302
- context = DockerERBContext.new(config.to_hash)
303
- ERB.new(template).result(context.get_binding)
304
- else
305
- build_dockerfile
306
- end
307
- end
308
-
309
- def parse_image_id(output)
310
- output.each_line do |line|
311
- if line =~ /image id|build successful|successfully built/i
312
- return line.split(/\s+/).last
313
- end
314
- end
315
- raise ActionFailed,
316
- 'Could not parse Docker build output for image ID'
317
- end
318
-
319
- def build_image(state)
320
- cmd = "build"
321
- cmd << " --no-cache" unless config[:use_cache]
322
- extra_build_options = config_to_options(config[:build_options])
323
- cmd << " #{extra_build_options}" unless extra_build_options.empty?
324
- dockerfile_contents = dockerfile
325
- build_context = config[:build_context] ? '.' : '-'
326
- file = Tempfile.new('Dockerfile-kitchen', Dir.pwd)
327
- output = begin
328
- file.write(dockerfile)
329
- file.close
330
- docker_command("#{cmd} -f #{Shellwords.escape(dockerfile_path(file))} #{build_context}", :input => dockerfile_contents)
331
- ensure
332
- file.close unless file.closed?
333
- file.unlink
334
- end
335
- parse_image_id(output)
336
- end
337
-
338
- def parse_container_id(output)
339
- container_id = output.chomp
340
- unless [12, 64].include?(container_id.size)
341
- raise ActionFailed,
342
- 'Could not parse Docker run output for container ID'
343
- end
344
- container_id
345
- end
346
-
347
- def build_run_command(image_id)
348
- cmd = "run -d -p 22"
349
- Array(config[:forward]).each {|port| cmd << " -p #{port}"}
350
- Array(config[:dns]).each {|dns| cmd << " --dns #{dns}"}
351
- Array(config[:add_host]).each {|host, ip| cmd << " --add-host=#{host}:#{ip}"}
352
- Array(config[:volume]).each {|volume| cmd << " -v #{volume}"}
353
- Array(config[:volumes_from]).each {|container| cmd << " --volumes-from #{container}"}
354
- Array(config[:links]).each {|link| cmd << " --link #{link}"}
355
- Array(config[:devices]).each {|device| cmd << " --device #{device}"}
356
- cmd << " --name #{config[:instance_name]}" if config[:instance_name]
357
- cmd << " -P" if config[:publish_all]
358
- cmd << " -h #{config[:hostname]}" if config[:hostname]
359
- cmd << " -m #{config[:memory]}" if config[:memory]
360
- cmd << " -c #{config[:cpu]}" if config[:cpu]
361
- cmd << " -e http_proxy=#{config[:http_proxy]}" if config[:http_proxy]
362
- cmd << " -e https_proxy=#{config[:https_proxy]}" if config[:https_proxy]
363
- cmd << " --privileged" if config[:privileged]
364
- Array(config[:cap_add]).each {|cap| cmd << " --cap-add=#{cap}"} if config[:cap_add]
365
- Array(config[:cap_drop]).each {|cap| cmd << " --cap-drop=#{cap}"} if config[:cap_drop]
366
- Array(config[:security_opt]).each {|opt| cmd << " --security-opt=#{opt}"} if config[:security_opt]
367
- extra_run_options = config_to_options(config[:run_options])
368
- cmd << " #{extra_run_options}" unless extra_run_options.empty?
369
- cmd << " #{image_id} #{config[:run_command]}"
370
- cmd
371
- end
372
-
373
- def run_container(state)
374
- cmd = build_run_command(state[:image_id])
375
- output = docker_command(cmd)
376
- parse_container_id(output)
377
- end
378
-
379
- def container_exists?(state)
380
- state[:container_id] && !!docker_command("top #{state[:container_id]}") rescue false
381
- end
382
-
383
- def image_exists?(state)
384
- state[:image_id] && !!docker_command("docker inspect --type=image #{state[:image_id]}") rescue false
385
- end
386
-
387
- def parse_container_ssh_port(output)
388
- begin
389
- _host, port = output.split(':')
390
- port.to_i
391
- rescue
392
- raise ActionFailed,
393
- 'Could not parse Docker port output for container SSH port'
394
- end
395
- end
396
-
397
- def container_ssh_port(state)
398
- begin
399
- if config[:use_internal_docker_network]
400
- return 22
401
- end
402
- output = docker_command("port #{state[:container_id]} 22/tcp")
403
- parse_container_ssh_port(output)
404
- rescue
405
- raise ActionFailed,
406
- 'Docker reports container has no ssh port mapped'
407
- end
408
- end
409
-
410
- def container_ip(state)
411
- begin
412
- cmd = "inspect --format '{{ .NetworkSettings.IPAddress }}'"
413
- cmd << " #{state[:container_id]}"
414
- docker_command(cmd).strip
415
- rescue
416
- raise ActionFailed,
417
- 'Error getting internal IP of Docker container'
418
- end
419
- end
420
-
421
- def rm_container(state)
422
- container_id = state[:container_id]
423
- docker_command("stop -t 0 #{container_id}")
424
- docker_command("rm #{container_id}")
425
- end
426
-
427
- def rm_image(state)
428
- image_id = state[:image_id]
429
- docker_command("rmi #{image_id}")
430
- end
431
-
432
- # Convert the config input for `:build_options` or `:run_options` in to a
433
- # command line string for use with Docker.
434
- #
435
- # @since 2.5.0
436
- # @param config [nil, String, Array, Hash] Config data to convert.
437
- # @return [String]
438
- def config_to_options(config)
439
- case config
440
- when nil
441
- ''
442
- when String
443
- config
444
- when Array
445
- config.map {|c| config_to_options(c) }.join(' ')
446
- when Hash
447
- config.map {|k, v| Array(v).map {|c| "--#{k}=#{Shellwords.escape(c)}" }.join(' ') }.join(' ')
448
- end
449
- end
450
-
451
- def dockerfile_path(file)
452
- config[:build_context] ? Pathname.new(file.path).relative_path_from(Pathname.pwd).to_s : file.path
453
- end
454
-
455
- end
456
- end
457
- end
1
+ #
2
+ # Copyright (C) 2014, Sean Porter
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require 'kitchen'
17
+ require 'json'
18
+ require 'securerandom'
19
+ require 'net/ssh'
20
+
21
+ require 'kitchen/driver/base'
22
+
23
+ require_relative '../docker/container/linux'
24
+ require_relative '../docker/container/windows'
25
+ require_relative '../docker/helpers/cli_helper'
26
+ require_relative '../docker/helpers/container_helper'
27
+
28
+ module Kitchen
29
+ module Driver
30
+ # Docker driver for Kitchen.
31
+ #
32
+ # @author Sean Porter <portertech@gmail.com>
33
+ class Docker < Kitchen::Driver::Base
34
+ include Kitchen::Docker::Helpers::CliHelper
35
+ include Kitchen::Docker::Helpers::ContainerHelper
36
+ include ShellOut
37
+
38
+ default_config :binary, 'docker'
39
+ default_config :build_options, nil
40
+ default_config :cap_add, nil
41
+ default_config :cap_drop, nil
42
+ default_config :disable_upstart, true
43
+ default_config :env_variables, nil
44
+ default_config :interactive, false
45
+ default_config :private_key, File.join(Dir.pwd, '.kitchen', 'docker_id_rsa')
46
+ default_config :privileged, false
47
+ default_config :public_key, File.join(Dir.pwd, '.kitchen', 'docker_id_rsa.pub')
48
+ default_config :publish_all, false
49
+ default_config :remove_images, false
50
+ default_config :run_options, nil
51
+ default_config :security_opt, nil
52
+ default_config :tls, false
53
+ default_config :tls_cacert, nil
54
+ default_config :tls_cert, nil
55
+ default_config :tls_key, nil
56
+ default_config :tls_verify, false
57
+ default_config :tty, false
58
+ default_config :use_cache, true
59
+ default_config :use_internal_docker_network, false
60
+ default_config :use_sudo, false
61
+ default_config :wait_for_transport, true
62
+
63
+ default_config :build_context do |driver|
64
+ !driver.remote_socket?
65
+ end
66
+
67
+ default_config :image do |driver|
68
+ driver.default_image
69
+ end
70
+
71
+ default_config :instance_name do |driver|
72
+ # Borrowed from kitchen-rackspace
73
+ [
74
+ driver.instance.name.gsub(/\W/, ''),
75
+ (Etc.getlogin || 'nologin').gsub(/\W/, ''),
76
+ Socket.gethostname.gsub(/\W/, '')[0..20],
77
+ Array.new(8) { rand(36).to_s(36) }.join
78
+ ].join('-')
79
+ end
80
+
81
+ default_config :platform do |driver|
82
+ driver.default_platform
83
+ end
84
+
85
+ default_config :run_command do |driver|
86
+ if driver.windows_os?
87
+ # Launch arbitrary process to keep the Windows container alive
88
+ # If running in interactive mode, launch powershell.exe instead
89
+ if driver[:interactive]
90
+ 'powershell.exe'
91
+ else
92
+ 'ping -t localhost'
93
+ end
94
+ else
95
+ '/usr/sbin/sshd -D -o UseDNS=no -o UsePAM=no -o PasswordAuthentication=yes '\
96
+ '-o UsePrivilegeSeparation=no -o PidFile=/tmp/sshd.pid'
97
+ end
98
+ end
99
+
100
+ default_config :socket do |driver|
101
+ socket = 'unix:///var/run/docker.sock'
102
+ socket = 'npipe:////./pipe/docker_engine' if driver.windows_os?
103
+ ENV['DOCKER_HOST'] || socket
104
+ end
105
+
106
+ default_config :username do |driver|
107
+ # Return nil to prevent username from being added to Docker
108
+ # command line args for Windows if a username was not specified
109
+ if driver.windows_os?
110
+ nil
111
+ else
112
+ 'kitchen'
113
+ end
114
+ end
115
+
116
+ def verify_dependencies
117
+ run_command("#{config[:binary]} >> #{dev_null} 2>&1", quiet: true, use_sudo: config[:use_sudo])
118
+ rescue
119
+ raise UserError, 'You must first install the Docker CLI tool https://www.docker.com/get-started'
120
+ end
121
+
122
+ def create(state)
123
+ container.create(state)
124
+
125
+ wait_for_transport(state)
126
+ end
127
+
128
+ def destroy(state)
129
+ container.destroy(state)
130
+ end
131
+
132
+ def wait_for_transport(state)
133
+ if config[:wait_for_transport]
134
+ instance.transport.connection(state) do |conn|
135
+ conn.wait_until_ready
136
+ end
137
+ end
138
+ end
139
+
140
+ def default_image
141
+ platform, release = instance.platform.name.split('-')
142
+ if platform == 'centos' && release
143
+ release = 'centos' + release.split('.').first
144
+ end
145
+ release ? [platform, release].join(':') : platform
146
+ end
147
+
148
+ def default_platform
149
+ instance.platform.name.split('-').first
150
+ end
151
+
152
+ protected
153
+
154
+ def container
155
+ @container ||= if windows_os?
156
+ Kitchen::Docker::Container::Windows.new(config)
157
+ else
158
+ Kitchen::Docker::Container::Linux.new(config)
159
+ end
160
+ @container
161
+ end
162
+ end
163
+ end
164
+ end