provider/docker: Add docker-exec command

This adds a new core command, `docker-exec`, which allows the user to
exec into an already-running container.

- Fixes #6566
- Fixes #5193
- Fixes #4904
- Fixes #4057
- Fixes #4179
- Fixes #4903
This commit is contained in:
Seth Vargo 2016-05-31 18:43:21 -04:00
parent 084a8e8fe7
commit bf96b3348b
No known key found for this signature in database
GPG Key ID: 905A90C2949E8787
8 changed files with 234 additions and 1 deletions

View File

@ -53,7 +53,7 @@ __vagrantinvestigate() {
_vagrant() { _vagrant() {
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}" prev="${COMP_WORDS[COMP_CWORD-1]}"
commands="box connect destroy docker-logs docker-run global-status halt help init list-commands login package plugin provision push rdp reload resume rsync rsync-auto share snapshot ssh ssh-config status suspend up version" commands="box connect destroy docker-exec docker-logs docker-run global-status halt help init list-commands login package plugin provision push rdp reload resume rsync rsync-auto share snapshot ssh ssh-config status suspend up version"
if [ $COMP_CWORD == 1 ] if [ $COMP_CWORD == 1 ]
then then

View File

@ -0,0 +1,93 @@
module VagrantPlugins
module DockerProvider
module Command
class Exec < Vagrant.plugin("2", :command)
def self.synopsis
"attach to an already-running docker container"
end
def execute
options = {}
options[:detach] = false
options[:pty] = false
options[:prefix] = true
opts = OptionParser.new do |o|
o.banner = "Usage: vagrant docker-exec [options] [name] -- <command> [args]"
o.separator ""
o.separator "Options:"
o.separator ""
o.on("--[no-]detach", "Run in the background") do |d|
options[:detach] = d
end
o.on("-t", "--[no-]tty", "Allocate a pty") do |t|
options[:pty] = t
end
o.on("--[no-]prefix", "Prefix output with machine names") do |p|
options[:prefix] = p
end
end
# Parse out the extra args to send to SSH, which is everything
# after the "--"
command = nil
split_index = @argv.index("--")
if split_index
command = @argv.drop(split_index + 1)
@argv = @argv.take(split_index)
end
# Parse the options
argv = parse_options(opts)
return if !argv
# Show the error if we don't have "--" _after_ parse_options
# so that "-h" and "--help" work properly.
if !split_index
raise Errors::ExecCommandRequired
end
target_opts = { provider: :docker }
target_opts[:single_target] = options[:pty]
with_target_vms(argv, target_opts) do |machine|
if machine.state.id != :running
@env.ui.info("#{machine.id} is not running.")
next
end
exec_command(machine, options, command)
end
return 0
end
def exec_command(machine, options, command)
exec_cmd = %w(docker exec)
exec_cmd << "-it" if options[:pty]
exec_cmd << machine.id
exec_cmd += options[:extra_args] if options[:extra_args]
exec_cmd += command
# Run this interactively if asked.
exec_options = options
exec_options[:stdin] = true if options[:pty]
output = ""
machine.provider.driver.execute(*exec_cmd, exec_options) do |type, data|
output += data
end
output_options = {}
output_options[:prefix] = false if !options[:prefix]
if !output.empty?
machine.ui.output(output.chomp, **output_options)
end
end
end
end
end
end

View File

@ -13,6 +13,10 @@ module VagrantPlugins
error_key(:execute_error) error_key(:execute_error)
end end
class ExecCommandRequired < DockerError
error_key(:exec_command_required)
end
class HostVMCommunicatorNotReady < DockerError class HostVMCommunicatorNotReady < DockerError
error_key(:host_vm_communicator_not_ready) error_key(:host_vm_communicator_not_ready)
end end

View File

@ -22,6 +22,12 @@ module VagrantPlugins
Provider Provider
end end
command("docker-exec", primary: false) do
require_relative "command/exec"
init!
Command::Exec
end
command("docker-logs", primary: false) do command("docker-logs", primary: false) do
require_relative "command/logs" require_relative "command/logs"
init! init!

View File

@ -153,6 +153,14 @@ en:
Stderr: %{stderr} Stderr: %{stderr}
Stdout: %{stdout} Stdout: %{stdout}
exec_command_required: |-
The "docker-exec" command requires a command to execute. This command
must be specified after a "--" in the command line. This is used to
separate machine name and options from the actual command to execute.
An example is show below:
$ vagrant docker-exec -t nginx -- bash
host_vm_communicator_not_ready: |- host_vm_communicator_not_ready: |-
The Docker provider was able to bring up the host VM successfully The Docker provider was able to bring up the host VM successfully
but the host VM is still reporting that SSH is unavailable. This but the host VM is still reporting that SSH is unavailable. This

View File

@ -0,0 +1,44 @@
require_relative "../../../../base"
require_relative "../../../../../../plugins/providers/docker/command/exec"
describe VagrantPlugins::DockerProvider::Command::Exec do
include_context "unit"
include_context "command plugin helpers"
let(:sandbox) do
isolated_environment
end
let(:argv) { [] }
let(:env) { sandbox.create_vagrant_env }
let(:vagrantfile_path) { File.join(env.cwd, "Vagrantfile") }
subject { described_class.new(argv, env) }
before(:all) do
I18n.load_path << Vagrant.source_root.join("templates/locales/providers_docker.yml")
I18n.reload!
end
before do
Vagrant.plugin("2").manager.stub(commands: {})
allow(subject).to receive(:exec_command)
end
after do
sandbox.close
end
describe "#execute" do
describe "without a command" do
let(:argv) { [] }
it "raises an error" do
expect {
subject.execute
}.to raise_error(VagrantPlugins::DockerProvider::Errors::ExecCommandRequired)
end
end
end
end

View File

@ -27,6 +27,7 @@ non-primary subcommands. They're executed just like any other subcommand:
The list of non-primary commands is below. Click on any command to learn The list of non-primary commands is below. Click on any command to learn
more about it. more about it.
* [docker-exec](/docs/docker/commands.html)
* [docker-logs](/docs/docker/commands.html) * [docker-logs](/docs/docker/commands.html)
* [docker-run](/docs/docker/commands.html) * [docker-run](/docs/docker/commands.html)
* [rsync](/docs/cli/rsync.html) * [rsync](/docs/cli/rsync.html)

View File

@ -16,6 +16,83 @@ useful for interacting with Docker containers. This helps with your
workflow on top of Vagrant so that you have full access to Docker workflow on top of Vagrant so that you have full access to Docker
underneath. underneath.
### docker-exec
`vagrant docker-exec` can be used to run one-off commands against
a Docker container that is currently running. If the container is not running,
an error will be returned.
```sh
$ vagrant docker-exec app -- rake db:migrate
```
The above would run `rake db:migrate` in the context of an `app` container.
Note that the "name" corresponds to the name of the VM, **not** the name of the
Docker container. Consider the following Vagrantfile:
```ruby
Vagrant.configure(2) do |config|
config.vm.provider "docker" do |d|
d.image = "consul"
end
end
```
This Vagrantfile will start the official Docker Consul image. However, the
associated Vagrant command to `docker-exec` into this instance is:
```sh
$ vagrant docker-exec -t -- /bin/sh
```
In particular, the command is actually:
```sh
$ vagrant docker-exec default -t -- /bin/sh
```
Because "default" is the default name of the first defined VM. In a
multi-machine Vagrant setup as shown below, the "name" attribute corresponds
to the name of the VM, **not** the name of the container:
```ruby
Vagrant.configure do |config|
config.vm.define "web" do
config.vm.provider "docker" do |d|
d.image = "nginx"
end
end
config.vm.define "consul" do
config.vm.provider "docker" do |d|
d.image = "consul"
end
end
end
```
The following command is invalid:
```sh
# Not valid
$ vagrant docker-exec -t nginx -- /bin/sh
```
This is because the "name" of the VM is "web", so the command is actually:
```sh
$ vagrant docker-exec -t web -- /bin/sh
```
For this reason, it is recommended that you name the VM the same as the
container. In the above example, it is unambiguous that the command to enter
the Consul container is:
```sh
$ vagrant docker-exec -t consul -- /bin/sh
```
### docker-logs ### docker-logs
`vagrant docker-logs` can be used to see the logs of a running container. `vagrant docker-logs` can be used to see the logs of a running container.