...

Source file src/k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition/strategy.go

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

     1  /*
     2  Copyright 2017 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 customresourcedefinition
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  
    23  	"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
    24  
    25  	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
    26  	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation"
    27  	apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
    28  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/fields"
    31  	"k8s.io/apimachinery/pkg/labels"
    32  	"k8s.io/apimachinery/pkg/runtime"
    33  	"k8s.io/apimachinery/pkg/util/validation/field"
    34  	"k8s.io/apiserver/pkg/registry/generic"
    35  	"k8s.io/apiserver/pkg/storage"
    36  	"k8s.io/apiserver/pkg/storage/names"
    37  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    38  )
    39  
    40  // strategy implements behavior for CustomResources.
    41  type strategy struct {
    42  	runtime.ObjectTyper
    43  	names.NameGenerator
    44  }
    45  
    46  func NewStrategy(typer runtime.ObjectTyper) strategy {
    47  	return strategy{typer, names.SimpleNameGenerator}
    48  }
    49  
    50  func (strategy) NamespaceScoped() bool {
    51  	return false
    52  }
    53  
    54  // GetResetFields returns the set of fields that get reset by the strategy
    55  // and should not be modified by the user.
    56  func (strategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
    57  	fields := map[fieldpath.APIVersion]*fieldpath.Set{
    58  		"apiextensions.k8s.io/v1": fieldpath.NewSet(
    59  			fieldpath.MakePathOrDie("status"),
    60  		),
    61  		"apiextensions.k8s.io/v1beta1": fieldpath.NewSet(
    62  			fieldpath.MakePathOrDie("status"),
    63  		),
    64  	}
    65  
    66  	return fields
    67  }
    68  
    69  // PrepareForCreate clears the status of a CustomResourceDefinition before creation.
    70  func (strategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
    71  	crd := obj.(*apiextensions.CustomResourceDefinition)
    72  	crd.Status = apiextensions.CustomResourceDefinitionStatus{}
    73  	crd.Generation = 1
    74  
    75  	for _, v := range crd.Spec.Versions {
    76  		if v.Storage {
    77  			if !apiextensions.IsStoredVersion(crd, v.Name) {
    78  				crd.Status.StoredVersions = append(crd.Status.StoredVersions, v.Name)
    79  			}
    80  			break
    81  		}
    82  	}
    83  	dropDisabledFields(crd, nil)
    84  }
    85  
    86  // PrepareForUpdate clears fields that are not allowed to be set by end users on update.
    87  func (strategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
    88  	newCRD := obj.(*apiextensions.CustomResourceDefinition)
    89  	oldCRD := old.(*apiextensions.CustomResourceDefinition)
    90  	newCRD.Status = oldCRD.Status
    91  
    92  	// Any changes to the spec increment the generation number, any changes to the
    93  	// status should reflect the generation number of the corresponding object. We push
    94  	// the burden of managing the status onto the clients because we can't (in general)
    95  	// know here what version of spec the writer of the status has seen. It may seem like
    96  	// we can at first -- since obj contains spec -- but in the future we will probably make
    97  	// status its own object, and even if we don't, writes may be the result of a
    98  	// read-update-write loop, so the contents of spec may not actually be the spec that
    99  	// the controller has *seen*.
   100  	if !apiequality.Semantic.DeepEqual(oldCRD.Spec, newCRD.Spec) {
   101  		newCRD.Generation = oldCRD.Generation + 1
   102  	}
   103  
   104  	for _, v := range newCRD.Spec.Versions {
   105  		if v.Storage {
   106  			if !apiextensions.IsStoredVersion(newCRD, v.Name) {
   107  				newCRD.Status.StoredVersions = append(newCRD.Status.StoredVersions, v.Name)
   108  			}
   109  			break
   110  		}
   111  	}
   112  	dropDisabledFields(newCRD, oldCRD)
   113  }
   114  
   115  // Validate validates a new CustomResourceDefinition.
   116  func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
   117  	return validation.ValidateCustomResourceDefinition(ctx, obj.(*apiextensions.CustomResourceDefinition))
   118  }
   119  
   120  // WarningsOnCreate returns warnings for the creation of the given object.
   121  func (strategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { return nil }
   122  
   123  // AllowCreateOnUpdate is false for CustomResourceDefinition; this means a POST is
   124  // needed to create one.
   125  func (strategy) AllowCreateOnUpdate() bool {
   126  	return false
   127  }
   128  
   129  // AllowUnconditionalUpdate is the default update policy for CustomResourceDefinition objects.
   130  func (strategy) AllowUnconditionalUpdate() bool {
   131  	return false
   132  }
   133  
   134  // Canonicalize normalizes the object after validation.
   135  func (strategy) Canonicalize(obj runtime.Object) {
   136  }
   137  
   138  // ValidateUpdate is the default update validation for an end user updating status.
   139  func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
   140  	return validation.ValidateCustomResourceDefinitionUpdate(ctx, obj.(*apiextensions.CustomResourceDefinition), old.(*apiextensions.CustomResourceDefinition))
   141  }
   142  
   143  // WarningsOnUpdate returns warnings for the given update.
   144  func (strategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
   145  	return nil
   146  }
   147  
   148  type statusStrategy struct {
   149  	runtime.ObjectTyper
   150  	names.NameGenerator
   151  }
   152  
   153  func NewStatusStrategy(typer runtime.ObjectTyper) statusStrategy {
   154  	return statusStrategy{typer, names.SimpleNameGenerator}
   155  }
   156  
   157  func (statusStrategy) NamespaceScoped() bool {
   158  	return false
   159  }
   160  
   161  // GetResetFields returns the set of fields that get reset by the strategy
   162  // and should not be modified by the user.
   163  func (statusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
   164  	fields := map[fieldpath.APIVersion]*fieldpath.Set{
   165  		"apiextensions.k8s.io/v1": fieldpath.NewSet(
   166  			fieldpath.MakePathOrDie("metadata"),
   167  			fieldpath.MakePathOrDie("spec"),
   168  		),
   169  		"apiextensions.k8s.io/v1beta1": fieldpath.NewSet(
   170  			fieldpath.MakePathOrDie("metadata"),
   171  			fieldpath.MakePathOrDie("spec"),
   172  		),
   173  	}
   174  
   175  	return fields
   176  }
   177  
   178  func (statusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
   179  	newObj := obj.(*apiextensions.CustomResourceDefinition)
   180  	oldObj := old.(*apiextensions.CustomResourceDefinition)
   181  	newObj.Spec = oldObj.Spec
   182  
   183  	// Status updates are for only for updating status, not objectmeta.
   184  	metav1.ResetObjectMetaForStatus(&newObj.ObjectMeta, &newObj.ObjectMeta)
   185  }
   186  
   187  func (statusStrategy) AllowCreateOnUpdate() bool {
   188  	return false
   189  }
   190  
   191  func (statusStrategy) AllowUnconditionalUpdate() bool {
   192  	return false
   193  }
   194  
   195  func (statusStrategy) Canonicalize(obj runtime.Object) {
   196  }
   197  
   198  func (statusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
   199  	return validation.ValidateUpdateCustomResourceDefinitionStatus(obj.(*apiextensions.CustomResourceDefinition), old.(*apiextensions.CustomResourceDefinition))
   200  }
   201  
   202  // WarningsOnUpdate returns warnings for the given update.
   203  func (statusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
   204  	return nil
   205  }
   206  
   207  // GetAttrs returns labels and fields of a given object for filtering purposes.
   208  func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
   209  	apiserver, ok := obj.(*apiextensions.CustomResourceDefinition)
   210  	if !ok {
   211  		return nil, nil, fmt.Errorf("given object is not a CustomResourceDefinition")
   212  	}
   213  	return labels.Set(apiserver.ObjectMeta.Labels), CustomResourceDefinitionToSelectableFields(apiserver), nil
   214  }
   215  
   216  // MatchCustomResourceDefinition is the filter used by the generic etcd backend to watch events
   217  // from etcd to clients of the apiserver only interested in specific labels/fields.
   218  func MatchCustomResourceDefinition(label labels.Selector, field fields.Selector) storage.SelectionPredicate {
   219  	return storage.SelectionPredicate{
   220  		Label:    label,
   221  		Field:    field,
   222  		GetAttrs: GetAttrs,
   223  	}
   224  }
   225  
   226  // CustomResourceDefinitionToSelectableFields returns a field set that represents the object.
   227  func CustomResourceDefinitionToSelectableFields(obj *apiextensions.CustomResourceDefinition) fields.Set {
   228  	return generic.ObjectMetaFieldsSet(&obj.ObjectMeta, true)
   229  }
   230  
   231  // dropDisabledFields drops disabled fields that are not used if their associated feature gates
   232  // are not enabled.
   233  func dropDisabledFields(newCRD *apiextensions.CustomResourceDefinition, oldCRD *apiextensions.CustomResourceDefinition) {
   234  	if !utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CRDValidationRatcheting) && (oldCRD == nil || (oldCRD != nil && !specHasOptionalOldSelf(&oldCRD.Spec))) {
   235  		if newCRD.Spec.Validation != nil {
   236  			dropOptionalOldSelfField(newCRD.Spec.Validation.OpenAPIV3Schema)
   237  		}
   238  
   239  		for _, v := range newCRD.Spec.Versions {
   240  			if v.Schema != nil {
   241  				dropOptionalOldSelfField(v.Schema.OpenAPIV3Schema)
   242  			}
   243  		}
   244  	}
   245  	if !utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceFieldSelectors) && (oldCRD == nil || (oldCRD != nil && !specHasSelectableFields(&oldCRD.Spec))) {
   246  		dropSelectableFields(&newCRD.Spec)
   247  	}
   248  }
   249  
   250  // dropOptionalOldSelfField drops field optionalOldSelf from CRD schema
   251  func dropOptionalOldSelfField(schema *apiextensions.JSONSchemaProps) {
   252  	if schema == nil {
   253  		return
   254  	}
   255  	for i := range schema.XValidations {
   256  		schema.XValidations[i].OptionalOldSelf = nil
   257  	}
   258  
   259  	if schema.AdditionalProperties != nil {
   260  		dropOptionalOldSelfField(schema.AdditionalProperties.Schema)
   261  	}
   262  	for def, jsonSchema := range schema.Properties {
   263  		dropOptionalOldSelfField(&jsonSchema)
   264  		schema.Properties[def] = jsonSchema
   265  	}
   266  	if schema.Items != nil {
   267  		dropOptionalOldSelfField(schema.Items.Schema)
   268  		for i, jsonSchema := range schema.Items.JSONSchemas {
   269  			dropOptionalOldSelfField(&jsonSchema)
   270  			schema.Items.JSONSchemas[i] = jsonSchema
   271  		}
   272  	}
   273  }
   274  
   275  func specHasOptionalOldSelf(spec *apiextensions.CustomResourceDefinitionSpec) bool {
   276  	return validation.HasSchemaWith(spec, schemaHasOptionalOldSelf)
   277  }
   278  
   279  func schemaHasOptionalOldSelf(s *apiextensions.JSONSchemaProps) bool {
   280  	return validation.SchemaHas(s, func(s *apiextensions.JSONSchemaProps) bool {
   281  		for _, v := range s.XValidations {
   282  			if v.OptionalOldSelf != nil {
   283  				return true
   284  			}
   285  
   286  		}
   287  		return false
   288  	})
   289  }
   290  
   291  func dropSelectableFields(spec *apiextensions.CustomResourceDefinitionSpec) {
   292  	spec.SelectableFields = nil
   293  	for i := range spec.Versions {
   294  		spec.Versions[i].SelectableFields = nil
   295  	}
   296  }
   297  
   298  func specHasSelectableFields(spec *apiextensions.CustomResourceDefinitionSpec) bool {
   299  	if spec.SelectableFields != nil {
   300  		return true
   301  	}
   302  	for _, v := range spec.Versions {
   303  		if v.SelectableFields != nil {
   304  			return true
   305  		}
   306  	}
   307  
   308  	return false
   309  }
   310  

View as plain text