module Sensu::Plugins::Kubernetes::Client

A mixin module that provides Kubernetes client (kubeclient) support.

Constants

INCLUSTER_CA_FILE

The location of the service account provided CA. (if the cluster is configured to provide it)

INCLUSTER_TOKEN_FILE

The location of the service account provided authentication token.

Public Instance Methods

kubeclient(options = {}) click to toggle source

Creates a new Kubeclient::Client instance using the given SSL and authentication options (if any)

@param [Hash] options The Kubernetes API connection details. @option options [String] :server URL to API server @option options [String] :version API version @option options [Boolean] :incluster

Use service account authentication if true

@option options [String] :ca_file

CA file used to verify API server certificate

@option options [String] :client_cert_file

Client certificate file to present

@option options [String] :client_key_file

Client private key file for the client certificate

@option options [String] :username

Username with access to API

@option options [String] :password

If a username is passed, also pass a password

@option options [String] :token

The bearer token for Kubernetes authorization

@option options [String] :token_file

A file containing the bearer token for Kubernetes authorization

@raise [ArgumentError] If an invalid option, or combination of options, is given. @raise [Errono::*] If there is a problem reading the client certificate or private key file. @raise [OpenSSL::X509::CertificateError] If there is a problem with the client certificate. @raise [OpenSSL::PKey::RSAError] If there is a problem with the client private key.

# File lib/sensu-plugins-kubernetes/client.rb, line 46
def kubeclient(options = {})
  raise(ArgumentError, 'options must be a hash') unless options.is_a?(Hash)

  api_server = options[:server]
  api_version = options[:version]

  ssl_options = {
    ca_file: options[:ca_file]
  }
  auth_options = {
    username: options[:username],
    password: options[:password],
    bearer_token: options[:token],
    bearer_token_file: options[:token_file]
  }

  if [:client_cert_file, :client_key_file].count { |k| options[k] } == 1
    raise ArgumentError, 'SSL requires both client cert and client key'
  end

  if options[:client_cert_file]
    begin
      ssl_options[:client_cert] = OpenSSL::X509::Certificate.new(File.read(options[:client_cert_file]))
    rescue => e
      raise e, "Unable to read client certificate: #{e}", e.backtrace
    end
  end

  if options[:client_key_file]
    begin
      ssl_options[:client_key] = OpenSSL::PKey::RSA.new(File.read(options[:client_key_file]))
    rescue => e
      raise e, "Unable to read client key: #{e}", e.backtrace
    end
  end

  if options[:incluster]
    # Provide in-cluster defaults, if not already specified
    # (following the kubernetes incluster config code, more or less)

    # api-server
    # TODO: use in-cluster DNS ??
    if api_server.nil?
      host = ENV['KUBERNETES_SERVICE_HOST']
      port = ENV['KUBERNETES_SERVICE_PORT']
      if host.nil? || port.nil?
        raise ArgumentError, 'unable to load in-cluster configuration,'\
             ' KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT'\
             ' must be defined'
      end
      api_server = URI::HTTPS.build(host: host, port: port, path: '/api')
    end

    # ca file, but only if it exists
    if ssl_options[:ca_file].nil? && File.exist?(INCLUSTER_CA_FILE)
      # Readability/permission issues should be left to kubeclient
      ssl_options[:ca_file] = INCLUSTER_CA_FILE
    end

    # token file
    if auth_options[:bearer_token_file].nil?
      auth_options[:bearer_token_file] = INCLUSTER_TOKEN_FILE
    end
  end

  ssl_options[:verify_ssl] = ssl_options[:ca_file] ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE

  begin
    # new only throws errors on bad arguments
    Kubeclient::Client.new(api_server, api_version,
                           ssl_options: ssl_options,
                           auth_options: auth_options)
  rescue URI::InvalidURIError => e
    # except for this one, which we'll re-wrap to make catching easier
    raise ArgumentError, "Invalid API server: #{e}", e.backtrace
  end
end