2013-03-10 22:15:31 +00:00
|
|
|
require "vagrant/util/retryable"
|
|
|
|
require "vagrant/util/subprocess"
|
|
|
|
|
|
|
|
require "vagrant-lxc/errors"
|
|
|
|
|
|
|
|
module Vagrant
|
|
|
|
module LXC
|
2013-04-05 05:17:19 +00:00
|
|
|
class Driver
|
2013-03-10 22:15:31 +00:00
|
|
|
class CLI
|
2013-03-10 23:31:32 +00:00
|
|
|
attr_accessor :name
|
|
|
|
|
|
|
|
class TransitionBlockNotProvided < RuntimeError; end
|
2013-05-07 14:07:35 +00:00
|
|
|
class TargetStateNotReached < RuntimeError
|
|
|
|
def initialize(target_state, state)
|
|
|
|
msg = "Target state '#{target_state}' not reached, currently on '#{state}'"
|
|
|
|
super(msg)
|
|
|
|
end
|
|
|
|
end
|
2013-03-10 23:31:32 +00:00
|
|
|
|
2013-03-10 22:15:31 +00:00
|
|
|
# Include this so we can use `Subprocess` more easily.
|
|
|
|
include Vagrant::Util::Retryable
|
|
|
|
|
|
|
|
def initialize(name = nil)
|
|
|
|
@name = name
|
|
|
|
@logger = Log4r::Logger.new("vagrant::provider::lxc::container::cli")
|
|
|
|
end
|
|
|
|
|
|
|
|
def list
|
2013-03-10 23:31:32 +00:00
|
|
|
run(:ls).split(/\s+/).uniq
|
|
|
|
end
|
|
|
|
|
2013-04-09 01:27:29 +00:00
|
|
|
def version
|
|
|
|
if run(:version) =~ /lxc version:\s+(.+)\s*$/
|
|
|
|
$1.downcase
|
|
|
|
else
|
|
|
|
# TODO: Raise an user friendly error
|
|
|
|
raise 'Unable to parse lxc version!'
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-03-10 23:31:32 +00:00
|
|
|
def state
|
|
|
|
if @name && run(:info, '--name', @name) =~ /^state:[^A-Z]+([A-Z]+)$/
|
|
|
|
$1.downcase.to_sym
|
|
|
|
elsif @name
|
|
|
|
:unknown
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2013-06-06 03:04:59 +00:00
|
|
|
def create(template, config_file, template_opts = {})
|
|
|
|
if config_file
|
|
|
|
config_opts = ['-f', config_file]
|
|
|
|
end
|
|
|
|
|
2013-03-10 23:31:32 +00:00
|
|
|
extra = template_opts.to_a.flatten
|
|
|
|
extra.unshift '--' unless extra.empty?
|
|
|
|
|
|
|
|
run :create,
|
|
|
|
'--template', template,
|
|
|
|
'--name', @name,
|
2013-06-06 03:04:59 +00:00
|
|
|
*(config_opts),
|
2013-04-05 05:28:25 +00:00
|
|
|
*extra
|
2013-03-10 23:31:32 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def destroy
|
|
|
|
run :destroy, '--name', @name
|
|
|
|
end
|
|
|
|
|
2013-04-06 01:08:02 +00:00
|
|
|
def start(overrides = [], extra_opts = [])
|
|
|
|
options = overrides.map { |key, value| ["-s", "lxc.#{key}=#{value}"] }.flatten
|
|
|
|
options += extra_opts if extra_opts
|
|
|
|
run :start, '-d', '--name', @name, *options
|
2013-03-10 23:31:32 +00:00
|
|
|
end
|
|
|
|
|
2013-05-07 14:07:35 +00:00
|
|
|
def stop
|
|
|
|
run :stop, '--name', @name
|
|
|
|
end
|
|
|
|
|
2013-03-10 23:31:32 +00:00
|
|
|
def shutdown
|
|
|
|
run :shutdown, '--name', @name
|
|
|
|
end
|
|
|
|
|
2013-03-19 04:25:27 +00:00
|
|
|
def attach(*cmd)
|
|
|
|
cmd = ['--'] + cmd
|
|
|
|
|
|
|
|
if cmd.last.is_a?(Hash)
|
|
|
|
opts = cmd.pop
|
|
|
|
namespaces = Array(opts[:namespaces]).map(&:upcase).join('|')
|
|
|
|
extra = ['--namespaces', namespaces] if namespaces
|
|
|
|
end
|
|
|
|
|
|
|
|
run :attach, '--name', @name, *((extra || []) + cmd)
|
|
|
|
end
|
|
|
|
|
2013-05-07 14:07:35 +00:00
|
|
|
def transition_to(target_state, tries = 30, timeout = 1, &block)
|
2013-03-10 23:31:32 +00:00
|
|
|
raise TransitionBlockNotProvided unless block_given?
|
|
|
|
|
|
|
|
yield self
|
|
|
|
|
2013-05-07 14:07:35 +00:00
|
|
|
while (last_state = self.state) != target_state && tries > 0
|
|
|
|
@logger.debug "Target state '#{target_state}' not reached, currently on '#{last_state}'"
|
|
|
|
sleep timeout
|
|
|
|
tries -= 1
|
|
|
|
end
|
|
|
|
|
|
|
|
unless last_state == target_state
|
|
|
|
raise TargetStateNotReached.new target_state, last_state
|
|
|
|
end
|
2013-03-10 22:15:31 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2013-03-10 23:31:32 +00:00
|
|
|
def run(command, *args)
|
2013-03-10 22:15:31 +00:00
|
|
|
execute('sudo', "lxc-#{command}", *args)
|
|
|
|
end
|
|
|
|
|
2013-03-10 23:31:32 +00:00
|
|
|
# TODO: Review code below this line, it was pretty much a copy and
|
|
|
|
# paste from VirtualBox base driver and has no tests
|
2013-03-10 22:15:31 +00:00
|
|
|
def execute(*command, &block)
|
|
|
|
# Get the options hash if it exists
|
|
|
|
opts = {}
|
|
|
|
opts = command.pop if command.last.is_a?(Hash)
|
|
|
|
|
|
|
|
tries = 0
|
|
|
|
tries = 3 if opts[:retryable]
|
|
|
|
|
|
|
|
sleep = opts.fetch(:sleep, 1)
|
|
|
|
|
|
|
|
# Variable to store our execution result
|
|
|
|
r = nil
|
|
|
|
|
|
|
|
retryable(:on => LXC::Errors::ExecuteError, :tries => tries, :sleep => sleep) do
|
|
|
|
# Execute the command
|
|
|
|
r = raw(*command, &block)
|
|
|
|
|
|
|
|
# If the command was a failure, then raise an exception that is
|
|
|
|
# nicely handled by Vagrant.
|
|
|
|
if r.exit_code != 0
|
|
|
|
if @interrupted
|
|
|
|
@logger.info("Exit code != 0, but interrupted. Ignoring.")
|
|
|
|
else
|
|
|
|
raise LXC::Errors::ExecuteError, :command => command.inspect
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Return the output, making sure to replace any Windows-style
|
|
|
|
# newlines with Unix-style.
|
|
|
|
r.stdout.gsub("\r\n", "\n")
|
|
|
|
end
|
|
|
|
|
|
|
|
def raw(*command, &block)
|
|
|
|
int_callback = lambda do
|
|
|
|
@interrupted = true
|
|
|
|
@logger.info("Interrupted.")
|
|
|
|
end
|
|
|
|
|
|
|
|
# Append in the options for subprocess
|
|
|
|
command << { :notify => [:stdout, :stderr] }
|
|
|
|
|
|
|
|
Vagrant::Util::Busy.busy(int_callback) do
|
|
|
|
Vagrant::Util::Subprocess.execute(*command, &block)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|