Skip to content

client

Client interface

// Client knows how to perform CRUD operations on Kubernetes objects.
type Client interface {
    Reader
    Writer
    StatusClient

    // Scheme returns the scheme this client is using.
    Scheme() *runtime.Scheme
    // RESTMapper returns the rest this client is using.
    RESTMapper() meta.RESTMapper
}
// Reader knows how to read and list Kubernetes objects.
type Reader interface {
    Get(ctx context.Context, key ObjectKey, obj Object) error
    List(ctx context.Context, list ObjectList, opts ...ListOption) error
}
// Writer knows how to create, delete, and update Kubernetes objects.
type Writer interface {
    Create(ctx context.Context, obj Object, opts ...CreateOption) error
    Delete(ctx context.Context, obj Object, opts ...DeleteOption) error
    Update(ctx context.Context, obj Object, opts ...UpdateOption) error
    Patch(ctx context.Context, obj Object, patch Patch, opts ...PatchOption) error
    DeleteAllOf(ctx context.Context, obj Object, opts ...DeleteAllOfOption) error
}
// StatusClient knows how to create a client which can update status subresource
// for kubernetes objects.
type StatusClient interface {
    Status() StatusWriter
}

// StatusWriter knows how to update status subresource of a Kubernetes object.
type StatusWriter interface {
    Update(ctx context.Context, obj Object, opts ...UpdateOption) error
    Patch(ctx context.Context, obj Object, patch Patch, opts ...PatchOption) error
}

delegatingClient

type delegatingClient struct {
    Reader
    Writer
    StatusClient

    scheme *runtime.Scheme
    mapper meta.RESTMapper
}

There's a function called shouldBypassCache to check if the target object is cached or not. If cached, call cacheReader, otherwise call clientReader

How client is used

  1. When a Manager is created, a Cluster is created internally. (You can check more details in cluster)
  2. When creating a cluster, client is also created. If Options.NewClient is not specified DefaultNewClient is used, which calls NewDelegatingClient to return a client.

    if options.NewClient == nil {
        options.NewClient = DefaultNewClient
    }
    
    writeObj, err := options.NewClient(cache, config, clientOptions, options.ClientDisableCacheFor...)
    
  3. In DefaultNewClient, new client is created first, and then delegatingClient is created.

    c, err := client.New(config, options)
    

    client.NewDelegatingClient(client.NewDelegatingClientInput{
        CacheReader:     cache,
        Client:          c,
        UncachedObjects: uncachedObjects,
    })
    

    As you can see, there's a struct for the input:

    // NewDelegatingClientInput encapsulates the input parameters to create a new delegating client.
    type NewDelegatingClientInput struct {
        CacheReader       Reader
        Client            Client
        UncachedObjects   []Object
        CacheUnstructured bool
    }
    

  4. delegatingClient is initialized in NewDelegatingClient

    Three roles: 1. Reader: client + cache <- utilize the cache to reduce API requests (Get and List) 1. Writer: client (Create, Update, Delete, etc) 1. StatusClient: client (Status().Update() or Status().Patch())

    &delegatingClient{
        scheme: in.Client.Scheme(),
        mapper: in.Client.RESTMapper(),
        Reader: &delegatingReader{
            CacheReader:       in.CacheReader,
            ClientReader:      in.Client,
            scheme:            in.Client.Scheme(),
            uncachedGVKs:      uncachedGVKs,
            cacheUnstructured: in.CacheUnstructured,
        },
        Writer:       in.Client,
        StatusClient: in.Client,
    }
    

    cacheReader:

    // CacheReader wraps a cache.Index to implement the client.CacheReader interface for a single type.
    type CacheReader struct {
        indexer cache.Indexer
        groupVersionKind schema.GroupVersionKind
        scopeName apimeta.RESTScopeName
        disableDeepCopy bool
    }
    

New

func newClient(config *rest.Config, options Options) (*client, error) {
c := &client{
    typedClient: typedClient{
        cache:      clientcache,
        paramCodec: runtime.NewParameterCodec(options.Scheme),
    },
    unstructuredClient: unstructuredClient{
        cache:      clientcache,
        paramCodec: noConversionParamCodec{},
    },
    metadataClient: metadataClient{
        client:     rawMetaClient,
        restMapper: options.Mapper,
    },
    scheme: options.Scheme,
    mapper: options.Mapper,
}

Tips

  1. https://zoetrope.github.io/kubebuilder-training/controller-runtime/client.html: When to use Patch? MergeFrom vs. StrategicMergeFrom