kitchen-docker 3.3.0 → 3.3.3
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 +399 -20
- 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 +73 -6
- data/lib/kitchen/docker/container/windows.rb +34 -3
- data/lib/kitchen/docker/container.rb +45 -1
- 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 +111 -34
- data/lib/kitchen/docker/helpers/container_helper.rb +151 -25
- data/lib/kitchen/docker/helpers/dockerfile_helper.rb +54 -0
- data/lib/kitchen/docker/helpers/file_helper.rb +24 -10
- data/lib/kitchen/docker/helpers/image_helper.rb +47 -1
- 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 +320 -0
- data/spec/container_helper_spec.rb +292 -0
- data/spec/container_spec.rb +115 -0
- data/spec/dockerfile_helper_spec.rb +97 -61
- data/spec/erb_context_spec.rb +60 -0
- data/spec/file_helper_spec.rb +110 -0
- data/spec/image_helper_spec.rb +181 -0
- data/spec/linux_container_spec.rb +252 -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 +15 -2
|
@@ -21,8 +21,14 @@ require_relative "../docker/helpers/inspec_helper"
|
|
|
21
21
|
# require_relative "../../docker/version"
|
|
22
22
|
|
|
23
23
|
module Kitchen
|
|
24
|
+
# Test Kitchen's transport plugins.
|
|
24
25
|
module Transport
|
|
26
|
+
# Test Kitchen transport that runs commands inside a Docker container.
|
|
27
|
+
#
|
|
28
|
+
# Commands go through `docker exec` rather than a network protocol, so no
|
|
29
|
+
# SSH or WinRM server is needed inside the image.
|
|
25
30
|
class Docker < Kitchen::Transport::Base
|
|
31
|
+
# Raised when a docker command against the container fails.
|
|
26
32
|
class DockerFailed < TransportFailed; end
|
|
27
33
|
|
|
28
34
|
# kitchen_transport_api_version 1
|
|
@@ -64,6 +70,15 @@ module Kitchen
|
|
|
64
70
|
end
|
|
65
71
|
end
|
|
66
72
|
|
|
73
|
+
# Builds a connection to the container.
|
|
74
|
+
#
|
|
75
|
+
# +DOCKER_HOST+ is exported here because the docker-api gem, used by the
|
|
76
|
+
# InSpec verifier, reads the daemon address from the environment rather
|
|
77
|
+
# than from Test Kitchen's configuration.
|
|
78
|
+
#
|
|
79
|
+
# @param state [Hash] instance state naming the container
|
|
80
|
+
# @yieldparam connection [Connection] if a block is given
|
|
81
|
+
# @return [Connection]
|
|
67
82
|
def connection(state, &block)
|
|
68
83
|
options = config.to_hash.merge(state)
|
|
69
84
|
options[:platform] = instance.platform.name
|
|
@@ -77,10 +92,19 @@ module Kitchen
|
|
|
77
92
|
Kitchen::Transport::Docker::Connection.new(options, &block)
|
|
78
93
|
end
|
|
79
94
|
|
|
80
|
-
|
|
95
|
+
# A connection to one container.
|
|
96
|
+
#
|
|
97
|
+
# The superclass is named in full rather than relying on Ruby resolving
|
|
98
|
+
# the bare `Connection` constant through this class's ancestors.
|
|
99
|
+
class Connection < Kitchen::Transport::Base::Connection
|
|
81
100
|
# Include the InSpec patches to be able to execute tests on Windows containers
|
|
82
101
|
include Kitchen::Docker::Helpers::InspecHelper
|
|
83
102
|
|
|
103
|
+
# Runs a command inside the container.
|
|
104
|
+
#
|
|
105
|
+
# @param command [String] the command to run; nil is a no-op
|
|
106
|
+
# @return [void]
|
|
107
|
+
# @raise [DockerFailed] if the command fails
|
|
84
108
|
def execute(command)
|
|
85
109
|
return if command.nil?
|
|
86
110
|
|
|
@@ -92,10 +116,18 @@ module Kitchen
|
|
|
92
116
|
raise DockerFailed, "Docker failed to execute command on container. Error Details: #{e}"
|
|
93
117
|
end
|
|
94
118
|
|
|
119
|
+
# Copies local files into the container.
|
|
120
|
+
#
|
|
121
|
+
# @param locals [String, Array<String>] one path or several
|
|
122
|
+
# @param remote [String] destination path inside the container
|
|
123
|
+
# @return [Array<String>] the files copied
|
|
95
124
|
def upload(locals, remote)
|
|
96
125
|
container.upload(locals, remote)
|
|
97
126
|
end
|
|
98
127
|
|
|
128
|
+
# The container implementation for this platform.
|
|
129
|
+
#
|
|
130
|
+
# @return [Kitchen::Docker::Container] a Windows or Linux container
|
|
99
131
|
def container
|
|
100
132
|
@container ||= if windows_container?
|
|
101
133
|
Kitchen::Docker::Container::Windows.new(@options)
|
|
@@ -105,7 +137,13 @@ module Kitchen
|
|
|
105
137
|
@container
|
|
106
138
|
end
|
|
107
139
|
|
|
108
|
-
#
|
|
140
|
+
# The command `kitchen login` execs to open a shell in the container.
|
|
141
|
+
#
|
|
142
|
+
# Documented here rather than inherited via `(see ...)`, because the
|
|
143
|
+
# superclass lives in the test-kitchen gem and YARD cannot resolve a
|
|
144
|
+
# reference into it from this project's docs.
|
|
145
|
+
#
|
|
146
|
+
# @return [Kitchen::LoginCommand] an interactive `docker exec` session
|
|
109
147
|
def login_command
|
|
110
148
|
argv = build_login_command
|
|
111
149
|
LoginCommand.new(argv.first, argv.drop(1))
|
|
@@ -113,6 +151,7 @@ module Kitchen
|
|
|
113
151
|
|
|
114
152
|
private
|
|
115
153
|
|
|
154
|
+
# @return [Boolean] whether the platform under test is Windows
|
|
116
155
|
def windows_container?
|
|
117
156
|
@options[:platform].to_s.include?("windows")
|
|
118
157
|
end
|
|
@@ -150,6 +189,8 @@ module Kitchen
|
|
|
150
189
|
docker + cmd
|
|
151
190
|
end
|
|
152
191
|
|
|
192
|
+
# @return [Array<String>] the shell to drop the user into, PowerShell on
|
|
193
|
+
# Windows and an interactive login bash elsewhere
|
|
153
194
|
def login_shell
|
|
154
195
|
windows_container? ? ["powershell"] : ["/bin/bash", "--login", "-i"]
|
|
155
196
|
end
|
|
@@ -0,0 +1,320 @@
|
|
|
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
|
+
expect(run_argv(volume: "/host dir:/data")).to include_consecutive("-v", "/host dir:/data")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it "keeps a hostname containing a space as one argument" do
|
|
111
|
+
expect(run_argv(hostname: "two words")).to include_consecutive("-h", "two words")
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it "keeps a mount specification containing a space as one argument" do
|
|
115
|
+
expect(run_argv(mount: "type=bind,source=/my dir,destination=/d"))
|
|
116
|
+
.to include_consecutive("--mount", "type=bind,source=/my dir,destination=/d")
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
describe "#build_exec_command" do
|
|
122
|
+
let(:state) { { container_id: "abc123" } }
|
|
123
|
+
|
|
124
|
+
def exec_argv(config = {}, command: "whoami")
|
|
125
|
+
argv(helper(config).build_exec_command(state, command))
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it "execs the command in the container" do
|
|
129
|
+
expect(exec_argv).to eq %w{exec abc123 whoami}
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
{
|
|
133
|
+
"detach" => [{ detach: true }, %w{-d}],
|
|
134
|
+
"privileged" => [{ privileged: true }, %w{--privileged}],
|
|
135
|
+
"tty" => [{ tty: true }, %w{-t}],
|
|
136
|
+
"interactive" => [{ interactive: true }, %w{-i}],
|
|
137
|
+
"username" => [{ username: "kitchen" }, %w{-u kitchen}],
|
|
138
|
+
"working_dir" => [{ working_dir: "/srv" }, %w{-w /srv}],
|
|
139
|
+
}.each do |option, (config, expected)|
|
|
140
|
+
it "passes #{option} through as #{expected.join(" ")}" do
|
|
141
|
+
expect(exec_argv(config)).to include_consecutive(*expected)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it "puts the container id before the command" do
|
|
146
|
+
# Reversing these makes Docker try to exec into the command name, with an
|
|
147
|
+
# error that does not point anywhere near the cause.
|
|
148
|
+
result = exec_argv({ username: "kitchen" }, command: "whoami")
|
|
149
|
+
expect(result.index("abc123")).to be < result.index("whoami")
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
describe "#build_env_variable_args" do
|
|
154
|
+
it "emits one -e flag per variable" do
|
|
155
|
+
expect(argv(helper.build_env_variable_args("A" => "1", "B" => "2")))
|
|
156
|
+
.to eq %w{-e A=1 -e B=2}
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it "strips surrounding whitespace from names and values" do
|
|
160
|
+
expect(argv(helper.build_env_variable_args(" A " => " 1 "))).to eq %w{-e A=1}
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
it "refuses anything that is not a Hash" do
|
|
164
|
+
# The driver would otherwise build "-e" flags out of an Array's elements
|
|
165
|
+
# and produce a command line that fails somewhere far from the cause.
|
|
166
|
+
expect { helper.build_env_variable_args(%w{A=1}) }
|
|
167
|
+
.to raise_error(Kitchen::ActionFailed, /not of a Hash type/)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
it "keeps a value containing a space as one argument" do
|
|
171
|
+
expect(argv(helper.build_env_variable_args("MSG" => "hello world")))
|
|
172
|
+
.to eq ["-e", "MSG=hello world"]
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
it "keeps a value containing a double quote intact" do
|
|
176
|
+
expect(argv(helper.build_env_variable_args("MSG" => 'say "hi"')))
|
|
177
|
+
.to eq ["-e", 'MSG=say "hi"']
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
it "keeps a value containing a dollar sign out of the shell's reach" do
|
|
181
|
+
# An unescaped $HOME would be expanded by the shell running the docker
|
|
182
|
+
# command, so the container would receive the workstation's home
|
|
183
|
+
# directory instead of the literal string.
|
|
184
|
+
expect(argv(helper.build_env_variable_args("P" => "$HOME/x")))
|
|
185
|
+
.to eq ["-e", "P=$HOME/x"]
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it "keeps a name containing a space as one argument" do
|
|
189
|
+
expect(argv(helper.build_env_variable_args("ODD NAME" => "v")))
|
|
190
|
+
.to eq ["-e", "ODD NAME=v"]
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
describe "#build_copy_command" do
|
|
195
|
+
it "copies local to remote" do
|
|
196
|
+
expect(argv(helper.build_copy_command("/tmp/a", "abc:/tmp/a")))
|
|
197
|
+
.to eq %w{cp /tmp/a abc:/tmp/a}
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
it "preserves ownership and mode when asked" do
|
|
201
|
+
expect(argv(helper.build_copy_command("/tmp/a", "abc:/tmp/a", archive: true)))
|
|
202
|
+
.to eq %w{cp -a /tmp/a abc:/tmp/a}
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
it "keeps a local path containing a space as one argument" do
|
|
206
|
+
expect(argv(helper.build_copy_command("/Users/me/My Cookbooks/f.rb", "abc:/tmp/f.rb")))
|
|
207
|
+
.to eq ["cp", "/Users/me/My Cookbooks/f.rb", "abc:/tmp/f.rb"]
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
describe "#config_to_options" do
|
|
212
|
+
subject { helper.config_to_options(input) }
|
|
213
|
+
|
|
214
|
+
context "with nil" do
|
|
215
|
+
let(:input) { nil }
|
|
216
|
+
|
|
217
|
+
it { is_expected.to eq "" }
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
context "with a string" do
|
|
221
|
+
let(:input) { "--foo" }
|
|
222
|
+
|
|
223
|
+
it { is_expected.to eq "--foo" }
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
context "with a string with spaces" do
|
|
227
|
+
let(:input) { "--foo bar" }
|
|
228
|
+
|
|
229
|
+
it { is_expected.to eq "--foo bar" }
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
context "with an array of strings" do
|
|
233
|
+
let(:input) { %w{--foo --bar} }
|
|
234
|
+
|
|
235
|
+
it { is_expected.to eq "--foo --bar" }
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
context "with an array of hashes" do
|
|
239
|
+
let(:input) { [{ foo: "bar" }, { other: "baz" }] }
|
|
240
|
+
|
|
241
|
+
it { is_expected.to eq "--foo=bar --other=baz" }
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
context "with a hash of strings" do
|
|
245
|
+
let(:input) { { foo: "bar", other: "baz" } }
|
|
246
|
+
|
|
247
|
+
it { is_expected.to eq "--foo=bar --other=baz" }
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
context "with a hash of arrays" do
|
|
251
|
+
let(:input) { { foo: %w{bar baz} } }
|
|
252
|
+
|
|
253
|
+
it { is_expected.to eq "--foo=bar --foo=baz" }
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
context "with a hash of strings with spaces" do
|
|
257
|
+
let(:input) { { foo: "bar two", other: "baz" } }
|
|
258
|
+
|
|
259
|
+
it { is_expected.to eq '--foo=bar\\ two --other=baz' }
|
|
260
|
+
|
|
261
|
+
it "survives shell splitting as one argument per flag" do
|
|
262
|
+
expect(argv(subject)).to eq ["--foo=bar two", "--other=baz"]
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
context "with a boolean value, as the README's build_options example uses" do
|
|
267
|
+
let(:input) { { rm: false } }
|
|
268
|
+
|
|
269
|
+
it { is_expected.to eq "--rm=false" }
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
describe "#run_command" do
|
|
274
|
+
it "returns stdout and stderr together" do
|
|
275
|
+
# docker writes build progress and image ids to stderr, so the driver
|
|
276
|
+
# reimplements run_command specifically to keep both streams.
|
|
277
|
+
expect(helper.run_command("echo out; echo err 1>&2")).to eq "out\nerr\n"
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
it "raises Kitchen::ShellOut::ShellCommandFailed when the command fails" do
|
|
281
|
+
# cli_helper raises a bare `ShellCommandFailed`, which resolves through
|
|
282
|
+
# the included Kitchen::ShellOut rather than being spelled out. Pin the
|
|
283
|
+
# class so a change to those includes cannot silently alter what callers
|
|
284
|
+
# have to rescue.
|
|
285
|
+
expect { helper.run_command("exit 7") }
|
|
286
|
+
.to raise_error(Kitchen::ShellOut::ShellCommandFailed, /exit with \[0\], but received '7'/)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
it "prefixes the command with the sudo command when use_sudo is set" do
|
|
290
|
+
# Uses `echo` as the sudo command so the prefixing is observable without
|
|
291
|
+
# needing real sudo on the machine running the specs.
|
|
292
|
+
expect(helper.run_command("echo hi", use_sudo: true, sudo_command: "echo SUDO"))
|
|
293
|
+
.to eq "SUDO echo hi\n"
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
it "does not prefix anything when use_sudo is unset" do
|
|
297
|
+
expect(helper.run_command("echo hi")).to eq "hi\n"
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
describe "#docker_shell_opts" do
|
|
302
|
+
it "translates suppress_output into silencing the live stream" do
|
|
303
|
+
expect(helper.docker_shell_opts(suppress_output: true)).to eq(live_stream: nil)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
it "removes suppress_output, which Mixlib::ShellOut would reject" do
|
|
307
|
+
expect(helper.docker_shell_opts(suppress_output: false)).not_to have_key(:suppress_output)
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
it "leaves other options alone" do
|
|
311
|
+
expect(helper.docker_shell_opts(timeout: 60)).to eq(timeout: 60)
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
describe "#dev_null" do
|
|
316
|
+
it "returns a device that exists on this platform" do
|
|
317
|
+
expect(helper.dev_null).to eq(Gem.win_platform? ? "NUL" : "/dev/null")
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
end
|