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() {
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
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 ]
|
||||
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)
|
||||
end
|
||||
|
||||
class ExecCommandRequired < DockerError
|
||||
error_key(:exec_command_required)
|
||||
end
|
||||
|
||||
class HostVMCommunicatorNotReady < DockerError
|
||||
error_key(:host_vm_communicator_not_ready)
|
||||
end
|
||||
|
|
|
@ -22,6 +22,12 @@ module VagrantPlugins
|
|||
Provider
|
||||
end
|
||||
|
||||
command("docker-exec", primary: false) do
|
||||
require_relative "command/exec"
|
||||
init!
|
||||
Command::Exec
|
||||
end
|
||||
|
||||
command("docker-logs", primary: false) do
|
||||
require_relative "command/logs"
|
||||
init!
|
||||
|
|
|
@ -153,6 +153,14 @@ en:
|
|||
Stderr: %{stderr}
|
||||
|
||||
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: |-
|
||||
The Docker provider was able to bring up the host VM successfully
|
||||
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
|
||||
more about it.
|
||||
|
||||
* [docker-exec](/docs/docker/commands.html)
|
||||
* [docker-logs](/docs/docker/commands.html)
|
||||
* [docker-run](/docs/docker/commands.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
|
||||
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
|
||||
|
||||
`vagrant docker-logs` can be used to see the logs of a running container.
|
||||
|
|
Loading…
Reference in New Issue