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 }