New abstraction: Systems. Updated config and environment to properly load configured system.
This allows the OS-specific tasks to be pulled out into separate classes, so that other systems could potentially be supported. For now, a "Linux" system has been created.
This commit is contained in:
parent
1429723277
commit
95938c652d
|
@ -21,6 +21,7 @@ Vagrant::Config.run do |config|
|
|||
config.vm.shared_folder_uid = nil
|
||||
config.vm.shared_folder_gid = nil
|
||||
config.vm.boot_mode = "vrdp"
|
||||
config.vm.system = :linux
|
||||
|
||||
config.package.name = 'vagrant'
|
||||
config.package.extension = '.box'
|
||||
|
|
|
@ -86,6 +86,7 @@ module Vagrant
|
|||
attr_accessor :provisioner
|
||||
attr_accessor :shared_folder_uid
|
||||
attr_accessor :shared_folder_gid
|
||||
attr_accessor :system
|
||||
|
||||
def initialize
|
||||
@forwarded_ports = {}
|
||||
|
|
|
@ -174,8 +174,7 @@ module Vagrant
|
|||
return if !root_path || !File.file?(dotfile_path)
|
||||
|
||||
File.open(dotfile_path) do |f|
|
||||
@vm = Vagrant::VM.find(f.read)
|
||||
@vm.env = self if @vm
|
||||
@vm = Vagrant::VM.find(f.read, self)
|
||||
end
|
||||
rescue Errno::ENOENT
|
||||
@vm = nil
|
||||
|
@ -206,9 +205,7 @@ module Vagrant
|
|||
# in {Command.up}. This will very likely be refactored at a later
|
||||
# time.
|
||||
def create_vm
|
||||
@vm = VM.new
|
||||
@vm.env = self
|
||||
@vm
|
||||
@vm = VM.new(self)
|
||||
end
|
||||
|
||||
# Persists this environment's VM to the dotfile so it can be
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
module Vagrant
|
||||
module Systems
|
||||
# The base class for a "system." A system represents an installed
|
||||
# operating system on a given box. There are some portions of
|
||||
# Vagrant which are fairly OS-specific (such as mounting shared
|
||||
# folders) and while the number is few, this abstraction allows
|
||||
# more obscure operating systems to be installed without having
|
||||
# to directly modify Vagrant internals.
|
||||
#
|
||||
# Subclasses of the system base class are expected to implement
|
||||
# all the methods. These methods are described in the comments
|
||||
# above their definition.
|
||||
#
|
||||
# **This is by no means a complete specification. The methods
|
||||
# required by systems can and will change at any time. Any
|
||||
# changes will be noted on release notes.**
|
||||
class Base
|
||||
include Vagrant::Util
|
||||
|
||||
# The VM which this system is tied to.
|
||||
attr_reader :vm
|
||||
|
||||
# Initializes the system. Any subclasses MUST make sure this
|
||||
# method is called on the parent. Therefore, if a subclass overrides
|
||||
# `initialize`, then you must call `super`.
|
||||
def initialize(vm)
|
||||
@vm = vm
|
||||
end
|
||||
|
||||
# Mounts a shared folder. This method is called by the shared
|
||||
# folder action with an open SSH session (passed in as `ssh`).
|
||||
# This method should create, mount, and properly set permissions
|
||||
# on the shared folder. This method should also properly
|
||||
# adhere to any configuration values such as `shared_folder_uid`
|
||||
# on `config.vm`.
|
||||
#
|
||||
# @param [Object] ssh The Net::SSH session.
|
||||
# @param [String] name The name of the shared folder.
|
||||
# @param [String] guestpath The path on the machine which the user
|
||||
# wants the folder mounted.
|
||||
def mount_shared_folder(ssh, name, guestpath); end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,46 @@
|
|||
module Vagrant
|
||||
module Systems
|
||||
# A general Vagrant system implementation for "linux." In general,
|
||||
# any linux-based OS will work fine with this system, although its
|
||||
# not tested exhaustively. BSD or other based systems may work as
|
||||
# well, but that hasn't been tested at all.
|
||||
#
|
||||
# At any rate, this system implementation should server as an
|
||||
# example of how to implement any custom systems necessary.
|
||||
class Linux < Base
|
||||
#-------------------------------------------------------------------
|
||||
# Overridden methods
|
||||
#-------------------------------------------------------------------
|
||||
def mount_shared_folder(ssh, name, guestpath)
|
||||
ssh.exec!("sudo mkdir -p #{guestpath}")
|
||||
mount_folder(ssh, name, guestpath)
|
||||
ssh.exec!("sudo chown #{vm.env.config.ssh.username} #{guestpath}")
|
||||
end
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
# "Private" methods which assist above methods
|
||||
#-------------------------------------------------------------------
|
||||
def mount_folder(ssh, name, guestpath, sleeptime=5)
|
||||
# Determine the permission string to attach to the mount command
|
||||
perms = []
|
||||
perms << "uid=#{vm.env.config.vm.shared_folder_uid}"
|
||||
perms << "gid=#{vm.env.config.vm.shared_folder_gid}"
|
||||
perms = " -o #{perms.join(",")}" if !perms.empty?
|
||||
|
||||
attempts = 0
|
||||
while true
|
||||
result = ssh.exec!("sudo mount -t vboxsf#{perms} #{name} #{guestpath}") do |ch, type, data|
|
||||
# net/ssh returns the value in ch[:result] (based on looking at source)
|
||||
ch[:result] = !!(type == :stderr && data =~ /No such device/i)
|
||||
end
|
||||
|
||||
break unless result
|
||||
|
||||
attempts += 1
|
||||
raise Actions::ActionException.new(:vm_mount_fail) if attempts >= 10
|
||||
sleep sleeptime
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,22 +2,48 @@ module Vagrant
|
|||
class VM < Actions::Runner
|
||||
include Vagrant::Util
|
||||
|
||||
attr_accessor :env
|
||||
attr_reader :env
|
||||
attr_reader :system
|
||||
attr_accessor :vm
|
||||
attr_accessor :from
|
||||
|
||||
class << self
|
||||
# Finds a virtual machine by a given UUID and either returns
|
||||
# a Vagrant::VM object or returns nil.
|
||||
def find(uuid)
|
||||
def find(uuid, env=nil)
|
||||
vm = VirtualBox::VM.find(uuid)
|
||||
return nil if vm.nil?
|
||||
new(vm)
|
||||
new(env, vm)
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(vm=nil)
|
||||
def initialize(env, vm=nil)
|
||||
@env = env
|
||||
@vm = vm
|
||||
|
||||
load_system!
|
||||
end
|
||||
|
||||
def load_system!
|
||||
system = env.config.vm.system
|
||||
|
||||
if system.is_a?(Class)
|
||||
@system = system.new(self)
|
||||
error_and_exit(:system_invalid_class, :system => system.to_s) unless @system.is_a?(Systems::Base)
|
||||
elsif system.is_a?(Symbol)
|
||||
# Hard-coded internal systems
|
||||
mapping = {
|
||||
:linux => Systems::Linux
|
||||
}
|
||||
|
||||
if !mapping.has_key?(system)
|
||||
error_and_exit(:system_unknown_type, :system => system.to_s)
|
||||
return # for tests
|
||||
end
|
||||
|
||||
@system = mapping[system].new(self)
|
||||
else
|
||||
error_and_exit(:system_unspecified)
|
||||
end
|
||||
end
|
||||
|
||||
def uuid
|
||||
|
|
|
@ -103,7 +103,17 @@
|
|||
For a more detailed guide please consult:
|
||||
|
||||
http://vagrantup.com/docs/getting-started/windows.html
|
||||
:system_invalid_class: |-
|
||||
The specified system does not inherit from `Vagrant::Systems::Base`. The
|
||||
specified system class must inherit from this class.
|
||||
|
||||
The specified system class was: <%= system %>
|
||||
:system_unknown_type: |-
|
||||
The specified system type is unknown: <%= system %>. Please change this
|
||||
to a proper value.
|
||||
:system_unspecified: |-
|
||||
A VM system type must be specified! This is done via the `config.vm.system`
|
||||
configuration value. Please read the documentation online for more information.
|
||||
:virtualbox_import_failure: |-
|
||||
The VM import failed! Try running `VBoxManage import` on the box file
|
||||
manually for more verbose error output.
|
||||
|
|
|
@ -41,6 +41,7 @@ class Test::Unit::TestCase
|
|||
config.vm.forward_port("ssh", 22, 2222)
|
||||
config.vm.shared_folder_uid = nil
|
||||
config.vm.shared_folder_gid = nil
|
||||
config.vm.system = :linux
|
||||
|
||||
config.package.name = 'vagrant'
|
||||
config.package.extension = '.box'
|
||||
|
|
|
@ -356,11 +356,10 @@ class EnvironmentTest < Test::Unit::TestCase
|
|||
|
||||
should "loading of the uuid from the dotfile" do
|
||||
vm = mock("vm")
|
||||
vm.expects(:env=).with(@env)
|
||||
|
||||
filemock = mock("filemock")
|
||||
filemock.expects(:read).returns("foo")
|
||||
Vagrant::VM.expects(:find).with("foo").returns(vm)
|
||||
Vagrant::VM.expects(:find).with("foo", @env).returns(vm)
|
||||
File.expects(:open).with(@env.dotfile_path).once.yields(filemock)
|
||||
File.expects(:file?).with(@env.dotfile_path).once.returns(true)
|
||||
@env.load_vm!
|
||||
|
@ -371,7 +370,7 @@ class EnvironmentTest < Test::Unit::TestCase
|
|||
should "not set the environment if the VM is nil" do
|
||||
filemock = mock("filemock")
|
||||
filemock.expects(:read).returns("foo")
|
||||
Vagrant::VM.expects(:find).with("foo").returns(nil)
|
||||
Vagrant::VM.expects(:find).with("foo", @env).returns(nil)
|
||||
File.expects(:open).with(@env.dotfile_path).once.yields(filemock)
|
||||
File.expects(:file?).with(@env.dotfile_path).once.returns(true)
|
||||
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
require File.join(File.dirname(__FILE__), '..', '..', 'test_helper')
|
||||
|
||||
class LinuxSystemTest < Test::Unit::TestCase
|
||||
setup do
|
||||
@klass = Vagrant::Systems::Linux
|
||||
|
||||
@vm = mock("vm")
|
||||
@vm.stubs(:env).returns(mock_environment)
|
||||
@instance = @klass.new(@vm)
|
||||
end
|
||||
|
||||
context "mounting shared folders" do
|
||||
setup do
|
||||
@ssh = mock("ssh")
|
||||
@name = "foo"
|
||||
@guestpath = "/bar"
|
||||
end
|
||||
|
||||
should "create the dir, mount the folder, then set permissions" do
|
||||
mount_seq = sequence("mount_seq")
|
||||
@ssh.expects(:exec!).with("sudo mkdir -p #{@guestpath}").in_sequence(mount_seq)
|
||||
@instance.expects(:mount_folder).with(@ssh, @name, @guestpath).in_sequence(mount_seq)
|
||||
@ssh.expects(:exec!).with("sudo chown #{@vm.env.config.ssh.username} #{@guestpath}").in_sequence(mount_seq)
|
||||
|
||||
@instance.mount_shared_folder(@ssh, @name, @guestpath)
|
||||
end
|
||||
end
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
# "Private" methods tests
|
||||
#-------------------------------------------------------------------
|
||||
context "mounting the main folder" do
|
||||
setup do
|
||||
@ssh = mock("ssh")
|
||||
@name = "foo"
|
||||
@guestpath = "bar"
|
||||
@sleeptime = 0
|
||||
@limit = 10
|
||||
|
||||
@success_return = false
|
||||
end
|
||||
|
||||
def mount_folder
|
||||
@instance.mount_folder(@ssh, @name, @guestpath, @sleeptime)
|
||||
end
|
||||
|
||||
should "execute the proper mount command" do
|
||||
@ssh.expects(:exec!).with("sudo mount -t vboxsf -o uid=#{@vm.env.config.ssh.username},gid=#{@vm.env.config.ssh.username} #{@name} #{@guestpath}").returns(@success_return)
|
||||
mount_folder
|
||||
end
|
||||
|
||||
should "test type of text and text string to detect error" do
|
||||
data = mock("data")
|
||||
data.expects(:[]=).with(:result, !@success_return)
|
||||
|
||||
@ssh.expects(:exec!).yields(data, :stderr, "No such device").returns(@success_return)
|
||||
mount_folder
|
||||
end
|
||||
|
||||
should "test type of text and test string to detect success" do
|
||||
data = mock("data")
|
||||
data.expects(:[]=).with(:result, @success_return)
|
||||
|
||||
@ssh.expects(:exec!).yields(data, :stdout, "Nothing such device").returns(@success_return)
|
||||
mount_folder
|
||||
end
|
||||
|
||||
should "raise an ActionException if the command fails constantly" do
|
||||
@ssh.expects(:exec!).times(@limit).returns(!@success_return)
|
||||
|
||||
assert_raises(Vagrant::Actions::ActionException) {
|
||||
mount_folder
|
||||
}
|
||||
end
|
||||
|
||||
should "not raise any exception if the command succeeded" do
|
||||
@ssh.expects(:exec!).once.returns(@success_return)
|
||||
|
||||
assert_nothing_raised {
|
||||
mount_folder
|
||||
}
|
||||
end
|
||||
|
||||
should "add uid AND gid to mount" do
|
||||
uid = "foo"
|
||||
gid = "bar"
|
||||
env = mock_environment do |config|
|
||||
config.vm.shared_folder_uid = uid
|
||||
config.vm.shared_folder_gid = gid
|
||||
end
|
||||
|
||||
@vm.stubs(:env).returns(env)
|
||||
|
||||
@ssh.expects(:exec!).with("sudo mount -t vboxsf -o uid=#{uid},gid=#{gid} #{@name} #{@guestpath}").returns(@success_return)
|
||||
mount_folder
|
||||
end
|
||||
end
|
||||
end
|
|
@ -14,7 +14,7 @@ class VMTest < Test::Unit::TestCase
|
|||
|
||||
context "being an action runner" do
|
||||
should "be an action runner" do
|
||||
vm = Vagrant::VM.new
|
||||
vm = Vagrant::VM.new(@env)
|
||||
assert vm.is_a?(Vagrant::Actions::Runner)
|
||||
end
|
||||
end
|
||||
|
@ -27,7 +27,7 @@ class VMTest < Test::Unit::TestCase
|
|||
|
||||
should "return a Vagrant::VM object for that VM otherwise" do
|
||||
VirtualBox::VM.expects(:find).with("foo").returns("bar")
|
||||
result = Vagrant::VM.find("foo")
|
||||
result = Vagrant::VM.find("foo", mock_environment)
|
||||
assert result.is_a?(Vagrant::VM)
|
||||
assert_equal "bar", result.vm
|
||||
end
|
||||
|
@ -35,10 +35,63 @@ class VMTest < Test::Unit::TestCase
|
|||
|
||||
context "vagrant VM instance" do
|
||||
setup do
|
||||
@vm = Vagrant::VM.new(@mock_vm)
|
||||
@vm = Vagrant::VM.new(@env, @mock_vm)
|
||||
@mock_vm.stubs(:uuid).returns("foo")
|
||||
end
|
||||
|
||||
context "loading associated system" do
|
||||
should "error and exit if system is not specified" do
|
||||
@vm.env.config.vm.system = nil
|
||||
|
||||
@vm.expects(:error_and_exit).with(:system_unspecified).once
|
||||
@vm.load_system!
|
||||
end
|
||||
|
||||
context "with a class" do
|
||||
class FakeSystemClass
|
||||
def initialize(vm); end
|
||||
end
|
||||
|
||||
should "initialize class if given" do
|
||||
@vm.env.config.vm.system = Vagrant::Systems::Linux
|
||||
|
||||
@vm.expects(:error_and_exit).never
|
||||
@vm.load_system!
|
||||
|
||||
assert @vm.system.is_a?(Vagrant::Systems::Linux)
|
||||
end
|
||||
|
||||
should "error and exit if class has invalid parent" do
|
||||
@vm.env.config.vm.system = FakeSystemClass
|
||||
@vm.expects(:error_and_exit).with(:system_invalid_class, :system => @vm.env.config.vm.system.to_s).once
|
||||
@vm.load_system!
|
||||
end
|
||||
end
|
||||
|
||||
context "with a symbol" do
|
||||
should "initialize proper symbols" do
|
||||
valid = {
|
||||
:linux => Vagrant::Systems::Linux
|
||||
}
|
||||
|
||||
valid.each do |symbol, klass|
|
||||
@vm.env.config.vm.system = symbol
|
||||
@vm.expects(:error_and_exit).never
|
||||
@vm.load_system!
|
||||
|
||||
assert @vm.system.is_a?(klass)
|
||||
assert_equal @vm, @vm.system.vm
|
||||
end
|
||||
end
|
||||
|
||||
should "error and exit with invalid symbol" do
|
||||
@vm.env.config.vm.system = :shall_never_exist
|
||||
@vm.expects(:error_and_exit).with(:system_unknown_type, :system => @vm.env.config.vm.system.to_s).once
|
||||
@vm.load_system!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "uuid" do
|
||||
should "call UUID on VM object" do
|
||||
uuid = mock("uuid")
|
||||
|
|
Loading…
Reference in New Issue