diff --git a/lib/vagrant-hostmanager/action/update_all.rb b/lib/vagrant-hostmanager/action/update_all.rb index 2791a6e..f7b88ee 100644 --- a/lib/vagrant-hostmanager/action/update_all.rb +++ b/lib/vagrant-hostmanager/action/update_all.rb @@ -1,11 +1,10 @@ -require 'vagrant-hostmanager/hosts_file' +require 'vagrant-hostmanager/hosts_file/updater' require 'vagrant-hostmanager/util' module VagrantPlugins module HostManager module Action class UpdateAll - include HostsFile def initialize(app, env) @app = app @@ -13,6 +12,7 @@ module VagrantPlugins @global_env = @machine.env @provider = @machine.provider_name @config = Util.get_config(@global_env) + @updater = HostsFile::Updater.new(@global_env, @provider) @logger = Log4r::Logger.new('vagrant::hostmanager::update_all') end @@ -31,14 +31,14 @@ module VagrantPlugins @global_env.active_machines.each do |name, p| if p == @provider machine = @global_env.machine(name, p) - update_guest(machine) + @updater.update_guest(machine) end end # update /etc/hosts files on host if enabled if @machine.config.hostmanager.manage_host? env[:ui].info I18n.t('vagrant_hostmanager.action.update_host') - update_host + @updater.update_host end end end diff --git a/lib/vagrant-hostmanager/action/update_guest.rb b/lib/vagrant-hostmanager/action/update_guest.rb index 7d9a34b..3ca99d9 100644 --- a/lib/vagrant-hostmanager/action/update_guest.rb +++ b/lib/vagrant-hostmanager/action/update_guest.rb @@ -1,18 +1,15 @@ -require 'vagrant-hostmanager/hosts_file' +require 'vagrant-hostmanager/hosts_file/updater' require 'vagrant-hostmanager/util' module VagrantPlugins module HostManager module Action class UpdateGuest - include HostsFile def initialize(app, env) @app = app @machine = env[:machine] - @global_env = @machine.env - @provider = env[:provider] - @config = Util.get_config(@global_env) + @updater = HostsFile::Updater.new(@machine.env, env[:provider]) @logger = Log4r::Logger.new('vagrant::hostmanager::update_guest') end @@ -20,7 +17,7 @@ module VagrantPlugins env[:ui].info I18n.t('vagrant_hostmanager.action.update_guest', { :name => @machine.name }) - update_guest(@machine) + @updater.update_guest(@machine) @app.call(env) end diff --git a/lib/vagrant-hostmanager/action/update_host.rb b/lib/vagrant-hostmanager/action/update_host.rb index ae945f8..233e471 100644 --- a/lib/vagrant-hostmanager/action/update_host.rb +++ b/lib/vagrant-hostmanager/action/update_host.rb @@ -1,24 +1,25 @@ -require 'vagrant-hostmanager/hosts_file' +require 'vagrant-hostmanager/hosts_file/updater' require 'vagrant-hostmanager/util' module VagrantPlugins module HostManager module Action class UpdateHost - include HostsFile def initialize(app, env) @app = app - @global_env = env[:global_env] - @provider = env[:provider] - @config = Util.get_config(@global_env) + + global_env = env[:global_env] + @config = Util.get_config(global_env) + @updater = HostsFile::Updater.new(global_env, env[:provider]) + @logger = Log4r::Logger.new('vagrant::hostmanager::update_host') end def call(env) if @config.hostmanager.manage_host? env[:ui].info I18n.t('vagrant_hostmanager.action.update_host') - update_host + @updater.update_host end @app.call(env) @@ -26,5 +27,4 @@ module VagrantPlugins end end end -end - +end \ No newline at end of file diff --git a/lib/vagrant-hostmanager/command.rb b/lib/vagrant-hostmanager/command.rb index a23a91b..5d320c3 100644 --- a/lib/vagrant-hostmanager/command.rb +++ b/lib/vagrant-hostmanager/command.rb @@ -1,7 +1,6 @@ module VagrantPlugins module HostManager class Command < Vagrant.plugin('2', :command) - include HostsFile # Show description when `vagrant list-commands` is triggered def self.synopsis diff --git a/lib/vagrant-hostmanager/hosts_file.rb b/lib/vagrant-hostmanager/hosts_file.rb deleted file mode 100644 index 6934a5d..0000000 --- a/lib/vagrant-hostmanager/hosts_file.rb +++ /dev/null @@ -1,195 +0,0 @@ -require 'tempfile' - -module VagrantPlugins - module HostManager - module HostsFile - def update_guest(machine) - return unless machine.communicate.ready? - - if (machine.communicate.test("uname -s | grep SunOS")) - realhostfile = '/etc/inet/hosts' - move_cmd = 'mv' - elsif (machine.communicate.test("test -d $Env:SystemRoot")) - windir = "" - machine.communicate.execute("echo %SYSTEMROOT%", {:shell => :cmd}) do |type, contents| - windir << contents.gsub("\r\n", '') if type == :stdout - end - realhostfile = "#{windir}\\System32\\drivers\\etc\\hosts" - move_cmd = 'mv -force' - else - realhostfile = '/etc/hosts' - move_cmd = 'mv -f' - end - # download and modify file with Vagrant-managed entries - file = @global_env.tmp_path.join("hosts.#{machine.name}") - machine.communicate.download(realhostfile, file) - if update_file(file, machine, false) - - # upload modified file and remove temporary file - machine.communicate.upload(file, '/tmp/hosts') - machine.communicate.sudo("#{move_cmd} /tmp/hosts #{realhostfile}") - end - - # i have no idea if this is a windows competibility issue or not, but sometimes it dosen't work on my machine - begin - FileUtils.rm(file) - rescue Exception => e - end - end - - def update_host - # copy and modify hosts file on host with Vagrant-managed entries - file = @global_env.tmp_path.join('hosts.local') - - if WindowsSupport.windows? - # lazily include windows Module - class << self - include WindowsSupport unless include? WindowsSupport - end - - hosts_location = "#{ENV['WINDIR']}\\System32\\drivers\\etc\\hosts" - copy_proc = Proc.new { windows_copy_file(file, hosts_location) } - else - hosts_location = '/etc/hosts' - copy_proc = Proc.new { `sudo cp #{file} #{hosts_location}` } - end - - FileUtils.cp(hosts_location, file) - if update_file(file) - copy_proc.call - end - end - - private - - def update_file(file, resolving_machine = nil, include_id = true) - file = Pathname.new(file) - old_file_content = file.read - new_file_content = update_content(old_file_content, resolving_machine, include_id) - file.open('w') { |io| io.write(new_file_content) } - old_file_content != new_file_content - end - - def update_content(file_content, resolving_machine, include_id) - id = include_id ? " id: #{read_or_create_id}" : "" - header = "## vagrant-hostmanager-start#{id}\n" - footer = "## vagrant-hostmanager-end\n" - body = get_machines - .map { |machine| get_hosts_file_entry(machine, resolving_machine) } - .join - get_new_content(header, footer, body, file_content) - end - - def get_hosts_file_entry(machine, resolving_machine) - ip = get_ip_address(machine, resolving_machine) - host = machine.config.vm.hostname || machine.name - aliases = machine.config.hostmanager.aliases - if ip != nil - "#{ip}\t#{host}\n" + aliases.map{|a| "#{ip}\t#{a}"}.join("\n") + "\n" - end - end - - def get_ip_address(machine, resolving_machine) - custom_ip_resolver = machine.config.hostmanager.ip_resolver - if custom_ip_resolver - custom_ip_resolver.call(machine, resolving_machine) - else - ip = nil - if machine.config.hostmanager.ignore_private_ip != true - machine.config.vm.networks.each do |network| - key, options = network[0], network[1] - ip = options[:ip] if key == :private_network - break if ip - end - end - ip || (machine.ssh_info ? machine.ssh_info[:host] : nil) - end - end - - def get_machines - if @config.hostmanager.include_offline? - machines = @global_env.machine_names - else - machines = @global_env.active_machines - .select { |name, provider| provider == @provider } - .collect { |name, provider| name } - end - # Collect only machines that exist for the current provider - machines.collect do |name| - begin - machine = @global_env.machine(name, @provider) - rescue Vagrant::Errors::MachineNotFound - # ignore - end - machine - end - .reject(&:nil?) - end - - def get_new_content(header, footer, body, old_content) - if body.empty? - block = "\n" - else - block = "\n\n" + header + body + footer + "\n" - end - # Pattern for finding existing block - header_pattern = Regexp.quote(header) - footer_pattern = Regexp.quote(footer) - pattern = Regexp.new("\n*#{header_pattern}.*?#{footer_pattern}\n*", Regexp::MULTILINE) - # Replace existing block or append - old_content.match(pattern) ? old_content.sub(pattern, block) : old_content.rstrip + block - end - - def read_or_create_id - file = Pathname.new("#{@global_env.local_data_path}/hostmanager/id") - if (file.file?) - id = file.read.strip - else - id = SecureRandom.uuid - file.dirname.mkpath - file.open('w') { |io| io.write(id) } - end - id - end - - ## Windows support for copying files, requesting elevated privileges if necessary - module WindowsSupport - require 'rbconfig' - - def self.windows? - RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/ - end - - require 'win32ole' if windows? - - def windows_copy_file(source, dest) - begin - # First, try Ruby copy - FileUtils.cp(source, dest) - rescue Errno::EACCES - # Access denied, try with elevated privileges - windows_copy_file_elevated(source, dest) - end - end - - private - - def windows_copy_file_elevated(source, dest) - # copy command only supports backslashes as separators - source, dest = [source, dest].map { |s| s.to_s.gsub(/\//, '\\') } - - # run 'cmd /C copy ...' with elevated privilege, minimized - copy_cmd = "copy \"#{source}\" \"#{dest}\"" - WIN32OLE.new('Shell.Application').ShellExecute('cmd', "/C #{copy_cmd}", nil, 'runas', 7) - - # Unfortunately, ShellExecute does not give us a status code, - # and it is non-blocking so we can't reliably compare the file contents - # to see if they were copied. - # - # If the user rejects the UAC prompt, vagrant will silently continue - # without updating the hostsfile. - end - end - end - end -end diff --git a/lib/vagrant-hostmanager/hosts_file/updater.rb b/lib/vagrant-hostmanager/hosts_file/updater.rb new file mode 100644 index 0000000..3b24260 --- /dev/null +++ b/lib/vagrant-hostmanager/hosts_file/updater.rb @@ -0,0 +1,205 @@ +require 'tempfile' + +module VagrantPlugins + module HostManager + module HostsFile + + class Updater + + def initialize(global_env, provider) + @global_env = global_env + @config = Util.get_config(@global_env) + @provider = provider + end + + def update_guest(machine) + return unless machine.communicate.ready? + + if (machine.communicate.test("uname -s | grep SunOS")) + realhostfile = '/etc/inet/hosts' + move_cmd = 'mv' + elsif (machine.communicate.test("test -d $Env:SystemRoot")) + windir = "" + machine.communicate.execute("echo %SYSTEMROOT%", {:shell => :cmd}) do |type, contents| + windir << contents.gsub("\r\n", '') if type == :stdout + end + realhostfile = "#{windir}\\System32\\drivers\\etc\\hosts" + move_cmd = 'mv -force' + else + realhostfile = '/etc/hosts' + move_cmd = 'mv -f' + end + # download and modify file with Vagrant-managed entries + file = @global_env.tmp_path.join("hosts.#{machine.name}") + machine.communicate.download(realhostfile, file) + if update_file(file, machine, false) + + # upload modified file and remove temporary file + machine.communicate.upload(file, '/tmp/hosts') + machine.communicate.sudo("#{move_cmd} /tmp/hosts #{realhostfile}") + end + + # i have no idea if this is a windows competibility issue or not, but sometimes it dosen't work on my machine + begin + FileUtils.rm(file) + rescue Exception => e + end + end + + def update_host + # copy and modify hosts file on host with Vagrant-managed entries + file = @global_env.tmp_path.join('hosts.local') + + if WindowsSupport.windows? + # lazily include windows Module + class << self + include WindowsSupport unless include? WindowsSupport + end + + hosts_location = "#{ENV['WINDIR']}\\System32\\drivers\\etc\\hosts" + copy_proc = Proc.new { windows_copy_file(file, hosts_location) } + else + hosts_location = '/etc/hosts' + copy_proc = Proc.new { `sudo cp #{file} #{hosts_location}` } + end + + FileUtils.cp(hosts_location, file) + if update_file(file) + copy_proc.call + end + end + + private + + def update_file(file, resolving_machine = nil, include_id = true) + file = Pathname.new(file) + old_file_content = file.read + new_file_content = update_content(old_file_content, resolving_machine, include_id) + file.open('w') { |io| io.write(new_file_content) } + old_file_content != new_file_content + end + + def update_content(file_content, resolving_machine, include_id) + id = include_id ? " id: #{read_or_create_id}" : "" + header = "## vagrant-hostmanager-start#{id}\n" + footer = "## vagrant-hostmanager-end\n" + body = get_machines + .map { |machine| get_hosts_file_entry(machine, resolving_machine) } + .join + get_new_content(header, footer, body, file_content) + end + + def get_hosts_file_entry(machine, resolving_machine) + ip = get_ip_address(machine, resolving_machine) + host = machine.config.vm.hostname || machine.name + aliases = machine.config.hostmanager.aliases + if ip != nil + "#{ip}\t#{host}\n" + aliases.map{|a| "#{ip}\t#{a}"}.join("\n") + "\n" + end + end + + def get_ip_address(machine, resolving_machine) + custom_ip_resolver = machine.config.hostmanager.ip_resolver + if custom_ip_resolver + custom_ip_resolver.call(machine, resolving_machine) + else + ip = nil + if machine.config.hostmanager.ignore_private_ip != true + machine.config.vm.networks.each do |network| + key, options = network[0], network[1] + ip = options[:ip] if key == :private_network + break if ip + end + end + ip || (machine.ssh_info ? machine.ssh_info[:host] : nil) + end + end + + def get_machines + if @config.hostmanager.include_offline? + machines = @global_env.machine_names + else + machines = @global_env.active_machines + .select { |name, provider| provider == @provider } + .collect { |name, provider| name } + end + # Collect only machines that exist for the current provider + machines.collect do |name| + begin + machine = @global_env.machine(name, @provider) + rescue Vagrant::Errors::MachineNotFound + # ignore + end + machine + end + .reject(&:nil?) + end + + def get_new_content(header, footer, body, old_content) + if body.empty? + block = "\n" + else + block = "\n\n" + header + body + footer + "\n" + end + # Pattern for finding existing block + header_pattern = Regexp.quote(header) + footer_pattern = Regexp.quote(footer) + pattern = Regexp.new("\n*#{header_pattern}.*?#{footer_pattern}\n*", Regexp::MULTILINE) + # Replace existing block or append + old_content.match(pattern) ? old_content.sub(pattern, block) : old_content.rstrip + block + end + + def read_or_create_id + file = Pathname.new("#{@global_env.local_data_path}/hostmanager/id") + if (file.file?) + id = file.read.strip + else + id = SecureRandom.uuid + file.dirname.mkpath + file.open('w') { |io| io.write(id) } + end + id + end + + ## Windows support for copying files, requesting elevated privileges if necessary + module WindowsSupport + require 'rbconfig' + + def self.windows? + RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/ + end + + require 'win32ole' if windows? + + def windows_copy_file(source, dest) + begin + # First, try Ruby copy + FileUtils.cp(source, dest) + rescue Errno::EACCES + # Access denied, try with elevated privileges + windows_copy_file_elevated(source, dest) + end + end + + private + + def windows_copy_file_elevated(source, dest) + # copy command only supports backslashes as separators + source, dest = [source, dest].map { |s| s.to_s.gsub(/\//, '\\') } + + # run 'cmd /C copy ...' with elevated privilege, minimized + copy_cmd = "copy \"#{source}\" \"#{dest}\"" + WIN32OLE.new('Shell.Application').ShellExecute('cmd', "/C #{copy_cmd}", nil, 'runas', 7) + + # Unfortunately, ShellExecute does not give us a status code, + # and it is non-blocking so we can't reliably compare the file contents + # to see if they were copied. + # + # If the user rejects the UAC prompt, vagrant will silently continue + # without updating the hostsfile. + end + end + end + end + end +end diff --git a/lib/vagrant-hostmanager/provisioner.rb b/lib/vagrant-hostmanager/provisioner.rb index 27f0b33..1576c73 100644 --- a/lib/vagrant-hostmanager/provisioner.rb +++ b/lib/vagrant-hostmanager/provisioner.rb @@ -1,19 +1,20 @@ +require 'vagrant-hostmanager/hosts_file/updater' + module VagrantPlugins module HostManager class Provisioner < Vagrant.plugin('2', :provisioner) - include HostsFile def initialize(machine, config) super(machine, config) - @global_env = machine.env - @provider = machine.provider_name - @config = Util.get_config(@global_env) + global_env = machine.env + @config = Util.get_config(global_env) + @updater = HostsFile::Updater.new(global_env, machine.provider_name) end def provision - update_guest(@machine) + @updater.update_guest(@machine) if @config.hostmanager.manage_host? - update_host + @updater.update_host end end end