kitchen-docker 3.4.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7cacd3efc6d10490456e00f5d077a3a1eb8f96bb5aef71c6c93b8d2044823a1f
4
- data.tar.gz: f6c3337ecdc3da7c134da8506472e7237b85a9784dd63fb5b46fa996303539b6
3
+ metadata.gz: 2d994f876f76b4950f4f85521fba2ed15fe2ed54d06b9993d464a578f2fd9c69
4
+ data.tar.gz: d942290a8f115d5b0937421b747ec3f413e180fd55cfc8af245524b8337b27d5
5
5
  SHA512:
6
- metadata.gz: ec737baa038bf371481c82599263ed09cf98921f687da8cb45e52f1becd13d75417fb545d72338806c533dee99d4b1a39d4c47618e8c15709487937557c30c75
7
- data.tar.gz: e8434376fb8dd607f224de53137d18ee4f9c644c1205b7341cbe3bcc43c7c07ad2aa575fb0ba5cdc7350ee721d6b0fcf6cb781f73421b2251cbfede68f154a0d
6
+ metadata.gz: 7db4fee864798aa758a3e253f433db4e120d7247e8b5125e34764cf393f4836c9885eb3df2eff767501a6910c2f895b4efab467b8a24f7e1ad6120cc58eeede2
7
+ data.tar.gz: f80f60d52168ea008378af1cb66530136bf1e53b6474b75877a9275ff336f0ae632f754833f3e3db7aadd2c0f3910d64269fd39be0bb3a90baf5367d6708f115
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "3.4.0"
2
+ ".": "3.4.1"
3
3
  }
data/CHANGELOG.md CHANGED
@@ -6,6 +6,15 @@ 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
+
9
18
  ## [3.4.0](https://github.com/test-kitchen/kitchen-docker/compare/v3.3.5...v3.4.0) (2026-08-23)
10
19
 
11
20
 
data/README.md CHANGED
@@ -205,7 +205,7 @@ platforms:
205
205
  | `dockerfile` | *(none)* | Path to your own Dockerfile, used instead of the generated one. Rendered as [ERB](#using-a-custom-dockerfile). |
206
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. |
207
207
  | `build_options` | *(none)* | Extra flags for `docker build`, as a string or a map. |
208
- | `build_tempdir` | working directory | Where the generated Dockerfile is written, relative to `build_context`. |
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. |
209
209
  | `use_cache` | `true` | Use Docker's build cache. `false` adds `--no-cache`. |
210
210
  | `remove_images` | `false` | Remove the built image on `kitchen destroy`. |
211
211
  | `package_name` | the instance name, tagged `latest` | Image `kitchen package` commits the container to. |
@@ -237,7 +237,9 @@ platforms:
237
237
  | `tty` | `false` | Pass `-t`, allocating a pseudo-TTY. |
238
238
  | `env_variables` | *(none)* | Environment variables set in the container, as a map. |
239
239
  | `wait_for_transport` | `true` | Wait for the transport to answer before converging. Set `false` for containers that do not stay up. |
240
- | `detach` | `false` | Run provisioner commands with `docker exec -d`, returning immediately instead of waiting for them. The container itself is always started detached, regardless of this setting, and `kitchen login` ignores it so the shell stays usable. |
240
+
241
+ The container itself is always started detached — see [`detach`](#transport-configuration)
242
+ under the transport for how provisioner commands are run.
241
243
 
242
244
  ### Networking
243
245
 
@@ -314,6 +316,7 @@ These options go under `transport:`, not `driver:`.
314
316
  | `working_dir` | *(none)* | Working directory inside the container (`-w`). |
315
317
  | `temp_dir` | `/tmp`, or `$env:TEMP` on Windows | Directory used to stage uploaded files. |
316
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. |
317
320
  | `privileged` | `false` | Run commands with `--privileged`. |
318
321
  | `interactive` | `false` | Pass `-i`. |
319
322
  | `tty` | `false` | Pass `-t`. |
@@ -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
- container_exec(@config, "/bin/bash #{remote_path}/#{filename}")
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
- cmd = build_powershell_command("-File #{remote_path}\\#{filename}")
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
@@ -18,6 +18,6 @@ module Kitchen
18
18
  # Everything belonging to the kitchen-docker plugin.
19
19
  module Docker
20
20
  # The version of the kitchen-docker gem.
21
- DOCKER_VERSION = "3.4.0".freeze
21
+ DOCKER_VERSION = "3.4.1".freeze
22
22
  end
23
23
  end
@@ -107,7 +107,7 @@ module Kitchen
107
107
  # @param cmd [String] the command to run
108
108
  # @param options [Hash] shell-out options
109
109
  # @return [String] combined stdout and stderr
110
- # @raise [Kitchen::ShellCommandFailed] if the command exits non-zero
110
+ # @raise [Kitchen::ShellOut::ShellCommandFailed] if the command exits non-zero
111
111
  def run_command(cmd, options = {})
112
112
  if options.fetch(:use_sudo, false)
113
113
  cmd = "#{options.fetch(:sudo_command, "sudo -E")} #{cmd}"
@@ -155,6 +155,17 @@ module Kitchen
155
155
 
156
156
  # Creates a directory inside the container, on Linux or Windows.
157
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
+ #
158
169
  # @param state [Hash] instance state naming the container
159
170
  # @param path [String] the directory to create; environment variable
160
171
  # references are expanded first
@@ -162,7 +173,7 @@ module Kitchen
162
173
  # @raise [RuntimeError] if the directory cannot be created
163
174
  def create_dir_on_container(state, path)
164
175
  path = replace_env_variables(state, path)
165
- cmd = "mkdir -p #{path}"
176
+ cmd = "mkdir -p #{Shellwords.escape(path)}"
166
177
 
167
178
  if state[:platform].include?("windows")
168
179
  psh = "-Command if(-not (Test-Path '#{path}')) { New-Item -Path '#{path}' -Force }"
@@ -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
@@ -23,6 +23,7 @@ require "kitchen/driver/base"
23
23
 
24
24
  require_relative "../docker/container/linux"
25
25
  require_relative "../docker/container/windows"
26
+ require_relative "../docker/docker_version"
26
27
  require_relative "../docker/helpers/cli_helper"
27
28
  require_relative "../docker/helpers/container_helper"
28
29
 
@@ -37,6 +38,13 @@ module Kitchen
37
38
  include Kitchen::Docker::Helpers::ContainerHelper
38
39
  include ShellOut
39
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
+
40
48
  default_config :binary, "docker"
41
49
  default_config :build_options, nil
42
50
  default_config :build_tempdir, Dir.pwd
@@ -15,6 +15,7 @@ require "kitchen"
15
15
 
16
16
  require_relative "../docker/container/linux"
17
17
  require_relative "../docker/container/windows"
18
+ require_relative "../docker/docker_version"
18
19
 
19
20
  require_relative "../docker/helpers/inspec_helper"
20
21
 
@@ -31,8 +32,11 @@ module Kitchen
31
32
  # Raised when a docker command against the container fails.
32
33
  class DockerFailed < TransportFailed; end
33
34
 
34
- # kitchen_transport_api_version 1
35
- plugin_version Kitchen::VERSION
35
+ # Reported by `kitchen diagnose`. plugin_version was Kitchen::VERSION,
36
+ # which is Test Kitchen's version rather than this gem's, so a diagnose
37
+ # reported the transport as 4.1.1 while kitchen-docker was at 3.3.4.
38
+ kitchen_transport_api_version 1
39
+ plugin_version Kitchen::Docker::DOCKER_VERSION
36
40
 
37
41
  default_config :binary, "docker"
38
42
  default_config :env_variables, nil
@@ -79,6 +83,8 @@ module Kitchen
79
83
  # than from Test Kitchen's configuration.
80
84
  #
81
85
  # @param state [Hash] instance state naming the container
86
+ # @param block [Proc, nil] forwarded to the connection's constructor, which
87
+ # yields the new connection to it; the connection is not closed afterwards
82
88
  # @yieldparam connection [Connection] if a block is given
83
89
  # @return [Connection]
84
90
  def connection(state, &block)
@@ -333,6 +333,89 @@ describe Kitchen::Docker::Helpers::ContainerHelper do
333
333
  end
334
334
  end
335
335
 
336
+ describe "#create_dir_on_container" do
337
+ def dir_maker(state)
338
+ helper.tap do |h|
339
+ @commands = []
340
+ allow(h).to receive(:replace_env_variables) { |_s, path| path }
341
+ allow(h).to receive(:docker_command) { |cmd, _opts = {}| @commands << cmd; "" }
342
+ end
343
+ end
344
+
345
+ it "creates the directory inside the container" do
346
+ dir_maker(nil).create_dir_on_container({ container_id: "abc", platform: "ubuntu-24.04" }, "/tmp")
347
+ expect(argv(@commands.first)).to include_consecutive("mkdir", "-p", "/tmp")
348
+ end
349
+
350
+ it "expands an environment variable reference before creating it" do
351
+ h = helper
352
+ allow(h).to receive(:container_env_variables).and_return("TEMP" => "/var/tmp")
353
+ allow(h).to receive(:docker_command) { |cmd, _opts = {}| @cmd = cmd; "" }
354
+ h.create_dir_on_container({ container_id: "abc", platform: "ubuntu-24.04" }, "$TEMP/kitchen")
355
+ expect(argv(@cmd)).to include_consecutive("mkdir", "-p", "/var/tmp/kitchen")
356
+ end
357
+
358
+ # A temp_dir with a space in it reached `mkdir -p` unquoted, so the shell
359
+ # tore it in two and mkdir made two directories -- neither of them the one
360
+ # asked for. Every later upload then landed somewhere that did not exist.
361
+ it "keeps a path containing a space as one argument" do
362
+ dir_maker(nil).create_dir_on_container({ container_id: "abc", platform: "ubuntu-24.04" },
363
+ "/var/tmp/kitchen docker")
364
+ expect(argv(@commands.first)).to include_consecutive("mkdir", "-p", "/var/tmp/kitchen docker")
365
+ end
366
+
367
+ it "uses PowerShell on a Windows container" do
368
+ dir_maker(nil).create_dir_on_container({ container_id: "abc", platform: "windows-2022" }, 'C:\\Temp')
369
+ expect(@commands.first).to include("powershell").and include("New-Item")
370
+ end
371
+
372
+ it "says which directory it could not create" do
373
+ h = helper
374
+ allow(h).to receive(:replace_env_variables) { |_s, path| path }
375
+ allow(h).to receive(:docker_command).and_raise("boom")
376
+ expect { h.create_dir_on_container({ container_id: "abc", platform: "ubuntu-24.04" }, "/tmp/kitchen") }
377
+ .to raise_error(RuntimeError, %r{Failed to create directory /tmp/kitchen})
378
+ end
379
+ end
380
+
381
+ describe "#container_exec" do
382
+ it "runs the command through docker exec" do
383
+ h = helper
384
+ allow(h).to receive(:docker_command) { |cmd, _opts = {}| @cmd = cmd; "output" }
385
+ expect(h.container_exec({ container_id: "abc" }, "echo hi")).to eq "output"
386
+ expect(argv(@cmd)).to include_consecutive("exec", "abc", "echo", "hi")
387
+ end
388
+
389
+ it "names the container operation when the command fails" do
390
+ h = helper
391
+ allow(h).to receive(:docker_command).and_raise("boom")
392
+ expect { h.container_exec({ container_id: "abc" }, "echo hi") }
393
+ .to raise_error(RuntimeError, /Failed to execute command on Docker container/)
394
+ end
395
+ end
396
+
397
+ describe "#run_container" do
398
+ it "returns the id docker printed for the container it started" do
399
+ h = helper(instance_name: "kitchen-test")
400
+ allow(h).to receive(:docker_command).and_return(DockerOutput::RUN_CLEAN)
401
+ expect(h.run_container({ image_id: "sha256:abc" }, 22)).to eq DockerOutput::RUN_CONTAINER_ID
402
+ end
403
+
404
+ it "publishes the port it was given" do
405
+ h = helper
406
+ allow(h).to receive(:docker_command) { |cmd, _opts = {}| @cmd = cmd; DockerOutput::RUN_CLEAN }
407
+ h.run_container({ image_id: "sha256:abc" }, 22)
408
+ expect(argv(@cmd)).to include_consecutive("-p", "22")
409
+ end
410
+
411
+ it "publishes no port when there is none, as for a Windows container" do
412
+ h = helper
413
+ allow(h).to receive(:docker_command) { |cmd, _opts = {}| @cmd = cmd; DockerOutput::RUN_CLEAN }
414
+ h.run_container(image_id: "sha256:abc")
415
+ expect(argv(@cmd)).not_to include("-p")
416
+ end
417
+ end
418
+
336
419
  describe "#copy_file_to_container" do
337
420
  let(:state) { { container_id: "abc", platform: "ubuntu-24.04" } }
338
421
 
data/spec/docker_spec.rb CHANGED
@@ -17,6 +17,24 @@
17
17
  require "spec_helper"
18
18
 
19
19
  describe Kitchen::Driver::Docker do
20
+ # `kitchen diagnose` is what bug reports are asked to include, so what it
21
+ # says about the plugin has to be true. The transport reported
22
+ # Kitchen::VERSION, which is Test Kitchen's version, and the driver reported
23
+ # nothing at all.
24
+ describe "plugin metadata" do
25
+ it "reports this gem's version, not Test Kitchen's" do
26
+ expect(described_class.diagnose[:version]).to eq Kitchen::Docker::DOCKER_VERSION
27
+ end
28
+
29
+ it "does not report Test Kitchen's version" do
30
+ expect(described_class.diagnose[:version]).not_to eq Kitchen::VERSION
31
+ end
32
+
33
+ it "declares the driver API version it is written against" do
34
+ expect(described_class.diagnose[:api_version]).to eq 2
35
+ end
36
+ end
37
+
20
38
  describe "#config_to_options" do
21
39
  let(:config) {}
22
40
  subject { described_class.new.send(:config_to_options, config) }
@@ -16,6 +16,30 @@ require "spec_helper"
16
16
 
17
17
  describe Kitchen::Docker::Helpers::ImageHelper do
18
18
  describe "#parse_image_id" do
19
+ # From #225. `docker build -q` prints the id on a line of its own and
20
+ # nothing else -- no "writing image", no "naming to", no "successfully
21
+ # built" -- so every pattern the parser had missed it, and a build with
22
+ # `build_options: -q` failed with "Could not parse Docker build output for
23
+ # image ID" rather than producing an instance.
24
+ context "with a quiet build" do
25
+ it "reads the id docker printed on its own" do
26
+ expect(helper.parse_image_id(DockerOutput::BUILD_QUIET))
27
+ .to eq DockerOutput::BUILD_QUIET_IMAGE_ID
28
+ end
29
+
30
+ it "reads it when docker also wrote a warning to stderr" do
31
+ expect(helper.parse_image_id("WARNING: something happened\n#{DockerOutput::BUILD_QUIET}"))
32
+ .to eq DockerOutput::BUILD_QUIET_IMAGE_ID
33
+ end
34
+
35
+ it "does not mistake a digest that is part of a longer line for the id" do
36
+ # Ordinary build output is full of "... sha256:... done" lines. Only a
37
+ # line that is nothing but a digest is the quiet form.
38
+ expect { helper.parse_image_id("#6 exporting manifest sha256:#{"a" * 64} done\n") }
39
+ .to raise_error(Kitchen::ActionFailed)
40
+ end
41
+ end
42
+
19
43
  # Docker has changed how it reports the built image's id several times, and
20
44
  # each change has broken this parser. Every format the driver claims to
21
45
  # support gets a case here, against output copied from a real build.
@@ -211,6 +211,33 @@ describe Kitchen::Docker::Container::Linux do
211
211
  c.execute("echo hi")
212
212
  expect(uploaded).to eq "echo hi"
213
213
  end
214
+
215
+ # The staged script is run by path, and that path is built from temp_dir,
216
+ # which the user sets.
217
+ def ran_in(temp_dir)
218
+ c = container(temp_dir: temp_dir)
219
+ allow(c).to receive(:create_dir_on_container)
220
+ allow(c).to receive(:replace_env_variables) { |_cfg, path| path }
221
+ allow(c).to receive(:upload)
222
+ ran = nil
223
+ allow(c).to receive(:container_exec) { |_cfg, cmd| ran = cmd }
224
+ c.execute("echo hi")
225
+ ran
226
+ end
227
+
228
+ it "runs the staged script with bash" do
229
+ ran = ran_in("/tmp")
230
+ expect(argv(ran).first).to eq "/bin/bash"
231
+ expect(argv(ran).last).to match(%r{\A/tmp/docker-[0-9a-f-]+\.sh\z})
232
+ end
233
+
234
+ # Interpolated unquoted, a temp_dir with a space in it reached `docker
235
+ # exec` as two arguments, and bash was handed the first half of the
236
+ # directory as the script to run.
237
+ it "keeps a temp_dir containing a space as one argument" do
238
+ expect(argv(ran_in("/var/tmp/kitchen docker")).last)
239
+ .to match(%r{\A/var/tmp/kitchen docker/docker-[0-9a-f-]+\.sh\z})
240
+ end
214
241
  end
215
242
 
216
243
  describe "#generate_keys" do
@@ -81,6 +81,15 @@ module DockerOutput
81
81
 
82
82
  BUILD_LEGACY_IMAGE_ID = "1a2b3c4d5e6f".freeze
83
83
 
84
+ # `docker build -q` on Docker 29.7.2. Quiet mode prints the id and nothing
85
+ # else -- no step lines, no exporting lines, none of the wording the other
86
+ # fixtures here are built around. Reachable through the driver as
87
+ # `build_options: -q`.
88
+ BUILD_QUIET = "sha256:ab86ce908a36ffb7de411a72550e416a4d8c268570a0f7313c284c79344d6c0f\n".freeze
89
+
90
+ BUILD_QUIET_IMAGE_ID =
91
+ "sha256:ab86ce908a36ffb7de411a72550e416a4d8c268570a0f7313c284c79344d6c0f".freeze
92
+
84
93
  # `docker run -d`, the ordinary case: the id and nothing else.
85
94
  RUN_CONTAINER_ID = "b89e1e8b07664a1ee0bd09decb833146eaf9cc810a152950e3576a56745944db".freeze
86
95
  RUN_CLEAN = "#{RUN_CONTAINER_ID}\n".freeze
@@ -15,6 +15,26 @@
15
15
  require "spec_helper"
16
16
  require "kitchen/transport/docker"
17
17
 
18
+ describe Kitchen::Transport::Docker do
19
+ # `kitchen diagnose` is what bug reports are asked to include, so what it
20
+ # says about the plugin has to be true.
21
+ describe "plugin metadata" do
22
+ it "reports this gem's version, not Test Kitchen's" do
23
+ expect(described_class.diagnose[:version]).to eq Kitchen::Docker::DOCKER_VERSION
24
+ end
25
+
26
+ it "does not report Test Kitchen's version" do
27
+ # It did: `plugin_version Kitchen::VERSION` made a diagnose say the
28
+ # transport was at Test Kitchen's version rather than this gem's.
29
+ expect(described_class.diagnose[:version]).not_to eq Kitchen::VERSION
30
+ end
31
+
32
+ it "declares the transport API version it is written against" do
33
+ expect(described_class.diagnose[:api_version]).to eq 1
34
+ end
35
+ end
36
+ end
37
+
18
38
  describe Kitchen::Transport::Docker::Connection do
19
39
  let(:options) do
20
40
  {
@@ -64,6 +64,60 @@ describe Kitchen::Docker::Container::Windows do
64
64
  end
65
65
  end
66
66
 
67
+ describe "#execute" do
68
+ def staged(config = {})
69
+ c = container({ temp_dir: 'C:\\Temp' }.merge(config))
70
+ allow(c).to receive(:create_dir_on_container)
71
+ allow(c).to receive(:replace_env_variables) { |_cfg, path| path }
72
+ allow(c).to receive(:upload)
73
+ allow(c).to receive(:container_exec) { |_cfg, cmd| @ran = cmd }
74
+ c
75
+ end
76
+
77
+ around do |example|
78
+ Dir.mktmpdir { |dir| Dir.chdir(dir) { example.run } }
79
+ end
80
+
81
+ it "runs the staged script with powershell" do
82
+ staged.execute("Write-Host hi")
83
+ expect(@ran).to start_with "powershell -ExecutionPolicy Bypass -NoLogo "
84
+ expect(@ran).to match(/-File .*docker-[0-9a-f-]+\.ps1/)
85
+ end
86
+
87
+ it "stages the script under the configured temp_dir" do
88
+ staged.execute("Write-Host hi")
89
+ expect(@ran).to include 'C:\\Temp\\docker-'
90
+ end
91
+
92
+ it "accepts a temp_dir written with forward slashes" do
93
+ # Kitchen configuration is YAML that is often shared with Linux suites,
94
+ # so the separator is normalised rather than rejected.
95
+ staged(temp_dir: "C:/Temp").execute("Write-Host hi")
96
+ expect(@ran).to include 'C:\\Temp\\docker-'
97
+ end
98
+
99
+ # A Windows temp_dir routinely contains a space -- $env:TEMP under a user
100
+ # whose name has one does. PowerShell's -File takes exactly one argument,
101
+ # so an unquoted path left it looking for a script named after the first
102
+ # word.
103
+ it "quotes a script path containing a space" do
104
+ staged(temp_dir: 'C:\\Users\\Foo Bar\\Temp').execute("Write-Host hi")
105
+ expect(@ran).to include('-File "C:\\Users\\Foo Bar\\Temp\\docker-').and end_with('.ps1"')
106
+ end
107
+
108
+ it "removes the staged script once the command has run" do
109
+ staged.execute("Write-Host hi")
110
+ expect(Dir.glob(".kitchen/temp/docker-*.ps1")).to be_empty
111
+ end
112
+
113
+ it "removes the staged script when the command fails" do
114
+ c = staged
115
+ allow(c).to receive(:container_exec).and_raise("command exploded")
116
+ expect { c.execute("Write-Host hi") }.to raise_error(/Failed to execute command/)
117
+ expect(Dir.glob(".kitchen/temp/docker-*.ps1")).to be_empty
118
+ end
119
+ end
120
+
67
121
  describe "the contract it shares with the Linux container" do
68
122
  # The two classes share almost no code but must agree on the state they
69
123
  # populate, because the driver and transport read the same keys whichever
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kitchen-docker
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.4.0
4
+ version: 3.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Porter
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-23 00:00:00.000000000 Z
11
+ date: 2026-08-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: test-kitchen