Merge pull request #10975 from timschumi/patch/alpine-guest-plugin

Integrate the vagrant-alpine guest plugin
This commit is contained in:
Brian Cain 2019-07-30 09:17:42 -07:00 committed by GitHub
commit 3bde70fec1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 545 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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