Bump sigs.k8s.io/controller-runtime to v0.14.4 (#5507)

* Bump sigs.k8s.io/controller-runtime to v0.14.4

* Update gofmt
This commit is contained in:
hongming
2023-02-08 14:06:15 +08:00
committed by GitHub
parent 129e6fbec3
commit 1c49fcd57e
1404 changed files with 141422 additions and 47769 deletions

View File

@@ -81,7 +81,7 @@ func GVKForObject(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersi
// (unstructured, partial, etc)
// check for PartialObjectMetadata, which is analogous to unstructured, but isn't handled by ObjectKinds
_, isPartial := obj.(*metav1.PartialObjectMetadata) //nolint:ifshort
_, isPartial := obj.(*metav1.PartialObjectMetadata)
_, isPartialList := obj.(*metav1.PartialObjectMetadataList)
if isPartial || isPartialList {
// we require that the GVK be populated in order to recognize the object

View File

@@ -17,7 +17,6 @@ limitations under the License.
package apiutil
import (
"errors"
"sync"
"sync/atomic"
@@ -41,6 +40,8 @@ type dynamicRESTMapper struct {
// Used for lazy init.
inited uint32
initMtx sync.Mutex
useLazyRestmapper bool
}
// DynamicRESTMapperOption is a functional option on the dynamicRESTMapper.
@@ -61,6 +62,12 @@ var WithLazyDiscovery DynamicRESTMapperOption = func(drm *dynamicRESTMapper) err
return nil
}
// WithExperimentalLazyMapper enables experimental more advanced Lazy Restmapping mechanism.
var WithExperimentalLazyMapper DynamicRESTMapperOption = func(drm *dynamicRESTMapper) error {
drm.useLazyRestmapper = true
return nil
}
// WithCustomMapper supports setting a custom RESTMapper refresher instead of
// the default method, which uses a discovery client.
//
@@ -96,6 +103,9 @@ func NewDynamicRESTMapper(cfg *rest.Config, opts ...DynamicRESTMapperOption) (me
return nil, err
}
}
if drm.useLazyRestmapper {
return newLazyRESTMapperWithClient(client)
}
if !drm.lazy {
if err := drm.setStaticMapper(); err != nil {
return nil, err
@@ -145,7 +155,7 @@ func (drm *dynamicRESTMapper) init() (err error) {
// checkAndReload attempts to call the given callback, which is assumed to be dependent
// on the data in the restmapper.
//
// If the callback returns an error that matches the given error, it will attempt to reload
// If the callback returns an error matching meta.IsNoMatchErr, it will attempt to reload
// the RESTMapper's data and re-call the callback once that's occurred.
// If the callback returns any other error, the function will return immediately regardless.
//
@@ -154,7 +164,7 @@ func (drm *dynamicRESTMapper) init() (err error) {
// the callback.
// It's thread-safe, and worries about thread-safety for the callback (so the callback does
// not need to attempt to lock the restmapper).
func (drm *dynamicRESTMapper) checkAndReload(needsReloadErr error, checkNeedsReload func() error) error {
func (drm *dynamicRESTMapper) checkAndReload(checkNeedsReload func() error) error {
// first, check the common path -- data is fresh enough
// (use an IIFE for the lock's defer)
err := func() error {
@@ -164,10 +174,7 @@ func (drm *dynamicRESTMapper) checkAndReload(needsReloadErr error, checkNeedsRel
return checkNeedsReload()
}()
// NB(directxman12): `Is` and `As` have a confusing relationship --
// `Is` is like `== or does this implement .Is`, whereas `As` says
// `can I type-assert into`
needsReload := errors.As(err, &needsReloadErr)
needsReload := meta.IsNoMatchError(err)
if !needsReload {
return err
}
@@ -178,7 +185,7 @@ func (drm *dynamicRESTMapper) checkAndReload(needsReloadErr error, checkNeedsRel
// ... and double-check that we didn't reload in the meantime
err = checkNeedsReload()
needsReload = errors.As(err, &needsReloadErr)
needsReload = meta.IsNoMatchError(err)
if !needsReload {
return err
}
@@ -206,7 +213,7 @@ func (drm *dynamicRESTMapper) KindFor(resource schema.GroupVersionResource) (sch
return schema.GroupVersionKind{}, err
}
var gvk schema.GroupVersionKind
err := drm.checkAndReload(&meta.NoResourceMatchError{}, func() error {
err := drm.checkAndReload(func() error {
var err error
gvk, err = drm.staticMapper.KindFor(resource)
return err
@@ -219,7 +226,7 @@ func (drm *dynamicRESTMapper) KindsFor(resource schema.GroupVersionResource) ([]
return nil, err
}
var gvks []schema.GroupVersionKind
err := drm.checkAndReload(&meta.NoResourceMatchError{}, func() error {
err := drm.checkAndReload(func() error {
var err error
gvks, err = drm.staticMapper.KindsFor(resource)
return err
@@ -233,7 +240,7 @@ func (drm *dynamicRESTMapper) ResourceFor(input schema.GroupVersionResource) (sc
}
var gvr schema.GroupVersionResource
err := drm.checkAndReload(&meta.NoResourceMatchError{}, func() error {
err := drm.checkAndReload(func() error {
var err error
gvr, err = drm.staticMapper.ResourceFor(input)
return err
@@ -246,7 +253,7 @@ func (drm *dynamicRESTMapper) ResourcesFor(input schema.GroupVersionResource) ([
return nil, err
}
var gvrs []schema.GroupVersionResource
err := drm.checkAndReload(&meta.NoResourceMatchError{}, func() error {
err := drm.checkAndReload(func() error {
var err error
gvrs, err = drm.staticMapper.ResourcesFor(input)
return err
@@ -259,7 +266,7 @@ func (drm *dynamicRESTMapper) RESTMapping(gk schema.GroupKind, versions ...strin
return nil, err
}
var mapping *meta.RESTMapping
err := drm.checkAndReload(&meta.NoKindMatchError{}, func() error {
err := drm.checkAndReload(func() error {
var err error
mapping, err = drm.staticMapper.RESTMapping(gk, versions...)
return err
@@ -272,7 +279,7 @@ func (drm *dynamicRESTMapper) RESTMappings(gk schema.GroupKind, versions ...stri
return nil, err
}
var mappings []*meta.RESTMapping
err := drm.checkAndReload(&meta.NoKindMatchError{}, func() error {
err := drm.checkAndReload(func() error {
var err error
mappings, err = drm.staticMapper.RESTMappings(gk, versions...)
return err
@@ -285,7 +292,7 @@ func (drm *dynamicRESTMapper) ResourceSingularizer(resource string) (string, err
return "", err
}
var singular string
err := drm.checkAndReload(&meta.NoResourceMatchError{}, func() error {
err := drm.checkAndReload(func() error {
var err error
singular, err = drm.staticMapper.ResourceSingularizer(resource)
return err

View File

@@ -0,0 +1,248 @@
/*
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apiutil
import (
"fmt"
"sync"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery"
"k8s.io/client-go/restmapper"
)
// lazyRESTMapper is a RESTMapper that will lazily query the provided
// client for discovery information to do REST mappings.
type lazyRESTMapper struct {
mapper meta.RESTMapper
client *discovery.DiscoveryClient
knownGroups map[string]*restmapper.APIGroupResources
apiGroups *metav1.APIGroupList
// mutex to provide thread-safe mapper reloading.
mu sync.Mutex
}
// newLazyRESTMapperWithClient initializes a LazyRESTMapper with a custom discovery client.
func newLazyRESTMapperWithClient(discoveryClient *discovery.DiscoveryClient) (meta.RESTMapper, error) {
return &lazyRESTMapper{
mapper: restmapper.NewDiscoveryRESTMapper([]*restmapper.APIGroupResources{}),
client: discoveryClient,
knownGroups: map[string]*restmapper.APIGroupResources{},
}, nil
}
// KindFor implements Mapper.KindFor.
func (m *lazyRESTMapper) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) {
res, err := m.mapper.KindFor(resource)
if meta.IsNoMatchError(err) {
if err = m.addKnownGroupAndReload(resource.Group, resource.Version); err != nil {
return res, err
}
res, err = m.mapper.KindFor(resource)
}
return res, err
}
// KindsFor implements Mapper.KindsFor.
func (m *lazyRESTMapper) KindsFor(resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error) {
res, err := m.mapper.KindsFor(resource)
if meta.IsNoMatchError(err) {
if err = m.addKnownGroupAndReload(resource.Group, resource.Version); err != nil {
return res, err
}
res, err = m.mapper.KindsFor(resource)
}
return res, err
}
// ResourceFor implements Mapper.ResourceFor.
func (m *lazyRESTMapper) ResourceFor(input schema.GroupVersionResource) (schema.GroupVersionResource, error) {
res, err := m.mapper.ResourceFor(input)
if meta.IsNoMatchError(err) {
if err = m.addKnownGroupAndReload(input.Group, input.Version); err != nil {
return res, err
}
res, err = m.mapper.ResourceFor(input)
}
return res, err
}
// ResourcesFor implements Mapper.ResourcesFor.
func (m *lazyRESTMapper) ResourcesFor(input schema.GroupVersionResource) ([]schema.GroupVersionResource, error) {
res, err := m.mapper.ResourcesFor(input)
if meta.IsNoMatchError(err) {
if err = m.addKnownGroupAndReload(input.Group, input.Version); err != nil {
return res, err
}
res, err = m.mapper.ResourcesFor(input)
}
return res, err
}
// RESTMapping implements Mapper.RESTMapping.
func (m *lazyRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*meta.RESTMapping, error) {
res, err := m.mapper.RESTMapping(gk, versions...)
if meta.IsNoMatchError(err) {
if err = m.addKnownGroupAndReload(gk.Group, versions...); err != nil {
return res, err
}
res, err = m.mapper.RESTMapping(gk, versions...)
}
return res, err
}
// RESTMappings implements Mapper.RESTMappings.
func (m *lazyRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) ([]*meta.RESTMapping, error) {
res, err := m.mapper.RESTMappings(gk, versions...)
if meta.IsNoMatchError(err) {
if err = m.addKnownGroupAndReload(gk.Group, versions...); err != nil {
return res, err
}
res, err = m.mapper.RESTMappings(gk, versions...)
}
return res, err
}
// ResourceSingularizer implements Mapper.ResourceSingularizer.
func (m *lazyRESTMapper) ResourceSingularizer(resource string) (string, error) {
return m.mapper.ResourceSingularizer(resource)
}
// addKnownGroupAndReload reloads the mapper with updated information about missing API group.
// versions can be specified for partial updates, for instance for v1beta1 version only.
func (m *lazyRESTMapper) addKnownGroupAndReload(groupName string, versions ...string) error {
m.mu.Lock()
defer m.mu.Unlock()
// If no specific versions are set by user, we will scan all available ones for the API group.
// This operation requires 2 requests: /api and /apis, but only once. For all subsequent calls
// this data will be taken from cache.
if len(versions) == 0 {
apiGroup, err := m.findAPIGroupByName(groupName)
if err != nil {
return err
}
for _, version := range apiGroup.Versions {
versions = append(versions, version.Version)
}
}
// Create or fetch group resources from cache.
groupResources := &restmapper.APIGroupResources{
Group: metav1.APIGroup{Name: groupName},
VersionedResources: make(map[string][]metav1.APIResource),
}
if _, ok := m.knownGroups[groupName]; ok {
groupResources = m.knownGroups[groupName]
}
// Update information for group resources about versioned resources.
// The number of API calls is equal to the number of versions: /apis/<group>/<version>.
groupVersionResources, err := m.fetchGroupVersionResources(groupName, versions...)
if err != nil {
return fmt.Errorf("failed to get API group resources: %w", err)
}
for version, resources := range groupVersionResources {
groupResources.VersionedResources[version.Version] = resources.APIResources
}
// Update information for group resources about the API group by adding new versions.
for _, version := range versions {
groupResources.Group.Versions = append(groupResources.Group.Versions, metav1.GroupVersionForDiscovery{
GroupVersion: metav1.GroupVersion{Group: groupName, Version: version}.String(),
Version: version,
})
}
// Update data in the cache.
m.knownGroups[groupName] = groupResources
// Finally, update the group with received information and regenerate the mapper.
updatedGroupResources := make([]*restmapper.APIGroupResources, 0, len(m.knownGroups))
for _, agr := range m.knownGroups {
updatedGroupResources = append(updatedGroupResources, agr)
}
m.mapper = restmapper.NewDiscoveryRESTMapper(updatedGroupResources)
return nil
}
// findAPIGroupByName returns API group by its name.
func (m *lazyRESTMapper) findAPIGroupByName(groupName string) (metav1.APIGroup, error) {
// Ensure that required info about existing API groups is received and stored in the mapper.
// It will make 2 API calls to /api and /apis, but only once.
if m.apiGroups == nil {
apiGroups, err := m.client.ServerGroups()
if err != nil {
return metav1.APIGroup{}, fmt.Errorf("failed to get server groups: %w", err)
}
if len(apiGroups.Groups) == 0 {
return metav1.APIGroup{}, fmt.Errorf("received an empty API groups list")
}
m.apiGroups = apiGroups
}
for i := range m.apiGroups.Groups {
if groupName == (&m.apiGroups.Groups[i]).Name {
return m.apiGroups.Groups[i], nil
}
}
return metav1.APIGroup{}, fmt.Errorf("failed to find API group %s", groupName)
}
// fetchGroupVersionResources fetches the resources for the specified group and its versions.
func (m *lazyRESTMapper) fetchGroupVersionResources(groupName string, versions ...string) (map[schema.GroupVersion]*metav1.APIResourceList, error) {
groupVersionResources := make(map[schema.GroupVersion]*metav1.APIResourceList)
failedGroups := make(map[schema.GroupVersion]error)
for _, version := range versions {
groupVersion := schema.GroupVersion{Group: groupName, Version: version}
apiResourceList, err := m.client.ServerResourcesForGroupVersion(groupVersion.String())
if err != nil {
failedGroups[groupVersion] = err
}
if apiResourceList != nil {
// even in case of error, some fallback might have been returned.
groupVersionResources[groupVersion] = apiResourceList
}
}
if len(failedGroups) > 0 {
return nil, &discovery.ErrGroupDiscoveryFailed{Groups: failedGroups}
}
return groupVersionResources, nil
}

View File

@@ -18,6 +18,7 @@ package client
import (
"context"
"errors"
"fmt"
"strings"
@@ -45,7 +46,7 @@ type WarningHandlerOptions struct {
// AllowDuplicateLogs does not deduplicate the to-be
// logged surfaced warnings messages. See
// log.WarningHandlerOptions for considerations
// regarding deuplication
// regarding deduplication
AllowDuplicateLogs bool
}
@@ -88,13 +89,12 @@ func newClient(config *rest.Config, options Options) (*client, error) {
// is log.KubeAPIWarningLogger with deduplication enabled.
// See log.KubeAPIWarningLoggerOptions for considerations
// regarding deduplication.
rest.SetDefaultWarningHandler(
log.NewKubeAPIWarningLogger(
logger,
log.KubeAPIWarningLoggerOptions{
Deduplicate: !options.Opts.AllowDuplicateLogs,
},
),
config = rest.CopyConfig(config)
config.WarningHandler = log.NewKubeAPIWarningLogger(
logger,
log.KubeAPIWarningLoggerOptions{
Deduplicate: !options.Opts.AllowDuplicateLogs,
},
)
}
@@ -241,16 +241,16 @@ func (c *client) Patch(ctx context.Context, obj Object, patch Patch, opts ...Pat
}
// Get implements client.Client.
func (c *client) Get(ctx context.Context, key ObjectKey, obj Object) error {
func (c *client) Get(ctx context.Context, key ObjectKey, obj Object, opts ...GetOption) error {
switch obj.(type) {
case *unstructured.Unstructured:
return c.unstructuredClient.Get(ctx, key, obj)
return c.unstructuredClient.Get(ctx, key, obj, opts...)
case *metav1.PartialObjectMetadata:
// Metadata only object should always preserve the GVK coming in from the caller.
defer c.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind())
return c.metadataClient.Get(ctx, key, obj)
return c.metadataClient.Get(ctx, key, obj, opts...)
default:
return c.typedClient.Get(ctx, key, obj)
return c.typedClient.Get(ctx, key, obj, opts...)
}
}
@@ -289,40 +289,194 @@ func (c *client) List(ctx context.Context, obj ObjectList, opts ...ListOption) e
}
// Status implements client.StatusClient.
func (c *client) Status() StatusWriter {
return &statusWriter{client: c}
func (c *client) Status() SubResourceWriter {
return c.SubResource("status")
}
// statusWriter is client.StatusWriter that writes status subresource.
type statusWriter struct {
client *client
func (c *client) SubResource(subResource string) SubResourceClient {
return &subResourceClient{client: c, subResource: subResource}
}
// ensure statusWriter implements client.StatusWriter.
var _ StatusWriter = &statusWriter{}
// subResourceClient is client.SubResourceWriter that writes to subresources.
type subResourceClient struct {
client *client
subResource string
}
// Update implements client.StatusWriter.
func (sw *statusWriter) Update(ctx context.Context, obj Object, opts ...UpdateOption) error {
defer sw.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind())
// ensure subResourceClient implements client.SubResourceClient.
var _ SubResourceClient = &subResourceClient{}
// SubResourceGetOptions holds all the possible configuration
// for a subresource Get request.
type SubResourceGetOptions struct {
Raw *metav1.GetOptions
}
// ApplyToSubResourceGet updates the configuaration to the given get options.
func (getOpt *SubResourceGetOptions) ApplyToSubResourceGet(o *SubResourceGetOptions) {
if getOpt.Raw != nil {
o.Raw = getOpt.Raw
}
}
// ApplyOptions applues the given options.
func (getOpt *SubResourceGetOptions) ApplyOptions(opts []SubResourceGetOption) *SubResourceGetOptions {
for _, o := range opts {
o.ApplyToSubResourceGet(getOpt)
}
return getOpt
}
// AsGetOptions returns the configured options as *metav1.GetOptions.
func (getOpt *SubResourceGetOptions) AsGetOptions() *metav1.GetOptions {
if getOpt.Raw == nil {
return &metav1.GetOptions{}
}
return getOpt.Raw
}
// SubResourceUpdateOptions holds all the possible configuration
// for a subresource update request.
type SubResourceUpdateOptions struct {
UpdateOptions
SubResourceBody Object
}
// ApplyToSubResourceUpdate updates the configuration on the given create options
func (uo *SubResourceUpdateOptions) ApplyToSubResourceUpdate(o *SubResourceUpdateOptions) {
uo.UpdateOptions.ApplyToUpdate(&o.UpdateOptions)
if uo.SubResourceBody != nil {
o.SubResourceBody = uo.SubResourceBody
}
}
// ApplyOptions applies the given options.
func (uo *SubResourceUpdateOptions) ApplyOptions(opts []SubResourceUpdateOption) *SubResourceUpdateOptions {
for _, o := range opts {
o.ApplyToSubResourceUpdate(uo)
}
return uo
}
// SubResourceUpdateAndPatchOption is an option that can be used for either
// a subresource update or patch request.
type SubResourceUpdateAndPatchOption interface {
SubResourceUpdateOption
SubResourcePatchOption
}
// WithSubResourceBody returns an option that uses the given body
// for a subresource Update or Patch operation.
func WithSubResourceBody(body Object) SubResourceUpdateAndPatchOption {
return &withSubresourceBody{body: body}
}
type withSubresourceBody struct {
body Object
}
func (wsr *withSubresourceBody) ApplyToSubResourceUpdate(o *SubResourceUpdateOptions) {
o.SubResourceBody = wsr.body
}
func (wsr *withSubresourceBody) ApplyToSubResourcePatch(o *SubResourcePatchOptions) {
o.SubResourceBody = wsr.body
}
// SubResourceCreateOptions are all the possible configurations for a subresource
// create request.
type SubResourceCreateOptions struct {
CreateOptions
}
// ApplyOptions applies the given options.
func (co *SubResourceCreateOptions) ApplyOptions(opts []SubResourceCreateOption) *SubResourceCreateOptions {
for _, o := range opts {
o.ApplyToSubResourceCreate(co)
}
return co
}
// ApplyToSubresourceCreate applies the the configuration on the given create options.
func (co *SubResourceCreateOptions) ApplyToSubresourceCreate(o *SubResourceCreateOptions) {
co.CreateOptions.ApplyToCreate(&co.CreateOptions)
}
// SubResourcePatchOptions holds all possible configurations for a subresource patch
// request.
type SubResourcePatchOptions struct {
PatchOptions
SubResourceBody Object
}
// ApplyOptions applies the given options.
func (po *SubResourcePatchOptions) ApplyOptions(opts []SubResourcePatchOption) *SubResourcePatchOptions {
for _, o := range opts {
o.ApplyToSubResourcePatch(po)
}
return po
}
// ApplyToSubResourcePatch applies the configuration on the given patch options.
func (po *SubResourcePatchOptions) ApplyToSubResourcePatch(o *SubResourcePatchOptions) {
po.PatchOptions.ApplyToPatch(&o.PatchOptions)
if po.SubResourceBody != nil {
o.SubResourceBody = po.SubResourceBody
}
}
func (sc *subResourceClient) Get(ctx context.Context, obj Object, subResource Object, opts ...SubResourceGetOption) error {
switch obj.(type) {
case *unstructured.Unstructured:
return sw.client.unstructuredClient.UpdateStatus(ctx, obj, opts...)
return sc.client.unstructuredClient.GetSubResource(ctx, obj, subResource, sc.subResource, opts...)
case *metav1.PartialObjectMetadata:
return errors.New("can not get subresource using only metadata")
default:
return sc.client.typedClient.GetSubResource(ctx, obj, subResource, sc.subResource, opts...)
}
}
// Create implements client.SubResourceClient
func (sc *subResourceClient) Create(ctx context.Context, obj Object, subResource Object, opts ...SubResourceCreateOption) error {
defer sc.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind())
defer sc.client.resetGroupVersionKind(subResource, subResource.GetObjectKind().GroupVersionKind())
switch obj.(type) {
case *unstructured.Unstructured:
return sc.client.unstructuredClient.CreateSubResource(ctx, obj, subResource, sc.subResource, opts...)
case *metav1.PartialObjectMetadata:
return fmt.Errorf("cannot update status using only metadata -- did you mean to patch?")
default:
return sw.client.typedClient.UpdateStatus(ctx, obj, opts...)
return sc.client.typedClient.CreateSubResource(ctx, obj, subResource, sc.subResource, opts...)
}
}
// Patch implements client.Client.
func (sw *statusWriter) Patch(ctx context.Context, obj Object, patch Patch, opts ...PatchOption) error {
defer sw.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind())
// Update implements client.SubResourceClient
func (sc *subResourceClient) Update(ctx context.Context, obj Object, opts ...SubResourceUpdateOption) error {
defer sc.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind())
switch obj.(type) {
case *unstructured.Unstructured:
return sw.client.unstructuredClient.PatchStatus(ctx, obj, patch, opts...)
return sc.client.unstructuredClient.UpdateSubResource(ctx, obj, sc.subResource, opts...)
case *metav1.PartialObjectMetadata:
return sw.client.metadataClient.PatchStatus(ctx, obj, patch, opts...)
return fmt.Errorf("cannot update status using only metadata -- did you mean to patch?")
default:
return sw.client.typedClient.PatchStatus(ctx, obj, patch, opts...)
return sc.client.typedClient.UpdateSubResource(ctx, obj, sc.subResource, opts...)
}
}
// Patch implements client.SubResourceWriter.
func (sc *subResourceClient) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error {
defer sc.client.resetGroupVersionKind(obj, obj.GetObjectKind().GroupVersionKind())
switch obj.(type) {
case *unstructured.Unstructured:
return sc.client.unstructuredClient.PatchSubResource(ctx, obj, sc.subResource, patch, opts...)
case *metav1.PartialObjectMetadata:
return sc.client.metadataClient.PatchSubResource(ctx, obj, sc.subResource, patch, opts...)
default:
return sc.client.typedClient.PatchSubResource(ctx, obj, sc.subResource, patch, opts...)
}
}

View File

@@ -29,15 +29,32 @@ import (
logf "sigs.k8s.io/controller-runtime/pkg/internal/log"
)
// KubeconfigFlagName is the name of the kubeconfig flag
const KubeconfigFlagName = "kubeconfig"
var (
kubeconfig string
log = logf.RuntimeLog.WithName("client").WithName("config")
)
// init registers the "kubeconfig" flag to the default command line FlagSet.
// TODO: This should be removed, as it potentially leads to redefined flag errors for users, if they already
// have registered the "kubeconfig" flag to the command line FlagSet in other parts of their code.
func init() {
// TODO: Fix this to allow double vendoring this library but still register flags on behalf of users
flag.StringVar(&kubeconfig, "kubeconfig", "",
"Paths to a kubeconfig. Only required if out-of-cluster.")
RegisterFlags(flag.CommandLine)
}
// RegisterFlags registers flag variables to the given FlagSet if not already registered.
// It uses the default command line FlagSet, if none is provided. Currently, it only registers the kubeconfig flag.
func RegisterFlags(fs *flag.FlagSet) {
if fs == nil {
fs = flag.CommandLine
}
if f := fs.Lookup(KubeconfigFlagName); f != nil {
kubeconfig = f.Value.String()
} else {
fs.StringVar(&kubeconfig, KubeconfigFlagName, "", "Paths to a kubeconfig. Only required if out-of-cluster.")
}
}
// GetConfig creates a *rest.Config for talking to a Kubernetes API server.
@@ -47,7 +64,7 @@ func init() {
// It also applies saner defaults for QPS and burst based on the Kubernetes
// controller manager defaults (20 QPS, 30 burst)
//
// Config precedence
// Config precedence:
//
// * --kubeconfig flag pointing at a file
//
@@ -67,7 +84,7 @@ func GetConfig() (*rest.Config, error) {
// It also applies saner defaults for QPS and burst based on the Kubernetes
// controller manager defaults (20 QPS, 30 burst)
//
// Config precedence
// Config precedence:
//
// * --kubeconfig flag pointing at a file
//
@@ -96,7 +113,7 @@ func GetConfigWithContext(context string) (*rest.Config, error) {
var loadInClusterConfig = rest.InClusterConfig
// loadConfig loads a REST Config as per the rules specified in GetConfig.
func loadConfig(context string) (*rest.Config, error) {
func loadConfig(context string) (config *rest.Config, configErr error) {
// If a flag is specified with the config location, use that
if len(kubeconfig) > 0 {
return loadConfigWithContext("", &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig}, context)
@@ -106,9 +123,16 @@ func loadConfig(context string) (*rest.Config, error) {
// try the in-cluster config.
kubeconfigPath := os.Getenv(clientcmd.RecommendedConfigPathEnvVar)
if len(kubeconfigPath) == 0 {
if c, err := loadInClusterConfig(); err == nil {
c, err := loadInClusterConfig()
if err == nil {
return c, nil
}
defer func() {
if configErr != nil {
log.Error(err, "unable to load in-cluster config")
}
}()
}
// If the recommended kubeconfig env variable is set, or there

View File

@@ -17,7 +17,7 @@ limitations under the License.
// Package client contains functionality for interacting with Kubernetes API
// servers.
//
// Clients
// # Clients
//
// Clients are split into two interfaces -- Readers and Writers. Readers
// get and list, while writers create, update, and delete.
@@ -25,18 +25,19 @@ limitations under the License.
// The New function can be used to create a new client that talks directly
// to the API server.
//
// A common pattern in Kubernetes to read from a cache and write to the API
// It is a common pattern in Kubernetes to read from a cache and write to the API
// server. This pattern is covered by the DelegatingClient type, which can
// be used to have a client whose Reader is different from the Writer.
//
// Options
// # Options
//
// Many client operations in Kubernetes support options. These options are
// represented as variadic arguments at the end of a given method call.
// For instance, to use a label selector on list, you can call
// err := someReader.List(context.Background(), &podList, client.MatchingLabels{"somelabel": "someval"})
//
// Indexing
// err := someReader.List(context.Background(), &podList, client.MatchingLabels{"somelabel": "someval"})
//
// # Indexing
//
// Indexes may be added to caches using a FieldIndexer. This allows you to easily
// and efficiently look up objects with certain properties. You can then make

View File

@@ -72,8 +72,8 @@ func (c *dryRunClient) Patch(ctx context.Context, obj Object, patch Patch, opts
}
// Get implements client.Client.
func (c *dryRunClient) Get(ctx context.Context, key ObjectKey, obj Object) error {
return c.client.Get(ctx, key, obj)
func (c *dryRunClient) Get(ctx context.Context, key ObjectKey, obj Object, opts ...GetOption) error {
return c.client.Get(ctx, key, obj, opts...)
}
// List implements client.Client.
@@ -82,25 +82,38 @@ func (c *dryRunClient) List(ctx context.Context, obj ObjectList, opts ...ListOpt
}
// Status implements client.StatusClient.
func (c *dryRunClient) Status() StatusWriter {
return &dryRunStatusWriter{client: c.client.Status()}
func (c *dryRunClient) Status() SubResourceWriter {
return c.SubResource("status")
}
// ensure dryRunStatusWriter implements client.StatusWriter.
var _ StatusWriter = &dryRunStatusWriter{}
// SubResource implements client.SubResourceClient.
func (c *dryRunClient) SubResource(subResource string) SubResourceClient {
return &dryRunSubResourceClient{client: c.client.SubResource(subResource)}
}
// dryRunStatusWriter is client.StatusWriter that writes status subresource with dryRun mode
// ensure dryRunSubResourceWriter implements client.SubResourceWriter.
var _ SubResourceWriter = &dryRunSubResourceClient{}
// dryRunSubResourceClient is client.SubResourceWriter that writes status subresource with dryRun mode
// enforced.
type dryRunStatusWriter struct {
client StatusWriter
type dryRunSubResourceClient struct {
client SubResourceClient
}
// Update implements client.StatusWriter.
func (sw *dryRunStatusWriter) Update(ctx context.Context, obj Object, opts ...UpdateOption) error {
func (sw *dryRunSubResourceClient) Get(ctx context.Context, obj, subResource Object, opts ...SubResourceGetOption) error {
return sw.client.Get(ctx, obj, subResource, opts...)
}
func (sw *dryRunSubResourceClient) Create(ctx context.Context, obj, subResource Object, opts ...SubResourceCreateOption) error {
return sw.client.Create(ctx, obj, subResource, append(opts, DryRunAll)...)
}
// Update implements client.SubResourceWriter.
func (sw *dryRunSubResourceClient) Update(ctx context.Context, obj Object, opts ...SubResourceUpdateOption) error {
return sw.client.Update(ctx, obj, append(opts, DryRunAll)...)
}
// Patch implements client.StatusWriter.
func (sw *dryRunStatusWriter) Patch(ctx context.Context, obj Object, patch Patch, opts ...PatchOption) error {
// Patch implements client.SubResourceWriter.
func (sw *dryRunSubResourceClient) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error {
return sw.client.Patch(ctx, obj, patch, append(opts, DryRunAll)...)
}

View File

@@ -30,6 +30,8 @@ import (
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
utilrand "k8s.io/apimachinery/pkg/util/rand"
@@ -37,6 +39,7 @@ import (
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/testing"
"sigs.k8s.io/controller-runtime/pkg/internal/field/selector"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
@@ -49,9 +52,14 @@ type versionedTracker struct {
}
type fakeClient struct {
tracker versionedTracker
scheme *runtime.Scheme
restMapper meta.RESTMapper
tracker versionedTracker
scheme *runtime.Scheme
restMapper meta.RESTMapper
// indexes maps each GroupVersionKind (GVK) to the indexes registered for that GVK.
// The inner map maps from index name to IndexerFunc.
indexes map[schema.GroupVersionKind]map[string]client.IndexerFunc
schemeWriteLock sync.Mutex
}
@@ -93,6 +101,10 @@ type ClientBuilder struct {
initLists []client.ObjectList
initRuntimeObjects []runtime.Object
objectTracker testing.ObjectTracker
// indexes maps each GroupVersionKind (GVK) to the indexes registered for that GVK.
// The inner map maps from index name to IndexerFunc.
indexes map[schema.GroupVersionKind]map[string]client.IndexerFunc
}
// WithScheme sets this builder's internal scheme.
@@ -135,6 +147,44 @@ func (f *ClientBuilder) WithObjectTracker(ot testing.ObjectTracker) *ClientBuild
return f
}
// WithIndex can be optionally used to register an index with name `field` and indexer `extractValue`
// for API objects of the same GroupVersionKind (GVK) as `obj` in the fake client.
// It can be invoked multiple times, both with objects of the same GVK or different ones.
// Invoking WithIndex twice with the same `field` and GVK (via `obj`) arguments will panic.
// WithIndex retrieves the GVK of `obj` using the scheme registered via WithScheme if
// WithScheme was previously invoked, the default scheme otherwise.
func (f *ClientBuilder) WithIndex(obj runtime.Object, field string, extractValue client.IndexerFunc) *ClientBuilder {
objScheme := f.scheme
if objScheme == nil {
objScheme = scheme.Scheme
}
gvk, err := apiutil.GVKForObject(obj, objScheme)
if err != nil {
panic(err)
}
// If this is the first index being registered, we initialize the map storing all the indexes.
if f.indexes == nil {
f.indexes = make(map[schema.GroupVersionKind]map[string]client.IndexerFunc)
}
// If this is the first index being registered for the GroupVersionKind of `obj`, we initialize
// the map storing the indexes for that GroupVersionKind.
if f.indexes[gvk] == nil {
f.indexes[gvk] = make(map[string]client.IndexerFunc)
}
if _, fieldAlreadyIndexed := f.indexes[gvk][field]; fieldAlreadyIndexed {
panic(fmt.Errorf("indexer conflict: field %s for GroupVersionKind %v is already indexed",
field, gvk))
}
f.indexes[gvk][field] = extractValue
return f
}
// Build builds and returns a new fake client.
func (f *ClientBuilder) Build() client.WithWatch {
if f.scheme == nil {
@@ -171,6 +221,7 @@ func (f *ClientBuilder) Build() client.WithWatch {
tracker: tracker,
scheme: f.scheme,
restMapper: f.restMapper,
indexes: f.indexes,
}
}
@@ -329,7 +380,7 @@ func (t versionedTracker) Update(gvr schema.GroupVersionResource, obj runtime.Ob
return t.ObjectTracker.Update(gvr, obj, ns)
}
func (c *fakeClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object) error {
func (c *fakeClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
gvr, err := getGVRFromObject(obj, c.scheme)
if err != nil {
return err
@@ -420,21 +471,88 @@ func (c *fakeClient) List(ctx context.Context, obj client.ObjectList, opts ...cl
return err
}
if listOpts.LabelSelector != nil {
objs, err := meta.ExtractList(obj)
if listOpts.LabelSelector == nil && listOpts.FieldSelector == nil {
return nil
}
// If we're here, either a label or field selector are specified (or both), so before we return
// the list we must filter it. If both selectors are set, they are ANDed.
objs, err := meta.ExtractList(obj)
if err != nil {
return err
}
filteredList, err := c.filterList(objs, gvk, listOpts.LabelSelector, listOpts.FieldSelector)
if err != nil {
return err
}
return meta.SetList(obj, filteredList)
}
func (c *fakeClient) filterList(list []runtime.Object, gvk schema.GroupVersionKind, ls labels.Selector, fs fields.Selector) ([]runtime.Object, error) {
// Filter the objects with the label selector
filteredList := list
if ls != nil {
objsFilteredByLabel, err := objectutil.FilterWithLabels(list, ls)
if err != nil {
return err
return nil, err
}
filteredObjs, err := objectutil.FilterWithLabels(objs, listOpts.LabelSelector)
filteredList = objsFilteredByLabel
}
// Filter the result of the previous pass with the field selector
if fs != nil {
objsFilteredByField, err := c.filterWithFields(filteredList, gvk, fs)
if err != nil {
return err
return nil, err
}
err = meta.SetList(obj, filteredObjs)
if err != nil {
return err
filteredList = objsFilteredByField
}
return filteredList, nil
}
func (c *fakeClient) filterWithFields(list []runtime.Object, gvk schema.GroupVersionKind, fs fields.Selector) ([]runtime.Object, error) {
// We only allow filtering on the basis of a single field to ensure consistency with the
// behavior of the cache reader (which we're faking here).
fieldKey, fieldVal, requiresExact := selector.RequiresExactMatch(fs)
if !requiresExact {
return nil, fmt.Errorf("field selector %s is not in one of the two supported forms \"key==val\" or \"key=val\"",
fs)
}
// Field selection is mimicked via indexes, so there's no sane answer this function can give
// if there are no indexes registered for the GroupVersionKind of the objects in the list.
indexes := c.indexes[gvk]
if len(indexes) == 0 || indexes[fieldKey] == nil {
return nil, fmt.Errorf("List on GroupVersionKind %v specifies selector on field %s, but no "+
"index with name %s has been registered for GroupVersionKind %v", gvk, fieldKey, fieldKey, gvk)
}
indexExtractor := indexes[fieldKey]
filteredList := make([]runtime.Object, 0, len(list))
for _, obj := range list {
if c.objMatchesFieldSelector(obj, indexExtractor, fieldVal) {
filteredList = append(filteredList, obj)
}
}
return nil
return filteredList, nil
}
func (c *fakeClient) objMatchesFieldSelector(o runtime.Object, extractIndex client.IndexerFunc, val string) bool {
obj, isClientObject := o.(client.Object)
if !isClientObject {
panic(fmt.Errorf("expected object %v to be of type client.Object, but it's not", o))
}
for _, extractedVal := range extractIndex(obj) {
if extractedVal == val {
return true
}
}
return false
}
func (c *fakeClient) Scheme() *runtime.Scheme {
@@ -634,8 +752,12 @@ func (c *fakeClient) Patch(ctx context.Context, obj client.Object, patch client.
return err
}
func (c *fakeClient) Status() client.StatusWriter {
return &fakeStatusWriter{client: c}
func (c *fakeClient) Status() client.SubResourceWriter {
return c.SubResource("status")
}
func (c *fakeClient) SubResource(subResource string) client.SubResourceClient {
return &fakeSubResourceClient{client: c}
}
func (c *fakeClient) deleteObject(gvr schema.GroupVersionResource, accessor metav1.Object) error {
@@ -664,20 +786,44 @@ func getGVRFromObject(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupV
return gvr, nil
}
type fakeStatusWriter struct {
type fakeSubResourceClient struct {
client *fakeClient
}
func (sw *fakeStatusWriter) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
// TODO(droot): This results in full update of the obj (spec + status). Need
// a way to update status field only.
return sw.client.Update(ctx, obj, opts...)
func (sw *fakeSubResourceClient) Get(ctx context.Context, obj, subResource client.Object, opts ...client.SubResourceGetOption) error {
panic("fakeSubResourceClient does not support get")
}
func (sw *fakeStatusWriter) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
// TODO(droot): This results in full update of the obj (spec + status). Need
// a way to update status field only.
return sw.client.Patch(ctx, obj, patch, opts...)
func (sw *fakeSubResourceClient) Create(ctx context.Context, obj client.Object, subResource client.Object, opts ...client.SubResourceCreateOption) error {
panic("fakeSubResourceWriter does not support create")
}
func (sw *fakeSubResourceClient) Update(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
// TODO(droot): This results in full update of the obj (spec + subresources). Need
// a way to update subresource only.
updateOptions := client.SubResourceUpdateOptions{}
updateOptions.ApplyOptions(opts)
body := obj
if updateOptions.SubResourceBody != nil {
body = updateOptions.SubResourceBody
}
return sw.client.Update(ctx, body, &updateOptions.UpdateOptions)
}
func (sw *fakeSubResourceClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) error {
// TODO(droot): This results in full update of the obj (spec + subresources). Need
// a way to update subresource only.
patchOptions := client.SubResourcePatchOptions{}
patchOptions.ApplyOptions(opts)
body := obj
if patchOptions.SubResourceBody != nil {
body = patchOptions.SubResourceBody
}
return sw.client.Patch(ctx, body, patch, &patchOptions.PatchOptions)
}
func allowsUnconditionalUpdate(gvk schema.GroupVersionKind) bool {

View File

@@ -28,12 +28,11 @@ When in doubt, it's almost always better not to use this package and instead use
envtest.Environment with a real client and API server.
WARNING: ⚠️ Current Limitations / Known Issues with the fake Client ⚠️
- This client does not have a way to inject specific errors to test handled vs. unhandled errors.
- There is some support for sub resources which can cause issues with tests if you're trying to update
e.g. metadata and status in the same reconcile.
- No OpeanAPI validation is performed when creating or updating objects.
- ObjectMeta's `Generation` and `ResourceVersion` don't behave properly, Patch or Update
operations that rely on these fields will fail, or give false positives.
- This client does not have a way to inject specific errors to test handled vs. unhandled errors.
- There is some support for sub resources which can cause issues with tests if you're trying to update
e.g. metadata and status in the same reconcile.
- No OpenAPI validation is performed when creating or updating objects.
- ObjectMeta's `Generation` and `ResourceVersion` don't behave properly, Patch or Update
operations that rely on these fields will fail, or give false positives.
*/
package fake

View File

@@ -50,7 +50,7 @@ type Reader interface {
// Get retrieves an obj for the given object key from the Kubernetes Cluster.
// obj must be a struct pointer so that obj can be updated with the response
// returned by the Server.
Get(ctx context.Context, key ObjectKey, obj Object) error
Get(ctx context.Context, key ObjectKey, obj Object, opts ...GetOption) error
// List retrieves list of objects for a given namespace and list options. On a
// successful call, Items field in the list will be populated with the
@@ -60,7 +60,8 @@ type Reader interface {
// Writer knows how to create, delete, and update Kubernetes objects.
type Writer interface {
// Create saves the object obj in the Kubernetes cluster.
// Create saves the object obj in the Kubernetes cluster. obj must be a
// struct pointer so that obj can be updated with the content returned by the Server.
Create(ctx context.Context, obj Object, opts ...CreateOption) error
// Delete deletes the given obj from Kubernetes cluster.
@@ -81,20 +82,80 @@ type Writer interface {
// StatusClient knows how to create a client which can update status subresource
// for kubernetes objects.
type StatusClient interface {
Status() StatusWriter
Status() SubResourceWriter
}
// StatusWriter knows how to update status subresource of a Kubernetes object.
type StatusWriter interface {
// SubResourceClientConstructor knows how to create a client which can update subresource
// for kubernetes objects.
type SubResourceClientConstructor interface {
// SubResourceClientConstructor returns a subresource client for the named subResource. Known
// upstream subResources usages are:
// - ServiceAccount token creation:
// sa := &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}}
// token := &authenticationv1.TokenRequest{}
// c.SubResourceClient("token").Create(ctx, sa, token)
//
// - Pod eviction creation:
// pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}}
// c.SubResourceClient("eviction").Create(ctx, pod, &policyv1.Eviction{})
//
// - Pod binding creation:
// pod := &corev1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}}
// binding := &corev1.Binding{Target: corev1.ObjectReference{Name: "my-node"}}
// c.SubResourceClient("binding").Create(ctx, pod, binding)
//
// - CertificateSigningRequest approval:
// csr := &certificatesv1.CertificateSigningRequest{
// ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"},
// Status: certificatesv1.CertificateSigningRequestStatus{
// Conditions: []certificatesv1.[]CertificateSigningRequestCondition{{
// Type: certificatesv1.CertificateApproved,
// Status: corev1.ConditionTrue,
// }},
// },
// }
// c.SubResourceClient("approval").Update(ctx, csr)
//
// - Scale retrieval:
// dep := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}}
// scale := &autoscalingv1.Scale{}
// c.SubResourceClient("scale").Get(ctx, dep, scale)
//
// - Scale update:
// dep := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"}}
// scale := &autoscalingv1.Scale{Spec: autoscalingv1.ScaleSpec{Replicas: 2}}
// c.SubResourceClient("scale").Update(ctx, dep, client.WithSubResourceBody(scale))
SubResource(subResource string) SubResourceClient
}
// StatusWriter is kept for backward compatibility.
type StatusWriter = SubResourceWriter
// SubResourceReader knows how to read SubResources
type SubResourceReader interface {
Get(ctx context.Context, obj Object, subResource Object, opts ...SubResourceGetOption) error
}
// SubResourceWriter knows how to update subresource of a Kubernetes object.
type SubResourceWriter interface {
// Create saves the subResource object in the Kubernetes cluster. obj must be a
// struct pointer so that obj can be updated with the content returned by the Server.
Create(ctx context.Context, obj Object, subResource Object, opts ...SubResourceCreateOption) error
// Update updates the fields corresponding to the status subresource for the
// given obj. obj must be a struct pointer so that obj can be updated
// with the content returned by the Server.
Update(ctx context.Context, obj Object, opts ...UpdateOption) error
Update(ctx context.Context, obj Object, opts ...SubResourceUpdateOption) error
// Patch patches the given object's subresource. obj must be a struct
// pointer so that obj can be updated with the content returned by the
// Server.
Patch(ctx context.Context, obj Object, patch Patch, opts ...PatchOption) error
Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error
}
// SubResourceClient knows how to perform CRU operations on Kubernetes objects.
type SubResourceClient interface {
SubResourceReader
SubResourceWriter
}
// Client knows how to perform CRUD operations on Kubernetes objects.
@@ -102,6 +163,7 @@ type Client interface {
Reader
Writer
StatusClient
SubResourceClientConstructor
// Scheme returns the scheme this client is using.
Scheme() *runtime.Scheme
@@ -143,3 +205,13 @@ func IgnoreNotFound(err error) error {
}
return err
}
// IgnoreAlreadyExists returns nil on AlreadyExists errors.
// All other values that are not AlreadyExists errors or nil are returned unmodified.
func IgnoreAlreadyExists(err error) error {
if apierrors.IsAlreadyExists(err) {
return nil
}
return err
}

View File

@@ -116,7 +116,7 @@ func (mc *metadataClient) Patch(ctx context.Context, obj Object, patch Patch, op
}
// Get implements client.Client.
func (mc *metadataClient) Get(ctx context.Context, key ObjectKey, obj Object) error {
func (mc *metadataClient) Get(ctx context.Context, key ObjectKey, obj Object, opts ...GetOption) error {
metadata, ok := obj.(*metav1.PartialObjectMetadata)
if !ok {
return fmt.Errorf("metadata client did not understand object: %T", obj)
@@ -124,12 +124,15 @@ func (mc *metadataClient) Get(ctx context.Context, key ObjectKey, obj Object) er
gvk := metadata.GroupVersionKind()
getOpts := GetOptions{}
getOpts.ApplyOptions(opts)
resInt, err := mc.getResourceInterface(gvk, key.Namespace)
if err != nil {
return err
}
res, err := resInt.Get(ctx, key.Name, metav1.GetOptions{})
res, err := resInt.Get(ctx, key.Name, *getOpts.AsGetOptions())
if err != nil {
return err
}
@@ -165,7 +168,7 @@ func (mc *metadataClient) List(ctx context.Context, obj ObjectList, opts ...List
return nil
}
func (mc *metadataClient) PatchStatus(ctx context.Context, obj Object, patch Patch, opts ...PatchOption) error {
func (mc *metadataClient) PatchSubResource(ctx context.Context, obj Object, subResource string, patch Patch, opts ...SubResourcePatchOption) error {
metadata, ok := obj.(*metav1.PartialObjectMetadata)
if !ok {
return fmt.Errorf("metadata client did not understand object: %T", obj)
@@ -177,16 +180,24 @@ func (mc *metadataClient) PatchStatus(ctx context.Context, obj Object, patch Pat
return err
}
data, err := patch.Data(obj)
patchOpts := &SubResourcePatchOptions{}
patchOpts.ApplyOptions(opts)
body := obj
if patchOpts.SubResourceBody != nil {
body = patchOpts.SubResourceBody
}
data, err := patch.Data(body)
if err != nil {
return err
}
patchOpts := &PatchOptions{}
res, err := resInt.Patch(ctx, metadata.Name, patch.Type(), data, *patchOpts.AsPatchOptions(), "status")
res, err := resInt.Patch(ctx, metadata.Name, patch.Type(), data, *patchOpts.AsPatchOptions(), subResource)
if err != nil {
return err
}
*metadata = *res
metadata.SetGroupVersionKind(gvk) // restore the GVK, which isn't set on metadata
return nil

View File

@@ -52,7 +52,7 @@ func (n *namespacedClient) RESTMapper() meta.RESTMapper {
return n.client.RESTMapper()
}
// Create implements clinet.Client.
// Create implements client.Client.
func (n *namespacedClient) Create(ctx context.Context, obj Object, opts ...CreateOption) error {
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, n.Scheme(), n.RESTMapper())
if err != nil {
@@ -138,18 +138,18 @@ func (n *namespacedClient) Patch(ctx context.Context, obj Object, patch Patch, o
}
// Get implements client.Client.
func (n *namespacedClient) Get(ctx context.Context, key ObjectKey, obj Object) error {
func (n *namespacedClient) Get(ctx context.Context, key ObjectKey, obj Object, opts ...GetOption) error {
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, n.Scheme(), n.RESTMapper())
if err != nil {
return fmt.Errorf("error finding the scope of the object: %w", err)
}
if isNamespaceScoped {
if key.Namespace != "" && key.Namespace != n.namespace {
return fmt.Errorf("namespace %s provided for the object %s does not match the namesapce %s on the client", key.Namespace, obj.GetName(), n.namespace)
return fmt.Errorf("namespace %s provided for the object %s does not match the namespace %s on the client", key.Namespace, obj.GetName(), n.namespace)
}
key.Namespace = n.namespace
}
return n.client.Get(ctx, key, obj)
return n.client.Get(ctx, key, obj, opts...)
}
// List implements client.Client.
@@ -161,21 +161,80 @@ func (n *namespacedClient) List(ctx context.Context, obj ObjectList, opts ...Lis
}
// Status implements client.StatusClient.
func (n *namespacedClient) Status() StatusWriter {
return &namespacedClientStatusWriter{StatusClient: n.client.Status(), namespace: n.namespace, namespacedclient: n}
func (n *namespacedClient) Status() SubResourceWriter {
return n.SubResource("status")
}
// ensure namespacedClientStatusWriter implements client.StatusWriter.
var _ StatusWriter = &namespacedClientStatusWriter{}
// SubResource implements client.SubResourceClient.
func (n *namespacedClient) SubResource(subResource string) SubResourceClient {
return &namespacedClientSubResourceClient{client: n.client.SubResource(subResource), namespace: n.namespace, namespacedclient: n}
}
type namespacedClientStatusWriter struct {
StatusClient StatusWriter
// ensure namespacedClientSubResourceClient implements client.SubResourceClient.
var _ SubResourceClient = &namespacedClientSubResourceClient{}
type namespacedClientSubResourceClient struct {
client SubResourceClient
namespace string
namespacedclient Client
}
// Update implements client.StatusWriter.
func (nsw *namespacedClientStatusWriter) Update(ctx context.Context, obj Object, opts ...UpdateOption) error {
func (nsw *namespacedClientSubResourceClient) Get(ctx context.Context, obj, subResource Object, opts ...SubResourceGetOption) error {
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, nsw.namespacedclient.Scheme(), nsw.namespacedclient.RESTMapper())
if err != nil {
return fmt.Errorf("error finding the scope of the object: %w", err)
}
objectNamespace := obj.GetNamespace()
if objectNamespace != nsw.namespace && objectNamespace != "" {
return fmt.Errorf("namespace %s of the object %s does not match the namespace %s on the client", objectNamespace, obj.GetName(), nsw.namespace)
}
if isNamespaceScoped && objectNamespace == "" {
obj.SetNamespace(nsw.namespace)
}
return nsw.client.Get(ctx, obj, subResource, opts...)
}
func (nsw *namespacedClientSubResourceClient) Create(ctx context.Context, obj, subResource Object, opts ...SubResourceCreateOption) error {
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, nsw.namespacedclient.Scheme(), nsw.namespacedclient.RESTMapper())
if err != nil {
return fmt.Errorf("error finding the scope of the object: %w", err)
}
objectNamespace := obj.GetNamespace()
if objectNamespace != nsw.namespace && objectNamespace != "" {
return fmt.Errorf("namespace %s of the object %s does not match the namespace %s on the client", objectNamespace, obj.GetName(), nsw.namespace)
}
if isNamespaceScoped && objectNamespace == "" {
obj.SetNamespace(nsw.namespace)
}
return nsw.client.Create(ctx, obj, subResource, opts...)
}
// Update implements client.SubResourceWriter.
func (nsw *namespacedClientSubResourceClient) Update(ctx context.Context, obj Object, opts ...SubResourceUpdateOption) error {
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, nsw.namespacedclient.Scheme(), nsw.namespacedclient.RESTMapper())
if err != nil {
return fmt.Errorf("error finding the scope of the object: %w", err)
}
objectNamespace := obj.GetNamespace()
if objectNamespace != nsw.namespace && objectNamespace != "" {
return fmt.Errorf("namespace %s of the object %s does not match the namespace %s on the client", objectNamespace, obj.GetName(), nsw.namespace)
}
if isNamespaceScoped && objectNamespace == "" {
obj.SetNamespace(nsw.namespace)
}
return nsw.client.Update(ctx, obj, opts...)
}
// Patch implements client.SubResourceWriter.
func (nsw *namespacedClientSubResourceClient) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error {
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, nsw.namespacedclient.Scheme(), nsw.namespacedclient.RESTMapper())
if err != nil {
@@ -190,24 +249,5 @@ func (nsw *namespacedClientStatusWriter) Update(ctx context.Context, obj Object,
if isNamespaceScoped && objectNamespace == "" {
obj.SetNamespace(nsw.namespace)
}
return nsw.StatusClient.Update(ctx, obj, opts...)
}
// Patch implements client.StatusWriter.
func (nsw *namespacedClientStatusWriter) Patch(ctx context.Context, obj Object, patch Patch, opts ...PatchOption) error {
isNamespaceScoped, err := objectutil.IsAPINamespaced(obj, nsw.namespacedclient.Scheme(), nsw.namespacedclient.RESTMapper())
if err != nil {
return fmt.Errorf("error finding the scope of the object: %w", err)
}
objectNamespace := obj.GetNamespace()
if objectNamespace != nsw.namespace && objectNamespace != "" {
return fmt.Errorf("namespace %s of the object %s does not match the namespace %s on the client", objectNamespace, obj.GetName(), nsw.namespace)
}
if isNamespaceScoped && objectNamespace == "" {
obj.SetNamespace(nsw.namespace)
}
return nsw.StatusClient.Patch(ctx, obj, patch, opts...)
return nsw.client.Patch(ctx, obj, patch, opts...)
}

View File

@@ -37,6 +37,12 @@ type DeleteOption interface {
ApplyToDelete(*DeleteOptions)
}
// GetOption is some configuration that modifies options for a get request.
type GetOption interface {
// ApplyToGet applies this configuration to the given get options.
ApplyToGet(*GetOptions)
}
// ListOption is some configuration that modifies options for a list request.
type ListOption interface {
// ApplyToList applies this configuration to the given list options.
@@ -61,6 +67,29 @@ type DeleteAllOfOption interface {
ApplyToDeleteAllOf(*DeleteAllOfOptions)
}
// SubResourceGetOption modifies options for a SubResource Get request.
type SubResourceGetOption interface {
ApplyToSubResourceGet(*SubResourceGetOptions)
}
// SubResourceUpdateOption is some configuration that modifies options for a update request.
type SubResourceUpdateOption interface {
// ApplyToSubResourceUpdate applies this configuration to the given update options.
ApplyToSubResourceUpdate(*SubResourceUpdateOptions)
}
// SubResourceCreateOption is some configuration that modifies options for a create request.
type SubResourceCreateOption interface {
// ApplyToSubResourceCreate applies this configuration to the given create options.
ApplyToSubResourceCreate(*SubResourceCreateOptions)
}
// SubResourcePatchOption configures a subresource patch request.
type SubResourcePatchOption interface {
// ApplyToSubResourcePatch applies the configuration on the given patch options.
ApplyToSubResourcePatch(*SubResourcePatchOptions)
}
// }}}
// {{{ Multi-Type Options
@@ -90,10 +119,23 @@ func (dryRunAll) ApplyToPatch(opts *PatchOptions) {
func (dryRunAll) ApplyToDelete(opts *DeleteOptions) {
opts.DryRun = []string{metav1.DryRunAll}
}
func (dryRunAll) ApplyToDeleteAllOf(opts *DeleteAllOfOptions) {
opts.DryRun = []string{metav1.DryRunAll}
}
func (dryRunAll) ApplyToSubResourceCreate(opts *SubResourceCreateOptions) {
opts.DryRun = []string{metav1.DryRunAll}
}
func (dryRunAll) ApplyToSubResourceUpdate(opts *SubResourceUpdateOptions) {
opts.DryRun = []string{metav1.DryRunAll}
}
func (dryRunAll) ApplyToSubResourcePatch(opts *SubResourcePatchOptions) {
opts.DryRun = []string{metav1.DryRunAll}
}
// FieldOwner set the field manager name for the given server-side apply patch.
type FieldOwner string
@@ -112,6 +154,21 @@ func (f FieldOwner) ApplyToUpdate(opts *UpdateOptions) {
opts.FieldManager = string(f)
}
// ApplyToSubResourcePatch applies this configuration to the given patch options.
func (f FieldOwner) ApplyToSubResourcePatch(opts *SubResourcePatchOptions) {
opts.FieldManager = string(f)
}
// ApplyToSubResourceCreate applies this configuration to the given create options.
func (f FieldOwner) ApplyToSubResourceCreate(opts *SubResourceCreateOptions) {
opts.FieldManager = string(f)
}
// ApplyToSubResourceUpdate applies this configuration to the given update options.
func (f FieldOwner) ApplyToSubResourceUpdate(opts *SubResourceUpdateOptions) {
opts.FieldManager = string(f)
}
// }}}
// {{{ Create Options
@@ -311,6 +368,45 @@ func (p PropagationPolicy) ApplyToDeleteAllOf(opts *DeleteAllOfOptions) {
// }}}
// {{{ Get Options
// GetOptions contains options for get operation.
// Now it only has a Raw field, with support for specific resourceVersion.
type GetOptions struct {
// Raw represents raw GetOptions, as passed to the API server. Note
// that these may not be respected by all implementations of interface.
Raw *metav1.GetOptions
}
var _ GetOption = &GetOptions{}
// ApplyToGet implements GetOption for GetOptions.
func (o *GetOptions) ApplyToGet(lo *GetOptions) {
if o.Raw != nil {
lo.Raw = o.Raw
}
}
// AsGetOptions returns these options as a flattened metav1.GetOptions.
// This may mutate the Raw field.
func (o *GetOptions) AsGetOptions() *metav1.GetOptions {
if o == nil || o.Raw == nil {
return &metav1.GetOptions{}
}
return o.Raw
}
// ApplyOptions applies the given get options on these options,
// and then returns itself (for convenient chaining).
func (o *GetOptions) ApplyOptions(opts []GetOption) *GetOptions {
for _, opt := range opts {
opt.ApplyToGet(o)
}
return o
}
// }}}
// {{{ List Options
// ListOptions contains options for limiting or filtering results.
@@ -341,6 +437,12 @@ type ListOptions struct {
// it has expired. This field is not supported if watch is true in the Raw ListOptions.
Continue string
// UnsafeDisableDeepCopy indicates not to deep copy objects during list objects.
// Be very careful with this, when enabled you must DeepCopy any object before mutating it,
// otherwise you will mutate the object in the cache.
// +optional
UnsafeDisableDeepCopy *bool
// Raw represents raw ListOptions, as passed to the API server. Note
// that these may not be respected by all implementations of interface,
// and the LabelSelector, FieldSelector, Limit and Continue fields are ignored.
@@ -369,6 +471,9 @@ func (o *ListOptions) ApplyToList(lo *ListOptions) {
if o.Continue != "" {
lo.Continue = o.Continue
}
if o.UnsafeDisableDeepCopy != nil {
lo.UnsafeDisableDeepCopy = o.UnsafeDisableDeepCopy
}
}
// AsListOptions returns these options as a flattened metav1.ListOptions.
@@ -511,6 +616,25 @@ func (l Limit) ApplyToList(opts *ListOptions) {
opts.Limit = int64(l)
}
// UnsafeDisableDeepCopyOption indicates not to deep copy objects during list objects.
// Be very careful with this, when enabled you must DeepCopy any object before mutating it,
// otherwise you will mutate the object in the cache.
type UnsafeDisableDeepCopyOption bool
// ApplyToList applies this configuration to the given an List options.
func (d UnsafeDisableDeepCopyOption) ApplyToList(opts *ListOptions) {
definitelyTrue := true
definitelyFalse := false
if d {
opts.UnsafeDisableDeepCopy = &definitelyTrue
} else {
opts.UnsafeDisableDeepCopy = &definitelyFalse
}
}
// UnsafeDisableDeepCopy indicates not to deep copy objects during list objects.
const UnsafeDisableDeepCopy = UnsafeDisableDeepCopyOption(true)
// Continue sets a continuation token to retrieve chunks of results when using limit.
// Continue does not implement DeleteAllOfOption interface because the server
// does not support setting it for deletecollection operations.

View File

@@ -19,7 +19,7 @@ package client
import (
"fmt"
jsonpatch "github.com/evanphx/json-patch"
jsonpatch "github.com/evanphx/json-patch/v5"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/strategicpatch"

View File

@@ -61,8 +61,9 @@ func NewDelegatingClient(in NewDelegatingClientInput) (Client, error) {
uncachedGVKs: uncachedGVKs,
cacheUnstructured: in.CacheUnstructured,
},
Writer: in.Client,
StatusClient: in.Client,
Writer: in.Client,
StatusClient: in.Client,
SubResourceClientConstructor: in.Client,
}, nil
}
@@ -70,6 +71,7 @@ type delegatingClient struct {
Reader
Writer
StatusClient
SubResourceClientConstructor
scheme *runtime.Scheme
mapper meta.RESTMapper
@@ -121,13 +123,13 @@ func (d *delegatingReader) shouldBypassCache(obj runtime.Object) (bool, error) {
}
// Get retrieves an obj for a given object key from the Kubernetes Cluster.
func (d *delegatingReader) Get(ctx context.Context, key ObjectKey, obj Object) error {
func (d *delegatingReader) Get(ctx context.Context, key ObjectKey, obj Object, opts ...GetOption) error {
if isUncached, err := d.shouldBypassCache(obj); err != nil {
return err
} else if isUncached {
return d.ClientReader.Get(ctx, key, obj)
return d.ClientReader.Get(ctx, key, obj, opts...)
}
return d.CacheReader.Get(ctx, key, obj)
return d.CacheReader.Get(ctx, key, obj, opts...)
}
// List retrieves list of objects for a given namespace and list options.

View File

@@ -24,7 +24,6 @@ import (
var _ Reader = &typedClient{}
var _ Writer = &typedClient{}
var _ StatusWriter = &typedClient{}
// client is a client.Client that reads and writes directly from/to an API server. It lazily initializes
// new clients at the time they are used, and caches the client.
@@ -42,6 +41,7 @@ func (c *typedClient) Create(ctx context.Context, obj Object, opts ...CreateOpti
createOpts := &CreateOptions{}
createOpts.ApplyOptions(opts)
return o.Post().
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
Resource(o.resource()).
@@ -60,6 +60,7 @@ func (c *typedClient) Update(ctx context.Context, obj Object, opts ...UpdateOpti
updateOpts := &UpdateOptions{}
updateOpts.ApplyOptions(opts)
return o.Put().
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
Resource(o.resource()).
@@ -121,25 +122,30 @@ func (c *typedClient) Patch(ctx context.Context, obj Object, patch Patch, opts .
}
patchOpts := &PatchOptions{}
patchOpts.ApplyOptions(opts)
return o.Patch(patch.Type()).
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
Resource(o.resource()).
Name(o.GetName()).
VersionedParams(patchOpts.ApplyOptions(opts).AsPatchOptions(), c.paramCodec).
VersionedParams(patchOpts.AsPatchOptions(), c.paramCodec).
Body(data).
Do(ctx).
Into(obj)
}
// Get implements client.Client.
func (c *typedClient) Get(ctx context.Context, key ObjectKey, obj Object) error {
func (c *typedClient) Get(ctx context.Context, key ObjectKey, obj Object, opts ...GetOption) error {
r, err := c.cache.getResource(obj)
if err != nil {
return err
}
getOpts := GetOptions{}
getOpts.ApplyOptions(opts)
return r.Get().
NamespaceIfScoped(key.Namespace, r.isNamespaced()).
Resource(r.resource()).
VersionedParams(getOpts.AsGetOptions(), c.paramCodec).
Name(key.Name).Do(ctx).Into(obj)
}
@@ -149,8 +155,10 @@ func (c *typedClient) List(ctx context.Context, obj ObjectList, opts ...ListOpti
if err != nil {
return err
}
listOpts := ListOptions{}
listOpts.ApplyOptions(opts)
return r.Get().
NamespaceIfScoped(listOpts.Namespace, r.isNamespaced()).
Resource(r.resource()).
@@ -159,8 +167,55 @@ func (c *typedClient) List(ctx context.Context, obj ObjectList, opts ...ListOpti
Into(obj)
}
// UpdateStatus used by StatusWriter to write status.
func (c *typedClient) UpdateStatus(ctx context.Context, obj Object, opts ...UpdateOption) error {
func (c *typedClient) GetSubResource(ctx context.Context, obj, subResourceObj Object, subResource string, opts ...SubResourceGetOption) error {
o, err := c.cache.getObjMeta(obj)
if err != nil {
return err
}
if subResourceObj.GetName() == "" {
subResourceObj.SetName(obj.GetName())
}
getOpts := &SubResourceGetOptions{}
getOpts.ApplyOptions(opts)
return o.Get().
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
Resource(o.resource()).
Name(o.GetName()).
SubResource(subResource).
VersionedParams(getOpts.AsGetOptions(), c.paramCodec).
Do(ctx).
Into(subResourceObj)
}
func (c *typedClient) CreateSubResource(ctx context.Context, obj Object, subResourceObj Object, subResource string, opts ...SubResourceCreateOption) error {
o, err := c.cache.getObjMeta(obj)
if err != nil {
return err
}
if subResourceObj.GetName() == "" {
subResourceObj.SetName(obj.GetName())
}
createOpts := &SubResourceCreateOptions{}
createOpts.ApplyOptions(opts)
return o.Post().
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
Resource(o.resource()).
Name(o.GetName()).
SubResource(subResource).
Body(subResourceObj).
VersionedParams(createOpts.AsCreateOptions(), c.paramCodec).
Do(ctx).
Into(subResourceObj)
}
// UpdateSubResource used by SubResourceWriter to write status.
func (c *typedClient) UpdateSubResource(ctx context.Context, obj Object, subResource string, opts ...SubResourceUpdateOption) error {
o, err := c.cache.getObjMeta(obj)
if err != nil {
return err
@@ -169,37 +224,58 @@ func (c *typedClient) UpdateStatus(ctx context.Context, obj Object, opts ...Upda
// wrapped to improve the UX ?
// It will be nice to receive an error saying the object doesn't implement
// status subresource and check CRD definition
updateOpts := &SubResourceUpdateOptions{}
updateOpts.ApplyOptions(opts)
body := obj
if updateOpts.SubResourceBody != nil {
body = updateOpts.SubResourceBody
}
if body.GetName() == "" {
body.SetName(obj.GetName())
}
if body.GetNamespace() == "" {
body.SetNamespace(obj.GetNamespace())
}
return o.Put().
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
Resource(o.resource()).
Name(o.GetName()).
SubResource("status").
Body(obj).
VersionedParams((&UpdateOptions{}).ApplyOptions(opts).AsUpdateOptions(), c.paramCodec).
SubResource(subResource).
Body(body).
VersionedParams(updateOpts.AsUpdateOptions(), c.paramCodec).
Do(ctx).
Into(obj)
Into(body)
}
// PatchStatus used by StatusWriter to write status.
func (c *typedClient) PatchStatus(ctx context.Context, obj Object, patch Patch, opts ...PatchOption) error {
// PatchSubResource used by SubResourceWriter to write subresource.
func (c *typedClient) PatchSubResource(ctx context.Context, obj Object, subResource string, patch Patch, opts ...SubResourcePatchOption) error {
o, err := c.cache.getObjMeta(obj)
if err != nil {
return err
}
data, err := patch.Data(obj)
patchOpts := &SubResourcePatchOptions{}
patchOpts.ApplyOptions(opts)
body := obj
if patchOpts.SubResourceBody != nil {
body = patchOpts.SubResourceBody
}
data, err := patch.Data(body)
if err != nil {
return err
}
patchOpts := &PatchOptions{}
return o.Patch(patch.Type()).
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
Resource(o.resource()).
Name(o.GetName()).
SubResource("status").
SubResource(subResource).
Body(data).
VersionedParams(patchOpts.ApplyOptions(opts).AsPatchOptions(), c.paramCodec).
VersionedParams(patchOpts.AsPatchOptions(), c.paramCodec).
Do(ctx).
Into(obj)
Into(body)
}

View File

@@ -27,7 +27,6 @@ import (
var _ Reader = &unstructuredClient{}
var _ Writer = &unstructuredClient{}
var _ StatusWriter = &unstructuredClient{}
// client is a client.Client that reads and writes directly from/to an API server. It lazily initializes
// new clients at the time they are used, and caches the client.
@@ -52,6 +51,7 @@ func (uc *unstructuredClient) Create(ctx context.Context, obj Object, opts ...Cr
createOpts := &CreateOptions{}
createOpts.ApplyOptions(opts)
result := o.Post().
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
Resource(o.resource()).
@@ -80,6 +80,7 @@ func (uc *unstructuredClient) Update(ctx context.Context, obj Object, opts ...Up
updateOpts := UpdateOptions{}
updateOpts.ApplyOptions(opts)
result := o.Put().
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
Resource(o.resource()).
@@ -106,6 +107,7 @@ func (uc *unstructuredClient) Delete(ctx context.Context, obj Object, opts ...De
deleteOpts := DeleteOptions{}
deleteOpts.ApplyOptions(opts)
return o.Delete().
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
Resource(o.resource()).
@@ -128,6 +130,7 @@ func (uc *unstructuredClient) DeleteAllOf(ctx context.Context, obj Object, opts
deleteAllOfOpts := DeleteAllOfOptions{}
deleteAllOfOpts.ApplyOptions(opts)
return o.Delete().
NamespaceIfScoped(deleteAllOfOpts.ListOptions.Namespace, o.isNamespaced()).
Resource(o.resource()).
@@ -154,18 +157,20 @@ func (uc *unstructuredClient) Patch(ctx context.Context, obj Object, patch Patch
}
patchOpts := &PatchOptions{}
patchOpts.ApplyOptions(opts)
return o.Patch(patch.Type()).
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
Resource(o.resource()).
Name(o.GetName()).
VersionedParams(patchOpts.ApplyOptions(opts).AsPatchOptions(), uc.paramCodec).
VersionedParams(patchOpts.AsPatchOptions(), uc.paramCodec).
Body(data).
Do(ctx).
Into(obj)
}
// Get implements client.Client.
func (uc *unstructuredClient) Get(ctx context.Context, key ObjectKey, obj Object) error {
func (uc *unstructuredClient) Get(ctx context.Context, key ObjectKey, obj Object, opts ...GetOption) error {
u, ok := obj.(*unstructured.Unstructured)
if !ok {
return fmt.Errorf("unstructured client did not understand object: %T", obj)
@@ -173,6 +178,9 @@ func (uc *unstructuredClient) Get(ctx context.Context, key ObjectKey, obj Object
gvk := u.GroupVersionKind()
getOpts := GetOptions{}
getOpts.ApplyOptions(opts)
r, err := uc.cache.getResource(obj)
if err != nil {
return err
@@ -181,6 +189,7 @@ func (uc *unstructuredClient) Get(ctx context.Context, key ObjectKey, obj Object
result := r.Get().
NamespaceIfScoped(key.Namespace, r.isNamespaced()).
Resource(r.resource()).
VersionedParams(getOpts.AsGetOptions(), uc.paramCodec).
Name(key.Name).
Do(ctx).
Into(obj)
@@ -200,14 +209,14 @@ func (uc *unstructuredClient) List(ctx context.Context, obj ObjectList, opts ...
gvk := u.GroupVersionKind()
gvk.Kind = strings.TrimSuffix(gvk.Kind, "List")
listOpts := ListOptions{}
listOpts.ApplyOptions(opts)
r, err := uc.cache.getResource(obj)
if err != nil {
return err
}
listOpts := ListOptions{}
listOpts.ApplyOptions(opts)
return r.Get().
NamespaceIfScoped(listOpts.Namespace, r.isNamespaced()).
Resource(r.resource()).
@@ -216,7 +225,70 @@ func (uc *unstructuredClient) List(ctx context.Context, obj ObjectList, opts ...
Into(obj)
}
func (uc *unstructuredClient) UpdateStatus(ctx context.Context, obj Object, opts ...UpdateOption) error {
func (uc *unstructuredClient) GetSubResource(ctx context.Context, obj, subResourceObj Object, subResource string, opts ...SubResourceGetOption) error {
if _, ok := obj.(*unstructured.Unstructured); !ok {
return fmt.Errorf("unstructured client did not understand object: %T", subResource)
}
if _, ok := subResourceObj.(*unstructured.Unstructured); !ok {
return fmt.Errorf("unstructured client did not understand object: %T", obj)
}
if subResourceObj.GetName() == "" {
subResourceObj.SetName(obj.GetName())
}
o, err := uc.cache.getObjMeta(obj)
if err != nil {
return err
}
getOpts := &SubResourceGetOptions{}
getOpts.ApplyOptions(opts)
return o.Get().
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
Resource(o.resource()).
Name(o.GetName()).
SubResource(subResource).
VersionedParams(getOpts.AsGetOptions(), uc.paramCodec).
Do(ctx).
Into(subResourceObj)
}
func (uc *unstructuredClient) CreateSubResource(ctx context.Context, obj, subResourceObj Object, subResource string, opts ...SubResourceCreateOption) error {
if _, ok := obj.(*unstructured.Unstructured); !ok {
return fmt.Errorf("unstructured client did not understand object: %T", subResourceObj)
}
if _, ok := subResourceObj.(*unstructured.Unstructured); !ok {
return fmt.Errorf("unstructured client did not understand object: %T", obj)
}
if subResourceObj.GetName() == "" {
subResourceObj.SetName(obj.GetName())
}
o, err := uc.cache.getObjMeta(obj)
if err != nil {
return err
}
createOpts := &SubResourceCreateOptions{}
createOpts.ApplyOptions(opts)
return o.Post().
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
Resource(o.resource()).
Name(o.GetName()).
SubResource(subResource).
Body(subResourceObj).
VersionedParams(createOpts.AsCreateOptions(), uc.paramCodec).
Do(ctx).
Into(subResourceObj)
}
func (uc *unstructuredClient) UpdateSubResource(ctx context.Context, obj Object, subResource string, opts ...SubResourceUpdateOption) error {
if _, ok := obj.(*unstructured.Unstructured); !ok {
return fmt.Errorf("unstructured client did not understand object: %T", obj)
}
@@ -226,18 +298,32 @@ func (uc *unstructuredClient) UpdateStatus(ctx context.Context, obj Object, opts
return err
}
updateOpts := SubResourceUpdateOptions{}
updateOpts.ApplyOptions(opts)
body := obj
if updateOpts.SubResourceBody != nil {
body = updateOpts.SubResourceBody
}
if body.GetName() == "" {
body.SetName(obj.GetName())
}
if body.GetNamespace() == "" {
body.SetNamespace(obj.GetNamespace())
}
return o.Put().
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
Resource(o.resource()).
Name(o.GetName()).
SubResource("status").
Body(obj).
VersionedParams((&UpdateOptions{}).ApplyOptions(opts).AsUpdateOptions(), uc.paramCodec).
SubResource(subResource).
Body(body).
VersionedParams(updateOpts.AsUpdateOptions(), uc.paramCodec).
Do(ctx).
Into(obj)
Into(body)
}
func (uc *unstructuredClient) PatchStatus(ctx context.Context, obj Object, patch Patch, opts ...PatchOption) error {
func (uc *unstructuredClient) PatchSubResource(ctx context.Context, obj Object, subResource string, patch Patch, opts ...SubResourcePatchOption) error {
u, ok := obj.(*unstructured.Unstructured)
if !ok {
return fmt.Errorf("unstructured client did not understand object: %T", obj)
@@ -250,21 +336,28 @@ func (uc *unstructuredClient) PatchStatus(ctx context.Context, obj Object, patch
return err
}
data, err := patch.Data(obj)
patchOpts := &SubResourcePatchOptions{}
patchOpts.ApplyOptions(opts)
body := obj
if patchOpts.SubResourceBody != nil {
body = patchOpts.SubResourceBody
}
data, err := patch.Data(body)
if err != nil {
return err
}
patchOpts := &PatchOptions{}
result := o.Patch(patch.Type()).
NamespaceIfScoped(o.GetNamespace(), o.isNamespaced()).
Resource(o.resource()).
Name(o.GetName()).
SubResource("status").
SubResource(subResource).
Body(data).
VersionedParams(patchOpts.ApplyOptions(opts).AsPatchOptions(), uc.paramCodec).
VersionedParams(patchOpts.AsPatchOptions(), uc.paramCodec).
Do(ctx).
Into(u)
Into(body)
u.SetGroupVersionKind(gvk)
return result