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:
parent
084a8e8fe7
commit
bf96b3348b
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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!
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue