vagrant-hostmanager-ext 1.8.7.rc1

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.
@@ -0,0 +1,95 @@
1
+ module VagrantPlugins
2
+ module HostManager
3
+ class Config < Vagrant.plugin('2', :config)
4
+ attr_accessor :enabled
5
+ attr_accessor :manage_host
6
+ attr_accessor :manage_guest
7
+ attr_accessor :ignore_private_ip
8
+ attr_accessor :aliases
9
+ attr_accessor :include_offline
10
+ attr_accessor :ip_resolver
11
+ attr_accessor :extra_hosts
12
+
13
+ alias_method :enabled?, :enabled
14
+ alias_method :include_offline?, :include_offline
15
+ alias_method :manage_host?, :manage_host
16
+ alias_method :manage_guest?, :manage_guest
17
+
18
+ def initialize
19
+ @enabled = UNSET_VALUE
20
+ @manage_host = UNSET_VALUE
21
+ @manage_guest = UNSET_VALUE
22
+ @ignore_private_ip = UNSET_VALUE
23
+ @include_offline = UNSET_VALUE
24
+ @aliases = UNSET_VALUE
25
+ @ip_resolver = UNSET_VALUE
26
+ @extra_hosts = UNSET_VALUE
27
+ end
28
+
29
+ def finalize!
30
+ @enabled = false if @enabled == UNSET_VALUE
31
+ @manage_host = false if @manage_host == UNSET_VALUE
32
+ @manage_guest = true if @manage_guest == UNSET_VALUE
33
+ @ignore_private_ip = false if @ignore_private_ip == UNSET_VALUE
34
+ @include_offline = false if @include_offline == UNSET_VALUE
35
+ @aliases = [] if @aliases == UNSET_VALUE
36
+ @ip_resolver = nil if @ip_resolver == UNSET_VALUE
37
+ @extra_hosts = [] if @extra_hosts == UNSET_VALUE
38
+
39
+ @aliases = [ @aliases ].flatten
40
+ end
41
+
42
+ def validate(machine)
43
+ errors = []
44
+
45
+ errors << validate_bool('hostmanager.enabled', @enabled)
46
+ errors << validate_bool('hostmanager.manage_host', @manage_host)
47
+ errors << validate_bool('hostmanager.manage_guest', @manage_guest)
48
+ errors << validate_bool('hostmanager.ignore_private_ip', @ignore_private_ip)
49
+ errors << validate_bool('hostmanager.include_offline', @include_offline)
50
+ errors.compact!
51
+
52
+ # check if aliases option is an Array or String
53
+ if !machine.config.hostmanager.aliases.kind_of?(Array) &&
54
+ !machine.config.hostmanager.aliases.kind_of?(String)
55
+ errors << I18n.t('vagrant_hostmanager.config.not_an_array_or_string', {
56
+ :config_key => 'hostmanager.aliases',
57
+ :is_class => aliases.class.to_s,
58
+ })
59
+ end
60
+
61
+ if !machine.config.hostmanager.extra_hosts.kind_of?(Array)
62
+ errors << I18n.t('vagrant_hostmanager.config.not_an_array', {
63
+ :config_key => 'hostmanager.extra_hosts',
64
+ :is_class => extra_hosts.class.to_s,
65
+ })
66
+ end
67
+
68
+ if !machine.config.hostmanager.ip_resolver.nil? &&
69
+ !machine.config.hostmanager.ip_resolver.kind_of?(Proc)
70
+ errors << I18n.t('vagrant_hostmanager.config.not_a_proc', {
71
+ :config_key => 'hostmanager.ip_resolver',
72
+ :is_class => ip_resolver.class.to_s,
73
+ })
74
+ end
75
+
76
+ errors.compact!
77
+ { "HostManager configuration" => errors }
78
+ end
79
+
80
+ private
81
+
82
+ def validate_bool(key, value)
83
+ if ![TrueClass, FalseClass].include?(value.class) &&
84
+ value != UNSET_VALUE
85
+ I18n.t('vagrant_hostmanager.config.not_a_bool', {
86
+ :config_key => key,
87
+ :value => value.class.to_s
88
+ })
89
+ else
90
+ nil
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,6 @@
1
+ module VagrantPlugins
2
+ module HostManager
3
+ module Errors
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,238 @@
1
+ require 'tempfile'
2
+
3
+ module VagrantPlugins
4
+ module HostManager
5
+ module HostsFile
6
+
7
+ class Updater
8
+
9
+ def initialize(global_env, provider)
10
+ @global_env = global_env
11
+ @config = Util.get_config(@global_env)
12
+ @provider = provider
13
+ @logger = Log4r::Logger.new('vagrant::hostmanager::updater')
14
+ @logger.debug("init updater")
15
+ end
16
+
17
+ def update_guest(machine)
18
+ return unless machine.communicate.ready?
19
+
20
+ if (machine.communicate.test("uname -s | grep SunOS"))
21
+ realhostfile = "/etc/inet/hosts"
22
+ line_endings = "lf"
23
+ elsif (machine.communicate.test("test -d $Env:SystemRoot"))
24
+ windir = ""
25
+ machine.communicate.execute("echo %SYSTEMROOT%", {:shell => :cmd}) do |type, contents|
26
+ windir << contents.gsub("\r\n", '') if type == :stdout
27
+ end
28
+ realhostfile = "#{windir}\\System32\\drivers\\etc\\hosts"
29
+ line_endings = "crlf"
30
+ else
31
+ realhostfile = "/etc/hosts"
32
+ line_endings = "lf"
33
+ end
34
+
35
+ # download and modify file with Vagrant-managed entries
36
+ file = @global_env.tmp_path.join("hosts.#{machine.name}")
37
+ machine.communicate.download(realhostfile, file)
38
+
39
+ @logger.debug("file is: #{file.to_s}")
40
+ @logger.debug("class of file is: #{file.class}")
41
+
42
+ if update_file(file, machine, false, line_endings)
43
+ # upload modified file and remove temporary file
44
+ machine.communicate.upload(file.to_s, "/tmp/hosts.#{machine.name}")
45
+ if windir
46
+ machine.communicate.sudo("mv -force /tmp/hosts.#{machine.name} #{realhostfile}")
47
+ else
48
+ machine.communicate.sudo("cat /tmp/hosts.#{machine.name} > #{realhostfile} && rm /tmp/hosts.#{machine.name}")
49
+ end
50
+ end
51
+
52
+ end
53
+
54
+ def update_host
55
+ # copy and modify hosts file on host with Vagrant-managed entries
56
+ file = @global_env.tmp_path.join('hosts.local')
57
+
58
+ if WindowsSupport.windows?
59
+ # lazily include windows Module
60
+ class << self
61
+ include WindowsSupport unless include? WindowsSupport
62
+ end
63
+ hosts_location = "#{ENV['WINDIR']}\\System32\\drivers\\etc\\hosts"
64
+ copy_proc = Proc.new { windows_copy_file(file, hosts_location) }
65
+ line_endings = "crlf"
66
+ else
67
+ hosts_location = '/etc/hosts'
68
+ copy_proc = Proc.new { `[ -w #{hosts_location} ] && cat #{file} > #{hosts_location} || sudo cp #{file} #{hosts_location}` }
69
+ line_endings = "lf"
70
+ end
71
+
72
+ FileUtils.cp(hosts_location, file)
73
+
74
+ if update_file(file, nil, true, line_endings)
75
+ copy_proc.call
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ def update_file(file, resolving_machine = nil, include_id = true, line_endings)
82
+ file = Pathname.new(file)
83
+ old_file_content = file.read
84
+ new_file_content = update_content(old_file_content, resolving_machine, include_id, line_endings)
85
+ file.open('wb') { |io| io.write(new_file_content) }
86
+ old_file_content != new_file_content
87
+ end
88
+
89
+ def update_content(file_content, resolving_machine, include_id, line_endings)
90
+ id = include_id ? " id: #{read_or_create_id}" : ""
91
+ header = "## vagrant-hostmanager-start#{id}\n"
92
+ footer = "## vagrant-hostmanager-end\n"
93
+ body = get_machines
94
+ .map { |machine| get_hosts_file_entry(machine, resolving_machine) }
95
+ .join
96
+ unless body.empty? #only when there is at least one active machine
97
+ extra_hosts = @config.hostmanager.extra_hosts
98
+ case depth(extra_hosts)
99
+ #allow single defs: config.hostmanager.extra_hosts = ['172.28.128.100', ['db.dev']]
100
+ when 1
101
+ body << extra_hosts
102
+ .map { |ip, aliases| "#{ip}\t#{aliases}"}.join(" ") + "\n\n"
103
+ #and multiple ones, config.hostmanager.extra_hosts = [['172.28.128.100', ['db.dev']], ['0.0.0.0', ['db.io', 'db.dev']] ]
104
+ when 2
105
+ body << extra_hosts
106
+ .map { |ip, aliases| aliases.map{|a| "#{ip}\t#{a}"}.join("\n") + "\n\n" }
107
+ .join
108
+ end
109
+ end
110
+ get_new_content(header, footer, body, file_content, line_endings)
111
+ end
112
+
113
+ def depth(array)
114
+ return 0 unless array.is_a?(Array)
115
+ return 1 + depth(array[0])
116
+ end
117
+
118
+ def get_hosts_file_entry(machine, resolving_machine)
119
+ ip = get_ip_address(machine, resolving_machine)
120
+ host = machine.config.vm.hostname || machine.name
121
+ aliases = machine.config.hostmanager.aliases
122
+ if ip != nil
123
+ "#{ip}\t#{host}\n" + aliases.map{|a| "#{ip}\t#{a}"}.join("\n") + "\n"
124
+ end
125
+ end
126
+
127
+ def get_ip_address(machine, resolving_machine)
128
+ custom_ip_resolver = machine.config.hostmanager.ip_resolver
129
+ if custom_ip_resolver
130
+ custom_ip_resolver.call(machine, resolving_machine)
131
+ else
132
+ ip = nil
133
+ if machine.config.hostmanager.ignore_private_ip != true
134
+ machine.config.vm.networks.each do |network|
135
+ key, options = network[0], network[1]
136
+ ip = options[:ip] if key == :private_network
137
+ break if ip
138
+ end
139
+ end
140
+ ip || (machine.ssh_info ? machine.ssh_info[:host] : nil)
141
+ end
142
+ end
143
+
144
+ def get_machines
145
+ if @config.hostmanager.include_offline?
146
+ machines = @global_env.machine_names
147
+ else
148
+ machines = @global_env.active_machines
149
+ .select { |name, provider| provider == @provider }
150
+ .collect { |name, provider| name }
151
+ end
152
+ # Collect only machines that exist for the current provider
153
+ machines.collect do |name|
154
+ begin
155
+ machine = @global_env.machine(name, @provider)
156
+ rescue Vagrant::Errors::MachineNotFound
157
+ # ignore
158
+ end
159
+ machine
160
+ end
161
+ .reject(&:nil?)
162
+ end
163
+
164
+ def get_new_content(header, footer, body, old_content, line_endings)
165
+ if body.empty?
166
+ block = "\n"
167
+ else
168
+ block = "\n\n" + header + body + footer + "\n"
169
+ end
170
+ # Pattern for finding existing block
171
+ header_pattern = Regexp.quote(header)
172
+ footer_pattern = Regexp.quote(footer)
173
+ pattern = Regexp.new("\n*#{header_pattern}.*?#{footer_pattern}\n*", Regexp::MULTILINE)
174
+ # Replace existing block or append
175
+ content = old_content.match(pattern) ? old_content.sub(pattern, block) : old_content.rstrip + block
176
+ if line_endings == "crlf"
177
+ content.encode(content.encoding, :universal_encoding => true).encode(content.encoding, :crlf_newline => true)
178
+ elsif line_endings == "lf"
179
+ content.encode(content.encoding, :universal_encoding => true)
180
+ else
181
+ content.encode(content.encoding, :universal_encoding => true)
182
+ end
183
+ end
184
+
185
+ def read_or_create_id
186
+ file = Pathname.new("#{@global_env.local_data_path}/hostmanager/id")
187
+ if (file.file?)
188
+ id = file.read.strip
189
+ else
190
+ id = SecureRandom.uuid
191
+ file.dirname.mkpath
192
+ file.open('w') { |io| io.write(id) }
193
+ end
194
+ id
195
+ end
196
+
197
+ ## Windows support for copying files, requesting elevated privileges if necessary
198
+ module WindowsSupport
199
+ require 'rbconfig'
200
+
201
+ def self.windows?
202
+ RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/
203
+ end
204
+
205
+ require 'win32ole' if windows?
206
+
207
+ def windows_copy_file(source, dest)
208
+ begin
209
+ # First, try Ruby copy
210
+ FileUtils.cp(source, dest)
211
+ rescue Errno::EACCES
212
+ # Access denied, try with elevated privileges
213
+ windows_copy_file_elevated(source, dest)
214
+ end
215
+ end
216
+
217
+ private
218
+
219
+ def windows_copy_file_elevated(source, dest)
220
+ # copy command only supports backslashes as separators
221
+ source, dest = [source, dest].map { |s| s.to_s.gsub(/\//, '\\') }
222
+
223
+ # run 'cmd /C copy ...' with elevated privilege, minimized
224
+ copy_cmd = "copy \"#{source}\" \"#{dest}\""
225
+ WIN32OLE.new('Shell.Application').ShellExecute('cmd', "/C #{copy_cmd}", nil, 'runas', 7)
226
+
227
+ # Unfortunately, ShellExecute does not give us a status code,
228
+ # and it is non-blocking so we can't reliably compare the file contents
229
+ # to see if they were copied.
230
+ #
231
+ # If the user rejects the UAC prompt, vagrant will silently continue
232
+ # without updating the hostsfile.
233
+ end
234
+ end
235
+ end
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,44 @@
1
+ require 'vagrant-hostmanager/action'
2
+
3
+ module VagrantPlugins
4
+ module HostManager
5
+ class Plugin < Vagrant.plugin('2')
6
+ name 'HostManager'
7
+ description <<-DESC
8
+ This plugin manages the /etc/hosts file for the host and guest machines.
9
+ An entry is created for each running machine using the hostname attribute.
10
+
11
+ You can also use the hostmanager provisioner to update the hosts file.
12
+ DESC
13
+
14
+ config(:hostmanager) do
15
+ require_relative 'config'
16
+ Config
17
+ end
18
+
19
+ action_hook(:hostmanager, :machine_action_up) do |hook|
20
+ hook.after(Vagrant::Action::Builtin::Provision, Action.update_all)
21
+ end
22
+
23
+ action_hook(:hostmanager, :machine_action_destroy) do |hook|
24
+ hook.prepend(Action.update_all)
25
+ end
26
+
27
+ provisioner(:hostmanager) do
28
+ require_relative 'provisioner'
29
+ Provisioner
30
+ end
31
+
32
+ # Work-around for vagrant >= 1.5
33
+ # It breaks without a provisioner config, so we provide a dummy one
34
+ config(:hostmanager, :provisioner) do
35
+ ::Vagrant::Config::V2::DummyConfig.new
36
+ end
37
+
38
+ command(:hostmanager) do
39
+ require_relative 'command'
40
+ Command
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,24 @@
1
+ require 'vagrant-hostmanager/hosts_file/updater'
2
+
3
+ module VagrantPlugins
4
+ module HostManager
5
+ class Provisioner < Vagrant.plugin('2', :provisioner)
6
+
7
+ def initialize(machine, config)
8
+ super(machine, config)
9
+ global_env = machine.env
10
+ @config = Util.get_config(global_env)
11
+ @updater = HostsFile::Updater.new(global_env, machine.provider_name)
12
+ end
13
+
14
+ def provision
15
+ if @config.hostmanager.manage_guest?
16
+ @updater.update_guest(@machine)
17
+ end
18
+ if @config.hostmanager.manage_host?
19
+ @updater.update_host
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,14 @@
1
+ module VagrantPlugins
2
+ module HostManager
3
+ module Util
4
+ def self.get_config(env)
5
+ # config_global has been removed from v1.5
6
+ if Gem::Version.new(::Vagrant::VERSION) >= Gem::Version.new('1.5')
7
+ env.vagrantfile.config
8
+ else
9
+ env.config_global
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ module VagrantPlugins
2
+ module HostManager
3
+ VERSION = '1.8.7.rc1'
4
+ end
5
+ end
data/locales/en.yml ADDED
@@ -0,0 +1,11 @@
1
+ en:
2
+ vagrant_hostmanager:
3
+ action:
4
+ update_guests: "[vagrant-hostmanager:guests] Updating hosts file on active guest virtual machines..."
5
+ update_guest: "[vagrant-hostmanager:guest] Updating hosts file on the virtual machine %{name}..."
6
+ update_host: "[vagrant-hostmanager:host] Updating hosts file on your workstation (password may be required)..."
7
+ config:
8
+ not_a_bool: "[vagrant-hostmanager:config:error] A value for %{config_key} can only be true or false, not type '%{value}'"
9
+ not_an_array: "[vagrant-hostmanager:config:error] A value for %{config_key} must be an Array, not type '%{is_class}'"
10
+ not_an_array_or_string: "[vagrant-hostmanager:config:error] A value for %{config_key} must be an Array or String, not type '%{is_class}'"
11
+ not_a_proc: "[vagrant-hostmanager:config:error] A value for %{config_key} must be a Proc, not type '%{is_class}'"