Skip to content

Commit

Permalink
Proof-of-concept support for managing /etc/hosts.
Browse files Browse the repository at this point in the history
Hooks into the up and destroy commands to manage changes to
the /etc/hosts file on active machines.
  • Loading branch information
smdahlen committed Mar 27, 2013
0 parents commit 665b96c
Show file tree
Hide file tree
Showing 15 changed files with 303 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
*.gem
pkg
Gemfile.lock
test/.vagrant
7 changes: 7 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
source 'https://rubygems.org'

gemspec

group :development do
gem 'vagrant', github: 'mitchellh/vagrant', tag: 'v1.1.2'
end
22 changes: 22 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -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 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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 changes: 5 additions & 0 deletions bin/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
rm *.gem
gem uninstall -a vagrant-hostmanager
gem build *.gemspec
gem install *.gem
vagrant plugin install vagrant-hostmanager
12 changes: 12 additions & 0 deletions bin/test.sh
Original file line number Diff line number Diff line change
@@ -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 ..

12 changes: 12 additions & 0 deletions lib/vagrant-hostmanager.rb
Original file line number Diff line number Diff line change
@@ -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
65 changes: 65 additions & 0 deletions lib/vagrant-hostmanager/action/update_hosts_file.rb
Original file line number Diff line number Diff line change
@@ -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
6 changes: 6 additions & 0 deletions lib/vagrant-hostmanager/errors.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module VagrantPlugins
module HostManager
module Errors
end
end
end
20 changes: 20 additions & 0 deletions lib/vagrant-hostmanager/helpers/translator.rb
Original file line number Diff line number Diff line change
@@ -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
64 changes: 64 additions & 0 deletions lib/vagrant-hostmanager/plugin.rb
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions lib/vagrant-hostmanager/version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module VagrantPlugins
module HostManager
VERSION = '0.0.1'
end
end
5 changes: 5 additions & 0 deletions locales/en.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
en:
vagrant_hostmanager:
action:
update_hosts_file:
update: "Updating /etc/hosts file on %{name}"
17 changes: 17 additions & 0 deletions test/Vagrantfile
Original file line number Diff line number Diff line change
@@ -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
17 changes: 17 additions & 0 deletions vagrant-hostmanager.gemspec
Original file line number Diff line number Diff line change
@@ -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 = ['[email protected]']
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

0 comments on commit 665b96c

Please sign in to comment.