vagrant-lxc-ng/lib/vagrant-lxc/driver.rb
Virgil Dupras aa777653f4 Use lxc-info instead of lxc-attach to retrieve container IP
`lxc-info -iH` to retrieve IP address was not available in early LXC
development but was there at LXC 1.0. Because we've bumped our minimum
LXC requirement to v1.0 recently, we can simplify the IP retrieval
process and also get rid of the `dnsmasq` fallback.
2018-01-13 15:53:28 -05:00

272 lines
8.1 KiB
Ruby

require "vagrant/util/retryable"
require "vagrant/util/subprocess"
require "vagrant-lxc/errors"
require "vagrant-lxc/driver/cli"
require "vagrant-lxc/sudo_wrapper"
require "etc"
require "tempfile"
module Vagrant
module LXC
class Driver
# This is raised if the container can't be found when initializing it with
# a name.
class ContainerNotFound < StandardError; end
# Default root folder where container configs are stored
DEFAULT_CONTAINERS_PATH = '/var/lib/lxc'
attr_reader :container_name,
:customizations
def initialize(container_name, sudo_wrapper = nil, cli = nil)
@container_name = container_name
@sudo_wrapper = sudo_wrapper || SudoWrapper.new()
@cli = cli || CLI.new(@sudo_wrapper, container_name)
@logger = Log4r::Logger.new("vagrant::provider::lxc::driver")
@customizations = []
end
def validate!
raise ContainerNotFound if @container_name && ! @cli.list.include?(@container_name)
end
# Root folder where container configs are stored
def containers_path
@containers_path ||= @cli.support_config_command? ? @cli.config('lxc.lxcpath') : DEFAULT_CONTAINERS_PATH
end
def all_containers
@cli.list
end
def base_path
Pathname.new("#{containers_path}/#{@container_name}")
end
def rootfs_path
config_entry = config_string.match(/^lxc\.rootfs\s+=\s+(.+)$/)[1]
case config_entry
when /^overlayfs:/
# Split on colon (:), ignoring any colon escaped by an escape character ( \ )
# Pays attention to when the escape character is itself escaped.
fs_type, master_path, overlay_path = config_entry.split(/(?<!\\)(?:\\\\)*:/)
if overlay_path
Pathname.new(overlay_path)
else
# Malformed: fall back to prior behaviour
Pathname.new(config_entry)
end
else
Pathname.new(config_entry)
end
end
def mac_address
return @mac_address if @mac_address
if config_string =~ /^lxc\.network\.hwaddr\s*+=\s*+(.+)$/
@mac_address = $1
end
end
def config_string
@sudo_wrapper.run('cat', base_path.join('config').to_s)
end
def create(name, backingstore, backingstore_options, template_path, config_file, template_options = {})
@cli.name = @container_name = name
@logger.debug "Creating container..."
@cli.create template_path, backingstore, backingstore_options, config_file, template_options
end
def share_folders(folders)
folders.each do |f|
share_folder(f[:hostpath], f[:guestpath], f.fetch(:mount_options, nil))
end
end
def share_folder(host_path, guest_path, mount_options = nil)
guest_path = guest_path.gsub(/^\//, '').gsub(' ', '\\\040')
mount_options = Array(mount_options || ['bind', 'create=dir'])
host_path = host_path.to_s.gsub(' ', '\\\040')
@customizations << ['mount.entry', "#{host_path} #{guest_path} none #{mount_options.join(',')} 0 0"]
end
def start(customizations)
@logger.info('Starting container...')
if ENV['LXC_START_LOG_FILE']
extra = ['-o', ENV['LXC_START_LOG_FILE'], '-l', 'DEBUG']
end
prune_customizations
write_customizations(customizations + @customizations)
@cli.start(extra)
end
def forced_halt
@logger.info('Shutting down container...')
@cli.transition_to(:stopped) { |c| c.stop }
end
def destroy
@cli.destroy
end
def supports_attach?
@cli.supports_attach?
end
def attach(*command)
@cli.attach(*command)
end
def info(*command)
@cli.info(*command)
end
def configure_private_network(bridge_name, bridge_ip, container_name, address_type, ip)
@logger.info "Configuring network interface for #{container_name} using #{ip} and bridge #{bridge_name}"
if ip
ip += '/24'
end
if ! bridge_exists?(bridge_name)
if not bridge_ip
raise "Bridge is missing and no IP was specified!"
end
@logger.info "Creating the bridge #{bridge_name}"
cmd = [
'brctl',
'addbr',
bridge_name
]
@sudo_wrapper.run(*cmd)
end
if ! bridge_has_an_ip?(bridge_name)
if not bridge_ip
raise "Bridge has no IP and none was specified!"
end
@logger.info "Adding #{bridge_ip} to the bridge #{bridge_name}"
cmd = [
'ip',
'addr',
'add',
"#{bridge_ip}/24",
'dev',
bridge_name
]
@sudo_wrapper.run(*cmd)
@sudo_wrapper.run('ip', 'link', 'set', bridge_name, 'up')
end
cmd = [
Vagrant::LXC.source_root.join('scripts/pipework').to_s,
bridge_name,
container_name,
ip ||= "dhcp"
]
@sudo_wrapper.run(*cmd)
end
def bridge_has_an_ip?(bridge_name)
@logger.info "Checking whether the bridge #{bridge_name} has an IP"
`ip -4 addr show scope global #{bridge_name}` =~ /^\s+inet ([0-9.]+)\/[0-9]+\s+/
end
def bridge_exists?(bridge_name)
@logger.info "Checking whether bridge #{bridge_name} exists"
brctl_output = `ip link | egrep -q " #{bridge_name}:"`
$?.to_i == 0
end
def bridge_is_in_use?(bridge_name)
# REFACTOR: This method is **VERY** hacky
@logger.info "Checking if bridge #{bridge_name} is in use"
brctl_output = `brctl show #{bridge_name} 2>/dev/null | tail -n +2 | grep -q veth`
$?.to_i == 0
end
def remove_bridge(bridge_name)
if ['lxcbr0', 'virbr0'].include? bridge_name
@logger.info "Skipping removal of system bridge #{bridge_name}"
return
end
return unless bridge_exists?(bridge_name)
@logger.info "Removing bridge #{bridge_name}"
@sudo_wrapper.run('ip', 'link', 'set', bridge_name, 'down')
@sudo_wrapper.run('brctl', 'delbr', bridge_name)
end
def version
@version ||= @cli.version
end
# TODO: This needs to be reviewed and specs needs to be written
def compress_rootfs
# TODO: Pass in tmpdir so we can clean up from outside
target_path = "#{Dir.mktmpdir}/rootfs.tar.gz"
@logger.info "Compressing '#{rootfs_path}' rootfs to #{target_path}"
@sudo_wrapper.run('tar', '--numeric-owner', '-cvzf', target_path, '-C',
rootfs_path.parent.to_s, "./#{rootfs_path.basename.to_s}")
@logger.info "Changing rootfs tarball owner"
user_details = Etc.getpwnam(Etc.getlogin)
@sudo_wrapper.run('chown', "#{user_details.uid}:#{user_details.gid}", target_path)
target_path
end
def state
if @container_name
@cli.state
end
end
def prune_customizations
# Use sed to just strip out the block of code which was inserted by Vagrant
@logger.debug 'Prunning vagrant-lxc customizations'
contents = config_string
contents.gsub! /^# VAGRANT-BEGIN(.|\s)*# VAGRANT-END\n/, ''
write_config(contents)
end
protected
def write_customizations(customizations)
customizations = customizations.map do |key, value|
"lxc.#{key}=#{value}"
end
customizations.unshift '# VAGRANT-BEGIN'
customizations << "# VAGRANT-END\n"
contents = config_string
contents << customizations.join("\n")
write_config(contents)
end
def write_config(contents)
Tempfile.new('lxc-config').tap do |file|
file.chmod 0644
file.write contents
file.close
@sudo_wrapper.run 'cp', '-f', file.path, base_path.join('config').to_s
@sudo_wrapper.run 'chown', 'root:root', base_path.join('config').to_s
end
end
end
end
end