client
These implementation notes and the diagram target controller-runtime v0.25.1, pinned in the repository's go.mod. Use the signatures and call paths below for this version.
Client interface
Client combines object reads, writes, status operations, and arbitrary subresources with Scheme/RESTMapper access. The concrete object can be typed, unstructured, or metadata-only.
type Client interface {
Reader
Writer
StatusClient
SubResourceClientConstructor
Scheme() *runtime.Scheme
RESTMapper() meta.RESTMapper
GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error)
IsObjectNamespaced(obj runtime.Object) (bool, error)
}
type Reader interface {
Get(ctx context.Context, key ObjectKey, obj Object, opts ...GetOption) error
List(ctx context.Context, list ObjectList, opts ...ListOption) error
}
type Writer interface {
Apply(ctx context.Context, obj runtime.ApplyConfiguration, opts ...ApplyOption) error
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
}
type StatusClient interface {
Status() SubResourceWriter
}
Status() returns a SubResourceWriter (StatusWriter is an alias). Status updates target /status; they are separate from updating the parent resource.
type SubResourceWriter interface {
Create(ctx context.Context, obj Object, subResource Object, opts ...SubResourceCreateOption) error
Update(ctx context.Context, obj Object, opts ...SubResourceUpdateOption) error
Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error
Apply(ctx context.Context, obj runtime.ApplyConfiguration, opts ...SubResourceApplyOption) error
}
delegatingClient migration: cache-backed client
The old delegatingClient, delegatingReader, and NewDelegatingClient from split.go have been replaced by cache options on client.New. The read/write split is now implemented inside client:
type client struct {
typedClient typedClient
unstructuredClient unstructuredClient
metadataClient metadataClient
scheme *runtime.Scheme
mapper meta.RESTMapper
cache Reader
uncachedGVKs map[schema.GroupVersionKind]struct{}
cacheUnstructured bool
}
shouldBypassCache selects the read path. No cache means direct reads. Types in CacheOptions.DisableFor bypass it. Unstructured objects bypass it unless CacheOptions.Unstructured is true. List routing resolves the corresponding item GVK before checking exclusions.
func (c *client) shouldBypassCache(obj runtime.Object) (bool, error) {
if c.cache == nil {
return true, nil
}
gvk, err := c.GroupVersionKindFor(obj)
if err != nil {
return false, err
}
if meta.IsListType(obj) {
gvk.Kind = strings.TrimSuffix(gvk.Kind, "List")
}
if _, isUncached := c.uncachedGVKs[gvk]; isUncached {
return true, nil
}
if !c.cacheUnstructured {
_, isUnstructured := obj.(runtime.Unstructured)
return isUnstructured, nil
}
return false, nil
}
How client is used
manager.Newcreates a Cluster.cluster.Newcreates a Cache, fillsclient.Options, and passes the cache asCacheOptions.Readertoclient.New.mgr.GetClient()returns that client. A reconciler receives it explicitly, for example&Reconciler{Client: mgr.GetClient()}.Get/Listdelegate to the cache unless bypass rules apply. The cache's per-GVKCacheReaderreads a client-go Indexer. It does not perform one HTTP request per Get.Create,Update,Patch,Delete, and status operations use the API server directly. Informers observe these writes asynchronously.mgr.GetAPIReader()is a separate Reader constructed without cache options. Use it when a particular read must bypass the informer cache.
The constructor options replacing NewDelegatingClientInput are:
type CacheOptions struct {
Reader Reader
DisableFor []Object
Unstructured bool
EnableReadYourWritesConsistency *bool
}
The per-GVK reader keeps the Indexer, GVK, scope, and copy policy together:
type CacheReader struct {
indexer cache.Indexer
groupVersionKind schema.GroupVersionKind
scopeName apimeta.RESTScopeName
disableDeepCopy bool
}
Cached reads can be stale after a successful write. Default cache reads deep-copy objects; enabling unsafe no-copy options makes the caller responsible for copying before mutations. EnableReadYourWritesConsistency is experimental and disabled by default in this version.
New
The public constructor creates the internal client and applies optional wrappers:
func New(config *rest.Config, options Options) (Client, error) {
_, c, err := newClient(config, options)
if err != nil {
return nil, err
}
return wrapClient(c, options), nil
}
Internally, newClient(config *rest.Config, options Options) (*client, Client, error):
- Validates the config, copies it, and initializes HTTPClient, Scheme, and Mapper defaults.
- Builds REST client resources and a metadata client.
- Builds
typedClientwithruntime.NewParameterCodec(options.Scheme),unstructuredClientwith its no-conversion codec, andmetadataClientwith the RESTMapper. - Sets
cache, the set of uncached GVKs, and the unstructured caching policy fromoptions.Cache. - Returns the internal client plus the client used by wrappers. The public
Newapplies DryRun, FieldOwner, and FieldValidation options throughwrapClient.
A standalone client.New(cfg, client.Options{Scheme: scheme}) has no informer cache. To build one outside a Manager, pass a started/synchronized cache as CacheOptions.Reader and manage its lifecycle yourself.
Tips
- Use
client.MergeFrom(before)for a JSON merge patch of only changed fields. Copybeforebefore mutating the working object. StrategicMergeFromdepends on strategic merge support and is not generally available for custom resources.- Ignore NotFound when reconciliation has nothing left to do. Return other errors for the controller's retry policy.
- Avoid writing when the desired value already matches; repeated writes generate more watch events.
- See the ReplicaSet example and the original Patch guide.