kitchen-docker 3.3.4 → 3.4.1
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/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +26 -0
- data/README.md +76 -5
- data/kitchen-docker.gemspec +1 -1
- data/lib/kitchen/docker/container/linux.rb +4 -1
- data/lib/kitchen/docker/container/windows.rb +5 -1
- data/lib/kitchen/docker/docker_version.rb +1 -1
- data/lib/kitchen/docker/helpers/cli_helper.rb +29 -1
- data/lib/kitchen/docker/helpers/container_helper.rb +82 -5
- data/lib/kitchen/docker/helpers/image_helper.rb +29 -1
- data/lib/kitchen/driver/docker.rb +131 -0
- data/lib/kitchen/transport/docker.rb +10 -2
- data/spec/cli_helper_spec.rb +49 -0
- data/spec/container_helper_spec.rb +156 -4
- data/spec/docker_spec.rb +171 -0
- data/spec/image_helper_spec.rb +69 -0
- data/spec/linux_container_spec.rb +27 -0
- data/spec/support/docker_output.rb +9 -0
- data/spec/transport_docker_spec.rb +20 -0
- data/spec/windows_container_spec.rb +54 -0
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2d994f876f76b4950f4f85521fba2ed15fe2ed54d06b9993d464a578f2fd9c69
|
|
4
|
+
data.tar.gz: d942290a8f115d5b0937421b747ec3f413e180fd55cfc8af245524b8337b27d5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7db4fee864798aa758a3e253f433db4e120d7247e8b5125e34764cf393f4836c9885eb3df2eff767501a6910c2f895b4efab467b8a24f7e1ad6120cc58eeede2
|
|
7
|
+
data.tar.gz: f80f60d52168ea008378af1cb66530136bf1e53b6474b75877a9275ff336f0ae632f754833f3e3db7aadd2c0f3910d64269fd39be0bb3a90baf5367d6708f115
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,32 @@ Future CHANGELOG notes will be in GitHub release notes
|
|
|
6
6
|
|
|
7
7
|
* Docs: document the last four options and split contributor docs ([#463](https://github.com/test-kitchen/kitchen-docker/pull/463)) ([4818bce](https://github.com/test-kitchen/kitchen-docker/commit/4818bce))
|
|
8
8
|
|
|
9
|
+
## [3.4.1](https://github.com/test-kitchen/kitchen-docker/compare/v3.4.0...v3.4.1) (2026-08-30)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
* quote container paths interpolated into docker exec command lines ([#496](https://github.com/test-kitchen/kitchen-docker/issues/496)) ([ae3a754](https://github.com/test-kitchen/kitchen-docker/commit/ae3a754046cdb7c6d9fdb8622a6cef818643ad56))
|
|
15
|
+
* read the image id from a quiet build ([#489](https://github.com/test-kitchen/kitchen-docker/issues/489)) ([ddc58b5](https://github.com/test-kitchen/kitchen-docker/commit/ddc58b50a16b7236ed047071228f5641092060a1))
|
|
16
|
+
* report this gem's own version to kitchen diagnose ([#490](https://github.com/test-kitchen/kitchen-docker/issues/490)) ([e28171d](https://github.com/test-kitchen/kitchen-docker/commit/e28171d268ab0a4950c0d553fcee12688663e626))
|
|
17
|
+
|
|
18
|
+
## [3.4.0](https://github.com/test-kitchen/kitchen-docker/compare/v3.3.5...v3.4.0) (2026-08-23)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Features
|
|
22
|
+
|
|
23
|
+
* implement the driver's package, doctor, and status hooks ([#491](https://github.com/test-kitchen/kitchen-docker/issues/491)) ([1366895](https://github.com/test-kitchen/kitchen-docker/commit/13668957b5b802f91c30aa7e34fc1b9f761e3b8d))
|
|
24
|
+
|
|
25
|
+
## [3.3.5](https://github.com/test-kitchen/kitchen-docker/compare/v3.3.4...v3.3.5) (2026-08-23)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
### Bug Fixes
|
|
29
|
+
|
|
30
|
+
* report when docker cp silently writes nothing ([#487](https://github.com/test-kitchen/kitchen-docker/issues/487)) ([1bc62ba](https://github.com/test-kitchen/kitchen-docker/commit/1bc62ba615d25d402a97775dbf6ac252af5cbdd8))
|
|
31
|
+
* require test-kitchen 3.0 or newer ([#492](https://github.com/test-kitchen/kitchen-docker/issues/492)) ([0c64225](https://github.com/test-kitchen/kitchen-docker/commit/0c64225dd22aca8a2a32e6f61a6d648bcd8c0b2b))
|
|
32
|
+
* run every docker command through sudo when use_sudo is set ([#485](https://github.com/test-kitchen/kitchen-docker/issues/485)) ([2481aad](https://github.com/test-kitchen/kitchen-docker/commit/2481aadfc6415e5b7ed597e1095d0fe8db89afbb))
|
|
33
|
+
* stop printing the image inspect JSON on destroy ([#488](https://github.com/test-kitchen/kitchen-docker/issues/488)) ([0011558](https://github.com/test-kitchen/kitchen-docker/commit/00115588236900d9ef3275a73892f9d3bdb4e3c8))
|
|
34
|
+
|
|
9
35
|
## [3.3.4](https://github.com/test-kitchen/kitchen-docker/compare/v3.3.3...v3.3.4) (2026-08-23)
|
|
10
36
|
|
|
11
37
|
|
data/README.md
CHANGED
|
@@ -28,6 +28,7 @@ machine to boot.
|
|
|
28
28
|
* [Driver configuration](#driver-configuration)
|
|
29
29
|
* [Transport configuration](#transport-configuration)
|
|
30
30
|
* [Logging into a container](#logging-into-a-container)
|
|
31
|
+
* [Other commands](#other-commands)
|
|
31
32
|
* [Examples](#examples)
|
|
32
33
|
* [Using with Chef](#using-with-chef)
|
|
33
34
|
* [Troubleshooting](#troubleshooting)
|
|
@@ -204,9 +205,10 @@ platforms:
|
|
|
204
205
|
| `dockerfile` | *(none)* | Path to your own Dockerfile, used instead of the generated one. Rendered as [ERB](#using-a-custom-dockerfile). |
|
|
205
206
|
| `build_context` | `true` locally, `false` for a remote daemon | Send the working directory to the daemon as build context. Required for `ADD` and `COPY`; slow against a remote daemon. |
|
|
206
207
|
| `build_options` | *(none)* | Extra flags for `docker build`, as a string or a map. |
|
|
207
|
-
| `build_tempdir` | working directory |
|
|
208
|
+
| `build_tempdir` | working directory | Directory the generated Dockerfile is written to. A relative path is resolved against the working directory, and the path must be inside the build context, since `docker build -f` is given it relative to the working directory. |
|
|
208
209
|
| `use_cache` | `true` | Use Docker's build cache. `false` adds `--no-cache`. |
|
|
209
210
|
| `remove_images` | `false` | Remove the built image on `kitchen destroy`. |
|
|
211
|
+
| `package_name` | the instance name, tagged `latest` | Image `kitchen package` commits the container to. |
|
|
210
212
|
| `docker_platform` | *(none)* | Target architecture, passed as `--platform` to both build and run — e.g. `linux/arm64`. |
|
|
211
213
|
|
|
212
214
|
### Provisioning the image
|
|
@@ -235,7 +237,9 @@ platforms:
|
|
|
235
237
|
| `tty` | `false` | Pass `-t`, allocating a pseudo-TTY. |
|
|
236
238
|
| `env_variables` | *(none)* | Environment variables set in the container, as a map. |
|
|
237
239
|
| `wait_for_transport` | `true` | Wait for the transport to answer before converging. Set `false` for containers that do not stay up. |
|
|
238
|
-
|
|
240
|
+
|
|
241
|
+
The container itself is always started detached — see [`detach`](#transport-configuration)
|
|
242
|
+
under the transport for how provisioner commands are run.
|
|
239
243
|
|
|
240
244
|
### Networking
|
|
241
245
|
|
|
@@ -284,6 +288,7 @@ Each of these accepts a single value or a list.
|
|
|
284
288
|
| `binary` | `docker` | Docker CLI to invoke — e.g. `docker.io`, or an absolute path. |
|
|
285
289
|
| `socket` | `$DOCKER_HOST`, else `unix:///var/run/docker.sock` (`npipe:////./pipe/docker_engine` on Windows) | Daemon to talk to. A `tcp://` value also supplies the host used for SSH to the container. |
|
|
286
290
|
| `use_sudo` | `false` | Run every `docker` command through `sudo`. |
|
|
291
|
+
| `sudo_command` | `sudo -E` | The command `use_sudo` prefixes, for hosts that use something else (`doas`, say). |
|
|
287
292
|
| `tls` | `false` | Use TLS when connecting. |
|
|
288
293
|
| `tls_verify` | `false` | Verify the daemon's certificate. |
|
|
289
294
|
| `tls_cacert` | *(none)* | Path to the CA certificate. |
|
|
@@ -311,14 +316,17 @@ These options go under `transport:`, not `driver:`.
|
|
|
311
316
|
| `working_dir` | *(none)* | Working directory inside the container (`-w`). |
|
|
312
317
|
| `temp_dir` | `/tmp`, or `$env:TEMP` on Windows | Directory used to stage uploaded files. |
|
|
313
318
|
| `env_variables` | *(none)* | Environment variables for each command. |
|
|
319
|
+
| `detach` | `false` | Run provisioner commands with `docker exec -d`, returning immediately instead of waiting for them to finish. `kitchen login` ignores it, so the shell stays usable. |
|
|
314
320
|
| `privileged` | `false` | Run commands with `--privileged`. |
|
|
315
321
|
| `interactive` | `false` | Pass `-i`. |
|
|
316
322
|
| `tty` | `false` | Pass `-t`. |
|
|
323
|
+
| `use_sudo` | `false` | Run every `docker` command through `sudo`. |
|
|
324
|
+
| `sudo_command` | `sudo -E` | The command `use_sudo` prefixes. |
|
|
317
325
|
| `tls`, `tls_verify`, `tls_cacert`, `tls_cert`, `tls_key` | as for the driver | TLS settings for the daemon connection. |
|
|
318
326
|
|
|
319
327
|
The driver and transport each read their own copy of `binary`, `socket`,
|
|
320
|
-
`username`, and the TLS settings. If you point one at a non-default
|
|
321
|
-
|
|
328
|
+
`username`, `use_sudo`, and the TLS settings. If you point one at a non-default
|
|
329
|
+
daemon, or need `sudo` to reach it, configure the other the same way.
|
|
322
330
|
|
|
323
331
|
## Logging into a container
|
|
324
332
|
|
|
@@ -334,6 +342,68 @@ starts `powershell`. The transport's `username`, `working_dir`,
|
|
|
334
342
|
`env_variables`, and `privileged` settings are honoured, so the shell matches
|
|
335
343
|
the environment the provisioner ran in.
|
|
336
344
|
|
|
345
|
+
## Other commands
|
|
346
|
+
|
|
347
|
+
### `kitchen package`
|
|
348
|
+
|
|
349
|
+
Commits the container to a Docker image, so a converged instance can be kept
|
|
350
|
+
and reused:
|
|
351
|
+
|
|
352
|
+
```sh
|
|
353
|
+
kitchen package default-ubuntu-2404
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
```text
|
|
357
|
+
-----> Packaging remote instance
|
|
358
|
+
[Docker] Packaged default-ubuntu-2404 as default-ubuntu-2404:latest (sha256:1f51c590...)
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
The image is named after the instance. Set `package_name` for something else:
|
|
362
|
+
|
|
363
|
+
```yaml
|
|
364
|
+
driver:
|
|
365
|
+
name: docker
|
|
366
|
+
package_name: myapp/under-test:candidate
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
Run `docker save` against the result if you want a tarball.
|
|
370
|
+
|
|
371
|
+
### `kitchen doctor`
|
|
372
|
+
|
|
373
|
+
Checks that the daemon is reachable and that the configuration points at
|
|
374
|
+
things that exist:
|
|
375
|
+
|
|
376
|
+
```sh
|
|
377
|
+
kitchen doctor default-ubuntu-2404
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
```text
|
|
381
|
+
-----> The doctor is in
|
|
382
|
+
Docker daemon at unix:///var/run/docker.sock is reachable, running 29.7.2.
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
It reports every problem it finds rather than stopping at the first, and exits
|
|
386
|
+
non-zero when there is one: a daemon it cannot reach, a `tls_cacert`,
|
|
387
|
+
`tls_cert`, `tls_key`, or `dockerfile` naming a path that is not there, or a
|
|
388
|
+
state file naming a container the daemon no longer has.
|
|
389
|
+
|
|
390
|
+
### `kitchen list --live`
|
|
391
|
+
|
|
392
|
+
Asks Docker what state each container is actually in, rather than reporting
|
|
393
|
+
only the last action Test Kitchen took:
|
|
394
|
+
|
|
395
|
+
```sh
|
|
396
|
+
kitchen list --live
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
```text
|
|
400
|
+
Instance Driver Provisioner Verifier Transport Last Action Last Error Live Status
|
|
401
|
+
default-ubuntu-2404 Docker Shell Dummy Docker Created <None> running
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
`running`, `stopped`, `gone` (the state file names a container the daemon does
|
|
405
|
+
not have), or `not created`.
|
|
406
|
+
|
|
337
407
|
## Examples
|
|
338
408
|
|
|
339
409
|
### Testing a systemd service
|
|
@@ -534,7 +604,8 @@ bootstrap. Set it to a supported family, or supply your own
|
|
|
534
604
|
to `false` against a remote daemon.
|
|
535
605
|
|
|
536
606
|
**Permission denied talking to the daemon.** Either add your user to the
|
|
537
|
-
`docker` group, or set `use_sudo: true
|
|
607
|
+
`docker` group, or set `use_sudo: true` under **both** `driver:` and
|
|
608
|
+
`transport:` -- the transport runs its own `docker exec` and `docker cp`.
|
|
538
609
|
|
|
539
610
|
**Anything else.** Run with `-l debug`:
|
|
540
611
|
|
data/kitchen-docker.gemspec
CHANGED
|
@@ -84,7 +84,10 @@ module Kitchen
|
|
|
84
84
|
debug("Executing temp script #{remote_path}/#{filename} on container")
|
|
85
85
|
remote_path = replace_env_variables(@config, remote_path)
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
# Escaped because the exec command line is assembled as one string:
|
|
88
|
+
# a temp_dir with a space in it was split before docker saw it, and
|
|
89
|
+
# bash was handed the first half of the directory as the script.
|
|
90
|
+
container_exec(@config, "/bin/bash #{Shellwords.escape("#{remote_path}/#{filename}")}")
|
|
88
91
|
rescue => e
|
|
89
92
|
raise "Failed to execute command on Linux container. #{e}"
|
|
90
93
|
ensure
|
|
@@ -69,7 +69,11 @@ module Kitchen
|
|
|
69
69
|
# Replace any environment variables used in the path and execute script file
|
|
70
70
|
debug("Executing temp script #{remote_path}\\#{filename} on container")
|
|
71
71
|
remote_path = replace_env_variables(@config, remote_path)
|
|
72
|
-
|
|
72
|
+
# Quoted because PowerShell's -File takes exactly one argument. An
|
|
73
|
+
# unquoted path with a space in it -- which $env:TEMP has whenever the
|
|
74
|
+
# user's name does -- left PowerShell looking for a script named
|
|
75
|
+
# after the first word of the directory.
|
|
76
|
+
cmd = build_powershell_command(%{-File "#{remote_path}\\#{filename}"})
|
|
73
77
|
|
|
74
78
|
container_exec(@config, cmd)
|
|
75
79
|
rescue => e
|
|
@@ -64,11 +64,39 @@ module Kitchen
|
|
|
64
64
|
docker << " --tlscacert=#{shell_escape(config[:tls_cacert])}" if config[:tls_cacert]
|
|
65
65
|
docker << " --tlscert=#{shell_escape(config[:tls_cert])}" if config[:tls_cert]
|
|
66
66
|
docker << " --tlskey=#{shell_escape(config[:tls_key])}" if config[:tls_key]
|
|
67
|
+
options = docker_sudo_opts(options)
|
|
67
68
|
logger.debug("docker_command: #{docker} #{cmd} shell_opts: #{docker_shell_opts(options)}")
|
|
68
69
|
run_command("#{docker} #{cmd}", docker_shell_opts(options))
|
|
69
70
|
end
|
|
70
71
|
# rubocop:enable Metrics/AbcSize
|
|
71
72
|
|
|
73
|
+
# Adds the sudo options {#run_command} reads, when +use_sudo+ is set.
|
|
74
|
+
#
|
|
75
|
+
# Sudo is a property of the call rather than of the configuration as far
|
|
76
|
+
# as the shell-out layer is concerned: it reads +:use_sudo+ from the
|
|
77
|
+
# options hash it is handed and knows nothing about +config+. So a
|
|
78
|
+
# docker command only runs through sudo if these are passed to it.
|
|
79
|
+
#
|
|
80
|
+
# Without this, +use_sudo+ reached exactly one command -- the
|
|
81
|
+
# `docker` probe in +verify_dependencies+ -- and every build, run,
|
|
82
|
+
# exec, cp, and rm still ran as the invoking user. On a host where the
|
|
83
|
+
# daemon socket needs root, that made the documented answer to
|
|
84
|
+
# "permission denied while trying to connect to the Docker daemon
|
|
85
|
+
# socket" do nothing at all.
|
|
86
|
+
#
|
|
87
|
+
# A copy is returned rather than the hash being edited in place, so a
|
|
88
|
+
# caller that reuses its options hash does not accumulate sudo.
|
|
89
|
+
#
|
|
90
|
+
# @param options [Hash] shell-out options
|
|
91
|
+
# @return [Hash] those options, with sudo added when configured
|
|
92
|
+
def docker_sudo_opts(options = {})
|
|
93
|
+
return options unless config[:use_sudo]
|
|
94
|
+
|
|
95
|
+
options = options.merge(use_sudo: true)
|
|
96
|
+
options[:sudo_command] = config[:sudo_command] if config[:sudo_command]
|
|
97
|
+
options
|
|
98
|
+
end
|
|
99
|
+
|
|
72
100
|
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
73
101
|
|
|
74
102
|
# Runs a shell command, returning stderr as well as stdout.
|
|
@@ -79,7 +107,7 @@ module Kitchen
|
|
|
79
107
|
# @param cmd [String] the command to run
|
|
80
108
|
# @param options [Hash] shell-out options
|
|
81
109
|
# @return [String] combined stdout and stderr
|
|
82
|
-
# @raise [Kitchen::ShellCommandFailed] if the command exits non-zero
|
|
110
|
+
# @raise [Kitchen::ShellOut::ShellCommandFailed] if the command exits non-zero
|
|
83
111
|
def run_command(cmd, options = {})
|
|
84
112
|
if options.fetch(:use_sudo, false)
|
|
85
113
|
cmd = "#{options.fetch(:sudo_command, "sudo -E")} #{cmd}"
|
|
@@ -33,6 +33,11 @@ module Kitchen
|
|
|
33
33
|
include Configurable
|
|
34
34
|
include Kitchen::Docker::Helpers::CliHelper
|
|
35
35
|
|
|
36
|
+
# Printed by the probe in {#file_on_container?} when the file is there.
|
|
37
|
+
# A marker is echoed rather than the exit status being read, because a
|
|
38
|
+
# non-zero exit from `docker exec` is raised rather than returned.
|
|
39
|
+
COPIED_MARKER = "kitchen_docker_copied".freeze
|
|
40
|
+
|
|
36
41
|
# Pulls the container id out of `docker run` output.
|
|
37
42
|
#
|
|
38
43
|
# Docker prints ids in short (12) or full (64) hex form, on a line of
|
|
@@ -150,6 +155,17 @@ module Kitchen
|
|
|
150
155
|
|
|
151
156
|
# Creates a directory inside the container, on Linux or Windows.
|
|
152
157
|
#
|
|
158
|
+
# The path is escaped for the shell, because it comes from the
|
|
159
|
+
# transport's +temp_dir+ and a `docker exec` command line is assembled
|
|
160
|
+
# as one string. A directory with a space in it was torn in two before
|
|
161
|
+
# docker ever saw it, and `mkdir -p` obligingly created both halves --
|
|
162
|
+
# neither of them the directory that was asked for. Every upload that
|
|
163
|
+
# followed then went to a path that did not exist.
|
|
164
|
+
#
|
|
165
|
+
# The PowerShell branch already quotes the path itself, and its
|
|
166
|
+
# argument is reassembled by PowerShell rather than split by a shell,
|
|
167
|
+
# so it is left as it is.
|
|
168
|
+
#
|
|
153
169
|
# @param state [Hash] instance state naming the container
|
|
154
170
|
# @param path [String] the directory to create; environment variable
|
|
155
171
|
# references are expanded first
|
|
@@ -157,7 +173,7 @@ module Kitchen
|
|
|
157
173
|
# @raise [RuntimeError] if the directory cannot be created
|
|
158
174
|
def create_dir_on_container(state, path)
|
|
159
175
|
path = replace_env_variables(state, path)
|
|
160
|
-
cmd = "mkdir -p #{path}"
|
|
176
|
+
cmd = "mkdir -p #{Shellwords.escape(path)}"
|
|
161
177
|
|
|
162
178
|
if state[:platform].include?("windows")
|
|
163
179
|
psh = "-Command if(-not (Test-Path '#{path}')) { New-Item -Path '#{path}' -Force }"
|
|
@@ -172,23 +188,84 @@ module Kitchen
|
|
|
172
188
|
|
|
173
189
|
# Copies a local file into the container.
|
|
174
190
|
#
|
|
191
|
+
# The copy is checked afterwards, because `docker cp` cannot write into
|
|
192
|
+
# a mount and does not say so. A destination under a tmpfs or a volume
|
|
193
|
+
# is written to the container's own filesystem layer, which the mount
|
|
194
|
+
# then hides, and docker exits 0 with no output -- so the copy looks
|
|
195
|
+
# like it worked and the file is simply not there.
|
|
196
|
+
#
|
|
197
|
+
# Left unchecked, that surfaces later and somewhere else. Running with
|
|
198
|
+
# `tmpfs: /tmp`, which is how the Docker documentation suggests running
|
|
199
|
+
# systemd, the first sign is the next command failing with
|
|
200
|
+
# `/bin/bash: /tmp/docker-<uuid>.sh: No such file or directory` (#387),
|
|
201
|
+
# which names neither the copy nor the mount.
|
|
202
|
+
#
|
|
175
203
|
# @param state [Hash] instance state naming the container
|
|
176
204
|
# @param local_file [String] source path
|
|
177
205
|
# @param remote_file [String] destination path inside the container
|
|
178
206
|
# @return [String] the command's combined output
|
|
179
|
-
# @raise [RuntimeError] if the copy fails
|
|
207
|
+
# @raise [RuntimeError] if the copy fails, or if it silently wrote
|
|
208
|
+
# nothing
|
|
180
209
|
def copy_file_to_container(state, local_file, remote_file)
|
|
181
210
|
debug("Copying local file #{local_file} to #{remote_file} on container")
|
|
182
211
|
|
|
183
212
|
remote_file = replace_env_variables(state, remote_file)
|
|
184
213
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
214
|
+
cmd = build_copy_command(local_file, "#{state[:container_id]}:#{remote_file}")
|
|
215
|
+
output = docker_command(cmd)
|
|
216
|
+
verify_file_copied(state, local_file, remote_file)
|
|
217
|
+
output
|
|
188
218
|
rescue => e
|
|
189
219
|
raise "Failed to copy file #{local_file} to container. #{e}"
|
|
190
220
|
end
|
|
191
221
|
|
|
222
|
+
# Checks that a copied file arrived, and says why if it did not.
|
|
223
|
+
#
|
|
224
|
+
# Only Linux containers are checked. tmpfs mounts are a Linux container
|
|
225
|
+
# feature, and `docker cp` against a Windows container is a different
|
|
226
|
+
# code path in Docker that this cannot be tried against, so those keep
|
|
227
|
+
# the behaviour they have always had.
|
|
228
|
+
#
|
|
229
|
+
# @param state [Hash] instance state naming the container
|
|
230
|
+
# @param local_file [String] the source that was copied
|
|
231
|
+
# @param remote_file [String] the destination it was copied to
|
|
232
|
+
# @return [void]
|
|
233
|
+
# @raise [Kitchen::ActionFailed] if the file is not there
|
|
234
|
+
def verify_file_copied(state, local_file, remote_file)
|
|
235
|
+
return if state[:platform].to_s.include?("windows")
|
|
236
|
+
return if file_on_container?(state, remote_file, ::File.basename(local_file))
|
|
237
|
+
|
|
238
|
+
raise ActionFailed,
|
|
239
|
+
"docker reported no error copying it to #{remote_file}, but the file is " \
|
|
240
|
+
"not there. `docker cp` cannot write into a mount -- if #{remote_file} is " \
|
|
241
|
+
"a tmpfs or a volume, set the transport's temp_dir and the provisioner's " \
|
|
242
|
+
"root_path to a path that is not."
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Whether a `docker cp` destination now holds the file that was copied.
|
|
246
|
+
#
|
|
247
|
+
# `docker cp SRC CONTAINER:DEST` copies into DEST when DEST is a
|
|
248
|
+
# directory and to DEST otherwise. Which of those happened is only known
|
|
249
|
+
# inside the container, so the choice is made there, in the one command,
|
|
250
|
+
# rather than by asking twice from here.
|
|
251
|
+
#
|
|
252
|
+
# The two paths are passed as arguments to `sh` rather than interpolated
|
|
253
|
+
# into the script, so that nothing in either is read as shell syntax.
|
|
254
|
+
#
|
|
255
|
+
# @param state [Hash] instance state naming the container
|
|
256
|
+
# @param remote_file [String] the destination that was copied to
|
|
257
|
+
# @param basename [String] the source's file name
|
|
258
|
+
# @return [Boolean] whether the file is there
|
|
259
|
+
def file_on_container?(state, remote_file, basename)
|
|
260
|
+
script = 'p="$1"; if [ -d "$p" ]; then p="$p/$2"; fi; ' \
|
|
261
|
+
"if [ -e \"$p\" ]; then echo #{COPIED_MARKER}; fi"
|
|
262
|
+
probe = "/bin/sh -c #{Shellwords.escape(script)} sh " \
|
|
263
|
+
"#{Shellwords.escape(remote_file)} #{Shellwords.escape(basename)}"
|
|
264
|
+
|
|
265
|
+
output = docker_command(build_exec_command(state, probe), suppress_output: !logger.debug?)
|
|
266
|
+
output.include?(COPIED_MARKER)
|
|
267
|
+
end
|
|
268
|
+
|
|
192
269
|
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
193
270
|
|
|
194
271
|
# Reads the container's environment.
|
|
@@ -27,6 +27,9 @@ module Kitchen
|
|
|
27
27
|
include Kitchen::Docker::Helpers::CliHelper
|
|
28
28
|
include Kitchen::Docker::Helpers::ContainerHelper
|
|
29
29
|
|
|
30
|
+
# An id on a line of its own, which is all `docker build -q` prints.
|
|
31
|
+
QUIET_BUILD_IMAGE_ID = /\A(sha256:[[:xdigit:]]{64})\z/
|
|
32
|
+
|
|
30
33
|
# Pulls the built image's id out of `docker build` output.
|
|
31
34
|
#
|
|
32
35
|
# Scanned in reverse, and against several patterns, because the wording has
|
|
@@ -37,6 +40,13 @@ module Kitchen
|
|
|
37
40
|
# @raise [Kitchen::ActionFailed] if no id could be found
|
|
38
41
|
def parse_image_id(output)
|
|
39
42
|
output.split("\n").reverse_each do |line|
|
|
43
|
+
line = line.strip
|
|
44
|
+
# `docker build -q` prints the id and nothing else -- none of the
|
|
45
|
+
# wording below appears -- so a build with `build_options: -q` had
|
|
46
|
+
# no line any of these matched and failed with "Could not parse
|
|
47
|
+
# Docker build output for image ID" (#225).
|
|
48
|
+
return Regexp.last_match(1) if line.match(QUIET_BUILD_IMAGE_ID)
|
|
49
|
+
|
|
40
50
|
if line =~ /writing image (sha256:[[:xdigit:]]{64})(?: \d*\.\ds)? done/i
|
|
41
51
|
img_id = line[/writing image (sha256:[[:xdigit:]]{64})(?: \d*\.\ds)? done/i, 1]
|
|
42
52
|
return img_id
|
|
@@ -126,10 +136,28 @@ module Kitchen
|
|
|
126
136
|
parse_image_id(output)
|
|
127
137
|
end
|
|
128
138
|
|
|
139
|
+
# Whether the image named in state is present locally.
|
|
140
|
+
#
|
|
141
|
+
# The inspect is silenced, as every other predicate that shells out to
|
|
142
|
+
# docker is. Left speaking, `kitchen destroy` on an instance with
|
|
143
|
+
# +remove_images+ set printed the image's entire `docker inspect` JSON
|
|
144
|
+
# -- config, every layer digest, metadata -- into the middle of the
|
|
145
|
+
# destroy output, between the container being removed and the image
|
|
146
|
+
# being removed. Nothing read it: only whether the command succeeded is
|
|
147
|
+
# used.
|
|
148
|
+
#
|
|
149
|
+
# It is still printed under `-l debug`, where the rest of the driver's
|
|
150
|
+
# docker traffic is.
|
|
151
|
+
#
|
|
129
152
|
# @param state [Hash] instance state naming the image
|
|
130
153
|
# @return [Boolean] whether the image is present locally
|
|
131
154
|
def image_exists?(state)
|
|
132
|
-
|
|
155
|
+
return false unless state[:image_id]
|
|
156
|
+
|
|
157
|
+
!!docker_command("inspect --type=image #{state[:image_id]}",
|
|
158
|
+
suppress_output: !logger.debug?)
|
|
159
|
+
rescue
|
|
160
|
+
false
|
|
133
161
|
end
|
|
134
162
|
end
|
|
135
163
|
end
|
|
@@ -17,11 +17,13 @@ require "kitchen"
|
|
|
17
17
|
require "json" unless defined?(JSON)
|
|
18
18
|
require "securerandom" unless defined?(SecureRandom)
|
|
19
19
|
require "net/ssh" unless defined?(Net::SSH)
|
|
20
|
+
require "time" unless defined?(Time)
|
|
20
21
|
|
|
21
22
|
require "kitchen/driver/base"
|
|
22
23
|
|
|
23
24
|
require_relative "../docker/container/linux"
|
|
24
25
|
require_relative "../docker/container/windows"
|
|
26
|
+
require_relative "../docker/docker_version"
|
|
25
27
|
require_relative "../docker/helpers/cli_helper"
|
|
26
28
|
require_relative "../docker/helpers/container_helper"
|
|
27
29
|
|
|
@@ -36,6 +38,13 @@ module Kitchen
|
|
|
36
38
|
include Kitchen::Docker::Helpers::ContainerHelper
|
|
37
39
|
include ShellOut
|
|
38
40
|
|
|
41
|
+
# Reported by `kitchen diagnose`, which is what a bug report is asked to
|
|
42
|
+
# include. The version is this gem's own: the transport used to report
|
|
43
|
+
# Kitchen::VERSION, which is Test Kitchen's, so a diagnose said the
|
|
44
|
+
# plugin was at whatever version Test Kitchen happened to be.
|
|
45
|
+
kitchen_driver_api_version 2
|
|
46
|
+
plugin_version Kitchen::Docker::DOCKER_VERSION
|
|
47
|
+
|
|
39
48
|
default_config :binary, "docker"
|
|
40
49
|
default_config :build_options, nil
|
|
41
50
|
default_config :build_tempdir, Dir.pwd
|
|
@@ -52,6 +61,7 @@ module Kitchen
|
|
|
52
61
|
default_config :remove_images, false
|
|
53
62
|
default_config :run_options, nil
|
|
54
63
|
default_config :security_opt, nil
|
|
64
|
+
default_config :sudo_command, nil
|
|
55
65
|
default_config :tls, false
|
|
56
66
|
default_config :tls_cacert, nil
|
|
57
67
|
default_config :tls_cert, nil
|
|
@@ -79,6 +89,13 @@ module Kitchen
|
|
|
79
89
|
].join("-").downcase
|
|
80
90
|
end
|
|
81
91
|
|
|
92
|
+
# The image `kitchen package` commits to. Derived from the instance name,
|
|
93
|
+
# which is already lowercase and dash-separated, so it is a valid Docker
|
|
94
|
+
# repository name as it stands.
|
|
95
|
+
default_config :package_name do |driver|
|
|
96
|
+
"#{driver.instance.name.downcase.gsub(/[^a-z0-9_.-]/, "-")}:latest"
|
|
97
|
+
end
|
|
98
|
+
|
|
82
99
|
default_config :platform, &:default_platform
|
|
83
100
|
|
|
84
101
|
default_config :run_command do |driver|
|
|
@@ -140,6 +157,82 @@ module Kitchen
|
|
|
140
157
|
container.destroy(state)
|
|
141
158
|
end
|
|
142
159
|
|
|
160
|
+
# Commits the container to a Docker image.
|
|
161
|
+
#
|
|
162
|
+
# `kitchen package` asks a driver to turn a converged instance into
|
|
163
|
+
# something reusable. For Docker that is an image: `docker commit` on the
|
|
164
|
+
# running container, which is the artifact every other docker tool
|
|
165
|
+
# already takes. Run `docker save` against the result for a tarball.
|
|
166
|
+
#
|
|
167
|
+
# @param state [Hash] instance state naming the container
|
|
168
|
+
# @return [void]
|
|
169
|
+
# @raise [Kitchen::ActionFailed] if the instance has not been created
|
|
170
|
+
def package(state)
|
|
171
|
+
unless state[:container_id]
|
|
172
|
+
raise ActionFailed, "Cannot package #{instance.name}: it has not been created."
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Asked here rather than left to `docker commit`, which reports a
|
|
176
|
+
# container that is gone as a bare "Error response from daemon: No such
|
|
177
|
+
# container: <64 hex characters>" with nothing about the instance or
|
|
178
|
+
# what to do next.
|
|
179
|
+
unless container_exists?(state)
|
|
180
|
+
raise ActionFailed, "Cannot package #{instance.name}: the state file names container " \
|
|
181
|
+
"#{state[:container_id]}, which the daemon does not have. " \
|
|
182
|
+
"Run `kitchen destroy` to clear it."
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
name = config[:package_name]
|
|
186
|
+
info("[Docker] Committing container #{state[:container_id]} to #{name}")
|
|
187
|
+
output = docker_command("commit #{shell_escape(state[:container_id])} #{shell_escape(name)}",
|
|
188
|
+
suppress_output: !logger.debug?)
|
|
189
|
+
image_id = output.lines.map(&:strip).find { |line| line.match?(/\Asha256:[[:xdigit:]]{64}\z/) }
|
|
190
|
+
info("[Docker] Packaged #{instance.name} as #{name}#{" (#{image_id})" if image_id}")
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Checks the configuration and the daemon it points at.
|
|
194
|
+
#
|
|
195
|
+
# Run by `kitchen doctor`. A true return is how Test Kitchen decides to
|
|
196
|
+
# exit non-zero, so every check runs and the results are OR-ed together
|
|
197
|
+
# rather than returning at the first problem -- somebody running `doctor`
|
|
198
|
+
# wants the whole list, not the first item on it.
|
|
199
|
+
#
|
|
200
|
+
# @param state [Hash] instance state
|
|
201
|
+
# @return [Boolean] whether a problem was found
|
|
202
|
+
def doctor(state)
|
|
203
|
+
[
|
|
204
|
+
doctor_daemon,
|
|
205
|
+
doctor_files,
|
|
206
|
+
doctor_container(state),
|
|
207
|
+
].any?
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Reports whether the container backing this instance is up.
|
|
211
|
+
#
|
|
212
|
+
# Read by `kitchen list --live`, which showed "unknown" for every
|
|
213
|
+
# instance: {Kitchen::Driver::Base} cannot know, and this driver never
|
|
214
|
+
# said. Docker can answer directly, and these are the same two questions
|
|
215
|
+
# create and destroy already ask.
|
|
216
|
+
#
|
|
217
|
+
# @param state [Hash] instance state naming the container
|
|
218
|
+
# @return [Hash] normalized status data
|
|
219
|
+
def status(state)
|
|
220
|
+
common = { source: "driver", checked_at: Time.now.utc.iso8601, resource_id: state[:container_id] }
|
|
221
|
+
|
|
222
|
+
if !state[:container_id]
|
|
223
|
+
common.merge(live: false, state: "not created",
|
|
224
|
+
message: "No container is recorded in the state file")
|
|
225
|
+
elsif !container_exists?(state)
|
|
226
|
+
common.merge(live: false, state: "gone",
|
|
227
|
+
message: "The state file names a container the daemon does not have")
|
|
228
|
+
elsif container_running?(state)
|
|
229
|
+
common.merge(live: true, state: "running")
|
|
230
|
+
else
|
|
231
|
+
common.merge(live: false, state: "stopped",
|
|
232
|
+
message: "The container exists but is not running")
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
143
236
|
# Waits for the transport to accept a connection, unless disabled.
|
|
144
237
|
#
|
|
145
238
|
# @param state [Hash] instance state describing how to connect
|
|
@@ -171,6 +264,44 @@ module Kitchen
|
|
|
171
264
|
|
|
172
265
|
protected
|
|
173
266
|
|
|
267
|
+
# @return [Boolean] whether the daemon could not be reached
|
|
268
|
+
def doctor_daemon
|
|
269
|
+
version = docker_command("version --format '{{.Server.Version}}'", suppress_output: true).strip
|
|
270
|
+
info("Docker daemon at #{config[:socket]} is reachable, running #{version}.")
|
|
271
|
+
false
|
|
272
|
+
rescue => e
|
|
273
|
+
error("Cannot reach the Docker daemon at #{config[:socket]}. #{e}")
|
|
274
|
+
true
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
# Checks paths the configuration names.
|
|
278
|
+
#
|
|
279
|
+
# A missing TLS file or Dockerfile is worth catching here because docker
|
|
280
|
+
# reports it far from the cause -- a missing client certificate surfaces
|
|
281
|
+
# as a connection error rather than as a missing file.
|
|
282
|
+
#
|
|
283
|
+
# @return [Boolean] whether any named path is missing
|
|
284
|
+
def doctor_files
|
|
285
|
+
%i{tls_cacert tls_cert tls_key dockerfile}.map do |key|
|
|
286
|
+
path = config[key]
|
|
287
|
+
next false if path.nil? || ::File.exist?(::File.expand_path(path))
|
|
288
|
+
|
|
289
|
+
error("#{key} is set to #{path}, which does not exist.")
|
|
290
|
+
true
|
|
291
|
+
end.any?
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# @param state [Hash] instance state naming the container
|
|
295
|
+
# @return [Boolean] whether state names a container that is gone
|
|
296
|
+
def doctor_container(state)
|
|
297
|
+
return false unless state[:container_id]
|
|
298
|
+
return false if container_exists?(state)
|
|
299
|
+
|
|
300
|
+
error("The state file names container #{state[:container_id]}, which the daemon does " \
|
|
301
|
+
"not have. Run `kitchen destroy` to clear it.")
|
|
302
|
+
true
|
|
303
|
+
end
|
|
304
|
+
|
|
174
305
|
# The container implementation for this platform.
|
|
175
306
|
#
|
|
176
307
|
# @return [Kitchen::Docker::Container] a Windows or Linux container
|