Proof-of-concept support for managing /etc/hosts.

Hooks into the up and destroy commands to manage changes to
the /etc/hosts file on active machines.
This commit is contained in:
Shawn Dahlen 2013-03-27 15:29:21 -04:00
commit 665b96c044
15 changed files with 303 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*.gem
pkg
Gemfile.lock
test/.vagrant

7
Gemfile Normal file
View File

@ -0,0 +1,7 @@
source 'https://rubygems.org'
gemspec
group :development do
gem 'vagrant', github: 'mitchellh/vagrant', tag: 'v1.1.2'
end

22
LICENSE.txt Normal file
View File

@ -0,0 +1,22 @@
Copyright (c) 2013 Shawn Dahlen
MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

42
README.md Normal file
View File

@ -0,0 +1,42 @@
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.4.
Installation
------------
Install the plugin following the typical Vagrant 1.1 procedure:
vagrant plugin install vagrant-hostmanager
Usage
-----
The plugin hooks into the `vagrant up` and `vagrant destroy` commands
automatically updating the `/etc/hosts` file on each active machine that
is using the same provider.
A machine's IP address is defined by either the static IP for a private
network configuration or by the SSH host configuration.
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.
Contribute
----------
Contributions are welcome.
1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create new Pull Request

5
bin/build.sh Executable file
View File

@ -0,0 +1,5 @@
rm *.gem
gem uninstall -a vagrant-hostmanager
gem build *.gemspec
gem install *.gem
vagrant plugin install vagrant-hostmanager

12
bin/test.sh Executable file
View File

@ -0,0 +1,12 @@
cd test
vagrant up
echo "[server1] /etc/hosts file:"
vagrant ssh server1 -c 'cat /etc/hosts'
echo "[server2] /etc/hosts file:"
vagrant ssh server2 -c 'cat /etc/hosts'
vagrant destroy server1 -f
echo "[server2] /etc/hosts file:"
vagrant ssh server2 -c 'cat /etc/hosts'
vagrant destroy server2 -f
cd ..

View File

@ -0,0 +1,12 @@
require 'vagrant'
require 'vagrant-hostmanager/plugin'
require 'vagrant-hostmanager/version'
require 'vagrant-hostmanager/errors'
module VagrantPlugins
module HostManager
def self.source_root
@source_root ||= Pathname.new(File.expand_path('../../', __FILE__))
end
end
end

View File

@ -0,0 +1,65 @@
module VagrantPlugins
module HostManager
module Action
class UpdateHostsFile
def initialize(app, env)
@app, @env = app, env
@translator = Helpers::Translator.new('action.update_hosts_file')
@logger =
Log4r::Logger.new('vagrant_hostmanager::action::update')
end
def call(env)
global_env = env[:machine].env
current_provider = env[:machine].provider_name
# build a list of host entries based on active machines that
# are using the same provider as the current one
matching_machines = []
entries = {}
entries['127.0.0.1'] = 'localhost'
global_env.active_machines.each do |name, provider|
if provider == current_provider
machine = global_env.machine(name, provider)
host = machine.config.vm.hostname || name
entries[get_ip_address(machine)] = host
matching_machines << machine
end
end
# generate hosts file
path = env[:tmp_path].join('hosts')
File.open(path, 'w') do |file|
entries.each_pair do |ip, host|
@logger.info "Adding /etc/hosts entry: #{ip} #{host}"
file << "#{ip}\t#{host}\n"
end
end
# copy the hosts file to each matching machine
# TODO append hostname to loopback address
matching_machines.each do |machine|
env[:ui].info @translator.t('update', { :name => machine.name })
machine.communicate.upload(path, '/tmp/hosts')
machine.communicate.sudo("mv /tmp/hosts /etc/hosts")
end
@app.call(env)
end
protected
def get_ip_address(machine)
ip = nil
machine.config.vm.networks.each do |network|
key, options = network[0], network[1]
ip = options[:ip] if key == :private_network
next if ip
end
ip || machine.ssh_info[:host]
end
end
end
end
end

View File

@ -0,0 +1,6 @@
module VagrantPlugins
module HostManager
module Errors
end
end
end

View File

@ -0,0 +1,20 @@
module VagrantPlugins
module HostManager
module Helpers
class Translator
def self.plugin_namespace=(val)
@@plugin_namespace = val
end
def initialize(namespace)
@namespace = namespace
end
def t(keys, opts = {})
value = I18n.t("#{@@plugin_namespace}.#{@namespace}.#{keys}", opts)
opts[:progress] == false ? value : value + "..."
end
end
end
end
end

View File

@ -0,0 +1,64 @@
require 'vagrant-hostmanager/helpers/translator'
require 'vagrant-hostmanager/action/update_hosts_file'
module VagrantPlugins
module HostManager
class Plugin < Vagrant.plugin('2')
name 'HostManager'
description <<-DESC
This plugin manages the /etc/hosts file for guest machines. A entry is
created for each active machine using the hostname attribute.
DESC
action_hook(:hostmanager_up, :machine_action_up) do |hook|
setup_i18n
setup_logging
# TODO use hook.append when defect is fixed within vagrant
hook.after(ProviderVirtualBox::Action::Boot, Action::UpdateHostsFile)
end
action_hook(:hostmanger_destroy, :machine_action_destroy) do |hook|
setup_i18n
setup_logging
# TODO use hook.append when defect is fixed within vagrant
hook.after(
ProviderVirtualBox::Action::DestroyUnusedNetworkInterfaces,
Action::UpdateHostsFile)
end
def self.setup_i18n
I18n.load_path << File.expand_path('locales/en.yml', HostManager.source_root)
I18n.reload!
Helpers::Translator.plugin_namespace = 'vagrant_hostmanager'
end
def self.setup_logging
level = nil
begin
level = Log4r.const_get(ENV["VAGRANT_LOG"].upcase)
rescue NameError
# This means that the logging constant wasn't found,
# which is fine. We just keep `level` as `nil`. But
# we tell the user.
level = nil
end
# Some constants, such as "true" resolve to booleans, so the
# above error checking doesn't catch it. This will check to make
# sure that the log level is an integer, as Log4r requires.
level = nil if !level.is_a?(Integer)
# Set the logging level on all "vagrant" namespaced
# logs as long as we have a valid level.
if level
logger = Log4r::Logger.new("vagrant_hostmanager")
logger.outputters = Log4r::Outputter.stderr
logger.level = level
logger = nil
end
end
end
end
end

View File

@ -0,0 +1,5 @@
module VagrantPlugins
module HostManager
VERSION = '0.0.1'
end
end

5
locales/en.yml Normal file
View File

@ -0,0 +1,5 @@
en:
vagrant_hostmanager:
action:
update_hosts_file:
update: "Updating /etc/hosts file on %{name}"

17
test/Vagrantfile vendored Normal file
View File

@ -0,0 +1,17 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
Vagrant.configure('2') do |config|
config.vm.box = 'precise64-chef11.2'
config.vm.box_url = 'https://opscode-vm.s3.amazonaws.com/vagrant/opscode_ubuntu-12.04_chef-11.2.0.box'
config.vm.define :server1 do |server|
server.vm.hostname = 'fry'
server.vm.network :private_network, :ip => '10.0.5.2'
end
config.vm.define :server2 do |server|
server.vm.hostname = 'bender'
server.vm.network :private_network, :ip => '10.0.5.3'
end
end

View File

@ -0,0 +1,17 @@
# -*- encoding: utf-8 -*-
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'vagrant-hostmanager/version'
Gem::Specification.new do |gem|
gem.name = 'vagrant-hostmanager'
gem.version = VagrantPlugins::HostManager::VERSION
gem.authors = ['Shawn Dahlen']
gem.email = ['shawn@dahlen.me']
gem.description = %q{A Vagrant plugin that manages the /etc/hosts file within a multi-machine environment}
gem.summary = gem.description
gem.files = `git ls-files`.split($/)
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
gem.require_paths = ['lib']
end