From 8c7ab333a016003452d68e91037bbb2c0acb250b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 10 Apr 2014 18:26:19 -0700 Subject: [PATCH] Squash the f-docker-hostmachine branch. Initial work commands/up: make sure all names to with_target_vms are strings providers/docker: create a docker host VM if needed providers/docker: executor abstraction for driver to eventually support remote providers/docker: vagrant executor providers/docker: support creating the machine providers/docker: status works if host VM is gone providers/docker: use start fence to get real docker output core: Call preserves stack ordering core: support Message post option providers/docker: Guard some features with HasSSH checks providers/docker: much better messaging around create/destroy providers/docker: output the container ID on create providers/docker: copy the hostmachine Vagrantfile to the data dir providers/docker: should make host machine before any up action providers/docker: HandleBox before the host machine providers/virtualbox: functional_vboxsf to disable vboxsf providers/virtualbox: synced folder usable method should take 2 args providers/docker: default machine name to :default --- lib/vagrant/action/builtin/call.rb | 9 +- lib/vagrant/action/builtin/message.rb | 10 +- lib/vagrant/ui.rb | 5 +- plugins/commands/up/command.rb | 2 +- plugins/providers/docker/action.rb | 42 ++++++- plugins/providers/docker/action/create.rb | 19 ++- plugins/providers/docker/action/destroy.rb | 3 +- plugins/providers/docker/action/has_ssh.rb | 18 +++ .../providers/docker/action/host_machine.rb | 56 +++++++++ plugins/providers/docker/config.rb | 43 ++++++- plugins/providers/docker/driver.rb | 45 ++----- plugins/providers/docker/errors.rb | 4 + plugins/providers/docker/executor/local.rb | 33 ++++++ plugins/providers/docker/executor/vagrant.rb | 58 +++++++++ .../providers/docker/hostmachine/Vagrantfile | 8 ++ plugins/providers/docker/plugin.rb | 5 + plugins/providers/docker/provider.rb | 112 ++++++++++++++++-- plugins/providers/virtualbox/config.rb | 12 ++ plugins/providers/virtualbox/synced_folder.rb | 5 +- templates/locales/providers_docker.yml | 43 +++++++ .../plugins/providers/docker/config_spec.rb | 37 ++++++ .../providers/virtualbox/config_test.rb | 1 + .../virtualbox/synced_folder_test.rb | 12 ++ test/unit/vagrant/action/builder_test.rb | 44 +++++++ test/unit/vagrant/action/builtin/call_test.rb | 31 +++++ .../vagrant/action/builtin/message_test.rb | 13 +- test/unit/vagrant/ui_test.rb | 5 + 27 files changed, 609 insertions(+), 66 deletions(-) create mode 100644 plugins/providers/docker/action/has_ssh.rb create mode 100644 plugins/providers/docker/action/host_machine.rb create mode 100644 plugins/providers/docker/executor/local.rb create mode 100644 plugins/providers/docker/executor/vagrant.rb create mode 100644 plugins/providers/docker/hostmachine/Vagrantfile diff --git a/lib/vagrant/action/builtin/call.rb b/lib/vagrant/action/builtin/call.rb index b12fd156d..5bd3088d4 100644 --- a/lib/vagrant/action/builtin/call.rb +++ b/lib/vagrant/action/builtin/call.rb @@ -46,15 +46,14 @@ module Vagrant builder = Builder.new @block.call(new_env, builder) - # Run the result with our new environment + # Append our own app onto the builder so we slide the new + # stack into our own chain... + builder.use @app @child_app = builder.to_app(new_env) - final_env = runner.run(@child_app, new_env) + final_env = runner.run(@child_app, new_env) # Merge the environment into our original environment env.merge!(final_env) - - # Call the next step using our final environment - @app.call(env) end def recover(env) diff --git a/lib/vagrant/action/builtin/message.rb b/lib/vagrant/action/builtin/message.rb index a340945c4..6b37e5241 100644 --- a/lib/vagrant/action/builtin/message.rb +++ b/lib/vagrant/action/builtin/message.rb @@ -6,11 +6,19 @@ module Vagrant def initialize(app, env, message, **opts) @app = app @message = message + @opts = opts end def call(env) - env[:ui].output(@message) + if !@opts[:post] + env[:ui].output(@message) + end + @app.call(env) + + if @opts[:post] + env[:ui].output(@message) + end end end end diff --git a/lib/vagrant/ui.rb b/lib/vagrant/ui.rb index 9e43f0f8b..08af9913d 100644 --- a/lib/vagrant/ui.rb +++ b/lib/vagrant/ui.rb @@ -293,9 +293,12 @@ module Vagrant # Fast-path if there is no prefix return message if prefix.empty? + target = @prefix + target = opts[:target] if opts.has_key?(:target) + # Otherwise, make sure to prefix every line properly message.split("\n").map do |line| - "#{prefix}#{@prefix}: #{line}" + "#{prefix}#{target}: #{line}" end.join("\n") end end diff --git a/plugins/commands/up/command.rb b/plugins/commands/up/command.rb index df5b23f1d..af708e5d4 100644 --- a/plugins/commands/up/command.rb +++ b/plugins/commands/up/command.rb @@ -60,7 +60,7 @@ module VagrantPlugins if names.empty? @env.vagrantfile.machine_names_and_options.each do |n, o| o[:autostart] = true if !o.has_key?(:autostart) - names << n if o[:autostart] + names << n.to_s if o[:autostart] end end diff --git a/plugins/providers/docker/action.rb b/plugins/providers/docker/action.rb index b9ee43c0d..c1d96bf55 100644 --- a/plugins/providers/docker/action.rb +++ b/plugins/providers/docker/action.rb @@ -9,13 +9,30 @@ module VagrantPlugins def self.action_up Vagrant::Action::Builder.new.tap do |b| b.use ConfigValidate + b.use HandleBox + + b.use Call, IsState, :host_state_unknown do |env, b2| + if env[:result] + b2.use HostMachine + end + end + b.use Call, IsState, :not_created do |env, b2| # If the VM is NOT created yet, then do the setup steps if env[:result] - b2.use HandleBox b2.use EnvSet, :port_collision_repair => true b2.use HandleForwardedPortCollisions - b2.use Provision + + 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 + b2.use PrepareNFSValidIds b2.use SyncedFolderCleanup b2.use SyncedFolders @@ -61,6 +78,12 @@ module VagrantPlugins # the virtual machine, gracefully or by force. def self.action_halt Vagrant::Action::Builder.new.tap do |b| + b.use Call, IsState, :host_state_unknown do |env, b2| + if env[:result] + b2.use HostMachine + end + end + b.use Call, IsState, :not_created do |env, b2| if env[:result] b2.use Message, I18n.t("docker_provider.messages.not_created") @@ -98,6 +121,12 @@ module VagrantPlugins # freeing the resources of the underlying virtual machine. def self.action_destroy Vagrant::Action::Builder.new.tap do |b| + b.use Call, IsState, :host_state_unknown do |env, b2| + if env[:result] + b2.use HostMachine + end + end + b.use Call, IsState, :not_created do |env, b2| if env[:result] b2.use Message, I18n.t("docker_provider.messages.not_created") @@ -177,7 +206,12 @@ module VagrantPlugins Vagrant::Action::Builder.new.tap do |b| # TODO: b.use SetHostname b.use Start - b.use WaitForCommunicator + + b.use Call, HasSSH do |env, b2| + if env[:result] + b2.use WaitForCommunicator + end + end end end @@ -186,6 +220,8 @@ module VagrantPlugins autoload :Create, action_root.join("create") autoload :Destroy, action_root.join("destroy") autoload :ForwardPorts, action_root.join("forward_ports") + autoload :HasSSH, action_root.join("has_ssh") + autoload :HostMachine, action_root.join("host_machine") autoload :Stop, action_root.join("stop") autoload :PrepareNFSValidIds, action_root.join("prepare_nfs_valid_ids") autoload :PrepareNFSSettings, action_root.join("prepare_nfs_settings") diff --git a/plugins/providers/docker/action/create.rb b/plugins/providers/docker/action/create.rb index a8ea7fe6d..38ca3890d 100644 --- a/plugins/providers/docker/action/create.rb +++ b/plugins/providers/docker/action/create.rb @@ -16,11 +16,19 @@ module VagrantPlugins guard_cmd_configured! + params = create_params + cid = '' @@mutex.synchronize do - cid = @driver.create(create_params) + env[:ui].output(I18n.t("docker_provider.creating")) + env[:ui].detail(" Name: #{params[:name]}") + env[:ui].detail("Image: #{params[:image]}") + + cid = @driver.create(params) end + env[:ui].detail(" \n"+I18n.t( + "docker_provider.created", id: cid[0...16])) @machine.id = cid @app.call(env) end @@ -31,13 +39,14 @@ module VagrantPlugins container_name << "_#{Time.now.to_i}" { - image: @provider_config.image, cmd: @provider_config.cmd, - ports: forwarded_ports, - name: container_name, + extra_args: @provider_config.create_args, hostname: @machine_config.vm.hostname, + image: @provider_config.image, + name: container_name, + ports: forwarded_ports, + privileged: @provider_config.privileged, volumes: @provider_config.volumes, - privileged: @provider_config.privileged } end diff --git a/plugins/providers/docker/action/destroy.rb b/plugins/providers/docker/action/destroy.rb index aba1cbf18..dd60169d2 100644 --- a/plugins/providers/docker/action/destroy.rb +++ b/plugins/providers/docker/action/destroy.rb @@ -7,10 +7,9 @@ module VagrantPlugins end def call(env) - env[:ui].info I18n.t("vagrant.actions.vm.destroy.destroying") + env[:ui].info I18n.t("docker_provider.messages.destroying") machine = env[:machine] - config = machine.provider_config driver = machine.provider.driver driver.rm(machine.id) diff --git a/plugins/providers/docker/action/has_ssh.rb b/plugins/providers/docker/action/has_ssh.rb new file mode 100644 index 000000000..2f8c461aa --- /dev/null +++ b/plugins/providers/docker/action/has_ssh.rb @@ -0,0 +1,18 @@ +module VagrantPlugins + module DockerProvider + module Action + # This middleware is used with Call to test if this machine supports + # SSH. + class HasSSH + def initialize(app, env) + @app = app + end + + def call(env) + env[:result] = env[:machine].provider_config.has_ssh + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/docker/action/host_machine.rb b/plugins/providers/docker/action/host_machine.rb new file mode 100644 index 000000000..e31377552 --- /dev/null +++ b/plugins/providers/docker/action/host_machine.rb @@ -0,0 +1,56 @@ +require "log4r" + +require "vagrant/util/platform" +require "vagrant/util/silence_warnings" + +module VagrantPlugins + module DockerProvider + module Action + # This action is responsible for creating the host machine if + # we need to. The host machine is where Docker containers will + # live. + class HostMachine + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new("vagrant::docker::hostmachine") + end + + def call(env) + if !env[:machine].provider.host_vm? + @logger.info("No host machine needed.") + return @app.call(env) + end + + env[:machine].ui.output(I18n.t( + "docker_provider.host_machine_needed")) + + # TODO(mitchellh): process-level lock so that we don't + # step on parallel Vagrant's toes. + + host_machine = env[:machine].provider.host_vm + + # See if the machine is ready already. + if host_machine.communicate.ready? + env[:machine].ui.detail(I18n.t("docker_provider.host_machine_ready")) + return @app.call(env) + end + + # Create a UI for this machine that stays at the detail level + proxy_ui = host_machine.ui.dup + proxy_ui.opts[:bold] = false + proxy_ui.opts[:prefix_spaces] = true + proxy_ui.opts[:target] = env[:machine].name.to_s + + env[:machine].ui.detail( + I18n.t("docker_provider.host_machine_starting")) + env[:machine].ui.detail(" ") + host_machine.with_ui(proxy_ui) do + host_machine.action(:up) + end + + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/docker/config.rb b/plugins/providers/docker/config.rb index 40d1ae188..9e33307b9 100644 --- a/plugins/providers/docker/config.rb +++ b/plugins/providers/docker/config.rb @@ -3,25 +3,66 @@ module VagrantPlugins class Config < Vagrant.plugin("2", :config) attr_accessor :image, :cmd, :ports, :volumes, :privileged + # Additional arguments to pass to `docker run` when creating + # the container for the first time. This is an array of args. + # + # @return [Array] + attr_accessor :create_args + + # True if the Docker container exposes SSH access. If this is true, + # then Vagrant can do a bunch more things like setting the hostname, + # provisioning, etc. + attr_accessor :has_ssh + + # The name of the machine in the Vagrantfile set with + # "vagrant_vagrantfile" that will be the docker host. Defaults + # to "default" + # + # See the "vagrant_vagrantfile" docs for more info. + # + # @return [String] + attr_accessor :vagrant_machine + + # The path to the Vagrantfile that contains a VM that will be + # started as the Docker host if needed (Windows, OS X, Linux + # without container support). + # + # Defaults to a built-in Vagrantfile that will load boot2docker. + # + # NOTE: This only has an effect if Vagrant needs a Docker host. + # Vagrant determines this automatically based on the environment + # it is running in. + # + # @return [String] + attr_accessor :vagrant_vagrantfile + def initialize @cmd = UNSET_VALUE + @create_args = [] + @has_ssh = UNSET_VALUE @image = UNSET_VALUE @ports = [] @privileged = UNSET_VALUE @volumes = [] + @vagrant_machine = UNSET_VALUE + @vagrant_vagrantfile = UNSET_VALUE end def finalize! @cmd = [] if @cmd == UNSET_VALUE + @create_args = [] if @create_args == UNSET_VALUE + @has_ssh = false if @has_ssh == UNSET_VALUE @image = nil if @image == UNSET_VALUE @privileged = false if @privileged == UNSET_VALUE + @vagrant_machine = nil if @vagrant_machine == UNSET_VALUE + @vagrant_vagrantfile = nil if @vagrant_vagrantfile == UNSET_VALUE end def validate(machine) errors = _detected_errors # TODO: Detect if base image has a CMD / ENTRYPOINT set before erroring out - errors << I18n.t("docker_provider.errors.config.cmd_not_set") if @cmd == UNSET_VALUE + errors << I18n.t("docker_provider.errors.config.cmd_not_set") if @cmd == UNSET_VALUE { "docker provider" => errors } end diff --git a/plugins/providers/docker/driver.rb b/plugins/providers/docker/driver.rb index 924a53409..ae28e0dc5 100644 --- a/plugins/providers/docker/driver.rb +++ b/plugins/providers/docker/driver.rb @@ -1,17 +1,17 @@ -require "vagrant/util/busy" -require "vagrant/util/subprocess" -require "vagrant/util/retryable" +require "json" -require 'log4r' -require 'json' +require "log4r" module VagrantPlugins module DockerProvider class Driver - include Vagrant::Util::Retryable + # The executor is responsible for actually executing Docker commands. + # This is set by the provider, but defaults to local execution. + attr_accessor :executor def initialize - @logger = Log4r::Logger.new("vagrant::docker::driver") + @logger = Log4r::Logger.new("vagrant::docker::driver") + @executor = Executor::Local.new end def create(params) @@ -26,6 +26,7 @@ module VagrantPlugins run_cmd += volumes.map { |v| ['-v', v.to_s] } run_cmd += %W(--privileged) if params[:privileged] run_cmd += %W(-h #{params[:hostname]}) if params[:hostname] + run_cmd += params[:extra_args] if params[:extra_args] run_cmd += [image, cmd] execute(*run_cmd.flatten).chomp @@ -101,35 +102,7 @@ module VagrantPlugins private def execute(*cmd, &block) - result = raw(*cmd, &block) - - if result.exit_code != 0 - if @interrupted - @logger.info("Exit code != 0, but interrupted. Ignoring.") - else - msg = result.stdout.gsub("\r\n", "\n") - msg << result.stderr.gsub("\r\n", "\n") - raise "#{cmd.inspect}\n#{msg}" #Errors::ExecuteError, :command => command.inspect - end - end - - # Return the output, making sure to replace any Windows-style - # newlines with Unix-style. - result.stdout.gsub("\r\n", "\n") - end - - def raw(*cmd, &block) - int_callback = lambda do - @interrupted = true - @logger.info("Interrupted.") - end - - # Append in the options for subprocess - cmd << { :notify => [:stdout, :stderr] } - - Vagrant::Util::Busy.busy(int_callback) do - Vagrant::Util::Subprocess.execute(*cmd, &block) - end + @executor.execute(*cmd, &block) end end end diff --git a/plugins/providers/docker/errors.rb b/plugins/providers/docker/errors.rb index 97b895f39..59db72d4d 100644 --- a/plugins/providers/docker/errors.rb +++ b/plugins/providers/docker/errors.rb @@ -5,6 +5,10 @@ module VagrantPlugins error_namespace("docker_provider.errors") end + class ExecuteError < DockerError + error_key(:execute_error) + end + class ImageNotConfiguredError < DockerError error_key(:docker_provider_image_not_configured) end diff --git a/plugins/providers/docker/executor/local.rb b/plugins/providers/docker/executor/local.rb new file mode 100644 index 000000000..033155c28 --- /dev/null +++ b/plugins/providers/docker/executor/local.rb @@ -0,0 +1,33 @@ +require "vagrant/util/busy" +require "vagrant/util/subprocess" + +module VagrantPlugins + module DockerProvider + module Executor + # The Local executor executes a Docker client that is running + # locally. + class Local + def execute(*cmd, &block) + # Append in the options for subprocess + cmd << { :notify => [:stdout, :stderr] } + + interrupted = false + int_callback = ->{ interrupted = true } + result = Vagrant::Util::Busy.busy(int_callback) do + Vagrant::Util::Subprocess.execute(*cmd, &block) + end + + if result.exit_code != 0 && !interrupted + msg = result.stdout.gsub("\r\n", "\n") + msg << result.stderr.gsub("\r\n", "\n") + raise "#{cmd.inspect}\n#{msg}" #Errors::ExecuteError, :command => command.inspect + end + + # Return the output, making sure to replace any Windows-style + # newlines with Unix-style. + result.stdout.gsub("\r\n", "\n") + end + end + end + end +end diff --git a/plugins/providers/docker/executor/vagrant.rb b/plugins/providers/docker/executor/vagrant.rb new file mode 100644 index 000000000..151c068a5 --- /dev/null +++ b/plugins/providers/docker/executor/vagrant.rb @@ -0,0 +1,58 @@ +require "vagrant/util/shell_quote" + +module VagrantPlugins + module DockerProvider + module Executor + # The Vagrant executor runs Docker over SSH against the given + # Vagrant-managed machine. + class Vagrant + def initialize(host_machine) + @host_machine = host_machine + end + + def execute(*cmd, &block) + quote = '"' + cmd = cmd.map do |a| + "#{quote}#{::Vagrant::Util::ShellQuote.escape(a, quote)}#{quote}" + end.join(" ") + + # Add a start fence so we know when to start reading output. + # 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}" + + stderr = "" + stdout = "" + fenced = false + comm = @host_machine.communicate + code = comm.execute(ssh_cmd, error_check: false) do |type, data| + next if ![:stdout, :stderr].include?(type) + stderr << data if type == :stderr + stdout << data if type == :stdout + + if !fenced + index = stdout.index(start_fence) + if index + index += start_fence.length + stdout = stdout[index..-1] + stdout.chomp! + end + end + + block.call(type, data) if block && fenced + end + + if code != 0 + raise Errors::ExecuteError, + command: cmd, + stderr: stderr.chomp, + stdout: stdout.chomp + end + + stdout.chomp + end + end + end + end +end diff --git a/plugins/providers/docker/hostmachine/Vagrantfile b/plugins/providers/docker/hostmachine/Vagrantfile new file mode 100644 index 000000000..af9e8373e --- /dev/null +++ b/plugins/providers/docker/hostmachine/Vagrantfile @@ -0,0 +1,8 @@ +Vagrant.configure("2") do |config| + config.vm.box = "mitchellh/boot2docker" + + config.vm.provider "virtualbox" do |v| + v.check_guest_additions = false + v.functional_vboxsf = false + end +end diff --git a/plugins/providers/docker/plugin.rb b/plugins/providers/docker/plugin.rb index fc6760c52..3fd4bcf07 100644 --- a/plugins/providers/docker/plugin.rb +++ b/plugins/providers/docker/plugin.rb @@ -4,6 +4,11 @@ module VagrantPlugins autoload :Driver, File.expand_path("../driver", __FILE__) autoload :Errors, File.expand_path("../errors", __FILE__) + module Executor + autoload :Local, File.expand_path("../executor/local", __FILE__) + autoload :Vagrant, File.expand_path("../executor/vagrant", __FILE__) + end + class Plugin < Vagrant.plugin("2") name "docker-provider" description <<-EOF diff --git a/plugins/providers/docker/provider.rb b/plugins/providers/docker/provider.rb index c18a7438d..69dd43d8a 100644 --- a/plugins/providers/docker/provider.rb +++ b/plugins/providers/docker/provider.rb @@ -1,14 +1,16 @@ +require "fileutils" + require "log4r" +require "vagrant/action/builtin/mixin_synced_folders" +require "vagrant/util/silence_warnings" + module VagrantPlugins module DockerProvider class Provider < Vagrant.plugin("2", :provider) - attr_reader :driver - def initialize(machine) @logger = Log4r::Logger.new("vagrant::provider::docker") @machine = machine - @driver = Driver.new end # @see Vagrant::Plugin::V2::Provider#action @@ -18,13 +20,107 @@ module VagrantPlugins nil end + # Returns the driver instance for this provider. + def driver + return @driver if @driver + @driver = Driver.new + + # If we are running on a host machine, then we set the executor + # to execute remotely. + if host_vm? + @driver.executor = Executor::Vagrant.new(host_vm) + end + + @driver + end + + # This returns the {Vagrant::Machine} that is our host machine. + # It does not perform any action on the machine or verify it is + # running. + # + # @return [Vagrant::Machine] + def host_vm + return @host_vm if @host_vm + + # TODO(mitchellh): process-wide lock + + vf_path = @machine.provider_config.vagrant_vagrantfile + host_machine_name = @machine.provider_config.vagrant_machine || :default + if !vf_path + # We don't have a Vagrantfile path set, so we're going to use + # the default but we need to copy it into the data dir so that + # we don't write into our installation dir (we can't). + default_path = File.expand_path("../hostmachine/Vagrantfile", __FILE__) + vf_path = @machine.env.data_dir.join("docker-host", "Vagrantfile") + vf_path.dirname.mkpath + FileUtils.cp(default_path, vf_path) + + # Set the machine name since we hardcode that for the default + host_machine_name = :default + end + + vf_file = File.basename(vf_path) + vf_path = File.dirname(vf_path) + + # Create the env to manage this machine + @host_vm = Vagrant::Util::SilenceWarnings.silence! do + host_env = Vagrant::Environment.new( + cwd: vf_path, + home_path: @machine.env.home_path, + ui_class: @machine.env.ui_class, + vagrantfile_name: vf_file, + ) + + # TODO(mitchellh): configure the provider of this machine somehow + host_env.machine(host_machine_name, :virtualbox) + end + + # Make sure we swap all the synced folders out from our + # machine so that we do a double synced folder: normal synced + # folders to the host machine, then Docker volumes within that host. + sf_helper_klass = Class.new do + include Vagrant::Action::Builtin::MixinSyncedFolders + end + sf_helper = sf_helper_klass.new + our_folders = sf_helper.synced_folders(@machine) + if our_folders[:docker] + our_folders[:docker].each do |id, data| + data = data.dup + data.delete(:type) + + # Add them to the host machine +=begin + @host_vm.config.vm.synced_folder( + data[:hostpath], + data[:guestpath], + data) +=end + + # Remove from our machine + @machine.config.vm.synced_folders.delete(id) + end + end + + @host_vm + end + + # This says whether or not Docker will be running within a VM + # rather than directly on our system. Docker needs to run in a VM + # when we're not on Linux, or not on a Linux that supports Docker. + def host_vm? + # TODO: It'd be nice to also check if Docker supports the version + # of Linux that Vagrant is running on so that we can spin up a VM + # on old versions of Linux as well. + !Vagrant::Util::Platform.linux? + end + # Returns the SSH info for accessing the Container. def ssh_info # If the Container is not created then we cannot possibly SSH into it, so # we return nil. return nil if state == :not_created - network = @driver.inspect_container(@machine.id)['NetworkSettings'] + network = driver.inspect_container(@machine.id)['NetworkSettings'] ip = network['IPAddress'] # If we were not able to identify the container's IP, we return nil @@ -39,12 +135,14 @@ module VagrantPlugins def state state_id = nil - state_id = :not_created if !@machine.id || !@driver.created?(@machine.id) - state_id = @driver.state(@machine.id) if @machine.id && !state_id + state_id = :host_state_unknown if host_vm? && !host_vm.communicate.ready? + state_id = :not_created if !state_id && \ + (!@machine.id || !driver.created?(@machine.id)) + state_id = driver.state(@machine.id) if @machine.id && !state_id state_id = :unknown if !state_id short = state_id.to_s.gsub("_", " ") - long = I18n.t("vagrant.commands.status.#{state_id}") + long = I18n.t("docker_provider.status.#{state_id}") Vagrant::MachineState.new(state_id, short, long) end diff --git a/plugins/providers/virtualbox/config.rb b/plugins/providers/virtualbox/config.rb index 278b0b840..be1d6040e 100644 --- a/plugins/providers/virtualbox/config.rb +++ b/plugins/providers/virtualbox/config.rb @@ -38,6 +38,13 @@ module VagrantPlugins # @return [String] attr_accessor :name + # Whether or not this VM has a functional vboxsf filesystem module. + # This defaults to true. If you set this to false, then the "virtualbox" + # synced folder type won't be valid. + # + # @return [Boolean] + attr_accessor :functional_vboxsf + # The defined network adapters. # # @return [Hash] @@ -48,6 +55,7 @@ module VagrantPlugins @check_guest_additions = UNSET_VALUE @customizations = [] @destroy_unused_network_interfaces = UNSET_VALUE + @functional_vboxsf = UNSET_VALUE @name = UNSET_VALUE @network_adapters = {} @gui = UNSET_VALUE @@ -113,6 +121,10 @@ module VagrantPlugins @destroy_unused_network_interfaces = false end + if @functional_vboxsf == UNSET_VALUE + @functional_vboxsf = true + end + # Default is to not show a GUI @gui = false if @gui == UNSET_VALUE diff --git a/plugins/providers/virtualbox/synced_folder.rb b/plugins/providers/virtualbox/synced_folder.rb index e5c147078..aa35b280f 100644 --- a/plugins/providers/virtualbox/synced_folder.rb +++ b/plugins/providers/virtualbox/synced_folder.rb @@ -3,9 +3,10 @@ require "vagrant/util/platform" module VagrantPlugins module ProviderVirtualBox class SyncedFolder < Vagrant.plugin("2", :synced_folder) - def usable?(machine) + def usable?(machine, raise_errors=false) # These synced folders only work if the provider if VirtualBox - machine.provider_name == :virtualbox + machine.provider_name == :virtualbox && + machine.provider_config.functional_vboxsf end def prepare(machine, folders, _opts) diff --git a/templates/locales/providers_docker.yml b/templates/locales/providers_docker.yml index a45524dc3..dff7f5e3a 100644 --- a/templates/locales/providers_docker.yml +++ b/templates/locales/providers_docker.yml @@ -1,10 +1,26 @@ en: docker_provider: + creating: |- + Creating the container... + created: |- + Container created: %{id} + host_machine_needed: |- + Docker host is required. One will be created if necessary... + host_machine_ready: |- + Docker host VM is already ready. + host_machine_starting: |- + Vagrant will now create or start a local VM to act as the Docker + host. You'll see the output of the `vagrant up` for this VM below. + messages: + destroying: |- + Deleting the container... not_created: |- The container hasn't been created yet. not_running: |- The container is not currently running. + provision_no_ssh: |- + Provisioners will not be run since container doesn't support SSH. will_not_destroy: |- The container will not be destroyed, since the confirmation was declined. starting: |- @@ -14,6 +30,23 @@ en: container_ready: |- Container started and ready for use! + status: + host_state_unknown: |- + The host VM for the Docker containers appears to not be running + or is currently inaccessible. Because of this, we can't determine + the state of the containers on that host. Run `vagrant up` to + bring up the host VM again. + not_created: |- + The environment has not yet been created. Run `vagrant up` to + create the environment. If a machine is not created, only the + default provider will be shown. So if a provider is not listed, + then the machine is not created for that environment. + stopped: |- + The container is created but not running. You can run it again + with `vagrant up`. If the container always goes to "stopped" + right away after being started, it is because the command being + run exits and doesn't keep running. + errors: config: cmd_not_set: |- @@ -25,6 +58,16 @@ en: and try again. docker_provider_image_not_configured: |- The base Docker image has not been set for the '%{name}' VM! + execute_error: |- + A Docker command executed by Vagrant didn't complete successfully! + The command run along with the output from the command is shown + below. + + Command: %{command} + + Stderr: %{stderr} + + Stdout: %{stdout} synced_folder_non_docker: |- The "docker" synced folder type can't be used because the provider in use is not Docker. This synced folder type only works with the diff --git a/test/unit/plugins/providers/docker/config_spec.rb b/test/unit/plugins/providers/docker/config_spec.rb index 347ca28fb..764986fa3 100644 --- a/test/unit/plugins/providers/docker/config_spec.rb +++ b/test/unit/plugins/providers/docker/config_spec.rb @@ -1,6 +1,43 @@ require_relative "../../../base" +require "vagrant/util/platform" + require Vagrant.source_root.join("plugins/providers/docker/config") describe VagrantPlugins::DockerProvider::Config do + let(:machine) { double("machine") } + + def assert_invalid + errors = subject.validate(machine) + if !errors.values.any? { |v| !v.empty? } + raise "No errors: #{errors.inspect}" + end + end + + def assert_valid + errors = subject.validate(machine) + if !errors.values.all? { |v| v.empty? } + raise "Errors: #{errors.inspect}" + end + end + + describe "defaults" do + before { subject.finalize! } + + its(:cmd) { should eq([]) } + its(:image) { should be_nil } + its(:privileged) { should be_false } + its(:vagrant_machine) { should be_nil } + its(:vagrant_vagrantfile) { should be_nil } + end + + before do + # By default lets be Linux for validations + Vagrant::Util::Platform.stub(linux: true) + end + + it "should be valid by default" do + subject.finalize! + assert_valid + end end diff --git a/test/unit/plugins/providers/virtualbox/config_test.rb b/test/unit/plugins/providers/virtualbox/config_test.rb index c17790c53..63fc593d8 100644 --- a/test/unit/plugins/providers/virtualbox/config_test.rb +++ b/test/unit/plugins/providers/virtualbox/config_test.rb @@ -11,6 +11,7 @@ describe VagrantPlugins::ProviderVirtualBox::Config do it { expect(subject.check_guest_additions).to be_true } it { expect(subject.gui).to be_false } it { expect(subject.name).to be_nil } + it { expect(subject.functional_vboxsf).to be_true } it "should have one NAT adapter" do expect(subject.network_adapters).to eql({ diff --git a/test/unit/plugins/providers/virtualbox/synced_folder_test.rb b/test/unit/plugins/providers/virtualbox/synced_folder_test.rb index 93cec0a18..cbe93d573 100644 --- a/test/unit/plugins/providers/virtualbox/synced_folder_test.rb +++ b/test/unit/plugins/providers/virtualbox/synced_folder_test.rb @@ -1,16 +1,23 @@ require "vagrant" require Vagrant.source_root.join("test/unit/base") +require Vagrant.source_root.join("plugins/providers/virtualbox/config") require Vagrant.source_root.join("plugins/providers/virtualbox/synced_folder") describe VagrantPlugins::ProviderVirtualBox::SyncedFolder do let(:machine) do double("machine").tap do |m| + m.stub(provider_config: VagrantPlugins::ProviderVirtualBox::Config.new) + m.stub(provider_name: :virtualbox) end end subject { described_class.new } + before do + machine.provider_config.finalize! + end + describe "usable" do it "should be with virtualbox provider" do machine.stub(provider_name: :virtualbox) @@ -21,6 +28,11 @@ describe VagrantPlugins::ProviderVirtualBox::SyncedFolder do machine.stub(provider_name: :vmware_fusion) expect(subject).not_to be_usable(machine) end + + it "should not be usable if not functional vboxsf" do + machine.provider_config.functional_vboxsf = false + expect(subject).to_not be_usable(machine) + end end describe "prepare" do diff --git a/test/unit/vagrant/action/builder_test.rb b/test/unit/vagrant/action/builder_test.rb index dfc20654a..98c198dfa 100644 --- a/test/unit/vagrant/action/builder_test.rb +++ b/test/unit/vagrant/action/builder_test.rb @@ -16,6 +16,20 @@ describe Vagrant::Action::Builder do result end + def wrapper_proc(data) + Class.new do + def initialize(app, env) + @app = app + end + + define_method(:call) do |env| + env[:data] << "#{data}_in" + @app.call(env) + env[:data] << "#{data}_out" + end + end + end + context "copying" do it "should copy the stack" do copy = subject.dup @@ -239,4 +253,34 @@ describe Vagrant::Action::Builder do subject.call(data) end end + + describe "calling another app later" do + it "calls in the proper order" do + # We have to do this because inside the Class.new, it can't see these + # rspec methods... + described_klass = described_class + wrapper_proc = self.method(:wrapper_proc) + + wrapper = Class.new do + def initialize(app, env) + @app = app + end + + define_method(:call) do |env| + inner = described_klass.new + inner.use wrapper_proc[2] + inner.use @app + inner.call(env) + end + end + + subject.use wrapper_proc(1) + subject.use wrapper + subject.use wrapper_proc(3) + subject.call(data) + + expect(data[:data]).to eq([ + "1_in", "2_in", "3_in", "3_out", "2_out", "1_out"]) + end + end end diff --git a/test/unit/vagrant/action/builtin/call_test.rb b/test/unit/vagrant/action/builtin/call_test.rb index e4e937b57..c096c5692 100644 --- a/test/unit/vagrant/action/builtin/call_test.rb +++ b/test/unit/vagrant/action/builtin/call_test.rb @@ -4,6 +4,20 @@ describe Vagrant::Action::Builtin::Call do let(:app) { lambda { |env| } } let(:env) { {} } + def wrapper_proc(data) + Class.new do + def initialize(app, env) + @app = app + end + + define_method(:call) do |env| + env[:data] << "#{data}_in" + @app.call(env) + env[:data] << "#{data}_out" + end + end + end + it "should yield the env to the block" do received = nil @@ -64,6 +78,23 @@ describe Vagrant::Action::Builtin::Call do expect(received).to eq(:bar) end + it "should call the next builder inserted in our own stack" do + callable = lambda { |env| } + + builder = Vagrant::Action::Builder.new.tap do |b| + b.use wrapper_proc(1) + b.use described_class, callable do |_env, b2| + b2.use wrapper_proc(2) + end + b.use wrapper_proc(3) + end + + env = { data: [] } + builder.call(env) + expect(env[:data]).to eq([ + "1_in", "2_in", "3_in", "3_out", "2_out", "1_out"]) + end + it "should instantiate the callable with the extra args" do env = {} diff --git a/test/unit/vagrant/action/builtin/message_test.rb b/test/unit/vagrant/action/builtin/message_test.rb index bdb400646..bb477da1b 100644 --- a/test/unit/vagrant/action/builtin/message_test.rb +++ b/test/unit/vagrant/action/builtin/message_test.rb @@ -13,8 +13,17 @@ describe Vagrant::Action::Builtin::Message do it "outputs the given message" do subject = described_class.new(app, env, "foo") - expect(ui).to receive(:output).with("foo") - expect(app).to receive(:call).with(env) + expect(ui).to receive(:output).with("foo").ordered + expect(app).to receive(:call).with(env).ordered + + subject.call(env) + end + + it "outputs the given message after the call" do + subject = described_class.new(app, env, "foo", post: true) + + expect(app).to receive(:call).with(env).ordered + expect(ui).to receive(:output).with("foo").ordered subject.call(env) end diff --git a/test/unit/vagrant/ui_test.rb b/test/unit/vagrant/ui_test.rb index 074f6ee49..537161a56 100644 --- a/test/unit/vagrant/ui_test.rb +++ b/test/unit/vagrant/ui_test.rb @@ -341,5 +341,10 @@ describe Vagrant::UI::Prefixed do expect(ui).to receive(:output).with("==> #{prefix}: foo", {}) subject.output("foo") end + + it "prefixes with another prefix if requested" do + expect(ui).to receive(:output).with("==> bar: foo", anything) + subject.output("foo", target: "bar") + end end end