Keda: provide client-go of keda

Created on 3 Dec 2019  ·  20Comments  ·  Source: kedacore/keda

In some case, we need to create keda crd object by client-go of keda.

pending feature

Most helpful comment

Might be my Go ignorance here as I thought the go client had to be pulled in and document what it enables but it's ok then.

All 20 comments

Interesting - Could you elaborate on your scenario please?

thanks @tomkerkhove

I think we should add the client-go repo under the kedacore organization.

For example, when we use k8s, only our k8s administrators will use yaml. We developed a management platform based on k8s client-go. Now we are ready to integrate keda, so we need keda crd's client-go.

or should i use operator manager?

So you want to programatically create the ScaledObjects rather than via Kubernetes itself - Is that correct?

yes. hi @tomkerkhove do you have a good idea?

This is an interesting scenario, although I don't have experience with it.

/cc @jeffhollan @ahmelsayed

This is extremely common. I interact with resources via the generated client-go versions about 50% of my time in Kubernetes.

This thread interested me so I have been playing with it for the last couple of days. Is this what you are looking for? https://github.com/smartfrog-oss/keda/commit/a47710bd98ece9e32b4a34fef3622a60c2f3f9d4

If so I could figure out how to turn it into a PR. Mostly it's adding the tags for clientgen to the CRD objects and then running some generation scripts. https://blog.openshift.com/kubernetes-deep-dive-code-generation-customresources/

@zach-dunton-sf looking good on the first look. It should be part of the CI as well

Thanks @zach-dunton-sf

great! code generation is one of ways to create client-go of keda.

I researched for a few days. we can also achieve the goal in the following ways:

1:use the client of operator-sdk
docs

my code is :

import (
    "gitlab.ushareit.me/sgt/devops/hulk/src/models"
    "k8s.io/apimachinery/pkg/runtime"
    "github.com/kedacore/keda/pkg/apis"
    "sigs.k8s.io/controller-runtime/pkg/client"
    ctrl "sigs.k8s.io/controller-runtime"
)

var scheme = runtime.NewScheme()

func init() {
    apis.AddToScheme(scheme)
}

func NewEventHpaClusterClient(cluster models.Cluster) (client.Client, error) {
    cfg, err := NewConfig(cluster.Name, cluster.Address, cluster.Token)
    if err != nil {
        return nil, err
    }

    options := ctrl.Options{Scheme:scheme}

    c, err := client.New(cfg, client.Options{Scheme: options.Scheme})
    if err !=nil {
        return nil, err
    }
    return c,nil
}

2:use restClient of k8s to implement client-go of keda

my code is :

package v1alpha1

import (
    "time"

    "github.com/kedacore/keda/pkg/apis/keda/v1alpha1"
    "k8s.io/apimachinery/pkg/watch"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/apimachinery/pkg/runtime/schema"
    "k8s.io/apimachinery/pkg/runtime/serializer"
    "k8s.io/client-go/kubernetes/scheme"
    "k8s.io/client-go/rest"
)

type Interface interface {
    Watch(opts metav1.ListOptions) (watch.Interface, error)
    List(opts metav1.ListOptions) (*v1alpha1.ScaledObjectList, error)
    Get(name string, options metav1.GetOptions) (*v1alpha1.ScaledObject, error)
    Create(*v1alpha1.ScaledObject) (*v1alpha1.ScaledObject, error)
    Update(*v1alpha1.ScaledObject) (*v1alpha1.ScaledObject, error)
    Delete(name string, options *metav1.DeleteOptions) error
}

type Clientset struct {
    restClient rest.Interface
    ns     string
}

func (c *Clientset) Watch(opts metav1.ListOptions) (watch.Interface, error) {
    var timeout time.Duration
    if opts.TimeoutSeconds != nil {
        timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
    }

    opts.Watch = true
    return c.restClient.
        Get().
        Namespace(c.ns).
        Resource("scaledobjects").
        VersionedParams(&opts, scheme.ParameterCodec).
        Timeout(timeout).
        Watch()
}

func (c *Clientset) List(opts metav1.ListOptions) (*v1alpha1.ScaledObjectList, error) {
    var timeout time.Duration
    if opts.TimeoutSeconds != nil {
        timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
    }

    result := v1alpha1.ScaledObjectList{}
    err := c.restClient.
        Get().
        Namespace(c.ns).
        Resource("scaledobjects").
        VersionedParams(&opts, scheme.ParameterCodec).
        Timeout(timeout).
        Do().
        Into(&result)

    return &result, err
}


func (c *Clientset)  Get(name string, opts metav1.GetOptions) (*v1alpha1.ScaledObject, error) {
    result := v1alpha1.ScaledObject{}

    err := c.restClient.
        Get().
        Namespace(c.ns).
        Resource("scaledobjects").
        VersionedParams(&opts, scheme.ParameterCodec).
        Do().
        Into(&result)

    return &result, err
}
func (c *Clientset) Create(scaledObject *v1alpha1.ScaledObject) (*v1alpha1.ScaledObject, error) {
    result := v1alpha1.ScaledObject{}
    err := c.restClient.
        Post().
        Namespace(c.ns).
        Resource("scaledobjects").
        Body(scaledObject).
        Do().
        Into(&result)

    return &result, err
}

func (c *Clientset) Update(scaledObject *v1alpha1.ScaledObject) (*v1alpha1.ScaledObject, error) {
    result := v1alpha1.ScaledObject{}
    err := c.restClient.
        Put().
        Namespace(c.ns).
        Resource("scaledobjects").
        Name(scaledObject.Name).
        Body(scaledObject).
        Do().
        Into(&result)

    return &result, err
}

func (c *Clientset) Delete(name string, options *metav1.DeleteOptions) error{
    return c.restClient.
        Delete().
        Namespace(c.ns).
        Resource("scaledobjects").
        Name(name).
        Body(options).
        Do().
        Error()
}

func (c *Clientset) ScaledObject(namespace string) Interface {
    return &Clientset{
        restClient: c.restClient,
        ns:         namespace,
    }
}

func NewForConfig(c *rest.Config) (*Clientset, error) {
    config := *c
    config.ContentConfig.GroupVersion = &schema.GroupVersion{Group: "keda.k8s.io", Version: "v1alpha1"}
    config.APIPath = "/apis"
    config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}
    config.UserAgent = rest.DefaultKubernetesUserAgent()

    client, err := rest.RESTClientFor(&config)
    if err != nil {
        return nil, err
    }

    return &Clientset{restClient:client}, nil
}

Which way do others think is better?i like client of operator-sdk and code generation .

//EDIT: used markdown to display code blocks properly (@zroubalik)

Are there any updated on official KEDA client-go?

It is covered here https://keda.sh/faq/, but that's it.

Hope that helps?

That is what I am using - it is very low level and I was hoping for something KEDA specific API

@aslom sounds like it makes sense to track a feature request to build a first-class client-go integration with KEDA. I assume a few pieces of functionality that would be expected:

  • Create Scaled Object
  • Get Scaled Objects by Name
  • Get Trigger Authentication by Name
  • Create Trigger Authentication
  • Get HPA Name by Deployment Name

Does this look like what you were expecting?

@jeffhollan that is exactly what I was thinking about. Today I create ScaledObject using unstructured.Unstructured and map[string]interface{} and then manually Get/Create using DynamicClientSet

@aslom if you use controller-runtime client, you don't have to mess with Unstructured, it's pretty straightforward. But I'll take a look, whether we can do something about it.

@aslom we will expose KEDA client-go in our v2 release se #787

Fixed in #787

Can we get some docs on this?

I am not sure what exactly we should document here? It is a kubernetes Go client https://github.com/kubernetes/client-go/

Might be my Go ignorance here as I thought the go client had to be pulled in and document what it enables but it's ok then.

Was this page helpful?
0 / 5 - 0 ratings