diff --git a/README.md b/README.md index 81cd9dd..aa0928e 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,32 @@ Vagrant Host Manager ==================== +[![Gem](https://img.shields.io/gem/v/vagrant-hostmanager.svg)](https://rubygems.org/gems/vagrant-hostmanager) [![Gem](https://img.shields.io/gem/dt/vagrant-hostmanager.svg)](https://rubygems.org/gems/vagrant-hostmanager) [![Gem](https://img.shields.io/gem/dtv/vagrant-hostmanager.svg)](https://rubygems.org/gems/vagrant-hostmanager) [![Twitter](https://img.shields.io/twitter/url/https/github.com/smdahlen/vagrant-hostmanager.svg?style=social)](https://twitter.com/intent/tweet?text=Check%20out%20this%20awesome%20Vagrant%20plugin%21&url=https%3A%2F%2Fgithub.com%2Fsmdahlen%2Fvagrant-hostmanager&hashtags=vagrant%hostmanager&original_referer=) +`vagrant-hostoverseer` is a fork of `vagrant-hostmanager` adding a few extras: +- Manage hostnames of multiple providers, +- Add the option to manages aliases on one line only, +- Add the option of automagically add the fqdn to the alias line. + +The reasons of these patches are: +- In the upstream hostmanager, if you spawn a machine with one provider, the addresses +in all other providers are lost. +- The format of `/etc/hosts` according the man page is that one IP must appear on one line only. +- As there are a few ways to set up fqdn on a server and different tools will +use different ways to get the fqdn, only adding aliases in `/etc/hosts` created some issues, +for instance on AWS. + +I would like to have these patches merged upstream but first I am not sure upstream +considers these contributions worthwhile and second I know that there is at least an +annoying limitation where the fqdn option will work properly only for linux hosts. + +Beyond the command to install the plugin, all references and configurations are +still named hostmanager as my goal is not keep this fork, but have it eventually +properly merged. + `vagrant-hostmanager` is a Vagrant 1.1+ plugin that manages the `/etc/hosts` file on guest machines (and optionally the host). Its goal is to enable resolution of multi-machine environments deployed with a cloud provider @@ -16,17 +38,17 @@ for some providers. Version 1.2 reverts this feature until a suitable implementa supporting all providers is available. ***Potentially breaking change in v1.5.0:*** the running order on `vagrant up` has changed -so that hostmanager runs before provisioning takes place. This ensures all hostnames are -available to the guest when it is being provisioned +so that hostmanager runs before provisioning takes place. This ensures all hostnames are +available to the guest when it is being provisioned (see [#73](https://github.com/smdahlen/vagrant-hostmanager/issues/73)). -Previously, hostmanager would run as the very last action. If you depend on the old behavior, +Previously, hostmanager would run as the very last action. If you depend on the old behavior, see the [provisioner](#provisioner) section. Installation ------------ Install the plugin following the typical Vagrant 1.1 procedure: - $ vagrant plugin install vagrant-hostmanager + $ vagrant plugin install vagrant-oversser Usage ----- @@ -45,6 +67,9 @@ Vagrantfile to activate this behavior. To update the host's `/etc/hosts` file, set the `hostmanager.manage_host` attribute to `true`. +To update the guests' `/etc/hosts` file, set the `hostmanager.manage_guest` +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.hostmanager.ignore_private_ip` @@ -59,12 +84,24 @@ 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. +On some systems, long alias lines have been reported to cause issues +(see [#60](https://github.com/smdahlen/vagrant-hostmanager/issues/60)). +In such cases, you may render aliases on separate lines by setting +```hostmanager.aliases_on_separate_lines = true```. + +If you have all your aliases on one line and you do not manage the fqdn fully +from vagrant (AWS for instance) you might want to add the fqdn as +well on this line to have only one canonical line. In such case, set +```hostmanager.add_current_fqdn = true```. + + Example configuration: ```ruby Vagrant.configure("2") do |config| config.hostmanager.enabled = true config.hostmanager.manage_host = true + config.hostmanager.manage_guest = true config.hostmanager.ignore_private_ip = false config.hostmanager.include_offline = true config.vm.define 'example-box' do |node| @@ -77,9 +114,9 @@ end ### Provisioner -Starting at version 1.5.0, `vagrant up` runs hostmanager before any provisioning occurs. -If you would like hostmanager to run after or during your provisioning stage, -you can use hostmanager as a provisioner. This allows you to use the provisioning +Starting at version 1.5.0, `vagrant up` runs hostmanager before any provisioning occurs. +If you would like hostmanager to run after or during your provisioning stage, +you can use hostmanager as a provisioner. This allows you to use the provisioning order to ensure that hostmanager runs when desired. The provisioner will collect hosts from boxes with the same provider as the running box. @@ -120,13 +157,31 @@ end Passwordless sudo ----------------- -Add the following snippet to the sudoers file (for example, to -```/etc/sudoers.d/vagrant_hostmanager```) to make it stop asking -password when updating hosts file (replace ```/home/user``` with your -actual home directory): +To avoid being asked for the password every time the hosts file is updated, +enable passwordless sudo for the specific command that hostmanager uses to +update the hosts file. + + - Add the following snippet to the sudoers file (e.g. + `/etc/sudoers.d/vagrant_hostmanager`): - Cmnd_Alias VAGRANT_HOSTMANAGER_UPDATE = /bin/cp /home/user/.vagrant.d/tmp/hosts.local /etc/hosts - %sudo ALL=(root) NOPASSWD: VAGRANT_HOSTMANAGER_UPDATE + ``` + Cmnd_Alias VAGRANT_HOSTMANAGER_UPDATE = /bin/cp /.vagrant.d/tmp/hosts.local /etc/hosts + % ALL=(root) NOPASSWD: VAGRANT_HOSTMANAGER_UPDATE + ``` + + Replace `` with your actual home directory (e.g. + `/home/joe`) and `` with the group that is used by the system + for sudo access (usually `sudo` on Debian/Ubuntu systems and `wheel` + on Fedora/Red Hat systems). + + - If necessary, add yourself to the ``: + + ``` + usermod -aG + ``` + + Replace `` with the group that is used by the system for sudo + access (see above) and `` with you user name. Windows support --------------- @@ -148,25 +203,44 @@ and give your user Modify permission. Due to limitations caused by UAC, cancelling out of the UAC prompt will not cause any visible errors, however the ```hosts``` file will not be updated. -Installing development version ------------------------------- - -If you want to install the bleeding version of vagrant-hostmanager (*at your own risk*), you can do the following -(requires ruby and git): - -``` -git clone https://github.com/smdahlen/vagrant-hostmanager.git -cd vagrant-hostmanager -rake gem:build -vagrant plugin install pkg/vagrant-hostmanager-*.gem -``` 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 +To contribute, fork then clone the repository, and then the following: + +**Developing** + +1. Install [Bundler](http://bundler.io/) +2. Currently the Bundler version is locked to 1.6.9, please install this version. + * `sudo gem install bundler -v '1.6.9'` +3. Then install vagrant-hostmanager dependancies: + * `bundle _1.6.9_ install` + +**Testing** + +1. Build and package your newly developed code: + * `rake gem:build` +2. Then install the packaged plugin: + * `vagrant plugin install pkg/vagrant-hostmanager-*.gem` +3. Once you're done testing, roll-back to the latest released version: + * `vagrant plugin uninstall vagrant-hostmanager` + * `vagrant plugin install vagrant-hostmanager` +4. Once you're satisfied developing and testing your new code, please submit a pull request for review. + +**Releasing** + +To release a new version of vagrant-hostmanager you will need to do the following: + +*(only contributors of the GitHub repo and owners of the project at RubyGems will have rights to do this)* + +1. First, bump the version in ~/lib/vagrant-hostmanager/version.rb: + * Follow [Semantic Versioning](http://semver.org/). +2. Then, create a matching GitHub Release (this will also create a tag): + * Preface the version number with a `v`. + * https://github.com/smdahlen/vagrant-hostmanager/releases +3. You will then need to build and push the new gem to RubyGems: + * `rake gem:build` + * `gem push pkg/vagrant-hostmanager-1.6.1.gem` +4. Then, when John Doe runs the following, they will receive the updated vagrant-hostmanager plugin: + * `vagrant plugin update` + * `vagrant plugin update vagrant-hostmanager` diff --git a/lib/vagrant-hostmanager/action/update_all.rb b/lib/vagrant-hostmanager/action/update_all.rb index f7b88ee..ee87734 100644 --- a/lib/vagrant-hostmanager/action/update_all.rb +++ b/lib/vagrant-hostmanager/action/update_all.rb @@ -27,11 +27,13 @@ def call(env) @app.call(env) # update /etc/hosts file on active machines - env[:ui].info I18n.t('vagrant_hostmanager.action.update_guests') - @global_env.active_machines.each do |name, p| - if p == @provider - machine = @global_env.machine(name, p) - @updater.update_guest(machine) + if @machine.config.hostmanager.manage_guest? + env[:ui].info I18n.t('vagrant_hostmanager.action.update_guests') + @global_env.active_machines.each do |name, p| + if p == @provider + machine = @global_env.machine(name, p) + @updater.update_guest(machine) + end end end diff --git a/lib/vagrant-hostmanager/action/update_guest.rb b/lib/vagrant-hostmanager/action/update_guest.rb index 3ca99d9..a957608 100644 --- a/lib/vagrant-hostmanager/action/update_guest.rb +++ b/lib/vagrant-hostmanager/action/update_guest.rb @@ -8,18 +8,22 @@ class UpdateGuest def initialize(app, env) @app = app + global_env = env[:global_env] + @config = Util.get_config(global_env) @machine = env[:machine] @updater = HostsFile::Updater.new(@machine.env, 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 - }) - @updater.update_guest(@machine) + if @config.hostmanager.manage_guest? + env[:ui].info I18n.t('vagrant_hostmanager.action.update_guest', { + :name => @machine.name + }) + @updater.update_guest(@machine) - @app.call(env) + @app.call(env) + end end end end diff --git a/lib/vagrant-hostmanager/command.rb b/lib/vagrant-hostmanager/command.rb index 95dbd8a..7767e3a 100644 --- a/lib/vagrant-hostmanager/command.rb +++ b/lib/vagrant-hostmanager/command.rb @@ -27,6 +27,7 @@ def execute # update /etc/hosts file for specified guest machines with_target_vms(argv, options) do |machine| @env.action_runner.run(Action.update_guest, { + :global_env => @env, :machine => machine, :provider => options[:provider] }) diff --git a/lib/vagrant-hostmanager/config.rb b/lib/vagrant-hostmanager/config.rb index 4e3d3dc..a2716a3 100644 --- a/lib/vagrant-hostmanager/config.rb +++ b/lib/vagrant-hostmanager/config.rb @@ -3,32 +3,41 @@ module HostManager class Config < Vagrant.plugin('2', :config) attr_accessor :enabled attr_accessor :manage_host + attr_accessor :manage_guest attr_accessor :ignore_private_ip attr_accessor :aliases attr_accessor :include_offline attr_accessor :ip_resolver + attr_accessor :aliases_on_separate_lines + attr_accessor :add_current_fqdn alias_method :enabled?, :enabled alias_method :include_offline?, :include_offline alias_method :manage_host?, :manage_host + alias_method :manage_guest?, :manage_guest def initialize @enabled = UNSET_VALUE @manage_host = UNSET_VALUE + @manage_guest = UNSET_VALUE @ignore_private_ip = UNSET_VALUE @include_offline = UNSET_VALUE @aliases = UNSET_VALUE @ip_resolver = UNSET_VALUE + @aliases_on_separate_lines = UNSET_VALUE + @add_current_fqdn = UNSET_VALUE end def finalize! @enabled = false if @enabled == UNSET_VALUE @manage_host = false if @manage_host == UNSET_VALUE + @manage_guest = true if @manage_guest == UNSET_VALUE @ignore_private_ip = false if @ignore_private_ip == UNSET_VALUE @include_offline = false if @include_offline == UNSET_VALUE @aliases = [] if @aliases == UNSET_VALUE @ip_resolver = nil if @ip_resolver == UNSET_VALUE - + @aliases_on_separate_lines = false if @aliases_on_separate_lines == UNSET_VALUE + @add_current_fqdn = false if @add_current_fqdn == UNSET_VALUE @aliases = [ @aliases ].flatten end @@ -37,6 +46,7 @@ def validate(machine) errors << validate_bool('hostmanager.enabled', @enabled) errors << validate_bool('hostmanager.manage_host', @manage_host) + errors << validate_bool('hostmanager.manage_guest', @manage_guest) errors << validate_bool('hostmanager.ignore_private_ip', @ignore_private_ip) errors << validate_bool('hostmanager.include_offline', @include_offline) errors.compact! diff --git a/lib/vagrant-hostmanager/hosts_file/updater.rb b/lib/vagrant-hostmanager/hosts_file/updater.rb index 9514508..39c1e59 100644 --- a/lib/vagrant-hostmanager/hosts_file/updater.rb +++ b/lib/vagrant-hostmanager/hosts_file/updater.rb @@ -10,6 +10,7 @@ def initialize(global_env, provider) @global_env = global_env @config = Util.get_config(@global_env) @provider = provider + @current_machine_config = nil end def update_guest(machine) @@ -17,31 +18,27 @@ def update_guest(machine) if (machine.communicate.test("uname -s | grep SunOS")) realhostfile = '/etc/inet/hosts' - move_cmd = 'mv' elsif (machine.communicate.test("test -d $Env:SystemRoot")) windir = "" machine.communicate.execute("echo %SYSTEMROOT%", {:shell => :cmd}) do |type, contents| windir << contents.gsub("\r\n", '') if type == :stdout end realhostfile = "#{windir}\\System32\\drivers\\etc\\hosts" - move_cmd = 'mv -force' else realhostfile = '/etc/hosts' - move_cmd = 'mv -f' end # download and modify file with Vagrant-managed entries file = @global_env.tmp_path.join("hosts.#{machine.name}") machine.communicate.download(realhostfile, file) + @current_machine_config = ((machine && machine.config) || @config) if update_file(file, machine, false) # upload modified file and remove temporary file machine.communicate.upload(file, '/tmp/hosts') if windir - machine.communicate.sudo("#{move_cmd} /tmp/hosts/hosts.#{machine.name} #{realhostfile}") - elsif machine.communicate.test('test -f /.dockerinit') - machine.communicate.sudo("cat /tmp/hosts > #{realhostfile}") + machine.communicate.sudo("mv -force /tmp/hosts/hosts.#{machine.name} #{realhostfile}") else - machine.communicate.sudo("#{move_cmd} /tmp/hosts #{realhostfile}") + machine.communicate.sudo("cat /tmp/hosts > #{realhostfile}") end end @@ -82,8 +79,8 @@ def update_file(file, resolving_machine = nil, include_id = true) def update_content(file_content, resolving_machine, include_id) id = include_id ? " id: #{read_or_create_id}" : "" - header = "## vagrant-hostmanager-start#{id}\n" - footer = "## vagrant-hostmanager-end\n" + header = "## vagrant-hostmanager-start-#{@provider}#{id}\n" + footer = "## vagrant-hostmanager-end-#{@provider}\n" body = get_machines .map { |machine| get_hosts_file_entry(machine, resolving_machine) } .join @@ -92,10 +89,14 @@ def update_content(file_content, resolving_machine, include_id) def get_hosts_file_entry(machine, resolving_machine) ip = get_ip_address(machine, resolving_machine) - host = machine.config.vm.hostname || machine.name - aliases = machine.config.hostmanager.aliases - if ip != nil - "#{ip}\t#{host}\n" + aliases.map{|a| "#{ip}\t#{a}"}.join("\n") + "\n" + + unless ip.nil? + names = get_names(machine, resolving_machine) + if @current_machine_config.hostmanager.aliases_on_separate_lines + names.map { |a| "#{ip}\t#{a}" }.join("\n") + "\n" + else + "#{ip}\t" + names.join(" ") + "\n" + end end end @@ -116,6 +117,37 @@ def get_ip_address(machine, resolving_machine) end end + # get all names including aliases, in the right order (fqdn first if relevant) + def get_names(machine, resolving_machine) + host = machine.config.vm.hostname || machine.name + aliases = machine.config.hostmanager.aliases + all_names = [host] + aliases + + # Optionally prepend current fqdn as well. Useful with hostname set outside + # vagrant (eg. aws) + if @current_machine_config.hostmanager.add_current_fqdn + dns = get_dns(machine) + # put fqdn in front + all_names = dns + all_names + end + + all_names.uniq # order is kept b uniq + end + + # return fqdn *and* short name + def get_dns(machine) + names = [] + unless machine.nil? + machine.communicate.execute('/bin/hostname -f') do |type, hostname| + names += [hostname.strip] + end + machine.communicate.execute('/bin/hostname') do |type, hostname| + names += [hostname.strip] + end + end + names + end + def get_machines if @config.hostmanager.include_offline? machines = @global_env.machine_names @@ -147,7 +179,39 @@ def get_new_content(header, footer, body, old_content) footer_pattern = Regexp.quote(footer) pattern = Regexp.new("\n*#{header_pattern}.*?#{footer_pattern}\n*", Regexp::MULTILINE) # Replace existing block or append - old_content.match(pattern) ? old_content.sub(pattern, block) : old_content.rstrip + block + newcontent = old_content.match(pattern) ? old_content.sub(pattern, block) : old_content.rstrip + block + + ### remove name duplication in 127.* + cleancontent = '' + + all_names = [] + # First, extract back names from the body. If a name appear there, it + # should not be anywhere else. + body.each_line do |line| + tokens = line.sub(/#.*$/, '').strip.split(/\s+/) + ip = tokens.shift + unless tokens.empty? + all_names += tokens + end + end + + # Then remove those names from any 127.* lines + newcontent.each_line do |line| + if line =~ /^\s*127\./ + # Here be dragons. + tokens = line.sub(/#.*$/, '').strip.split(/\s+/) + ip=tokens.shift + dedup = tokens - all_names + unless dedup.empty? + cleancontent += "#{ip}\t" + dedup.join(' ') + "\n" + end + else + cleancontent += line + end + end + + cleancontent + end def read_or_create_id diff --git a/lib/vagrant-hostmanager/provisioner.rb b/lib/vagrant-hostmanager/provisioner.rb index 1576c73..cbe6f7e 100644 --- a/lib/vagrant-hostmanager/provisioner.rb +++ b/lib/vagrant-hostmanager/provisioner.rb @@ -12,7 +12,9 @@ def initialize(machine, config) end def provision - @updater.update_guest(@machine) + if @config.hostmanager.manage_guest? + @updater.update_guest(@machine) + end if @config.hostmanager.manage_host? @updater.update_host end diff --git a/lib/vagrant-hostmanager/version.rb b/lib/vagrant-hostmanager/version.rb index 2b5cc41..9e371fd 100644 --- a/lib/vagrant-hostmanager/version.rb +++ b/lib/vagrant-hostmanager/version.rb @@ -1,5 +1,5 @@ module VagrantPlugins module HostManager - VERSION = '1.6.1' + VERSION = '1.8.1.0' end end diff --git a/test/Vagrantfile b/test/Vagrantfile index 59ca656..de89dbb 100644 --- a/test/Vagrantfile +++ b/test/Vagrantfile @@ -16,16 +16,21 @@ Vagrant.configure('2') do |config| config.hostmanager.enabled = true config.hostmanager.manage_host = true + config.hostmanager.manage_guest = true + # config.hostmanager.aliases_on_separate_lines = true + config.vm.define :server1 do |server| server.vm.hostname = 'fry' server.vm.network :private_network, :ip => '10.0.5.2' - server.hostmanager.aliases = %w(test-alias) + server.hostmanager.aliases = %w(alias1 alias2) end config.vm.define :server2 do |server| server.vm.hostname = 'bender' server.vm.network :private_network, :ip => '10.0.5.3' + server.hostmanager.aliases = %w(alias3 alias4) + server.hostmanager.aliases_on_separate_lines = true end config.vm.define :server3 do |server| diff --git a/vagrant-hostmanager.gemspec b/vagrant-hostoverseer.gemspec similarity index 71% rename from vagrant-hostmanager.gemspec rename to vagrant-hostoverseer.gemspec index 19f98e4..863651a 100644 --- a/vagrant-hostmanager.gemspec +++ b/vagrant-hostoverseer.gemspec @@ -5,11 +5,12 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'vagrant-hostmanager/version' Gem::Specification.new do |gem| - gem.name = 'vagrant-hostmanager' + gem.name = 'vagrant-hostoverseer' gem.version = VagrantPlugins::HostManager::VERSION - gem.authors = ['Shawn Dahlen'] - gem.email = ['shawn@dahlen.me'] + gem.authors = ['Shawn Dahlen','Seth Reeser','Guillaume'] + gem.email = ['shawn@dahlen.me','info@devopsgroup.io','guillaume@lomig.net'] gem.description = %q{A Vagrant plugin that manages the /etc/hosts file within a multi-machine environment} + gem.homepage = 'https://github.com/lomignet/vagrant-hostoverseer' gem.summary = gem.description gem.license = 'MIT'