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

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

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

View File

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

View File

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

View File

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

View File

@ -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}'"