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
This commit is contained in:
Mitchell Hashimoto 2014-04-10 18:26:19 -07:00
parent d42d62ead1
commit 8c7ab333a0
27 changed files with 609 additions and 66 deletions

View File

@ -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)
# 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)

View File

@ -6,11 +6,19 @@ module Vagrant
def initialize(app, env, message, **opts)
@app = app
@message = message
@opts = opts
end
def call(env)
if !@opts[:post]
env[:ui].output(@message)
end
@app.call(env)
if @opts[:post]
env[:ui].output(@message)
end
end
end
end

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -3,18 +3,59 @@ 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<String>]
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)

View File

@ -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")
@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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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({

View File

@ -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

View File

@ -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

View File

@ -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 = {}

View File

@ -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

View File

@ -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