class Kitchen::Driver::Kubernetes

Kubernetes driver for Kitchen.

@author Noah Kantrowitz <noah@coderanger> @since 1.0 @see Kitchen::Transport::Kubernetes

Public Instance Methods

create(state) click to toggle source

(see Base#create)

# File lib/kitchen/driver/kubernetes.rb, line 159
def create(state)
  # Already created, we're good.
  return if state[:pod_id]
  # Lock in our name with randomness and whatever.
  pod_id = config[:pod_name]
  # Render the pod YAML and feed it to kubectl.
  tpl = ERB.new(IO.read(config[:pod_template]), 0, '-')
  tpl.filename = config[:pod_template]
  pod_yaml = tpl.result(binding)
  debug("Creating pod with YAML:\n#{pod_yaml}\n")
  run_command(kubectl_command('create', '--filename', '-'), input: pod_yaml)
  # Wait until the pod reaches Running status.
  status = nil
  start_time = Time.now
  while status != 'Running'
    if Time.now - start_time > 20
      # More than 20 seconds, start giving user feedback. 20 second threshold
      # was 100% pulled from my ass based on how long it takes to launch
      # on my local minikube, may need changing for reality.
      info("Waiting for pod #{pod_id} to be running, currently #{status}")
    end
    sleep(1)
    # Can't use run_command here because error! is unwanted and logging is a bit much.
    status_cmd = Mixlib::ShellOut.new(kubectl_command('get', 'pod', pod_id, '--output=json'))
    status_cmd.run_command
    unless status_cmd.error? || status_cmd.stdout.empty?
      status = JSON.parse(status_cmd.stdout)['status']['phase']
    end
  end
  # Save the pod ID.
  state[:pod_id] = pod_id
rescue Exception => ex
  # If something goes wrong, try to clean up.
  if pod_id
    begin
      debug("Failure during create, trying to clean up pod #{pod_id}")
      run_command(kubectl_command('delete', 'pod', pod_id, '--now'))
    rescue ShellCommandFailed => cleanup_ex
      # Welp, we tried.
      debug("Cleanup failed, continuing anyway: #{cleanup_ex}")
    end
  end
  raise ex
end
default_image() click to toggle source

Work out the default primary container image to use for this instance. Can be overridden by subclasses. Must return a string compatible with a Kubernetes container image specification.

@return [String]

# File lib/kitchen/driver/kubernetes.rb, line 92
def default_image
  if instance.platform.name =~ /^(.*)-([^-]*)$/
    "#{$1}:#{$2}"
  else
    instance.platform.name
  end
end
destroy(state) click to toggle source

(see Base#destroy)

# File lib/kitchen/driver/kubernetes.rb, line 205
def destroy(state)
  return unless state[:pod_id]
  run_command(kubectl_command('delete', 'pod', state[:pod_id], '--now'))
  # Explicitly not waiting for the delete to finish, if k8s has problems
  # with deletes in the future, I can add a wait here.
rescue ShellCommandFailed => ex
  raise unless ex.to_s.include?('(NotFound)')
end
finalize_config!(instance) click to toggle source

Muck with some other plugins to make the UX easier. Haxxxx.

@api private

Calls superclass method
# File lib/kitchen/driver/kubernetes.rb, line 103
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::Kubernetes.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 my copy of the kubernetes train transport.
      # Pending https://github.com/chef/train/pull/205 and https://github.com/chef/kitchen-inspec/pull/148
      # or https://github.com/chef/kitchen-inspec/pull/149.
      require 'kitchen/verifier/train_kubernetes_hack'
      _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::Kubernetes)
          # Initiate 1337 ha><0rz.
          {
            "backend" => "kubernetes_hack",
            "logger" => logger,
            "pod" => state[:pod_id],
            "container" => "default",
            "kubectl_path" => _config[:kubectl_path],
            "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