diff --git a/plugins/guests/debian/cap/change_host_name.rb b/plugins/guests/debian/cap/change_host_name.rb index d75d45d50..8b563cc21 100644 --- a/plugins/guests/debian/cap/change_host_name.rb +++ b/plugins/guests/debian/cap/change_host_name.rb @@ -3,33 +3,79 @@ module VagrantPlugins module Cap class ChangeHostName def self.change_host_name(machine, name) - machine.communicate.tap do |comm| + new(machine, name).change! + end - # Get the current hostname - # if existing fqdn setup improperly, this returns just hostname - old = '' - comm.sudo "hostname -f" do |type, data| - if type == :stdout - old = data.chomp - end - end + attr_reader :machine, :new_hostname - # this works even if they're not both fqdn - if old.split('.')[0] != name.split('.')[0] + def initialize(machine, new_hostname) + @machine = machine + @new_hostname = new_hostname + end - comm.sudo("sed -i 's/.*$/#{name.split('.')[0]}/' /etc/hostname") + def change! + return unless should_change? - # hosts should resemble: - # 127.0.0.1 localhost host.fqdn.com host - # 127.0.1.1 host.fqdn.com host - comm.sudo("sed -ri 's@^(([0-9]{1,3}\.){3}[0-9]{1,3})\\s+(localhost)\\b.*$@\\1\\t\\3 #{name} #{name.split('.')[0]}@g' /etc/hosts") - comm.sudo("sed -ri 's@^(([0-9]{1,3}\.){3}[0-9]{1,3})\\s+(#{old.split('.')[0]})\\b.*$@\\1\\t#{name} #{name.split('.')[0]}@g' /etc/hosts") + update_etc_hostname + update_etc_hosts + refresh_hostname_service + update_mailname + renew_dhcp + end - comm.sudo("hostname -F /etc/hostname") - comm.sudo("hostname --fqdn > /etc/mailname") - comm.sudo("ifdown -a; ifup -a; ifup eth0") - end + def should_change? + new_hostname != current_hostname + end + + def current_hostname + @current_hostname ||= get_current_hostname + end + + def get_current_hostname + sudo "hostname -f" do |type, data| + return data.chomp if type == :stdout end + '' + end + + def update_etc_hostname + sudo("echo '#{short_hostname}' > /etc/hostname") + end + + # /etc/hosts should resemble: + # 127.0.0.1 localhost + # 127.0.1.1 host.fqdn.com host.fqdn host + def update_etc_hosts + ip_address = '([0-9]{1,3}\.){3}[0-9]{1,3}' + search = "^(#{ip_address})\\s+#{current_hostname}\\b.*$" + replace = "\\1\\t#{fqdn} #{short_hostname}" + expression = ['s', search, replace, 'g'].join('@') + + sudo("sed -ri '#{expression}' /etc/hosts") + end + + def refresh_hostname_service + sudo("hostname -F /etc/hostname") + end + + def update_mailname + sudo("hostname --fqdn > /etc/mailname") + end + + def renew_dhcp + sudo("ifdown -a; ifup -a; ifup eth0") + end + + def fqdn + new_hostname + end + + def short_hostname + new_hostname.split('.').first + end + + def sudo(cmd, &block) + machine.communicate.sudo(cmd, &block) end end end diff --git a/plugins/guests/ubuntu/cap/change_host_name.rb b/plugins/guests/ubuntu/cap/change_host_name.rb index 9eef21756..beb84d739 100644 --- a/plugins/guests/ubuntu/cap/change_host_name.rb +++ b/plugins/guests/ubuntu/cap/change_host_name.rb @@ -1,46 +1,27 @@ module VagrantPlugins module GuestUbuntu module Cap - class ChangeHostName + class ChangeHostName < VagrantPlugins::GuestDebian::Cap::ChangeHostName def self.change_host_name(machine, name) - machine.communicate.tap do |comm| + super + end - # Get the current hostname - # if existing fqdn setup improperly, this returns just hostname - old = '' - comm.sudo "hostname -f" do |type, data| - if type == :stdout - old = data.chomp - end - end - - # this works even if they're not both fqdn - if old.split('.')[0] != name.split('.')[0] - - comm.sudo("sed -i 's/.*$/#{name.split('.')[0]}/' /etc/hostname") - - # hosts should resemble: - # 127.0.0.1 localhost - # 127.0.1.1 host.fqdn.com host - if name.split('.').length > 1 - # if there's an FQDN, put it in the right format - comm.sudo("sed -ri 's@^(([0-9]{1,3}\.){3}[0-9]{1,3})\\s+(#{old.split('.')[0]})\\b.*$@\\1\\t#{name} #{name.split('.')[0]}@g' /etc/hosts") - else - # if there's not an FQDN, don't print the hostname twice - comm.sudo("sed -ri 's@^(([0-9]{1,3}\.){3}[0-9]{1,3})\\s+(#{old.split('.')[0]})\\b.*$@\\1\\t#{name}@g' /etc/hosts") - end - - if comm.test("[ `lsb_release -c -s` = hardy ]") - # hostname.sh returns 1, so I grep for the right name in /etc/hostname just to have a 0 exitcode - comm.sudo("/etc/init.d/hostname.sh start; grep '#{name}' /etc/hostname") - else - comm.sudo("service hostname start") - end - comm.sudo("hostname --fqdn > /etc/mailname") - comm.sudo("ifdown -a; ifup -a; ifup -a --allow=hotplug") - end + def refresh_hostname_service + if hardy? + # hostname.sh returns 1, so use `true` to get a 0 exitcode + sudo("/etc/init.d/hostname.sh start; true") + else + sudo("service hostname start") end end + + def hardy? + machine.communicate.test("[ `lsb_release -c -s` = hardy ]") + end + + def renew_dhcp + sudo("ifdown -a; ifup -a; ifup -a --allow=hotplug") + end end end end diff --git a/plugins/guests/ubuntu/plugin.rb b/plugins/guests/ubuntu/plugin.rb index b4057a21b..7e2acd04d 100644 --- a/plugins/guests/ubuntu/plugin.rb +++ b/plugins/guests/ubuntu/plugin.rb @@ -12,6 +12,8 @@ module VagrantPlugins end guest_capability("ubuntu", "change_host_name") do + # ubuntu is just just a specialization of the debian code for this capability + require_relative "../debian/cap/change_host_name" require_relative "cap/change_host_name" Cap::ChangeHostName end diff --git a/test/unit/base.rb b/test/unit/base.rb index f471228a1..dbbd92984 100644 --- a/test/unit/base.rb +++ b/test/unit/base.rb @@ -10,6 +10,7 @@ $:.unshift File.expand_path("../../", __FILE__) # Load in helpers require "support/tempdir" +require "unit/support/dummy_communicator" require "unit/support/dummy_provider" require "unit/support/shared/base_context" diff --git a/test/unit/plugins/guests/debian/cap/change_host_name_test.rb b/test/unit/plugins/guests/debian/cap/change_host_name_test.rb new file mode 100644 index 000000000..37678b39e --- /dev/null +++ b/test/unit/plugins/guests/debian/cap/change_host_name_test.rb @@ -0,0 +1,33 @@ +require File.expand_path("../../../../../base", __FILE__) +require File.expand_path("../../../support/shared/debian_like_host_name_examples", __FILE__) + +describe "VagrantPlugins::GuestDebian::Cap::ChangeHostName" do + let(:described_class) do + VagrantPlugins::GuestDebian::Plugin.components.guest_capabilities[:debian].get(:change_host_name) + end + let(:machine) { double("machine") } + let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) } + + before do + machine.stub(:communicate).and_return(communicator) + communicator.stub_command('hostname -f', stdout: 'oldhostname.olddomain.tld') + end + + after do + communicator.verify_expectations! + end + + describe ".change_host_name" do + it_behaves_like "a debian-like host name change" + + it "refreshes the hostname service with the hostname command" do + communicator.expect_command(%q(hostname -F /etc/hostname)) + described_class.change_host_name(machine, 'newhostname.newdomain.tld') + end + + it "renews dhcp on the system with the new hostname" do + communicator.expect_command(%q(ifdown -a; ifup -a; ifup eth0)) + described_class.change_host_name(machine, 'newhostname.newdomain.tld') + end + end +end diff --git a/test/unit/plugins/guests/support/shared/debian_like_host_name_examples.rb b/test/unit/plugins/guests/support/shared/debian_like_host_name_examples.rb new file mode 100644 index 000000000..a4a945ff4 --- /dev/null +++ b/test/unit/plugins/guests/support/shared/debian_like_host_name_examples.rb @@ -0,0 +1,25 @@ +shared_examples "a debian-like host name change" do + it "updates /etc/hostname on the machine" do + communicator.expect_command(%q(echo 'newhostname' > /etc/hostname)) + described_class.change_host_name(machine, 'newhostname.newdomain.tld') + end + + it "flips out the old hostname in /etc/hosts" do + sed_find = '^(([0-9]{1,3}\.){3}[0-9]{1,3})\s+oldhostname.olddomain.tld\b.*$' + sed_replace = '\1\tnewhostname.newdomain.tld newhostname' + communicator.expect_command( + %Q(sed -ri 's@#{sed_find}@#{sed_replace}@g' /etc/hosts) + ) + described_class.change_host_name(machine, 'newhostname.newdomain.tld') + end + + it "updates mailname to prevent problems with the default mailer" do + communicator.expect_command(%q(hostname --fqdn > /etc/mailname)) + described_class.change_host_name(machine, 'newhostname.newdomain.tld') + end + + it "does nothing when the provided hostname is not different" do + described_class.change_host_name(machine, 'oldhostname.olddomain.tld') + communicator.received_commands.should == ['hostname -f'] + end +end diff --git a/test/unit/plugins/guests/ubuntu/cap/change_host_name_test.rb b/test/unit/plugins/guests/ubuntu/cap/change_host_name_test.rb new file mode 100644 index 000000000..1e59bf3c4 --- /dev/null +++ b/test/unit/plugins/guests/ubuntu/cap/change_host_name_test.rb @@ -0,0 +1,33 @@ +require File.expand_path("../../../../../base", __FILE__) +require File.expand_path("../../../support/shared/debian_like_host_name_examples", __FILE__) + +describe "VagrantPlugins::GuestUbuntu::Cap::ChangeHostName" do + let(:described_class) do + VagrantPlugins::GuestUbuntu::Plugin.components.guest_capabilities[:ubuntu].get(:change_host_name) + end + let(:machine) { double("machine") } + let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) } + + before do + machine.stub(:communicate).and_return(communicator) + communicator.stub_command('hostname -f', stdout: 'oldhostname.olddomain.tld') + end + + after do + communicator.verify_expectations! + end + + describe ".change_host_name" do + it_behaves_like "a debian-like host name change" + + it "refreshes the hostname service with upstart" do + communicator.expect_command(%q(service hostname start)) + described_class.change_host_name(machine, 'newhostname.newdomain.tld') + end + + it "renews dhcp on the system with the new hostname (with hotplug allowed)" do + communicator.expect_command(%q(ifdown -a; ifup -a; ifup -a --allow=hotplug)) + described_class.change_host_name(machine, 'newhostname.newdomain.tld') + end + end +end diff --git a/test/unit/support/dummy_communicator.rb b/test/unit/support/dummy_communicator.rb new file mode 100644 index 000000000..fa401d47b --- /dev/null +++ b/test/unit/support/dummy_communicator.rb @@ -0,0 +1,79 @@ +module VagrantTests + module DummyCommunicator + class Communicator < Vagrant.plugin("2", :communicator) + def ready? + true + end + + attr_reader :known_commands + + def initialize(machine) + @known_commands = Hash.new do |hash, key| + hash[key] = { expected: 0, received: 0, response: nil } + end + end + + def expected_commands + known_commands.select do |command, info| + info[:expected] > 0 + end + end + + def received_commands + known_commands.select do |command, info| + info[:received] > 0 + end.keys + end + + def stub_command(command, response) + known_commands[command][:response] = response + end + + def expect_command(command) + known_commands[command][:expected] += 1 + end + + def received_summary + received_commands.map { |cmd| " - #{cmd}" }.unshift('received:').join("\n") + end + + def verify_expectations! + expected_commands.each do |command, info| + if info[:expected] != info[:received] + fail([ + "expected to receive '#{command}' #{info[:expected]} times", + "got #{info[:received]} times instead", + received_summary + ].join("\n")) + end + end + end + + def execute(command, opts=nil) + known = known_commands[command] + known[:received] += 1 + response = known[:response] + return unless response + + if block_given? + [:stdout, :stderr].each do |type| + Array(response[type]).each do |line| + yield type, line + end + end + end + + if response[:raise] + raise response[:raise] + end + + response[:exit_code] + end + + def sudo(command, opts=nil, &block) + execute(command, opts, &block) + end + end + end +end + diff --git a/test/unit/support/shared/debian_examples.rb b/test/unit/support/shared/debian_examples.rb new file mode 100644 index 000000000..e69de29bb