diff --git a/README.md b/README.md index 0f11188..72bc72c 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,9 @@ Vagrant Host Manager ==================== `vagrant-hostmanager` is a Vagrant 1.1+ plugin that manages the `/etc/hosts` -file on guest machines. Its goal is to enable resolution of multi-machine -environments deployed with a cloud provider where IP addresses are not known -in advance. - -Status ------- -The current implementation is a proof-of-concept supporting the larger -objective of using Vagrant as a cloud management interface for development -and production environments. - -The plugin has been tested with Vagrant 1.1.5. +file on guest machines (and optionally the host). Its goal is to enable +resolution of multi-machine environments deployed with a cloud provider +where IP addresses are not known in advance. Installation ------------ @@ -32,6 +24,9 @@ machines with the same provider will have their `/etc/hosts` file updated accordingly. Set the `hostmanager.enabled` attribute to `true` in the Vagrantfile to activate this behavior. +To update the host's `/etc/hosts` file, set the `hostmanager.manage_host` +attribute to `true`. + A machine's IP address is defined by either the static IP for a private network configuration or by the SSH host configuration. To disable using the private network IP address, set `config.hostmanger.ignore_private_ip` @@ -40,9 +35,8 @@ to true. A machine's host name is defined by `config.vm.hostname`. If this is not set, it falls back to the symbol defining the machine in the Vagrantfile. -When using include_offline set to true, only boxes that are up or have a -private ip configured will be added to the hosts file. You will receive a -warning on skipped boxes. +If the `hostmanager.include_offline` attribute is set to `true`, boxes that are +up or have a private ip configured will be added to the hosts file. In addition, the `hostmanager.aliases` configuration attribute can be used to provide aliases for your host names. @@ -52,17 +46,18 @@ Example configuration: ```ruby Vagrant.configure("2") do |config| config.hostmanager.enabled = true + config.hostmanager.manage_host = true config.hostmanager.ignore_private_ip = false config.hostmanager.include_offline = true - config.vm.define "example-box" do |node| - node.vm.hostname = "example-box-hostname" - node.vm.network :private_network, ip: "192.168.42.42" + config.vm.define 'example-box' do |node| + node.vm.hostname = 'example-box-hostname' + node.vm.network :private_network, ip: '192.168.42.42' node.hostmanager.aliases = %w(example-box.localdomain example-box-alias) end end ``` -As a last option, you can also choose hostmanager as a provisioner. +As a last option, you can use hostmanager as a provisioner. This allows you to use the provisioning order to ensure that hostmanager runs before or after provisioning. The provisioner will collect hosts from boxes with the same provider as the running box. diff --git a/lib/vagrant-hostmanager/action.rb b/lib/vagrant-hostmanager/action.rb new file mode 100644 index 0000000..419ec2a --- /dev/null +++ b/lib/vagrant-hostmanager/action.rb @@ -0,0 +1,31 @@ +require 'vagrant-hostmanager/action/update_all' +require 'vagrant-hostmanager/action/update_guest' +require 'vagrant-hostmanager/action/update_host' + +module VagrantPlugins + module HostManager + module Action + include Vagrant::Action::Builtin + + def self.update_all + Vagrant::Action::Builder.new.tap do |builder| + builder.use ConfigValidate + builder.use UpdateAll + end + end + + def self.update_guest + Vagrant::Action::Builder.new.tap do |builder| + builder.use ConfigValidate + builder.use UpdateGuest + end + end + + def self.update_host + Vagrant::Action::Builder.new.tap do |builder| + builder.use UpdateHost + end + end + end + end +end diff --git a/lib/vagrant-hostmanager/action/update_hosts_file.rb b/lib/vagrant-hostmanager/action/update_all.rb similarity index 59% rename from lib/vagrant-hostmanager/action/update_hosts_file.rb rename to lib/vagrant-hostmanager/action/update_all.rb index 34266ba..eb4d6b2 100644 --- a/lib/vagrant-hostmanager/action/update_hosts_file.rb +++ b/lib/vagrant-hostmanager/action/update_all.rb @@ -3,19 +3,22 @@ require 'vagrant-hostmanager/hosts_file' module VagrantPlugins module HostManager module Action - class UpdateHostsFile + class UpdateAll include HostsFile def initialize(app, env) @app = app @machine = env[:machine] - @logger = Log4r::Logger.new('vagrant::hostmanager::update_hosts_file') + @global_env = @machine.env + @provider = @machine.provider_name + @logger = Log4r::Logger.new('vagrant::hostmanager::update_all') end def call(env) - # check if machine is already active - return @app.call(env) if @machine.id - @logger.info 'Continuing update of hosts file for new machine' + # skip if machine is already active on up action + return @app.call(env) if @machine.id && env[:machine_action] == :up + # skip if machine is not active on destroy action + return @app.call(env) if !@machine.id && env[:machine_action] == :destroy # check config to see if the hosts file should be update automatically return @app.call(env) unless @machine.config.hostmanager.enabled? @@ -25,12 +28,17 @@ module VagrantPlugins # update /etc/hosts file on active machines env[:ui].info I18n.t('vagrant_hostmanager.action.update_guests') - update_guests(@machine.env, @machine.provider_name) + @global_env.active_machines.each do |name, p| + if p == @provider + machine = @global_env.machine(name, p) + 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(@machine.env, @machine.provider_name) + update_host end end end diff --git a/lib/vagrant-hostmanager/action/update_guest.rb b/lib/vagrant-hostmanager/action/update_guest.rb new file mode 100644 index 0000000..73a7cb1 --- /dev/null +++ b/lib/vagrant-hostmanager/action/update_guest.rb @@ -0,0 +1,28 @@ +require 'vagrant-hostmanager/hosts_file' + +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] + @logger = Log4r::Logger.new('vagrant::hostmanager::update_guest') + end + + def call(env) + env[:ui].info I18n.t('vagrant_hostmanager.action.update_guest', { + :name => @machine.name + }) + update_guest(@machine) + + @app.call(env) + end + end + end + end +end diff --git a/lib/vagrant-hostmanager/action/update_host.rb b/lib/vagrant-hostmanager/action/update_host.rb new file mode 100644 index 0000000..5418953 --- /dev/null +++ b/lib/vagrant-hostmanager/action/update_host.rb @@ -0,0 +1,28 @@ +require 'vagrant-hostmanager/hosts_file' + +module VagrantPlugins + module HostManager + module Action + class UpdateHost + include HostsFile + + def initialize(app, env) + @app = app + @global_env = env[:global_env] + @provider = env[:provider] + @logger = Log4r::Logger.new('vagrant::hostmanager::update_host') + end + + def call(env) + if @global_env.config_global.hostmanager.manage_host? + env[:ui].info I18n.t('vagrant_hostmanager.action.update_host') + update_host + end + + @app.call(env) + end + end + end + end +end + diff --git a/lib/vagrant-hostmanager/command.rb b/lib/vagrant-hostmanager/command.rb index 415af03..1634678 100644 --- a/lib/vagrant-hostmanager/command.rb +++ b/lib/vagrant-hostmanager/command.rb @@ -6,7 +6,7 @@ module VagrantPlugins def execute options = {} opts = OptionParser.new do |o| - o.banner = 'Usage: vagrant hostmanager' + o.banner = 'Usage: vagrant hostmanager [vm-name]' o.separator '' o.version = VagrantPlugins::HostManager::VERSION o.program_name = 'vagrant hostmanager' @@ -17,14 +17,22 @@ module VagrantPlugins end end - parse_options(opts) - + argv = parse_options(opts) options[:provider] ||= @env.default_provider - update_guests(@env, options[:provider]) - if (@env.config_global.hostmanager.manage_host?) - update_host(@env, options[:provider]) + # update /etc/hosts file for specified guest machines + with_target_vms(argv, options) do |machine| + @env.action_runner.run(Action.update_guest, { + :machine => machine, + :provider => options[:provider] + }) end + + # update /etc/hosts file for host + @env.action_runner.run(Action.update_host, { + :global_env => @env, + :provider => options[:provider] + }) end end end diff --git a/lib/vagrant-hostmanager/hosts_file.rb b/lib/vagrant-hostmanager/hosts_file.rb index 8699fff..b694c67 100644 --- a/lib/vagrant-hostmanager/hosts_file.rb +++ b/lib/vagrant-hostmanager/hosts_file.rb @@ -3,35 +3,25 @@ require 'tempfile' module VagrantPlugins module HostManager module HostsFile - def update_guests(env, provider) - entries = get_entries(env, provider) + def update_guest(machine) + return unless machine.communicate.ready? - # update hosts file on each active machine with matching provider - env.active_machines.each do |name, p| - if provider == p - target = env.machine(name, p) - next unless target.communicate.ready? + # download and modify file with Vagrant-managed entries + file = @global_env.tmp_path.join("hosts.#{machine.name}") + machine.communicate.download('/etc/hosts', file) + update_file(file) - # download and modify file with Vagrant-managed entries - file = env.tmp_path.join("hosts.#{name}") - target.communicate.download('/etc/hosts', file) - update_file(file, entries, env.tmp_path) - - # upload modified file and remove temporary file - target.communicate.upload(file, '/tmp/hosts') - target.communicate.sudo('mv /tmp/hosts /etc/hosts') - FileUtils.rm(file) - end - end + # upload modified file and remove temporary file + machine.communicate.upload(file, '/tmp/hosts') + machine.communicate.sudo('mv /tmp/hosts /etc/hosts') + FileUtils.rm(file) end - def update_host(env, provider) - entries = get_entries(env, provider) - + def update_host # copy and modify hosts file on host with Vagrant-managed entries - file = env.tmp_path.join('hosts.local') + file = @global_env.tmp_path.join('hosts.local') FileUtils.cp('/etc/hosts', file) - update_file(file, entries, env.tmp_path) + update_file(file) # copy modified file using sudo for permission `sudo cp #{file} /etc/hosts` @@ -39,8 +29,21 @@ module VagrantPlugins private - def update_file(file, entries, tmp_path) - tmp_file = Tempfile.open('hostmanager', tmp_path, 'a') + def update_file(file) + # build array of host file entries from Vagrant configuration + entries = [] + get_machines.each do |name, p| + if @provider == p + machine = @global_env.machine(name, p) + host = machine.config.vm.hostname || name + id = machine.id + ip = get_ip_address(machine) + aliases = machine.config.hostmanager.aliases.join(' ').chomp + entries << "#{ip}\t#{host} #{aliases}\t# VAGRANT ID: #{id}\n" + end + end + + tmp_file = Tempfile.open('hostmanager', @global_env.tmp_path, 'a') begin # copy each line not managed by Vagrant File.open(file).each_line do |line| @@ -56,22 +59,6 @@ module VagrantPlugins end end - def get_entries(env, provider) - entries = [] - get_machines(env, provider).each do |name, p| - if provider == p - machine = env.machine(name, p) - host = machine.config.vm.hostname || name - id = machine.id - ip = get_ip_address(machine) - aliases = machine.config.hostmanager.aliases.join(' ').chomp - entries << "#{ip}\t#{host} #{aliases}\t# VAGRANT ID: #{id}\n" - end - end - - entries - end - def get_ip_address(machine) ip = nil if machine.config.hostmanager.ignore_private_ip != true @@ -84,20 +71,20 @@ module VagrantPlugins ip || (machine.ssh_info ? machine.ssh_info[:host] : nil) end - def get_machines(env, provider) + def get_machines # check if offline machines should be included in host entries - if env.config_global.hostmanager.include_offline? + if @global_env.config_global.hostmanager.include_offline? machines = [] - env.machine_names.each do |name| + @global_env.machine_names.each do |name| begin - env.machine(name, provider) - machines << [name, provider] + @global_env.machine(name, @provider) + machines << [name, @provider] rescue Vagrant::Errors::MachineNotFound end end machines else - env.active_machines + @global_env.active_machines end end diff --git a/lib/vagrant-hostmanager/plugin.rb b/lib/vagrant-hostmanager/plugin.rb index 820b3b7..935efc1 100644 --- a/lib/vagrant-hostmanager/plugin.rb +++ b/lib/vagrant-hostmanager/plugin.rb @@ -1,4 +1,4 @@ -require 'vagrant-hostmanager/action/update_hosts_file' +require 'vagrant-hostmanager/action' module VagrantPlugins module HostManager @@ -17,11 +17,11 @@ module VagrantPlugins end action_hook(:hostmanager, :machine_action_up) do |hook| - hook.prepend(Action::UpdateHostsFile) + hook.prepend(Action.update_all) end action_hook(:hostmanager, :machine_action_destroy) do |hook| - hook.append(Action::UpdateHostsFile) + hook.prepend(Action.update_all) end provisioner(:hostmanager) do diff --git a/lib/vagrant-hostmanager/provisioner.rb b/lib/vagrant-hostmanager/provisioner.rb index d726636..4c5397e 100644 --- a/lib/vagrant-hostmanager/provisioner.rb +++ b/lib/vagrant-hostmanager/provisioner.rb @@ -3,10 +3,16 @@ module VagrantPlugins class Provisioner < Vagrant.plugin('2', :provisioner) include HostsFile + def initialize(machine, config) + super(machine, config) + @global_env = machine.env + @provider = machine.provider_name + end + def provision - update_guests(@machine.env, @machine.provider_name) - if @machine.env.config_global.hostmanager.manage_host? - update_host(@machine.env, @machine.provider_name) + update_guest(@machine) + if @global_env.config_global.hostmanager.manage_host? + update_host end end end diff --git a/locales/en.yml b/locales/en.yml index 17d2858..e70efc9 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -2,6 +2,7 @@ en: vagrant_hostmanager: action: update_guests: "Updating /etc/hosts file on active guest machines..." + update_guest: "[%{name}] Updating /etc/hosts file..." update_host: "Updating /etc/hosts file on host machine (password may be required)..." config: not_a_bool: "A value for %{config_key} can only be true or false, not type '%{value}'"