knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/testing/resource.go (about)

     1  /*
     2  Copyright 2017 The Knative 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 testing
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  
    23  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    24  	"k8s.io/apimachinery/pkg/runtime"
    25  	"k8s.io/apimachinery/pkg/runtime/schema"
    26  
    27  	"knative.dev/pkg/apis"
    28  )
    29  
    30  // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
    31  
    32  // Resource is a simple resource that's compatible with our webhook
    33  type Resource struct {
    34  	metav1.TypeMeta   `json:",inline"`
    35  	metav1.ObjectMeta `json:"metadata,omitempty"`
    36  
    37  	Spec ResourceSpec `json:"spec,omitempty"`
    38  }
    39  
    40  // Check that Resource may be validated and defaulted.
    41  var (
    42  	_ apis.Validatable = (*Resource)(nil)
    43  	_ apis.Defaultable = (*Resource)(nil)
    44  	_ apis.Listable    = (*Resource)(nil)
    45  )
    46  
    47  // ResourceSpec represents test resource spec.
    48  type ResourceSpec struct {
    49  	FieldWithDefault                         string `json:"fieldWithDefault,omitempty"`
    50  	FieldWithContextDefault                  string `json:"fieldWithContextDefault,omitempty"`
    51  	FieldWithValidation                      string `json:"fieldWithValidation,omitempty"`
    52  	FieldThatsImmutable                      string `json:"fieldThatsImmutable,omitempty"`
    53  	FieldThatsImmutableWithDefault           string `json:"fieldThatsImmutableWithDefault,omitempty"`
    54  	FieldForCallbackValidation               string `json:"fieldThatCallbackRejects,omitempty"`
    55  	FieldForCallbackDefaulting               string `json:"fieldForCallbackDefaulting,omitempty"`
    56  	FieldForCallbackDefaultingIsWithinUpdate bool   `json:"fieldForCallbackDefaultingIsWithinUpdate,omitempty"`
    57  	FieldForCallbackDefaultingUsername       string `json:"fieldForCallbackDefaultingUsername,omitempty"`
    58  }
    59  
    60  // Reject any udpates to this subresource.
    61  const disallowedSubresource = "badbadsubresource"
    62  
    63  // GetGroupVersionKind returns the GroupVersionKind.
    64  func (r *Resource) GetGroupVersionKind() schema.GroupVersionKind {
    65  	return SchemeGroupVersion.WithKind("Resource")
    66  }
    67  
    68  // GetGroupVersionKindMeta returns the metav1.GroupVersionKind.
    69  func (r *Resource) GetGroupVersionKindMeta() metav1.GroupVersionKind {
    70  	gvk := r.GroupVersionKind()
    71  	return metav1.GroupVersionKind{
    72  		Group:   gvk.Group,
    73  		Version: gvk.Version,
    74  		Kind:    gvk.Kind,
    75  	}
    76  }
    77  
    78  // GetUntypedSpec returns the spec of the resource.
    79  func (r *Resource) GetUntypedSpec() interface{} {
    80  	return r.Spec
    81  }
    82  
    83  // SetDefaults sets the defaults on the object.
    84  func (r *Resource) SetDefaults(ctx context.Context) {
    85  	r.Spec.SetDefaults(ctx)
    86  }
    87  
    88  func (r *Resource) Validate(ctx context.Context) *apis.FieldError {
    89  	err := r.Spec.Validate(ctx).ViaField("spec")
    90  
    91  	if apis.IsInUpdate(ctx) {
    92  		original := apis.GetBaseline(ctx).(*Resource)
    93  		err = err.Also(r.CheckImmutableFields(ctx, original))
    94  		err = err.Also(r.CheckAllowedSubresourceUpdate(ctx, original))
    95  	}
    96  	return err
    97  }
    98  
    99  type onContextKey struct{}
   100  
   101  // WithValue returns a WithContext for attaching an OnContext with the given value.
   102  func WithValue(ctx context.Context, val string) context.Context {
   103  	return context.WithValue(ctx, onContextKey{}, &OnContext{Value: val})
   104  }
   105  
   106  // OnContext is a struct for holding a value attached to a context.
   107  type OnContext struct {
   108  	Value string
   109  }
   110  
   111  // SetDefaults sets the defaults on the spec.
   112  func (cs *ResourceSpec) SetDefaults(ctx context.Context) {
   113  	if cs.FieldWithDefault == "" {
   114  		cs.FieldWithDefault = "I'm a default."
   115  	}
   116  	if cs.FieldWithContextDefault == "" {
   117  		oc, ok := ctx.Value(onContextKey{}).(*OnContext)
   118  		if ok {
   119  			cs.FieldWithContextDefault = oc.Value
   120  		}
   121  	}
   122  	if cs.FieldThatsImmutableWithDefault == "" {
   123  		cs.FieldThatsImmutableWithDefault = "this is another default value"
   124  	}
   125  }
   126  
   127  func (cs *ResourceSpec) Validate(ctx context.Context) *apis.FieldError {
   128  	if cs.FieldWithValidation != "magic value" {
   129  		return apis.ErrInvalidValue(cs.FieldWithValidation, "fieldWithValidation")
   130  	}
   131  	return nil
   132  }
   133  
   134  func (r *Resource) CheckImmutableFields(ctx context.Context, original *Resource) *apis.FieldError {
   135  	if original.Spec.FieldThatsImmutable != r.Spec.FieldThatsImmutable {
   136  		return &apis.FieldError{
   137  			Message: "Immutable field changed",
   138  			Paths:   []string{"spec.fieldThatsImmutable"},
   139  			Details: fmt.Sprintf("got: %v, want: %v", r.Spec.FieldThatsImmutable,
   140  				original.Spec.FieldThatsImmutable),
   141  		}
   142  	}
   143  
   144  	if original.Spec.FieldThatsImmutableWithDefault != r.Spec.FieldThatsImmutableWithDefault {
   145  		return &apis.FieldError{
   146  			Message: "Immutable field changed",
   147  			Paths:   []string{"spec.fieldThatsImmutableWithDefault"},
   148  			Details: fmt.Sprintf("got: %v, want: %v", r.Spec.FieldThatsImmutableWithDefault,
   149  				original.Spec.FieldThatsImmutableWithDefault),
   150  		}
   151  	}
   152  	return nil
   153  }
   154  
   155  func (r *Resource) CheckAllowedSubresourceUpdate(ctx context.Context, original *Resource) *apis.FieldError {
   156  	if apis.GetUpdatedSubresource(ctx) == disallowedSubresource {
   157  		return &apis.FieldError{
   158  			Message: "Disallowed subresource update",
   159  			Details: "Disallowed subresource update: " + disallowedSubresource,
   160  		}
   161  	}
   162  	return nil
   163  }
   164  
   165  // GetListType implements apis.Listable
   166  func (r *Resource) GetListType() runtime.Object {
   167  	return &ResourceList{}
   168  }
   169  
   170  // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
   171  
   172  // ResourceList is a list of Resource resources
   173  type ResourceList struct {
   174  	metav1.TypeMeta `json:",inline"`
   175  	metav1.ListMeta `json:"metadata"`
   176  
   177  	Items []Resource `json:"items"`
   178  }