From af1fcd0ae055ac5cbd1ca879d0ffd4119eeded65 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 12 Feb 2010 19:53:34 -0800 Subject: [PATCH] Initial attempt at refactoring commands out into evented actions. Heavy documentation on the "vagrant/actions/base.rb" class. New VM action runner functionality is well tested, as well. --- lib/vagrant/actions/base.rb | 50 ++++++++++++++++++++++++++++ lib/vagrant/vm.rb | 35 +++++++++++++++++--- test/vagrant/vm_test.rb | 65 +++++++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+), 4 deletions(-) create mode 100644 lib/vagrant/actions/base.rb diff --git a/lib/vagrant/actions/base.rb b/lib/vagrant/actions/base.rb new file mode 100644 index 000000000..b91154496 --- /dev/null +++ b/lib/vagrant/actions/base.rb @@ -0,0 +1,50 @@ +module Vagrant + module Actions + # Base class for any command actions. A command action handles + # executing a step or steps on a given Vagrant::VM object. The + # action should define any callbacks that it will call, or + # attach itself to some callbacks on the VM object. + class Base + attr_reader :vm + + include Vagrant::Util + + # Initialization of the actions are done all at once. The guarantee + # is that when an action is initialized, no other action has had + # its `prepare` or `execute!` method called yet, so an action can + # setup anything it needs to with this safety. An example of this + # would be instance_evaling the vm instance to include a module so + # additionally functionality could be defined on the vm which other + # action `prepare` methods may rely on. + def initialize(vm) + @vm = vm + end + + # This method is called once per action, allowing the action + # to setup any callbacks, add more events, etc. Prepare is + # called in the order the actions are defined, and the action + # itself has no control over this, so no race conditions between + # action setups should be done here. + def prepare + # Examples: + # + # Perhaps we need an additional action to go, specifically + # maybe only if a configuration is set + # + #@vm.actions << FooAction if Vagrant.config[:foo] == :bar + end + + # This method is called once, after preparing, to execute the + # actual task. This method is responsible for calling any + # callbacks. Adding new actions here will have NO EFFECT, and + # adding callbacks has unpredictable effects. + def execute! + # Example code: + # + # @vm.invoke_callback(:before_oven, "cookies") + # Do lots of stuff here + # @vm.invoke_callback(:after_oven, "more", "than", "one", "option") + end + end + end +end \ No newline at end of file diff --git a/lib/vagrant/vm.rb b/lib/vagrant/vm.rb index db1f6f482..53993977d 100644 --- a/lib/vagrant/vm.rb +++ b/lib/vagrant/vm.rb @@ -1,7 +1,8 @@ module Vagrant class VM include Vagrant::Util - attr_reader :vm + attr_accessor :vm + attr_reader :actions class << self # Bring up the virtual machine. Imports the base image and @@ -21,6 +22,32 @@ module Vagrant def initialize(vm=nil) @vm = vm + @actions = [] + end + + def execute! + # Initialize each action. Prepare is not done together with + # this since initialization is a time which guarantees that + # prepare has not been called for any other action yet. + @actions.collect! do |action_class| + action_class.new(self) + end + + # Call the prepare method on each once its + # initialized, then call the execute! method + [:prepare, :execute!].each do |method| + @actions.each do |action| + action.send(method) + end + end + end + + def invoke_callback(name, *args) + # Attempt to call the method for the callback on each of the + # actions + @actions.each do |action| + action.send(name, *args) if action.respond_to?(name) + end end def create @@ -178,15 +205,15 @@ error ovf_path = File.join(folder, "#{name}.ovf") tar_path = "#{folder}.box" - + logger.info "Exporting required VM files to working directory ..." @vm.export(ovf_path) - + # TODO use zlib ... logger.info "Packaging VM into #{name}.box ..." Tar.open(tar_path, File::CREAT | File::WRONLY, 0644, Tar::GNU) do |tar| begin - # appending the expanded file path adds the whole folder tree + # appending the expanded file path adds the whole folder tree # to the tar archive there must be a better way working_dir = FileUtils.pwd FileUtils.cd(to) diff --git a/test/vagrant/vm_test.rb b/test/vagrant/vm_test.rb index 12c7bb27d..9a3a07761 100644 --- a/test/vagrant/vm_test.rb +++ b/test/vagrant/vm_test.rb @@ -11,6 +11,71 @@ class VMTest < Test::Unit::TestCase Net::SSH.stubs(:start) end + context "callbacks" do + setup do + @vm = Vagrant::VM.new(@mock_vm) + end + + should "not invoke callback on actions which don't respond to it" do + action = mock("action") + action.stubs(:respond_to?).with(:foo).returns(false) + action.expects(:foo).never + + assert_nothing_raised do + @vm.actions << action + @vm.invoke_callback(:foo) + end + end + + should "invoke callback on actions which do respond to the method" do + action = mock("action") + action.expects(:foo).once + + @vm.actions << action + @vm.invoke_callback(:foo) + end + end + + context "actions" do + setup do + @vm = Vagrant::VM.new(@mock_vm) + end + + should "be empty initially" do + assert @vm.actions.empty? + end + + should "be able to add actions" do + assert_nothing_raised do + @vm.actions << "Foo" + @vm.actions << "Bar" + assert_equal 2, @vm.actions.length + end + end + + should "run #prepare on all actions, then #execute!" do + action_seq = sequence("action_seq") + actions = [] + 5.times do |i| + action = mock("action#{i}") + action_class = mock("action_class#{i}") + + action_class.expects(:new).once.returns(action).in_sequence(action_seq) + + @vm.actions << action_class + actions << action + end + + [:prepare, :execute!].each do |method| + actions.each do |action| + action.expects(method).once.in_sequence(action_seq) + end + end + + @vm.execute! + end + end + context "vagrant up" do should "create a Vagrant::VM instance and call create" do inst = mock("instance")