kitchen-docker 2.8.0 → 2.12.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.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +7 -0
- data/.gitignore +2 -0
- data/.kitchen.windows.yml +33 -0
- data/.kitchen.yml +26 -17
- data/.travis.yml +54 -14
- data/CHANGELOG.md +75 -60
- data/README.md +135 -20
- data/docker.ps1 +9 -0
- data/kitchen-docker.gemspec +4 -5
- data/lib/docker/version.rb +25 -0
- data/lib/kitchen/docker/container/linux.rb +136 -0
- data/lib/kitchen/docker/container/windows.rb +85 -0
- data/lib/kitchen/docker/container.rb +75 -0
- data/lib/kitchen/{driver → docker}/docker_version.rb +2 -5
- data/lib/kitchen/{driver/docker/erb.rb → docker/erb_context.rb} +2 -5
- data/lib/kitchen/docker/helpers/cli_helper.rb +172 -0
- data/lib/kitchen/docker/helpers/container_helper.rb +172 -0
- data/lib/kitchen/docker/helpers/dockerfile_helper.rb +136 -0
- data/lib/kitchen/docker/helpers/file_helper.rb +40 -0
- data/lib/kitchen/docker/helpers/image_helper.rb +76 -0
- data/lib/kitchen/docker/helpers/inspec_helper.rb +40 -0
- data/lib/kitchen/driver/docker.rb +77 -361
- data/lib/kitchen/transport/docker.rb +111 -0
- data/lib/train/docker.rb +125 -0
- data/test/Dockerfile +1 -1
- data/test/integration/default/serverspec/default_spec.rb +1 -1
- data/test/integration/default/serverspec/spec_helper.rb +21 -0
- data/test/integration/inspec/inspec_spec.rb +8 -2
- metadata +47 -10
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
|
2
1
|
#
|
|
3
2
|
# Copyright (C) 2014, Sean Porter
|
|
4
3
|
#
|
|
@@ -17,15 +16,14 @@
|
|
|
17
16
|
require 'kitchen'
|
|
18
17
|
require 'json'
|
|
19
18
|
require 'securerandom'
|
|
20
|
-
require 'uri'
|
|
21
19
|
require 'net/ssh'
|
|
22
|
-
require 'tempfile'
|
|
23
|
-
require 'shellwords'
|
|
24
|
-
require 'base64'
|
|
25
20
|
|
|
26
21
|
require 'kitchen/driver/base'
|
|
27
22
|
|
|
28
|
-
require_relative '
|
|
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'
|
|
29
27
|
|
|
30
28
|
module Kitchen
|
|
31
29
|
module Driver
|
|
@@ -33,48 +31,44 @@ module Kitchen
|
|
|
33
31
|
#
|
|
34
32
|
# @author Sean Porter <portertech@gmail.com>
|
|
35
33
|
class Docker < Kitchen::Driver::Base
|
|
34
|
+
include Kitchen::Docker::Helpers::CliHelper
|
|
35
|
+
include Kitchen::Docker::Helpers::ContainerHelper
|
|
36
36
|
include ShellOut
|
|
37
37
|
|
|
38
38
|
default_config :binary, 'docker'
|
|
39
|
-
default_config :
|
|
40
|
-
default_config :privileged, false
|
|
39
|
+
default_config :build_options, nil
|
|
41
40
|
default_config :cap_add, nil
|
|
42
41
|
default_config :cap_drop, nil
|
|
43
|
-
default_config :
|
|
44
|
-
default_config :
|
|
42
|
+
default_config :disable_upstart, true
|
|
43
|
+
default_config :env_variables, nil
|
|
44
|
+
default_config :isolation, nil
|
|
45
|
+
default_config :interactive, false
|
|
46
|
+
default_config :private_key, File.join(Dir.pwd, '.kitchen', 'docker_id_rsa')
|
|
47
|
+
default_config :privileged, false
|
|
48
|
+
default_config :public_key, File.join(Dir.pwd, '.kitchen', 'docker_id_rsa.pub')
|
|
49
|
+
default_config :publish_all, false
|
|
45
50
|
default_config :remove_images, false
|
|
46
|
-
default_config :
|
|
47
|
-
|
|
48
|
-
default_config :username, 'kitchen'
|
|
51
|
+
default_config :run_options, nil
|
|
52
|
+
default_config :security_opt, nil
|
|
49
53
|
default_config :tls, false
|
|
50
|
-
default_config :tls_verify, false
|
|
51
54
|
default_config :tls_cacert, nil
|
|
52
55
|
default_config :tls_cert, nil
|
|
53
56
|
default_config :tls_key, nil
|
|
54
|
-
default_config :
|
|
55
|
-
default_config :
|
|
56
|
-
default_config :
|
|
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
|
|
57
|
+
default_config :tls_verify, false
|
|
58
|
+
default_config :tty, false
|
|
59
|
+
default_config :use_cache, true
|
|
60
60
|
default_config :use_internal_docker_network, false
|
|
61
|
-
|
|
62
61
|
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
|
|
62
|
+
default_config :wait_for_transport, true
|
|
73
63
|
|
|
74
64
|
default_config :build_context do |driver|
|
|
75
65
|
!driver.remote_socket?
|
|
76
66
|
end
|
|
77
67
|
|
|
68
|
+
default_config :image do |driver|
|
|
69
|
+
driver.default_image
|
|
70
|
+
end
|
|
71
|
+
|
|
78
72
|
default_config :instance_name do |driver|
|
|
79
73
|
# Borrowed from kitchen-rackspace
|
|
80
74
|
[
|
|
@@ -82,368 +76,90 @@ module Kitchen
|
|
|
82
76
|
(Etc.getlogin || 'nologin').gsub(/\W/, ''),
|
|
83
77
|
Socket.gethostname.gsub(/\W/, '')[0..20],
|
|
84
78
|
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 http://www.docker.io/gettingstarted/'
|
|
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
|
|
79
|
+
].join('-').downcase
|
|
112
80
|
end
|
|
113
81
|
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
}))
|
|
82
|
+
default_config :platform do |driver|
|
|
83
|
+
driver.default_platform
|
|
168
84
|
end
|
|
169
85
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
|
86
|
+
default_config :run_command do |driver|
|
|
87
|
+
if driver.windows_os?
|
|
88
|
+
# Launch arbitrary process to keep the Windows container alive
|
|
89
|
+
# If running in interactive mode, launch powershell.exe instead
|
|
90
|
+
if driver[:interactive]
|
|
91
|
+
'powershell.exe'
|
|
92
|
+
else
|
|
93
|
+
'ping -t localhost'
|
|
184
94
|
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', 'fedora', 'oraclelinux'
|
|
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 'opensuse', 'sles'
|
|
228
|
-
<<-eos
|
|
229
|
-
ENV container docker
|
|
230
|
-
RUN zypper install -y sudo openssh which curl
|
|
231
|
-
RUN [ -f "/etc/ssh/ssh_host_rsa_key" ] || ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key -N ''
|
|
232
|
-
RUN [ -f "/etc/ssh/ssh_host_dsa_key" ] || ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key -N ''
|
|
233
|
-
eos
|
|
234
|
-
when 'arch'
|
|
235
|
-
# See https://bugs.archlinux.org/task/47052 for why we
|
|
236
|
-
# blank out limits.conf.
|
|
237
|
-
<<-eos
|
|
238
|
-
RUN pacman --noconfirm -Sy archlinux-keyring
|
|
239
|
-
RUN pacman-db-upgrade
|
|
240
|
-
RUN pacman --noconfirm -Syu openssl openssh sudo curl
|
|
241
|
-
RUN [ -f "/etc/ssh/ssh_host_rsa_key" ] || ssh-keygen -A -t rsa -f /etc/ssh/ssh_host_rsa_key
|
|
242
|
-
RUN [ -f "/etc/ssh/ssh_host_dsa_key" ] || ssh-keygen -A -t dsa -f /etc/ssh/ssh_host_dsa_key
|
|
243
|
-
RUN echo >/etc/security/limits.conf
|
|
244
|
-
eos
|
|
245
|
-
when 'gentoo'
|
|
246
|
-
<<-eos
|
|
247
|
-
RUN emerge --sync
|
|
248
|
-
RUN emerge net-misc/openssh app-admin/sudo
|
|
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
|
-
eos
|
|
252
|
-
when 'gentoo-paludis'
|
|
253
|
-
<<-eos
|
|
254
|
-
RUN cave sync
|
|
255
|
-
RUN cave resolve -zx net-misc/openssh app-admin/sudo
|
|
256
|
-
RUN [ -f "/etc/ssh/ssh_host_rsa_key" ] || ssh-keygen -A -t rsa -f /etc/ssh/ssh_host_rsa_key
|
|
257
|
-
RUN [ -f "/etc/ssh/ssh_host_dsa_key" ] || ssh-keygen -A -t dsa -f /etc/ssh/ssh_host_dsa_key
|
|
258
|
-
eos
|
|
259
|
-
else
|
|
260
|
-
raise ActionFailed,
|
|
261
|
-
"Unknown platform '#{config[:platform]}'"
|
|
262
|
-
end
|
|
263
|
-
|
|
264
|
-
username = config[:username]
|
|
265
|
-
public_key = IO.read(config[:public_key]).strip
|
|
266
|
-
homedir = username == 'root' ? '/root' : "/home/#{username}"
|
|
267
|
-
|
|
268
|
-
base = <<-eos
|
|
269
|
-
RUN if ! getent passwd #{username}; then \
|
|
270
|
-
useradd -d #{homedir} -m -s /bin/bash -p '*' #{username}; \
|
|
271
|
-
fi
|
|
272
|
-
RUN echo "#{username} ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
|
|
273
|
-
RUN echo "Defaults !requiretty" >> /etc/sudoers
|
|
274
|
-
RUN mkdir -p #{homedir}/.ssh
|
|
275
|
-
RUN chown -R #{username} #{homedir}/.ssh
|
|
276
|
-
RUN chmod 0700 #{homedir}/.ssh
|
|
277
|
-
RUN touch #{homedir}/.ssh/authorized_keys
|
|
278
|
-
RUN chown #{username} #{homedir}/.ssh/authorized_keys
|
|
279
|
-
RUN chmod 0600 #{homedir}/.ssh/authorized_keys
|
|
280
|
-
RUN mkdir -p /run/sshd
|
|
281
|
-
eos
|
|
282
|
-
custom = ''
|
|
283
|
-
Array(config[:provision_command]).each do |cmd|
|
|
284
|
-
custom << "RUN #{cmd}\n"
|
|
285
|
-
end
|
|
286
|
-
ssh_key = "RUN echo #{Shellwords.escape(public_key)} >> #{homedir}/.ssh/authorized_keys"
|
|
287
|
-
# Empty string to ensure the file ends with a newline.
|
|
288
|
-
[from, env_variables, platform, base, custom, ssh_key, ''].join("\n")
|
|
289
|
-
end
|
|
290
|
-
|
|
291
|
-
def dockerfile
|
|
292
|
-
if config[:dockerfile]
|
|
293
|
-
template = IO.read(File.expand_path(config[:dockerfile]))
|
|
294
|
-
context = DockerERBContext.new(config.to_hash)
|
|
295
|
-
ERB.new(template).result(context.get_binding)
|
|
296
95
|
else
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
end
|
|
300
|
-
|
|
301
|
-
def parse_image_id(output)
|
|
302
|
-
output.each_line do |line|
|
|
303
|
-
if line =~ /image id|build successful|successfully built/i
|
|
304
|
-
return line.split(/\s+/).last
|
|
305
|
-
end
|
|
96
|
+
'/usr/sbin/sshd -D -o UseDNS=no -o UsePAM=no -o PasswordAuthentication=yes '\
|
|
97
|
+
'-o UsePrivilegeSeparation=no -o PidFile=/tmp/sshd.pid'
|
|
306
98
|
end
|
|
307
|
-
raise ActionFailed,
|
|
308
|
-
'Could not parse Docker build output for image ID'
|
|
309
99
|
end
|
|
310
100
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
cmd << " #{extra_build_options}" unless extra_build_options.empty?
|
|
316
|
-
dockerfile_contents = dockerfile
|
|
317
|
-
build_context = config[:build_context] ? '.' : '-'
|
|
318
|
-
file = Tempfile.new('Dockerfile-kitchen', Dir.pwd)
|
|
319
|
-
output = begin
|
|
320
|
-
file.write(dockerfile)
|
|
321
|
-
file.close
|
|
322
|
-
docker_command("#{cmd} -f #{Shellwords.escape(dockerfile_path(file))} #{build_context}", :input => dockerfile_contents)
|
|
323
|
-
ensure
|
|
324
|
-
file.close unless file.closed?
|
|
325
|
-
file.unlink
|
|
326
|
-
end
|
|
327
|
-
parse_image_id(output)
|
|
101
|
+
default_config :socket do |driver|
|
|
102
|
+
socket = 'unix:///var/run/docker.sock'
|
|
103
|
+
socket = 'npipe:////./pipe/docker_engine' if driver.windows_os?
|
|
104
|
+
ENV['DOCKER_HOST'] || socket
|
|
328
105
|
end
|
|
329
106
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
107
|
+
default_config :username do |driver|
|
|
108
|
+
# Return nil to prevent username from being added to Docker
|
|
109
|
+
# command line args for Windows if a username was not specified
|
|
110
|
+
if driver.windows_os?
|
|
111
|
+
nil
|
|
112
|
+
else
|
|
113
|
+
'kitchen'
|
|
335
114
|
end
|
|
336
|
-
container_id
|
|
337
115
|
end
|
|
338
116
|
|
|
339
|
-
def
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
Array(config[:add_host]).each {|host, ip| cmd << " --add-host=#{host}:#{ip}"}
|
|
344
|
-
Array(config[:volume]).each {|volume| cmd << " -v #{volume}"}
|
|
345
|
-
Array(config[:volumes_from]).each {|container| cmd << " --volumes-from #{container}"}
|
|
346
|
-
Array(config[:links]).each {|link| cmd << " --link #{link}"}
|
|
347
|
-
Array(config[:devices]).each {|device| cmd << " --device #{device}"}
|
|
348
|
-
cmd << " --name #{config[:instance_name]}" if config[:instance_name]
|
|
349
|
-
cmd << " -P" if config[:publish_all]
|
|
350
|
-
cmd << " -h #{config[:hostname]}" if config[:hostname]
|
|
351
|
-
cmd << " -m #{config[:memory]}" if config[:memory]
|
|
352
|
-
cmd << " -c #{config[:cpu]}" if config[:cpu]
|
|
353
|
-
cmd << " -e http_proxy=#{config[:http_proxy]}" if config[:http_proxy]
|
|
354
|
-
cmd << " -e https_proxy=#{config[:https_proxy]}" if config[:https_proxy]
|
|
355
|
-
cmd << " --privileged" if config[:privileged]
|
|
356
|
-
Array(config[:cap_add]).each {|cap| cmd << " --cap-add=#{cap}"} if config[:cap_add]
|
|
357
|
-
Array(config[:cap_drop]).each {|cap| cmd << " --cap-drop=#{cap}"} if config[:cap_drop]
|
|
358
|
-
Array(config[:security_opt]).each {|opt| cmd << " --security-opt=#{opt}"} if config[:security_opt]
|
|
359
|
-
extra_run_options = config_to_options(config[:run_options])
|
|
360
|
-
cmd << " #{extra_run_options}" unless extra_run_options.empty?
|
|
361
|
-
cmd << " #{image_id} #{config[:run_command]}"
|
|
362
|
-
cmd
|
|
363
|
-
end
|
|
364
|
-
|
|
365
|
-
def run_container(state)
|
|
366
|
-
cmd = build_run_command(state[:image_id])
|
|
367
|
-
output = docker_command(cmd)
|
|
368
|
-
parse_container_id(output)
|
|
117
|
+
def verify_dependencies
|
|
118
|
+
run_command("#{config[:binary]} >> #{dev_null} 2>&1", quiet: true, use_sudo: config[:use_sudo])
|
|
119
|
+
rescue
|
|
120
|
+
raise UserError, 'You must first install the Docker CLI tool https://www.docker.com/get-started'
|
|
369
121
|
end
|
|
370
122
|
|
|
371
|
-
def
|
|
372
|
-
|
|
373
|
-
end
|
|
123
|
+
def create(state)
|
|
124
|
+
container.create(state)
|
|
374
125
|
|
|
375
|
-
|
|
376
|
-
state[:image_id] && !!docker_command("docker inspect --type=image #{state[:image_id]}") rescue false
|
|
126
|
+
wait_for_transport(state)
|
|
377
127
|
end
|
|
378
128
|
|
|
379
|
-
def
|
|
380
|
-
|
|
381
|
-
_host, port = output.split(':')
|
|
382
|
-
port.to_i
|
|
383
|
-
rescue
|
|
384
|
-
raise ActionFailed,
|
|
385
|
-
'Could not parse Docker port output for container SSH port'
|
|
386
|
-
end
|
|
129
|
+
def destroy(state)
|
|
130
|
+
container.destroy(state)
|
|
387
131
|
end
|
|
388
132
|
|
|
389
|
-
def
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
133
|
+
def wait_for_transport(state)
|
|
134
|
+
if config[:wait_for_transport]
|
|
135
|
+
instance.transport.connection(state) do |conn|
|
|
136
|
+
conn.wait_until_ready
|
|
393
137
|
end
|
|
394
|
-
output = docker_command("port #{state[:container_id]} 22/tcp")
|
|
395
|
-
parse_container_ssh_port(output)
|
|
396
|
-
rescue
|
|
397
|
-
raise ActionFailed,
|
|
398
|
-
'Docker reports container has no ssh port mapped'
|
|
399
138
|
end
|
|
400
139
|
end
|
|
401
140
|
|
|
402
|
-
def
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
docker_command(cmd).strip
|
|
407
|
-
rescue
|
|
408
|
-
raise ActionFailed,
|
|
409
|
-
'Error getting internal IP of Docker container'
|
|
141
|
+
def default_image
|
|
142
|
+
platform, release = instance.platform.name.split('-')
|
|
143
|
+
if platform == 'centos' && release
|
|
144
|
+
release = 'centos' + release.split('.').first
|
|
410
145
|
end
|
|
146
|
+
release ? [platform, release].join(':') : platform
|
|
411
147
|
end
|
|
412
148
|
|
|
413
|
-
def
|
|
414
|
-
|
|
415
|
-
docker_command("stop -t 0 #{container_id}")
|
|
416
|
-
docker_command("rm #{container_id}")
|
|
417
|
-
end
|
|
418
|
-
|
|
419
|
-
def rm_image(state)
|
|
420
|
-
image_id = state[:image_id]
|
|
421
|
-
docker_command("rmi #{image_id}")
|
|
149
|
+
def default_platform
|
|
150
|
+
instance.platform.name.split('-').first
|
|
422
151
|
end
|
|
423
152
|
|
|
424
|
-
|
|
425
|
-
# command line string for use with Docker.
|
|
426
|
-
#
|
|
427
|
-
# @since 2.5.0
|
|
428
|
-
# @param config [nil, String, Array, Hash] Config data to convert.
|
|
429
|
-
# @return [String]
|
|
430
|
-
def config_to_options(config)
|
|
431
|
-
case config
|
|
432
|
-
when nil
|
|
433
|
-
''
|
|
434
|
-
when String
|
|
435
|
-
config
|
|
436
|
-
when Array
|
|
437
|
-
config.map {|c| config_to_options(c) }.join(' ')
|
|
438
|
-
when Hash
|
|
439
|
-
config.map {|k, v| Array(v).map {|c| "--#{k}=#{Shellwords.escape(c)}" }.join(' ') }.join(' ')
|
|
440
|
-
end
|
|
441
|
-
end
|
|
153
|
+
protected
|
|
442
154
|
|
|
443
|
-
def
|
|
444
|
-
|
|
155
|
+
def container
|
|
156
|
+
@container ||= if windows_os?
|
|
157
|
+
Kitchen::Docker::Container::Windows.new(config)
|
|
158
|
+
else
|
|
159
|
+
Kitchen::Docker::Container::Linux.new(config)
|
|
160
|
+
end
|
|
161
|
+
@container
|
|
445
162
|
end
|
|
446
|
-
|
|
447
163
|
end
|
|
448
164
|
end
|
|
449
165
|
end
|
|
@@ -0,0 +1,111 @@
|
|
|
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
|
+
|
|
16
|
+
require_relative '../docker/container/linux'
|
|
17
|
+
require_relative '../docker/container/windows'
|
|
18
|
+
|
|
19
|
+
require_relative '../docker/helpers/inspec_helper'
|
|
20
|
+
|
|
21
|
+
require_relative '../../docker/version.rb'
|
|
22
|
+
require_relative '../../train/docker.rb'
|
|
23
|
+
|
|
24
|
+
module Kitchen
|
|
25
|
+
module Transport
|
|
26
|
+
class Docker < Kitchen::Transport::Base
|
|
27
|
+
class DockerFailed < TransportFailed; end
|
|
28
|
+
|
|
29
|
+
kitchen_transport_api_version 1
|
|
30
|
+
plugin_version Kitchen::VERSION
|
|
31
|
+
|
|
32
|
+
default_config :binary, 'docker'
|
|
33
|
+
default_config :env_variables, nil
|
|
34
|
+
default_config :interactive, false
|
|
35
|
+
default_config :privileged, false
|
|
36
|
+
default_config :tls, false
|
|
37
|
+
default_config :tls_cacert, nil
|
|
38
|
+
default_config :tls_cert, nil
|
|
39
|
+
default_config :tls_key, nil
|
|
40
|
+
default_config :tls_verify, false
|
|
41
|
+
default_config :tty, false
|
|
42
|
+
default_config :working_dir, nil
|
|
43
|
+
|
|
44
|
+
default_config :socket do |transport|
|
|
45
|
+
socket = 'unix:///var/run/docker.sock'
|
|
46
|
+
socket = 'npipe:////./pipe/docker_engine' if transport.windows_os?
|
|
47
|
+
ENV['DOCKER_HOST'] || socket
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
default_config :temp_dir do |transport|
|
|
51
|
+
if transport.windows_os?
|
|
52
|
+
'$env:TEMP'
|
|
53
|
+
else
|
|
54
|
+
'/tmp'
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
default_config :username do |transport|
|
|
59
|
+
# Return an empty string to prevent username from being added to Docker
|
|
60
|
+
# command line args for Windows if a username was not specified
|
|
61
|
+
if transport.windows_os?
|
|
62
|
+
nil
|
|
63
|
+
else
|
|
64
|
+
'kitchen'
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def connection(state, &block)
|
|
69
|
+
options = config.to_hash.merge(state)
|
|
70
|
+
options[:platform] = instance.platform.name
|
|
71
|
+
|
|
72
|
+
# Set value for DOCKER_HOST environment variable for the docker-api gem
|
|
73
|
+
# This allows Windows systems to use the TCP socket for the InSpec verifier
|
|
74
|
+
# See the lib/docker.rb file here: https://github.com/swipely/docker-api/blob/master/lib/docker.rb
|
|
75
|
+
# default_socket_url is set to a Unix socket and env_url requires an environment variable to be set
|
|
76
|
+
ENV['DOCKER_HOST'] = options[:socket] if !options[:socket].nil? && ENV['DOCKER_HOST'].nil?
|
|
77
|
+
|
|
78
|
+
Kitchen::Transport::Docker::Connection.new(options, &block)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
class Connection < Kitchen::Transport::Docker::Connection
|
|
82
|
+
# Include the InSpec patches to be able to execute tests on Windows containers
|
|
83
|
+
include Kitchen::Docker::Helpers::InspecHelper
|
|
84
|
+
|
|
85
|
+
def execute(command)
|
|
86
|
+
return if command.nil?
|
|
87
|
+
|
|
88
|
+
debug("[Docker] Executing command: #{command}")
|
|
89
|
+
info("[Docker] Executing command on container")
|
|
90
|
+
|
|
91
|
+
container.execute(command)
|
|
92
|
+
rescue => e
|
|
93
|
+
raise DockerFailed, "Docker failed to execute command on container. Error Details: #{e}"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def upload(locals, remote)
|
|
97
|
+
container.upload(locals, remote)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def container
|
|
101
|
+
@container ||= if @options[:platform].include?('windows')
|
|
102
|
+
Kitchen::Docker::Container::Windows.new(@options)
|
|
103
|
+
else
|
|
104
|
+
Kitchen::Docker::Container::Linux.new(@options)
|
|
105
|
+
end
|
|
106
|
+
@container
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|