diff --git a/api/api-rules/violation_exceptions.list b/api/api-rules/violation_exceptions.list index 398e9f1a6852..4e58149d9e7b 100644 --- a/api/api-rules/violation_exceptions.list +++ b/api/api-rules/violation_exceptions.list @@ -270,7 +270,6 @@ API rule violation: list_type_missing,k8s.io/apiextensions-apiserver/pkg/apis/ap API rule violation: list_type_missing,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,CustomResourceDefinitionNames,Categories API rule violation: list_type_missing,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,CustomResourceDefinitionNames,ShortNames API rule violation: list_type_missing,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,CustomResourceDefinitionSpec,Versions -API rule violation: list_type_missing,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,CustomResourceDefinitionStatus,Conditions API rule violation: list_type_missing,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,CustomResourceDefinitionStatus,StoredVersions API rule violation: list_type_missing,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,CustomResourceDefinitionVersion,AdditionalPrinterColumns API rule violation: list_type_missing,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSON,Raw @@ -291,7 +290,6 @@ API rule violation: list_type_missing,k8s.io/apiextensions-apiserver/pkg/apis/ap API rule violation: list_type_missing,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1,CustomResourceDefinitionNames,ShortNames API rule violation: list_type_missing,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1,CustomResourceDefinitionSpec,AdditionalPrinterColumns API rule violation: list_type_missing,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1,CustomResourceDefinitionSpec,Versions -API rule violation: list_type_missing,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1,CustomResourceDefinitionStatus,Conditions API rule violation: list_type_missing,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1,CustomResourceDefinitionStatus,StoredVersions API rule violation: list_type_missing,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1,CustomResourceDefinitionVersion,AdditionalPrinterColumns API rule violation: list_type_missing,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1,JSON,Raw diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index 7ee29b5152a7..00cf74d8db06 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -17260,7 +17260,11 @@ "items": { "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.CustomResourceDefinitionCondition" }, - "type": "array" + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" }, "storedVersions": { "description": "storedVersions lists all versions of CustomResources that were ever persisted. Tracking these versions allows a migration path for stored versions in etcd. The field is mutable so a migration controller can finish a migration to another version (ensuring no old objects are left in storage), and then remove the rest of the versions from this list. Versions may not be removed from `spec.versions` while they exist in this list.", @@ -17908,7 +17912,11 @@ "items": { "$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.CustomResourceDefinitionCondition" }, - "type": "array" + "type": "array", + "x-kubernetes-list-map-keys": [ + "type" + ], + "x-kubernetes-list-type": "map" }, "storedVersions": { "description": "storedVersions lists all versions of CustomResources that were ever persisted. Tracking these versions allows a migration path for stored versions in etcd. The field is mutable so a migration controller can finish a migration to another version (ensuring no old objects are left in storage), and then remove the rest of the versions from this list. Versions may not be removed from `spec.versions` while they exist in this list.", diff --git a/go.mod b/go.mod index b787c4c3a4bd..2f0c974e73ef 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/hack/make-rules/test-cmd.sh b/hack/make-rules/test-cmd.sh index 62059b6a1cba..52b7a3f48939 100755 --- a/hack/make-rules/test-cmd.sh +++ b/hack/make-rules/test-cmd.sh @@ -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": { diff --git a/pkg/registry/apiserverinternal/storageversion/storage/storage.go b/pkg/registry/apiserverinternal/storageversion/storage/storage.go index a6157b7b5a3b..3a0901391e9b 100644 --- a/pkg/registry/apiserverinternal/storageversion/storage/storage.go +++ b/pkg/registry/apiserverinternal/storageversion/storage/storage.go @@ -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() +} diff --git a/pkg/registry/apiserverinternal/storageversion/strategy.go b/pkg/registry/apiserverinternal/storageversion/strategy.go index 5a1f2982760f..535d31d142c5 100644 --- a/pkg/registry/apiserverinternal/storageversion/strategy.go +++ b/pkg/registry/apiserverinternal/storageversion/strategy.go @@ -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) diff --git a/pkg/registry/apps/daemonset/storage/storage.go b/pkg/registry/apps/daemonset/storage/storage.go index e824f2001430..ec337f19a772 100644 --- a/pkg/registry/apps/daemonset/storage/storage.go +++ b/pkg/registry/apps/daemonset/storage/storage.go @@ -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() +} diff --git a/pkg/registry/apps/daemonset/strategy.go b/pkg/registry/apps/daemonset/strategy.go index af934471baed..cd60ad024953 100644 --- a/pkg/registry/apps/daemonset/strategy.go +++ b/pkg/registry/apps/daemonset/strategy.go @@ -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) diff --git a/pkg/registry/apps/deployment/storage/storage.go b/pkg/registry/apps/deployment/storage/storage.go index 8bbe4690ceae..8b2b48c6a54f 100644 --- a/pkg/registry/apps/deployment/storage/storage.go +++ b/pkg/registry/apps/deployment/storage/storage.go @@ -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 diff --git a/pkg/registry/apps/deployment/strategy.go b/pkg/registry/apps/deployment/strategy.go index e3aab529a25f..15a115e09ab3 100644 --- a/pkg/registry/apps/deployment/strategy.go +++ b/pkg/registry/apps/deployment/strategy.go @@ -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) diff --git a/pkg/registry/apps/replicaset/storage/storage.go b/pkg/registry/apps/replicaset/storage/storage.go index 7a751ef0964a..be129c7ea82c 100644 --- a/pkg/registry/apps/replicaset/storage/storage.go +++ b/pkg/registry/apps/replicaset/storage/storage.go @@ -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 diff --git a/pkg/registry/apps/replicaset/strategy.go b/pkg/registry/apps/replicaset/strategy.go index e1328fe46cb8..0f54eca3992e 100644 --- a/pkg/registry/apps/replicaset/strategy.go +++ b/pkg/registry/apps/replicaset/strategy.go @@ -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) diff --git a/pkg/registry/apps/statefulset/storage/storage.go b/pkg/registry/apps/statefulset/storage/storage.go index 34e31723f128..0afa6b69098d 100644 --- a/pkg/registry/apps/statefulset/storage/storage.go +++ b/pkg/registry/apps/statefulset/storage/storage.go @@ -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{} diff --git a/pkg/registry/apps/statefulset/strategy.go b/pkg/registry/apps/statefulset/strategy.go index a1f27417fb3b..b718746415aa 100644 --- a/pkg/registry/apps/statefulset/strategy.go +++ b/pkg/registry/apps/statefulset/strategy.go @@ -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) diff --git a/pkg/registry/autoscaling/horizontalpodautoscaler/storage/storage.go b/pkg/registry/autoscaling/horizontalpodautoscaler/storage/storage.go index 2a8f90015a68..8e17de502127 100644 --- a/pkg/registry/autoscaling/horizontalpodautoscaler/storage/storage.go +++ b/pkg/registry/autoscaling/horizontalpodautoscaler/storage/storage.go @@ -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() +} diff --git a/pkg/registry/autoscaling/horizontalpodautoscaler/strategy.go b/pkg/registry/autoscaling/horizontalpodautoscaler/strategy.go index 75b8e4eccdd7..edddda37dcba 100644 --- a/pkg/registry/autoscaling/horizontalpodautoscaler/strategy.go +++ b/pkg/registry/autoscaling/horizontalpodautoscaler/strategy.go @@ -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) diff --git a/pkg/registry/batch/cronjob/storage/storage.go b/pkg/registry/batch/cronjob/storage/storage.go index 7fba3f5d08bb..06724df16721 100644 --- a/pkg/registry/batch/cronjob/storage/storage.go +++ b/pkg/registry/batch/cronjob/storage/storage.go @@ -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() +} diff --git a/pkg/registry/batch/cronjob/strategy.go b/pkg/registry/batch/cronjob/strategy.go index 6d66fda84783..46e7d5b6797f 100644 --- a/pkg/registry/batch/cronjob/strategy.go +++ b/pkg/registry/batch/cronjob/strategy.go @@ -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) diff --git a/pkg/registry/batch/job/storage/storage.go b/pkg/registry/batch/job/storage/storage.go index b7390780717e..66ce3f58126e 100644 --- a/pkg/registry/batch/job/storage/storage.go +++ b/pkg/registry/batch/job/storage/storage.go @@ -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() +} diff --git a/pkg/registry/batch/job/strategy.go b/pkg/registry/batch/job/strategy.go index 7466f7bfc917..212b0459b677 100644 --- a/pkg/registry/batch/job/strategy.go +++ b/pkg/registry/batch/job/strategy.go @@ -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) diff --git a/pkg/registry/certificates/certificates/storage/storage.go b/pkg/registry/certificates/certificates/storage/storage.go index 6e24dfd02fc1..dd4bd8c88aeb 100644 --- a/pkg/registry/certificates/certificates/storage/storage.go +++ b/pkg/registry/certificates/certificates/storage/storage.go @@ -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{}) diff --git a/pkg/registry/certificates/certificates/strategy.go b/pkg/registry/certificates/certificates/strategy.go index 67bd853ceae9..c88ff0fdce9a 100644 --- a/pkg/registry/certificates/certificates/strategy.go +++ b/pkg/registry/certificates/certificates/strategy.go @@ -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) { diff --git a/pkg/registry/core/namespace/storage/storage.go b/pkg/registry/core/namespace/storage/storage.go index 1d66a23203eb..375445e3a0f1 100644 --- a/pkg/registry/core/namespace/storage/storage.go +++ b/pkg/registry/core/namespace/storage/storage.go @@ -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() +} diff --git a/pkg/registry/core/namespace/strategy.go b/pkg/registry/core/namespace/strategy.go index 1bf63d95178b..1dc84284f7f3 100644 --- a/pkg/registry/core/namespace/strategy.go +++ b/pkg/registry/core/namespace/strategy.go @@ -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) diff --git a/pkg/registry/core/node/storage/storage.go b/pkg/registry/core/node/storage/storage.go index 8357a450ba46..695596cde8f9 100644 --- a/pkg/registry/core/node/storage/storage.go +++ b/pkg/registry/core/node/storage/storage.go @@ -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} diff --git a/pkg/registry/core/node/strategy.go b/pkg/registry/core/node/strategy.go index e415b176bf52..2fe255155bcb 100644 --- a/pkg/registry/core/node/strategy.go +++ b/pkg/registry/core/node/strategy.go @@ -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) diff --git a/pkg/registry/core/persistentvolume/storage/storage.go b/pkg/registry/core/persistentvolume/storage/storage.go index 6b4cec9819a8..f4f303bac99b 100644 --- a/pkg/registry/core/persistentvolume/storage/storage.go +++ b/pkg/registry/core/persistentvolume/storage/storage.go @@ -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() +} diff --git a/pkg/registry/core/persistentvolume/strategy.go b/pkg/registry/core/persistentvolume/strategy.go index 5ab7d1edd4ac..2cabf3946214 100644 --- a/pkg/registry/core/persistentvolume/strategy.go +++ b/pkg/registry/core/persistentvolume/strategy.go @@ -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) diff --git a/pkg/registry/core/persistentvolumeclaim/storage/storage.go b/pkg/registry/core/persistentvolumeclaim/storage/storage.go index 560b7264371e..409e0695365e 100644 --- a/pkg/registry/core/persistentvolumeclaim/storage/storage.go +++ b/pkg/registry/core/persistentvolumeclaim/storage/storage.go @@ -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() +} diff --git a/pkg/registry/core/persistentvolumeclaim/strategy.go b/pkg/registry/core/persistentvolumeclaim/strategy.go index 14e0d26a1636..23b7acb3c9d4 100644 --- a/pkg/registry/core/persistentvolumeclaim/strategy.go +++ b/pkg/registry/core/persistentvolumeclaim/strategy.go @@ -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) diff --git a/pkg/registry/core/pod/storage/storage.go b/pkg/registry/core/pod/storage/storage.go index abdde48e2b9f..786560a8110e 100644 --- a/pkg/registry/core/pod/storage/storage.go +++ b/pkg/registry/core/pod/storage/storage.go @@ -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 diff --git a/pkg/registry/core/pod/strategy.go b/pkg/registry/core/pod/strategy.go index 836a42405555..15aeb7b5d388 100644 --- a/pkg/registry/core/pod/strategy.go +++ b/pkg/registry/core/pod/strategy.go @@ -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) diff --git a/pkg/registry/core/replicationcontroller/storage/storage.go b/pkg/registry/core/replicationcontroller/storage/storage.go index 1eac4333c3d7..8ae12247f185 100644 --- a/pkg/registry/core/replicationcontroller/storage/storage.go +++ b/pkg/registry/core/replicationcontroller/storage/storage.go @@ -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 } diff --git a/pkg/registry/core/replicationcontroller/strategy.go b/pkg/registry/core/replicationcontroller/strategy.go index 3ae193d6a71f..3722365cd435 100644 --- a/pkg/registry/core/replicationcontroller/strategy.go +++ b/pkg/registry/core/replicationcontroller/strategy.go @@ -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) diff --git a/pkg/registry/core/resourcequota/storage/storage.go b/pkg/registry/core/resourcequota/storage/storage.go index de470913676d..961905e48324 100644 --- a/pkg/registry/core/resourcequota/storage/storage.go +++ b/pkg/registry/core/resourcequota/storage/storage.go @@ -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() +} diff --git a/pkg/registry/core/resourcequota/strategy.go b/pkg/registry/core/resourcequota/strategy.go index 706954c6e035..63b3f6089d45 100644 --- a/pkg/registry/core/resourcequota/strategy.go +++ b/pkg/registry/core/resourcequota/strategy.go @@ -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) diff --git a/pkg/registry/core/service/storage/rest.go b/pkg/registry/core/service/storage/rest.go index b080326e9638..95ea56b54bf4 100644 --- a/pkg/registry/core/service/storage/rest.go +++ b/pkg/registry/core/service/storage/rest.go @@ -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{}) diff --git a/pkg/registry/core/service/storage/rest_test.go b/pkg/registry/core/service/storage/rest_test.go index bee9080e6f16..6d353370eb2e 100644 --- a/pkg/registry/core/service/storage/rest_test.go +++ b/pkg/registry/core/service/storage/rest_test.go @@ -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) } diff --git a/pkg/registry/core/service/storage/storage.go b/pkg/registry/core/service/storage/storage.go index da44e8b23564..0e706bea01dc 100644 --- a/pkg/registry/core/service/storage/storage.go +++ b/pkg/registry/core/service/storage/storage.go @@ -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 diff --git a/pkg/registry/core/service/strategy.go b/pkg/registry/core/service/strategy.go index e3c31dee967c..db60cc2290f6 100644 --- a/pkg/registry/core/service/strategy.go +++ b/pkg/registry/core/service/strategy.go @@ -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) diff --git a/pkg/registry/flowcontrol/flowschema/storage/storage.go b/pkg/registry/flowcontrol/flowschema/storage/storage.go index e5b90b7fb7b4..d8f14b9fd167 100644 --- a/pkg/registry/flowcontrol/flowschema/storage/storage.go +++ b/pkg/registry/flowcontrol/flowschema/storage/storage.go @@ -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() +} diff --git a/pkg/registry/flowcontrol/flowschema/strategy.go b/pkg/registry/flowcontrol/flowschema/strategy.go index 6286e1a1cc05..fd18476dc777 100644 --- a/pkg/registry/flowcontrol/flowschema/strategy.go +++ b/pkg/registry/flowcontrol/flowschema/strategy.go @@ -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) diff --git a/pkg/registry/flowcontrol/prioritylevelconfiguration/storage/storage.go b/pkg/registry/flowcontrol/prioritylevelconfiguration/storage/storage.go index 4e5e5976354d..baa80be83812 100644 --- a/pkg/registry/flowcontrol/prioritylevelconfiguration/storage/storage.go +++ b/pkg/registry/flowcontrol/prioritylevelconfiguration/storage/storage.go @@ -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() +} diff --git a/pkg/registry/flowcontrol/prioritylevelconfiguration/strategy.go b/pkg/registry/flowcontrol/prioritylevelconfiguration/strategy.go index 9cc7877a4d75..54208747eaa7 100644 --- a/pkg/registry/flowcontrol/prioritylevelconfiguration/strategy.go +++ b/pkg/registry/flowcontrol/prioritylevelconfiguration/strategy.go @@ -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) diff --git a/pkg/registry/networking/ingress/storage/storage.go b/pkg/registry/networking/ingress/storage/storage.go index 6c04f71ed326..0280c27b030f 100644 --- a/pkg/registry/networking/ingress/storage/storage.go +++ b/pkg/registry/networking/ingress/storage/storage.go @@ -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() +} diff --git a/pkg/registry/networking/ingress/strategy.go b/pkg/registry/networking/ingress/strategy.go index 5fac2499815f..01e0ff339969 100644 --- a/pkg/registry/networking/ingress/strategy.go +++ b/pkg/registry/networking/ingress/strategy.go @@ -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) diff --git a/pkg/registry/policy/poddisruptionbudget/storage/storage.go b/pkg/registry/policy/poddisruptionbudget/storage/storage.go index 687baaafd5a0..f402cfc5391a 100644 --- a/pkg/registry/policy/poddisruptionbudget/storage/storage.go +++ b/pkg/registry/policy/poddisruptionbudget/storage/storage.go @@ -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() +} diff --git a/pkg/registry/policy/poddisruptionbudget/strategy.go b/pkg/registry/policy/poddisruptionbudget/strategy.go index 66e1cb80bebf..dfb19b5b438c 100644 --- a/pkg/registry/policy/poddisruptionbudget/strategy.go +++ b/pkg/registry/policy/poddisruptionbudget/strategy.go @@ -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) diff --git a/pkg/registry/storage/volumeattachment/storage/storage.go b/pkg/registry/storage/volumeattachment/storage/storage.go index fb74627126cd..6f8d291e1e42 100644 --- a/pkg/registry/storage/volumeattachment/storage/storage.go +++ b/pkg/registry/storage/volumeattachment/storage/storage.go @@ -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() +} diff --git a/pkg/registry/storage/volumeattachment/strategy.go b/pkg/registry/storage/volumeattachment/strategy.go index b00712c60221..e1d03c2682b8 100644 --- a/pkg/registry/storage/volumeattachment/strategy.go +++ b/pkg/registry/storage/volumeattachment/strategy.go @@ -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) diff --git a/staging/src/k8s.io/apiextensions-apiserver/go.mod b/staging/src/k8s.io/apiextensions-apiserver/go.mod index 416ef6144372..e178c1a63062 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/go.mod +++ b/staging/src/k8s.io/apiextensions-apiserver/go.mod @@ -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 ) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/types.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/types.go index ee77a4229de3..b1c5f6f4c09f 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/types.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/types.go @@ -326,6 +326,8 @@ type CustomResourceDefinitionCondition struct { // CustomResourceDefinitionStatus indicates the state of the CustomResourceDefinition type CustomResourceDefinitionStatus struct { // Conditions indicate state for particular aspects of a CustomResourceDefinition + // +listType=map + // +listMapKey=type Conditions []CustomResourceDefinitionCondition // AcceptedNames are the names that are actually being used to serve discovery diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/generated.proto b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/generated.proto index 3494a31ba064..a82824ae71ec 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/generated.proto +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/generated.proto @@ -234,6 +234,8 @@ message CustomResourceDefinitionSpec { message CustomResourceDefinitionStatus { // conditions indicate state for particular aspects of a CustomResourceDefinition // +optional + // +listType=map + // +listMapKey=type repeated CustomResourceDefinitionCondition conditions = 1; // acceptedNames are the names that are actually being used to serve discovery. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/types.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/types.go index 02922c593e68..a55dd5b1b739 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/types.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1/types.go @@ -329,6 +329,8 @@ type CustomResourceDefinitionCondition struct { type CustomResourceDefinitionStatus struct { // conditions indicate state for particular aspects of a CustomResourceDefinition // +optional + // +listType=map + // +listMapKey=type Conditions []CustomResourceDefinitionCondition `json:"conditions" protobuf:"bytes,1,opt,name=conditions"` // acceptedNames are the names that are actually being used to serve discovery. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/generated.proto b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/generated.proto index 40635aff379c..e738f423c85f 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/generated.proto +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/generated.proto @@ -280,6 +280,8 @@ message CustomResourceDefinitionSpec { message CustomResourceDefinitionStatus { // conditions indicate state for particular aspects of a CustomResourceDefinition // +optional + // +listType=map + // +listMapKey=type repeated CustomResourceDefinitionCondition conditions = 1; // acceptedNames are the names that are actually being used to serve discovery. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/types.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/types.go index 806c68aa6c10..671869b9f7f5 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/types.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/types.go @@ -361,6 +361,8 @@ type CustomResourceDefinitionCondition struct { type CustomResourceDefinitionStatus struct { // conditions indicate state for particular aspects of a CustomResourceDefinition // +optional + // +listType=map + // +listMapKey=type Conditions []CustomResourceDefinitionCondition `json:"conditions" protobuf:"bytes,1,opt,name=conditions"` // acceptedNames are the names that are actually being used to serve discovery. diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go index e7876fe6d593..b264b5ab5596 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/customresource_handler.go @@ -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) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd.go index a2e7557449a1..2b51d64c3c09 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/etcd.go @@ -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 diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy.go index b31b627039fa..a2ff535187b5 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy.go @@ -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) diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/strategy.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/strategy.go index a65595dc141c..3025a605f091 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/strategy.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/strategy.go @@ -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 { diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/etcd.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/etcd.go index be857ecd2fe1..2eb7a246399d 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/etcd.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/etcd.go @@ -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() +} diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/strategy.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/strategy.go index 6702bcb6e690..e1ab194fb062 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/strategy.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/strategy.go @@ -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 { diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go index 71a4eff8e80f..5ef25dc81c40 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager.go @@ -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) } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager_test.go index d249e5f29a2b..1d33039d6281 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/fieldmanager_test.go @@ -103,6 +103,7 @@ func NewTestFieldManager(gvk schema.GroupVersionKind, ignoreManagedFieldsFromReq &fakeObjectDefaulter{}, gvk.GroupVersion(), gvk.GroupVersion(), + nil, ) if err != nil { panic(err) diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/structuredmerge.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/structuredmerge.go index b801d3d47171..bfa3a3f715a7 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/structuredmerge.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/structuredmerge.go @@ -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 } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/installer.go b/staging/src/k8s.io/apiserver/pkg/endpoints/installer.go index f56718862a3e..23fca82598f5 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/installer.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/installer.go @@ -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) diff --git a/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go b/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go index c2eec48b8395..d40214f9db9b 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go +++ b/staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go @@ -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 { diff --git a/staging/src/k8s.io/apiserver/pkg/registry/rest/rest.go b/staging/src/k8s.io/apiserver/pkg/registry/rest/rest.go index 66e7844a277f..8dba9b84bf85 100644 --- a/staging/src/k8s.io/apiserver/pkg/registry/rest/rest.go +++ b/staging/src/k8s.io/apiserver/pkg/registry/rest/rest.go @@ -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 +} diff --git a/staging/src/k8s.io/kube-aggregator/go.mod b/staging/src/k8s.io/kube-aggregator/go.mod index bc261e003eca..6efe83f36320 100644 --- a/staging/src/k8s.io/kube-aggregator/go.mod +++ b/staging/src/k8s.io/kube-aggregator/go.mod @@ -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 ( diff --git a/staging/src/k8s.io/kube-aggregator/pkg/registry/apiservice/etcd/etcd.go b/staging/src/k8s.io/kube-aggregator/pkg/registry/apiservice/etcd/etcd.go index 4fe241a5a3aa..0fdd1e971426 100644 --- a/staging/src/k8s.io/kube-aggregator/pkg/registry/apiservice/etcd/etcd.go +++ b/staging/src/k8s.io/kube-aggregator/pkg/registry/apiservice/etcd/etcd.go @@ -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() +} diff --git a/staging/src/k8s.io/kube-aggregator/pkg/registry/apiservice/strategy.go b/staging/src/k8s.io/kube-aggregator/pkg/registry/apiservice/strategy.go index d9bfe543e273..1818d503c914 100644 --- a/staging/src/k8s.io/kube-aggregator/pkg/registry/apiservice/strategy.go +++ b/staging/src/k8s.io/kube-aggregator/pkg/registry/apiservice/strategy.go @@ -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) diff --git a/test/integration/apiserver/apply/reset_fields_test.go b/test/integration/apiserver/apply/reset_fields_test.go new file mode 100644 index 000000000000..ffed948c04ff --- /dev/null +++ b/test/integration/apiserver/apply/reset_fields_test.go @@ -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) + } + }) + } + } +} diff --git a/test/integration/apiserver/apply/status_test.go b/test/integration/apiserver/apply/status_test.go index 9cba66645756..a72991db2214 100644 --- a/test/integration/apiserver/apply/status_test.go +++ b/test/integration/apiserver/apply/status_test.go @@ -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 diff --git a/vendor/modules.txt b/vendor/modules.txt index e5fdfd25ac79..0c493c87a6f8 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -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