kitchen-docker 3.3.0 → 3.3.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/.rubocop.yml +0 -1
- data/.yardopts +11 -0
- data/CHANGELOG.md +381 -21
- data/CONTRIBUTING.md +203 -0
- data/Gemfile +4 -0
- data/README.md +417 -533
- data/Rakefile +18 -0
- data/lib/kitchen/docker/container/linux.rb +50 -0
- data/lib/kitchen/docker/container/windows.rb +27 -0
- data/lib/kitchen/docker/container.rb +34 -0
- data/lib/kitchen/docker/docker_version.rb +4 -2
- data/lib/kitchen/docker/erb_context.rb +9 -0
- data/lib/kitchen/docker/helpers/cli_helper.rb +56 -3
- data/lib/kitchen/docker/helpers/container_helper.rb +91 -19
- data/lib/kitchen/docker/helpers/dockerfile_helper.rb +54 -0
- data/lib/kitchen/docker/helpers/file_helper.rb +8 -0
- data/lib/kitchen/docker/helpers/image_helper.rb +28 -0
- data/lib/kitchen/docker/helpers/inspec_helper.rb +1 -0
- data/lib/kitchen/driver/docker.rb +27 -0
- data/lib/kitchen/transport/docker.rb +43 -2
- data/spec/cli_helper_spec.rb +312 -0
- data/spec/container_helper_spec.rb +178 -0
- data/spec/dockerfile_helper_spec.rb +97 -61
- data/spec/erb_context_spec.rb +60 -0
- data/spec/image_helper_spec.rb +142 -0
- data/spec/linux_container_spec.rb +194 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/support/argv.rb +55 -0
- data/spec/support/docker_output.rb +117 -0
- data/spec/support/harness.rb +70 -0
- data/spec/transport_docker_spec.rb +7 -0
- data/spec/windows_container_spec.rb +101 -0
- metadata +13 -2
|
@@ -0,0 +1,312 @@
|
|
|
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
|
+
|
|
15
|
+
require "spec_helper"
|
|
16
|
+
|
|
17
|
+
describe Kitchen::Docker::Helpers::CliHelper do
|
|
18
|
+
let(:image_id) { "sha256:abc123" }
|
|
19
|
+
|
|
20
|
+
# Assertions are made against the argument vector a shell would produce, not
|
|
21
|
+
# against the command string. See spec/support/argv.rb for why.
|
|
22
|
+
def run_argv(config = {})
|
|
23
|
+
argv(helper(config.merge(run_command: "/usr/sbin/sshd -D")).build_run_command(image_id))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe "#build_run_command" do
|
|
27
|
+
describe "the baseline command" do
|
|
28
|
+
it "runs the image detached, with the configured run command" do
|
|
29
|
+
expect(run_argv).to eq ["run", "-d", image_id, "/usr/sbin/sshd", "-D"]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "adds nothing that was not configured" do
|
|
33
|
+
# A flag leaking in when its option is unset is a silent behaviour
|
|
34
|
+
# change for every user; pinning the empty case catches it.
|
|
35
|
+
expect(run_argv.length).to eq 5
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Each entry pairs a configuration with the arguments Docker must receive.
|
|
40
|
+
# A dropped or misspelled flag is the failure mode this table exists for:
|
|
41
|
+
# the option is accepted in kitchen.yml, nothing happens, and nothing warns.
|
|
42
|
+
{
|
|
43
|
+
"privileged" => [{ privileged: true }, %w{--privileged}],
|
|
44
|
+
"publish_all" => [{ publish_all: true }, %w{-P}],
|
|
45
|
+
"interactive" => [{ interactive: true }, %w{-i}],
|
|
46
|
+
"tty" => [{ tty: true }, %w{-t}],
|
|
47
|
+
"hostname" => [{ hostname: "web.local" }, %w{-h web.local}],
|
|
48
|
+
"memory" => [{ memory: "512m" }, %w{-m 512m}],
|
|
49
|
+
"cpu" => [{ cpu: "512" }, %w{-c 512}],
|
|
50
|
+
"gpus" => [{ gpus: "all" }, %w{--gpus all}],
|
|
51
|
+
"isolation" => [{ isolation: "hyperv" }, %w{--isolation hyperv}],
|
|
52
|
+
"instance_name" => [{ instance_name: "web" }, %w{--name web}],
|
|
53
|
+
"forward" => [{ forward: "80:8080" }, %w{-p 80:8080}],
|
|
54
|
+
"dns" => [{ dns: "8.8.8.8" }, %w{--dns 8.8.8.8}],
|
|
55
|
+
"volume" => [{ volume: "/ftp:/ftp" }, %w{-v /ftp:/ftp}],
|
|
56
|
+
"volumes_from" => [{ volumes_from: "repos" }, %w{--volumes-from repos}],
|
|
57
|
+
"links" => [{ links: "db:db" }, %w{--link db:db}],
|
|
58
|
+
"devices" => [{ devices: "/dev/vboxdrv" }, %w{--device /dev/vboxdrv}],
|
|
59
|
+
"tmpfs" => [{ tmpfs: "/tmp" }, %w{--tmpfs /tmp}],
|
|
60
|
+
"mount" => [{ mount: "type=tmpfs,destination=/run" }, %w{--mount type=tmpfs,destination=/run}],
|
|
61
|
+
"add_host" => [{ add_host: { "db" => "10.0.0.5" } }, %w{--add-host=db:10.0.0.5}],
|
|
62
|
+
"cap_add" => [{ cap_add: "SYS_PTRACE" }, %w{--cap-add=SYS_PTRACE}],
|
|
63
|
+
"cap_drop" => [{ cap_drop: "CHOWN" }, %w{--cap-drop=CHOWN}],
|
|
64
|
+
"security_opt" => [{ security_opt: "apparmor:my_profile" }, %w{--security-opt=apparmor:my_profile}],
|
|
65
|
+
"docker_platform" => [{ docker_platform: "linux/arm64" }, %w{--platform=linux/arm64}],
|
|
66
|
+
"http_proxy" => [{ http_proxy: "http://p:8080" }, %w{-e http_proxy=http://p:8080}],
|
|
67
|
+
"https_proxy" => [{ https_proxy: "http://p:8080" }, %w{-e https_proxy=http://p:8080}],
|
|
68
|
+
}.each do |option, (config, expected)|
|
|
69
|
+
it "passes #{option} through as #{expected.join(" ")}" do
|
|
70
|
+
expect(run_argv(config)).to include_consecutive(*expected)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
describe "options documented as accepting one value or a list" do
|
|
75
|
+
# README tells users these take either form. Array() is what makes that
|
|
76
|
+
# true, and it is easy to drop when an option is edited.
|
|
77
|
+
%i{forward dns volume volumes_from links devices tmpfs mount cap_add cap_drop security_opt}.each do |option|
|
|
78
|
+
it "accepts a bare value for #{option}" do
|
|
79
|
+
expect { run_argv(option => "one") }.not_to raise_error
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "emits one argument group per entry for #{option}" do
|
|
83
|
+
many = run_argv(option => %w{one two})
|
|
84
|
+
expect(many.grep(/one/)).not_to be_empty
|
|
85
|
+
expect(many.grep(/two/)).not_to be_empty
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
describe "the transport port" do
|
|
91
|
+
it "publishes the port the transport asks for" do
|
|
92
|
+
cmd = helper({ run_command: "sshd" }).build_run_command(image_id, 2222)
|
|
93
|
+
expect(argv(cmd)).to include_consecutive("-p", "2222")
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it "publishes nothing when the transport asks for no port" do
|
|
97
|
+
cmd = helper({ run_command: "sshd" }).build_run_command(image_id, nil)
|
|
98
|
+
expect(argv(cmd)).not_to include "-p"
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
describe "quoting" do
|
|
103
|
+
# Docker command lines are built as strings and handed to a shell, so a
|
|
104
|
+
# configured value containing a space has to survive shell splitting as a
|
|
105
|
+
# single argument. These are the cases where it does not.
|
|
106
|
+
it "keeps a volume path containing a space as one argument" do
|
|
107
|
+
pending "BUG: build_run_command interpolates volume unquoted, so a path with a space becomes two arguments"
|
|
108
|
+
expect(run_argv(volume: "/host dir:/data")).to include_consecutive("-v", "/host dir:/data")
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it "keeps a hostname containing a space as one argument" do
|
|
112
|
+
pending "BUG: build_run_command interpolates hostname unquoted"
|
|
113
|
+
expect(run_argv(hostname: "two words")).to include_consecutive("-h", "two words")
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it "keeps a mount specification containing a space as one argument" do
|
|
117
|
+
pending "BUG: build_run_command interpolates mount unquoted"
|
|
118
|
+
expect(run_argv(mount: "type=bind,source=/my dir,destination=/d"))
|
|
119
|
+
.to include_consecutive("--mount", "type=bind,source=/my dir,destination=/d")
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
describe "#build_exec_command" do
|
|
125
|
+
let(:state) { { container_id: "abc123" } }
|
|
126
|
+
|
|
127
|
+
def exec_argv(config = {}, command: "whoami")
|
|
128
|
+
argv(helper(config).build_exec_command(state, command))
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it "execs the command in the container" do
|
|
132
|
+
expect(exec_argv).to eq %w{exec abc123 whoami}
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
{
|
|
136
|
+
"detach" => [{ detach: true }, %w{-d}],
|
|
137
|
+
"privileged" => [{ privileged: true }, %w{--privileged}],
|
|
138
|
+
"tty" => [{ tty: true }, %w{-t}],
|
|
139
|
+
"interactive" => [{ interactive: true }, %w{-i}],
|
|
140
|
+
"username" => [{ username: "kitchen" }, %w{-u kitchen}],
|
|
141
|
+
"working_dir" => [{ working_dir: "/srv" }, %w{-w /srv}],
|
|
142
|
+
}.each do |option, (config, expected)|
|
|
143
|
+
it "passes #{option} through as #{expected.join(" ")}" do
|
|
144
|
+
expect(exec_argv(config)).to include_consecutive(*expected)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
it "puts the container id before the command" do
|
|
149
|
+
# Reversing these makes Docker try to exec into the command name, with an
|
|
150
|
+
# error that does not point anywhere near the cause.
|
|
151
|
+
result = exec_argv({ username: "kitchen" }, command: "whoami")
|
|
152
|
+
expect(result.index("abc123")).to be < result.index("whoami")
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
describe "#build_env_variable_args" do
|
|
157
|
+
it "emits one -e flag per variable" do
|
|
158
|
+
expect(argv(helper.build_env_variable_args("A" => "1", "B" => "2")))
|
|
159
|
+
.to eq %w{-e A=1 -e B=2}
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
it "strips surrounding whitespace from names and values" do
|
|
163
|
+
expect(argv(helper.build_env_variable_args(" A " => " 1 "))).to eq %w{-e A=1}
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
it "refuses anything that is not a Hash" do
|
|
167
|
+
# The driver would otherwise build "-e" flags out of an Array's elements
|
|
168
|
+
# and produce a command line that fails somewhere far from the cause.
|
|
169
|
+
expect { helper.build_env_variable_args(%w{A=1}) }
|
|
170
|
+
.to raise_error(Kitchen::ActionFailed, /not of a Hash type/)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
it "keeps a value containing a space as one argument" do
|
|
174
|
+
expect(argv(helper.build_env_variable_args("MSG" => "hello world")))
|
|
175
|
+
.to eq ["-e", "MSG=hello world"]
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it "keeps a value containing a double quote intact" do
|
|
179
|
+
pending "BUG: values are wrapped in double quotes, so an embedded double quote ends the quoting early"
|
|
180
|
+
expect(argv(helper.build_env_variable_args("MSG" => 'say "hi"')))
|
|
181
|
+
.to eq ["-e", 'MSG=say "hi"']
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
describe "#build_copy_command" do
|
|
186
|
+
it "copies local to remote" do
|
|
187
|
+
expect(argv(helper.build_copy_command("/tmp/a", "abc:/tmp/a")))
|
|
188
|
+
.to eq %w{cp /tmp/a abc:/tmp/a}
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
it "preserves ownership and mode when asked" do
|
|
192
|
+
expect(argv(helper.build_copy_command("/tmp/a", "abc:/tmp/a", archive: true)))
|
|
193
|
+
.to eq %w{cp -a /tmp/a abc:/tmp/a}
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
it "keeps a local path containing a space as one argument" do
|
|
197
|
+
pending "BUG: build_copy_command interpolates both paths unquoted, so uploads from a path with a space fail"
|
|
198
|
+
expect(argv(helper.build_copy_command("/Users/me/My Cookbooks/f.rb", "abc:/tmp/f.rb")))
|
|
199
|
+
.to eq ["cp", "/Users/me/My Cookbooks/f.rb", "abc:/tmp/f.rb"]
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
describe "#config_to_options" do
|
|
204
|
+
subject { helper.config_to_options(input) }
|
|
205
|
+
|
|
206
|
+
context "with nil" do
|
|
207
|
+
let(:input) { nil }
|
|
208
|
+
|
|
209
|
+
it { is_expected.to eq "" }
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
context "with a string" do
|
|
213
|
+
let(:input) { "--foo" }
|
|
214
|
+
|
|
215
|
+
it { is_expected.to eq "--foo" }
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
context "with a string with spaces" do
|
|
219
|
+
let(:input) { "--foo bar" }
|
|
220
|
+
|
|
221
|
+
it { is_expected.to eq "--foo bar" }
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
context "with an array of strings" do
|
|
225
|
+
let(:input) { %w{--foo --bar} }
|
|
226
|
+
|
|
227
|
+
it { is_expected.to eq "--foo --bar" }
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
context "with an array of hashes" do
|
|
231
|
+
let(:input) { [{ foo: "bar" }, { other: "baz" }] }
|
|
232
|
+
|
|
233
|
+
it { is_expected.to eq "--foo=bar --other=baz" }
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
context "with a hash of strings" do
|
|
237
|
+
let(:input) { { foo: "bar", other: "baz" } }
|
|
238
|
+
|
|
239
|
+
it { is_expected.to eq "--foo=bar --other=baz" }
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
context "with a hash of arrays" do
|
|
243
|
+
let(:input) { { foo: %w{bar baz} } }
|
|
244
|
+
|
|
245
|
+
it { is_expected.to eq "--foo=bar --foo=baz" }
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
context "with a hash of strings with spaces" do
|
|
249
|
+
let(:input) { { foo: "bar two", other: "baz" } }
|
|
250
|
+
|
|
251
|
+
it { is_expected.to eq '--foo=bar\\ two --other=baz' }
|
|
252
|
+
|
|
253
|
+
it "survives shell splitting as one argument per flag" do
|
|
254
|
+
expect(argv(subject)).to eq ["--foo=bar two", "--other=baz"]
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
context "with a boolean value, as the README's build_options example uses" do
|
|
259
|
+
let(:input) { { rm: false } }
|
|
260
|
+
|
|
261
|
+
it { is_expected.to eq "--rm=false" }
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
describe "#run_command" do
|
|
266
|
+
it "returns stdout and stderr together" do
|
|
267
|
+
# docker writes build progress and image ids to stderr, so the driver
|
|
268
|
+
# reimplements run_command specifically to keep both streams.
|
|
269
|
+
expect(helper.run_command("echo out; echo err 1>&2")).to eq "out\nerr\n"
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
it "raises Kitchen::ShellOut::ShellCommandFailed when the command fails" do
|
|
273
|
+
# cli_helper raises a bare `ShellCommandFailed`, which resolves through
|
|
274
|
+
# the included Kitchen::ShellOut rather than being spelled out. Pin the
|
|
275
|
+
# class so a change to those includes cannot silently alter what callers
|
|
276
|
+
# have to rescue.
|
|
277
|
+
expect { helper.run_command("exit 7") }
|
|
278
|
+
.to raise_error(Kitchen::ShellOut::ShellCommandFailed, /exit with \[0\], but received '7'/)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
it "prefixes the command with the sudo command when use_sudo is set" do
|
|
282
|
+
# Uses `echo` as the sudo command so the prefixing is observable without
|
|
283
|
+
# needing real sudo on the machine running the specs.
|
|
284
|
+
expect(helper.run_command("echo hi", use_sudo: true, sudo_command: "echo SUDO"))
|
|
285
|
+
.to eq "SUDO echo hi\n"
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
it "does not prefix anything when use_sudo is unset" do
|
|
289
|
+
expect(helper.run_command("echo hi")).to eq "hi\n"
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
describe "#docker_shell_opts" do
|
|
294
|
+
it "translates suppress_output into silencing the live stream" do
|
|
295
|
+
expect(helper.docker_shell_opts(suppress_output: true)).to eq(live_stream: nil)
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
it "removes suppress_output, which Mixlib::ShellOut would reject" do
|
|
299
|
+
expect(helper.docker_shell_opts(suppress_output: false)).not_to have_key(:suppress_output)
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
it "leaves other options alone" do
|
|
303
|
+
expect(helper.docker_shell_opts(timeout: 60)).to eq(timeout: 60)
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
describe "#dev_null" do
|
|
308
|
+
it "returns a device that exists on this platform" do
|
|
309
|
+
expect(helper.dev_null).to eq(Gem.win_platform? ? "NUL" : "/dev/null")
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
end
|
|
@@ -0,0 +1,178 @@
|
|
|
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
|
+
|
|
15
|
+
require "spec_helper"
|
|
16
|
+
|
|
17
|
+
describe Kitchen::Docker::Helpers::ContainerHelper do
|
|
18
|
+
describe "#parse_container_id" do
|
|
19
|
+
it "accepts a full-length id" do
|
|
20
|
+
expect(helper.parse_container_id(DockerOutput::RUN_CLEAN))
|
|
21
|
+
.to eq DockerOutput::RUN_CONTAINER_ID
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "accepts a short id" do
|
|
25
|
+
expect(helper.parse_container_id("abc123abc123\n")).to eq "abc123abc123"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "fails loudly when the output holds no id" do
|
|
29
|
+
expect { helper.parse_container_id("Error response from daemon: no such image\n") }
|
|
30
|
+
.to raise_error(Kitchen::ActionFailed, /Could not parse Docker run output/)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# run_command returns stdout + stderr, and the parser requires the whole
|
|
34
|
+
# combined string to be exactly the id. Anything docker writes to stderr on
|
|
35
|
+
# a successful `run` therefore fails container creation, with an error that
|
|
36
|
+
# blames parsing rather than naming the warning.
|
|
37
|
+
it "finds the id when docker pulled the image and logged progress to stderr" do
|
|
38
|
+
pending "BUG: parse_container_id requires the entire output to be the id, so any stderr output fails the run"
|
|
39
|
+
expect(helper.parse_container_id(DockerOutput::RUN_WITH_PULL_ON_STDERR))
|
|
40
|
+
.to eq DockerOutput::RUN_CONTAINER_ID
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it "finds the id when the kernel lacks swap accounting" do
|
|
44
|
+
pending "BUG: the 'No swap limit support' warning docker emits on many Linux hosts is concatenated onto the id"
|
|
45
|
+
expect(helper.parse_container_id(DockerOutput::RUN_WITH_SWAP_WARNING))
|
|
46
|
+
.to eq DockerOutput::RUN_CONTAINER_ID
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe "#remote_socket? and #socket_uri" do
|
|
51
|
+
{
|
|
52
|
+
"unix:///var/run/docker.sock" => false,
|
|
53
|
+
"npipe:////./pipe/docker_engine" => false,
|
|
54
|
+
"tcp://docker.example.com:2376" => true,
|
|
55
|
+
"tcp://127.0.0.1:2375" => true,
|
|
56
|
+
}.each do |socket, remote|
|
|
57
|
+
it "treats #{socket} as #{remote ? "remote" : "local"}" do
|
|
58
|
+
expect(helper(socket: socket).remote_socket?).to eq remote
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it "reports a socket-less configuration as local" do
|
|
63
|
+
expect(helper(socket: nil).remote_socket?).to be false
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "exposes the socket as a URI" do
|
|
67
|
+
expect(helper(socket: "tcp://docker.example.com:2376").socket_uri.port).to eq 2376
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
describe "#dockerfile_proxy_config" do
|
|
72
|
+
it "is empty when no proxy is configured" do
|
|
73
|
+
expect(helper.dockerfile_proxy_config).to eq ""
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Tools inside the image disagree about the spelling, so both cases have to
|
|
77
|
+
# be set. Dropping either half breaks builds behind a proxy in a way that
|
|
78
|
+
# only shows up on somebody else's network.
|
|
79
|
+
{
|
|
80
|
+
http_proxy: %w{http_proxy HTTP_PROXY},
|
|
81
|
+
https_proxy: %w{https_proxy HTTPS_PROXY},
|
|
82
|
+
no_proxy: %w{no_proxy NO_PROXY},
|
|
83
|
+
}.each do |option, spellings|
|
|
84
|
+
it "sets #{spellings.join(" and ")} for #{option}" do
|
|
85
|
+
lines = helper(option => "http://proxy:8080").dockerfile_proxy_config.lines.map(&:strip)
|
|
86
|
+
spellings.each { |name| expect(lines).to include "ENV #{name}=http://proxy:8080" }
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it "emits only the proxies that are configured" do
|
|
91
|
+
config = helper(http_proxy: "http://proxy:8080").dockerfile_proxy_config
|
|
92
|
+
expect(config).not_to match(/no_proxy|https_proxy/i)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
describe "#container_env_variables" do
|
|
97
|
+
def with_printenv(output)
|
|
98
|
+
h = helper
|
|
99
|
+
allow(h).to receive(:docker_command).and_return(output)
|
|
100
|
+
h.container_env_variables(platform: "ubuntu-24.04")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "reads the container's environment" do
|
|
104
|
+
expect(with_printenv("HOME=/root\nUSER=kitchen\n"))
|
|
105
|
+
.to eq("HOME" => "/root", "USER" => "kitchen")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# printenv output is `NAME=VALUE`, and values routinely contain `=`:
|
|
109
|
+
# LS_COLORS and any JAVA_OPTS-style variable do. Splitting on every `=`
|
|
110
|
+
# rather than the first silently truncates them, and the truncated value
|
|
111
|
+
# then flows into replace_env_variables and into upload paths.
|
|
112
|
+
it "keeps a value containing an equals sign intact" do
|
|
113
|
+
pending "BUG: values are split on every '=' rather than the first; use split(\"=\", 2)"
|
|
114
|
+
expect(with_printenv("LS_COLORS=rs=0:di=01;34\n"))
|
|
115
|
+
.to eq("LS_COLORS" => "rs=0:di=01;34")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it "parses the JSON a Windows container returns" do
|
|
119
|
+
h = helper
|
|
120
|
+
allow(h).to receive(:docker_command).and_return('{"TEMP":"C:\\\\Users\\\\ADMINI~1\\\\AppData\\\\Local\\\\Temp"}')
|
|
121
|
+
expect(h.container_env_variables(platform: "windows-2022"))
|
|
122
|
+
.to eq("TEMP" => 'C:\Users\ADMINI~1\AppData\Local\Temp')
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
describe "#replace_env_variables" do
|
|
127
|
+
let(:linux_state) { { platform: "ubuntu-24.04", container_id: "abc" } }
|
|
128
|
+
let(:windows_state) { { platform: "windows-2022", container_id: "abc" } }
|
|
129
|
+
|
|
130
|
+
def helper_returning(vars)
|
|
131
|
+
h = helper
|
|
132
|
+
allow(h).to receive(:container_env_variables).and_return(vars)
|
|
133
|
+
h
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it "expands a POSIX $VAR reference" do
|
|
137
|
+
h = helper_returning("TEMP" => "/var/tmp")
|
|
138
|
+
expect(h.replace_env_variables(linux_state, "$TEMP/kitchen")).to eq "/var/tmp/kitchen"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it "expands a PowerShell $env:VAR reference" do
|
|
142
|
+
h = helper_returning("TEMP" => 'C:\Temp')
|
|
143
|
+
expect(h.replace_env_variables(windows_state, '$env:TEMP\kitchen')).to eq 'C:\Temp\kitchen'
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
it "leaves a path with no variable reference alone" do
|
|
147
|
+
expect(helper.replace_env_variables(linux_state, "/tmp/kitchen")).to eq "/tmp/kitchen"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it "substitutes an empty string when the variable is not set in the container" do
|
|
151
|
+
# Documents current behaviour rather than endorsing it: an unset variable
|
|
152
|
+
# silently yields a path like "/kitchen" instead of failing, and the
|
|
153
|
+
# upload then lands somewhere unexpected.
|
|
154
|
+
h = helper_returning({})
|
|
155
|
+
expect(h.replace_env_variables(linux_state, "$TEMP/kitchen")).to eq "/kitchen"
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
describe "#remove_container" do
|
|
160
|
+
it "stops the container before removing it" do
|
|
161
|
+
# `docker rm` refuses to remove a running container, so the order matters.
|
|
162
|
+
h = helper
|
|
163
|
+
calls = []
|
|
164
|
+
allow(h).to receive(:docker_command) { |cmd| calls << cmd }
|
|
165
|
+
h.remove_container(container_id: "abc")
|
|
166
|
+
expect(calls).to eq ["stop -t 0 abc", "rm abc"]
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
describe "#copy_file_to_container" do
|
|
171
|
+
it "addresses the destination as container:path" do
|
|
172
|
+
h = helper
|
|
173
|
+
allow(h).to receive(:replace_env_variables) { |_state, path| path }
|
|
174
|
+
expect(h).to receive(:docker_command).with("cp /local/f.rb abc:/tmp/f.rb")
|
|
175
|
+
h.copy_file_to_container({ container_id: "abc", platform: "ubuntu-24.04" }, "/local/f.rb", "/tmp/f.rb")
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|