vagrantup 0.8.7 → 0.8.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +21 -0
  3. data/CHANGELOG.md +25 -0
  4. data/README.md +38 -8
  5. data/Rakefile +13 -6
  6. data/bin/vagrant +6 -1
  7. data/config/default.rb +2 -2
  8. data/lib/vagrant/action/box/download.rb +14 -2
  9. data/lib/vagrant/action/vm/check_box.rb +8 -1
  10. data/lib/vagrant/action/vm/check_guest_additions.rb +6 -3
  11. data/lib/vagrant/action/vm/share_folders.rb +12 -1
  12. data/lib/vagrant/command/init.rb +1 -1
  13. data/lib/vagrant/downloaders/http.rb +29 -2
  14. data/lib/vagrant/hosts.rb +1 -0
  15. data/lib/vagrant/hosts/bsd.rb +1 -0
  16. data/lib/vagrant/hosts/freebsd.rb +51 -0
  17. data/lib/vagrant/provisioners/chef.rb +6 -5
  18. data/lib/vagrant/provisioners/chef_client.rb +1 -1
  19. data/lib/vagrant/provisioners/chef_solo.rb +13 -4
  20. data/lib/vagrant/ssh.rb +24 -49
  21. data/lib/vagrant/ssh/session.rb +1 -1
  22. data/lib/vagrant/systems/solaris.rb +57 -11
  23. data/lib/vagrant/test_helpers.rb +23 -0
  24. data/lib/vagrant/ui.rb +1 -1
  25. data/lib/vagrant/util.rb +0 -1
  26. data/lib/vagrant/util/file_checksum.rb +38 -0
  27. data/lib/vagrant/util/platform.rb +1 -1
  28. data/lib/vagrant/util/safe_exec.rb +2 -1
  29. data/lib/vagrant/version.rb +1 -1
  30. data/tasks/acceptance.rake +113 -0
  31. data/tasks/bundler.rake +3 -0
  32. data/tasks/test.rake +15 -0
  33. data/templates/commands/init/Vagrantfile.erb +3 -0
  34. data/templates/locales/en.yml +1 -1
  35. data/test/acceptance/base.rb +48 -0
  36. data/test/acceptance/box_test.rb +77 -0
  37. data/test/acceptance/destroy_test.rb +37 -0
  38. data/test/acceptance/halt_test.rb +72 -0
  39. data/test/acceptance/init_test.rb +33 -0
  40. data/test/acceptance/resume_test.rb +17 -0
  41. data/test/acceptance/ssh_test.rb +41 -0
  42. data/test/acceptance/support/config.rb +42 -0
  43. data/test/acceptance/support/isolated_environment.rb +226 -0
  44. data/test/acceptance/support/matchers/have_color.rb +9 -0
  45. data/test/acceptance/support/matchers/match_output.rb +14 -0
  46. data/test/acceptance/support/output.rb +87 -0
  47. data/test/acceptance/support/shared/base_context.rb +65 -0
  48. data/test/acceptance/support/shared/command_examples.rb +33 -0
  49. data/test/acceptance/support/tempdir.rb +34 -0
  50. data/test/acceptance/support/virtualbox.rb +36 -0
  51. data/test/acceptance/suspend_test.rb +56 -0
  52. data/test/acceptance/up_basic_test.rb +58 -0
  53. data/test/acceptance/up_with_box_url.rb +40 -0
  54. data/test/acceptance/vagrant_test.rb +47 -0
  55. data/test/acceptance/version_test.rb +20 -0
  56. data/test/buildbot/README.md +72 -0
  57. data/test/buildbot/buildbot_config/__init__.py +0 -0
  58. data/test/buildbot/buildbot_config/config/__init__.py +0 -0
  59. data/test/buildbot/buildbot_config/config/loader.py +24 -0
  60. data/test/buildbot/buildbot_config/config/master.py +24 -0
  61. data/test/buildbot/buildbot_config/config/slave.py +22 -0
  62. data/test/buildbot/buildbot_config/master/__init__.py +6 -0
  63. data/test/buildbot/buildbot_config/master/builders.py +78 -0
  64. data/test/buildbot/buildbot_config/master/buildsteps.py +100 -0
  65. data/test/buildbot/buildbot_config/master/change_sources.py +8 -0
  66. data/test/buildbot/buildbot_config/master/schedulers.py +32 -0
  67. data/test/buildbot/buildbot_config/master/slaves.py +60 -0
  68. data/test/buildbot/buildbot_config/master/status.py +52 -0
  69. data/test/buildbot/master/Makefile.sample +28 -0
  70. data/test/buildbot/master/buildbot.tac +36 -0
  71. data/test/buildbot/master/master.cfg +67 -0
  72. data/test/buildbot/master/public_html/bg_gradient.jpg +0 -0
  73. data/test/buildbot/master/public_html/default.css +545 -0
  74. data/test/buildbot/master/public_html/favicon.ico +0 -0
  75. data/test/buildbot/master/public_html/robots.txt +10 -0
  76. data/test/buildbot/master/public_html/static/css/bootstrap-1.4.0.min.css +356 -0
  77. data/test/buildbot/master/public_html/static/css/prettify.css +97 -0
  78. data/test/buildbot/master/public_html/static/css/syntax.css +60 -0
  79. data/test/buildbot/master/public_html/static/css/vagrant.base.css +205 -0
  80. data/test/buildbot/master/public_html/static/images/base_box_mac.jpg +0 -0
  81. data/test/buildbot/master/public_html/static/images/getting-started/success.jpg +0 -0
  82. data/test/buildbot/master/public_html/static/images/icons/error.png +0 -0
  83. data/test/buildbot/master/public_html/static/images/vagrant_chilling.png +0 -0
  84. data/test/buildbot/master/public_html/static/images/vagrant_holding.png +0 -0
  85. data/test/buildbot/master/public_html/static/images/vagrant_looking.png +0 -0
  86. data/test/buildbot/master/public_html/static/images/windows/alter_path.jpg +0 -0
  87. data/test/buildbot/master/public_html/static/images/windows/edit_path.jpg +0 -0
  88. data/test/buildbot/master/public_html/static/images/windows/environment_variables_button.jpg +0 -0
  89. data/test/buildbot/master/public_html/static/images/windows/port_and_ppk_path.jpg +0 -0
  90. data/test/buildbot/master/public_html/static/images/windows/ppk_selection.jpg +0 -0
  91. data/test/buildbot/master/public_html/static/images/windows/putty_first_screen.jpg +0 -0
  92. data/test/buildbot/master/public_html/static/images/windows/save_result.jpg +0 -0
  93. data/test/buildbot/master/public_html/static/images/windows/vbox_manage_default_location.jpg +0 -0
  94. data/test/buildbot/master/public_html/static/js/bootstrap-tabs.js +80 -0
  95. data/test/buildbot/master/public_html/static/js/jquery-1.7.min.js +4 -0
  96. data/test/buildbot/master/templates/authfail.html +9 -0
  97. data/test/buildbot/master/templates/build.html +205 -0
  98. data/test/buildbot/master/templates/builder.html +118 -0
  99. data/test/buildbot/master/templates/builders.html +33 -0
  100. data/test/buildbot/master/templates/buildslave.html +72 -0
  101. data/test/buildbot/master/templates/buildslaves.html +70 -0
  102. data/test/buildbot/master/templates/change.html +15 -0
  103. data/test/buildbot/master/templates/layouts/base.html +58 -0
  104. data/test/buildbot/master/templates/macros/box.html +37 -0
  105. data/test/buildbot/master/templates/macros/build_line.html +50 -0
  106. data/test/buildbot/master/templates/macros/change.html +81 -0
  107. data/test/buildbot/master/templates/macros/forms.html +300 -0
  108. data/test/buildbot/master/templates/root.html +42 -0
  109. data/test/buildbot/master/templates/waterfall.html +53 -0
  110. data/test/buildbot/requirements.txt +4 -0
  111. data/test/buildbot/scripts/deploy.sh +38 -0
  112. data/test/buildbot/scripts/setup.sh +107 -0
  113. data/test/buildbot/slave/buildbot.tac +43 -0
  114. data/test/buildbot/slave/info/admin +1 -0
  115. data/test/buildbot/slave/info/host +1 -0
  116. data/test/buildbot/tests/__init__.py +0 -0
  117. data/test/buildbot/tests/master/__init__.py +0 -0
  118. data/test/buildbot/tests/master/test_slaves.py +41 -0
  119. data/test/buildbot/vendor/choices-0.4.0.tar.gz +0 -0
  120. data/test/config/acceptance_boxes.yml +7 -0
  121. data/test/unit/test_helper.rb +4 -0
  122. data/test/unit/vagrant/action/box/download_test.rb +2 -2
  123. data/test/unit/vagrant/action/vm/check_box_test.rb +6 -1
  124. data/test/unit/vagrant/action/vm/share_folders_test.rb +1 -1
  125. data/test/unit/vagrant/command/init_test.rb +10 -0
  126. data/test/unit/vagrant/downloaders/http_test.rb +12 -1
  127. data/test/unit/vagrant/provisioners/chef_test.rb +7 -0
  128. data/test/unit/vagrant/ssh/session_test.rb +2 -2
  129. data/test/unit/vagrant/ssh_test.rb +5 -8
  130. data/vagrant.gemspec +6 -0
  131. metadata +177 -1
@@ -0,0 +1,3 @@
1
+ # This installs the tasks that help with gem creation and
2
+ # publishing.
3
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,15 @@
1
+ require 'rake/testtask'
2
+ require 'rspec/core/rake_task'
3
+
4
+ namespace :test do
5
+ Rake::TestTask.new do |t|
6
+ t.name = "unit"
7
+ t.libs << "test/unit"
8
+ t.pattern = "test/unit/**/*_test.rb"
9
+ end
10
+
11
+ RSpec::Core::RakeTask.new do |t|
12
+ t.name = "acceptance"
13
+ t.pattern = "test/acceptance/**/*_test.rb"
14
+ end
15
+ end
@@ -1,3 +1,6 @@
1
+ # -*- mode: ruby -*-
2
+ # vi: set ft=ruby :
3
+
1
4
  Vagrant::Config.run do |config|
2
5
  # All Vagrant configuration is done here. The most common configuration
3
6
  # options are documented and commented below. For a complete reference,
@@ -227,7 +227,7 @@ en:
227
227
  above with their current state. For more information about a specific
228
228
  VM, run `vagrant status NAME`.
229
229
  up:
230
- vm_created: "VM already created. Booting if its not already running..."
230
+ vm_created: "VM already created. Booting if it's not already running..."
231
231
  upgrade_to_060:
232
232
  already_done: "Environment appears to already be upgraded to 0.6.0. Doing nothing!"
233
233
  ask: "Are you sure you want to execute this command?"
@@ -0,0 +1,48 @@
1
+ require "rubygems"
2
+ require "rspec/autorun"
3
+
4
+ require "log4r"
5
+
6
+ # Add this directory to the load path, since it just makes
7
+ # everything else so much easier.
8
+ $:.unshift File.expand_path("../", __FILE__)
9
+
10
+ # Load in the supporting files for our tests
11
+ require "support/shared/base_context"
12
+ require "support/config"
13
+ require "support/virtualbox"
14
+ require "support/matchers/match_output"
15
+
16
+ # Do not buffer output
17
+ $stdout.sync = true
18
+ $stderr.sync = true
19
+
20
+ # If VirtualBox is currently running, fail.
21
+ if Acceptance::VirtualBox.find_vboxsvc
22
+ $stderr.puts "VirtualBox must be closed and remain closed for the duration of the tests."
23
+ abort
24
+ end
25
+
26
+ # Enable logging if requested
27
+ if ENV["ACCEPTANCE_LOGGING"]
28
+ logger = Log4r::Logger.new("acceptance")
29
+ logger.outputters = Log4r::Outputter.stdout
30
+ logger.level = Log4r.const_get(ENV["ACCEPTANCE_LOGGING"].upcase)
31
+ logger = nil
32
+ end
33
+
34
+ # Parse the command line options and load the global configuration.
35
+ if !ENV.has_key?("ACCEPTANCE_CONFIG")
36
+ $stderr.puts "A configuration file must be passed into the acceptance test."
37
+ abort
38
+ elsif !File.file?(ENV["ACCEPTANCE_CONFIG"])
39
+ $stderr.puts "The configuration file must exist."
40
+ abort
41
+ end
42
+
43
+ $acceptance_options = Acceptance::Config.new(ENV["ACCEPTANCE_CONFIG"])
44
+
45
+ # Configure RSpec
46
+ RSpec.configure do |c|
47
+ c.expect_with :rspec, :stdlib
48
+ end
@@ -0,0 +1,77 @@
1
+ require File.expand_path("../base", __FILE__)
2
+
3
+ describe "vagrant box" do
4
+ include_context "acceptance"
5
+
6
+ it "has no boxes by default" do
7
+ result = execute("vagrant", "box", "list")
8
+ result.stdout.should match_output(:no_boxes)
9
+ end
10
+
11
+ it "can add a box from a file" do
12
+ require_box("default")
13
+
14
+ # Add the box, which we expect to succeed
15
+ result = execute("vagrant", "box", "add", "foo", box_path("default"))
16
+ result.should be_success
17
+
18
+ # Verify that the box now shows up in the list of available boxes
19
+ result = execute("vagrant", "box", "list")
20
+ result.stdout.should match_output(:box_installed, "foo")
21
+ end
22
+
23
+ it "gives an error if the file doesn't exist" do
24
+ result = execute("vagrant", "box", "add", "foo", "/tmp/nope/nope/nope/nonono.box")
25
+ result.should_not be_success
26
+ result.stdout.should match_output(:box_path_doesnt_exist)
27
+ end
28
+
29
+ it "gives an error if the file is not a valid box" do
30
+ invalid = environment.workdir.join("nope.txt")
31
+ invalid.open("w+") do |f|
32
+ f.write("INVALID!")
33
+ end
34
+
35
+ result = execute("vagrant", "box", "add", "foo", invalid.to_s)
36
+ result.should_not be_success
37
+ result.stdout.should match_output(:box_invalid)
38
+ end
39
+
40
+ it "can add a box from an HTTP server" do
41
+ pending("Need to setup HTTP server functionality")
42
+ end
43
+
44
+ it "can remove a box" do
45
+ require_box("default")
46
+
47
+ # Add the box, remove the box, then verify that the box no longer
48
+ # shows up in the list of available boxes.
49
+ execute("vagrant", "box", "add", "foo", box_path("default"))
50
+ execute("vagrant", "box", "remove", "foo")
51
+ result = execute("vagrant", "box", "list")
52
+ result.should be_success
53
+ result.stdout.should match_output(:no_boxes)
54
+ end
55
+
56
+ it "can repackage a box" do
57
+ require_box("default")
58
+
59
+ original_size = File.size(box_path("default"))
60
+ logger.debug("Original package size: #{original_size}")
61
+
62
+ # Add the box, repackage it, and verify that a package.box is
63
+ # dumped of relatively similar size.
64
+ execute("vagrant", "box", "add", "foo", box_path("default"))
65
+ execute("vagrant", "box", "repackage", "foo")
66
+
67
+ # By default, repackage should dump into package.box into the CWD
68
+ repackaged_file = environment.workdir.join("package.box")
69
+ repackaged_file.file?.should be, "package.box should exist in cwd of environment"
70
+
71
+ # Compare the sizes
72
+ repackaged_size = repackaged_file.size
73
+ logger.debug("Repackaged size: #{repackaged_size}")
74
+ size_diff = (repackaged_size - original_size).abs
75
+ size_diff.should be < 1000, "Sizes should be very similar"
76
+ end
77
+ end
@@ -0,0 +1,37 @@
1
+ require File.expand_path("../base", __FILE__)
2
+ require "support/shared/command_examples"
3
+
4
+ describe "vagrant destroy" do
5
+ include_context "acceptance"
6
+ it_behaves_like "a command that requires a Vagrantfile", ["vagrant", "destroy"]
7
+
8
+ it "succeeds and ignores if the VM is not created" do
9
+ require_box("default")
10
+
11
+ assert_execute("vagrant", "box", "add", "base", box_path("default"))
12
+ assert_execute("vagrant", "init")
13
+
14
+ result = assert_execute("vagrant", "destroy")
15
+ result.stdout.should match_output(:vm_not_created_warning)
16
+ end
17
+
18
+ it "is able to destroy a running virtual machine" do
19
+ require_box("default")
20
+
21
+ assert_execute("vagrant", "box", "add", "base", box_path("default"))
22
+ assert_execute("vagrant", "init")
23
+ assert_execute("vagrant", "up")
24
+
25
+ # Destroy the VM and assert that it worked properly (seemingly)
26
+ result = assert_execute("vagrant", "destroy")
27
+ result.stdout.should match_output(:vm_destroyed)
28
+
29
+ # Assert that the VM no longer is created
30
+ result = assert_execute("vagrant", "status")
31
+ result.stdout.should match_output(:status, "default", "not created")
32
+ end
33
+
34
+ # TODO:
35
+ # it is able to destroy a halted virtual machine
36
+ # it is able to destroy a suspended virtual machine
37
+ end
@@ -0,0 +1,72 @@
1
+ require File.expand_path("../base", __FILE__)
2
+ require "support/shared/command_examples"
3
+
4
+ describe "vagrant halt" do
5
+ include_context "acceptance"
6
+ it_behaves_like "a command that requires a Vagrantfile", ["vagrant", "halt"]
7
+
8
+ it "succeeds and ignores if the VM is not created" do
9
+ require_box("default")
10
+
11
+ assert_execute("vagrant", "box", "add", "base", box_path("default"))
12
+ assert_execute("vagrant", "init")
13
+
14
+ result = assert_execute("vagrant", "halt")
15
+ result.stdout.should match_output(:vm_not_created_warning)
16
+ end
17
+
18
+ it "is able to halt a running virtual machine" do
19
+ require_box("default")
20
+
21
+ assert_execute("vagrant", "box", "add", "base", box_path("default"))
22
+ assert_execute("vagrant", "init")
23
+ assert_execute("vagrant", "up")
24
+
25
+ # Halt the VM and assert that it worked properly (seemingly)
26
+ result = assert_execute("vagrant", "halt")
27
+ result.stdout.should match_output(:vm_halt_graceful)
28
+
29
+ # Assert that the VM is no longer running
30
+ result = assert_execute("vagrant", "status")
31
+ result.stdout.should match_output(:status, "default", "powered off")
32
+ end
33
+
34
+ it "is able to force halt a running virtual machine" do
35
+ require_box("default")
36
+
37
+ assert_execute("vagrant", "box", "add", "base", box_path("default"))
38
+ assert_execute("vagrant", "init")
39
+ assert_execute("vagrant", "up")
40
+
41
+ # Halt the VM and assert that it worked properly (seemingly)
42
+ result = assert_execute("vagrant", "halt", "--force")
43
+ result.stdout.should match_output(:vm_halt_force)
44
+
45
+ # Assert that the VM is no longer running
46
+ result = assert_execute("vagrant", "status")
47
+ result.stdout.should match_output(:status, "default", "powered off")
48
+ end
49
+
50
+ it "is able to come back up after the machine has been halted" do
51
+ require_box("default")
52
+
53
+ assert_execute("vagrant", "box", "add", "base", box_path("default"))
54
+ assert_execute("vagrant", "init")
55
+ assert_execute("vagrant", "up")
56
+ assert_execute("vagrant", "halt")
57
+
58
+ # Assert that the VM is no longer running
59
+ result = assert_execute("vagrant", "status")
60
+ result.stdout.should match_output(:status, "default", "powered off")
61
+
62
+ assert_execute("vagrant", "up")
63
+
64
+ # Assert that the VM is once again running
65
+ result = assert_execute("vagrant", "status")
66
+ result.stdout.should match_output(:status, "default", "running")
67
+ end
68
+
69
+ # TODO:
70
+ # halt behavior on suspend machine
71
+ # halt behavior if machine is already powered off
72
+ end
@@ -0,0 +1,33 @@
1
+ require File.expand_path("../base", __FILE__)
2
+
3
+ describe "vagrant init" do
4
+ include_context "acceptance"
5
+
6
+ it "creates a Vagrantfile in the working directory" do
7
+ vagrantfile = environment.workdir.join("Vagrantfile")
8
+ vagrantfile.exist?.should_not be, "Vagrantfile shouldn't exist initially"
9
+
10
+ result = execute("vagrant", "init")
11
+ result.should be_success
12
+ vagrantfile.exist?.should be, "Vagrantfile should exist"
13
+ end
14
+
15
+ it "creates a Vagrantfile with the box set to the given argument" do
16
+ vagrantfile = environment.workdir.join("Vagrantfile")
17
+
18
+ result = execute("vagrant", "init", "foo")
19
+ result.should be_success
20
+ vagrantfile.read.should match(/config.vm.box = "foo"$/)
21
+ end
22
+
23
+ it "creates a Vagrantfile with the box URL set to the given argument" do
24
+ vagrantfile = environment.workdir.join("Vagrantfile")
25
+
26
+ result = execute("vagrant", "init", "foo", "bar")
27
+ result.should be_success
28
+
29
+ contents = vagrantfile.read
30
+ contents.should match(/config.vm.box = "foo"$/)
31
+ contents.should match(/config.vm.box_url = "bar"$/)
32
+ end
33
+ end
@@ -0,0 +1,17 @@
1
+ require File.expand_path("../base", __FILE__)
2
+ require "support/shared/command_examples"
3
+
4
+ describe "vagrant resume" do
5
+ include_context "acceptance"
6
+ it_behaves_like "a command that requires a Vagrantfile", ["vagrant", "resume"]
7
+
8
+ it "succeeds and ignores if the VM is not created" do
9
+ require_box("default")
10
+
11
+ assert_execute("vagrant", "box", "add", "base", box_path("default"))
12
+ assert_execute("vagrant", "init")
13
+
14
+ result = assert_execute("vagrant", "resume")
15
+ result.stdout.should match_output(:vm_not_created_warning)
16
+ end
17
+ end
@@ -0,0 +1,41 @@
1
+ require File.expand_path("../base", __FILE__)
2
+ require "support/shared/command_examples"
3
+
4
+ describe "vagrant ssh" do
5
+ include_context "acceptance"
6
+ it_behaves_like "a command that requires a Vagrantfile", ["vagrant", "ssh"]
7
+ it_behaves_like "a command that requires a virtual machine", ["vagrant", "ssh"]
8
+
9
+ it "is able to SSH into a running virtual machine" do
10
+ require_box("default")
11
+
12
+ assert_execute("vagrant", "box", "add", "base", box_path("default"))
13
+ assert_execute("vagrant", "init")
14
+ assert_execute("vagrant", "up")
15
+
16
+ outputted = false
17
+ result = assert_execute("vagrant", "ssh") do |io_type, data|
18
+ if io_type == :stdin and !outputted
19
+ data.puts("echo hello")
20
+ data.puts("exit")
21
+ outputted = true
22
+ end
23
+ end
24
+
25
+ result.stdout.chomp.should eql("hello"), "Vagrant should bring up a VM to be able to SSH into."
26
+ end
27
+
28
+ it "is able to execute a single command via the command line" do
29
+ require_box("default")
30
+
31
+ assert_execute("vagrant", "box", "add", "base", box_path("default"))
32
+ assert_execute("vagrant", "init")
33
+ assert_execute("vagrant", "up")
34
+
35
+ result = assert_execute("vagrant", "ssh", "-c", "echo foo")
36
+ result.stdout.should == "foo\n"
37
+ end
38
+
39
+ # TODO:
40
+ # SSH should fail if the VM is not running
41
+ end
@@ -0,0 +1,42 @@
1
+ require "yaml"
2
+
3
+ require "log4r"
4
+
5
+ module Acceptance
6
+ # This represents a configuration object for acceptance tests.
7
+ class Config
8
+ attr_reader :vagrant_path
9
+ attr_reader :vagrant_version
10
+ attr_reader :env
11
+ attr_reader :box_directory
12
+
13
+ def initialize(path)
14
+ @logger = Log4r::Logger.new("acceptance::config")
15
+ @logger.info("Loading configuration from: #{path}")
16
+ options = YAML.load_file(path)
17
+ @logger.info("Loaded: #{options.inspect}")
18
+
19
+ @vagrant_path = options["vagrant_path"]
20
+ @vagrant_version = options["vagrant_version"]
21
+ @env = options["env"]
22
+ @box_directory = options["box_directory"]
23
+
24
+ # Verify the configuration object.
25
+ validate
26
+ end
27
+
28
+ # This method verifies the configuration and makes sure that
29
+ # all the configuration is available and appears good. This
30
+ # method will raise an ArgumentError in the case that anything
31
+ # is wrong.
32
+ def validate
33
+ if !@vagrant_path || !File.file?(@vagrant_path)
34
+ raise ArgumentError, "'vagrant_path' must point to the `vagrant` executable"
35
+ elsif !@vagrant_version
36
+ raise ArgumentError, "`vagrant_version' must be set to the version of the `vagrant` executable"
37
+ elsif !@box_directory || !File.directory?(@box_directory)
38
+ raise ArgumentError, "`box_directory` must be set to a folder containing boxes for the tests."
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,226 @@
1
+ require "fileutils"
2
+ require "pathname"
3
+
4
+ require "log4r"
5
+ require "childprocess"
6
+
7
+ require File.expand_path("../tempdir", __FILE__)
8
+ require File.expand_path("../virtualbox", __FILE__)
9
+
10
+ module Acceptance
11
+ # This class manages an isolated environment for Vagrant to
12
+ # run in. It creates a temporary directory to act as the
13
+ # working directory as well as sets a custom home directory.
14
+ class IsolatedEnvironment
15
+ attr_reader :homedir
16
+ attr_reader :workdir
17
+
18
+ # Initializes an isolated environment. You can pass in some
19
+ # options here to configure runing custom applications in place
20
+ # of others as well as specifying environmental variables.
21
+ #
22
+ # @param [Hash] apps A mapping of application name (such as "vagrant")
23
+ # to an alternate full path to the binary to run.
24
+ # @param [Hash] env Additional environmental variables to inject
25
+ # into the execution environments.
26
+ def initialize(apps=nil, env=nil)
27
+ @logger = Log4r::Logger.new("acceptance::isolated_environment")
28
+
29
+ @apps = apps || {}
30
+ @env = env || {}
31
+
32
+ # Create a temporary directory for our work
33
+ @tempdir = Tempdir.new("vagrant")
34
+ @logger.info("Initialize isolated environment: #{@tempdir.path}")
35
+
36
+ # Setup the home and working directories
37
+ @homedir = Pathname.new(File.join(@tempdir.path, "home"))
38
+ @workdir = Pathname.new(File.join(@tempdir.path, "work"))
39
+
40
+ @homedir.mkdir
41
+ @workdir.mkdir
42
+
43
+ # Set the home directory and virtualbox home directory environmental
44
+ # variables so that Vagrant and VirtualBox see the proper paths here.
45
+ @env["HOME"] = @homedir.to_s
46
+ @env["VBOX_USER_HOME"] = @homedir.to_s
47
+ end
48
+
49
+ # Executes a command in the context of this isolated environment.
50
+ # Any command executed will therefore see our temporary directory
51
+ # as the home directory.
52
+ def execute(command, *argN)
53
+ command = replace_command(command)
54
+
55
+ # Get the hash options passed to this method
56
+ options = argN.last.is_a?(Hash) ? argN.pop : {}
57
+ timeout = options.delete(:timeout)
58
+
59
+ # Build a child process to run this command. For the stdout/stderr
60
+ # we use pipes so that we can select() on it and block and stream
61
+ # data in as it comes.
62
+ @logger.info("Executing: #{command} #{argN.inspect}. Output will stream in...")
63
+ process = ChildProcess.build(command, *argN)
64
+ stdout, stdout_writer = IO.pipe
65
+ process.io.stdout = stdout_writer
66
+
67
+ stderr, stderr_writer = IO.pipe
68
+ process.io.stderr = stderr_writer
69
+ process.duplex = true
70
+
71
+ @env.each do |k, v|
72
+ process.environment[k] = v
73
+ end
74
+
75
+ Dir.chdir(@workdir.to_s) do
76
+ process.start
77
+ process.io.stdin.sync = true
78
+ end
79
+
80
+ # Close our side of the pipes, since we're just reading
81
+ stdout_writer.close
82
+ stderr_writer.close
83
+
84
+ # Create a hash to store all the data we see.
85
+ io_data = { stdout => "", stderr => "" }
86
+
87
+ # Record the start time for timeout purposes
88
+ start_time = Time.now.to_i
89
+
90
+ @logger.debug("Selecting on IO...")
91
+ while true
92
+ results = IO.select([stdout, stderr],
93
+ [process.io.stdin], nil, timeout || 5)
94
+
95
+ # Check if we have exceeded our timeout from waiting on a select()
96
+ raise TimeoutExceeded, process.pid if timeout && (Time.now.to_i - start_time) > timeout
97
+
98
+ # Check the readers first to see if they're ready
99
+ readers = results[0]
100
+ if !readers.empty?
101
+ begin
102
+ readers.each do |r|
103
+ data = r.read_nonblock(1024)
104
+ io_data[r] += data
105
+ io_name = r == stdout ? "stdout" : "stderr"
106
+ @logger.debug(data)
107
+ yield io_name.to_sym, data if block_given?
108
+ end
109
+ rescue IO::WaitReadable
110
+ # This just means the IO wasn't actually ready and we should
111
+ # wait some more. So we just let this pass through.
112
+ rescue EOFError
113
+ # Process exited, so break out of this while loop
114
+ break
115
+ end
116
+ end
117
+
118
+ # Check if the process exited in order to break the loop before
119
+ # we try to see if any stdin is ready.
120
+ break if process.exited?
121
+
122
+ # Check the writers to see if they're ready, and notify any listeners
123
+ if !results[1].empty?
124
+ yield :stdin, process.io.stdin if block_given?
125
+ end
126
+ end
127
+
128
+ # Continually try to wait for the process to end, but do so asynchronously
129
+ # so that we can also check to see if we have exceeded a timeout.
130
+ begin
131
+ # If a timeout is not set, we set a very large timeout to
132
+ # simulate "forever"
133
+ @logger.debug("Waiting for process to exit...")
134
+ remaining = (timeout || 32000) - (Time.now.to_i - start_time)
135
+ remaining = 0 if remaining < 0
136
+ process.poll_for_exit(remaining)
137
+ rescue ChildProcess::TimeoutError
138
+ raise TimeoutExceeded, process.pid
139
+ end
140
+
141
+ @logger.debug("Exit status: #{process.exit_code}")
142
+ return ExecuteProcess.new(process.exit_code, io_data[stdout], io_data[stderr])
143
+ end
144
+
145
+ # Closes the environment, cleans up the temporary directories, etc.
146
+ def close
147
+ # Only delete virtual machines if VBoxSVC is running, meaning
148
+ # that something related to VirtualBox started running in this
149
+ # environment.
150
+ delete_virtual_machines if VirtualBox.find_vboxsvc
151
+
152
+ # Delete the temporary directory
153
+ @logger.info("Removing isolated environment: #{@tempdir.path}")
154
+ FileUtils.rm_rf(@tempdir.path)
155
+ end
156
+
157
+ def delete_virtual_machines
158
+ # Delete all virtual machines
159
+ @logger.debug("Finding all virtual machines")
160
+ execute("VBoxManage", "list", "vms").stdout.lines.each do |line|
161
+ data = /^"(?<name>.+?)" {(?<uuid>.+?)}$/.match(line)
162
+
163
+ begin
164
+ @logger.debug("Removing VM: #{data[:name]}")
165
+
166
+ # We add a timeout onto this because sometimes for seemingly no
167
+ # reason it will simply freeze, although the VM is successfully
168
+ # "aborted." The timeout gets around this strange behavior.
169
+ execute("VBoxManage", "controlvm", data[:uuid], "poweroff", :timeout => 5)
170
+ rescue TimeoutExceeded => e
171
+ @logger.info("Failed to poweroff VM '#{data[:uuid]}'. Killing process.")
172
+
173
+ # Kill the process and wait a bit for it to disappear
174
+ Process.kill('KILL', e.pid)
175
+ Process.waitpid2(e.pid)
176
+ end
177
+
178
+ sleep 0.5
179
+
180
+ result = execute("VBoxManage", "unregistervm", data[:uuid], "--delete")
181
+ raise Exception, "VM unregistration failed!" if result.exit_status != 0
182
+ end
183
+
184
+ @logger.info("Removed all virtual machines")
185
+ end
186
+
187
+ # This replaces a command with a replacement defined when this
188
+ # isolated environment was initialized. If nothing was defined,
189
+ # then the command itself is returned.
190
+ def replace_command(command)
191
+ return @apps[command] if @apps.has_key?(command)
192
+ return command
193
+ end
194
+ end
195
+
196
+ # This class represents a process which has run via the IsolatedEnvironment.
197
+ # This is a readonly structure that can be used to inspect the exit status,
198
+ # stdout, stderr, etc. from the process which ran.
199
+ class ExecuteProcess
200
+ attr_reader :exit_status
201
+ attr_reader :stdout
202
+ attr_reader :stderr
203
+
204
+ def initialize(exit_status, stdout, stderr)
205
+ @exit_status = exit_status
206
+ @stdout = stdout
207
+ @stderr = stderr
208
+ end
209
+
210
+ def success?
211
+ @exit_status == 0
212
+ end
213
+ end
214
+
215
+ # This exception is raised if the timeout for a process is exceeded.
216
+ class TimeoutExceeded < StandardError
217
+ attr_reader :pid
218
+
219
+ def initialize(pid)
220
+ @pid = pid
221
+
222
+ super()
223
+ end
224
+ end
225
+ end
226
+