vagrant 0.7.8 → 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +39 -0
- data/Gemfile +1 -7
- data/Rakefile +0 -11
- data/bin/vagrant +4 -0
- data/config/default.rb +1 -2
- data/lib/vagrant.rb +7 -5
- data/lib/vagrant/action.rb +5 -1
- data/lib/vagrant/action/builtin.rb +4 -1
- data/lib/vagrant/action/general/package.rb +6 -2
- data/lib/vagrant/action/vm.rb +2 -0
- data/lib/vagrant/action/vm/clear_forwarded_ports.rb +9 -22
- data/lib/vagrant/action/vm/clear_shared_folders.rb +9 -14
- data/lib/vagrant/action/vm/customize.rb +9 -4
- data/lib/vagrant/action/vm/forward_ports.rb +10 -11
- data/lib/vagrant/action/vm/match_mac_address.rb +8 -3
- data/lib/vagrant/action/vm/modify.rb +37 -0
- data/lib/vagrant/action/vm/network.rb +9 -2
- data/lib/vagrant/action/vm/provision.rb +10 -17
- data/lib/vagrant/action/vm/provisioner_cleanup.rb +26 -0
- data/lib/vagrant/action/vm/share_folders.rb +16 -8
- data/lib/vagrant/action/warden.rb +8 -2
- data/lib/vagrant/command/ssh.rb +4 -4
- data/lib/vagrant/command/ssh_config.rb +4 -2
- data/lib/vagrant/config/ssh.rb +3 -0
- data/lib/vagrant/config/vm.rb +16 -12
- data/lib/vagrant/downloaders/http.rb +2 -0
- data/lib/vagrant/environment.rb +136 -12
- data/lib/vagrant/errors.rb +15 -0
- data/lib/vagrant/provisioners.rb +1 -1
- data/lib/vagrant/provisioners/base.rb +4 -0
- data/lib/vagrant/provisioners/chef.rb +13 -11
- data/lib/vagrant/provisioners/{chef_server.rb → chef_client.rb} +5 -5
- data/lib/vagrant/provisioners/chef_solo.rb +48 -89
- data/lib/vagrant/provisioners/shell.rb +47 -12
- data/lib/vagrant/ssh.rb +61 -27
- data/lib/vagrant/systems.rb +1 -0
- data/lib/vagrant/systems/base.rb +1 -1
- data/lib/vagrant/systems/linux.rb +7 -9
- data/lib/vagrant/systems/redhat.rb +12 -4
- data/lib/vagrant/systems/solaris.rb +9 -4
- data/lib/vagrant/systems/suse.rb +9 -0
- data/lib/vagrant/ui.rb +12 -5
- data/lib/vagrant/util.rb +2 -2
- data/lib/vagrant/util/counter.rb +22 -0
- data/lib/vagrant/util/platform.rb +1 -2
- data/lib/vagrant/util/safe_exec.rb +28 -0
- data/lib/vagrant/version.rb +1 -1
- data/lib/vagrant/vm.rb +2 -0
- data/templates/chef_solo_solo.erb +4 -4
- data/templates/commands/init/Vagrantfile.erb +4 -0
- data/templates/locales/en.yml +31 -8
- data/templates/ssh_config.erb +6 -0
- data/test/test_helper.rb +5 -3
- data/test/vagrant/action/builder_test.rb +4 -0
- data/test/vagrant/action/vm/clear_forwarded_ports_test.rb +18 -38
- data/test/vagrant/action/vm/clear_shared_folders_test.rb +7 -16
- data/test/vagrant/action/vm/customize_test.rb +12 -5
- data/test/vagrant/action/vm/forward_ports_test.rb +12 -7
- data/test/vagrant/action/vm/match_mac_address_test.rb +5 -1
- data/test/vagrant/action/vm/modify_test.rb +38 -0
- data/test/vagrant/action/vm/provision_test.rb +13 -38
- data/test/vagrant/action/vm/provisioner_cleanup_test.rb +56 -0
- data/test/vagrant/action/vm/share_folders_test.rb +10 -5
- data/test/vagrant/action/warden_test.rb +13 -7
- data/test/vagrant/config/vm_test.rb +0 -22
- data/test/vagrant/downloaders/http_test.rb +2 -0
- data/test/vagrant/environment_test.rb +110 -20
- data/test/vagrant/provisioners/{chef_server_test.rb → chef_client_test.rb} +2 -2
- data/test/vagrant/provisioners/chef_solo_test.rb +16 -173
- data/test/vagrant/ssh_test.rb +8 -43
- data/test/vagrant/systems/linux_test.rb +9 -19
- data/test/vagrant/util/counter_test.rb +29 -0
- data/test/vagrant/util/platform_test.rb +2 -2
- data/vagrant.gemspec +1 -2
- metadata +114 -84
- data/lib/vagrant/util/plain_logger.rb +0 -25
- data/lib/vagrant/util/resource_logger.rb +0 -63
- data/test/vagrant/util/plain_logger_test.rb +0 -17
- data/test/vagrant/util/resource_logger_test.rb +0 -78
data/lib/vagrant/errors.rb
CHANGED
@@ -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")
|
data/lib/vagrant/provisioners.rb
CHANGED
@@ -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/
|
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.
|
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 = {
|
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
|
-
#
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
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
|
8
|
-
register :
|
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
|
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 !
|
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
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
52
|
-
|
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
|
-
|
58
|
-
|
59
|
-
|
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
|
-
|
91
|
-
|
92
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
37
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
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
|
data/lib/vagrant/ssh.rb
CHANGED
@@ -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
|
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
|
-
|
58
|
-
|
59
|
-
|
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
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
185
|
+
pnum_by_name = nil
|
186
|
+
pnum_by_destination = nil
|
162
187
|
env.vm.vm.network_adapters.each do |na|
|
163
|
-
|
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
|
-
|
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
|
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
|