From 43cadfe8302de8b0ae4930e1f382fa577bb6b304 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 17 Dec 2011 09:14:05 -0800 Subject: [PATCH] `vagrant up` --- lib/vagrant.rb | 13 +++- lib/vagrant/cli.rb | 18 ++++- lib/vagrant/command.rb | 2 + lib/vagrant/command/base.rb | 59 +++++++++++++++++ lib/vagrant/command/helpers.rb | 43 ------------ lib/vagrant/command/up.rb | 35 ++++++++-- lib/vagrant/environment.rb | 1 + test/unit/vagrant/command/base_test.rb | 91 +++++++++++++++++++++++++- test/unit/vagrant_test.rb | 4 ++ 9 files changed, 212 insertions(+), 54 deletions(-) delete mode 100644 lib/vagrant/command/helpers.rb diff --git a/lib/vagrant.rb b/lib/vagrant.rb index 5c9b88e5f..d359bf0fe 100644 --- a/lib/vagrant.rb +++ b/lib/vagrant.rb @@ -46,6 +46,14 @@ module Vagrant @source_root ||= Pathname.new(File.expand_path('../../', __FILE__)) end + # Global registry of commands that are available via the CLI. + # + # This registry is used to look up the sub-commands that are available + # to Vagrant. + def self.commands + @commands ||= Registry.new + end + # Global registry of config keys that are available. # # This registry is used to look up the keys for `config` objects. @@ -85,7 +93,10 @@ end # # Default I18n to load the en locale I18n.load_path << File.expand_path("templates/locales/en.yml", Vagrant.source_root) -# Registry the build-in config keys +# Register the built-in commands +Vagrant.commands.register(:up) { Vagrant::Command::Up } + +# Register the built-in config keys Vagrant.config_keys.register(:vagrant) { Vagrant::Config::VagrantConfig } Vagrant.config_keys.register(:ssh) { Vagrant::Config::SSHConfig } Vagrant.config_keys.register(:nfs) { Vagrant::Config::NFSConfig } diff --git a/lib/vagrant/cli.rb b/lib/vagrant/cli.rb index 403847a0a..68b2c8422 100644 --- a/lib/vagrant/cli.rb +++ b/lib/vagrant/cli.rb @@ -1,11 +1,16 @@ +require 'log4r' require 'optparse' module Vagrant # Manages the command line interface to Vagrant. class CLI < Command::Base def initialize(argv, env) - @env = env + super + + @logger = Log4r::Logger.new("vagrant::cli") @main_args, @sub_command, @sub_args = split_main_and_subcommand(argv) + + @logger.info("CLI: #{@main_args.inspect} #{@sub_command.inspect} #{@sub_args.inspect}") end def execute @@ -22,9 +27,18 @@ module Vagrant # the help and exit. return help end + + # If we reached this far then we must have a subcommand. If not, + # then we also just print the help and exit. + command_class = Vagrant.commands.get(@sub_command.to_sym) + return help if !command_class || !@sub_command + @logger.debug("Invoking command class: #{command_class} #{@sub_args.inspect}") + + # Initialize and execute the command class. + command_class.new(@sub_args, @env).execute end - # This prints the help for the CLI out. + # This prints out the help for the CLI. def help # We use the optionparser for this. Its just easier. We don't use # an optionparser above because I don't think the performance hits diff --git a/lib/vagrant/command.rb b/lib/vagrant/command.rb index 6d1f5f87d..e4cff3477 100644 --- a/lib/vagrant/command.rb +++ b/lib/vagrant/command.rb @@ -1,5 +1,7 @@ module Vagrant module Command autoload :Base, 'vagrant/command/base' + + autoload :Up, 'vagrant/command/up' end end diff --git a/lib/vagrant/command/base.rb b/lib/vagrant/command/base.rb index 8058a7fea..09a2a01f3 100644 --- a/lib/vagrant/command/base.rb +++ b/lib/vagrant/command/base.rb @@ -1,8 +1,63 @@ +require 'log4r' + module Vagrant module Command + # Base class for any CLI commands. + # + # This class provides documentation on the interface as well as helper + # functions that a command has. class Base + def initialize(argv, env) + @argv = argv + @env = env + @logger = Log4r::Logger.new("vagrant::command::#{self.class.to_s.downcase}") + end + + # This is what is called on the class to actually execute it. Any + # subclasses should implement this method and do any option parsing + # and validation here. + def execute; end + protected + # Parses the options given an OptionParser instance. + # + # This is a convenience method that properly handles duping the + # originally argv array so that it is not destroyed. That is all. + def parse_options(opts) + argv = @argv.dup + opts.parse!(argv) + return argv + end + + # Yields a VM for each target VM for the command. + # + # This is a convenience method for easily implementing methods that + # take a target VM (in the case of multi-VM) or every VM if no + # specific VM name is specified. + # + # @param [String] name The name of the VM. Nil if every VM. + def with_target_vms(name=nil) + # First determine the proper array of VMs. + vms = [] + if name + raise Errors::MultiVMEnvironmentRequired if !@env.multivm? + vms << @env.vms[name.to_sym] + raise Errors::VMNotFoundError, :name => name if !vms[0] + else + vms = @env.vms_ordered + end + + # Go through each VM and yield it! + vms.each do |old_vm| + # We get a new VM from the environment here to avoid potentially + # stale VMs (if there was a config reload on the environment + # or something). + vm = @env.vms[old_vm.name] + yield vm + end + end + # This method will split the argv given into three parts: the # flags to this command, the subcommand, and the flags to the # subcommand. For example: @@ -36,6 +91,10 @@ module Vagrant main_args = argv[0, i] sub_command = argv[i] sub_args = argv[i + 1, argv.length - i + 1] + + # Break so we don't find the next non flag and shift our + # main args. + break end end diff --git a/lib/vagrant/command/helpers.rb b/lib/vagrant/command/helpers.rb deleted file mode 100644 index 80eda6a27..000000000 --- a/lib/vagrant/command/helpers.rb +++ /dev/null @@ -1,43 +0,0 @@ -module Vagrant - module Command - module Helpers - # Initializes the environment by pulling the environment out of - # the configuration hash and sets up the UI if necessary. - def initialize_environment(args, options, config) - raise Errors::CLIMissingEnvironment if !config[:env] - @env = config[:env] - end - - # This returns an array of {VM} objects depending on the arguments - # given to the command. - def target_vms(name=nil) - raise Errors::NoEnvironmentError if !env.root_path - - name ||= self.name rescue nil - - @target_vms ||= begin - if env.multivm? - return env.vms_ordered if !name - vm = env.vms[name.to_sym] - raise Errors::VMNotFoundError, :name => name if !vm - else - raise Errors::MultiVMEnvironmentRequired if name - vm = env.vms.values.first - end - - [vm] - end - end - - # This will yield for each target VM to the command. The VM is guaranteed - # to be loaded on each iteration. - def with_target_vms - target_vms.each do |old_vm| - # We get a new VM here to avoid potentially stale VMs - vm = env.vms[old_vm.name] - yield vm - end - end - end - end -end diff --git a/lib/vagrant/command/up.rb b/lib/vagrant/command/up.rb index 7ec6b5bc2..d647a2844 100644 --- a/lib/vagrant/command/up.rb +++ b/lib/vagrant/command/up.rb @@ -1,15 +1,38 @@ +require 'optparse' + module Vagrant module Command - class UpCommand < NamedBase - class_option :provision, :type => :boolean, :default => true - register "up", "Creates the Vagrant environment" - + class Up < Base def execute - with_target_vms do |vm| + options = {} + + opts = OptionParser.new do |opts| + opts.banner = "Usage: vagrant up [vm-name] [--[no-]provision] [-h]" + + opts.separator "" + + opts.on("--[no-]provision", "Enable or disable provisioning") do |p| + options[:provision] = p + end + + opts.on("-h", "--help", "Print this help") do + puts opts.help + return + end + end + + # Parse the options + argv = parse_options(opts) + + # Go over each VM and bring it up + @logger.debug("'Up' each target VM...") + with_target_vms(argv[0]) do |vm| if vm.created? - vm.env.ui.info I18n.t("vagrant.commands.up.vm_created") + @logger.info("Booting: #{vm.name}") + vm.ui.info I18n.t("vagrant.commands.up.vm_created") vm.start("provision.enabled" => options[:provision]) else + @logger.info("Creating: #{vm.name}") vm.up("provision.enabled" => options[:provision]) end end diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 61fe584b5..2811bacef 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -139,6 +139,7 @@ module Vagrant # # @return [Array] def vms_ordered + return @vms.values if !multivm? @vms_enum ||= config.global.vm.defined_vm_keys.map { |name| @vms[name] } end diff --git a/test/unit/vagrant/command/base_test.rb b/test/unit/vagrant/command/base_test.rb index 25eda35d1..bab3fd35e 100644 --- a/test/unit/vagrant/command/base_test.rb +++ b/test/unit/vagrant/command/base_test.rb @@ -1,12 +1,94 @@ require File.expand_path("../../../base", __FILE__) +require 'optparse' describe Vagrant::Command::Base do + describe "parsing options" do + let(:klass) do + Class.new(described_class) do + # Make the method public since it is normally protected + public :parse_options + end + end + + it "returns the remaining arguments" do + options = {} + opts = OptionParser.new do |opts| + opts.on("-f") do |f| + options[:f] = f + end + end + + result = klass.new(["-f", "foo"], nil).parse_options(opts) + + # Check the results + options[:f].should be + result.should == ["foo"] + end + end + + describe "target VMs" do + let(:klass) do + Class.new(described_class) do + # Make the method public since it is normally protected + public :with_target_vms + end + end + + let(:environment) { double("environment") } + let(:instance) { klass.new([], environment) } + + it "should raise an exception if a name is given in a non-multivm environment" do + environment.stub(:multivm?).and_return(false) + + expect { instance.with_target_vms("foo") }. + to raise_error(Vagrant::Errors::MultiVMEnvironmentRequired) + end + + it "should yield every VM in order is no name is given" do + foo_vm = double("foo") + foo_vm.stub(:name).and_return("foo") + + bar_vm = double("bar") + bar_vm.stub(:name).and_return("bar") + + environment.stub(:multivm? => true, + :vms => { "foo" => foo_vm, "bar" => bar_vm }, + :vms_ordered => [foo_vm, bar_vm]) + + vms = [] + instance.with_target_vms do |vm| + vms << vm + end + + vms.should == [foo_vm, bar_vm] + end + + it "raises an exception if the named VM doesn't exist" do + environment.stub(:multivm? => true, :vms => {}) + + expect { instance.with_target_vms("foo") }. + to raise_error(Vagrant::Errors::VMNotFoundError) + end + + it "yields the given VM if a name is given" do + foo_vm = double("foo") + foo_vm.stub(:name).and_return(:foo) + + environment.stub(:multivm? => true, + :vms => { :foo => foo_vm, :bar => nil }) + + vms = [] + instance.with_target_vms("foo") { |vm| vms << vm } + vms.should == [foo_vm] + end + end + describe "splitting the main and subcommand args" do let(:instance) do Class.new(described_class) do - # Make the method public since it is normal protected + # Make the method public since it is normally protected public :split_main_and_subcommand - end.new + end.new(nil, nil) end it "should work when given all 3 parts" do @@ -28,5 +110,10 @@ describe Vagrant::Command::Base do result = instance.split_main_and_subcommand(["status"]) result.should == [[], "status", []] end + + it "works when there are other non-flag args after the subcommand" do + result = instance.split_main_and_subcommand(["-v", "box", "add", "-h"]) + result.should == [["-v"], "box", ["add", "-h"]] + end end end diff --git a/test/unit/vagrant_test.rb b/test/unit/vagrant_test.rb index 46ab37d63..de2b2f379 100644 --- a/test/unit/vagrant_test.rb +++ b/test/unit/vagrant_test.rb @@ -5,6 +5,10 @@ describe Vagrant do described_class.source_root.should == Pathname.new(File.expand_path("../../../", __FILE__)) end + it "has a registry for commands" do + described_class.commands.should be_a(Vagrant::Registry) + end + it "has a registry for config keys" do described_class.config_keys.should be_a(Vagrant::Registry) end