Server-Side Apply: Status Wiping/Reset Fields

Adds and implements ResetFieldsProvder interface in order to ensure that
the fieldmanager no longer owns fields that get reset before the object
is persisted.

Co-authored-by: Kevin Wiesmueller <kwiesmul@redhat.com>
Co-authored-by: Kevin Delgado <kevindelgado@google.com>
This commit is contained in:
Kevin Delgado 2021-03-09 23:54:55 +00:00
parent d66477d512
commit a1fac8cbd9
67 changed files with 1448 additions and 121 deletions

1
go.mod
View File

@ -137,6 +137,7 @@ require (
k8s.io/sample-apiserver v0.0.0
k8s.io/system-validators v1.4.0
k8s.io/utils v0.0.0-20201110183641-67b214c5f920
sigs.k8s.io/structured-merge-diff/v4 v4.0.3
sigs.k8s.io/yaml v1.2.0
)

View File

@ -130,6 +130,10 @@ EOF
# Creates a node object with name 127.0.0.1. This is required because we do not
# run kubelet.
#
# An arbitrary annotation is needed to ensure field managers are saved on the
# object. Without it, we would be creating an empty object and because status
# and name get wiped, there were be no field managers tracking any fields.
#
# Exports:
# SUPPORTED_RESOURCES(Array of all resources supported by the apiserver).
function create_node() {
@ -138,7 +142,10 @@ function create_node() {
"kind": "Node",
"apiVersion": "v1",
"metadata": {
"name": "127.0.0.1"
"name": "127.0.0.1",
"annotations": {
"save-managers": "true"
}
},
"status": {
"capacity": {

View File

@ -29,6 +29,7 @@ import (
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
strategy "k8s.io/kubernetes/pkg/registry/apiserverinternal/storageversion"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// REST implements a RESTStorage for storage version against etcd
@ -46,10 +47,11 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
},
DefaultQualifiedResource: apiserverinternal.Resource("storageversions"),
CreateStrategy: strategy.Strategy,
UpdateStrategy: strategy.Strategy,
DeleteStrategy: strategy.Strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
CreateStrategy: strategy.Strategy,
UpdateStrategy: strategy.Strategy,
DeleteStrategy: strategy.Strategy,
ResetFieldsStrategy: strategy.Strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}
options := &generic.StoreOptions{RESTOptions: optsGetter}
if err := store.CompleteWithOptions(options); err != nil {
@ -57,6 +59,7 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
}
statusStore := *store
statusStore.UpdateStrategy = strategy.StatusStrategy
statusStore.ResetFieldsStrategy = strategy.StatusStrategy
return &REST{store}, &StatusREST{store: &statusStore}, nil
}
@ -81,3 +84,8 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat
// subresources should never allow create on update.
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}

View File

@ -26,6 +26,7 @@ import (
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/apiserverinternal"
"k8s.io/kubernetes/pkg/apis/apiserverinternal/validation"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// storageVersionStrategy implements verification logic for StorageVersion.
@ -42,6 +43,18 @@ func (storageVersionStrategy) NamespaceScoped() bool {
return false
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (storageVersionStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"internal.apiserver.k8s.io/v1alpha1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
return fields
}
// PrepareForCreate clears the status of an StorageVersion before creation.
func (storageVersionStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
sv := obj.(*apiserverinternal.StorageVersion)
@ -90,6 +103,19 @@ type storageVersionStatusStrategy struct {
// StatusStrategy is the default logic invoked when updating object status.
var StatusStrategy = storageVersionStatusStrategy{Strategy}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (storageVersionStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"internal.apiserver.k8s.io/v1alpha1": fieldpath.NewSet(
fieldpath.MakePathOrDie("metadata"),
fieldpath.MakePathOrDie("spec"),
),
}
return fields
}
func (storageVersionStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newSV := obj.(*apiserverinternal.StorageVersion)
oldSV := old.(*apiserverinternal.StorageVersion)

View File

@ -29,6 +29,7 @@ import (
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
"k8s.io/kubernetes/pkg/registry/apps/daemonset"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// REST implements a RESTStorage for DaemonSets
@ -44,9 +45,10 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
NewListFunc: func() runtime.Object { return &apps.DaemonSetList{} },
DefaultQualifiedResource: apps.Resource("daemonsets"),
CreateStrategy: daemonset.Strategy,
UpdateStrategy: daemonset.Strategy,
DeleteStrategy: daemonset.Strategy,
CreateStrategy: daemonset.Strategy,
UpdateStrategy: daemonset.Strategy,
DeleteStrategy: daemonset.Strategy,
ResetFieldsStrategy: daemonset.Strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}
@ -57,6 +59,7 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
statusStore := *store
statusStore.UpdateStrategy = daemonset.StatusStrategy
statusStore.ResetFieldsStrategy = daemonset.StatusStrategy
return &REST{store, []string{"all"}}, &StatusREST{store: &statusStore}, nil
}
@ -103,3 +106,8 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat
// subresources should never allow create on update.
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}

View File

@ -36,6 +36,7 @@ import (
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/apps/validation"
"k8s.io/kubernetes/pkg/features"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// daemonSetStrategy implements verification logic for daemon sets.
@ -68,6 +69,18 @@ func (daemonSetStrategy) NamespaceScoped() bool {
return true
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (daemonSetStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"apps/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
return fields
}
// PrepareForCreate clears the status of a daemon set before creation.
func (daemonSetStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
daemonSet := obj.(*apps.DaemonSet)
@ -202,6 +215,16 @@ type daemonSetStatusStrategy struct {
// StatusStrategy is the default logic invoked when updating object status.
var StatusStrategy = daemonSetStatusStrategy{Strategy}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (daemonSetStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return map[fieldpath.APIVersion]*fieldpath.Set{
"apps/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
),
}
}
func (daemonSetStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newDaemonSet := obj.(*apps.DaemonSet)
oldDaemonSet := old.(*apps.DaemonSet)

View File

@ -43,6 +43,7 @@ import (
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
"k8s.io/kubernetes/pkg/registry/apps/deployment"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// DeploymentStorage includes dummy storage for Deployments and for Scale subresource.
@ -81,9 +82,10 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, *Rollbac
NewListFunc: func() runtime.Object { return &apps.DeploymentList{} },
DefaultQualifiedResource: apps.Resource("deployments"),
CreateStrategy: deployment.Strategy,
UpdateStrategy: deployment.Strategy,
DeleteStrategy: deployment.Strategy,
CreateStrategy: deployment.Strategy,
UpdateStrategy: deployment.Strategy,
DeleteStrategy: deployment.Strategy,
ResetFieldsStrategy: deployment.Strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}
@ -94,6 +96,7 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, *Rollbac
statusStore := *store
statusStore.UpdateStrategy = deployment.StatusStrategy
statusStore.ResetFieldsStrategy = deployment.StatusStrategy
return &REST{store, []string{"all"}}, &StatusREST{store: &statusStore}, &RollbackREST{store: store}, nil
}
@ -141,6 +144,11 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}
// RollbackREST implements the REST endpoint for initiating the rollback of a deployment
type RollbackREST struct {
store *genericregistry.Store

View File

@ -34,6 +34,7 @@ import (
"k8s.io/kubernetes/pkg/api/pod"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/apps/validation"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// deploymentStrategy implements behavior for Deployments.
@ -67,6 +68,18 @@ func (deploymentStrategy) NamespaceScoped() bool {
return true
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (deploymentStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"apps/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
return fields
}
// PrepareForCreate clears fields that are not allowed to be set by end users on creation.
func (deploymentStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
deployment := obj.(*apps.Deployment)
@ -147,6 +160,17 @@ type deploymentStatusStrategy struct {
// StatusStrategy is the default logic invoked when updating object status.
var StatusStrategy = deploymentStatusStrategy{Strategy}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (deploymentStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return map[fieldpath.APIVersion]*fieldpath.Set{
"apps/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
fieldpath.MakePathOrDie("metadata", "labels"),
),
}
}
// PrepareForUpdate clears fields that are not allowed to be set by end users on update of status
func (deploymentStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newDeployment := obj.(*apps.Deployment)

View File

@ -40,6 +40,7 @@ import (
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
"k8s.io/kubernetes/pkg/registry/apps/replicaset"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// ReplicaSetStorage includes dummy storage for ReplicaSets and for Scale subresource.
@ -77,9 +78,10 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
PredicateFunc: replicaset.MatchReplicaSet,
DefaultQualifiedResource: apps.Resource("replicasets"),
CreateStrategy: replicaset.Strategy,
UpdateStrategy: replicaset.Strategy,
DeleteStrategy: replicaset.Strategy,
CreateStrategy: replicaset.Strategy,
UpdateStrategy: replicaset.Strategy,
DeleteStrategy: replicaset.Strategy,
ResetFieldsStrategy: replicaset.Strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}
@ -90,6 +92,7 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
statusStore := *store
statusStore.UpdateStrategy = replicaset.StatusStrategy
statusStore.ResetFieldsStrategy = replicaset.StatusStrategy
return &REST{store, []string{"all"}}, &StatusREST{store: &statusStore}, nil
}
@ -138,6 +141,11 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}
// ScaleREST implements a Scale for ReplicaSet.
type ScaleREST struct {
store *genericregistry.Store

View File

@ -41,6 +41,7 @@ import (
"k8s.io/kubernetes/pkg/api/pod"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/apps/validation"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// rsStrategy implements verification logic for ReplicaSets.
@ -73,6 +74,18 @@ func (rsStrategy) NamespaceScoped() bool {
return true
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (rsStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"apps/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
return fields
}
// PrepareForCreate clears the status of a ReplicaSet before creation.
func (rsStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
rs := obj.(*apps.ReplicaSet)
@ -189,6 +202,16 @@ type rsStatusStrategy struct {
// StatusStrategy is the default logic invoked when updating object status.
var StatusStrategy = rsStatusStrategy{Strategy}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (rsStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return map[fieldpath.APIVersion]*fieldpath.Set{
"apps/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
),
}
}
func (rsStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newRS := obj.(*apps.ReplicaSet)
oldRS := old.(*apps.ReplicaSet)

View File

@ -37,6 +37,7 @@ import (
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
"k8s.io/kubernetes/pkg/registry/apps/statefulset"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// StatefulSetStorage includes dummy storage for StatefulSets, and their Status and Scale subresource.
@ -72,9 +73,10 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
NewListFunc: func() runtime.Object { return &apps.StatefulSetList{} },
DefaultQualifiedResource: apps.Resource("statefulsets"),
CreateStrategy: statefulset.Strategy,
UpdateStrategy: statefulset.Strategy,
DeleteStrategy: statefulset.Strategy,
CreateStrategy: statefulset.Strategy,
UpdateStrategy: statefulset.Strategy,
DeleteStrategy: statefulset.Strategy,
ResetFieldsStrategy: statefulset.Strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}
@ -85,6 +87,7 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
statusStore := *store
statusStore.UpdateStrategy = statefulset.StatusStrategy
statusStore.ResetFieldsStrategy = statefulset.StatusStrategy
return &REST{store}, &StatusREST{store: &statusStore}, nil
}
@ -118,6 +121,11 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}
// Implement ShortNamesProvider
var _ rest.ShortNamesProvider = &REST{}

View File

@ -32,6 +32,7 @@ import (
"k8s.io/kubernetes/pkg/api/pod"
"k8s.io/kubernetes/pkg/apis/apps"
"k8s.io/kubernetes/pkg/apis/apps/validation"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// statefulSetStrategy implements verification logic for Replication StatefulSets.
@ -64,6 +65,18 @@ func (statefulSetStrategy) NamespaceScoped() bool {
return true
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (statefulSetStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"apps/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
return fields
}
// PrepareForCreate clears the status of an StatefulSet before creation.
func (statefulSetStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
statefulSet := obj.(*apps.StatefulSet)
@ -132,6 +145,16 @@ type statefulSetStatusStrategy struct {
// StatusStrategy is the default logic invoked when updating object status.
var StatusStrategy = statefulSetStatusStrategy{Strategy}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (statefulSetStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return map[fieldpath.APIVersion]*fieldpath.Set{
"apps/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
),
}
}
// PrepareForUpdate clears fields that are not allowed to be set by end users on update of status
func (statefulSetStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newStatefulSet := obj.(*apps.StatefulSet)

View File

@ -29,6 +29,7 @@ import (
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
"k8s.io/kubernetes/pkg/registry/autoscaling/horizontalpodautoscaler"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// REST implements a RESTStorage for pod disruption budgets against etcd
@ -43,9 +44,10 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
NewListFunc: func() runtime.Object { return &autoscaling.HorizontalPodAutoscalerList{} },
DefaultQualifiedResource: autoscaling.Resource("horizontalpodautoscalers"),
CreateStrategy: horizontalpodautoscaler.Strategy,
UpdateStrategy: horizontalpodautoscaler.Strategy,
DeleteStrategy: horizontalpodautoscaler.Strategy,
CreateStrategy: horizontalpodautoscaler.Strategy,
UpdateStrategy: horizontalpodautoscaler.Strategy,
DeleteStrategy: horizontalpodautoscaler.Strategy,
ResetFieldsStrategy: horizontalpodautoscaler.Strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}
@ -56,6 +58,7 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
statusStore := *store
statusStore.UpdateStrategy = horizontalpodautoscaler.StatusStrategy
statusStore.ResetFieldsStrategy = horizontalpodautoscaler.StatusStrategy
return &REST{store}, &StatusREST{store: &statusStore}, nil
}
@ -96,3 +99,8 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat
// subresources should never allow create on update.
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}

View File

@ -27,6 +27,7 @@ import (
"k8s.io/kubernetes/pkg/apis/autoscaling"
"k8s.io/kubernetes/pkg/apis/autoscaling/validation"
"k8s.io/kubernetes/pkg/features"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// autoscalerStrategy implements behavior for HorizontalPodAutoscalers
@ -44,6 +45,24 @@ func (autoscalerStrategy) NamespaceScoped() bool {
return true
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (autoscalerStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"autoscaling/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
"autoscaling/v2beta1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
"autoscaling/v2beta2": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
return fields
}
// PrepareForCreate clears fields that are not allowed to be set by end users on creation.
func (autoscalerStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
newHPA := obj.(*autoscaling.HorizontalPodAutoscaler)
@ -115,6 +134,24 @@ type autoscalerStatusStrategy struct {
// StatusStrategy is the default logic invoked when updating object status.
var StatusStrategy = autoscalerStatusStrategy{Strategy}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (autoscalerStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"autoscaling/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
),
"autoscaling/v2beta1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
),
"autoscaling/v2beta2": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
),
}
return fields
}
func (autoscalerStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newAutoscaler := obj.(*autoscaling.HorizontalPodAutoscaler)
oldAutoscaler := old.(*autoscaling.HorizontalPodAutoscaler)

View File

@ -29,6 +29,7 @@ import (
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
"k8s.io/kubernetes/pkg/registry/batch/cronjob"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// REST implements a RESTStorage for scheduled jobs against etcd
@ -43,9 +44,10 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
NewListFunc: func() runtime.Object { return &batch.CronJobList{} },
DefaultQualifiedResource: batch.Resource("cronjobs"),
CreateStrategy: cronjob.Strategy,
UpdateStrategy: cronjob.Strategy,
DeleteStrategy: cronjob.Strategy,
CreateStrategy: cronjob.Strategy,
UpdateStrategy: cronjob.Strategy,
DeleteStrategy: cronjob.Strategy,
ResetFieldsStrategy: cronjob.Strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}
@ -56,6 +58,7 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
statusStore := *store
statusStore.UpdateStrategy = cronjob.StatusStrategy
statusStore.ResetFieldsStrategy = cronjob.StatusStrategy
return &REST{store}, &StatusREST{store: &statusStore}, nil
}
@ -94,3 +97,8 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat
// subresources should never allow create on update.
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}

View File

@ -30,6 +30,7 @@ import (
"k8s.io/kubernetes/pkg/api/pod"
"k8s.io/kubernetes/pkg/apis/batch"
"k8s.io/kubernetes/pkg/apis/batch/validation"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// cronJobStrategy implements verification logic for Replication Controllers.
@ -62,6 +63,21 @@ func (cronJobStrategy) NamespaceScoped() bool {
return true
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (cronJobStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"batch/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
"batch/v1beta1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
return fields
}
// PrepareForCreate clears the status of a scheduled job before creation.
func (cronJobStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
cronJob := obj.(*batch.CronJob)
@ -115,6 +131,19 @@ type cronJobStatusStrategy struct {
// StatusStrategy is the default logic invoked when updating object status.
var StatusStrategy = cronJobStatusStrategy{Strategy}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (cronJobStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return map[fieldpath.APIVersion]*fieldpath.Set{
"batch/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
),
"batch/v1beta1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
),
}
}
func (cronJobStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newJob := obj.(*batch.CronJob)
oldJob := old.(*batch.CronJob)

View File

@ -29,6 +29,7 @@ import (
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
"k8s.io/kubernetes/pkg/registry/batch/job"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// JobStorage includes dummy storage for Job.
@ -63,9 +64,10 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
PredicateFunc: job.MatchJob,
DefaultQualifiedResource: batch.Resource("jobs"),
CreateStrategy: job.Strategy,
UpdateStrategy: job.Strategy,
DeleteStrategy: job.Strategy,
CreateStrategy: job.Strategy,
UpdateStrategy: job.Strategy,
DeleteStrategy: job.Strategy,
ResetFieldsStrategy: job.Strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}
@ -76,6 +78,7 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
statusStore := *store
statusStore.UpdateStrategy = job.StatusStrategy
statusStore.ResetFieldsStrategy = job.StatusStrategy
return &REST{store}, &StatusREST{store: &statusStore}, nil
}
@ -109,3 +112,8 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat
// subresources should never allow create on update.
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}

View File

@ -40,6 +40,7 @@ import (
"k8s.io/kubernetes/pkg/apis/batch/validation"
"k8s.io/kubernetes/pkg/features"
"k8s.io/utils/pointer"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// jobStrategy implements verification logic for Replication Controllers.
@ -72,6 +73,18 @@ func (jobStrategy) NamespaceScoped() bool {
return true
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (jobStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"batch/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
return fields
}
// PrepareForCreate clears the status of a job before creation.
func (jobStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
job := obj.(*batch.Job)
@ -210,6 +223,16 @@ type jobStatusStrategy struct {
var StatusStrategy = jobStatusStrategy{Strategy}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (jobStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return map[fieldpath.APIVersion]*fieldpath.Set{
"batch/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
),
}
}
func (jobStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newJob := obj.(*batch.Job)
oldJob := old.(*batch.Job)

View File

@ -29,6 +29,7 @@ import (
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
csrregistry "k8s.io/kubernetes/pkg/registry/certificates/certificates"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// REST implements a RESTStorage for CertificateSigningRequest.
@ -43,9 +44,10 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, *Approva
NewListFunc: func() runtime.Object { return &certificates.CertificateSigningRequestList{} },
DefaultQualifiedResource: certificates.Resource("certificatesigningrequests"),
CreateStrategy: csrregistry.Strategy,
UpdateStrategy: csrregistry.Strategy,
DeleteStrategy: csrregistry.Strategy,
CreateStrategy: csrregistry.Strategy,
UpdateStrategy: csrregistry.Strategy,
DeleteStrategy: csrregistry.Strategy,
ResetFieldsStrategy: csrregistry.Strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}
@ -59,9 +61,11 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, *Approva
// dedicated strategies.
statusStore := *store
statusStore.UpdateStrategy = csrregistry.StatusStrategy
statusStore.ResetFieldsStrategy = csrregistry.StatusStrategy
approvalStore := *store
approvalStore.UpdateStrategy = csrregistry.ApprovalStrategy
approvalStore.ResetFieldsStrategy = csrregistry.ApprovalStrategy
return &REST{store}, &StatusREST{store: &statusStore}, &ApprovalREST{store: &approvalStore}, nil
}
@ -96,6 +100,11 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}
var _ = rest.Patcher(&StatusREST{})
// ApprovalREST implements the REST endpoint for changing the approval state of a CSR.
@ -120,4 +129,9 @@ func (r *ApprovalREST) Update(ctx context.Context, name string, objInfo rest.Upd
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *ApprovalREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}
var _ = rest.Patcher(&ApprovalREST{})

View File

@ -33,6 +33,7 @@ import (
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/certificates"
"k8s.io/kubernetes/pkg/apis/certificates/validation"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// csrStrategy implements behavior for CSRs
@ -50,6 +51,23 @@ func (csrStrategy) NamespaceScoped() bool {
return false
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (csrStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"certificates.k8s.io/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
fieldpath.MakePathOrDie("status"),
),
"certificates.k8s.io/v1beta1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
fieldpath.MakePathOrDie("status"),
),
}
return fields
}
// AllowCreateOnUpdate is false for CSRs.
func (csrStrategy) AllowCreateOnUpdate() bool {
return false
@ -125,6 +143,23 @@ type csrStatusStrategy struct {
var StatusStrategy = csrStatusStrategy{Strategy}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (csrStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"certificates.k8s.io/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
fieldpath.MakePathOrDie("status", "conditions"),
),
"certificates.k8s.io/v1beta1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
fieldpath.MakePathOrDie("status", "conditions"),
),
}
return fields
}
func (csrStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newCSR := obj.(*certificates.CertificateSigningRequest)
oldCSR := old.(*certificates.CertificateSigningRequest)
@ -220,6 +255,23 @@ type csrApprovalStrategy struct {
var ApprovalStrategy = csrApprovalStrategy{Strategy}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (csrApprovalStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"certificates.k8s.io/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
fieldpath.MakePathOrDie("status", "certificate"),
),
"certificates.k8s.io/v1beta1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
fieldpath.MakePathOrDie("status", "certificate"),
),
}
return fields
}
// PrepareForUpdate prepares the new certificate signing request by limiting
// the data that is updated to only the conditions and populating condition timestamps
func (csrApprovalStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {

View File

@ -24,6 +24,7 @@ import (
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/apiserver/pkg/registry/generic"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
@ -31,13 +32,12 @@ import (
"k8s.io/apiserver/pkg/storage"
storageerr "k8s.io/apiserver/pkg/storage/errors"
"k8s.io/apiserver/pkg/util/dryrun"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/printers"
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
"k8s.io/kubernetes/pkg/registry/core/namespace"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// rest implements a RESTStorage for namespaces
@ -67,6 +67,7 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, *Finaliz
CreateStrategy: namespace.Strategy,
UpdateStrategy: namespace.Strategy,
DeleteStrategy: namespace.Strategy,
ResetFieldsStrategy: namespace.Strategy,
ReturnDeletedObject: true,
ShouldDeleteDuringUpdate: ShouldDeleteNamespaceDuringUpdate,
@ -80,9 +81,11 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, *Finaliz
statusStore := *store
statusStore.UpdateStrategy = namespace.StatusStrategy
statusStore.ResetFieldsStrategy = namespace.StatusStrategy
finalizeStore := *store
finalizeStore.UpdateStrategy = namespace.FinalizeStrategy
finalizeStore.ResetFieldsStrategy = namespace.FinalizeStrategy
return &REST{store: store, status: &statusStore}, &StatusREST{store: &statusStore}, &FinalizeREST{store: &finalizeStore}, nil
}
@ -291,6 +294,10 @@ func (r *REST) StorageVersion() runtime.GroupVersioner {
return r.store.StorageVersion()
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *REST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}
func (r *StatusREST) New() runtime.Object {
return r.store.New()
}
@ -307,6 +314,10 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}
func (r *FinalizeREST) New() runtime.Object {
return r.store.New()
}
@ -317,3 +328,8 @@ func (r *FinalizeREST) Update(ctx context.Context, name string, objInfo rest.Upd
// subresources should never allow create on update.
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *FinalizeREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}

View File

@ -33,6 +33,7 @@ import (
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/validation"
"k8s.io/kubernetes/pkg/features"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// namespaceStrategy implements behavior for Namespaces
@ -50,6 +51,16 @@ func (namespaceStrategy) NamespaceScoped() bool {
return false
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (namespaceStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return map[fieldpath.APIVersion]*fieldpath.Set{
"v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
}
// PrepareForCreate clears fields that are not allowed to be set by end users on creation.
func (namespaceStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
// on create, status is active
@ -138,6 +149,16 @@ type namespaceStatusStrategy struct {
var StatusStrategy = namespaceStatusStrategy{Strategy}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (namespaceStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return map[fieldpath.APIVersion]*fieldpath.Set{
"v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
),
}
}
func (namespaceStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newNamespace := obj.(*api.Namespace)
oldNamespace := old.(*api.Namespace)
@ -158,6 +179,16 @@ func (namespaceFinalizeStrategy) ValidateUpdate(ctx context.Context, obj, old ru
return validation.ValidateNamespaceFinalizeUpdate(obj.(*api.Namespace), old.(*api.Namespace))
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (namespaceFinalizeStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return map[fieldpath.APIVersion]*fieldpath.Set{
"v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
}
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
func (namespaceFinalizeStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newNamespace := obj.(*api.Namespace)

View File

@ -37,6 +37,7 @@ import (
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
"k8s.io/kubernetes/pkg/registry/core/node"
noderest "k8s.io/kubernetes/pkg/registry/core/node/rest"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// NodeStorage includes storage for nodes and all sub resources.
@ -77,6 +78,11 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}
// NewStorage returns a NodeStorage object that will work against nodes.
func NewStorage(optsGetter generic.RESTOptionsGetter, kubeletClientConfig client.KubeletClientConfig, proxyTransport http.RoundTripper) (*NodeStorage, error) {
store := &genericregistry.Store{
@ -85,9 +91,10 @@ func NewStorage(optsGetter generic.RESTOptionsGetter, kubeletClientConfig client
PredicateFunc: node.MatchNode,
DefaultQualifiedResource: api.Resource("nodes"),
CreateStrategy: node.Strategy,
UpdateStrategy: node.Strategy,
DeleteStrategy: node.Strategy,
CreateStrategy: node.Strategy,
UpdateStrategy: node.Strategy,
DeleteStrategy: node.Strategy,
ResetFieldsStrategy: node.Strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}
@ -102,6 +109,7 @@ func NewStorage(optsGetter generic.RESTOptionsGetter, kubeletClientConfig client
statusStore := *store
statusStore.UpdateStrategy = node.StatusStrategy
statusStore.ResetFieldsStrategy = node.StatusStrategy
// Set up REST handlers
nodeREST := &REST{Store: store, proxyTransport: proxyTransport}

View File

@ -41,6 +41,7 @@ import (
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/kubelet/client"
proxyutil "k8s.io/kubernetes/pkg/proxy/util"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// nodeStrategy implements behavior for nodes
@ -58,6 +59,18 @@ func (nodeStrategy) NamespaceScoped() bool {
return false
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (nodeStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
return fields
}
// AllowCreateOnUpdate is false for nodes.
func (nodeStrategy) AllowCreateOnUpdate() bool {
return false
@ -147,6 +160,18 @@ type nodeStatusStrategy struct {
var StatusStrategy = nodeStatusStrategy{Strategy}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (nodeStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
),
}
return fields
}
func (nodeStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newNode := obj.(*api.Node)
oldNode := old.(*api.Node)

View File

@ -29,6 +29,7 @@ import (
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
"k8s.io/kubernetes/pkg/registry/core/persistentvolume"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// REST implements a RESTStorage for persistent volumes.
@ -48,6 +49,7 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
UpdateStrategy: persistentvolume.Strategy,
DeleteStrategy: persistentvolume.Strategy,
ReturnDeletedObject: true,
ResetFieldsStrategy: persistentvolume.Strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}
@ -58,6 +60,7 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
statusStore := *store
statusStore.UpdateStrategy = persistentvolume.StatusStrategy
statusStore.ResetFieldsStrategy = persistentvolume.StatusStrategy
return &REST{store}, &StatusREST{store: &statusStore}, nil
}
@ -91,3 +94,8 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat
// subresources should never allow create on update.
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}

View File

@ -32,6 +32,7 @@ import (
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/validation"
volumevalidation "k8s.io/kubernetes/pkg/volume/validation"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// persistentvolumeStrategy implements behavior for PersistentVolume objects
@ -48,6 +49,18 @@ func (persistentvolumeStrategy) NamespaceScoped() bool {
return false
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (persistentvolumeStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
return fields
}
// ResetBeforeCreate clears the Status field which is not allowed to be set by end users on creation.
func (persistentvolumeStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
pv := obj.(*api.PersistentVolume)
@ -96,6 +109,18 @@ type persistentvolumeStatusStrategy struct {
var StatusStrategy = persistentvolumeStatusStrategy{Strategy}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (persistentvolumeStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
),
}
return fields
}
// PrepareForUpdate sets the Spec field which is not allowed to be changed when updating a PV's Status
func (persistentvolumeStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newPv := obj.(*api.PersistentVolume)

View File

@ -29,6 +29,7 @@ import (
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
"k8s.io/kubernetes/pkg/registry/core/persistentvolumeclaim"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// REST implements a RESTStorage for persistent volume claims.
@ -48,6 +49,7 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
UpdateStrategy: persistentvolumeclaim.Strategy,
DeleteStrategy: persistentvolumeclaim.Strategy,
ReturnDeletedObject: true,
ResetFieldsStrategy: persistentvolumeclaim.Strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}
@ -58,6 +60,7 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
statusStore := *store
statusStore.UpdateStrategy = persistentvolumeclaim.StatusStrategy
statusStore.ResetFieldsStrategy = persistentvolumeclaim.StatusStrategy
return &REST{store}, &StatusREST{store: &statusStore}, nil
}
@ -91,3 +94,8 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat
// subresources should never allow create on update.
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}

View File

@ -33,6 +33,7 @@ import (
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/validation"
"k8s.io/kubernetes/pkg/features"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// persistentvolumeclaimStrategy implements behavior for PersistentVolumeClaim objects
@ -49,6 +50,18 @@ func (persistentvolumeclaimStrategy) NamespaceScoped() bool {
return true
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (persistentvolumeclaimStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
return fields
}
// PrepareForCreate clears the Status field which is not allowed to be set by end users on creation.
func (persistentvolumeclaimStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
pvc := obj.(*api.PersistentVolumeClaim)
@ -94,6 +107,18 @@ type persistentvolumeclaimStatusStrategy struct {
var StatusStrategy = persistentvolumeclaimStatusStrategy{Strategy}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (persistentvolumeclaimStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
),
}
return fields
}
// PrepareForUpdate sets the Spec field which is not allowed to be changed when updating a PV's Status
func (persistentvolumeclaimStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newPv := obj.(*api.PersistentVolumeClaim)

View File

@ -44,6 +44,7 @@ import (
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
registrypod "k8s.io/kubernetes/pkg/registry/core/pod"
podrest "k8s.io/kubernetes/pkg/registry/core/pod/rest"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// PodStorage includes storage for pods and all sub resources
@ -79,6 +80,7 @@ func NewStorage(optsGetter generic.RESTOptionsGetter, k client.ConnectionInfoGet
CreateStrategy: registrypod.Strategy,
UpdateStrategy: registrypod.Strategy,
DeleteStrategy: registrypod.Strategy,
ResetFieldsStrategy: registrypod.Strategy,
ReturnDeletedObject: true,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
@ -95,6 +97,7 @@ func NewStorage(optsGetter generic.RESTOptionsGetter, k client.ConnectionInfoGet
statusStore := *store
statusStore.UpdateStrategy = registrypod.StatusStrategy
statusStore.ResetFieldsStrategy = registrypod.StatusStrategy
ephemeralContainersStore := *store
ephemeralContainersStore.UpdateStrategy = registrypod.EphemeralContainersStrategy
@ -278,6 +281,11 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}
// EphemeralContainersREST implements the REST endpoint for adding EphemeralContainers
type EphemeralContainersREST struct {
store *genericregistry.Store

View File

@ -48,6 +48,7 @@ import (
"k8s.io/kubernetes/pkg/apis/core/validation"
"k8s.io/kubernetes/pkg/kubelet/client"
proxyutil "k8s.io/kubernetes/pkg/proxy/util"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// podStrategy implements behavior for Pods
@ -65,6 +66,18 @@ func (podStrategy) NamespaceScoped() bool {
return true
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (podStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
return fields
}
// PrepareForCreate clears fields that are not allowed to be set by end users on creation.
func (podStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
pod := obj.(*api.Pod)
@ -154,6 +167,18 @@ type podStatusStrategy struct {
// StatusStrategy wraps and exports the used podStrategy for the storage package.
var StatusStrategy = podStatusStrategy{Strategy}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (podStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return map[fieldpath.APIVersion]*fieldpath.Set{
"v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
fieldpath.MakePathOrDie("metadata", "deletionTimestamp"),
fieldpath.MakePathOrDie("metadata", "ownerReferences"),
),
}
}
func (podStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newPod := obj.(*api.Pod)
oldPod := old.(*api.Pod)

View File

@ -39,6 +39,7 @@ import (
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
"k8s.io/kubernetes/pkg/registry/core/replicationcontroller"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// ControllerStorage includes dummy storage for Replication Controllers and for Scale subresource.
@ -73,9 +74,10 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
PredicateFunc: replicationcontroller.MatchController,
DefaultQualifiedResource: api.Resource("replicationcontrollers"),
CreateStrategy: replicationcontroller.Strategy,
UpdateStrategy: replicationcontroller.Strategy,
DeleteStrategy: replicationcontroller.Strategy,
CreateStrategy: replicationcontroller.Strategy,
UpdateStrategy: replicationcontroller.Strategy,
DeleteStrategy: replicationcontroller.Strategy,
ResetFieldsStrategy: replicationcontroller.Strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}
@ -86,6 +88,7 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
statusStore := *store
statusStore.UpdateStrategy = replicationcontroller.StatusStrategy
statusStore.ResetFieldsStrategy = replicationcontroller.StatusStrategy
return &REST{store}, &StatusREST{store: &statusStore}, nil
}
@ -127,6 +130,11 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}
type ScaleREST struct {
store *genericregistry.Store
}

View File

@ -41,6 +41,7 @@ import (
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/helper"
"k8s.io/kubernetes/pkg/apis/core/validation"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// rcStrategy implements verification logic for Replication Controllers.
@ -73,6 +74,18 @@ func (rcStrategy) NamespaceScoped() bool {
return true
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (rcStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
return fields
}
// PrepareForCreate clears the status of a replication controller before creation.
func (rcStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
controller := obj.(*api.ReplicationController)
@ -192,6 +205,16 @@ type rcStatusStrategy struct {
// StatusStrategy is the default logic invoked when updating object status.
var StatusStrategy = rcStatusStrategy{Strategy}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (rcStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return map[fieldpath.APIVersion]*fieldpath.Set{
"v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
),
}
}
func (rcStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newRc := obj.(*api.ReplicationController)
oldRc := old.(*api.ReplicationController)

View File

@ -29,6 +29,7 @@ import (
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
"k8s.io/kubernetes/pkg/registry/core/resourcequota"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// REST implements a RESTStorage for resource quotas.
@ -46,6 +47,7 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
CreateStrategy: resourcequota.Strategy,
UpdateStrategy: resourcequota.Strategy,
DeleteStrategy: resourcequota.Strategy,
ResetFieldsStrategy: resourcequota.Strategy,
ReturnDeletedObject: true,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
@ -57,6 +59,7 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
statusStore := *store
statusStore.UpdateStrategy = resourcequota.StatusStrategy
statusStore.ResetFieldsStrategy = resourcequota.StatusStrategy
return &REST{store}, &StatusREST{store: &statusStore}, nil
}
@ -90,3 +93,8 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat
// subresources should never allow create on update.
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}

View File

@ -27,6 +27,7 @@ import (
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/validation"
"k8s.io/kubernetes/pkg/features"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// resourcequotaStrategy implements behavior for ResourceQuota objects
@ -44,6 +45,18 @@ func (resourcequotaStrategy) NamespaceScoped() bool {
return true
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (resourcequotaStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
return fields
}
// PrepareForCreate clears fields that are not allowed to be set by end users on creation.
func (resourcequotaStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
resourcequota := obj.(*api.ResourceQuota)
@ -91,6 +104,18 @@ type resourcequotaStatusStrategy struct {
// StatusStrategy is the default logic invoked when updating object status.
var StatusStrategy = resourcequotaStatusStrategy{Strategy}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (resourcequotaStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
),
}
return fields
}
func (resourcequotaStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newResourcequota := obj.(*api.ResourceQuota)
oldResourcequota := old.(*api.ResourceQuota)

View File

@ -36,18 +36,17 @@ import (
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/util/dryrun"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/klog/v2"
apiservice "k8s.io/kubernetes/pkg/api/service"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/validation"
"k8s.io/kubernetes/pkg/features"
registry "k8s.io/kubernetes/pkg/registry/core/service"
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
"k8s.io/kubernetes/pkg/registry/core/service/portallocator"
netutil "k8s.io/utils/net"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/features"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// REST adapts a service registry into apiserver's RESTStorage model.
@ -80,6 +79,7 @@ type ServiceStorage interface {
rest.GracefulDeleter
rest.Watcher
rest.StorageVersionProvider
rest.ResetFieldsStrategy
}
type EndpointsStorage interface {
@ -514,6 +514,11 @@ func (rs *REST) Update(ctx context.Context, name string, objInfo rest.UpdatedObj
return out, created, err
}
// GetResetFields implements rest.ResetFieldsStrategy
func (rs *REST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return rs.services.GetResetFields()
}
// Implement Redirector.
var _ = rest.Redirector(&REST{})

View File

@ -44,6 +44,7 @@ import (
"k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
"k8s.io/kubernetes/pkg/registry/core/service/portallocator"
"k8s.io/kubernetes/pkg/registry/registrytest"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
utilfeature "k8s.io/apiserver/pkg/util/feature"
featuregatetesting "k8s.io/component-base/featuregate/testing"
@ -167,6 +168,11 @@ func (s *serviceStorage) StorageVersion() runtime.GroupVersioner {
panic("not implemented")
}
// GetResetFields implements rest.ResetFieldsStrategy
func (s *serviceStorage) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return nil
}
func NewTestREST(t *testing.T, endpoints *api.EndpointsList, ipFamilies []api.IPFamily) (*REST, *serviceStorage, *etcd3testing.EtcdTestServer) {
return NewTestRESTWithPods(t, endpoints, nil, ipFamilies)
}

View File

@ -34,6 +34,7 @@ import (
"k8s.io/kubernetes/pkg/registry/core/service"
registry "k8s.io/kubernetes/pkg/registry/core/service"
svcreg "k8s.io/kubernetes/pkg/registry/core/service"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
netutil "k8s.io/utils/net"
)
@ -54,9 +55,10 @@ func NewGenericREST(optsGetter generic.RESTOptionsGetter, serviceCIDR net.IPNet,
DefaultQualifiedResource: api.Resource("services"),
ReturnDeletedObject: true,
CreateStrategy: strategy,
UpdateStrategy: strategy,
DeleteStrategy: strategy,
CreateStrategy: strategy,
UpdateStrategy: strategy,
DeleteStrategy: strategy,
ResetFieldsStrategy: strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}
@ -66,7 +68,9 @@ func NewGenericREST(optsGetter generic.RESTOptionsGetter, serviceCIDR net.IPNet,
}
statusStore := *store
statusStore.UpdateStrategy = service.NewServiceStatusStrategy(strategy)
statusStrategy := service.NewServiceStatusStrategy(strategy)
statusStore.UpdateStrategy = statusStrategy
statusStore.ResetFieldsStrategy = statusStrategy
ipv4 := api.IPv4Protocol
ipv6 := api.IPv6Protocol
@ -125,6 +129,11 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}
// defaultOnRead sets interlinked fields that were not previously set on read.
// We can't do this in the normal defaulting path because that same logic
// applies on Get, Create, and Update, but we need to distinguish between them.
@ -212,7 +221,6 @@ func (r *GenericREST) defaultOnReadService(service *api.Service) {
service.Spec.IPFamilyPolicy = &singleStack
}
}
} else {
// headful
// make sure a slice exists to receive the families

View File

@ -32,10 +32,12 @@ import (
"k8s.io/kubernetes/pkg/apis/core/validation"
"k8s.io/kubernetes/pkg/features"
netutil "k8s.io/utils/net"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
type Strategy interface {
rest.RESTCreateUpdateStrategy
rest.ResetFieldsStrategy
}
// svcStrategy implements behavior for Services
@ -90,6 +92,18 @@ func (svcStrategy) NamespaceScoped() bool {
return true
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (svcStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
return fields
}
// PrepareForCreate sets contextual defaults and clears fields that are not allowed to be set by end users on creation.
func (strategy svcStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
service := obj.(*api.Service)
@ -263,6 +277,18 @@ func NewServiceStatusStrategy(strategy Strategy) Strategy {
return serviceStatusStrategy{strategy}
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (serviceStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
),
}
return fields
}
// PrepareForUpdate clears fields that are not allowed to be set by end users on update of status
func (serviceStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newService := obj.(*api.Service)

View File

@ -29,6 +29,7 @@ import (
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
"k8s.io/kubernetes/pkg/registry/flowcontrol/flowschema"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// FlowSchemaStorage implements storage for flow schema.
@ -49,9 +50,10 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
NewListFunc: func() runtime.Object { return &flowcontrol.FlowSchemaList{} },
DefaultQualifiedResource: flowcontrol.Resource("flowschemas"),
CreateStrategy: flowschema.Strategy,
UpdateStrategy: flowschema.Strategy,
DeleteStrategy: flowschema.Strategy,
CreateStrategy: flowschema.Strategy,
UpdateStrategy: flowschema.Strategy,
DeleteStrategy: flowschema.Strategy,
ResetFieldsStrategy: flowschema.Strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}
@ -64,6 +66,7 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
statusStore.CreateStrategy = nil
statusStore.UpdateStrategy = flowschema.StatusStrategy
statusStore.DeleteStrategy = nil
statusStore.ResetFieldsStrategy = flowschema.StatusStrategy
return &REST{store}, &StatusREST{store: &statusStore}, nil
}
@ -89,3 +92,8 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat
// subresources should never allow create on update.
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}

View File

@ -26,6 +26,7 @@ import (
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/flowcontrol"
"k8s.io/kubernetes/pkg/apis/flowcontrol/validation"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// flowSchemaStrategy implements verification logic for FlowSchema.
@ -42,6 +43,21 @@ func (flowSchemaStrategy) NamespaceScoped() bool {
return false
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (flowSchemaStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"flowcontrol.apiserver.k8s.io/v1alpha1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
"flowcontrol.apiserver.k8s.io/v1beta1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
return fields
}
// PrepareForCreate clears the status of a flow-schema before creation.
func (flowSchemaStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
fl := obj.(*flowcontrol.FlowSchema)
@ -91,6 +107,23 @@ type flowSchemaStatusStrategy struct {
// StatusStrategy is the default logic that applies when updating flow-schema objects' status.
var StatusStrategy = flowSchemaStatusStrategy{Strategy}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (flowSchemaStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"flowcontrol.apiserver.k8s.io/v1alpha1": fieldpath.NewSet(
fieldpath.MakePathOrDie("metadata"),
fieldpath.MakePathOrDie("spec"),
),
"flowcontrol.apiserver.k8s.io/v1beta1": fieldpath.NewSet(
fieldpath.MakePathOrDie("metadata"),
fieldpath.MakePathOrDie("spec"),
),
}
return fields
}
func (flowSchemaStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newFlowSchema := obj.(*flowcontrol.FlowSchema)
oldFlowSchema := old.(*flowcontrol.FlowSchema)

View File

@ -29,6 +29,7 @@ import (
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
"k8s.io/kubernetes/pkg/registry/flowcontrol/prioritylevelconfiguration"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// PriorityLevelConfigurationStorage implements storage for priority level configuration.
@ -49,9 +50,10 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
NewListFunc: func() runtime.Object { return &flowcontrol.PriorityLevelConfigurationList{} },
DefaultQualifiedResource: flowcontrol.Resource("prioritylevelconfigurations"),
CreateStrategy: prioritylevelconfiguration.Strategy,
UpdateStrategy: prioritylevelconfiguration.Strategy,
DeleteStrategy: prioritylevelconfiguration.Strategy,
CreateStrategy: prioritylevelconfiguration.Strategy,
UpdateStrategy: prioritylevelconfiguration.Strategy,
DeleteStrategy: prioritylevelconfiguration.Strategy,
ResetFieldsStrategy: prioritylevelconfiguration.Strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}
@ -64,6 +66,7 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
statusStore.CreateStrategy = nil
statusStore.UpdateStrategy = prioritylevelconfiguration.StatusStrategy
statusStore.DeleteStrategy = nil
statusStore.ResetFieldsStrategy = prioritylevelconfiguration.StatusStrategy
return &REST{store}, &StatusREST{store: &statusStore}, nil
}
@ -89,3 +92,8 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat
// subresources should never allow create on update.
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}

View File

@ -26,6 +26,7 @@ import (
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/flowcontrol"
"k8s.io/kubernetes/pkg/apis/flowcontrol/validation"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// priorityLevelConfigurationStrategy implements verification logic for priority level configurations.
@ -42,6 +43,21 @@ func (priorityLevelConfigurationStrategy) NamespaceScoped() bool {
return false
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (priorityLevelConfigurationStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"flowcontrol.apiserver.k8s.io/v1alpha1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
"flowcontrol.apiserver.k8s.io/v1beta1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
return fields
}
// PrepareForCreate clears the status of a priority-level-configuration before creation.
func (priorityLevelConfigurationStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
pl := obj.(*flowcontrol.PriorityLevelConfiguration)
@ -91,6 +107,23 @@ type priorityLevelConfigurationStatusStrategy struct {
// StatusStrategy is the default logic that applies when updating priority level configuration objects' status.
var StatusStrategy = priorityLevelConfigurationStatusStrategy{Strategy}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (priorityLevelConfigurationStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"flowcontrol.apiserver.k8s.io/v1alpha1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
fieldpath.MakePathOrDie("metadata"),
),
"flowcontrol.apiserver.k8s.io/v1beta1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
fieldpath.MakePathOrDie("metadata"),
),
}
return fields
}
func (priorityLevelConfigurationStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newPriorityLevelConfiguration := obj.(*flowcontrol.PriorityLevelConfiguration)
oldPriorityLevelConfiguration := old.(*flowcontrol.PriorityLevelConfiguration)

View File

@ -29,6 +29,7 @@ import (
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
"k8s.io/kubernetes/pkg/registry/networking/ingress"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// REST implements a RESTStorage for replication controllers
@ -43,9 +44,10 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
NewListFunc: func() runtime.Object { return &networking.IngressList{} },
DefaultQualifiedResource: networking.Resource("ingresses"),
CreateStrategy: ingress.Strategy,
UpdateStrategy: ingress.Strategy,
DeleteStrategy: ingress.Strategy,
CreateStrategy: ingress.Strategy,
UpdateStrategy: ingress.Strategy,
DeleteStrategy: ingress.Strategy,
ResetFieldsStrategy: ingress.Strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}
@ -56,6 +58,7 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
statusStore := *store
statusStore.UpdateStrategy = ingress.StatusStrategy
statusStore.ResetFieldsStrategy = ingress.StatusStrategy
return &REST{store}, &StatusREST{store: &statusStore}, nil
}
@ -88,3 +91,8 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat
// subresources should never allow create on update.
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}

View File

@ -18,6 +18,7 @@ package ingress
import (
"context"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
@ -27,6 +28,7 @@ import (
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/networking"
"k8s.io/kubernetes/pkg/apis/networking/validation"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// ingressStrategy implements verification logic for Replication Ingress.
@ -43,6 +45,24 @@ func (ingressStrategy) NamespaceScoped() bool {
return true
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (ingressStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"extensions/v1beta1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
"networking.k8s.io/v1beta1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
"networking.k8s.io/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
return fields
}
// PrepareForCreate clears the status of an Ingress before creation.
func (ingressStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
ingress := obj.(*networking.Ingress)
@ -108,6 +128,24 @@ type ingressStatusStrategy struct {
// StatusStrategy implements logic used to validate and prepare for updates of the status subresource
var StatusStrategy = ingressStatusStrategy{Strategy}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (ingressStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"extensions/v1beta1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
),
"networking.k8s.io/v1beta1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
),
"networking.k8s.io/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
),
}
return fields
}
// PrepareForUpdate clears fields that are not allowed to be set by end users on update of status
func (ingressStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newIngress := obj.(*networking.Ingress)

View File

@ -29,6 +29,7 @@ import (
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
"k8s.io/kubernetes/pkg/registry/policy/poddisruptionbudget"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// REST implements a RESTStorage for pod disruption budgets against etcd.
@ -43,9 +44,10 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
NewListFunc: func() runtime.Object { return &policyapi.PodDisruptionBudgetList{} },
DefaultQualifiedResource: policyapi.Resource("poddisruptionbudgets"),
CreateStrategy: poddisruptionbudget.Strategy,
UpdateStrategy: poddisruptionbudget.Strategy,
DeleteStrategy: poddisruptionbudget.Strategy,
CreateStrategy: poddisruptionbudget.Strategy,
UpdateStrategy: poddisruptionbudget.Strategy,
DeleteStrategy: poddisruptionbudget.Strategy,
ResetFieldsStrategy: poddisruptionbudget.Strategy,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
}
@ -56,6 +58,7 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
statusStore := *store
statusStore.UpdateStrategy = poddisruptionbudget.StatusStrategy
statusStore.ResetFieldsStrategy = poddisruptionbudget.StatusStrategy
return &REST{store}, &StatusREST{store: &statusStore}, nil
}
@ -85,3 +88,8 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat
// subresources should never allow create on update.
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}

View File

@ -28,6 +28,7 @@ import (
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/policy"
"k8s.io/kubernetes/pkg/apis/policy/validation"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// podDisruptionBudgetStrategy implements verification logic for PodDisruptionBudgets.
@ -44,6 +45,21 @@ func (podDisruptionBudgetStrategy) NamespaceScoped() bool {
return true
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (podDisruptionBudgetStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"policy/v1beta1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
"policy/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
return fields
}
// PrepareForCreate clears the status of an PodDisruptionBudget before creation.
func (podDisruptionBudgetStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
podDisruptionBudget := obj.(*policy.PodDisruptionBudget)
@ -101,6 +117,21 @@ type podDisruptionBudgetStatusStrategy struct {
// StatusStrategy is the default logic invoked when updating object status.
var StatusStrategy = podDisruptionBudgetStatusStrategy{Strategy}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (podDisruptionBudgetStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"policy/v1beta1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
),
"policy/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
),
}
return fields
}
// PrepareForUpdate clears fields that are not allowed to be set by end users on update of status
func (podDisruptionBudgetStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newPodDisruptionBudget := obj.(*policy.PodDisruptionBudget)

View File

@ -29,6 +29,7 @@ import (
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
"k8s.io/kubernetes/pkg/registry/storage/volumeattachment"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// VolumeAttachmentStorage includes storage for VolumeAttachments and all subresources
@ -52,6 +53,7 @@ func NewStorage(optsGetter generic.RESTOptionsGetter) (*VolumeAttachmentStorage,
CreateStrategy: volumeattachment.Strategy,
UpdateStrategy: volumeattachment.Strategy,
DeleteStrategy: volumeattachment.Strategy,
ResetFieldsStrategy: volumeattachment.Strategy,
ReturnDeletedObject: true,
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
@ -63,6 +65,7 @@ func NewStorage(optsGetter generic.RESTOptionsGetter) (*VolumeAttachmentStorage,
statusStore := *store
statusStore.UpdateStrategy = volumeattachment.StatusStrategy
statusStore.ResetFieldsStrategy = volumeattachment.StatusStrategy
return &VolumeAttachmentStorage{
VolumeAttachment: &REST{store},
@ -93,3 +96,8 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat
// subresources should never allow create on update.
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}

View File

@ -31,6 +31,7 @@ import (
"k8s.io/kubernetes/pkg/apis/storage"
"k8s.io/kubernetes/pkg/apis/storage/validation"
"k8s.io/kubernetes/pkg/features"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// volumeAttachmentStrategy implements behavior for VolumeAttachment objects
@ -47,6 +48,18 @@ func (volumeAttachmentStrategy) NamespaceScoped() bool {
return false
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (volumeAttachmentStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"storage.k8s.io/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
return fields
}
// ResetBeforeCreate clears the Status field which is not allowed to be set by end users on creation.
func (volumeAttachmentStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
var groupVersion schema.GroupVersion
@ -143,6 +156,19 @@ type volumeAttachmentStatusStrategy struct {
// VolumeAttachmentStatus subresource via the REST API.
var StatusStrategy = volumeAttachmentStatusStrategy{Strategy}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (volumeAttachmentStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"storage.k8s.io/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("metadata"),
fieldpath.MakePathOrDie("spec"),
),
}
return fields
}
// PrepareForUpdate sets the Status fields which is not allowed to be set by an end user updating a VolumeAttachment
func (volumeAttachmentStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newVolumeAttachment := obj.(*storage.VolumeAttachment)

View File

@ -27,6 +27,7 @@ require (
k8s.io/klog/v2 v2.5.0
k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7
k8s.io/utils v0.0.0-20201110183641-67b214c5f920
sigs.k8s.io/structured-merge-diff/v4 v4.0.3
sigs.k8s.io/yaml v1.2.0
)

View File

@ -27,6 +27,7 @@ import (
"time"
goopenapispec "github.com/go-openapi/spec"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
apiextensionshelpers "k8s.io/apiextensions-apiserver/pkg/apihelpers"
apiextensionsinternal "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
@ -865,14 +866,12 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
MaxRequestBodyBytes: r.maxRequestBodyBytes,
}
if utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
resetFields := storages[v.Name].CustomResource.GetResetFields()
reqScope := *requestScopes[v.Name]
reqScope.FieldManager, err = fieldmanager.NewDefaultCRDFieldManager(
reqScope, err = scopeWithFieldManager(
typeConverter,
reqScope.Convertor,
reqScope.Defaulter,
reqScope.Creater,
reqScope.Kind,
reqScope.HubGroupVersion,
reqScope,
resetFields,
false,
)
if err != nil {
@ -901,20 +900,6 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
// override status subresource values
// shallow copy
statusScope := *requestScopes[v.Name]
if utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
statusScope.FieldManager, err = fieldmanager.NewDefaultCRDFieldManager(
typeConverter,
statusScope.Convertor,
statusScope.Defaulter,
statusScope.Creater,
statusScope.Kind,
statusScope.HubGroupVersion,
true,
)
if err != nil {
return nil, err
}
}
statusScope.Subresource = "status"
statusScope.Namer = handlers.ContextBasedNaming{
SelfLinker: meta.NewAccessor(),
@ -922,6 +907,20 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
SelfLinkPathPrefix: selfLinkPrefix,
SelfLinkPathSuffix: "/status",
}
if utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) && subresources != nil && subresources.Status != nil {
resetFields := storages[v.Name].Status.GetResetFields()
statusScope, err = scopeWithFieldManager(
typeConverter,
statusScope,
resetFields,
true,
)
if err != nil {
return nil, err
}
}
statusScopes[v.Name] = &statusScope
if v.Deprecated {
@ -959,6 +958,24 @@ func (r *crdHandler) getOrCreateServingInfoFor(uid types.UID, name string) (*crd
return ret, nil
}
func scopeWithFieldManager(typeConverter fieldmanager.TypeConverter, reqScope handlers.RequestScope, resetFields map[fieldpath.APIVersion]*fieldpath.Set, ignoreManagedFieldsFromRequestObject bool) (handlers.RequestScope, error) {
fieldManager, err := fieldmanager.NewDefaultCRDFieldManager(
typeConverter,
reqScope.Convertor,
reqScope.Defaulter,
reqScope.Creater,
reqScope.Kind,
reqScope.HubGroupVersion,
ignoreManagedFieldsFromRequestObject,
resetFields,
)
if err != nil {
return handlers.RequestScope{}, err
}
reqScope.FieldManager = fieldManager
return reqScope, nil
}
func defaultDeprecationWarning(deprecatedVersion string, crd apiextensionsv1.CustomResourceDefinitionSpec) string {
msg := fmt.Sprintf("%s/%s %s is deprecated", crd.Group, deprecatedVersion, crd.Names.Kind)

View File

@ -31,6 +31,7 @@ import (
"k8s.io/apiserver/pkg/registry/generic"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/apiserver/pkg/registry/rest"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// CustomResourceStorage includes dummy storage for CustomResources, and their Status and Scale subresources.
@ -92,9 +93,10 @@ func newREST(resource schema.GroupResource, kind, listKind schema.GroupVersionKi
PredicateFunc: strategy.MatchCustomResourceDefinitionStorage,
DefaultQualifiedResource: resource,
CreateStrategy: strategy,
UpdateStrategy: strategy,
DeleteStrategy: strategy,
CreateStrategy: strategy,
UpdateStrategy: strategy,
DeleteStrategy: strategy,
ResetFieldsStrategy: strategy,
TableConvertor: tableConvertor,
}
@ -104,7 +106,9 @@ func newREST(resource schema.GroupResource, kind, listKind schema.GroupVersionKi
}
statusStore := *store
statusStore.UpdateStrategy = NewStatusStrategy(strategy)
statusStrategy := NewStatusStrategy(strategy)
statusStore.UpdateStrategy = statusStrategy
statusStore.ResetFieldsStrategy = statusStrategy
return &REST{store, categories}, &StatusREST{store: &statusStore}
}
@ -199,6 +203,11 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}
type ScaleREST struct {
store *genericregistry.Store
specReplicasPath string

View File

@ -22,6 +22,7 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation/field"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
type statusStrategy struct {
@ -32,6 +33,26 @@ func NewStatusStrategy(strategy customResourceStrategy) statusStrategy {
return statusStrategy{strategy}
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (a statusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
fieldpath.APIVersion(a.customResourceStrategy.kind.GroupVersion().String()): fieldpath.NewSet(
// Note that if there are other top level fields unique to CRDs,
// those will also get removed by the apiserver prior to persisting,
// but wont be added to the resetFields set.
// This isn't an issue now, but if it becomes an issue in the future
// we might need a mechanism that is the inverse of resetFields where
// you specify only the fields to be kept rather than the fields to be wiped
// that way you could wipe everything but the status in this case.
fieldpath.MakePathOrDie("spec"),
),
}
return fields
}
func (a statusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
// update is only allowed to set status
newCustomResourceObject := obj.(*unstructured.Unstructured)

View File

@ -36,6 +36,7 @@ import (
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
structurallisttype "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/listtype"
schemaobjectmeta "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/objectmeta"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// customResourceStrategy implements behavior for CustomResources.
@ -48,6 +49,7 @@ type customResourceStrategy struct {
structuralSchemas map[string]*structuralschema.Structural
status *apiextensions.CustomResourceSubresourceStatus
scale *apiextensions.CustomResourceSubresourceScale
kind schema.GroupVersionKind
}
func NewStrategy(typer runtime.ObjectTyper, namespaceScoped bool, kind schema.GroupVersionKind, schemaValidator, statusSchemaValidator *validate.SchemaValidator, structuralSchemas map[string]*structuralschema.Structural, status *apiextensions.CustomResourceSubresourceStatus, scale *apiextensions.CustomResourceSubresourceScale) customResourceStrategy {
@ -64,6 +66,7 @@ func NewStrategy(typer runtime.ObjectTyper, namespaceScoped bool, kind schema.Gr
statusSchemaValidator: statusSchemaValidator,
},
structuralSchemas: structuralSchemas,
kind: kind,
}
}
@ -71,6 +74,20 @@ func (a customResourceStrategy) NamespaceScoped() bool {
return a.namespaceScoped
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (a customResourceStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{}
if a.status != nil {
fields[fieldpath.APIVersion(a.kind.GroupVersion().String())] = fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
)
}
return fields
}
// PrepareForCreate clears the status of a CustomResource before creation.
func (a customResourceStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
if a.status != nil {

View File

@ -30,6 +30,7 @@ import (
"k8s.io/apiserver/pkg/storage"
storageerr "k8s.io/apiserver/pkg/storage/errors"
"k8s.io/apiserver/pkg/util/dryrun"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// rest implements a RESTStorage for API services against etcd
@ -47,9 +48,10 @@ func NewREST(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) (*RES
PredicateFunc: MatchCustomResourceDefinition,
DefaultQualifiedResource: apiextensions.Resource("customresourcedefinitions"),
CreateStrategy: strategy,
UpdateStrategy: strategy,
DeleteStrategy: strategy,
CreateStrategy: strategy,
UpdateStrategy: strategy,
DeleteStrategy: strategy,
ResetFieldsStrategy: strategy,
// TODO: define table converter that exposes more than name/creation timestamp
TableConvertor: rest.NewDefaultTableConvertor(apiextensions.Resource("customresourcedefinitions")),
@ -177,7 +179,9 @@ func NewStatusREST(scheme *runtime.Scheme, rest *REST) *StatusREST {
statusStore := *rest.Store
statusStore.CreateStrategy = nil
statusStore.DeleteStrategy = nil
statusStore.UpdateStrategy = NewStatusStrategy(scheme)
statusStrategy := NewStatusStrategy(scheme)
statusStore.UpdateStrategy = statusStrategy
statusStore.ResetFieldsStrategy = statusStrategy
return &StatusREST{store: &statusStore}
}
@ -202,3 +206,8 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat
// subresources should never allow create on update.
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}

View File

@ -23,6 +23,7 @@ import (
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation"
apiequality "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
@ -32,6 +33,7 @@ import (
"k8s.io/apiserver/pkg/registry/generic"
"k8s.io/apiserver/pkg/storage"
"k8s.io/apiserver/pkg/storage/names"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// strategy implements behavior for CustomResources.
@ -48,6 +50,21 @@ func (strategy) NamespaceScoped() bool {
return false
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (strategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"apiextensions.k8s.io/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
"apiextensions.k8s.io/v1beta1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
return fields
}
// PrepareForCreate clears the status of a CustomResourceDefinition before creation.
func (strategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
crd := obj.(*apiextensions.CustomResourceDefinition)
@ -140,18 +157,30 @@ func (statusStrategy) NamespaceScoped() bool {
return false
}
// GetResetFields returns the set of fields that get reset by the strategy
// and should not be modified by the user.
func (statusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"apiextensions.k8s.io/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("metadata"),
fieldpath.MakePathOrDie("spec"),
),
"apiextensions.k8s.io/v1beta1": fieldpath.NewSet(
fieldpath.MakePathOrDie("metadata"),
fieldpath.MakePathOrDie("spec"),
),
}
return fields
}
func (statusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newObj := obj.(*apiextensions.CustomResourceDefinition)
oldObj := old.(*apiextensions.CustomResourceDefinition)
newObj.Spec = oldObj.Spec
// Status updates are for only for updating status, not objectmeta.
// TODO: Update after ResetObjectMetaForStatus is added to meta/v1.
newObj.Labels = oldObj.Labels
newObj.Annotations = oldObj.Annotations
newObj.OwnerReferences = oldObj.OwnerReferences
newObj.Generation = oldObj.Generation
newObj.SelfLink = oldObj.SelfLink
metav1.ResetObjectMetaForStatus(&newObj.ObjectMeta, &newObj.ObjectMeta)
}
func (statusStrategy) AllowCreateOnUpdate() bool {

View File

@ -78,8 +78,8 @@ func NewFieldManager(f Manager, ignoreManagedFieldsFromRequestObject bool) *Fiel
// NewDefaultFieldManager creates a new FieldManager that merges apply requests
// and update managed fields for other types of requests.
func NewDefaultFieldManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind, hub schema.GroupVersion, ignoreManagedFieldsFromRequestObject bool) (*FieldManager, error) {
f, err := NewStructuredMergeManager(typeConverter, objectConverter, objectDefaulter, kind.GroupVersion(), hub)
func NewDefaultFieldManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind, hub schema.GroupVersion, ignoreManagedFieldsFromRequestObject bool, resetFields map[fieldpath.APIVersion]*fieldpath.Set) (*FieldManager, error) {
f, err := NewStructuredMergeManager(typeConverter, objectConverter, objectDefaulter, kind.GroupVersion(), hub, resetFields)
if err != nil {
return nil, fmt.Errorf("failed to create field manager: %v", err)
}
@ -89,8 +89,8 @@ func NewDefaultFieldManager(typeConverter TypeConverter, objectConverter runtime
// NewDefaultCRDFieldManager creates a new FieldManager specifically for
// CRDs. This allows for the possibility of fields which are not defined
// in models, as well as having no models defined at all.
func NewDefaultCRDFieldManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind, hub schema.GroupVersion, ignoreManagedFieldsFromRequestObject bool) (_ *FieldManager, err error) {
f, err := NewCRDStructuredMergeManager(typeConverter, objectConverter, objectDefaulter, kind.GroupVersion(), hub)
func NewDefaultCRDFieldManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, objectCreater runtime.ObjectCreater, kind schema.GroupVersionKind, hub schema.GroupVersion, ignoreManagedFieldsFromRequestObject bool, resetFields map[fieldpath.APIVersion]*fieldpath.Set) (_ *FieldManager, err error) {
f, err := NewCRDStructuredMergeManager(typeConverter, objectConverter, objectDefaulter, kind.GroupVersion(), hub, resetFields)
if err != nil {
return nil, fmt.Errorf("failed to create field manager: %v", err)
}

View File

@ -103,6 +103,7 @@ func NewTestFieldManager(gvk schema.GroupVersionKind, ignoreManagedFieldsFromReq
&fakeObjectDefaulter{},
gvk.GroupVersion(),
gvk.GroupVersion(),
nil,
)
if err != nil {
panic(err)

View File

@ -41,7 +41,7 @@ var _ Manager = &structuredMergeManager{}
// NewStructuredMergeManager creates a new Manager that merges apply requests
// and update managed fields for other types of requests.
func NewStructuredMergeManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, gv schema.GroupVersion, hub schema.GroupVersion) (Manager, error) {
func NewStructuredMergeManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, gv schema.GroupVersion, hub schema.GroupVersion, resetFields map[fieldpath.APIVersion]*fieldpath.Set) (Manager, error) {
return &structuredMergeManager{
typeConverter: typeConverter,
objectConverter: objectConverter,
@ -49,7 +49,8 @@ func NewStructuredMergeManager(typeConverter TypeConverter, objectConverter runt
groupVersion: gv,
hubVersion: hub,
updater: merge.Updater{
Converter: newVersionConverter(typeConverter, objectConverter, hub), // This is the converter provided to SMD from k8s
Converter: newVersionConverter(typeConverter, objectConverter, hub), // This is the converter provided to SMD from k8s
IgnoredFields: resetFields,
},
}, nil
}
@ -57,7 +58,7 @@ func NewStructuredMergeManager(typeConverter TypeConverter, objectConverter runt
// NewCRDStructuredMergeManager creates a new Manager specifically for
// CRDs. This allows for the possibility of fields which are not defined
// in models, as well as having no models defined at all.
func NewCRDStructuredMergeManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, gv schema.GroupVersion, hub schema.GroupVersion) (_ Manager, err error) {
func NewCRDStructuredMergeManager(typeConverter TypeConverter, objectConverter runtime.ObjectConvertor, objectDefaulter runtime.ObjectDefaulter, gv schema.GroupVersion, hub schema.GroupVersion, resetFields map[fieldpath.APIVersion]*fieldpath.Set) (_ Manager, err error) {
return &structuredMergeManager{
typeConverter: typeConverter,
objectConverter: objectConverter,
@ -65,7 +66,8 @@ func NewCRDStructuredMergeManager(typeConverter TypeConverter, objectConverter r
groupVersion: gv,
hubVersion: hub,
updater: merge.Updater{
Converter: newCRDVersionConverter(typeConverter, objectConverter, hub),
Converter: newCRDVersionConverter(typeConverter, objectConverter, hub),
IgnoredFields: resetFields,
},
}, nil
}

View File

@ -46,6 +46,7 @@ import (
"k8s.io/apiserver/pkg/storageversion"
utilfeature "k8s.io/apiserver/pkg/util/feature"
versioninfo "k8s.io/component-base/version"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
const (
@ -258,6 +259,13 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
isCreater = true
}
var resetFields map[fieldpath.APIVersion]*fieldpath.Set
if a.group.OpenAPIModels != nil && utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
if resetFieldsStrategy, isResetFieldsStrategy := storage.(rest.ResetFieldsStrategy); isResetFieldsStrategy {
resetFields = resetFieldsStrategy.GetResetFields()
}
}
var versionedList interface{}
if isLister {
list := lister.NewList()
@ -597,6 +605,7 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
fqKindToRegister,
reqScope.HubGroupVersion,
isSubresource,
resetFields,
)
if err != nil {
return nil, nil, fmt.Errorf("failed to create field manager: %v", err)

View File

@ -45,6 +45,7 @@ import (
"k8s.io/apiserver/pkg/storage/etcd3/metrics"
"k8s.io/apiserver/pkg/util/dryrun"
"k8s.io/client-go/tools/cache"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
"k8s.io/klog/v2"
)
@ -201,6 +202,10 @@ type Store struct {
// of items into tabular output. If unset, the default will be used.
TableConvertor rest.TableConvertor
// ResetFieldsStrategy provides the fields reset by the strategy that
// should not be modified by the user.
ResetFieldsStrategy rest.ResetFieldsStrategy
// Storage is the interface for the underlying storage for the
// resource. It is wrapped into a "DryRunnableStorage" that will
// either pass-through or simply dry-run.
@ -1445,6 +1450,14 @@ func (e *Store) StorageVersion() runtime.GroupVersioner {
return e.StorageVersioner
}
// GetResetFields implements rest.ResetFieldsStrategy
func (e *Store) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
if e.ResetFieldsStrategy == nil {
return nil
}
return e.ResetFieldsStrategy.GetResetFields()
}
// validateIndexers will check the prefix of indexers.
func validateIndexers(indexers *cache.Indexers) error {
if indexers == nil {

View File

@ -27,6 +27,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/watch"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
//TODO:
@ -339,3 +340,23 @@ type StorageVersionProvider interface {
// list of kinds the object might belong to.
StorageVersion() runtime.GroupVersioner
}
// ResetFieldsStrategy is an optional interface that a storage object can
// implement if it wishes to provide the fields reset by its strategies.
type ResetFieldsStrategy interface {
GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set
}
// CreateUpdateResetFieldsStrategy is a union of RESTCreateUpdateStrategy
// and ResetFieldsStrategy.
type CreateUpdateResetFieldsStrategy interface {
RESTCreateUpdateStrategy
ResetFieldsStrategy
}
// UpdateResetFieldsStrategy is a union of RESTUpdateStrategy
// and ResetFieldsStrategy.
type UpdateResetFieldsStrategy interface {
RESTUpdateStrategy
ResetFieldsStrategy
}

View File

@ -23,6 +23,7 @@ require (
k8s.io/klog/v2 v2.5.0
k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7
k8s.io/utils v0.0.0-20201110183641-67b214c5f920
sigs.k8s.io/structured-merge-diff/v4 v4.0.3
)
replace (

View File

@ -29,6 +29,7 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/kube-aggregator/pkg/apis/apiregistration"
"k8s.io/kube-aggregator/pkg/registry/apiservice"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
// REST implements a RESTStorage for API services against etcd
@ -45,9 +46,10 @@ func NewREST(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) *REST
PredicateFunc: apiservice.MatchAPIService,
DefaultQualifiedResource: apiregistration.Resource("apiservices"),
CreateStrategy: strategy,
UpdateStrategy: strategy,
DeleteStrategy: strategy,
CreateStrategy: strategy,
UpdateStrategy: strategy,
DeleteStrategy: strategy,
ResetFieldsStrategy: strategy,
// TODO: define table converter that exposes more than name/creation timestamp
TableConvertor: rest.NewDefaultTableConvertor(apiregistration.Resource("apiservices")),
@ -126,10 +128,12 @@ func getCondition(conditions []apiregistration.APIServiceCondition, conditionTyp
// NewStatusREST makes a RESTStorage for status that has more limited options.
// It is based on the original REST so that we can share the same underlying store
func NewStatusREST(scheme *runtime.Scheme, rest *REST) *StatusREST {
strategy := apiservice.NewStatusStrategy(scheme)
statusStore := *rest.Store
statusStore.CreateStrategy = nil
statusStore.DeleteStrategy = nil
statusStore.UpdateStrategy = apiservice.NewStatusStrategy(scheme)
statusStore.UpdateStrategy = strategy
statusStore.ResetFieldsStrategy = strategy
return &StatusREST{store: &statusStore}
}
@ -156,3 +160,8 @@ func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.Updat
// subresources should never allow create on update.
return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options)
}
// GetResetFields implements rest.ResetFieldsStrategy
func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
return r.store.GetResetFields()
}

View File

@ -31,6 +31,7 @@ import (
"k8s.io/kube-aggregator/pkg/apis/apiregistration"
"k8s.io/kube-aggregator/pkg/apis/apiregistration/validation"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
)
type apiServerStrategy struct {
@ -40,9 +41,10 @@ type apiServerStrategy struct {
// apiServerStrategy must implement rest.RESTCreateUpdateStrategy
var _ rest.RESTCreateUpdateStrategy = apiServerStrategy{}
var Strategy = apiServerStrategy{}
// NewStrategy creates a new apiServerStrategy.
func NewStrategy(typer runtime.ObjectTyper) rest.RESTCreateUpdateStrategy {
func NewStrategy(typer runtime.ObjectTyper) rest.CreateUpdateResetFieldsStrategy {
return apiServerStrategy{typer, names.SimpleNameGenerator}
}
@ -50,6 +52,19 @@ func (apiServerStrategy) NamespaceScoped() bool {
return false
}
func (apiServerStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"apiregistration.k8s.io/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
"apiregistration.k8s.io/v1beta1": fieldpath.NewSet(
fieldpath.MakePathOrDie("status"),
),
}
return fields
}
func (apiServerStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
apiservice := obj.(*apiregistration.APIService)
apiservice.Status = apiregistration.APIServiceStatus{}
@ -91,7 +106,7 @@ type apiServerStatusStrategy struct {
}
// NewStatusStrategy creates a new apiServerStatusStrategy.
func NewStatusStrategy(typer runtime.ObjectTyper) rest.RESTUpdateStrategy {
func NewStatusStrategy(typer runtime.ObjectTyper) rest.UpdateResetFieldsStrategy {
return apiServerStatusStrategy{typer, names.SimpleNameGenerator}
}
@ -99,6 +114,21 @@ func (apiServerStatusStrategy) NamespaceScoped() bool {
return false
}
func (apiServerStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
fields := map[fieldpath.APIVersion]*fieldpath.Set{
"apiregistration.k8s.io/v1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
fieldpath.MakePathOrDie("metadata"),
),
"apiregistration.k8s.io/v1beta1": fieldpath.NewSet(
fieldpath.MakePathOrDie("spec"),
fieldpath.MakePathOrDie("metadata"),
),
}
return fields
}
func (apiServerStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
newAPIService := obj.(*apiregistration.APIService)
oldAPIService := old.(*apiregistration.APIService)

View File

@ -0,0 +1,298 @@
/*
Copyright 2020 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 apiserver
import (
"context"
"encoding/json"
"reflect"
"strings"
"testing"
v1 "k8s.io/api/core/v1"
apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
"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/runtime/schema"
"k8s.io/apimachinery/pkg/types"
genericfeatures "k8s.io/apiserver/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
featuregatetesting "k8s.io/component-base/featuregate/testing"
apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
"k8s.io/kubernetes/test/integration/etcd"
"k8s.io/kubernetes/test/integration/framework"
"k8s.io/kubernetes/test/utils/image"
"sigs.k8s.io/yaml"
)
// namespace used for all tests, do not change this
const resetFieldsNamespace = "reset-fields-namespace"
// resetFieldsStatusData contains statuses for all the resources in the
// statusData list with slightly different data to create a field manager
// conflict.
var resetFieldsStatusData = map[schema.GroupVersionResource]string{
gvr("", "v1", "persistentvolumes"): `{"status": {"message": "hello2"}}`,
gvr("", "v1", "resourcequotas"): `{"status": {"used": {"cpu": "25M"}}}`,
gvr("", "v1", "services"): `{"status": {"loadBalancer": {"ingress": [{"ip": "127.0.0.2"}]}}}`,
gvr("extensions", "v1beta1", "ingresses"): `{"status": {"loadBalancer": {"ingress": [{"ip": "127.0.0.2"}]}}}`,
gvr("networking.k8s.io", "v1beta1", "ingresses"): `{"status": {"loadBalancer": {"ingress": [{"ip": "127.0.0.2"}]}}}`,
gvr("networking.k8s.io", "v1", "ingresses"): `{"status": {"loadBalancer": {"ingress": [{"ip": "127.0.0.2"}]}}}`,
gvr("autoscaling", "v1", "horizontalpodautoscalers"): `{"status": {"currentReplicas": 25}}`,
gvr("batch", "v1", "cronjobs"): `{"status": {"lastScheduleTime": "2020-01-01T00:00:00Z"}}`,
gvr("batch", "v1beta1", "cronjobs"): `{"status": {"lastScheduleTime": "2020-01-01T00:00:00Z"}}`,
gvr("storage.k8s.io", "v1", "volumeattachments"): `{"status": {"attached": false}}`,
gvr("policy", "v1", "poddisruptionbudgets"): `{"status": {"currentHealthy": 25}}`,
gvr("policy", "v1beta1", "poddisruptionbudgets"): `{"status": {"currentHealthy": 25}}`,
gvr("internal.apiserver.k8s.io", "v1alpha1", "storageversions"): `{"status": {"commonEncodingVersion":"v1","storageVersions":[{"apiServerID":"1","decodableVersions":["v1","v2"],"encodingVersion":"v1"}],"conditions":[{"type":"AllEncodingVersionsEqual","status":"False","lastTransitionTime":"2020-01-01T00:00:00Z","reason":"allEncodingVersionsEqual","message":"all encoding versions are set to v1"}]}}`,
}
// resetFieldsStatusDefault conflicts with statusDefault
const resetFieldsStatusDefault = `{"status": {"conditions": [{"type": "MyStatus", "status":"False"}]}}`
var resetFieldsSkippedResources = map[string]struct{}{
// TODO: flowschemas is flaking,
// possible bug in the flowschemas controller.
"flowschemas": {},
}
// noConflicts is the set of reources for which
// a conflict cannot occur.
var noConflicts = map[string]struct{}{
// both spec and status get wiped for CSRs,
// nothing is expected to be managed for it, skip it
"certificatesigningrequests": {},
// storageVersions are skipped because their spec is empty
// and thus they can never have a conflict.
"storageversions": {},
// namespaces only have a spec.finalizers field which is also skipped,
// thus it will never have a conflict.
"namespaces": {},
}
var image2 = image.GetE2EImage(image.Etcd)
// resetFieldsSpecData contains conflicting data with the objects in
// etcd.GetEtcdStorageDataForNamespace()
// It contains the minimal changes needed to conflict with all the fields
// added to resetFields by the strategy of each resource.
// In most cases, just one field on the spec is changed, but
// some also wipe metadata or other fields.
var resetFieldsSpecData = map[schema.GroupVersionResource]string{
gvr("", "v1", "resourcequotas"): `{"spec": {"hard": {"cpu": "25M"}}}`,
gvr("", "v1", "namespaces"): `{"spec": {"finalizers": ["kubernetes2"]}}`,
gvr("", "v1", "nodes"): `{"spec": {"unschedulable": false}}`,
gvr("", "v1", "persistentvolumes"): `{"spec": {"capacity": {"storage": "23M"}}}`,
gvr("", "v1", "persistentvolumeclaims"): `{"spec": {"resources": {"limits": {"storage": "21M"}}}}`,
gvr("", "v1", "pods"): `{"metadata": {"deletionTimestamp": "2020-01-01T00:00:00Z", "ownerReferences":[]}, "spec": {"containers": [{"image": "` + image2 + `", "name": "container7"}]}}`,
gvr("", "v1", "replicationcontrollers"): `{"spec": {"selector": {"new": "stuff2"}}}`,
gvr("", "v1", "resourcequotas"): `{"spec": {"hard": {"cpu": "25M"}}}`,
gvr("", "v1", "services"): `{"spec": {"externalName": "service2name"}}`,
gvr("apps", "v1", "daemonsets"): `{"spec": {"template": {"spec": {"containers": [{"image": "` + image2 + `", "name": "container6"}]}}}}`,
gvr("apps", "v1", "deployments"): `{"metadata": {"labels": {"a":"c"}}, "spec": {"template": {"spec": {"containers": [{"image": "` + image2 + `", "name": "container6"}]}}}}`,
gvr("apps", "v1", "replicasets"): `{"spec": {"template": {"spec": {"containers": [{"image": "` + image2 + `", "name": "container4"}]}}}}`,
gvr("apps", "v1", "statefulsets"): `{"spec": {"selector": {"matchLabels": {"a2": "b2"}}}}`,
gvr("autoscaling", "v1", "horizontalpodautoscalers"): `{"spec": {"maxReplicas": 23}}`,
gvr("autoscaling", "v2beta1", "horizontalpodautoscalers"): `{"spec": {"maxReplicas": 23}}`,
gvr("autoscaling", "v2beta2", "horizontalpodautoscalers"): `{"spec": {"maxReplicas": 23}}`,
gvr("batch", "v1", "jobs"): `{"spec": {"template": {"spec": {"containers": [{"image": "` + image2 + `", "name": "container1"}]}}}}`,
gvr("batch", "v1", "cronjobs"): `{"spec": {"jobTemplate": {"spec": {"template": {"spec": {"containers": [{"image": "` + image2 + `", "name": "container0"}]}}}}}}`,
gvr("batch", "v1beta1", "cronjobs"): `{"spec": {"jobTemplate": {"spec": {"template": {"spec": {"containers": [{"image": "` + image2 + `", "name": "container0"}]}}}}}}`,
gvr("certificates.k8s.io", "v1", "certificatesigningrequests"): `{}`,
gvr("certificates.k8s.io", "v1beta1", "certificatesigningrequests"): `{}`,
gvr("flowcontrol.apiserver.k8s.io", "v1alpha1", "flowschemas"): `{"metadata": {"labels":{"a":"c"}}, "spec": {"priorityLevelConfiguration": {"name": "name2"}}}`,
gvr("flowcontrol.apiserver.k8s.io", "v1beta1", "flowschemas"): `{"metadata": {"labels":{"a":"c"}}, "spec": {"priorityLevelConfiguration": {"name": "name2"}}}`,
gvr("flowcontrol.apiserver.k8s.io", "v1alpha1", "prioritylevelconfigurations"): `{"metadata": {"labels":{"a":"c"}}, "spec": {"limited": {"assuredConcurrencyShares": 23}}}`,
gvr("flowcontrol.apiserver.k8s.io", "v1beta1", "prioritylevelconfigurations"): `{"metadata": {"labels":{"a":"c"}}, "spec": {"limited": {"assuredConcurrencyShares": 23}}}`,
gvr("extensions", "v1beta1", "ingresses"): `{"spec": {"backend": {"serviceName": "service2"}}}`,
gvr("networking.k8s.io", "v1beta1", "ingresses"): `{"spec": {"backend": {"serviceName": "service2"}}}`,
gvr("networking.k8s.io", "v1", "ingresses"): `{"spec": {"defaultBackend": {"service": {"name": "service2"}}}}`,
gvr("policy", "v1", "poddisruptionbudgets"): `{"spec": {"selector": {"matchLabels": {"anokkey2": "anokvalue"}}}}`,
gvr("policy", "v1beta1", "poddisruptionbudgets"): `{"spec": {"selector": {"matchLabels": {"anokkey2": "anokvalue"}}}}`,
gvr("storage.k8s.io", "v1alpha1", "volumeattachments"): `{"metadata": {"name": "vaName2"}, "spec": {"nodeName": "localhost2"}}`,
gvr("storage.k8s.io", "v1", "volumeattachments"): `{"metadata": {"name": "vaName2"}, "spec": {"nodeName": "localhost2"}}`,
gvr("apiextensions.k8s.io", "v1", "customresourcedefinitions"): `{"metadata": {"labels":{"a":"c"}}, "spec": {"group": "webconsole22.operator.openshift.io"}}`,
gvr("apiextensions.k8s.io", "v1beta1", "customresourcedefinitions"): `{"metadata": {"labels":{"a":"c"}}, "spec": {"group": "webconsole22.operator.openshift.io"}}`,
gvr("awesome.bears.com", "v1", "pandas"): `{"spec": {"replicas": 102}}`,
gvr("awesome.bears.com", "v3", "pandas"): `{"spec": {"replicas": 302}}`,
gvr("apiregistration.k8s.io", "v1beta1", "apiservices"): `{"metadata": {"labels": {"a":"c"}}, "spec": {"group": "foo2.com"}}`,
gvr("apiregistration.k8s.io", "v1", "apiservices"): `{"metadata": {"labels": {"a":"c"}}, "spec": {"group": "foo2.com"}}`,
gvr("internal.apiserver.k8s.io", "v1alpha1", "storageversions"): `{}`,
}
// TestResetFields makes sure that fieldManager does not own fields reset by the storage strategy.
// It takes 2 objects obj1 and obj2 that differ by one field in the spec and one field in the status.
// It applies obj1 to the spec endpoint and obj2 to the status endpoint, the lack of conflicts
// confirms that the fieldmanager1 is wiped of the status and fieldmanager2 is wiped of the spec.
// We then attempt to apply obj2 to the spec endpoint which fails with an expected conflict.
func TestApplyResetFields(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
server, err := apiservertesting.StartTestServer(t, apiservertesting.NewDefaultTestServerOptions(), []string{"--disable-admission-plugins", "ServiceAccount,TaintNodesByCondition"}, framework.SharedEtcd())
if err != nil {
t.Fatal(err)
}
defer server.TearDownFn()
client, err := kubernetes.NewForConfig(server.ClientConfig)
if err != nil {
t.Fatal(err)
}
dynamicClient, err := dynamic.NewForConfig(server.ClientConfig)
if err != nil {
t.Fatal(err)
}
// create CRDs so we can make sure that custom resources do not get lost
etcd.CreateTestCRDs(t, apiextensionsclientset.NewForConfigOrDie(server.ClientConfig), false, etcd.GetCustomResourceDefinitionData()...)
if _, err := client.CoreV1().Namespaces().Create(context.TODO(), &v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: resetFieldsNamespace}}, metav1.CreateOptions{}); err != nil {
t.Fatal(err)
}
createData := etcd.GetEtcdStorageDataForNamespace(resetFieldsNamespace)
// gather resources to test
_, resourceLists, err := client.Discovery().ServerGroupsAndResources()
if err != nil {
t.Fatalf("Failed to get ServerGroupsAndResources with error: %+v", err)
}
for _, resourceList := range resourceLists {
for _, resource := range resourceList.APIResources {
if !strings.HasSuffix(resource.Name, "/status") {
continue
}
mapping, err := createMapping(resourceList.GroupVersion, resource)
if err != nil {
t.Fatal(err)
}
t.Run(mapping.Resource.String(), func(t *testing.T) {
if _, ok := resetFieldsSkippedResources[mapping.Resource.Resource]; ok {
t.Skip()
}
namespace := resetFieldsNamespace
if mapping.Scope == meta.RESTScopeRoot {
namespace = ""
}
// assemble first object
status, ok := statusData[mapping.Resource]
if !ok {
status = statusDefault
}
resource, ok := createData[mapping.Resource]
if !ok {
t.Fatalf("no test data for %s. Please add a test for your new type to etcd.GetEtcdStorageData() or getResetFieldsEtcdStorageData()", mapping.Resource)
}
obj1 := unstructured.Unstructured{}
if err := json.Unmarshal([]byte(resource.Stub), &obj1.Object); err != nil {
t.Fatal(err)
}
if err := json.Unmarshal([]byte(status), &obj1.Object); err != nil {
t.Fatal(err)
}
name := obj1.GetName()
obj1.SetAPIVersion(mapping.GroupVersionKind.GroupVersion().String())
obj1.SetKind(mapping.GroupVersionKind.Kind)
obj1.SetName(name)
obj1YAML, err := yaml.Marshal(obj1.Object)
if err != nil {
t.Fatal(err)
}
// apply the spec of the first object
_, err = dynamicClient.
Resource(mapping.Resource).
Namespace(namespace).
Patch(context.TODO(), name, types.ApplyPatchType, obj1YAML, metav1.PatchOptions{FieldManager: "fieldmanager1"}, "")
if err != nil {
t.Fatalf("Failed to apply obj1: %v", err)
}
// create second object
obj2 := &unstructured.Unstructured{}
obj1.DeepCopyInto(obj2)
if err := json.Unmarshal([]byte(resetFieldsSpecData[mapping.Resource]), &obj2.Object); err != nil {
t.Fatal(err)
}
status2, ok := resetFieldsStatusData[mapping.Resource]
if !ok {
status2 = resetFieldsStatusDefault
}
if err := json.Unmarshal([]byte(status2), &obj2.Object); err != nil {
t.Fatal(err)
}
if reflect.DeepEqual(obj1, obj2) {
t.Fatalf("obj1 and obj2 should not be equal %v", obj2)
}
obj2YAML, err := yaml.Marshal(obj2.Object)
if err != nil {
t.Fatal(err)
}
// apply the status of the second object
// this won't conflict if resetfields are set correctly
// and will conflict if they are not
_, err = dynamicClient.
Resource(mapping.Resource).
Namespace(namespace).
Patch(context.TODO(), name, types.ApplyPatchType, obj2YAML, metav1.PatchOptions{FieldManager: "fieldmanager2"}, "status")
if err != nil {
t.Fatalf("Failed to apply obj2: %v", err)
}
// skip checking for conflicts on resources
// that will never have conflicts
if _, ok = noConflicts[mapping.Resource.Resource]; !ok {
// reapply second object to the spec endpoint
// that should fail with a conflict
_, err = dynamicClient.
Resource(mapping.Resource).
Namespace(namespace).
Patch(context.TODO(), name, types.ApplyPatchType, obj2YAML, metav1.PatchOptions{FieldManager: "fieldmanager2"}, "")
if err == nil || !strings.Contains(err.Error(), "conflict") {
t.Fatalf("expected conflict, got error %v", err)
}
// reapply first object to the status endpoint
// that should fail with a conflict
_, err = dynamicClient.
Resource(mapping.Resource).
Namespace(namespace).
Patch(context.TODO(), name, types.ApplyPatchType, obj1YAML, metav1.PatchOptions{FieldManager: "fieldmanager1"}, "status")
if err == nil || !strings.Contains(err.Error(), "conflict") {
t.Fatalf("expected conflict, got error %v", err)
}
}
// cleanup
rsc := dynamicClient.Resource(mapping.Resource).Namespace(namespace)
if err := rsc.Delete(context.TODO(), name, *metav1.NewDeleteOptions(0)); err != nil {
t.Fatalf("deleting final object failed: %v", err)
}
})
}
}
}

View File

@ -44,21 +44,19 @@ import (
const testNamespace = "statusnamespace"
var statusData = map[schema.GroupVersionResource]string{
gvr("", "v1", "persistentvolumes"): `{"status": {"message": "hello"}}`,
gvr("", "v1", "resourcequotas"): `{"status": {"used": {"cpu": "5M"}}}`,
gvr("", "v1", "services"): `{"status": {"loadBalancer": {"ingress": [{"ip": "127.0.0.1"}]}}}`,
gvr("extensions", "v1beta1", "ingresses"): `{"status": {"loadBalancer": {"ingress": [{"ip": "127.0.0.1"}]}}}`,
gvr("networking.k8s.io", "v1beta1", "ingresses"): `{"status": {"loadBalancer": {"ingress": [{"ip": "127.0.0.1"}]}}}`,
gvr("networking.k8s.io", "v1", "ingresses"): `{"status": {"loadBalancer": {"ingress": [{"ip": "127.0.0.1"}]}}}`,
gvr("autoscaling", "v1", "horizontalpodautoscalers"): `{"status": {"currentReplicas": 5}}`,
gvr("batch", "v1", "cronjobs"): `{"status": {"lastScheduleTime": null}}`,
gvr("batch", "v1beta1", "cronjobs"): `{"status": {"lastScheduleTime": null}}`,
gvr("storage.k8s.io", "v1", "volumeattachments"): `{"status": {"attached": true}}`,
gvr("policy", "v1", "poddisruptionbudgets"): `{"status": {"currentHealthy": 5}}`,
gvr("policy", "v1beta1", "poddisruptionbudgets"): `{"status": {"currentHealthy": 5}}`,
gvr("certificates.k8s.io", "v1beta1", "certificatesigningrequests"): `{"status": {"conditions": [{"type": "MyStatus"}]}}`,
gvr("certificates.k8s.io", "v1", "certificatesigningrequests"): `{"status": {"conditions": [{"type": "MyStatus", "status": "True"}]}}`,
gvr("internal.apiserver.k8s.io", "v1alpha1", "storageversions"): `{"status": {"commonEncodingVersion":"v1","storageVersions":[{"apiServerID":"1","decodableVersions":["v1","v2"],"encodingVersion":"v1"}],"conditions":[{"type":"AllEncodingVersionsEqual","status":"True","lastTransitionTime":"2020-01-01T00:00:00Z","reason":"allEncodingVersionsEqual","message":"all encoding versions are set to v1"}]}}`,
gvr("", "v1", "persistentvolumes"): `{"status": {"message": "hello"}}`,
gvr("", "v1", "resourcequotas"): `{"status": {"used": {"cpu": "5M"}}}`,
gvr("", "v1", "services"): `{"status": {"loadBalancer": {"ingress": [{"ip": "127.0.0.1"}]}}}`,
gvr("extensions", "v1beta1", "ingresses"): `{"status": {"loadBalancer": {"ingress": [{"ip": "127.0.0.1"}]}}}`,
gvr("networking.k8s.io", "v1beta1", "ingresses"): `{"status": {"loadBalancer": {"ingress": [{"ip": "127.0.0.1"}]}}}`,
gvr("networking.k8s.io", "v1", "ingresses"): `{"status": {"loadBalancer": {"ingress": [{"ip": "127.0.0.1"}]}}}`,
gvr("autoscaling", "v1", "horizontalpodautoscalers"): `{"status": {"currentReplicas": 5}}`,
gvr("batch", "v1", "cronjobs"): `{"status": {"lastScheduleTime": null}}`,
gvr("batch", "v1beta1", "cronjobs"): `{"status": {"lastScheduleTime": null}}`,
gvr("storage.k8s.io", "v1", "volumeattachments"): `{"status": {"attached": true}}`,
gvr("policy", "v1", "poddisruptionbudgets"): `{"status": {"currentHealthy": 5}}`,
gvr("policy", "v1beta1", "poddisruptionbudgets"): `{"status": {"currentHealthy": 5}}`,
gvr("internal.apiserver.k8s.io", "v1alpha1", "storageversions"): `{"status": {"commonEncodingVersion":"v1","storageVersions":[{"apiServerID":"1","decodableVersions":["v1","v2"],"encodingVersion":"v1"}],"conditions":[{"type":"AllEncodingVersionsEqual","status":"True","lastTransitionTime":"2020-01-01T00:00:00Z","reason":"allEncodingVersionsEqual","message":"all encoding versions are set to v1"}]}}`,
}
const statusDefault = `{"status": {"conditions": [{"type": "MyStatus", "status":"True"}]}}`
@ -133,6 +131,12 @@ func TestApplyStatus(t *testing.T) {
t.Fatal(err)
}
t.Run(mapping.Resource.String(), func(t *testing.T) {
// both spec and status get wiped for CSRs,
// nothing is expected to be managed for it, skip it
if mapping.Resource.Resource == "certificatesigningrequests" {
t.Skip()
}
status, ok := statusData[mapping.Resource]
if !ok {
status = statusDefault

1
vendor/modules.txt vendored
View File

@ -2710,6 +2710,7 @@ sigs.k8s.io/kustomize/kyaml/yaml/merge3
sigs.k8s.io/kustomize/kyaml/yaml/schema
sigs.k8s.io/kustomize/kyaml/yaml/walk
# sigs.k8s.io/structured-merge-diff/v4 v4.0.3 => sigs.k8s.io/structured-merge-diff/v4 v4.0.3
## explicit
# sigs.k8s.io/structured-merge-diff/v4 => sigs.k8s.io/structured-merge-diff/v4 v4.0.3
sigs.k8s.io/structured-merge-diff/v4/fieldpath
sigs.k8s.io/structured-merge-diff/v4/merge