class Kitchen::Driver::K8s

Driver for Kitchen

@author Chris McFee <cmcfee@kent.edu>

Public Instance Methods

create(state) click to toggle source

(see Base#create)

# File lib/kitchen/driver/k8s.rb, line 109
def create(state)
  return if state[:pod_id]
  pod_id = config[:pod_name].downcase
  state[:pod_id] = pod_id
  state[:namespace] = config[:namespace]
  client = kubectl_client
  # Render pod
  pod = render_pod_info(pod_id)
  debug("Creating pod with YAML:\n#{pod}\n")
  client.create_pod(render_pod_info(pod_id))
  # Wait until pod is running
  status = nil
  start_time = Time.now
  while status != 'Running'
    if Time.now - start_time > 20
      info("Waiting for pod #{pod_id} to be running, currently #{status}")
    end
    sleep(1)
    status = client.get_pod(pod_id, config[:namespace]).status.phase
  end
  platform_dependencies.each do |cmd|
    run_command(kubectl_command('exec', '--tty', '--container=default', pod_id, '--', *Shellwords.split(wrap_command(cmd))))
  end
  if config[:provision_command]
    config[:provision_command].each do |cmd|
      run_command(kubectl_command('exec', '--tty', '--container=default', pod_id, '--', *Shellwords.split(wrap_command(cmd))))
    end
  end
  state[:pod_id] = pod_id
end
default_platform() click to toggle source
# File lib/kitchen/driver/k8s.rb, line 51
def default_platform
  instance.platform.name.split('-').first
end
destroy(state) click to toggle source

(see Base#destroy)

# File lib/kitchen/driver/k8s.rb, line 141
def destroy(state)
  return unless state[:pod_id]
  client = kubectl_client
  client.delete_pod(state[:pod_id], config[:namespace])
end
finalize_config!(instance) click to toggle source

@api private

Calls superclass method
# File lib/kitchen/driver/k8s.rb, line 56
def finalize_config!(instance)
  super.tap do
    # Force the use of the Kubernetes transport since it isn't much use
    # without that.
    instance.transport = Kitchen::Transport::K8s.new(config)
    # Leave room for the possibility of other provisioners in the future,
    # but force some options we need.
    if instance.provisioner.is_a?(Kitchen::Provisioner::ChefBase)
      instance.provisioner.send(:config).update(
        require_chef_omnibus: false,
        product_name: nil,
        chef_omnibus_root: '/opt/chef',
        sudo: false
      )
    end
    # Ditto to the above, other verifiers will need their own hacks, but
    # this is a start at least.
    if instance.verifier.is_a?(Kitchen::Verifier::Busser)
      instance.verifier.send(:config).update(
        root_path: '/tmp/kitchen/verifier',
        sudo: false,
      )
    elsif defined?(Kitchen::Verifier::Inspec) && instance.verifier.is_a?(Kitchen::Verifier::Inspec)
      # Monkeypatch kitchen-inspec to use a copy of the k8s train transport.
      require 'kitchen/verifier/k8s'
      _config = config # Because closure madness.
      old_runner_options = instance.verifier.method(:runner_options)
      instance.verifier.send(:define_singleton_method, :runner_options) do |transport, state = {}, platform = nil, suite = nil|
        if transport.is_a?(Kitchen::Transport::K8s)
          {
            backend: 'k8s_hack',
            logger: logger,
            pod: state[:pod_id],
            container: 'default',
            kubectl_path: _config[:binary],
            context: _config[:context],
          }.tap do |runner_options|
            # Copied directly from kitchen-inspec because there is no way not to. Sigh.
            runner_options['color'] = (config[:color].nil? ? true : config[:color])
            runner_options['format'] = config[:format] unless config[:format].nil?
            runner_options['output'] = config[:output] % { platform: platform, suite: suite } unless config[:output].nil?
            runner_options['profiles_path'] = config[:profiles_path] unless config[:profiles_path].nil?
            runner_options[:controls] = config[:controls]
          end
        else
          old_runner_options.call(transport, state, platform, suite)
        end
      end
    end
  end
end

Protected Instance Methods

kubectl_client() click to toggle source

Get an instance of Kubeclient::Client

# File lib/kitchen/driver/k8s.rb, line 154
def kubectl_client
  kubeconfig = Kubeclient::Config.read(config[:kubeconfig])
  client = Kubeclient::Client.new(
    kubeconfig.context.api_endpoint,
    kubeconfig.context.api_version,
    {
      ssl_options: kubeconfig.context.ssl_options,
      auth_options: kubeconfig.context.auth_options
    }
  )
  return client
end
platform_dependencies() click to toggle source

Build platform dependent commands

# File lib/kitchen/driver/k8s.rb, line 168
def platform_dependencies
  case config[:platform]
  when 'debian', 'ubuntu'
    packages = [
      'export DEBIAN_FRONTEND=noninteractive',
      'apt-get update',
      'apt-get install -y sudo curl lsb-release'
    ]
  when 'rhel', 'centos', 'fedora'
    packages = [
      'yum install -y sudo which curl',
      'yum clean all'
    ]
  when 'opensuse', 'sles'
    packages = [
      'zypper install -y sudo which curl'
    ]
  when 'arch'
    packages = [
      'pacman --noconfirm -Sy archlinux-keyring',
      'pacman-db-upgrade',
      'pacman --noconfirm -Sy sudo curl'
    ]
  when 'gentoo'
    packages = [
      'emerge --sync',
      'emerge app-admin/sudo'
    ]
  when 'gentoo-paludis'
    packages = [
      'cave sync',
      'cave resolve -zx app-admin/sudo'
    ]
  else
    raise ActionFailed, "Unknown platform '#{config[:platform]}'"
  end
  return packages
end
render_pod_info(pod_id) click to toggle source

Create JSON pod information

# File lib/kitchen/driver/k8s.rb, line 208
def render_pod_info(pod_id)
  pod = Kubeclient::Resource.new
  pod.metadata = {}
  pod.metadata.name = pod_id
  pod.metadata.namespace = config[:namespace]
  pod.metadata.labels = { heritage: 'kitchen-k8s' }
  pod.metadata.annotations = {
    'kitchen.ci/kitchen-instance' => instance.name,
    'kitchen.ci/kitchen-platform' => instance.platform.name,
    'kitchen.ci/kitchen-created-by' => Etc.getlogin || 'unknown',
    'kitchen.ci/kitchen-created-on' => Socket.gethostname
  }
  pod.spec = {}
  pod.spec.containers = [{
    name: 'default',
    image: config[:image],
    command: ['/bin/sh', '-c', "trap 'exit 0' TERM; sleep 2147483647 & wait"],
    securityContext: { privileged: true }
  }]
  pod.spec.containers[0].env = config[:environment] if config[:environment]
  pod.spec.serviceAccount = config[:service_account]
  pod.spec.serviceAccountName = config[:service_account]
  return pod
end
wrap_command(cmd) click to toggle source
# File lib/kitchen/driver/k8s.rb, line 149
def wrap_command(cmd)
  cmd.match(/\Ash\s\-c/) ? cmd : Util.wrap_command(cmd.gsub('\'', "'\\\\''"))
end