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 attr_reader :container_name, :customizations def initialize(container_name, sudo_wrapper = nil, cli = nil, privileged: true) @container_name = container_name @sudo_wrapper = sudo_wrapper || SudoWrapper.new(privileged: privileged) @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.config('lxc.lxcpath') end def all_containers @cli.list end def base_path Pathname.new("#{containers_path}/#{@container_name}") end def config_path base_path.join('config').to_s end def rootfs_path pathtype, path = config_string.match(/^lxc\.rootfs(?:\.path)?\s+=\s+(.+:)?(.+)$/)[1..2] case pathtype when 'overlayfs:' # Split on colon (:), ignoring any colon escaped by an escape character ( \ ) # Pays attention to when the escape character is itself escaped. _, overlay_path = config_entry.split(/(?/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 def supports_new_config_format Gem::Version.new(version) >= Gem::Version.new('2.1.0') 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 def update_config_keys(path = nil) path = path || config_path @cli.update_config(path) rescue Errors::ExecuteError # not on LXC 2.1+. Doesn't matter, ignore. 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) confpath = base_path.join('config').to_s begin File.open(confpath, File::WRONLY|File::TRUNC) do |file| file.write contents end rescue # We don't have permissions to write in the conf file. That's probably because it's a # privileged container. Work around that through sudo_wrapper. Tempfile.new('lxc-config').tap do |file| file.chmod 0644 file.write contents file.close @sudo_wrapper.run 'cp', '-f', file.path, confpath @sudo_wrapper.run 'chown', 'root:root', confpath end end end end end end