vagrant 0.7.8 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/CHANGELOG.md +39 -0
  2. data/Gemfile +1 -7
  3. data/Rakefile +0 -11
  4. data/bin/vagrant +4 -0
  5. data/config/default.rb +1 -2
  6. data/lib/vagrant.rb +7 -5
  7. data/lib/vagrant/action.rb +5 -1
  8. data/lib/vagrant/action/builtin.rb +4 -1
  9. data/lib/vagrant/action/general/package.rb +6 -2
  10. data/lib/vagrant/action/vm.rb +2 -0
  11. data/lib/vagrant/action/vm/clear_forwarded_ports.rb +9 -22
  12. data/lib/vagrant/action/vm/clear_shared_folders.rb +9 -14
  13. data/lib/vagrant/action/vm/customize.rb +9 -4
  14. data/lib/vagrant/action/vm/forward_ports.rb +10 -11
  15. data/lib/vagrant/action/vm/match_mac_address.rb +8 -3
  16. data/lib/vagrant/action/vm/modify.rb +37 -0
  17. data/lib/vagrant/action/vm/network.rb +9 -2
  18. data/lib/vagrant/action/vm/provision.rb +10 -17
  19. data/lib/vagrant/action/vm/provisioner_cleanup.rb +26 -0
  20. data/lib/vagrant/action/vm/share_folders.rb +16 -8
  21. data/lib/vagrant/action/warden.rb +8 -2
  22. data/lib/vagrant/command/ssh.rb +4 -4
  23. data/lib/vagrant/command/ssh_config.rb +4 -2
  24. data/lib/vagrant/config/ssh.rb +3 -0
  25. data/lib/vagrant/config/vm.rb +16 -12
  26. data/lib/vagrant/downloaders/http.rb +2 -0
  27. data/lib/vagrant/environment.rb +136 -12
  28. data/lib/vagrant/errors.rb +15 -0
  29. data/lib/vagrant/provisioners.rb +1 -1
  30. data/lib/vagrant/provisioners/base.rb +4 -0
  31. data/lib/vagrant/provisioners/chef.rb +13 -11
  32. data/lib/vagrant/provisioners/{chef_server.rb → chef_client.rb} +5 -5
  33. data/lib/vagrant/provisioners/chef_solo.rb +48 -89
  34. data/lib/vagrant/provisioners/shell.rb +47 -12
  35. data/lib/vagrant/ssh.rb +61 -27
  36. data/lib/vagrant/systems.rb +1 -0
  37. data/lib/vagrant/systems/base.rb +1 -1
  38. data/lib/vagrant/systems/linux.rb +7 -9
  39. data/lib/vagrant/systems/redhat.rb +12 -4
  40. data/lib/vagrant/systems/solaris.rb +9 -4
  41. data/lib/vagrant/systems/suse.rb +9 -0
  42. data/lib/vagrant/ui.rb +12 -5
  43. data/lib/vagrant/util.rb +2 -2
  44. data/lib/vagrant/util/counter.rb +22 -0
  45. data/lib/vagrant/util/platform.rb +1 -2
  46. data/lib/vagrant/util/safe_exec.rb +28 -0
  47. data/lib/vagrant/version.rb +1 -1
  48. data/lib/vagrant/vm.rb +2 -0
  49. data/templates/chef_solo_solo.erb +4 -4
  50. data/templates/commands/init/Vagrantfile.erb +4 -0
  51. data/templates/locales/en.yml +31 -8
  52. data/templates/ssh_config.erb +6 -0
  53. data/test/test_helper.rb +5 -3
  54. data/test/vagrant/action/builder_test.rb +4 -0
  55. data/test/vagrant/action/vm/clear_forwarded_ports_test.rb +18 -38
  56. data/test/vagrant/action/vm/clear_shared_folders_test.rb +7 -16
  57. data/test/vagrant/action/vm/customize_test.rb +12 -5
  58. data/test/vagrant/action/vm/forward_ports_test.rb +12 -7
  59. data/test/vagrant/action/vm/match_mac_address_test.rb +5 -1
  60. data/test/vagrant/action/vm/modify_test.rb +38 -0
  61. data/test/vagrant/action/vm/provision_test.rb +13 -38
  62. data/test/vagrant/action/vm/provisioner_cleanup_test.rb +56 -0
  63. data/test/vagrant/action/vm/share_folders_test.rb +10 -5
  64. data/test/vagrant/action/warden_test.rb +13 -7
  65. data/test/vagrant/config/vm_test.rb +0 -22
  66. data/test/vagrant/downloaders/http_test.rb +2 -0
  67. data/test/vagrant/environment_test.rb +110 -20
  68. data/test/vagrant/provisioners/{chef_server_test.rb → chef_client_test.rb} +2 -2
  69. data/test/vagrant/provisioners/chef_solo_test.rb +16 -173
  70. data/test/vagrant/ssh_test.rb +8 -43
  71. data/test/vagrant/systems/linux_test.rb +9 -19
  72. data/test/vagrant/util/counter_test.rb +29 -0
  73. data/test/vagrant/util/platform_test.rb +2 -2
  74. data/vagrant.gemspec +1 -2
  75. metadata +114 -84
  76. data/lib/vagrant/util/plain_logger.rb +0 -25
  77. data/lib/vagrant/util/resource_logger.rb +0 -63
  78. data/test/vagrant/util/plain_logger_test.rb +0 -17
  79. data/test/vagrant/util/resource_logger_test.rb +0 -78
@@ -148,6 +148,21 @@ module Vagrant
148
148
  error_key(:socket_error, "vagrant.downloaders.http")
149
149
  end
150
150
 
151
+ class DownloaderHTTPStatusError < VagrantError
152
+ status_code(51)
153
+ error_key(:status_error, "vagrant.downloaders.http")
154
+ end
155
+
156
+ class EnvironmentLockedError < VagrantError
157
+ status_code(52)
158
+ error_key(:environment_locked)
159
+ end
160
+
161
+ class HomeDirectoryMigrationFailed < VagrantError
162
+ status_code(53)
163
+ error_key(:home_dir_migration_failed)
164
+ end
165
+
151
166
  class ForwardPortAutolistEmpty < VagrantError
152
167
  status_code(27)
153
168
  error_key(:auto_empty, "vagrant.actions.vm.forward_ports")
@@ -2,7 +2,7 @@
2
2
  # as configuration classes right away with Vagrant.
3
3
  require 'vagrant/provisioners/base'
4
4
  require 'vagrant/provisioners/chef'
5
- require 'vagrant/provisioners/chef_server'
5
+ require 'vagrant/provisioners/chef_client'
6
6
  require 'vagrant/provisioners/chef_solo'
7
7
  require 'vagrant/provisioners/puppet'
8
8
  require 'vagrant/provisioners/puppet_server'
@@ -58,6 +58,10 @@ module Vagrant
58
58
  # is expected to do whatever necessary to provision the system (create files,
59
59
  # SSH, etc.)
60
60
  def provision!; end
61
+
62
+ # This is the method called to when the system is being destroyed
63
+ # and allows the provisioners to engage in any cleanup tasks necessary.
64
+ def cleanup; end
61
65
  end
62
66
  end
63
67
  end
@@ -59,7 +59,7 @@ module Vagrant
59
59
 
60
60
  # Merge with the "extra data" which isn't put under the
61
61
  # vagrant namespace by default
62
- data.merge!(config.json)
62
+ data.merge!(config.merged_json)
63
63
 
64
64
  json = data.to_json
65
65
 
@@ -76,6 +76,8 @@ module Vagrant
76
76
  class Chef < Base
77
77
  # This is the configuration which is available through `config.chef`
78
78
  class Config < Vagrant::Config::Base
79
+ extend Util::Counter
80
+
79
81
  # Shared config
80
82
  attr_accessor :node_name
81
83
  attr_accessor :provisioning_path
@@ -90,11 +92,12 @@ module Vagrant
90
92
  attr_accessor :no_proxy
91
93
  attr_accessor :binary_path
92
94
  attr_accessor :binary_env
95
+ attr_accessor :run_list
93
96
 
94
97
  def initialize
95
- @provisioning_path = "/tmp/vagrant-chef"
98
+ @provisioning_path = "/tmp/vagrant-chef-#{self.class.get_and_update_counter}"
96
99
  @log_level = :info
97
- @json = { :instance_role => "vagrant" }
100
+ @json = {}
98
101
  @http_proxy = nil
99
102
  @http_proxy_user = nil
100
103
  @http_proxy_pass = nil
@@ -104,16 +107,15 @@ module Vagrant
104
107
  @no_proxy = nil
105
108
  @binary_path = nil
106
109
  @binary_env = nil
110
+ @run_list = []
107
111
  end
108
112
 
109
- # Returns the run list for the provisioning
110
- def run_list
111
- json[:run_list] ||= []
112
- end
113
-
114
- # Sets the run list to the specified value
115
- def run_list=(value)
116
- json[:run_list] = value
113
+ # This returns the json that is merged with the defaults and the
114
+ # user set data.
115
+ def merged_json
116
+ { :instance_role => "vagrant",
117
+ :run_list => run_list
118
+ }.merge(json || {})
117
119
  end
118
120
 
119
121
  # Adds a recipe to the run list
@@ -4,8 +4,8 @@ module Vagrant
4
4
  module Provisioners
5
5
  # This class implements provisioning via chef-client, allowing provisioning
6
6
  # with a chef server.
7
- class ChefServer < Chef
8
- register :chef_server
7
+ class ChefClient < Chef
8
+ register :chef_client
9
9
 
10
10
  class Config < Chef::Config
11
11
  attr_accessor :chef_server_url
@@ -34,7 +34,7 @@ module Vagrant
34
34
 
35
35
  errors.add(I18n.t("vagrant.config.chef.server_url_empty")) if !chef_server_url || chef_server_url.strip == ""
36
36
  errors.add(I18n.t("vagrant.config.chef.validation_key_path")) if !validation_key_path
37
- errors.add(I18n.t("vagrant.config.chef.run_list_empty")) if json[:run_list] && run_list.empty?
37
+ errors.add(I18n.t("vagrant.config.chef.run_list_empty")) if run_list && run_list.empty?
38
38
  end
39
39
  end
40
40
 
@@ -68,7 +68,7 @@ module Vagrant
68
68
  env.ui.info I18n.t("vagrant.provisioners.chef.upload_validation_key")
69
69
  vm.ssh.upload!(validation_key_path, guest_validation_key_path)
70
70
  end
71
-
71
+
72
72
  def upload_encrypted_data_bag_secret
73
73
  env.ui.info I18n.t("vagrant.provisioners.chef.upload_encrypted_data_bag_secret_key")
74
74
  vm.ssh.upload!(encrypted_data_bag_secret_key_path, config.encrypted_data_bag_secret)
@@ -107,7 +107,7 @@ module Vagrant
107
107
  def validation_key_path
108
108
  File.expand_path(config.validation_key_path, env.root_path)
109
109
  end
110
-
110
+
111
111
  def encrypted_data_bag_secret_key_path
112
112
  File.expand_path(config.encrypted_data_bag_secret_key_path, env.root_path)
113
113
  end
@@ -24,14 +24,22 @@ module Vagrant
24
24
  super
25
25
 
26
26
  errors.add(I18n.t("vagrant.config.chef.cookbooks_path_empty")) if !cookbooks_path || [cookbooks_path].flatten.empty?
27
- errors.add(I18n.t("vagrant.config.chef.run_list_empty")) if !json[:run_list] || run_list.empty?
27
+ errors.add(I18n.t("vagrant.config.chef.run_list_empty")) if !run_list || run_list.empty?
28
28
  end
29
29
  end
30
30
 
31
+ attr_reader :cookbook_folders
32
+ attr_reader :role_folders
33
+ attr_reader :data_bags_folders
34
+
31
35
  def prepare
32
- share_cookbook_folders
33
- share_role_folders
34
- share_data_bags_folders
36
+ @cookbook_folders = expanded_folders(config.cookbooks_path)
37
+ @role_folders = expanded_folders(config.roles_path)
38
+ @data_bags_folders = expanded_folders(config.data_bags_path)
39
+
40
+ share_folders("csc", @cookbook_folders)
41
+ share_folders("csr", @role_folders)
42
+ share_folders("csdb", @data_bags_folders)
35
43
  end
36
44
 
37
45
  def provision!
@@ -42,25 +50,47 @@ module Vagrant
42
50
  run_chef_solo
43
51
  end
44
52
 
45
- def share_cookbook_folders
46
- host_cookbook_paths.each_with_index do |cookbook, i|
47
- env.config.vm.share_folder("v-csc-#{i}", cookbook_path(i), cookbook, :nfs => config.nfs)
48
- end
49
- end
53
+ # Converts paths to a list of properly expanded paths with types.
54
+ def expanded_folders(paths)
55
+ # Convert the path to an array if it is a string or just a single
56
+ # path element which contains the folder location (:host or :vm)
57
+ paths = [paths] if paths.is_a?(String) || paths.first.is_a?(Symbol)
58
+
59
+ index = 0
60
+ paths.map do |path|
61
+ path = [:host, path] if !path.is_a?(Array)
62
+ type, path = path
63
+
64
+ # Create the local/remote path based on whether this is a host
65
+ # or VM path.
66
+ local_path = nil
67
+ local_path = File.expand_path(path, env.root_path) if type == :host
68
+ remote_path = type == :host ? "#{config.provisioning_path}/chef-solo-#{index}" : path
69
+ index += 1
50
70
 
51
- def share_role_folders
52
- host_role_paths.each_with_index do |role, i|
53
- env.config.vm.share_folder("v-csr-#{i}", role_path(i), role, :nfs => config.nfs)
71
+ # Return the result
72
+ [type, local_path, remote_path]
54
73
  end
55
74
  end
56
75
 
57
- def share_data_bags_folders
58
- host_data_bag_paths.each_with_index do |data_bag, i|
59
- env.config.vm.share_folder("v-csdb-#{i}", data_bag_path(i), data_bag, :nfs => config.nfs)
76
+ # Shares the given folders with the given prefix. The folders should
77
+ # be of the structure resulting from the `expanded_folders` function.
78
+ def share_folders(prefix, folders)
79
+ index = 0
80
+ folders.each do |type, local_path, remote_path|
81
+ if type == :host
82
+ env.config.vm.share_folder("v-#{prefix}-#{index}",
83
+ remote_path, local_path, :nfs => config.nfs)
84
+ index += 1
85
+ end
60
86
  end
61
87
  end
62
88
 
63
89
  def setup_solo_config
90
+ cookbooks_path = guest_paths(@cookbook_folders)
91
+ roles_path = guest_paths(@role_folders)
92
+ data_bags_path = guest_paths(@data_bags_folders)
93
+
64
94
  setup_config("chef_solo_solo", "solo.rb", {
65
95
  :node_name => config.node_name,
66
96
  :provisioning_path => config.provisioning_path,
@@ -87,80 +117,9 @@ module Vagrant
87
117
  end
88
118
  end
89
119
 
90
- def host_folder_paths(paths)
91
- # Convert single cookbook paths such as "cookbooks" or [:vm, "cookbooks"]
92
- # into a proper array representation.
93
- paths = [paths] if paths.is_a?(String) || paths.first.is_a?(Symbol)
94
-
95
- paths.inject([]) do |acc, path|
96
- path = [:host, path] if !path.is_a?(Array)
97
- type, path = path
98
-
99
- acc << File.expand_path(path, env.root_path) if type == :host
100
- acc
101
- end
102
- end
103
-
104
- def folder_path(*args)
105
- File.join(config.provisioning_path, args.join("-"))
106
- end
107
-
108
- def folders_path(folders, folder)
109
- # Convert single cookbook paths such as "cookbooks" or [:vm, "cookbooks"]
110
- # into a proper array representation.
111
- folders = [folders] if folders.is_a?(String) || folders.first.is_a?(Symbol)
112
-
113
- # Convert each path to the proper absolute path depending on if the path
114
- # is a host path or a VM path
115
- result = []
116
- folders.each_with_index do |path, i|
117
- path = [:host, path] if !path.is_a?(Array)
118
- type, path = path
119
-
120
- result << folder_path(folder, i) if type == :host
121
- result << folder_path(path) if type == :vm
122
- end
123
-
124
- # We're lucky that ruby's string and array syntax for strings is the
125
- # same as JSON, so we can just convert to JSON here and use that
126
- result = result[0].to_s if result.length == 1
127
- result
128
- end
129
-
130
- def host_cookbook_paths
131
- host_folder_paths(config.cookbooks_path)
132
- end
133
-
134
- def host_role_paths
135
- host_folder_paths(config.roles_path)
136
- end
137
-
138
- def host_data_bag_paths
139
- host_folder_paths(config.data_bags_path)
140
- end
141
-
142
- def cookbook_path(i)
143
- folder_path("cookbooks", i)
144
- end
145
-
146
- def role_path(i)
147
- folder_path("roles", i)
148
- end
149
-
150
- def data_bag_path(i)
151
- folder_path("data_bags", i)
152
- end
153
-
154
- def cookbooks_path
155
- folders_path(config.cookbooks_path, "cookbooks").to_json
156
- end
157
-
158
- def roles_path
159
- folders_path(config.roles_path, "roles").to_json
160
- end
161
-
162
- def data_bags_path
163
- folders_path(config.data_bags_path, "data_bags").to_json
120
+ # Extracts only the remote paths from a list of folders
121
+ def guest_paths(folders)
122
+ folders.map { |parts| parts[2] }
164
123
  end
165
124
  end
166
125
  end
@@ -4,10 +4,13 @@ module Vagrant
4
4
  register :shell
5
5
 
6
6
  class Config < Vagrant::Config::Base
7
+ attr_accessor :inline
7
8
  attr_accessor :path
8
9
  attr_accessor :upload_path
9
10
 
10
11
  def initialize
12
+ @inline = nil
13
+ @path = nil
11
14
  @upload_path = "/tmp/vagrant-shell"
12
15
  end
13
16
 
@@ -18,31 +21,63 @@ module Vagrant
18
21
  def validate(errors)
19
22
  super
20
23
 
21
- if !path
22
- errors.add(I18n.t("vagrant.provisioners.shell.path_not_set"))
23
- elsif !expanded_path.file?
24
+ # Validate that the parameters are properly set
25
+ if path && inline
26
+ errors.add(I18n.t("vagrant.provisioners.shell.path_and_inline_set"))
27
+ elsif !path && !inline
28
+ errors.add(I18n.t("vagrant.provisioners.shell.no_path_or_inline"))
29
+ end
30
+
31
+ # Validate the existence of a script to upload
32
+ if path && !expanded_path.file?
24
33
  errors.add(I18n.t("vagrant.provisioners.shell.path_invalid", :path => expanded_path))
25
34
  end
26
35
 
36
+ # There needs to be a path to upload the script to
27
37
  if !upload_path
28
38
  errors.add(I18n.t("vagrant.provisioners.shell.upload_path_not_set"))
29
39
  end
30
40
  end
31
41
  end
32
42
 
43
+ # This method yields the path to a script to upload and execute
44
+ # on the remote server. This method will properly clean up the
45
+ # script file if needed.
46
+ def with_script_file
47
+ if config.path
48
+ # Just yield the path to that file...
49
+ yield config.expanded_path
50
+ return
51
+ end
52
+
53
+ # Otherwise we have an inline script, we need to Tempfile it,
54
+ # and handle it specially...
55
+ file = Tempfile.new('vagrant-shell')
56
+ begin
57
+ file.write(config.inline)
58
+ file.fsync
59
+ yield file.path
60
+ ensure
61
+ file.close
62
+ file.unlink
63
+ end
64
+ end
65
+
33
66
  def provision!
34
67
  commands = ["chmod +x #{config.upload_path}", config.upload_path]
35
68
 
36
- # Upload the script to the VM
37
- vm.ssh.upload!(config.expanded_path.to_s, config.upload_path)
69
+ with_script_file do |path|
70
+ # Upload the script to the VM
71
+ vm.ssh.upload!(path.to_s, config.upload_path)
38
72
 
39
- # Execute it with sudo
40
- vm.ssh.execute do |ssh|
41
- ssh.sudo!(commands) do |ch, type, data|
42
- if type == :exit_status
43
- ssh.check_exit_status(data, commands)
44
- else
45
- env.ui.info(data)
73
+ # Execute it with sudo
74
+ vm.ssh.execute do |ssh|
75
+ ssh.sudo!(commands) do |ch, type, data|
76
+ if type == :exit_status
77
+ ssh.check_exit_status(data, commands)
78
+ else
79
+ env.ui.info(data)
80
+ end
46
81
  end
47
82
  end
48
83
  end
@@ -1,16 +1,17 @@
1
- require 'timeout'
2
1
  require 'net/ssh'
3
2
  require 'net/scp'
4
- require 'mario'
5
-
6
- require 'vagrant/ssh/session'
7
3
 
8
4
  module Vagrant
9
5
  # Manages SSH access to a specific environment. Allows an environment to
10
6
  # replace the process with SSH itself, run a specific set of commands,
11
7
  # upload files, or even check if a host is up.
12
8
  class SSH
9
+ # Autoload this guy because he is really only used in one location
10
+ # and not for every Vagrant command.
11
+ autoload :Session, 'vagrant/ssh/session'
12
+
13
13
  include Util::Retryable
14
+ include Util::SafeExec
14
15
 
15
16
  # Reference back up to the environment which this SSH object belongs
16
17
  # to
@@ -18,13 +19,14 @@ module Vagrant
18
19
 
19
20
  def initialize(environment)
20
21
  @env = environment
22
+ @current_session = nil
21
23
  end
22
24
 
23
25
  # Connects to the environment's virtual machine, replacing the ruby
24
26
  # process with an SSH process. This method optionally takes a hash
25
27
  # of options which override the configuration values.
26
28
  def connect(opts={})
27
- if Mario::Platform.windows?
29
+ if Util::Platform.windows?
28
30
  raise Errors::SSHUnavailableWindows, :key_path => env.config.ssh.private_key_path,
29
31
  :ssh_port => port(opts)
30
32
  end
@@ -54,10 +56,9 @@ module Vagrant
54
56
  # Some hackery going on here. On Mac OS X Leopard (10.5), exec fails
55
57
  # (GH-51). As a workaround, we fork and wait. On all other platforms,
56
58
  # we simply exec.
57
- pid = nil
58
- pid = fork if Util::Platform.leopard? || Util::Platform.tiger?
59
- Kernel.exec "ssh #{command_options.join(" ")} #{options[:username]}@#{options[:host]}".strip if pid.nil?
60
- Process.wait(pid) if pid
59
+ command = "ssh #{command_options.join(" ")} #{options[:username]}@#{options[:host]}".strip
60
+ env.logger.info("ssh") { "Invoking SSH: #{command}" }
61
+ safe_exec(command)
61
62
  end
62
63
 
63
64
  # Opens an SSH connection to this environment's virtual machine and yields
@@ -71,17 +72,37 @@ module Vagrant
71
72
  opts[:forward_agent] = true if env.config.ssh.forward_agent
72
73
  opts[:port] ||= port
73
74
 
74
- retryable(:tries => 5, :on => Errno::ECONNREFUSED) do
75
- Net::SSH.start(env.config.ssh.host,
76
- env.config.ssh.username,
77
- opts.merge( :keys => [env.config.ssh.private_key_path],
78
- :keys_only => true,
79
- :user_known_hosts_file => [],
80
- :paranoid => false,
81
- :config => false)) do |ssh|
82
- yield SSH::Session.new(ssh, env)
75
+ # Check if we have a currently open SSH session which has the
76
+ # same options, and use that if possible
77
+ session, options = @current_session
78
+
79
+ if !session || options != opts
80
+ env.logger.info("ssh") { "Connecting to SSH: #{env.config.ssh.host} #{opts[:port]}" }
81
+
82
+ # The exceptions which are acceptable to retry on during
83
+ # attempts to connect to SSH
84
+ exceptions = [Errno::ECONNREFUSED, Net::SSH::Disconnect]
85
+
86
+ # Connect to SSH and gather the session
87
+ session = retryable(:tries => 5, :on => exceptions) do
88
+ connection = Net::SSH.start(env.config.ssh.host,
89
+ env.config.ssh.username,
90
+ opts.merge( :keys => [env.config.ssh.private_key_path],
91
+ :keys_only => true,
92
+ :user_known_hosts_file => [],
93
+ :paranoid => false,
94
+ :config => false))
95
+ SSH::Session.new(connection, env)
83
96
  end
97
+
98
+ # Save the new session along with the options which created it
99
+ @current_session = [session, opts]
100
+ else
101
+ env.logger.info("ssh") { "Using cached SSH session: #{session}" }
84
102
  end
103
+
104
+ # Yield our session for executing
105
+ return yield session if block_given?
85
106
  rescue Errno::ECONNREFUSED
86
107
  raise Errors::SSHConnectionRefused
87
108
  end
@@ -107,6 +128,7 @@ module Vagrant
107
128
  # Windows
108
129
  ssh_port = port
109
130
 
131
+ require 'timeout'
110
132
  Timeout.timeout(env.config.ssh.timeout) do
111
133
  execute(:timeout => env.config.ssh.timeout,
112
134
  :port => ssh_port) { |ssh| }
@@ -124,13 +146,16 @@ module Vagrant
124
146
  # if needed, or on failure erroring.
125
147
  def check_key_permissions(key_path)
126
148
  # Windows systems don't have this issue
127
- return if Mario::Platform.windows?
149
+ return if Util::Platform.windows?
150
+
151
+ env.logger.info("ssh") { "Checking key permissions: #{key_path}" }
128
152
 
129
153
  stat = File.stat(key_path)
130
154
 
131
155
  if stat.owned? && file_perms(key_path) != "600"
132
- File.chmod(0600, key_path)
156
+ env.logger.info("ssh") { "Attempting to correct key permissions to 0600" }
133
157
 
158
+ File.chmod(0600, key_path)
134
159
  raise Errors::SSHKeyBadPermissions, :key_path => key_path if file_perms(key_path) != "600"
135
160
  end
136
161
  rescue Errno::EPERM
@@ -151,23 +176,32 @@ module Vagrant
151
176
  # `config.ssh.forwarded_port_key`.
152
177
  def port(opts={})
153
178
  # Check if port was specified in options hash
154
- pnum = opts[:port]
155
- return pnum if pnum
179
+ return opts[:port] if opts[:port]
156
180
 
157
181
  # Check if a port was specified in the config
158
182
  return env.config.ssh.port if env.config.ssh.port
159
-
183
+
160
184
  # Check if we have an SSH forwarded port
161
- pnum = nil
185
+ pnum_by_name = nil
186
+ pnum_by_destination = nil
162
187
  env.vm.vm.network_adapters.each do |na|
163
- pnum = na.nat_driver.forwarded_ports.detect do |fp|
188
+ # Look for the port number by name...
189
+ pnum_by_name = na.nat_driver.forwarded_ports.detect do |fp|
164
190
  fp.name == env.config.ssh.forwarded_port_key
165
191
  end
166
192
 
167
- break if pnum
193
+ # Look for the port number by destination...
194
+ pnum_by_destination = na.nat_driver.forwarded_ports.detect do |fp|
195
+ fp.guestport == env.config.ssh.forwarded_port_destination
196
+ end
197
+
198
+ # pnum_by_name is what we're looking for here, so break early
199
+ # if we have it.
200
+ break if pnum_by_name
168
201
  end
169
202
 
170
- return pnum.hostport if pnum
203
+ return pnum_by_name.hostport if pnum_by_name
204
+ return pnum_by_destination.hostport if pnum_by_destination
171
205
 
172
206
  # This should NEVER happen.
173
207
  raise Errors::SSHPortNotDetected