Merge pull request #10975 from timschumi/patch/alpine-guest-plugin
Integrate the vagrant-alpine guest plugin
This commit is contained in:
commit
3bde70fec1
|
@ -0,0 +1,83 @@
|
|||
module VagrantPlugins
|
||||
module GuestAlpine
|
||||
module Cap
|
||||
class ChangeHostName
|
||||
def self.change_host_name(machine, name)
|
||||
new(machine, name).change!
|
||||
end
|
||||
|
||||
attr_reader :machine, :new_hostname
|
||||
|
||||
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 ||= fetch_current_hostname
|
||||
end
|
||||
|
||||
def fetch_current_hostname
|
||||
hostname = ''
|
||||
machine.communicate.sudo 'hostname -f' do |type, data|
|
||||
hostname = data.chomp if type == :stdout && hostname.empty?
|
||||
end
|
||||
|
||||
hostname
|
||||
end
|
||||
|
||||
def update_etc_hostname
|
||||
machine.communicate.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
|
||||
if machine.communicate.test("grep '#{current_hostname}' /etc/hosts")
|
||||
# Current hostname entry is in /etc/hosts
|
||||
ip_address = '([0-9]{1,3}\.){3}[0-9]{1,3}'
|
||||
search = "^(#{ip_address})\\s+#{Regexp.escape(current_hostname)}(\\s.*)?$"
|
||||
replace = "\\1 #{new_hostname} #{short_hostname}"
|
||||
expression = ['s', search, replace, 'g'].join('@')
|
||||
|
||||
machine.communicate.sudo("sed -ri '#{expression}' /etc/hosts")
|
||||
else
|
||||
# Current hostname entry isn't in /etc/hosts, just append it
|
||||
machine.communicate.sudo("echo '127.0.1.1 #{new_hostname} #{short_hostname}' >>/etc/hosts")
|
||||
end
|
||||
end
|
||||
|
||||
def refresh_hostname_service
|
||||
machine.communicate.sudo('hostname -F /etc/hostname')
|
||||
end
|
||||
|
||||
def update_mailname
|
||||
machine.communicate.sudo('hostname -f > /etc/mailname')
|
||||
end
|
||||
|
||||
def renew_dhcp
|
||||
machine.communicate.sudo('ifdown -a; ifup -a; ifup eth0')
|
||||
end
|
||||
|
||||
def short_hostname
|
||||
new_hostname.split('.').first
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,64 @@
|
|||
# rubocop:disable Metrics/MethodLength
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
# rubocop:disable Style/BracesAroundHashParameters
|
||||
#
|
||||
# FIXME: address disabled warnings
|
||||
#
|
||||
require 'set'
|
||||
require 'tempfile'
|
||||
require 'pathname'
|
||||
require 'vagrant/util/template_renderer'
|
||||
|
||||
module VagrantPlugins
|
||||
module GuestAlpine
|
||||
module Cap
|
||||
class ConfigureNetworks
|
||||
include Vagrant::Util
|
||||
def self.configure_networks(machine, networks)
|
||||
machine.communicate.tap do |comm|
|
||||
# First, remove any previous network modifications
|
||||
# from the interface file.
|
||||
comm.sudo("sed -e '/^#VAGRANT-BEGIN/,$ d' /etc/network/interfaces > /tmp/vagrant-network-interfaces.pre")
|
||||
comm.sudo("sed -ne '/^#VAGRANT-END/,$ p' /etc/network/interfaces | tail -n +2 > /tmp/vagrant-network-interfaces.post")
|
||||
|
||||
# Accumulate the configurations to add to the interfaces file as
|
||||
# well as what interfaces we're actually configuring since we use that
|
||||
# later.
|
||||
interfaces = Set.new
|
||||
entries = []
|
||||
networks.each do |network|
|
||||
interfaces.add(network[:interface])
|
||||
entry = TemplateRenderer.render("guests/alpine/network_#{network[:type]}", { options: network })
|
||||
entries << entry
|
||||
end
|
||||
|
||||
# Perform the careful dance necessary to reconfigure
|
||||
# the network interfaces
|
||||
temp = Tempfile.new('vagrant')
|
||||
temp.binmode
|
||||
temp.write(entries.join("\n"))
|
||||
temp.close
|
||||
|
||||
comm.upload(temp.path, '/tmp/vagrant-network-entry')
|
||||
|
||||
# Bring down all the interfaces we're reconfiguring. By bringing down
|
||||
# each specifically, we avoid reconfiguring eth0 (the NAT interface) so
|
||||
# SSH never dies.
|
||||
interfaces.each do |interface|
|
||||
comm.sudo("/sbin/ifdown eth#{interface} 2> /dev/null")
|
||||
comm.sudo("/sbin/ip addr flush dev eth#{interface} 2> /dev/null")
|
||||
end
|
||||
|
||||
comm.sudo('cat /tmp/vagrant-network-interfaces.pre /tmp/vagrant-network-entry /tmp/vagrant-network-interfaces.post > /etc/network/interfaces')
|
||||
comm.sudo('rm -f /tmp/vagrant-network-interfaces.pre /tmp/vagrant-network-entry /tmp/vagrant-network-interfaces.post')
|
||||
|
||||
# Bring back up each network interface, reconfigured
|
||||
interfaces.each do |interface|
|
||||
comm.sudo("/sbin/ifup eth#{interface}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,21 @@
|
|||
# rubocop:disable Style/RedundantBegin
|
||||
# rubocop:disable Lint/HandleExceptions
|
||||
#
|
||||
# FIXME: address disabled warnings
|
||||
#
|
||||
module VagrantPlugins
|
||||
module GuestAlpine
|
||||
module Cap
|
||||
class Halt
|
||||
def self.halt(machine)
|
||||
begin
|
||||
machine.communicate.sudo('poweroff')
|
||||
rescue Net::SSH::Disconnect, IOError
|
||||
# Ignore, this probably means connection closed because it
|
||||
# shut down and SSHd was stopped.
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,14 @@
|
|||
module VagrantPlugins
|
||||
module GuestAlpine
|
||||
module Cap
|
||||
class NFSClient
|
||||
def self.nfs_client_install(machine)
|
||||
machine.communicate.sudo('apk update')
|
||||
machine.communicate.sudo('apk add --upgrade nfs-utils')
|
||||
machine.communicate.sudo('rc-update add rpc.statd')
|
||||
machine.communicate.sudo('rc-service rpc.statd start')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
module VagrantPlugins
|
||||
module GuestAlpine
|
||||
module Cap
|
||||
class RSync
|
||||
def self.rsync_installed(machine)
|
||||
machine.communicate.test('test -f /usr/bin/rsync')
|
||||
end
|
||||
|
||||
def self.rsync_install(machine)
|
||||
machine.communicate.tap do |comm|
|
||||
comm.sudo('apk add rsync')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
module VagrantPlugins
|
||||
module GuestAlpine
|
||||
module Cap
|
||||
class SMB
|
||||
def self.smb_install(machine)
|
||||
machine.communicate.tap do |comm|
|
||||
comm.sudo('apk add cifs-utils')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,11 @@
|
|||
require 'vagrant'
|
||||
|
||||
module VagrantPlugins
|
||||
module GuestAlpine
|
||||
class Guest < Vagrant.plugin('2', :guest)
|
||||
def detect?(machine)
|
||||
machine.communicate.test('cat /etc/alpine-release')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,50 @@
|
|||
require 'vagrant'
|
||||
|
||||
module VagrantPlugins
|
||||
module GuestAlpine
|
||||
class Plugin < Vagrant.plugin('2')
|
||||
name 'Alpine guest'
|
||||
description 'Alpine Linux guest support.'
|
||||
|
||||
guest(:alpine, :linux) do
|
||||
require File.expand_path('../guest', __FILE__)
|
||||
Guest
|
||||
end
|
||||
|
||||
guest_capability(:alpine, :configure_networks) do
|
||||
require_relative 'cap/configure_networks'
|
||||
Cap::ConfigureNetworks
|
||||
end
|
||||
|
||||
guest_capability(:alpine, :halt) do
|
||||
require_relative 'cap/halt'
|
||||
Cap::Halt
|
||||
end
|
||||
|
||||
guest_capability(:alpine, :change_host_name) do
|
||||
require_relative 'cap/change_host_name'
|
||||
Cap::ChangeHostName
|
||||
end
|
||||
|
||||
guest_capability(:alpine, :nfs_client_install) do
|
||||
require_relative 'cap/nfs_client'
|
||||
Cap::NFSClient
|
||||
end
|
||||
|
||||
guest_capability(:alpine, :rsync_installed) do
|
||||
require_relative 'cap/rsync'
|
||||
Cap::RSync
|
||||
end
|
||||
|
||||
guest_capability(:alpine, :rsync_install) do
|
||||
require_relative 'cap/rsync'
|
||||
Cap::RSync
|
||||
end
|
||||
|
||||
guest_capability(:alpine, :smb_install) do
|
||||
require_relative 'cap/smb'
|
||||
Cap::SMB
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
#VAGRANT-BEGIN
|
||||
# The contents below are automatically generated by Vagrant. Do not modify.
|
||||
auto eth<%= options[:interface] %>
|
||||
iface eth<%= options[:interface] %> inet dhcp
|
||||
<% if !options[:use_dhcp_assigned_default_route] %>
|
||||
post-up route del default dev $IFACE || true
|
||||
<% else %>
|
||||
# We need to disable eth0, see GH-2648
|
||||
post-up route del default dev eth0
|
||||
post-up dhclient $IFACE
|
||||
pre-down route add default dev eth0
|
||||
<% end %>
|
||||
#VAGRANT-END
|
|
@ -0,0 +1,7 @@
|
|||
#VAGRANT-BEGIN
|
||||
# The contents below are automatically generated by Vagrant. Do not modify.
|
||||
auto eth<%= options[:interface] %>
|
||||
iface eth<%= options[:interface] %> inet static
|
||||
address <%= options[:ip] %>
|
||||
netmask <%= options[:netmask] %>
|
||||
#VAGRANT-END
|
|
@ -0,0 +1,127 @@
|
|||
require_relative "../../../../base"
|
||||
|
||||
describe 'VagrantPlugins::GuestAlpine::Cap::ChangeHostname' do
|
||||
let(:described_class) do
|
||||
VagrantPlugins::GuestAlpine::Plugin.components.guest_capabilities[:alpine].get(:change_host_name)
|
||||
end
|
||||
let(:machine) { double('machine') }
|
||||
let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }
|
||||
let(:old_hostname) { 'oldhostname.olddomain.tld' }
|
||||
|
||||
before do
|
||||
allow(machine).to receive(:communicate).and_return(communicator)
|
||||
communicator.stub_command('hostname -f', stdout: old_hostname)
|
||||
end
|
||||
|
||||
after do
|
||||
communicator.verify_expectations!
|
||||
end
|
||||
|
||||
describe '.change_host_name' do
|
||||
it 'updates /etc/hostname on the machine' do
|
||||
communicator.expect_command("echo 'newhostname' > /etc/hostname")
|
||||
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')
|
||||
expect(communicator.received_commands).to eq(['hostname -f'])
|
||||
end
|
||||
|
||||
it 'refreshes the hostname service with the hostname command' do
|
||||
communicator.expect_command('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('ifdown -a; ifup -a; ifup eth0')
|
||||
described_class.change_host_name(machine, 'newhostname.newdomain.tld')
|
||||
end
|
||||
|
||||
describe 'flipping out the old hostname in /etc/hosts' do
|
||||
let(:sed_command) do
|
||||
# Here we run the change_host_name through and extract the recorded sed
|
||||
# command from the dummy communicator
|
||||
described_class.change_host_name(machine, 'newhostname.newdomain.tld')
|
||||
communicator.received_commands.find { |cmd| cmd =~ /^sed/ }
|
||||
end
|
||||
|
||||
# Now we extract the regexp from that sed command so we can do some
|
||||
# verification on it
|
||||
let(:expression) { sed_command.sub(%r{^sed -ri '\(.*\)' /etc/hosts$}, "\1") }
|
||||
let(:search) { Regexp.new(expression.split('@')[1], Regexp::EXTENDED) }
|
||||
let(:replace) { expression.split('@')[2] }
|
||||
|
||||
let(:grep_command) { "grep '#{old_hostname}' /etc/hosts" }
|
||||
|
||||
before do
|
||||
communicator.stub_command(grep_command, exit_code: 0)
|
||||
end
|
||||
|
||||
it 'works on an simple /etc/hosts file' do
|
||||
original_etc_hosts = <<-ETC_HOSTS.gsub(/^ */, '')
|
||||
127.0.0.1 localhost
|
||||
127.0.1.1 oldhostname.olddomain.tld oldhostname
|
||||
ETC_HOSTS
|
||||
|
||||
modified_etc_hosts = original_etc_hosts.gsub(search, replace)
|
||||
|
||||
expect(modified_etc_hosts).to eq <<-RESULT.gsub(/^ */, '')
|
||||
127.0.0.1 localhost
|
||||
127.0.1.1 newhostname.newdomain.tld newhostname
|
||||
RESULT
|
||||
end
|
||||
|
||||
it 'does not modify lines which contain similar hostnames' do
|
||||
original_etc_hosts = <<-ETC_HOSTS.gsub(/^ */, '')
|
||||
127.0.0.1 localhost
|
||||
127.0.1.1 oldhostname.olddomain.tld oldhostname
|
||||
# common prefix, but different fqdn
|
||||
192.168.12.34 oldhostname.olddomain.tld.different
|
||||
# different characters at the dot
|
||||
192.168.34.56 oldhostname-olddomain.tld
|
||||
ETC_HOSTS
|
||||
|
||||
modified_etc_hosts = original_etc_hosts.gsub(search, replace)
|
||||
|
||||
expect(modified_etc_hosts).to eq <<-RESULT.gsub(/^ */, '')
|
||||
127.0.0.1 localhost
|
||||
127.0.1.1 newhostname.newdomain.tld newhostname
|
||||
# common prefix, but different fqdn
|
||||
192.168.12.34 oldhostname.olddomain.tld.different
|
||||
# different characters at the dot
|
||||
192.168.34.56 oldhostname-olddomain.tld
|
||||
RESULT
|
||||
end
|
||||
|
||||
it "appends 127.0.1.1 if it isn't there" do
|
||||
communicator.stub_command(grep_command, exit_code: 1)
|
||||
described_class.change_host_name(machine, 'newhostname.newdomain.tld')
|
||||
|
||||
sed = communicator.received_commands.find { |cmd| cmd =~ /^sed/ }
|
||||
expect(sed).to be_nil
|
||||
|
||||
echo = communicator.received_commands.find { |cmd| cmd =~ /^echo/ }
|
||||
expect(echo).to_not be_nil
|
||||
end
|
||||
|
||||
context 'when the old fqdn has a trailing dot' do
|
||||
let(:old_hostname) { 'oldhostname.withtrailing.dot.' }
|
||||
|
||||
it 'modifies /etc/hosts properly' do
|
||||
original_etc_hosts = <<-ETC_HOSTS.gsub(/^ */, '')
|
||||
127.0.0.1 localhost
|
||||
127.0.1.1 oldhostname.withtrailing.dot. oldhostname
|
||||
ETC_HOSTS
|
||||
|
||||
modified_etc_hosts = original_etc_hosts.gsub(search, replace)
|
||||
|
||||
expect(modified_etc_hosts).to eq <<-RESULT.gsub(/^ */, '')
|
||||
127.0.0.1 localhost
|
||||
127.0.1.1 newhostname.newdomain.tld newhostname
|
||||
RESULT
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,39 @@
|
|||
require_relative "../../../../base"
|
||||
|
||||
describe 'VagrantPlugins::GuestAlpine::Cap::ConfigureNetworks' do
|
||||
let(:described_class) do
|
||||
VagrantPlugins::GuestAlpine::Plugin.components.guest_capabilities[:alpine].get(:configure_networks)
|
||||
end
|
||||
let(:machine) { double('machine') }
|
||||
let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }
|
||||
|
||||
before do
|
||||
allow(machine).to receive(:communicate).and_return(communicator)
|
||||
end
|
||||
|
||||
after do
|
||||
communicator.verify_expectations!
|
||||
end
|
||||
|
||||
it 'should configure networks' do
|
||||
networks = [
|
||||
{ type: :static, ip: '192.168.10.10', netmask: '255.255.255.0', interface: 0, name: 'eth0' },
|
||||
{ type: :dhcp, interface: 1, name: 'eth1' }
|
||||
]
|
||||
|
||||
expect(communicator).to receive(:sudo).with("sed -e '/^#VAGRANT-BEGIN/,$ d' /etc/network/interfaces > /tmp/vagrant-network-interfaces.pre")
|
||||
expect(communicator).to receive(:sudo).with("sed -ne '/^#VAGRANT-END/,$ p' /etc/network/interfaces | tail -n +2 > /tmp/vagrant-network-interfaces.post")
|
||||
expect(communicator).to receive(:sudo).with('/sbin/ifdown eth0 2> /dev/null')
|
||||
expect(communicator).to receive(:sudo).with('/sbin/ip addr flush dev eth0 2> /dev/null')
|
||||
expect(communicator).to receive(:sudo).with('/sbin/ifdown eth1 2> /dev/null')
|
||||
expect(communicator).to receive(:sudo).with('/sbin/ip addr flush dev eth1 2> /dev/null')
|
||||
expect(communicator).to receive(:sudo).with('cat /tmp/vagrant-network-interfaces.pre /tmp/vagrant-network-entry /tmp/vagrant-network-interfaces.post > /etc/network/interfaces')
|
||||
expect(communicator).to receive(:sudo).with('rm -f /tmp/vagrant-network-interfaces.pre /tmp/vagrant-network-entry /tmp/vagrant-network-interfaces.post')
|
||||
expect(communicator).to receive(:sudo).with('/sbin/ifup eth0')
|
||||
expect(communicator).to receive(:sudo).with('/sbin/ifup eth1')
|
||||
|
||||
allow_message_expectations_on_nil
|
||||
|
||||
described_class.configure_networks(machine, networks)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,23 @@
|
|||
require_relative "../../../../base"
|
||||
|
||||
describe 'VagrantPlugins::GuestAlpine::Cap::Halt' do
|
||||
let(:described_class) do
|
||||
VagrantPlugins::GuestAlpine::Plugin.components.guest_capabilities[:alpine].get(:halt)
|
||||
end
|
||||
let(:machine) { double('machine') }
|
||||
let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }
|
||||
|
||||
before do
|
||||
allow(machine).to receive(:communicate).and_return(communicator)
|
||||
end
|
||||
|
||||
after do
|
||||
communicator.verify_expectations!
|
||||
end
|
||||
|
||||
it 'should halt guest' do
|
||||
expect(communicator).to receive(:sudo).with('poweroff')
|
||||
allow_message_expectations_on_nil
|
||||
described_class.halt(machine)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,27 @@
|
|||
require_relative "../../../../base"
|
||||
|
||||
describe 'VagrantPlugins::GuestAlpine::Cap::NFSClient' do
|
||||
let(:described_class) do
|
||||
VagrantPlugins::GuestAlpine::Plugin.components.guest_capabilities[:alpine].get(:nfs_client_install)
|
||||
end
|
||||
|
||||
let(:machine) { double('machine') }
|
||||
let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }
|
||||
|
||||
before do
|
||||
allow(machine).to receive(:communicate).and_return(communicator)
|
||||
end
|
||||
|
||||
after do
|
||||
communicator.verify_expectations!
|
||||
end
|
||||
|
||||
it 'should install nfs client' do
|
||||
described_class.nfs_client_install(machine)
|
||||
|
||||
expect(communicator.received_commands[0]).to match(/apk update/)
|
||||
expect(communicator.received_commands[1]).to match(/apk add --upgrade nfs-utils/)
|
||||
expect(communicator.received_commands[2]).to match(/rc-update add rpc.statd/)
|
||||
expect(communicator.received_commands[3]).to match(/rc-service rpc.statd start/)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,36 @@
|
|||
require_relative "../../../../base"
|
||||
|
||||
describe 'VagrantPlugins::GuestAlpine::Cap::RSync' do
|
||||
let(:machine) { double('machine') }
|
||||
let(:communicator) { VagrantTests::DummyCommunicator::Communicator.new(machine) }
|
||||
|
||||
before do
|
||||
allow(machine).to receive(:communicate).and_return(communicator)
|
||||
end
|
||||
|
||||
after do
|
||||
communicator.verify_expectations!
|
||||
end
|
||||
|
||||
let(:described_class) do
|
||||
VagrantPlugins::GuestAlpine::Plugin.components.guest_capabilities[:alpine].get(:rsync_install)
|
||||
end
|
||||
|
||||
it 'should install rsync' do
|
||||
# communicator.should_receive(:sudo).with('apk add rsync')
|
||||
expect(communicator).to receive(:sudo).with('apk add rsync')
|
||||
allow_message_expectations_on_nil
|
||||
described_class.rsync_install(machine)
|
||||
end
|
||||
|
||||
let(:described_class) do
|
||||
VagrantPlugins::GuestAlpine::Plugin.components.guest_capabilities[:alpine].get(:rsync_installed)
|
||||
end
|
||||
|
||||
it 'should verify rsync installed' do
|
||||
# communicator.should_receive(:test).with('test -f /usr/bin/rsync')
|
||||
expect(communicator).to receive(:test).with('test -f /usr/bin/rsync')
|
||||
allow_message_expectations_on_nil
|
||||
described_class.rsync_installed(machine)
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue