class Biosphere::Kube::Client

Public Class Methods

new(hostname, ssl_options) click to toggle source
# File lib/biosphere/kube.rb, line 109
def initialize(hostname, ssl_options)
    @clients = []

    @clients << ::Kubeclient::Client.new("#{hostname}/api" , "v1", ssl_options: ssl_options)
    @clients << ::Kubeclient::Client.new("#{hostname}/apis/apps/" , "v1beta1", ssl_options: ssl_options)
    @clients << ::Kubeclient::Client.new("#{hostname}/apis/extensions/" , "v1beta1", ssl_options: ssl_options)
    @clients << ::Kubeclient::Client.new("#{hostname}/apis/batch/" , "v2alpha1", ssl_options: ssl_options)
    @clients << ::Kubeclient::Client.new("#{hostname}/apis/storage.k8s.io/" , "v1", ssl_options: ssl_options)
    @clients << ::Kubeclient::Client.new("#{hostname}/apis/autoscaling/" , "v1", ssl_options: ssl_options)

    @clients.each do |c|
        begin
            c.discover
        rescue KubeException => e
            puts "Could not discover api #{c.api_endpoint} - maybe this kube version is too old."
        end
    end
end

Public Instance Methods

apply_resource(kuberesource) click to toggle source

Applies the KubeResource into the api server

The update process has the following sequence:

1) Try to fetch the resource to check if the resource is already there
2.1) If a new resource: issue a POST
2.2) If resource exists: merge existing resource with the KubeResource and issue a PUT (update)
# File lib/biosphere/kube.rb, line 238
def apply_resource(kuberesource)
    resource = kuberesource.document
    name = resource["metadata"]["name"]
    responses = []
    not_found = false
    begin
        response = get(resource)
    rescue RestClient::NotFound => e
        not_found = true
    end

    if not_found
        begin
            response = post(resource)
            puts "Created resource #{response[:resource]}"
            responses << response
        rescue RestClient::NotFound => e
            pp e
            pp JSON.parse(e.response.body)
            puts "404 when applying resources might mean one of the following:"
            puts "\t * You're trying to apply a non-namespaced manifest."
            puts "\t   Confirm if your manifests metadata should contain a namespace field or not"
        rescue RestClient::UnprocessableEntity => e
            pp e
            pp JSON.parse(e.response.body)
        end
    else
        puts "Updating resource #{response[:resource]}"

        # Get the current full resource from apiserver
        current_resource = response[:body]

        update_resource = kuberesource.merge_for_put(current_resource)

        begin
            responses << put(update_resource)
        rescue RestClient::BadRequest => e
            handle_bad_request(client, e, body, ns_prefix, resource_name)
            raise e
        rescue RestClient::Exception => e
            puts "Error updating resource: #{e} #{e.class}"
            pp JSON.parse(e.response)
        end
        
        return responses
    end
end
get(resource) click to toggle source
# File lib/biosphere/kube.rb, line 192
def get(resource)
    name = resource["metadata"]["name"]
    client = get_client(resource)
    resource_name = get_resource_name(resource)

    if !client
        raise ArgumentError, "Unknown resource #{resource["kind"]} of #{name} for kubernetes. Maybe this is in a new extension api?"
    end

    ns_prefix = client.build_namespace_prefix(resource["metadata"]["namespace"])
    key = ns_prefix + resource_name + "/#{name}"
    ret = client.rest_client[key].get(client.instance_variable_get("@headers"))
    return {
        action: :get,
        resource: key,
        body: JSON.parse(ret.body)
    }
end
get_client(resource) click to toggle source
# File lib/biosphere/kube.rb, line 139
def get_client(resource)
    kind = resource["kind"].underscore_case
    @clients.each do |c|
        if c.instance_variable_get("@api_group") + c.instance_variable_get("@api_version") == resource["apiVersion"]
            return c
        end
    end
    return nil
end
get_resource_name(resource) click to toggle source
# File lib/biosphere/kube.rb, line 128
def get_resource_name(resource)
    resource_name = nil
    kind = resource["kind"].underscore_case
    @clients.each do |c|
        if c.instance_variable_get("@entities")[kind]
            return c.instance_variable_get("@entities")[kind].resource_name
        end
    end
    return nil
end
handle_bad_request(client, e, body, ns_prefix, resource_name) click to toggle source
# File lib/biosphere/kube.rb, line 299
def handle_bad_request(client, e, body, ns_prefix, resource_name)
    puts "Error calling API (on RestClient::BadRequest rescue): #{e}"
    puts "rest_client: #{ns_prefix + resource_name}, client: #{client.rest_client[ns_prefix + resource_name]}"

    begin
        msg = JSON.parse(e.http_body)
        if msg["message"]
            m = msg["message"].match(/\[pos ([0-9]+?)\]:\s?(.+)/)
            if m
                error_pos = m[1].to_i
                if error_pos < body.length
                    # Find the line number where the error is
                    line_number = 0
                    for pos in 0..body.length - 1
                        if body[pos] == "\n"
                            line_number += 1
                        end
                        if pos >= m[1].to_i
                            break
                        end
                    end
                    print_error_location(body.split("\n"), line_number)
                end
            end
        end
    rescue
        puts "Error message from body #{e.http_body}"
    end
end
post(resource) click to toggle source
# File lib/biosphere/kube.rb, line 149
def post(resource)
    name = resource["metadata"]["name"]
    client = get_client(resource)
    resource_name = get_resource_name(resource)

    if !client
        raise ArgumentError, "Unknown resource #{resource[:kind]} of #{name} for kubernetes. Maybe this is in a new extension api?"
    end

    ns_prefix = client.build_namespace_prefix(resource["metadata"]["namespace"])
    body = JSON.pretty_generate(resource.to_h)
    begin
        ret =  client.rest_client[ns_prefix + resource_name].post(body, { 'Content-Type' => 'application/json' }.merge(client.instance_variable_get("@headers")))
    rescue RestClient::MethodNotAllowed => e
        if !resource[:metadata][:namespace]
            puts "Error doing api call: #{e}".colorize(:red)
            puts "This might be because you did not specify namespace in your resource: #{resource[:metadata]}".colorize(:yellow)
        else 
            puts "Error calling API (on RestClient::MethodNotAllowed): #{e}"
        end
        puts "rest_client: #{ns_prefix + resource_name}, client: #{client.rest_client[ns_prefix + resource_name]}"
        puts "Dumpin resource request:"
        pp body
        raise e

    rescue RestClient::BadRequest => e
        handle_bad_request(client, e, body, ns_prefix, resource_name)
        raise e

    rescue RestClient::Exception => e
        puts "Error calling API (on RestClient::Exception rescue): #{e}"
        puts "rest_client: #{ns_prefix + resource_name}, client: #{client.rest_client[ns_prefix + resource_name]}"
        puts "Dumpin resource request:"
        pp resource.to_h.to_json
        raise e
    end
    return {
        action: :post,
        resource: ns_prefix + resource_name + "/#{name}",
        body: JSON.parse(ret.body)
    }
end
print_error_location(lines, linenumber) click to toggle source
put(resource) click to toggle source
# File lib/biosphere/kube.rb, line 211
def put(resource)
    name = resource["metadata"]["name"]
    client = get_client(resource)
    resource_name = get_resource_name(resource)

    if !client
        raise ArgumentError, "Unknown resource #{resource["kind"]} of #{name} for kubernetes. Maybe this is in a new extension api?"
    end

    ns_prefix = client.build_namespace_prefix(resource["metadata"]["namespace"])
    key = ns_prefix + resource_name + "/#{name}"
    ret = client.rest_client[key].put(resource.to_h.to_json, { 'Content-Type' => 'application/json' }.merge(client.instance_variable_get("@headers")))
    return {
        action: :put,
        resource: key,
        body: JSON.parse(ret.body)
    }

end