From 03e18d16680d7e9654156d0a860f720222e9eb15 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 27 Apr 2014 17:21:46 -0700 Subject: [PATCH 1/8] core: better logging around locks --- lib/vagrant/environment.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 8189b3337..cf37ffeb4 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -390,6 +390,7 @@ module Vagrant # reset to false so we don't think we have a lock when we # actually don't. @locks.delete(name) + @logger.info("Released process lock: #{name}") end # Clean up the lock file, this requires another lock From 2694f746a7c3e0defa4867595fd3defbaec5df39 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 27 Apr 2014 17:26:24 -0700 Subject: [PATCH 2/8] providers/virtualbox: no SSH info if not running --- plugins/providers/virtualbox/provider.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/providers/virtualbox/provider.rb b/plugins/providers/virtualbox/provider.rb index a52c07fb3..70adf34ef 100644 --- a/plugins/providers/virtualbox/provider.rb +++ b/plugins/providers/virtualbox/provider.rb @@ -56,9 +56,8 @@ module VagrantPlugins # Returns the SSH info for accessing the VirtualBox VM. def ssh_info - # If the VM is not created then we cannot possibly SSH into it, so - # we return nil. - return nil if state.id == :not_created + # If the VM is not running that we can't possibly SSH into it + return nil if state.id != :running # Return what we know. The host is always "127.0.0.1" because # VirtualBox VMs are always local. The port we try to discover From abf7c0526bc48d4122ab9ed81acdb994060fa3d2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 27 Apr 2014 18:10:41 -0700 Subject: [PATCH 3/8] providers/docker: docker-run command starting points --- plugins/providers/docker/action.rb | 59 ++++++++++++++----- plugins/providers/docker/action/create.rb | 31 ++++++++-- plugins/providers/docker/command/run.rb | 70 +++++++++++++++++++++++ plugins/providers/docker/driver.rb | 1 + plugins/providers/docker/plugin.rb | 6 ++ templates/locales/providers_docker.yml | 11 ++++ 6 files changed, 159 insertions(+), 19 deletions(-) create mode 100644 plugins/providers/docker/command/run.rb diff --git a/plugins/providers/docker/action.rb b/plugins/providers/docker/action.rb index f6faddaee..7b24bb020 100644 --- a/plugins/providers/docker/action.rb +++ b/plugins/providers/docker/action.rb @@ -4,6 +4,27 @@ module VagrantPlugins # Include the built-in modules so we can use them as top-level things. include Vagrant::Action::Builtin + # This action starts another container just like the real one running + # but only for the purpose of running a single command rather than + # to exist long-running. + def self.action_run_command + Vagrant::Action::Builder.new.tap do |b| + b.use ConfigValidate + b.use HostMachine + + b.use Call, IsState, :not_created do |env, b2| + if env[:result] + b2.use Message, + I18n.t("docker_provider.messages.not_created_original") + next + else + end + end + + b.use action_start + end + end + # This action brings the "machine" up from nothing, including creating the # container, configuring metadata, and booting. def self.action_up @@ -198,16 +219,18 @@ module VagrantPlugins def self.action_start Vagrant::Action::Builder.new.tap do |b| b.use Call, IsState, :running do |env, b2| - # If the container is running, then our work here is done, exit - next if env[:result] + # If the container is running and we're doing a run, we're done + next if env[:result] && env[:machine_action] != :run_command - b2.use Call, HasSSH do |env2, b3| - if env2[:result] - b3.use Provision - else - b3.use Message, - I18n.t("docker_provider.messages.provision_no_ssh"), - post: true + if env[:machine_action] != :run_command + b2.use Call, HasSSH do |env2, b3| + if env2[:result] + b3.use Provision + else + b3.use Message, + I18n.t("docker_provider.messages.provision_no_ssh"), + post: true + end end end @@ -224,7 +247,7 @@ module VagrantPlugins b2.use PrepareNFSSettings b2.use Build - # If the VM is NOT created yet, then do some setup steps + # If the container is NOT created yet, then do some setup steps # necessary for creating it. b2.use Call, IsState, :not_created do |env2, b3| if env2[:result] @@ -240,12 +263,18 @@ module VagrantPlugins end end - b2.use Start - b2.use WaitForRunning + # If we're doing a one-off command, then we create + if env[:machine_action] == :run_command + b2.use SyncedFolders + b2.use Create + else + b2.use Start + b2.use WaitForRunning - b2.use Call, HasSSH do |env2, b3| - if env2[:result] - b3.use WaitForCommunicator + b2.use Call, HasSSH do |env2, b3| + if env2[:result] + b3.use WaitForCommunicator + end end end end diff --git a/plugins/providers/docker/action/create.rb b/plugins/providers/docker/action/create.rb index 031573e52..381eee7f7 100644 --- a/plugins/providers/docker/action/create.rb +++ b/plugins/providers/docker/action/create.rb @@ -15,9 +15,27 @@ module VagrantPlugins params = create_params + # If we're running a single command, we modify the params a bit + if env[:machine_action] == :run_command + # Use the command that is given to us + params[:cmd] = env[:run_command] + + # Don't detach, we want to watch the command run + params[:detach] = false + + # No ports should be shared to the host + params[:ports] = [] + + # We link to our original container + # TODO + end + env[:ui].output(I18n.t("docker_provider.creating")) env[:ui].detail(" Name: #{params[:name]}") env[:ui].detail(" Image: #{params[:image]}") + if params[:cmd] + env[:ui].detail(" Cmd: #{params[:cmd].join(" ")}") + end params[:volumes].each do |volume| env[:ui].detail("Volume: #{volume}") end @@ -30,10 +48,14 @@ module VagrantPlugins cid = @driver.create(params) - env[:ui].detail(" \n"+I18n.t( - "docker_provider.created", id: cid[0...16])) - @machine.id = cid - @app.call(env) + # If this isn't just a one-off command, then save the ID + if env[:machine_action] != :run_command + env[:ui].detail(" \n"+I18n.t( + "docker_provider.created", id: cid[0...16])) + @machine.id = cid + end + + @app.call(env) end def create_params @@ -55,6 +77,7 @@ module VagrantPlugins { cmd: @provider_config.cmd, + detach: true, env: @provider_config.env, extra_args: @provider_config.create_args, hostname: @machine_config.vm.hostname, diff --git a/plugins/providers/docker/command/run.rb b/plugins/providers/docker/command/run.rb new file mode 100644 index 000000000..dee9f01ce --- /dev/null +++ b/plugins/providers/docker/command/run.rb @@ -0,0 +1,70 @@ +module VagrantPlugins + module DockerProvider + module Command + class Run < Vagrant.plugin("2", :command) + def self.synopsis + "run a one-off command in the context of a container" + end + + def execute + options = {} + options[:detach] = false + + opts = OptionParser.new do |o| + o.banner = "Usage: vagrant docker-run [command...]" + o.separator "" + o.separator "Options:" + o.separator "" + + o.on("--[no-]detach", "Run in the background") do |d| + options[:detach] = d + end + end + + # Parse out the extra args to send to SSH, which is everything + # after the "--" + split_index = @argv.index("--") + if !split_index + @env.ui.error(I18n.t("docker_provider.run_command_required")) + return 1 + end + + command = @argv.drop(split_index + 1) + @argv = @argv.take(split_index) + + # Parse the options + argv = parse_options(opts) + return if !argv + + any_success = false + with_target_vms(argv) do |machine| + if machine.provider_name != :docker + machine.ui.output(I18n.t("docker_provider.not_docker_provider")) + next + end + + state = machine.state + if state == :host_state_unknown + machine.ui.output(I18n.t("docker_provider.logs_host_state_unknown")) + next + elsif state == :not_created + machine.ui.output(I18n.t("docker_provider.not_created_skip")) + next + end + + # At least one was run! + any_success = true + + # Run it! + machine.action( + :run_command, + run_command: command, + run_detach: options[:detach]) + end + + return any_success ? 0 : 1 + end + end + end + end +end diff --git a/plugins/providers/docker/driver.rb b/plugins/providers/docker/driver.rb index 415b40aaf..c73b7f548 100644 --- a/plugins/providers/docker/driver.rb +++ b/plugins/providers/docker/driver.rb @@ -35,6 +35,7 @@ module VagrantPlugins env = params.fetch(:env) run_cmd = %W(docker run --name #{name} -d) + run_cmd << "-d" if params[:detach] run_cmd += env.map { |k,v| ['-e', "#{k}=#{v}"] } run_cmd += links.map { |k, v| ['--link', "#{k}:#{v}"] } run_cmd += ports.map { |p| ['-p', p.to_s] } diff --git a/plugins/providers/docker/plugin.rb b/plugins/providers/docker/plugin.rb index 1073f15e8..b6988661b 100644 --- a/plugins/providers/docker/plugin.rb +++ b/plugins/providers/docker/plugin.rb @@ -28,6 +28,12 @@ module VagrantPlugins Command::Logs end + command("docker-run", primary: false) do + require_relative "command/run" + init! + Command::Run + end + communicator(:docker_hostvm) do require_relative "communicator" init! diff --git a/templates/locales/providers_docker.yml b/templates/locales/providers_docker.yml index b4e27565f..6ff514a17 100644 --- a/templates/locales/providers_docker.yml +++ b/templates/locales/providers_docker.yml @@ -37,6 +37,14 @@ en: Container not created. Skipping. not_docker_provider: |- Not backed by Docker provider. Skipping. + run_command_required: |- + `vagrant docker-run` requires a command to execute. This command + must be specified after a `--` in the command line. This is used + to separate possible machine names and options from the actual + command to execute. An example is shown below: + + vagrant docker-run web -- rails new . + ssh_through_host_vm: |- SSH will be proxied through the Docker virtual machine since we're not running Docker natively. This is just a notice, and not an error. @@ -52,6 +60,9 @@ en: Deleting the container... not_created: |- The container hasn't been created yet. + not_created_original: |- + The original container hasn't been created yet. Run `vagrant up` + for this machine first. not_running: |- The container is not currently running. provision_no_ssh: |- From 68fe0b4258d178d3d267a7260372505c00abdab6 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 27 Apr 2014 18:19:44 -0700 Subject: [PATCH 4/8] core: Fix bug where if outputting empty string, would output nothing --- lib/vagrant/ui.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/vagrant/ui.rb b/lib/vagrant/ui.rb index 08af9913d..3ec7821e2 100644 --- a/lib/vagrant/ui.rb +++ b/lib/vagrant/ui.rb @@ -296,8 +296,13 @@ module Vagrant target = @prefix target = opts[:target] if opts.has_key?(:target) + # Get the lines. The first default is because if the message + # is an empty string, then we want to still use the empty string. + lines = [message] + lines = message.split("\n") if message != "" + # Otherwise, make sure to prefix every line properly - message.split("\n").map do |line| + lines.map do |line| "#{prefix}#{target}: #{line}" end.join("\n") end From f1e1617cfd89cafcdbc3e9a3d7f8d029bca70770 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 27 Apr 2014 18:23:31 -0700 Subject: [PATCH 5/8] providers/docker: stream data for run --- plugins/providers/docker/action/create.rb | 13 ++++++++++--- plugins/providers/docker/driver.rb | 6 +++--- templates/locales/providers_docker.yml | 4 ++++ 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/plugins/providers/docker/action/create.rb b/plugins/providers/docker/action/create.rb index 381eee7f7..abd4ce152 100644 --- a/plugins/providers/docker/action/create.rb +++ b/plugins/providers/docker/action/create.rb @@ -46,13 +46,20 @@ module VagrantPlugins env[:ui].detail(" Link: #{name}:#{other}") end - cid = @driver.create(params) - - # If this isn't just a one-off command, then save the ID if env[:machine_action] != :run_command + # For regular "ups" create it and get the CID + cid = @driver.create(params) env[:ui].detail(" \n"+I18n.t( "docker_provider.created", id: cid[0...16])) @machine.id = cid + elsif params[:detach] + env[:ui].detail(" \n"+I18n.t("docker_provider.running_detached")) + else + # For run commands, we run it and stream back the output + env[:ui].detail(" \n"+I18n.t("docker_provider.running")) + @driver.create(params) do |type, data| + env[:ui].detail(data) + end end @app.call(env) diff --git a/plugins/providers/docker/driver.rb b/plugins/providers/docker/driver.rb index c73b7f548..6cbf09060 100644 --- a/plugins/providers/docker/driver.rb +++ b/plugins/providers/docker/driver.rb @@ -25,7 +25,7 @@ module VagrantPlugins match[1] end - def create(params) + def create(params, &block) image = params.fetch(:image) links = params.fetch(:links) ports = Array(params[:ports]) @@ -34,7 +34,7 @@ module VagrantPlugins cmd = Array(params.fetch(:cmd)) env = params.fetch(:env) - run_cmd = %W(docker run --name #{name} -d) + run_cmd = %W(docker run --name #{name}) run_cmd << "-d" if params[:detach] run_cmd += env.map { |k,v| ['-e', "#{k}=#{v}"] } run_cmd += links.map { |k, v| ['--link', "#{k}:#{v}"] } @@ -45,7 +45,7 @@ module VagrantPlugins run_cmd += params[:extra_args] if params[:extra_args] run_cmd += [image, cmd] - execute(*run_cmd.flatten).chomp + execute(*run_cmd.flatten, &block).chomp end def state(cid) diff --git a/templates/locales/providers_docker.yml b/templates/locales/providers_docker.yml index 6ff514a17..3652d2c97 100644 --- a/templates/locales/providers_docker.yml +++ b/templates/locales/providers_docker.yml @@ -45,6 +45,10 @@ en: vagrant docker-run web -- rails new . + running: |- + Container is starting. Output will stream in below... + running_detached: |- + Container is started detached. ssh_through_host_vm: |- SSH will be proxied through the Docker virtual machine since we're not running Docker natively. This is just a notice, and not an error. From a8822e84d20080ad60a264ba8c7cb243985f61b5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 27 Apr 2014 18:26:12 -0700 Subject: [PATCH 6/8] providers/docker: fix some extranneous newlines in streamed output --- plugins/providers/docker/action/create.rb | 2 +- plugins/providers/docker/executor/vagrant.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/providers/docker/action/create.rb b/plugins/providers/docker/action/create.rb index abd4ce152..84ecf49f2 100644 --- a/plugins/providers/docker/action/create.rb +++ b/plugins/providers/docker/action/create.rb @@ -56,7 +56,7 @@ module VagrantPlugins env[:ui].detail(" \n"+I18n.t("docker_provider.running_detached")) else # For run commands, we run it and stream back the output - env[:ui].detail(" \n"+I18n.t("docker_provider.running")) + env[:ui].detail(" \n"+I18n.t("docker_provider.running")+"\n ") @driver.create(params) do |type, data| env[:ui].detail(data) end diff --git a/plugins/providers/docker/executor/vagrant.rb b/plugins/providers/docker/executor/vagrant.rb index 046686c62..85f274614 100644 --- a/plugins/providers/docker/executor/vagrant.rb +++ b/plugins/providers/docker/executor/vagrant.rb @@ -20,7 +20,7 @@ module VagrantPlugins # We have to do this because boot2docker outputs a login shell # boot2docker version that we get otherwise and messes up output. start_fence = "========== VAGRANT DOCKER BEGIN ==========" - ssh_cmd = "echo \"#{start_fence}\"; #{cmd}" + ssh_cmd = "echo -n \"#{start_fence}\"; #{cmd}" stderr = "" stdout = "" @@ -42,8 +42,8 @@ module VagrantPlugins # We're now fenced, send all the data through if block - block.call(:stdout, stdout) - block.call(:stderr, stderr) + block.call(:stdout, stdout) if stdout != "" + block.call(:stderr, stderr) if stderr != "" end end else From ff9de3586a6bff5d2f716b4fbb175391ddd969fe Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 27 Apr 2014 18:28:11 -0700 Subject: [PATCH 7/8] providers/docker: run_command doesn't need to actual setup host machine --- plugins/providers/docker/action.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/providers/docker/action.rb b/plugins/providers/docker/action.rb index 7b24bb020..64f80186d 100644 --- a/plugins/providers/docker/action.rb +++ b/plugins/providers/docker/action.rb @@ -10,7 +10,12 @@ module VagrantPlugins def self.action_run_command Vagrant::Action::Builder.new.tap do |b| b.use ConfigValidate - b.use HostMachine + + b.use Call, IsState, :host_state_unknown do |env, b2| + if env[:result] + raise "Invalid usage" + end + end b.use Call, IsState, :not_created do |env, b2| if env[:result] From c818a14072de6e721100f0d800f9c5d7048f6f4b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sun, 27 Apr 2014 18:37:25 -0700 Subject: [PATCH 8/8] providers/docker: expose ports --- plugins/providers/docker/action/create.rb | 1 + plugins/providers/docker/config.rb | 13 ++++++++++ plugins/providers/docker/driver.rb | 2 ++ .../plugins/providers/docker/config_spec.rb | 25 +++++++++++++++++++ 4 files changed, 41 insertions(+) diff --git a/plugins/providers/docker/action/create.rb b/plugins/providers/docker/action/create.rb index 84ecf49f2..67ab35e6d 100644 --- a/plugins/providers/docker/action/create.rb +++ b/plugins/providers/docker/action/create.rb @@ -86,6 +86,7 @@ module VagrantPlugins cmd: @provider_config.cmd, detach: true, env: @provider_config.env, + expose: @provider_config.expose, extra_args: @provider_config.create_args, hostname: @machine_config.vm.hostname, image: image, diff --git a/plugins/providers/docker/config.rb b/plugins/providers/docker/config.rb index d156b2143..27b6713cb 100644 --- a/plugins/providers/docker/config.rb +++ b/plugins/providers/docker/config.rb @@ -22,6 +22,12 @@ module VagrantPlugins # @return [Hash] attr_accessor :env + # Ports to expose from the container but not to the host machine. + # This is useful for links. + # + # @return [Array] + attr_accessor :expose + # Force using a proxy VM, even on Linux hosts. # # @return [Boolean] @@ -71,6 +77,7 @@ module VagrantPlugins @cmd = UNSET_VALUE @create_args = [] @env = {} + @expose = [] @force_host_vm = UNSET_VALUE @has_ssh = UNSET_VALUE @image = UNSET_VALUE @@ -108,6 +115,10 @@ module VagrantPlugins env.merge!(other.env) if other.env result.env = env + expose = self.expose.dup + expose += other.expose + result.instance_variable_set(:@expose, expose) + links = _links.dup links += other._links result.instance_variable_set(:@links, links) @@ -127,6 +138,8 @@ module VagrantPlugins @remains_running = true if @remains_running == UNSET_VALUE @vagrant_machine = nil if @vagrant_machine == UNSET_VALUE @vagrant_vagrantfile = nil if @vagrant_vagrantfile == UNSET_VALUE + + @expose.uniq! end def validate(machine) diff --git a/plugins/providers/docker/driver.rb b/plugins/providers/docker/driver.rb index 6cbf09060..aa43ca91b 100644 --- a/plugins/providers/docker/driver.rb +++ b/plugins/providers/docker/driver.rb @@ -33,10 +33,12 @@ module VagrantPlugins name = params.fetch(:name) cmd = Array(params.fetch(:cmd)) env = params.fetch(:env) + expose = Array(params[:expose]) run_cmd = %W(docker run --name #{name}) run_cmd << "-d" if params[:detach] run_cmd += env.map { |k,v| ['-e', "#{k}=#{v}"] } + run_cmd += expose.map { |p| ['--expose', "#{p}"] } run_cmd += links.map { |k, v| ['--link', "#{k}:#{v}"] } run_cmd += ports.map { |p| ['-p', p.to_s] } run_cmd += volumes.map { |v| ['-v', v.to_s] } diff --git a/test/unit/plugins/providers/docker/config_spec.rb b/test/unit/plugins/providers/docker/config_spec.rb index cb316238d..c93bf2c89 100644 --- a/test/unit/plugins/providers/docker/config_spec.rb +++ b/test/unit/plugins/providers/docker/config_spec.rb @@ -39,6 +39,7 @@ describe VagrantPlugins::DockerProvider::Config do before { subject.finalize! } its(:build_dir) { should be_nil } + its(:expose) { should eq([]) } its(:cmd) { should eq([]) } its(:env) { should eq({}) } its(:force_host_vm) { should be_false } @@ -82,6 +83,20 @@ describe VagrantPlugins::DockerProvider::Config do end end + describe "#expose" do + before do + valid_defaults + end + + it "uniqs the ports" do + subject.expose = [1, 1, 4, 5] + subject.finalize! + assert_valid + + expect(subject.expose).to eq([1, 4, 5]) + end + end + describe "#image" do it "should be valid if set" do subject.image = "foo" @@ -166,6 +181,16 @@ describe VagrantPlugins::DockerProvider::Config do end end + context "exposed ports" do + it "merges the exposed ports" do + one.expose << 1234 + two.expose = [42, 54] + + expect(subject.expose).to eq([ + 1234, 42, 54]) + end + end + context "links" do it "should merge the links" do one.link "foo"