A vagrant provider for lxd container.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

261 lines
7.8 KiB

##
# Probably useful lxc commands
# - get mac address:
# lxc config get <container> volatile.eth0.hwaddr
# - get a json formated list of containers:
# lxc list --format=json -c ns4tS,volatile.eth0.hwaddr:MAC
# It seems that -c is ignored when json is user so:
# lxc list --format=json
# We care only about local containers... so ignore the remote.
# We might only want to list specific container started by vagrant, thus we
# should prefix each container name by the term 'vagrant_' and list only
# containers matching that pattern.
# lxc list vagrant- --format=json
# - The json above also seems to hold all the config information, anyway
# another way to show all config values for a given container in a more
# human readable form is:
# lxc config show <container>
# - Box/Image management is completely integrated with lxd. All image commands
# are:
# lxc image <something>
# The only thing we might need to keep information over is the image name or
# id to be used... here a remote might also be useful, to be able to use
# different image sources. But probably our box files will be quite simple.
# Anyway, i have not now completely figured out how box files work.
#
# This is pretty much all for now.... start, stop, init, etc. are left for
# later.
# One other thought... it might or might not be a good idea to connect all
# vagrant vms to the same bridge interface created by vagrant... anyway this
# should be configurable in some way.
#
# test this with e.g.:
# ~> bundel exec irb
# irb(main):001:0> load 'lxd.rb'
# => true
# irb(main):002:0> Vagrant::Lxd::Driver.new('vagrant-gentoo').vmdata
#
# General note: use pp to make the hash human readable.
#
# Network related commands:
#
# - Create a bridged network:
# lxc network create vagrantbr0
# Another example:
# lxc network create vagrantbr0 ipv6.address=none ipv4.address=10.0.3.1/24 ipv4.nat=true
# - Attach network to container:
# lxc network attach vagrantbr0 <container> default eth0
#
# Further things... right now gentoo specific
#
# - Create vagrant user:
# lxc exec <container> -- useradd vagrant
# - Set password:
# lxc exec <container> -- chpasswd <<<vagrant:vagrant
# - Enable sshd service:
# lxc exec <container> -- rc-update add sshd default
# - Start sshd service manually:
# lxc exec <container> -- /etc/init.d/sshd start
#
require 'json'
require 'log4r'
require 'yaml'
require 'vagrant/util/retryable'
module Vagrant
module Lxd
class Driver
include Vagrant::Util::Retryable
attr_reader :name
def initialize(machine)
@machine = machine
@name = "vagrant-#{machine.name}"
@logger = Log4r::Logger.new("vagrant::provider::lxd::driver")
# This flag is used to keep track of interrupted state (SIGINT)
@interrupted = false
@image = machine.box.name.split("/")[1] if machine.box
bridge
end
# Get all available images and their aliases
def images
data = JSON.parse(execute("image", "list", "--format=json"))
Hash[data.collect do |d|
d["aliases"].collect { |d2| [d2["name"], d] }
end.flatten(1)]
end
def image?
images.key? @image
end
# Get infos about all existing containers
def containers
data = JSON.parse(execute("list", "--format=json"))
Hash[data.collect { |d| [d["name"], d] }]
end
def container?
containers.key? @name
end
# This one will get infos about the managed container.
def container_data
containers[@name]
end
def network
container_data["state"]["network"]
end
def ipv4
network["eth0"]["addresses"].select do |d|
d["family"] == "inet"
end[0]["address"]
end
def state
return :not_created if not container?
return :stopped if not container_data["state"]
container_data["state"]["status"].downcase.to_sym
end
def get_image(remote)
return if image? # image already exists
args = [
"image",
"copy",
"#{remote}:#{@image}",
"local:",
"--copy-aliases"
]
execute(*args)
end
def create
# network could be also attached right here if it turns out to be
# a good idea.
execute("init", @image, @name, "-n", @bridge["name"])
end
def start
if state != :runnning
execute("start", @name)
end
end
def bridge
while not @bridge do
begin
@bridge = YAML.load(execute("network", "show", "vagrantbr0"))
rescue
execute("network", "create", "vagrantbr0")
end
end
@bridge
end
def vagrant_user
pwent = []
while pwent.empty? do
begin
pwent = execute(
"exec", @name, "getent", "passwd", "vagrant"
).split(":")
rescue
execute("exec", @name, "--", "useradd", "-m", "vagrant")
end
end
execute(
"file",
"push",
"--uid=#{pwent[2]}",
"--gid=#{pwent[3]}",
"--mode=0400",
"#{@machine.box.directory}/vagrant.pub",
"#{@name}/#{pwent[5]}/.ssh/authorized_keys"
)
end
def enable_ssh
begin
execute("exec", @name, "--", "rc-update", "add", "sshd", "default")
execute("exec", @name, "--", "/etc/init.d/sshd", "start")
rescue
end
end
# Taken from Virtualbox provider and modified in some parts.
# Execute the given subcommand for Lxc and return the output.
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]
# Variable to store our execution result
r = nil
# Most probably retrying is of no use here... if the command does not
# work it most likely will not work for the second time anyway...
# I leave this because I guess that vagrant tries commands even it the
# container is not up and running at the current time.
retryable(on: Vagrant::Errors::ProviderNotUsable, tries: tries, sleep: 1) 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 Vagrant::Errors::ProviderNotUsable,
provider: 'lxd',
machine: @machine.name,
message: "\"#{command.inspect}\" failed",
command: command.inspect,
stderr: r.stderr,
stdout: r.stdout
end
end
end
# Return the output, making sure to replace any Windows-style
# newlines with Unix-style.
r.stdout.gsub("\r\n", "\n")
end
# Executes a command and returns the raw result object.
def raw(*command, &block)
int_callback = lambda do
@interrupted = true
# We have to execute this in a thread due to trap contexts
# and locks.
Thread.new { @logger.info("Interrupted.") }.join
end
# Append in the options for subprocess
command << { notify: [:stdout, :stderr] }
Vagrant::Util::Busy.busy(int_callback) do
Vagrant::Util::Subprocess.execute('lxc', *command, &block)
end
rescue Vagrant::Util::Subprocess::LaunchError => e
raise Vagrant::Errors::ProviderNotUsable,
message: e.to_s
end
end
end
end
# vim: set et ts=2 sw=2: