vagrantup 0.1.4 → 0.2.0

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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/Rakefile +1 -1
  4. data/VERSION +1 -1
  5. data/bin/vagrant +1 -1
  6. data/bin/vagrant-box +1 -2
  7. data/bin/vagrant-down +1 -2
  8. data/bin/vagrant-halt +1 -2
  9. data/bin/vagrant-init +1 -2
  10. data/bin/vagrant-package +1 -2
  11. data/bin/vagrant-reload +1 -2
  12. data/bin/vagrant-resume +1 -2
  13. data/bin/vagrant-ssh +1 -2
  14. data/bin/vagrant-status +29 -0
  15. data/bin/vagrant-suspend +1 -2
  16. data/bin/vagrant-up +1 -2
  17. data/config/default.rb +5 -9
  18. data/keys/README.md +10 -0
  19. data/keys/vagrant +27 -0
  20. data/keys/vagrant.pub +1 -0
  21. data/lib/vagrant.rb +10 -5
  22. data/lib/vagrant/actions/base.rb +14 -0
  23. data/lib/vagrant/actions/box/download.rb +3 -0
  24. data/lib/vagrant/actions/collection.rb +36 -0
  25. data/lib/vagrant/actions/runner.rb +4 -10
  26. data/lib/vagrant/actions/vm/boot.rb +4 -5
  27. data/lib/vagrant/actions/vm/customize.rb +17 -0
  28. data/lib/vagrant/actions/vm/destroy.rb +11 -2
  29. data/lib/vagrant/actions/vm/forward_ports.rb +24 -0
  30. data/lib/vagrant/actions/vm/import.rb +1 -0
  31. data/lib/vagrant/actions/vm/provision.rb +30 -52
  32. data/lib/vagrant/actions/vm/reload.rb +2 -2
  33. data/lib/vagrant/actions/vm/shared_folders.rb +37 -25
  34. data/lib/vagrant/actions/vm/up.rb +8 -4
  35. data/lib/vagrant/active_list.rb +66 -0
  36. data/lib/vagrant/commands.rb +44 -0
  37. data/lib/vagrant/config.rb +64 -47
  38. data/lib/vagrant/downloaders/base.rb +3 -0
  39. data/lib/vagrant/downloaders/file.rb +11 -11
  40. data/lib/vagrant/env.rb +48 -12
  41. data/lib/vagrant/provisioners/base.rb +22 -0
  42. data/lib/vagrant/provisioners/chef.rb +102 -0
  43. data/lib/vagrant/provisioners/chef_server.rb +96 -0
  44. data/lib/vagrant/provisioners/chef_solo.rb +67 -0
  45. data/lib/vagrant/ssh.rb +25 -6
  46. data/lib/vagrant/stacked_proc_runner.rb +33 -0
  47. data/lib/vagrant/vm.rb +8 -0
  48. data/test/test_helper.rb +22 -6
  49. data/test/vagrant/actions/box/download_test.rb +11 -0
  50. data/test/vagrant/actions/collection_test.rb +110 -0
  51. data/test/vagrant/actions/runner_test.rb +11 -7
  52. data/test/vagrant/actions/vm/boot_test.rb +7 -7
  53. data/test/vagrant/actions/vm/customize_test.rb +16 -0
  54. data/test/vagrant/actions/vm/destroy_test.rb +19 -6
  55. data/test/vagrant/actions/vm/forward_ports_test.rb +52 -0
  56. data/test/vagrant/actions/vm/import_test.rb +10 -3
  57. data/test/vagrant/actions/vm/provision_test.rb +75 -70
  58. data/test/vagrant/actions/vm/reload_test.rb +3 -2
  59. data/test/vagrant/actions/vm/shared_folders_test.rb +62 -9
  60. data/test/vagrant/actions/vm/up_test.rb +4 -4
  61. data/test/vagrant/active_list_test.rb +169 -0
  62. data/test/vagrant/config_test.rb +145 -29
  63. data/test/vagrant/downloaders/base_test.rb +7 -0
  64. data/test/vagrant/downloaders/file_test.rb +12 -18
  65. data/test/vagrant/env_test.rb +96 -23
  66. data/test/vagrant/provisioners/base_test.rb +27 -0
  67. data/test/vagrant/provisioners/chef_server_test.rb +175 -0
  68. data/test/vagrant/provisioners/chef_solo_test.rb +142 -0
  69. data/test/vagrant/provisioners/chef_test.rb +116 -0
  70. data/test/vagrant/ssh_test.rb +29 -8
  71. data/test/vagrant/stacked_proc_runner_test.rb +43 -0
  72. data/test/vagrant/vm_test.rb +23 -0
  73. data/vagrant.gemspec +34 -7
  74. metadata +33 -5
  75. data/script/vagrant-ssh-expect.sh +0 -22
@@ -5,6 +5,9 @@ module Vagrant
5
5
  class Base
6
6
  include Vagrant::Util
7
7
 
8
+ # Called prior to execution so any error checks can be done
9
+ def prepare(source_url); end
10
+
8
11
  # Downloads the source file to the destination file. It is up to
9
12
  # implementors of this class to handle the logic.
10
13
  def download!(source_url, destination_file); end
@@ -3,19 +3,19 @@ module Vagrant
3
3
  # "Downloads" a file to a temporary file. Basically, this downloader
4
4
  # simply does a file copy.
5
5
  class File < Base
6
- BUFFERSIZE = 1048576 # 1 MB
6
+ def prepare(source_url)
7
+ if !::File.file?(source_url)
8
+ raise Actions::ActionException.new(<<-msg)
9
+ The given box does not exist on the file system:
7
10
 
8
- def download!(source_url, destination_file)
9
- # For now we read the contents of one into a buffer
10
- # and copy it into the other. In the future, we should do
11
- # a system-level file copy (FileUtils.cp).
12
- open(source_url) do |f|
13
- loop do
14
- break if f.eof?
15
- destination_file.write(f.read(BUFFERSIZE))
16
- end
11
+ #{source_url}
12
+ msg
17
13
  end
18
14
  end
15
+
16
+ def download!(source_url, destination_file)
17
+ FileUtils.cp(source_url, destination_file.path)
18
+ end
19
19
  end
20
20
  end
21
- end
21
+ end
data/lib/vagrant/env.rb CHANGED
@@ -24,21 +24,52 @@ module Vagrant
24
24
  load_config!
25
25
  load_home_directory!
26
26
  load_box!
27
+ load_config!
28
+ check_virtualbox!
27
29
  load_vm!
28
30
  end
29
31
 
32
+ def check_virtualbox!
33
+ version = VirtualBox::Command.version
34
+ if version.nil?
35
+ error_and_exit(<<-msg)
36
+ Vagrant could not detect VirtualBox! Make sure VirtualBox is properly installed.
37
+ If VirtualBox is installed, you may need to tweak the paths to the `VBoxManage`
38
+ application which ships with VirtualBox and the path to the global XML configuration
39
+ which VirtualBox typically stores somewhere in your home directory.
40
+
41
+ The following shows how to configure VirtualBox. This can be done in the
42
+ Vagrantfile. Note that 90% of the time, you shouldn't need to do this if VirtualBox
43
+ is installed. Please use the various Vagrant support lines to request more information
44
+ if you can't get this working.
45
+
46
+ VirtualBox::Command.vboxmanage = "/path/to/my/VBoxManage"
47
+ VirtualBox::Global.vboxconfig = "~/path/to/VirtualBox.xml"
48
+ msg
49
+ elsif version.to_f < 3.1
50
+ error_and_exit(<<-msg)
51
+ Vagrant has detected that you have VirtualBox version #{version} installed!
52
+ Vagrant requires that you use at least VirtualBox version 3.1. Please install
53
+ a more recent version of VirtualBox to continue.
54
+ msg
55
+ end
56
+ end
57
+
30
58
  def load_config!
31
59
  # Prepare load paths for config files
32
60
  load_paths = [File.join(PROJECT_ROOT, "config", "default.rb")]
33
61
  load_paths << File.join(box.directory, ROOTFILE_NAME) if box
62
+ load_paths << File.join(home_path, ROOTFILE_NAME) if Vagrant.config.vagrant.home
34
63
  load_paths << File.join(root_path, ROOTFILE_NAME) if root_path
35
64
 
36
65
  # Then clear out the old data
37
66
  Config.reset!
38
67
 
39
68
  load_paths.each do |path|
40
- logger.info "Loading config from #{path}..."
41
- load path if File.exist?(path)
69
+ if File.exist?(path)
70
+ logger.info "Loading config from #{path}..."
71
+ load path
72
+ end
42
73
  end
43
74
 
44
75
  # Execute the configurations
@@ -63,11 +94,6 @@ module Vagrant
63
94
  return unless root_path
64
95
 
65
96
  @@box = Box.find(Vagrant.config.vm.box) if Vagrant.config.vm.box
66
-
67
- if @@box
68
- logger.info("Reloading configuration to account for loaded box...")
69
- load_config!
70
- end
71
97
  end
72
98
 
73
99
  def load_vm!
@@ -81,18 +107,28 @@ module Vagrant
81
107
  end
82
108
 
83
109
  def persist_vm(vm)
110
+ # Save to the dotfile for this project
84
111
  File.open(dotfile_path, 'w+') do |f|
85
112
  f.write(vm.uuid)
86
113
  end
114
+
115
+ # Also add to the global store
116
+ ActiveList.add(vm)
117
+ end
118
+
119
+ def depersist_vm(vm)
120
+ # Delete the dotfile if it exists
121
+ File.delete(dotfile_path) if File.exist?(dotfile_path)
122
+
123
+ # Remove from the global store
124
+ ActiveList.remove(vm)
87
125
  end
88
126
 
89
127
  def load_root_path!(path=nil)
90
- path ||= Pathname.new(Dir.pwd)
128
+ path = Pathname.new(File.expand_path(path || Dir.pwd))
91
129
 
92
- # Stop if we're at the root. 2nd regex matches windows drives
93
- # such as C:. and Z:. Portability of this check will need to be
94
- # researched.
95
- return false if path.to_s == '/' || path.to_s =~ /^[A-Z]:\.$/
130
+ # Stop if we're at the root.
131
+ return false if path.root?
96
132
 
97
133
  file = "#{path}/#{ROOTFILE_NAME}"
98
134
  if File.exist?(file)
@@ -0,0 +1,22 @@
1
+ module Vagrant
2
+ module Provisioners
3
+ # The base class for a "provisioner." A provisioner is responsible for
4
+ # provisioning a Vagrant system. This has been abstracted out to provide
5
+ # support for multiple solutions such as Chef Solo, Chef Client, and
6
+ # Puppet.
7
+ class Base
8
+ include Vagrant::Util
9
+
10
+ # This is the method called to "prepare" the provisioner. This is called
11
+ # before any actions are run by the action runner (see {Vagrant::Actions::Runner}).
12
+ # This can be used to setup shared folders, forward ports, etc. Whatever is
13
+ # necessary on a "meta" level.
14
+ def prepare; end
15
+
16
+ # This is the method called to provision the system. This method
17
+ # is expected to do whatever necessary to provision the system (create files,
18
+ # SSH, etc.)
19
+ def provision!; end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,102 @@
1
+ module Vagrant
2
+ module Provisioners
3
+ # This class is a base class where the common functinality shared between
4
+ # chef-solo and chef-client provisioning are stored. This is **not an actual
5
+ # provisioner**. Instead, {ChefSolo} or {ChefServer} should be used.
6
+ class Chef < Base
7
+ # This is the configuration which is available through `config.chef`
8
+ class ChefConfig < Vagrant::Config::Base
9
+ # Chef server specific config
10
+ attr_accessor :chef_server_url
11
+ attr_accessor :validation_key_path
12
+ attr_accessor :validation_client_name
13
+ attr_accessor :client_key_path
14
+
15
+ # Chef solo specific config
16
+ attr_accessor :cookbooks_path
17
+
18
+ # Shared config
19
+ attr_accessor :provisioning_path
20
+ attr_accessor :json
21
+
22
+ def initialize
23
+ @validation_client_name = "chef-validator"
24
+ @client_key_path = "/etc/chef/client.pem"
25
+
26
+ @cookbooks_path = "cookbooks"
27
+ @provisioning_path = "/tmp/vagrant-chef"
28
+ @json = {
29
+ :instance_role => "vagrant",
30
+ :run_list => ["recipe[vagrant_main]"]
31
+ }
32
+ end
33
+
34
+ # Returns the run list for the provisioning
35
+ def run_list
36
+ json[:run_list]
37
+ end
38
+
39
+ # Sets the run list to the specified value
40
+ def run_list=(value)
41
+ json[:run_list] = value
42
+ end
43
+
44
+ # Adds a recipe to the run list
45
+ def add_recipe(name)
46
+ name = "recipe[#{name}]" unless name =~ /^recipe\[(.+?)\]$/
47
+ run_list << name
48
+ end
49
+
50
+ # Adds a role to the run list
51
+ def add_role(name)
52
+ name = "role[#{name}]" unless name =~ /^role\[(.+?)\]$/
53
+ run_list << name
54
+ end
55
+
56
+ def to_json
57
+ # Overridden so that the 'json' key could be removed, since its just
58
+ # merged into the config anyways
59
+ data = instance_variables_hash
60
+ data.delete(:json)
61
+ data.to_json
62
+ end
63
+ end
64
+
65
+ # Tell the Vagrant configure class about our custom configuration
66
+ Config.configures :chef, ChefConfig
67
+
68
+ def prepare
69
+ raise Actions::ActionException.new("Vagrant::Provisioners::Chef is not a valid provisioner! Use ChefSolo or ChefServer instead.")
70
+ end
71
+
72
+ def chown_provisioning_folder
73
+ logger.info "Setting permissions on chef provisioning folder..."
74
+ SSH.execute do |ssh|
75
+ ssh.exec!("sudo mkdir -p #{Vagrant.config.chef.provisioning_path}")
76
+ ssh.exec!("sudo chown #{Vagrant.config.ssh.username} #{Vagrant.config.chef.provisioning_path}")
77
+ end
78
+ end
79
+
80
+ def setup_json
81
+ logger.info "Generating chef JSON and uploading..."
82
+
83
+ # Set up initial configuration
84
+ data = {
85
+ :config => Vagrant.config,
86
+ :directory => Vagrant.config.vm.project_directory,
87
+ }
88
+
89
+ # And wrap it under the "vagrant" namespace
90
+ data = { :vagrant => data }
91
+
92
+ # Merge with the "extra data" which isn't put under the
93
+ # vagrant namespace by default
94
+ data.merge!(Vagrant.config.chef.json)
95
+
96
+ json = data.to_json
97
+
98
+ SSH.upload!(StringIO.new(json), File.join(Vagrant.config.chef.provisioning_path, "dna.json"))
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,96 @@
1
+ module Vagrant
2
+ module Provisioners
3
+ # This class implements provisioning via chef-client, allowing provisioning
4
+ # with a chef server.
5
+ class ChefServer < Chef
6
+ def prepare
7
+ if Vagrant.config.chef.validation_key_path.nil?
8
+ raise Actions::ActionException.new(<<-msg)
9
+ Chef server provisioning requires that the `config.chef.validation_key_path` configuration
10
+ be set to a path on your local machine of the validation key used to register the
11
+ VM with the chef server.
12
+ msg
13
+ elsif !File.file?(Vagrant.config.chef.validation_key_path)
14
+ raise Actions::ActionException.new(<<-msg)
15
+ The validation key set for `config.chef.validation_key_path` does not exist! This
16
+ file needs to exist so it can be uploaded to the virtual machine. It is
17
+ currently set to "#{Vagrant.config.chef.validation_key_path}"
18
+ msg
19
+ end
20
+
21
+ if Vagrant.config.chef.chef_server_url.nil?
22
+ raise Actions::ActionException.new(<<-msg)
23
+ Chef server provisioning requires that the `config.chef.chef_server_url` be set to the
24
+ URL of your chef server. Examples include "http://12.12.12.12:4000" and
25
+ "http://myserver.com:4000" (the port of course can be different, but 4000 is the default)
26
+ msg
27
+ end
28
+ end
29
+
30
+ def provision!
31
+ chown_provisioning_folder
32
+ create_client_key_folder
33
+ upload_validation_key
34
+ setup_json
35
+ setup_config
36
+ run_chef_client
37
+ end
38
+
39
+ def create_client_key_folder
40
+ logger.info "Creating folder to hold client key..."
41
+ path = Pathname.new(Vagrant.config.chef.client_key_path)
42
+
43
+ SSH.execute do |ssh|
44
+ ssh.exec!("sudo mkdir -p #{path.dirname}")
45
+ end
46
+ end
47
+
48
+ def upload_validation_key
49
+ logger.info "Uploading chef client validation key..."
50
+ SSH.upload!(validation_key_path, guest_validation_key_path)
51
+ end
52
+
53
+ def setup_config
54
+ solo_file = <<-solo
55
+ log_level :info
56
+ log_location STDOUT
57
+ ssl_verify_mode :verify_none
58
+ chef_server_url "#{Vagrant.config.chef.chef_server_url}"
59
+
60
+ validation_client_name "#{Vagrant.config.chef.validation_client_name}"
61
+ validation_key "#{guest_validation_key_path}"
62
+ client_key "#{Vagrant.config.chef.client_key_path}"
63
+
64
+ file_store_path "/srv/chef/file_store"
65
+ file_cache_path "/srv/chef/cache"
66
+
67
+ pid_file "/var/run/chef/chef-client.pid"
68
+
69
+ Mixlib::Log::Formatter.show_time = true
70
+ solo
71
+
72
+ logger.info "Uploading chef-client configuration script..."
73
+ SSH.upload!(StringIO.new(solo_file), File.join(Vagrant.config.chef.provisioning_path, "client.rb"))
74
+ end
75
+
76
+ def run_chef_client
77
+ logger.info "Running chef-client..."
78
+ SSH.execute do |ssh|
79
+ ssh.exec!("cd #{Vagrant.config.chef.provisioning_path} && sudo chef-client -c client.rb -j dna.json") do |channel, data, stream|
80
+ # TODO: Very verbose. It would be easier to save the data and only show it during
81
+ # an error, or when verbosity level is set high
82
+ logger.info("#{stream}: #{data}")
83
+ end
84
+ end
85
+ end
86
+
87
+ def validation_key_path
88
+ File.expand_path(Vagrant.config.chef.validation_key_path, Env.root_path)
89
+ end
90
+
91
+ def guest_validation_key_path
92
+ File.join(Vagrant.config.chef.provisioning_path, "validation.pem")
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,67 @@
1
+ module Vagrant
2
+ module Provisioners
3
+ # This class implements provisioning via chef-solo.
4
+ class ChefSolo < Chef
5
+ def prepare
6
+ share_cookbook_folders
7
+ end
8
+
9
+ def provision!
10
+ chown_provisioning_folder
11
+ setup_json
12
+ setup_solo_config
13
+ run_chef_solo
14
+ end
15
+
16
+ def share_cookbook_folders
17
+ host_cookbook_paths.each_with_index do |cookbook, i|
18
+ Vagrant.config.vm.share_folder("vagrant-chef-solo-#{i}", cookbook_path(i), cookbook)
19
+ end
20
+ end
21
+
22
+ def setup_solo_config
23
+ solo_file = <<-solo
24
+ file_cache_path "#{Vagrant.config.chef.provisioning_path}"
25
+ cookbook_path #{cookbooks_path}
26
+ solo
27
+
28
+ logger.info "Uploading chef-solo configuration script..."
29
+ SSH.upload!(StringIO.new(solo_file), File.join(Vagrant.config.chef.provisioning_path, "solo.rb"))
30
+ end
31
+
32
+ def run_chef_solo
33
+ logger.info "Running chef-solo..."
34
+ SSH.execute do |ssh|
35
+ ssh.exec!("cd #{Vagrant.config.chef.provisioning_path} && sudo chef-solo -c solo.rb -j dna.json") do |channel, data, stream|
36
+ # TODO: Very verbose. It would be easier to save the data and only show it during
37
+ # an error, or when verbosity level is set high
38
+ logger.info("#{stream}: #{data}")
39
+ end
40
+ end
41
+ end
42
+
43
+ def host_cookbook_paths
44
+ cookbooks = Vagrant.config.chef.cookbooks_path
45
+ cookbooks = [cookbooks] unless cookbooks.is_a?(Array)
46
+ cookbooks.collect! { |cookbook| File.expand_path(cookbook, Env.root_path) }
47
+ return cookbooks
48
+ end
49
+
50
+ def cookbook_path(i)
51
+ File.join(Vagrant.config.chef.provisioning_path, "cookbooks-#{i}")
52
+ end
53
+
54
+ def cookbooks_path
55
+ result = []
56
+ host_cookbook_paths.each_with_index do |host_path, i|
57
+ result << cookbook_path(i)
58
+ end
59
+
60
+ # We're lucky that ruby's string and array syntax for strings is the
61
+ # same as JSON, so we can just convert to JSON here and use that
62
+ result = result.to_s if result.length == 1
63
+ result.to_json
64
+ end
65
+ end
66
+ end
67
+ end
data/lib/vagrant/ssh.rb CHANGED
@@ -1,19 +1,22 @@
1
1
  module Vagrant
2
2
  class SSH
3
- SCRIPT = File.join(File.dirname(__FILE__), '..', '..', 'script', 'vagrant-ssh-expect.sh')
3
+ include Vagrant::Util
4
4
 
5
5
  class << self
6
6
  def connect(opts={})
7
7
  options = {}
8
- [:host, :password, :username].each do |param|
8
+ [:host, :username, :private_key_path].each do |param|
9
9
  options[param] = opts[param] || Vagrant.config.ssh.send(param)
10
10
  end
11
11
 
12
- Kernel.exec "#{SCRIPT} #{options[:username]} #{options[:password]} #{options[:host]} #{port(opts)}".strip
12
+ Kernel.exec "ssh -p #{port(opts)} -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i #{options[:private_key_path]} #{options[:username]}@#{options[:host]}".strip
13
13
  end
14
14
 
15
- def execute
16
- Net::SSH.start(Vagrant.config.ssh.host, Vagrant.config[:ssh][:username], :port => port, :password => Vagrant.config[:ssh][:password]) do |ssh|
15
+ def execute(opts={})
16
+ Net::SSH.start(Vagrant.config.ssh.host,
17
+ Vagrant.config[:ssh][:username],
18
+ opts.merge( :port => port,
19
+ :keys => [Vagrant.config.ssh.private_key_path])) do |ssh|
17
20
  yield ssh
18
21
  end
19
22
  end
@@ -29,7 +32,7 @@ module Vagrant
29
32
  check_thread = Thread.new do
30
33
  begin
31
34
  Thread.current[:result] = false
32
- Net::SSH.start(Vagrant.config.ssh.host, Vagrant.config.ssh.username, :port => port, :password => Vagrant.config.ssh.password, :timeout => Vagrant.config.ssh.timeout) do |ssh|
35
+ execute(:timeout => Vagrant.config.ssh.timeout) do |ssh|
33
36
  Thread.current[:result] = true
34
37
  end
35
38
  rescue Errno::ECONNREFUSED, Net::SSH::Disconnect
@@ -39,6 +42,22 @@ module Vagrant
39
42
 
40
43
  check_thread.join(Vagrant.config.ssh.timeout)
41
44
  return check_thread[:result]
45
+ rescue Net::SSH::AuthenticationFailed
46
+ error_and_exit(<<-msg)
47
+ SSH authentication failed! While this could be due to a variety of reasons,
48
+ the two most common are: private key path is incorrect or you're using a box
49
+ which was built for Vagrant 0.1.x.
50
+
51
+ Vagrant 0.2.x dropped support for password-based authentication. If you're
52
+ tring to `vagrant up` a box which does not support Vagrant's private/public
53
+ keypair, then this error will be raised. To resolve this, read the guide
54
+ on converting base boxes from password-based to keypairs here:
55
+
56
+ http://vagrantup.com/docs/converting_password_to_key_ssh.html
57
+
58
+ If the box was built for 0.2.x and contains a custom public key, perhaps
59
+ the path to the private key is incorrect. Check your `config.ssh.private_key_path`.
60
+ msg
42
61
  end
43
62
 
44
63
  def port(opts={})