class Kitchen::Driver::Lxd
Constants
- UBUNTU_RELEASES
only bothering with the releases on linuxcontainers.org leaving this mutable so that end-users can append new releases to it Usage Note: If a future release is not in the below table, just specify the full image name in the kitchen yml instead of using ubuntu-<version>
Public Class Methods
# File lib/kitchen/driver/lxd.rb, line 22 def initialize(config = {}) # pp 'Config:', config super end
Public Instance Methods
# File lib/kitchen/driver/lxd.rb, line 35 def create(state) state[:config] = config.select { |k, _| [:server, :port, :rest_options, :image_server].include? k } info "Utilizing REST interface at " + host_address if respond_to?(:info) && can_rest? state[:username] = config[:username] if config.key? :username state[:container_name] = new_container_name unless state[:container_name] # TODO: convergent behavior on container_options change? (:profiles :config) state[:container_options] = container_options info "Container name: #{state[:container_name]}" driver.create_container(state[:container_name], state[:container_options]) # Allow SSH transport on known images with sshd enabled # This will only work if the container is routable. LXD does not do port forwarding (yet) # Which also means that you might need to do 'ssh_login: false' in the config if you're using a cloud-image and aren't routable # think ahead for default behaviour once LXD can port forward # FUTURE: If I get time I'll look into faking a port forward with something under /dev/ until then if use_ssh? # Normalize [:ssh_login] config[:ssh_login] = { username: config[:ssh_login] } if config[:ssh_login].is_a? String config[:ssh_login] ||= {} # if config[:ssh_login] && !config.to_hash[:ssh_login].is_a?(Hash) state[:username] = config[:ssh_login][:username] if config[:ssh_login].key? :username state[:username] ||= "root" end if (state[:username] && (state[:username] != "root")) && cloud_image? info "Waiting for cloud-init..." driver.wait_for state[:container_name], :cloud_init else info "Waiting for an IP address..." end state[:ip_address] = state[:hostname] = container_ip(state) if use_ssh? setup_ssh(state[:username], config[:ssh_login][:public_key] || "#{ENV['HOME']}/.ssh/id_rsa.pub", state) info "SSH access enabled on #{state[:ip_address]}" else # TODO: this section is only for the base images on linuxcontainers.org... (and I still need to account for yum) # they need patched because they don't have wget, or anything else with which to download the chef client # Custom images should account for this, so I won't run this patch for them (in the name of testing speed) unless cloud_image? transport = nx_transport(state) transport.reset_user # only centos/7 and various ubuntu versions have been tested here # - ubuntu non-cloud has no download utilities in order to dl the chef package so we must adapt that # - centos/7 needs sudo installed, or you need to use sudo:false on the provisioner... leaving it explicit for the user to fix unless transport.execute("test -d /etc/apt").error? info "Installing additional dependencies..." transport.execute("apt install openssl curl ca-certificates -y").error! end end end end
# File lib/kitchen/driver/lxd.rb, line 97 def destroy(state) info "Destroying container #{state[:container_name]}" driver.delete_container state[:container_name] end
# File lib/kitchen/driver/lxd.rb, line 91 def finalize_config!(instance) super.tap do instance.transport = Kitchen::Transport::Lxd.new config unless lxd_transport_selected? || use_ssh? end end
Private Instance Methods
# File lib/kitchen/driver/lxd.rb, line 120 def cloud_image? server = image_server return false unless server && server[:server] server[:server].downcase.start_with? "https://cloud-images.ubuntu.com" end
# File lib/kitchen/driver/lxd.rb, line 231 def container_ip(state) # TODO: make timeout configurable driver.wait_for state[:container_name], :ip # , 60 # default timeout=60 end
# File lib/kitchen/driver/lxd.rb, line 188 def container_options options = image_server # 0: found = false %w{:alias :fingerprint :properties}.each do |k| if config.key? k options[k] = config[k] found = true end end options[:alias] = image_name(options[:server]) unless found options.merge(config.select { |k, _| [:profiles, :config, :devices].include? k }) end
Special cases: using example `ubuntu-16.04`
0: if alias, fingerprint, or properties are specified, use instead of the below: (handled by caller) 1: if server.start_with? 'https://cloud-images.ubuntu.com' - trim the leading `ubuntu-` (optionally specified) 2: if server.start_with? 'https://images.linuxcontainers.org' - replace `-` with `/` (in all cases?) - replace version with codename, if dist == 'ubuntu'
# File lib/kitchen/driver/lxd.rb, line 152 def image_name(server) name = instance.platform.name return name unless server # 1: if server.downcase.start_with? "https://cloud-images.ubuntu.com" info "Using cloud-image '#{name}'" return name.downcase.sub(/^ubuntu-/, "") end # 2: if server.downcase.start_with? "https://images.linuxcontainers.org" name = name.downcase.split("-") # 'core' parses out in this method as the 'version' so just use 'ubuntu-core' in the kitchen.yml if UBUNTU_RELEASES.key?(name[1]) && name[0] == "ubuntu" name[1] = UBUNTU_RELEASES[name[1]] name[0] = "ubuntu-core" if name[1] == "16" # Logic patch for the edge case. We'll do something different if this gets complicated end name = name.join("/") info "Using standard image #{name}" end name end
Normalize into a hash with the correct protocol We'll take a given hash verbatim But we'll allow a simple string to default to the simplestreams protocol if no port is specified (otherwise 'lxd' is default, but counterintuitive given that if we specify neither a port nor a protocol, 8443 will be appended for lxd's default) Side effect: differing behavior (protocol) depending on whether a port is specified on a simple string
which is (ok?) If you want to specify an odd port, you should probably also specify which protocol
# File lib/kitchen/driver/lxd.rb, line 136 def image_server server = config[:image_server] if server.is_a? String server = { server: server } server[:protocol] = "simplestreams" if server[:server].split(":", 3)[2].nil? end server end
if selected by user during startup (finalize_config!) later, if selected by the user, or if deemed necessary by the driver
# File lib/kitchen/driver/lxd.rb, line 116 def lxd_transport_selected? instance.transport.is_a? Kitchen::Transport::Lxd end
# File lib/kitchen/driver/lxd.rb, line 126 def new_container_name instance.name + "-" + SecureRandom.hex(8) end
# File lib/kitchen/driver/lxd.rb, line 202 def setup_ssh(username, pubkey, state) # DEFERRED: should I create the ssh user if it doesn't exist? (I've seen that in other drivers) # not for now... that is an edge case within an edge case, and the default case just shells in as 'root' without concept of 'users' # submit a feature request if you need me to create a user # and that is if it is unfeasible for you to create a custom image with that user included return if state[:ssh_enabled] raise ActionFailed, "Public Key File does not exist (#{pubkey})" unless File.exist? pubkey transport = nx_transport(state) transport.reset_user remote_file = "/tmp/#{state[:container_name]}-publickey" transport.upload_file pubkey, remote_file begin sshdir = transport.execute("bash -c \"grep '^#{username}:' /etc/passwd | cut -d':' -f 6\"").error!.stdout.strip rescue => e fatal "Transport Error querying SSH User: #{sshdir}\n#{e.message}" raise ensure raise ActionFailed, "User (#{username}), or their home directory, were not found within container (#{state[:container_name]})" unless sshdir && !sshdir.empty? sshdir += "/.ssh" end ak_file = sshdir + "/authorized_keys" info "Inserting public key for container user '#{username}'" transport.execute("bash -c 'mkdir -p #{sshdir} 2> /dev/null; cat #{remote_file} >> #{ak_file} \ && rm -rf #{remote_file} && chown -R #{username}:#{username} #{sshdir}'").error! # , capture: false state[:ssh_enabled] = true end
ssh is kitchen's default unless lxd is selected by the user otherwise, only use ssh when the user supplies info and automatically, only on capable cloud images (requires kitchen 1.19 to perform the automatic override)
# File lib/kitchen/driver/lxd.rb, line 107 def use_ssh? return false if lxd_transport_selected? return true if config[:ssh_login] return false if config[:ssh_login] == false # allow forced disable for cloud-images... or (TODO: should I not default enable for them?) cloud_image? end