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
- When a Manager is created, a Cluster is created internally. (You can check more details in cluster)
-
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...)
-
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 }
-
delegatingClient
is initialized in NewDelegatingClientThree roles: 1.
Reader
: client + cache <- utilize the cache to reduce API requests (Get
andList
) 1.Writer
: client (Create
,Update
,Delete
, etc) 1.StatusClient
: client (Status().Update()
orStatus().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 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
- https://zoetrope.github.io/kubebuilder-training/controller-runtime/client.html: When to use
Patch
?MergeFrom
vs.StrategicMergeFrom