update dependencies (#6267)

Signed-off-by: hongming <coder.scala@gmail.com>
This commit is contained in:
hongming
2024-11-06 10:27:06 +08:00
committed by GitHub
parent faf255a084
commit cfebd96a1f
4263 changed files with 341374 additions and 132036 deletions

View File

@@ -72,7 +72,10 @@ func IsObjectNamespaced(obj runtime.Object, scheme *runtime.Scheme, restmapper m
// IsGVKNamespaced returns true if the object having the provided
// GVK is namespace scoped.
func IsGVKNamespaced(gvk schema.GroupVersionKind, restmapper meta.RESTMapper) (bool, error) {
restmapping, err := restmapper.RESTMapping(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind})
// Fetch the RESTMapping using the complete GVK. If we exclude the Version, the Version set
// will be populated using the cached Group if available. This can lead to failures updating
// the cache with new Versions of CRDs registered at runtime.
restmapping, err := restmapper.RESTMapping(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind}, gvk.Version)
if err != nil {
return false, fmt.Errorf("failed to get restmapping: %w", err)
}

View File

@@ -50,28 +50,10 @@ type Options struct {
// Cache, if provided, is used to read objects from the cache.
Cache *CacheOptions
// WarningHandler is used to configure the warning handler responsible for
// surfacing and handling warnings messages sent by the API server.
WarningHandler WarningHandlerOptions
// DryRun instructs the client to only perform dry run requests.
DryRun *bool
}
// WarningHandlerOptions are options for configuring a
// warning handler for the client which is responsible
// for surfacing API Server warnings.
type WarningHandlerOptions struct {
// SuppressWarnings decides if the warnings from the
// API server are suppressed or surfaced in the client.
SuppressWarnings bool
// AllowDuplicateLogs does not deduplicate the to-be
// logged surfaced warnings messages. See
// log.WarningHandlerOptions for considerations
// regarding deduplication
AllowDuplicateLogs bool
}
// CacheOptions are options for creating a cache-backed client.
type CacheOptions struct {
// Reader is a cache-backed reader that will be used to read objects from the cache.
@@ -91,6 +73,12 @@ type NewClientFunc func(config *rest.Config, options Options) (Client, error)
// New returns a new Client using the provided config and Options.
//
// By default, the client surfaces warnings returned by the server. To
// suppress warnings, set config.WarningHandler = rest.NoWarnings{}. To
// define custom behavior, implement the rest.WarningHandler interface.
// See [sigs.k8s.io/controller-runtime/pkg/log.KubeAPIWarningLogger] for
// an example.
//
// The client's read behavior is determined by Options.Cache.
// If either Options.Cache or Options.Cache.Reader is nil,
// the client reads directly from the API server.
@@ -124,17 +112,12 @@ func newClient(config *rest.Config, options Options) (*client, error) {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
if !options.WarningHandler.SuppressWarnings {
// surface warnings
logger := log.Log.WithName("KubeAPIWarningLogger")
// Set a WarningHandler, the default WarningHandler
// is log.KubeAPIWarningLogger with deduplication enabled.
// See log.KubeAPIWarningLoggerOptions for considerations
// regarding deduplication.
if config.WarningHandler == nil {
// By default, we de-duplicate and surface warnings.
config.WarningHandler = log.NewKubeAPIWarningLogger(
logger,
log.Log.WithName("KubeAPIWarningLogger"),
log.KubeAPIWarningLoggerOptions{
Deduplicate: !options.WarningHandler.AllowDuplicateLogs,
Deduplicate: true,
},
)
}
@@ -523,8 +506,8 @@ func (co *SubResourceCreateOptions) ApplyOptions(opts []SubResourceCreateOption)
return co
}
// ApplyToSubresourceCreate applies the the configuration on the given create options.
func (co *SubResourceCreateOptions) ApplyToSubresourceCreate(o *SubResourceCreateOptions) {
// ApplyToSubResourceCreate applies the the configuration on the given create options.
func (co *SubResourceCreateOptions) ApplyToSubResourceCreate(o *SubResourceCreateOptions) {
co.CreateOptions.ApplyToCreate(&co.CreateOptions)
}

View File

@@ -30,7 +30,9 @@ import (
"time"
// Using v4 to match upstream
jsonpatch "github.com/evanphx/json-patch"
jsonpatch "gopkg.in/evanphx/json-patch.v4"
appsv1 "k8s.io/api/apps/v1"
autoscalingv1 "k8s.io/api/autoscaling/v1"
corev1 "k8s.io/api/core/v1"
policyv1 "k8s.io/api/policy/v1"
policyv1beta1 "k8s.io/api/policy/v1beta1"
@@ -50,6 +52,7 @@ import (
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/testing"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
@@ -65,16 +68,21 @@ type versionedTracker struct {
}
type fakeClient struct {
tracker versionedTracker
scheme *runtime.Scheme
// trackerWriteLock must be acquired before writing to
// the tracker or performing reads that affect a following
// write.
trackerWriteLock sync.Mutex
tracker versionedTracker
schemeWriteLock sync.Mutex
scheme *runtime.Scheme
restMapper meta.RESTMapper
withStatusSubresource sets.Set[schema.GroupVersionKind]
// 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
}
var _ client.WithWatch = &fakeClient{}
@@ -83,6 +91,8 @@ const (
maxNameLength = 63
randomLength = 5
maxGeneratedNameLength = maxNameLength - randomLength
subResourceScale = "scale"
)
// NewFakeClient creates a new fake client for testing.
@@ -301,7 +311,7 @@ func (t versionedTracker) Add(obj runtime.Object) error {
return nil
}
func (t versionedTracker) Create(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error {
func (t versionedTracker) Create(gvr schema.GroupVersionResource, obj runtime.Object, ns string, opts ...metav1.CreateOptions) error {
accessor, err := meta.Accessor(obj)
if err != nil {
return fmt.Errorf("failed to get accessor for object: %w", err)
@@ -320,7 +330,7 @@ func (t versionedTracker) Create(gvr schema.GroupVersionResource, obj runtime.Ob
if err != nil {
return err
}
if err := t.ObjectTracker.Create(gvr, obj, ns); err != nil {
if err := t.ObjectTracker.Create(gvr, obj, ns, opts...); err != nil {
accessor.SetResourceVersion("")
return err
}
@@ -359,24 +369,59 @@ func convertFromUnstructuredIfNecessary(s *runtime.Scheme, o runtime.Object) (ru
return typed, nil
}
func (t versionedTracker) Update(gvr schema.GroupVersionResource, obj runtime.Object, ns string) error {
func (t versionedTracker) Update(gvr schema.GroupVersionResource, obj runtime.Object, ns string, opts ...metav1.UpdateOptions) error {
updateOpts, err := getSingleOrZeroOptions(opts)
if err != nil {
return err
}
return t.update(gvr, obj, ns, false, false, updateOpts)
}
func (t versionedTracker) update(gvr schema.GroupVersionResource, obj runtime.Object, ns string, isStatus, deleting bool, opts metav1.UpdateOptions) error {
obj, err := t.updateObject(gvr, obj, ns, isStatus, deleting, opts.DryRun)
if err != nil {
return err
}
if obj == nil {
return nil
}
return t.ObjectTracker.Update(gvr, obj, ns, opts)
}
func (t versionedTracker) Patch(gvr schema.GroupVersionResource, obj runtime.Object, ns string, opts ...metav1.PatchOptions) error {
patchOptions, err := getSingleOrZeroOptions(opts)
if err != nil {
return err
}
isStatus := false
// We apply patches using a client-go reaction that ends up calling the trackers Update. As we can't change
// We apply patches using a client-go reaction that ends up calling the trackers Patch. As we can't change
// that reaction, we use the callstack to figure out if this originated from the status client.
if bytes.Contains(debug.Stack(), []byte("sigs.k8s.io/controller-runtime/pkg/client/fake.(*fakeSubResourceClient).statusPatch")) {
isStatus = true
}
return t.update(gvr, obj, ns, isStatus, false)
obj, err = t.updateObject(gvr, obj, ns, isStatus, false, patchOptions.DryRun)
if err != nil {
return err
}
if obj == nil {
return nil
}
return t.ObjectTracker.Patch(gvr, obj, ns, patchOptions)
}
func (t versionedTracker) update(gvr schema.GroupVersionResource, obj runtime.Object, ns string, isStatus bool, deleting bool) error {
func (t versionedTracker) updateObject(gvr schema.GroupVersionResource, obj runtime.Object, ns string, isStatus, deleting bool, dryRun []string) (runtime.Object, error) {
accessor, err := meta.Accessor(obj)
if err != nil {
return fmt.Errorf("failed to get accessor for object: %w", err)
return nil, fmt.Errorf("failed to get accessor for object: %w", err)
}
if accessor.GetName() == "" {
return apierrors.NewInvalid(
return nil, apierrors.NewInvalid(
obj.GetObjectKind().GroupVersionKind().GroupKind(),
accessor.GetName(),
field.ErrorList{field.Required(field.NewPath("metadata.name"), "name is required")})
@@ -384,7 +429,7 @@ func (t versionedTracker) update(gvr schema.GroupVersionResource, obj runtime.Ob
gvk, err := apiutil.GVKForObject(obj, t.scheme)
if err != nil {
return err
return nil, err
}
oldObject, err := t.ObjectTracker.Get(gvr, ns, accessor.GetName())
@@ -392,65 +437,75 @@ func (t versionedTracker) update(gvr schema.GroupVersionResource, obj runtime.Ob
// If the resource is not found and the resource allows create on update, issue a
// create instead.
if apierrors.IsNotFound(err) && allowsCreateOnUpdate(gvk) {
return t.Create(gvr, obj, ns)
return nil, t.Create(gvr, obj, ns)
}
return err
return nil, err
}
if t.withStatusSubresource.Has(gvk) {
if isStatus { // copy everything but status and metadata.ResourceVersion from original object
if err := copyStatusFrom(obj, oldObject); err != nil {
return fmt.Errorf("failed to copy non-status field for object with status subresouce: %w", err)
return nil, fmt.Errorf("failed to copy non-status field for object with status subresouce: %w", err)
}
passedRV := accessor.GetResourceVersion()
if err := copyFrom(oldObject, obj); err != nil {
return fmt.Errorf("failed to restore non-status fields: %w", err)
return nil, fmt.Errorf("failed to restore non-status fields: %w", err)
}
accessor.SetResourceVersion(passedRV)
} else { // copy status from original object
if err := copyStatusFrom(oldObject, obj); err != nil {
return fmt.Errorf("failed to copy the status for object with status subresource: %w", err)
return nil, fmt.Errorf("failed to copy the status for object with status subresource: %w", err)
}
}
} else if isStatus {
return apierrors.NewNotFound(gvr.GroupResource(), accessor.GetName())
return nil, apierrors.NewNotFound(gvr.GroupResource(), accessor.GetName())
}
oldAccessor, err := meta.Accessor(oldObject)
if err != nil {
return err
return nil, err
}
// If the new object does not have the resource version set and it allows unconditional update,
// default it to the resource version of the existing resource
if accessor.GetResourceVersion() == "" && allowsUnconditionalUpdate(gvk) {
accessor.SetResourceVersion(oldAccessor.GetResourceVersion())
if accessor.GetResourceVersion() == "" {
switch {
case allowsUnconditionalUpdate(gvk):
accessor.SetResourceVersion(oldAccessor.GetResourceVersion())
// This is needed because if the patch explicitly sets the RV to null, the client-go reaction we use
// to apply it and whose output we process here will have it unset. It is not clear why the Kubernetes
// apiserver accepts such a patch, but it does so we just copy that behavior.
// Kubernetes apiserver behavior can be checked like this:
// `kubectl patch configmap foo --patch '{"metadata":{"annotations":{"foo":"bar"},"resourceVersion":null}}' -v=9`
case bytes.
Contains(debug.Stack(), []byte("sigs.k8s.io/controller-runtime/pkg/client/fake.(*fakeClient).Patch")):
// We apply patches using a client-go reaction that ends up calling the trackers Update. As we can't change
// that reaction, we use the callstack to figure out if this originated from the "fakeClient.Patch" func.
accessor.SetResourceVersion(oldAccessor.GetResourceVersion())
}
}
if accessor.GetResourceVersion() != oldAccessor.GetResourceVersion() {
return apierrors.NewConflict(gvr.GroupResource(), accessor.GetName(), errors.New("object was modified"))
return nil, apierrors.NewConflict(gvr.GroupResource(), accessor.GetName(), errors.New("object was modified"))
}
if oldAccessor.GetResourceVersion() == "" {
oldAccessor.SetResourceVersion("0")
}
intResourceVersion, err := strconv.ParseUint(oldAccessor.GetResourceVersion(), 10, 64)
if err != nil {
return fmt.Errorf("can not convert resourceVersion %q to int: %w", oldAccessor.GetResourceVersion(), err)
return nil, fmt.Errorf("can not convert resourceVersion %q to int: %w", oldAccessor.GetResourceVersion(), err)
}
intResourceVersion++
accessor.SetResourceVersion(strconv.FormatUint(intResourceVersion, 10))
if !deleting && !deletionTimestampEqual(accessor, oldAccessor) {
return fmt.Errorf("error: Unable to edit %s: metadata.deletionTimestamp field is immutable", accessor.GetName())
return nil, fmt.Errorf("error: Unable to edit %s: metadata.deletionTimestamp field is immutable", accessor.GetName())
}
if !accessor.GetDeletionTimestamp().IsZero() && len(accessor.GetFinalizers()) == 0 {
return t.ObjectTracker.Delete(gvr, accessor.GetNamespace(), accessor.GetName())
return nil, t.ObjectTracker.Delete(gvr, accessor.GetNamespace(), accessor.GetName(), metav1.DeleteOptions{DryRun: dryRun})
}
obj, err = convertFromUnstructuredIfNecessary(t.scheme, obj)
if err != nil {
return err
}
return t.ObjectTracker.Update(gvr, obj, ns)
return convertFromUnstructuredIfNecessary(t.scheme, obj)
}
func (c *fakeClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
@@ -463,7 +518,10 @@ func (c *fakeClient) Get(ctx context.Context, key client.ObjectKey, obj client.O
return err
}
if _, isUnstructured := obj.(runtime.Unstructured); isUnstructured {
_, isUnstructured := obj.(runtime.Unstructured)
_, isPartialObject := obj.(*metav1.PartialObjectMetadata)
if isUnstructured || isPartialObject {
gvk, err := apiutil.GVKForObject(obj, c.scheme)
if err != nil {
return err
@@ -684,6 +742,8 @@ func (c *fakeClient) Create(ctx context.Context, obj client.Object, opts ...clie
accessor.SetDeletionTimestamp(nil)
}
c.trackerWriteLock.Lock()
defer c.trackerWriteLock.Unlock()
return c.tracker.Create(gvr, obj, accessor.GetNamespace())
}
@@ -705,6 +765,8 @@ func (c *fakeClient) Delete(ctx context.Context, obj client.Object, opts ...clie
}
}
c.trackerWriteLock.Lock()
defer c.trackerWriteLock.Unlock()
// Check the ResourceVersion if that Precondition was specified.
if delOptions.Preconditions != nil && delOptions.Preconditions.ResourceVersion != nil {
name := accessor.GetName()
@@ -727,7 +789,7 @@ func (c *fakeClient) Delete(ctx context.Context, obj client.Object, opts ...clie
}
}
return c.deleteObject(gvr, accessor)
return c.deleteObjectLocked(gvr, accessor)
}
func (c *fakeClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error {
@@ -745,6 +807,9 @@ func (c *fakeClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ..
}
}
c.trackerWriteLock.Lock()
defer c.trackerWriteLock.Unlock()
gvr, _ := meta.UnsafeGuessKindToResource(gvk)
o, err := c.tracker.List(gvr, gvk, dcOptions.Namespace)
if err != nil {
@@ -764,7 +829,7 @@ func (c *fakeClient) DeleteAllOf(ctx context.Context, obj client.Object, opts ..
if err != nil {
return err
}
err = c.deleteObject(gvr, accessor)
err = c.deleteObjectLocked(gvr, accessor)
if err != nil {
return err
}
@@ -794,7 +859,10 @@ func (c *fakeClient) update(obj client.Object, isStatus bool, opts ...client.Upd
if err != nil {
return err
}
return c.tracker.update(gvr, obj, accessor.GetNamespace(), isStatus, false)
c.trackerWriteLock.Lock()
defer c.trackerWriteLock.Unlock()
return c.tracker.update(gvr, obj, accessor.GetNamespace(), isStatus, false, *updateOptions.AsUpdateOptions())
}
func (c *fakeClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
@@ -829,6 +897,8 @@ func (c *fakeClient) patch(obj client.Object, patch client.Patch, opts ...client
return err
}
c.trackerWriteLock.Lock()
defer c.trackerWriteLock.Unlock()
oldObj, err := c.tracker.Get(gvr, accessor.GetNamespace(), accessor.GetName())
if err != nil {
return err
@@ -1037,7 +1107,7 @@ func (c *fakeClient) SubResource(subResource string) client.SubResourceClient {
return &fakeSubResourceClient{client: c, subResource: subResource}
}
func (c *fakeClient) deleteObject(gvr schema.GroupVersionResource, accessor metav1.Object) error {
func (c *fakeClient) deleteObjectLocked(gvr schema.GroupVersionResource, accessor metav1.Object) error {
old, err := c.tracker.Get(gvr, accessor.GetNamespace(), accessor.GetName())
if err == nil {
oldAccessor, err := meta.Accessor(old)
@@ -1047,7 +1117,7 @@ func (c *fakeClient) deleteObject(gvr schema.GroupVersionResource, accessor meta
oldAccessor.SetDeletionTimestamp(&now)
// Call update directly with mutability parameter set to true to allow
// changes to deletionTimestamp
return c.tracker.update(gvr, old, accessor.GetNamespace(), false, true)
return c.tracker.update(gvr, old, accessor.GetNamespace(), false, true, metav1.UpdateOptions{})
}
}
}
@@ -1071,7 +1141,26 @@ type fakeSubResourceClient struct {
}
func (sw *fakeSubResourceClient) Get(ctx context.Context, obj, subResource client.Object, opts ...client.SubResourceGetOption) error {
panic("fakeSubResourceClient does not support get")
switch sw.subResource {
case subResourceScale:
// Actual client looks up resource, then extracts the scale sub-resource:
// https://github.com/kubernetes/kubernetes/blob/fb6bbc9781d11a87688c398778525c4e1dcb0f08/pkg/registry/apps/deployment/storage/storage.go#L307
if err := sw.client.Get(ctx, client.ObjectKeyFromObject(obj), obj); err != nil {
return err
}
scale, isScale := subResource.(*autoscalingv1.Scale)
if !isScale {
return apierrors.NewBadRequest(fmt.Sprintf("expected Scale, got %t", subResource))
}
scaleOut, err := extractScale(obj)
if err != nil {
return err
}
*scale = *scaleOut
return nil
default:
return fmt.Errorf("fakeSubResourceClient does not support get for %s", sw.subResource)
}
}
func (sw *fakeSubResourceClient) Create(ctx context.Context, obj client.Object, subResource client.Object, opts ...client.SubResourceCreateOption) error {
@@ -1098,11 +1187,30 @@ func (sw *fakeSubResourceClient) Update(ctx context.Context, obj client.Object,
updateOptions := client.SubResourceUpdateOptions{}
updateOptions.ApplyOptions(opts)
body := obj
if updateOptions.SubResourceBody != nil {
body = updateOptions.SubResourceBody
switch sw.subResource {
case subResourceScale:
if err := sw.client.Get(ctx, client.ObjectKeyFromObject(obj), obj.DeepCopyObject().(client.Object)); err != nil {
return err
}
if updateOptions.SubResourceBody == nil {
return apierrors.NewBadRequest("missing SubResourceBody")
}
scale, isScale := updateOptions.SubResourceBody.(*autoscalingv1.Scale)
if !isScale {
return apierrors.NewBadRequest(fmt.Sprintf("expected Scale, got %t", updateOptions.SubResourceBody))
}
if err := applyScale(obj, scale); err != nil {
return err
}
return sw.client.update(obj, false, &updateOptions.UpdateOptions)
default:
body := obj
if updateOptions.SubResourceBody != nil {
body = updateOptions.SubResourceBody
}
return sw.client.update(body, true, &updateOptions.UpdateOptions)
}
return sw.client.update(body, true, &updateOptions.UpdateOptions)
}
func (sw *fakeSubResourceClient) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.SubResourcePatchOption) error {
@@ -1269,3 +1377,138 @@ func zero(x interface{}) {
res := reflect.ValueOf(x).Elem()
res.Set(reflect.Zero(res.Type()))
}
// getSingleOrZeroOptions returns the single options value in the slice, its
// zero value if the slice is empty, or an error if the slice contains more than
// one option value.
func getSingleOrZeroOptions[T any](opts []T) (opt T, err error) {
switch len(opts) {
case 0:
case 1:
opt = opts[0]
default:
err = fmt.Errorf("expected single or no options value, got %d values", len(opts))
}
return
}
func extractScale(obj client.Object) (*autoscalingv1.Scale, error) {
switch obj := obj.(type) {
case *appsv1.Deployment:
var replicas int32 = 1
if obj.Spec.Replicas != nil {
replicas = *obj.Spec.Replicas
}
var selector string
if obj.Spec.Selector != nil {
selector = obj.Spec.Selector.String()
}
return &autoscalingv1.Scale{
ObjectMeta: metav1.ObjectMeta{
Namespace: obj.Namespace,
Name: obj.Name,
UID: obj.UID,
ResourceVersion: obj.ResourceVersion,
CreationTimestamp: obj.CreationTimestamp,
},
Spec: autoscalingv1.ScaleSpec{
Replicas: replicas,
},
Status: autoscalingv1.ScaleStatus{
Replicas: obj.Status.Replicas,
Selector: selector,
},
}, nil
case *appsv1.ReplicaSet:
var replicas int32 = 1
if obj.Spec.Replicas != nil {
replicas = *obj.Spec.Replicas
}
var selector string
if obj.Spec.Selector != nil {
selector = obj.Spec.Selector.String()
}
return &autoscalingv1.Scale{
ObjectMeta: metav1.ObjectMeta{
Namespace: obj.Namespace,
Name: obj.Name,
UID: obj.UID,
ResourceVersion: obj.ResourceVersion,
CreationTimestamp: obj.CreationTimestamp,
},
Spec: autoscalingv1.ScaleSpec{
Replicas: replicas,
},
Status: autoscalingv1.ScaleStatus{
Replicas: obj.Status.Replicas,
Selector: selector,
},
}, nil
case *corev1.ReplicationController:
var replicas int32 = 1
if obj.Spec.Replicas != nil {
replicas = *obj.Spec.Replicas
}
return &autoscalingv1.Scale{
ObjectMeta: metav1.ObjectMeta{
Namespace: obj.Namespace,
Name: obj.Name,
UID: obj.UID,
ResourceVersion: obj.ResourceVersion,
CreationTimestamp: obj.CreationTimestamp,
},
Spec: autoscalingv1.ScaleSpec{
Replicas: replicas,
},
Status: autoscalingv1.ScaleStatus{
Replicas: obj.Status.Replicas,
Selector: labels.Set(obj.Spec.Selector).String(),
},
}, nil
case *appsv1.StatefulSet:
var replicas int32 = 1
if obj.Spec.Replicas != nil {
replicas = *obj.Spec.Replicas
}
var selector string
if obj.Spec.Selector != nil {
selector = obj.Spec.Selector.String()
}
return &autoscalingv1.Scale{
ObjectMeta: metav1.ObjectMeta{
Namespace: obj.Namespace,
Name: obj.Name,
UID: obj.UID,
ResourceVersion: obj.ResourceVersion,
CreationTimestamp: obj.CreationTimestamp,
},
Spec: autoscalingv1.ScaleSpec{
Replicas: replicas,
},
Status: autoscalingv1.ScaleStatus{
Replicas: obj.Status.Replicas,
Selector: selector,
},
}, nil
default:
// TODO: CRDs https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#scale-subresource
return nil, fmt.Errorf("unimplemented scale subresource for resource %T", obj)
}
}
func applyScale(obj client.Object, scale *autoscalingv1.Scale) error {
switch obj := obj.(type) {
case *appsv1.Deployment:
obj.Spec.Replicas = ptr.To(scale.Spec.Replicas)
case *appsv1.ReplicaSet:
obj.Spec.Replicas = ptr.To(scale.Spec.Replicas)
case *corev1.ReplicationController:
obj.Spec.Replicas = ptr.To(scale.Spec.Replicas)
case *appsv1.StatefulSet:
obj.Spec.Replicas = ptr.To(scale.Spec.Replicas)
default:
// TODO: CRDs https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#scale-subresource
return fmt.Errorf("unimplemented scale subresource for resource %T", obj)
}
return nil
}

View File

@@ -0,0 +1,106 @@
/*
Copyright 2024 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 client
import (
"context"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// WithFieldOwner wraps a Client and adds the fieldOwner as the field
// manager to all write requests from this client. If additional [FieldOwner]
// options are specified on methods of this client, the value specified here
// will be overridden.
func WithFieldOwner(c Client, fieldOwner string) Client {
return &clientWithFieldManager{
owner: fieldOwner,
c: c,
Reader: c,
}
}
type clientWithFieldManager struct {
owner string
c Client
Reader
}
func (f *clientWithFieldManager) Create(ctx context.Context, obj Object, opts ...CreateOption) error {
return f.c.Create(ctx, obj, append([]CreateOption{FieldOwner(f.owner)}, opts...)...)
}
func (f *clientWithFieldManager) Update(ctx context.Context, obj Object, opts ...UpdateOption) error {
return f.c.Update(ctx, obj, append([]UpdateOption{FieldOwner(f.owner)}, opts...)...)
}
func (f *clientWithFieldManager) Patch(ctx context.Context, obj Object, patch Patch, opts ...PatchOption) error {
return f.c.Patch(ctx, obj, patch, append([]PatchOption{FieldOwner(f.owner)}, opts...)...)
}
func (f *clientWithFieldManager) Delete(ctx context.Context, obj Object, opts ...DeleteOption) error {
return f.c.Delete(ctx, obj, opts...)
}
func (f *clientWithFieldManager) DeleteAllOf(ctx context.Context, obj Object, opts ...DeleteAllOfOption) error {
return f.c.DeleteAllOf(ctx, obj, opts...)
}
func (f *clientWithFieldManager) Scheme() *runtime.Scheme { return f.c.Scheme() }
func (f *clientWithFieldManager) RESTMapper() meta.RESTMapper { return f.c.RESTMapper() }
func (f *clientWithFieldManager) GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error) {
return f.c.GroupVersionKindFor(obj)
}
func (f *clientWithFieldManager) IsObjectNamespaced(obj runtime.Object) (bool, error) {
return f.c.IsObjectNamespaced(obj)
}
func (f *clientWithFieldManager) Status() StatusWriter {
return &subresourceClientWithFieldOwner{
owner: f.owner,
subresourceWriter: f.c.Status(),
}
}
func (f *clientWithFieldManager) SubResource(subresource string) SubResourceClient {
c := f.c.SubResource(subresource)
return &subresourceClientWithFieldOwner{
owner: f.owner,
subresourceWriter: c,
SubResourceReader: c,
}
}
type subresourceClientWithFieldOwner struct {
owner string
subresourceWriter SubResourceWriter
SubResourceReader
}
func (f *subresourceClientWithFieldOwner) Create(ctx context.Context, obj Object, subresource Object, opts ...SubResourceCreateOption) error {
return f.subresourceWriter.Create(ctx, obj, subresource, append([]SubResourceCreateOption{FieldOwner(f.owner)}, opts...)...)
}
func (f *subresourceClientWithFieldOwner) Update(ctx context.Context, obj Object, opts ...SubResourceUpdateOption) error {
return f.subresourceWriter.Update(ctx, obj, append([]SubResourceUpdateOption{FieldOwner(f.owner)}, opts...)...)
}
func (f *subresourceClientWithFieldOwner) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error {
return f.subresourceWriter.Patch(ctx, obj, patch, append([]SubResourcePatchOption{FieldOwner(f.owner)}, opts...)...)
}

View File

@@ -0,0 +1,106 @@
/*
Copyright 2024 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 client
import (
"context"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// WithFieldValidation wraps a Client and configures field validation, by
// default, for all write requests from this client. Users can override field
// validation for individual write requests.
func WithFieldValidation(c Client, validation FieldValidation) Client {
return &clientWithFieldValidation{
validation: validation,
client: c,
Reader: c,
}
}
type clientWithFieldValidation struct {
validation FieldValidation
client Client
Reader
}
func (c *clientWithFieldValidation) Create(ctx context.Context, obj Object, opts ...CreateOption) error {
return c.client.Create(ctx, obj, append([]CreateOption{c.validation}, opts...)...)
}
func (c *clientWithFieldValidation) Update(ctx context.Context, obj Object, opts ...UpdateOption) error {
return c.client.Update(ctx, obj, append([]UpdateOption{c.validation}, opts...)...)
}
func (c *clientWithFieldValidation) Patch(ctx context.Context, obj Object, patch Patch, opts ...PatchOption) error {
return c.client.Patch(ctx, obj, patch, append([]PatchOption{c.validation}, opts...)...)
}
func (c *clientWithFieldValidation) Delete(ctx context.Context, obj Object, opts ...DeleteOption) error {
return c.client.Delete(ctx, obj, opts...)
}
func (c *clientWithFieldValidation) DeleteAllOf(ctx context.Context, obj Object, opts ...DeleteAllOfOption) error {
return c.client.DeleteAllOf(ctx, obj, opts...)
}
func (c *clientWithFieldValidation) Scheme() *runtime.Scheme { return c.client.Scheme() }
func (c *clientWithFieldValidation) RESTMapper() meta.RESTMapper { return c.client.RESTMapper() }
func (c *clientWithFieldValidation) GroupVersionKindFor(obj runtime.Object) (schema.GroupVersionKind, error) {
return c.client.GroupVersionKindFor(obj)
}
func (c *clientWithFieldValidation) IsObjectNamespaced(obj runtime.Object) (bool, error) {
return c.client.IsObjectNamespaced(obj)
}
func (c *clientWithFieldValidation) Status() StatusWriter {
return &subresourceClientWithFieldValidation{
validation: c.validation,
subresourceWriter: c.client.Status(),
}
}
func (c *clientWithFieldValidation) SubResource(subresource string) SubResourceClient {
srClient := c.client.SubResource(subresource)
return &subresourceClientWithFieldValidation{
validation: c.validation,
subresourceWriter: srClient,
SubResourceReader: srClient,
}
}
type subresourceClientWithFieldValidation struct {
validation FieldValidation
subresourceWriter SubResourceWriter
SubResourceReader
}
func (c *subresourceClientWithFieldValidation) Create(ctx context.Context, obj Object, subresource Object, opts ...SubResourceCreateOption) error {
return c.subresourceWriter.Create(ctx, obj, subresource, append([]SubResourceCreateOption{c.validation}, opts...)...)
}
func (c *subresourceClientWithFieldValidation) Update(ctx context.Context, obj Object, opts ...SubResourceUpdateOption) error {
return c.subresourceWriter.Update(ctx, obj, append([]SubResourceUpdateOption{c.validation}, opts...)...)
}
func (c *subresourceClientWithFieldValidation) Patch(ctx context.Context, obj Object, patch Patch, opts ...SubResourcePatchOption) error {
return c.subresourceWriter.Patch(ctx, obj, patch, append([]SubResourcePatchOption{c.validation}, opts...)...)
}

View File

@@ -169,6 +169,39 @@ func (f FieldOwner) ApplyToSubResourceUpdate(opts *SubResourceUpdateOptions) {
opts.FieldManager = string(f)
}
// FieldValidation configures field validation for the given requests.
type FieldValidation string
// ApplyToPatch applies this configuration to the given patch options.
func (f FieldValidation) ApplyToPatch(opts *PatchOptions) {
opts.FieldValidation = string(f)
}
// ApplyToCreate applies this configuration to the given create options.
func (f FieldValidation) ApplyToCreate(opts *CreateOptions) {
opts.FieldValidation = string(f)
}
// ApplyToUpdate applies this configuration to the given update options.
func (f FieldValidation) ApplyToUpdate(opts *UpdateOptions) {
opts.FieldValidation = string(f)
}
// ApplyToSubResourcePatch applies this configuration to the given patch options.
func (f FieldValidation) ApplyToSubResourcePatch(opts *SubResourcePatchOptions) {
opts.FieldValidation = string(f)
}
// ApplyToSubResourceCreate applies this configuration to the given create options.
func (f FieldValidation) ApplyToSubResourceCreate(opts *SubResourceCreateOptions) {
opts.FieldValidation = string(f)
}
// ApplyToSubResourceUpdate applies this configuration to the given update options.
func (f FieldValidation) ApplyToSubResourceUpdate(opts *SubResourceUpdateOptions) {
opts.FieldValidation = string(f)
}
// }}}
// {{{ Create Options
@@ -187,6 +220,24 @@ type CreateOptions struct {
// this request. It must be set with server-side apply.
FieldManager string
// fieldValidation instructs the server on how to handle
// objects in the request (POST/PUT/PATCH) containing unknown
// or duplicate fields. Valid values are:
// - Ignore: This will ignore any unknown fields that are silently
// dropped from the object, and will ignore all but the last duplicate
// field that the decoder encounters. This is the default behavior
// prior to v1.23.
// - Warn: This will send a warning via the standard warning response
// header for each unknown field that is dropped from the object, and
// for each duplicate field that is encountered. The request will
// still succeed if there are no other errors, and will only persist
// the last of any duplicate fields. This is the default in v1.23+
// - Strict: This will fail the request with a BadRequest error if
// any unknown fields would be dropped from the object, or if any
// duplicate fields are present. The error returned from the server
// will contain all unknown and duplicate fields encountered.
FieldValidation string
// Raw represents raw CreateOptions, as passed to the API server.
Raw *metav1.CreateOptions
}
@@ -203,6 +254,7 @@ func (o *CreateOptions) AsCreateOptions() *metav1.CreateOptions {
o.Raw.DryRun = o.DryRun
o.Raw.FieldManager = o.FieldManager
o.Raw.FieldValidation = o.FieldValidation
return o.Raw
}
@@ -223,6 +275,9 @@ func (o *CreateOptions) ApplyToCreate(co *CreateOptions) {
if o.FieldManager != "" {
co.FieldManager = o.FieldManager
}
if o.FieldValidation != "" {
co.FieldValidation = o.FieldValidation
}
if o.Raw != nil {
co.Raw = o.Raw
}
@@ -679,6 +734,24 @@ type UpdateOptions struct {
// this request. It must be set with server-side apply.
FieldManager string
// fieldValidation instructs the server on how to handle
// objects in the request (POST/PUT/PATCH) containing unknown
// or duplicate fields. Valid values are:
// - Ignore: This will ignore any unknown fields that are silently
// dropped from the object, and will ignore all but the last duplicate
// field that the decoder encounters. This is the default behavior
// prior to v1.23.
// - Warn: This will send a warning via the standard warning response
// header for each unknown field that is dropped from the object, and
// for each duplicate field that is encountered. The request will
// still succeed if there are no other errors, and will only persist
// the last of any duplicate fields. This is the default in v1.23+
// - Strict: This will fail the request with a BadRequest error if
// any unknown fields would be dropped from the object, or if any
// duplicate fields are present. The error returned from the server
// will contain all unknown and duplicate fields encountered.
FieldValidation string
// Raw represents raw UpdateOptions, as passed to the API server.
Raw *metav1.UpdateOptions
}
@@ -695,6 +768,7 @@ func (o *UpdateOptions) AsUpdateOptions() *metav1.UpdateOptions {
o.Raw.DryRun = o.DryRun
o.Raw.FieldManager = o.FieldManager
o.Raw.FieldValidation = o.FieldValidation
return o.Raw
}
@@ -717,6 +791,9 @@ func (o *UpdateOptions) ApplyToUpdate(uo *UpdateOptions) {
if o.FieldManager != "" {
uo.FieldManager = o.FieldManager
}
if o.FieldValidation != "" {
uo.FieldValidation = o.FieldValidation
}
if o.Raw != nil {
uo.Raw = o.Raw
}
@@ -745,6 +822,24 @@ type PatchOptions struct {
// this request. It must be set with server-side apply.
FieldManager string
// fieldValidation instructs the server on how to handle
// objects in the request (POST/PUT/PATCH) containing unknown
// or duplicate fields. Valid values are:
// - Ignore: This will ignore any unknown fields that are silently
// dropped from the object, and will ignore all but the last duplicate
// field that the decoder encounters. This is the default behavior
// prior to v1.23.
// - Warn: This will send a warning via the standard warning response
// header for each unknown field that is dropped from the object, and
// for each duplicate field that is encountered. The request will
// still succeed if there are no other errors, and will only persist
// the last of any duplicate fields. This is the default in v1.23+
// - Strict: This will fail the request with a BadRequest error if
// any unknown fields would be dropped from the object, or if any
// duplicate fields are present. The error returned from the server
// will contain all unknown and duplicate fields encountered.
FieldValidation string
// Raw represents raw PatchOptions, as passed to the API server.
Raw *metav1.PatchOptions
}
@@ -771,6 +866,7 @@ func (o *PatchOptions) AsPatchOptions() *metav1.PatchOptions {
o.Raw.DryRun = o.DryRun
o.Raw.Force = o.Force
o.Raw.FieldManager = o.FieldManager
o.Raw.FieldValidation = o.FieldValidation
return o.Raw
}
@@ -787,6 +883,9 @@ func (o *PatchOptions) ApplyToPatch(po *PatchOptions) {
if o.FieldManager != "" {
po.FieldManager = o.FieldManager
}
if o.FieldValidation != "" {
po.FieldValidation = o.FieldValidation
}
if o.Raw != nil {
po.Raw = o.Raw
}