## # Probably useful lxc commands # - get mac address: # lxc config get 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 # - Box/Image management is completely integrated with lxd. All image commands # are: # lxc image # 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 default eth0 # # Further things... right now gentoo specific # # - Create vagrant user: # lxc exec -- useradd vagrant # - Set password: # lxc exec -- chpasswd << -- rc-update add sshd default # - Start sshd service manually: # lxc exec -- /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", "dns.mode=dynamic") 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 def exec(*command) execute("exec", @name, "--", *command) 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: