kitchen-docker 2.9.0 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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