Server-Side Apply: Status Wiping/Reset Fields

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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