refactoring ubuntu/debian change_host_name

there's been a lot of churn around this code, so i figure it was worth
trying to clean it up.

 - the methods were doing a lot, so make them into template methods with
   one helper per step
 - spread out /etc/hosts regexp into a couple of helper variables for
   clarity
 - remove handling for broken hostname implementations (like basing all
   of the checks on name.split('.')[0]), since it seems reasonable to
   remove code dedicated only to handling broken boxes
 - DRY up the shared code between debian/ubuntu implementations, which
   clarifies the differences as well
 - add unit tests around the behavior; this will help us in the future
   to separate flaws in our understanding from flaws in implementation
 - includes a new DummyCommunicator in tests which should be useful in
   supporting additional unit testing of this kind
 - manually tested this on squeeze, wheezy, precise, quantal, raring,
   and saucy successfully.

handles the issue in #2333
This commit is contained in:
phinze 2013-11-23 17:48:00 -06:00
parent 0379853202
commit 688bca14f5
9 changed files with 257 additions and 57 deletions

View File

@ -3,33 +3,79 @@ module VagrantPlugins
module Cap
class ChangeHostName
def self.change_host_name(machine, name)
machine.communicate.tap do |comm|
# 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
new(machine, name).change!
end
# this works even if they're not both fqdn
if old.split('.')[0] != name.split('.')[0]
attr_reader :machine, :new_hostname
comm.sudo("sed -i 's/.*$/#{name.split('.')[0]}/' /etc/hostname")
# 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")
comm.sudo("hostname -F /etc/hostname")
comm.sudo("hostname --fqdn > /etc/mailname")
comm.sudo("ifdown -a; ifup -a; ifup eth0")
def initialize(machine, new_hostname)
@machine = machine
@new_hostname = new_hostname
end
def change!
return unless should_change?
update_etc_hostname
update_etc_hosts
refresh_hostname_service
update_mailname
renew_dhcp
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

View File

@ -1,45 +1,26 @@
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|
# 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
super
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")
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
# 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")
sudo("service hostname start")
end
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 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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