Finish support for managing host /etc/hosts file.

This commit moves logic into action middleware and ensures that
config validation is executed when the hostmanager command is called.
This commit is contained in:
Shawn Dahlen 2013-06-20 12:54:20 -04:00
parent 064d9b8658
commit 4ce7cd498e
10 changed files with 176 additions and 84 deletions

View File

@ -1,17 +1,9 @@
Vagrant Host Manager Vagrant Host Manager
==================== ====================
`vagrant-hostmanager` is a Vagrant 1.1+ plugin that manages the `/etc/hosts` `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 file on guest machines (and optionally the host). Its goal is to enable
environments deployed with a cloud provider where IP addresses are not known resolution of multi-machine environments deployed with a cloud provider
in advance. 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.
Installation 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 accordingly. Set the `hostmanager.enabled` attribute to `true` in the
Vagrantfile to activate this behavior. 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 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 network configuration or by the SSH host configuration. To disable
using the private network IP address, set `config.hostmanger.ignore_private_ip` 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 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. 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 If the `hostmanager.include_offline` attribute is set to `true`, boxes that are
private ip configured will be added to the hosts file. You will receive a up or have a private ip configured will be added to the hosts file.
warning on skipped boxes.
In addition, the `hostmanager.aliases` configuration attribute can be used In addition, the `hostmanager.aliases` configuration attribute can be used
to provide aliases for your host names. to provide aliases for your host names.
@ -52,17 +46,18 @@ Example configuration:
```ruby ```ruby
Vagrant.configure("2") do |config| Vagrant.configure("2") do |config|
config.hostmanager.enabled = true config.hostmanager.enabled = true
config.hostmanager.manage_host = true
config.hostmanager.ignore_private_ip = false config.hostmanager.ignore_private_ip = false
config.hostmanager.include_offline = true config.hostmanager.include_offline = true
config.vm.define "example-box" do |node| config.vm.define 'example-box' do |node|
node.vm.hostname = "example-box-hostname" node.vm.hostname = 'example-box-hostname'
node.vm.network :private_network, ip: "192.168.42.42" node.vm.network :private_network, ip: '192.168.42.42'
node.hostmanager.aliases = %w(example-box.localdomain example-box-alias) node.hostmanager.aliases = %w(example-box.localdomain example-box-alias)
end end
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 This allows you to use the provisioning order to ensure that hostmanager
runs before or after provisioning. The provisioner will collect hosts from runs before or after provisioning. The provisioner will collect hosts from
boxes with the same provider as the running box. boxes with the same provider as the running box.

View File

@ -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

View File

@ -3,19 +3,22 @@ require 'vagrant-hostmanager/hosts_file'
module VagrantPlugins module VagrantPlugins
module HostManager module HostManager
module Action module Action
class UpdateHostsFile class UpdateAll
include HostsFile include HostsFile
def initialize(app, env) def initialize(app, env)
@app = app @app = app
@machine = env[:machine] @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 end
def call(env) def call(env)
# check if machine is already active # skip if machine is already active on up action
return @app.call(env) if @machine.id return @app.call(env) if @machine.id && env[:machine_action] == :up
@logger.info 'Continuing update of hosts file for new machine' # 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 # check config to see if the hosts file should be update automatically
return @app.call(env) unless @machine.config.hostmanager.enabled? return @app.call(env) unless @machine.config.hostmanager.enabled?
@ -25,12 +28,17 @@ module VagrantPlugins
# update /etc/hosts file on active machines # update /etc/hosts file on active machines
env[:ui].info I18n.t('vagrant_hostmanager.action.update_guests') 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 # update /etc/hosts files on host if enabled
if @machine.config.hostmanager.manage_host? if @machine.config.hostmanager.manage_host?
env[:ui].info I18n.t('vagrant_hostmanager.action.update_host') env[:ui].info I18n.t('vagrant_hostmanager.action.update_host')
update_host(@machine.env, @machine.provider_name) update_host
end end
end end
end end

View File

@ -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

View File

@ -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

View File

@ -6,7 +6,7 @@ module VagrantPlugins
def execute def execute
options = {} options = {}
opts = OptionParser.new do |o| opts = OptionParser.new do |o|
o.banner = 'Usage: vagrant hostmanager' o.banner = 'Usage: vagrant hostmanager [vm-name]'
o.separator '' o.separator ''
o.version = VagrantPlugins::HostManager::VERSION o.version = VagrantPlugins::HostManager::VERSION
o.program_name = 'vagrant hostmanager' o.program_name = 'vagrant hostmanager'
@ -17,14 +17,22 @@ module VagrantPlugins
end end
end end
parse_options(opts) argv = parse_options(opts)
options[:provider] ||= @env.default_provider options[:provider] ||= @env.default_provider
update_guests(@env, options[:provider]) # update /etc/hosts file for specified guest machines
if (@env.config_global.hostmanager.manage_host?) with_target_vms(argv, options) do |machine|
update_host(@env, options[:provider]) @env.action_runner.run(Action.update_guest, {
end :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 end
end end

View File

@ -3,35 +3,25 @@ require 'tempfile'
module VagrantPlugins module VagrantPlugins
module HostManager module HostManager
module HostsFile module HostsFile
def update_guests(env, provider) def update_guest(machine)
entries = get_entries(env, provider) 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 # download and modify file with Vagrant-managed entries
file = env.tmp_path.join("hosts.#{name}") file = @global_env.tmp_path.join("hosts.#{machine.name}")
target.communicate.download('/etc/hosts', file) machine.communicate.download('/etc/hosts', file)
update_file(file, entries, env.tmp_path) update_file(file)
# upload modified file and remove temporary file # upload modified file and remove temporary file
target.communicate.upload(file, '/tmp/hosts') machine.communicate.upload(file, '/tmp/hosts')
target.communicate.sudo('mv /tmp/hosts /etc/hosts') machine.communicate.sudo('mv /tmp/hosts /etc/hosts')
FileUtils.rm(file) FileUtils.rm(file)
end end
end
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 # 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) FileUtils.cp('/etc/hosts', file)
update_file(file, entries, env.tmp_path) update_file(file)
# copy modified file using sudo for permission # copy modified file using sudo for permission
`sudo cp #{file} /etc/hosts` `sudo cp #{file} /etc/hosts`
@ -39,8 +29,21 @@ module VagrantPlugins
private private
def update_file(file, entries, tmp_path) def update_file(file)
tmp_file = Tempfile.open('hostmanager', tmp_path, 'a') # 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 begin
# copy each line not managed by Vagrant # copy each line not managed by Vagrant
File.open(file).each_line do |line| File.open(file).each_line do |line|
@ -56,22 +59,6 @@ module VagrantPlugins
end end
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) def get_ip_address(machine)
ip = nil ip = nil
if machine.config.hostmanager.ignore_private_ip != true if machine.config.hostmanager.ignore_private_ip != true
@ -84,20 +71,20 @@ module VagrantPlugins
ip || (machine.ssh_info ? machine.ssh_info[:host] : nil) ip || (machine.ssh_info ? machine.ssh_info[:host] : nil)
end end
def get_machines(env, provider) def get_machines
# check if offline machines should be included in host entries # 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 = [] machines = []
env.machine_names.each do |name| @global_env.machine_names.each do |name|
begin begin
env.machine(name, provider) @global_env.machine(name, @provider)
machines << [name, provider] machines << [name, @provider]
rescue Vagrant::Errors::MachineNotFound rescue Vagrant::Errors::MachineNotFound
end end
end end
machines machines
else else
env.active_machines @global_env.active_machines
end end
end end

View File

@ -1,4 +1,4 @@
require 'vagrant-hostmanager/action/update_hosts_file' require 'vagrant-hostmanager/action'
module VagrantPlugins module VagrantPlugins
module HostManager module HostManager
@ -17,11 +17,11 @@ module VagrantPlugins
end end
action_hook(:hostmanager, :machine_action_up) do |hook| action_hook(:hostmanager, :machine_action_up) do |hook|
hook.prepend(Action::UpdateHostsFile) hook.prepend(Action.update_all)
end end
action_hook(:hostmanager, :machine_action_destroy) do |hook| action_hook(:hostmanager, :machine_action_destroy) do |hook|
hook.append(Action::UpdateHostsFile) hook.prepend(Action.update_all)
end end
provisioner(:hostmanager) do provisioner(:hostmanager) do

View File

@ -3,10 +3,16 @@ module VagrantPlugins
class Provisioner < Vagrant.plugin('2', :provisioner) class Provisioner < Vagrant.plugin('2', :provisioner)
include HostsFile include HostsFile
def initialize(machine, config)
super(machine, config)
@global_env = machine.env
@provider = machine.provider_name
end
def provision def provision
update_guests(@machine.env, @machine.provider_name) update_guest(@machine)
if @machine.env.config_global.hostmanager.manage_host? if @global_env.config_global.hostmanager.manage_host?
update_host(@machine.env, @machine.provider_name) update_host
end end
end end
end end

View File

@ -2,6 +2,7 @@ en:
vagrant_hostmanager: vagrant_hostmanager:
action: action:
update_guests: "Updating /etc/hosts file on active guest machines..." 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)..." update_host: "Updating /etc/hosts file on host machine (password may be required)..."
config: config:
not_a_bool: "A value for %{config_key} can only be true or false, not type '%{value}'" not_a_bool: "A value for %{config_key} can only be true or false, not type '%{value}'"