a4768c26ca
Some platforms (most notably CentOS and RHEL) use a kernel without 'attach' support. This patch detects this absence and falls back to the alternative ways of doing things like detection of IP address and halting the container. It does so by running the command "true" through lxc-attach.
217 lines
6.4 KiB
Ruby
217 lines
6.4 KiB
Ruby
require "vagrant/util/retryable"
|
|
require "vagrant/util/subprocess"
|
|
|
|
require "vagrant-lxc/errors"
|
|
require "vagrant-lxc/driver/cli"
|
|
|
|
require "etc"
|
|
|
|
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
|
|
|
|
# Root folder where container configs are stored
|
|
CONTAINERS_PATH = '/var/lib/lxc'
|
|
|
|
attr_reader :container_name,
|
|
:customizations
|
|
|
|
def initialize(container_name, sudo_wrapper, cli = nil)
|
|
@container_name = container_name
|
|
@sudo_wrapper = sudo_wrapper
|
|
@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
|
|
|
|
def all_containers
|
|
@cli.list
|
|
end
|
|
|
|
def base_path
|
|
Pathname.new("#{CONTAINERS_PATH}/#{@container_name}")
|
|
end
|
|
|
|
def rootfs_path
|
|
Pathname.new(config_string.match(/^lxc\.rootfs\s+=\s+(.+)$/)[1])
|
|
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
|
|
|
|
import_template(template_path) do |template_name|
|
|
@logger.debug "Creating container..."
|
|
@cli.create template_name, backingstore, backingstore_options, config_file, template_options
|
|
end
|
|
end
|
|
|
|
def share_folders(folders)
|
|
folders.each do |f|
|
|
share_folder(f[:hostpath], f[:guestpath], f.fetch(:mount_options, 'bind'))
|
|
end
|
|
end
|
|
|
|
def share_folder(host_path, guest_path, mount_options = nil)
|
|
guest_path = guest_path.gsub(/^\//, '')
|
|
guest_full_path = rootfs_path.join(guest_path)
|
|
|
|
unless guest_full_path.directory?
|
|
begin
|
|
@logger.debug("Guest path doesn't exist, creating: #{guest_full_path}")
|
|
@sudo_wrapper.run('mkdir', '-p', guest_full_path.to_s)
|
|
rescue Errno::EACCES
|
|
raise Vagrant::Errors::SharedFolderCreateFailed, :path => guest_path.to_s
|
|
end
|
|
end
|
|
|
|
mount_options = Array(mount_options || ['bind'])
|
|
@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...')
|
|
# TODO: Remove `lxc-shutdown` usage, graceful halt is enough
|
|
@cli.transition_to(:stopped) { |c| c.shutdown }
|
|
# REFACTOR: Do not use exception to control the flow
|
|
rescue CLI::TargetStateNotReached, CLI::ShutdownNotSupported
|
|
@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 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
|
|
|
|
def import_template(path)
|
|
template_name = "vagrant-tmp-#{@container_name}"
|
|
tmp_template_path = templates_path.join("lxc-#{template_name}").to_s
|
|
|
|
@logger.info 'Copying LXC template into place'
|
|
@sudo_wrapper.run('cp', path, tmp_template_path)
|
|
@sudo_wrapper.run('chmod', '+x', tmp_template_path)
|
|
|
|
yield template_name
|
|
ensure
|
|
@logger.info 'Removing LXC template'
|
|
if tmp_template_path
|
|
@sudo_wrapper.run('rm', tmp_template_path)
|
|
end
|
|
end
|
|
|
|
TEMPLATES_PATH_LOOKUP = %w(
|
|
/usr/share/lxc/templates
|
|
/usr/lib/lxc/templates
|
|
/usr/lib64/lxc/templates
|
|
/usr/local/lib/lxc/templates
|
|
)
|
|
def templates_path
|
|
return @templates_path if @templates_path
|
|
|
|
path = TEMPLATES_PATH_LOOKUP.find { |candidate| File.directory?(candidate) }
|
|
if !path
|
|
raise Errors::TemplatesDirMissing.new paths: TEMPLATES_PATH_LOOKUP.inspect
|
|
end
|
|
|
|
@templates_path = Pathname(path)
|
|
end
|
|
end
|
|
end
|
|
end
|