Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ Each of these accepts a single value or a list.
| `binary` | `docker` | Docker CLI to invoke — e.g. `docker.io`, or an absolute path. |
| `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. |
| `use_sudo` | `false` | Run every `docker` command through `sudo`. |
| `sudo_command` | `sudo -E` | The command `use_sudo` prefixes, for hosts that use something else (`doas`, say). |
| `tls` | `false` | Use TLS when connecting. |
| `tls_verify` | `false` | Verify the daemon's certificate. |
| `tls_cacert` | *(none)* | Path to the CA certificate. |
Expand Down Expand Up @@ -314,11 +315,13 @@ These options go under `transport:`, not `driver:`.
| `privileged` | `false` | Run commands with `--privileged`. |
| `interactive` | `false` | Pass `-i`. |
| `tty` | `false` | Pass `-t`. |
| `use_sudo` | `false` | Run every `docker` command through `sudo`. |
| `sudo_command` | `sudo -E` | The command `use_sudo` prefixes. |
| `tls`, `tls_verify`, `tls_cacert`, `tls_cert`, `tls_key` | as for the driver | TLS settings for the daemon connection. |

The driver and transport each read their own copy of `binary`, `socket`,
`username`, and the TLS settings. If you point one at a non-default daemon,
point the other at it too.
`username`, `use_sudo`, and the TLS settings. If you point one at a non-default
daemon, or need `sudo` to reach it, configure the other the same way.

## Logging into a container

Expand Down Expand Up @@ -534,7 +537,8 @@ bootstrap. Set it to a supported family, or supply your own
to `false` against a remote daemon.

**Permission denied talking to the daemon.** Either add your user to the
`docker` group, or set `use_sudo: true`.
`docker` group, or set `use_sudo: true` under **both** `driver:` and
`transport:` -- the transport runs its own `docker exec` and `docker cp`.

**Anything else.** Run with `-l debug`:

Expand Down
28 changes: 28 additions & 0 deletions lib/kitchen/docker/helpers/cli_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,39 @@ def docker_command(cmd, options = {})
docker << " --tlscacert=#{shell_escape(config[:tls_cacert])}" if config[:tls_cacert]
docker << " --tlscert=#{shell_escape(config[:tls_cert])}" if config[:tls_cert]
docker << " --tlskey=#{shell_escape(config[:tls_key])}" if config[:tls_key]
options = docker_sudo_opts(options)
logger.debug("docker_command: #{docker} #{cmd} shell_opts: #{docker_shell_opts(options)}")
run_command("#{docker} #{cmd}", docker_shell_opts(options))
end
# rubocop:enable Metrics/AbcSize

# Adds the sudo options {#run_command} reads, when +use_sudo+ is set.
#
# Sudo is a property of the call rather than of the configuration as far
# as the shell-out layer is concerned: it reads +:use_sudo+ from the
# options hash it is handed and knows nothing about +config+. So a
# docker command only runs through sudo if these are passed to it.
#
# Without this, +use_sudo+ reached exactly one command -- the
# `docker` probe in +verify_dependencies+ -- and every build, run,
# exec, cp, and rm still ran as the invoking user. On a host where the
# daemon socket needs root, that made the documented answer to
# "permission denied while trying to connect to the Docker daemon
# socket" do nothing at all.
#
# A copy is returned rather than the hash being edited in place, so a
# caller that reuses its options hash does not accumulate sudo.
#
# @param options [Hash] shell-out options
# @return [Hash] those options, with sudo added when configured
def docker_sudo_opts(options = {})
return options unless config[:use_sudo]

options = options.merge(use_sudo: true)
options[:sudo_command] = config[:sudo_command] if config[:sudo_command]
options
end

# rubocop:disable Metrics/MethodLength, Metrics/AbcSize

# Runs a shell command, returning stderr as well as stdout.
Expand Down
1 change: 1 addition & 0 deletions lib/kitchen/driver/docker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class Docker < Kitchen::Driver::Base
default_config :remove_images, false
default_config :run_options, nil
default_config :security_opt, nil
default_config :sudo_command, nil
default_config :tls, false
default_config :tls_cacert, nil
default_config :tls_cert, nil
Expand Down
2 changes: 2 additions & 0 deletions lib/kitchen/transport/docker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,14 @@ class DockerFailed < TransportFailed; end
default_config :env_variables, nil
default_config :interactive, false
default_config :privileged, false
default_config :sudo_command, nil
default_config :tls, false
default_config :tls_cacert, nil
default_config :tls_cert, nil
default_config :tls_key, nil
default_config :tls_verify, false
default_config :tty, false
default_config :use_sudo, false
default_config :working_dir, nil

default_config :socket do |transport|
Expand Down
49 changes: 49 additions & 0 deletions spec/cli_helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,55 @@ def exec_argv(config = {}, command: "whoami")
end
end

describe "#docker_command" do
# `binary` is set to `echo` and `sudo_command` to `echo SUDO`, so the
# assembled command line is observable as output without needing real
# sudo -- or a real docker -- on the machine running the specs.
def echoed(config = {})
helper({ binary: "echo", socket: nil, sudo_command: "echo SUDO" }.merge(config))
.docker_command("ps -a")
end

it "runs the command as the invoking user by default" do
expect(echoed).to eq "ps -a\n"
end

# Cases from the README's "Permission denied talking to the daemon"
# advice. `use_sudo` reached only the `verify_dependencies` probe, so
# every command that actually touches the daemon still ran unprivileged
# and the documented fix did nothing.
it "runs the command through sudo when use_sudo is set" do
expect(echoed(use_sudo: true)).to eq "SUDO echo ps -a\n"
end

it "leaves the sudo command at its default when none is configured" do
expect(helper(binary: "echo", socket: nil, use_sudo: true).docker_sudo_opts({}))
.to eq(use_sudo: true)
end
end

describe "#docker_sudo_opts" do
it "adds nothing when use_sudo is unset" do
expect(helper.docker_sudo_opts(suppress_output: true)).to eq(suppress_output: true)
end

it "adds the sudo options the shell-out layer reads" do
expect(helper(use_sudo: true, sudo_command: "doas").docker_sudo_opts({}))
.to eq(use_sudo: true, sudo_command: "doas")
end

it "keeps the options it was given" do
expect(helper(use_sudo: true).docker_sudo_opts(suppress_output: true))
.to eq(suppress_output: true, use_sudo: true)
end

it "does not mutate the options it was given" do
options = {}
helper(use_sudo: true).docker_sudo_opts(options)
expect(options).to eq({})
end
end

describe "#docker_shell_opts" do
it "translates suppress_output into silencing the live stream" do
expect(helper.docker_shell_opts(suppress_output: true)).to eq(live_stream: nil)
Expand Down
Loading