guests: Add unmodified alpine support plugin from maier

This has been available as `vagrant-alpine` in the plugin
repository so far.
This commit is contained in:
Tim Schumacher 2019-07-17 16:44:01 +02:00
parent cca36d91ed
commit fbffb41fc4
15 changed files with 645 additions and 0 deletions

View File

@ -0,0 +1,95 @@
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 = ''
sudo 'hostname -f' do |type, data|
hostname = data.chomp if type == :stdout && hostname.empty?
end
hostname
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
if 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 #{fqdn} #{short_hostname}"
expression = ['s', search, replace, 'g'].join('@')
sudo("sed -ri '#{expression}' /etc/hosts")
else
# Current hostname entry isn't in /etc/hosts, just append it
sudo("echo '127.0.1.1 #{fqdn} #{short_hostname}' >>/etc/hosts")
end
end
def refresh_hostname_service
sudo('hostname -F /etc/hostname')
end
def update_mailname
sudo('hostname -f > /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
def test(cmd)
machine.communicate.test(cmd)
end
end
end
end
end

View File

@ -0,0 +1,67 @@
# 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
def self.source_root
@source_root ||= Pathname.new(File.expand_path('../../../../', __FILE__))
end
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, template_root: source_root.join('templates') })
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,46 @@
module VagrantPlugins
module GuestAlpine
module Cap
class NFSClient
def self.nfs_client_install(machine)
comm = machine.communicate
comm.sudo <<-EOS.gsub(/^\s+\|\s?/, '')
| # work around defunct repository in configuration
| # box: maier/apline-3.3
| repo_file="/etc/apk/repositories"
| if [ $(grep -c "repos.dfw.lax-noc.com" $repo_file) -ne 0 ]; then
| repo_file_bak="${repo_file}.orig"
| echo "updating repositories"
| cp $repo_file $repo_file_bak
| sed -e 's/repos.dfw.lax-noc.com/dl-cdn.alpinelinux.org/' $repo_file_bak > $repo_file
| fi
|
| echo "updating repository indices"
| apk update
| if [ $? -ne 0 ]; then
| exit 1
| fi
|
| echo "installing nfs-utils"
| apk add --upgrade nfs-utils
| if [ $? -ne 0 ]; then
| exit 1
| fi
|
| echo "installing rpc.statd"
| rc-update add rpc.statd
| if [ $? -ne 0 ]; then
| exit 1
| fi
|
| echo "starting rpc.statd service"
| rc-service rpc.statd start
| if [ $? -ne 0 ]; then
| exit 1
| fi
EOS
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,19 @@
begin
require 'vagrant'
rescue LoadError
raise 'The Vagrant Alpine Linux Guest plugin must be run within Vagrant.'
end
if Vagrant::VERSION < '1.7.0'
fail 'The vagrant-alpine plugin is only compatible with Vagrant 1.7+'
end
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,58 @@
begin
require 'vagrant'
rescue LoadError
raise 'The Vagrant Alpine Linux Guest plugin must be run within Vagrant.'
end
if Vagrant::VERSION < '1.7.0'
fail 'The vagrant-alpine plugin is only compatible with Vagrant 1.7+'
end
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,128 @@
require 'spec_helper'
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,40 @@
require 'spec_helper'
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,24 @@
require 'spec_helper'
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,60 @@
require 'spec_helper'
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
x = <<-EOS.gsub(/^\s+\|\s?/, '')
| # work around defunct repository in configuration
| # box: maier/apline-3.3
| repo_file="/etc/apk/repositories"
| if [ $(grep -c "repos.dfw.lax-noc.com" $repo_file) -ne 0 ]; then
| repo_file_bak="${repo_file}.orig"
| echo "updating repositories"
| cp $repo_file $repo_file_bak
| sed -e 's/repos.dfw.lax-noc.com/dl-cdn.alpinelinux.org/' $repo_file_bak > $repo_file
| fi
|
| echo "updating repository indices"
| apk update
| if [ $? -ne 0 ]; then
| exit 1
| fi
|
| echo "installing nfs-utils"
| apk add --upgrade nfs-utils
| if [ $? -ne 0 ]; then
| exit 1
| fi
|
| echo "installing rpc.statd"
| rc-update add rpc.statd
| if [ $? -ne 0 ]; then
| exit 1
| fi
|
| echo "starting rpc.statd service"
| rc-service rpc.statd start
| if [ $? -ne 0 ]; then
| exit 1
| fi
EOS
expect(communicator).to receive(:sudo).with(x)
allow_message_expectations_on_nil
described_class.nfs_client_install(machine)
end
end

View File

@ -0,0 +1,37 @@
require 'spec_helper'
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