...

Source file src/k8s.io/apiextensions-apiserver/pkg/registry/customresource/status_strategy.go

Documentation: k8s.io/apiextensions-apiserver/pkg/registry/customresource

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package customresource
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  
    23  	"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
    24  
    25  	structurallisttype "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/listtype"
    26  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    27  	"k8s.io/apimachinery/pkg/runtime"
    28  	"k8s.io/apimachinery/pkg/util/validation/field"
    29  	celconfig "k8s.io/apiserver/pkg/apis/cel"
    30  )
    31  
    32  type statusStrategy struct {
    33  	customResourceStrategy
    34  }
    35  
    36  func NewStatusStrategy(strategy customResourceStrategy) statusStrategy {
    37  	return statusStrategy{strategy}
    38  }
    39  
    40  // GetResetFields returns the set of fields that get reset by the strategy
    41  // and should not be modified by the user.
    42  func (a statusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
    43  	fields := map[fieldpath.APIVersion]*fieldpath.Set{
    44  		fieldpath.APIVersion(a.customResourceStrategy.kind.GroupVersion().String()): fieldpath.NewSet(
    45  			// Note that if there are other top level fields unique to CRDs,
    46  			// those will also get removed by the apiserver prior to persisting,
    47  			// but won't be added to the resetFields set.
    48  
    49  			// This isn't an issue now, but if it becomes an issue in the future
    50  			// we might need a mechanism that is the inverse of resetFields where
    51  			// you specify only the fields to be kept rather than the fields to be wiped
    52  			// that way you could wipe everything but the status in this case.
    53  			fieldpath.MakePathOrDie("spec"),
    54  		),
    55  	}
    56  
    57  	return fields
    58  }
    59  
    60  func (a statusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
    61  	// update is only allowed to set status
    62  	newCustomResourceObject := obj.(*unstructured.Unstructured)
    63  	newCustomResource := newCustomResourceObject.UnstructuredContent()
    64  	status, ok := newCustomResource["status"]
    65  
    66  	// managedFields must be preserved since it's been modified to
    67  	// track changed fields in the status update.
    68  	managedFields := newCustomResourceObject.GetManagedFields()
    69  
    70  	// copy old object into new object
    71  	oldCustomResourceObject := old.(*unstructured.Unstructured)
    72  	// overridding the resourceVersion in metadata is safe here, we have already checked that
    73  	// new object and old object have the same resourceVersion.
    74  	*newCustomResourceObject = *oldCustomResourceObject.DeepCopy()
    75  
    76  	// set status
    77  	newCustomResourceObject.SetManagedFields(managedFields)
    78  	newCustomResource = newCustomResourceObject.UnstructuredContent()
    79  	if ok {
    80  		newCustomResource["status"] = status
    81  	} else {
    82  		delete(newCustomResource, "status")
    83  	}
    84  }
    85  
    86  // ValidateUpdate is the default update validation for an end user updating status.
    87  func (a statusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
    88  	uNew, ok := obj.(*unstructured.Unstructured)
    89  	if !ok {
    90  		return field.ErrorList{field.Invalid(field.NewPath(""), obj, fmt.Sprintf("has type %T. Must be a pointer to an Unstructured type", obj))}
    91  	}
    92  	uOld, ok := old.(*unstructured.Unstructured)
    93  	if !ok {
    94  		return field.ErrorList{field.Invalid(field.NewPath(""), old, fmt.Sprintf("has type %T. Must be a pointer to an Unstructured type", old))}
    95  	}
    96  
    97  	var errs field.ErrorList
    98  	errs = append(errs, a.customResourceStrategy.validator.ValidateStatusUpdate(ctx, uNew, uOld, a.scale)...)
    99  
   100  	// ratcheting validation of x-kubernetes-list-type value map and set
   101  	if newErrs := structurallisttype.ValidateListSetsAndMaps(nil, a.structuralSchema, uNew.Object); len(newErrs) > 0 {
   102  		if oldErrs := structurallisttype.ValidateListSetsAndMaps(nil, a.structuralSchema, uOld.Object); len(oldErrs) == 0 {
   103  			errs = append(errs, newErrs...)
   104  		}
   105  	}
   106  
   107  	// validate x-kubernetes-validations rules
   108  	if celValidator := a.customResourceStrategy.celValidator; celValidator != nil {
   109  		if has, err := hasBlockingErr(errs); has {
   110  			errs = append(errs, err)
   111  		} else {
   112  			err, _ := celValidator.Validate(ctx, nil, a.customResourceStrategy.structuralSchema, uNew.Object, uOld.Object, celconfig.RuntimeCELCostBudget)
   113  			errs = append(errs, err...)
   114  		}
   115  	}
   116  	return errs
   117  }
   118  
   119  // WarningsOnUpdate returns warnings for the given update.
   120  func (statusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
   121  	return nil
   122  }
   123  

View as plain text