knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/webhook/resourcesemantics/conversion/conversion.go (about) 1 /* 2 Copyright 2020 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 conversion 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 24 "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" 25 "go.uber.org/zap" 26 apixv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/runtime" 29 "k8s.io/apimachinery/pkg/runtime/schema" 30 31 "knative.dev/pkg/apis" 32 "knative.dev/pkg/kmeta" 33 "knative.dev/pkg/logging" 34 "knative.dev/pkg/logging/logkey" 35 "knative.dev/pkg/webhook" 36 ) 37 38 // Convert implements webhook.ConversionController 39 func (r *reconciler) Convert( 40 ctx context.Context, 41 req *apixv1.ConversionRequest, 42 ) *apixv1.ConversionResponse { 43 if r.withContext != nil { 44 ctx = r.withContext(ctx) 45 } 46 47 res := &apixv1.ConversionResponse{ 48 UID: req.UID, 49 Result: metav1.Status{ 50 Status: metav1.StatusSuccess, 51 }, 52 } 53 54 result := make([]runtime.RawExtension, 0, len(req.Objects)) 55 56 for _, obj := range req.Objects { 57 converted, err := r.convert(ctx, obj, req.DesiredAPIVersion) 58 if err != nil { 59 logging.FromContext(ctx).Errorw("Conversion failed", zap.Error(err)) 60 res.Result.Status = metav1.StatusFailure 61 res.Result.Message = err.Error() 62 break 63 } 64 65 result = append(result, converted) 66 } 67 68 res.ConvertedObjects = result 69 return res 70 } 71 72 func (r *reconciler) convert( 73 ctx context.Context, 74 inRaw runtime.RawExtension, 75 targetVersion string, 76 ) (runtime.RawExtension, error) { 77 logger := logging.FromContext(ctx) 78 var ret runtime.RawExtension 79 80 inGVK, err := parseGVK(inRaw) 81 if err != nil { 82 return ret, err 83 } 84 85 // otelhttp middleware creates the labeler 86 labeler, _ := otelhttp.LabelerFromContext(ctx) 87 labeler.Add( 88 webhook.KindAttr.With(inGVK.Kind), 89 ) 90 91 inGK := inGVK.GroupKind() 92 conv, ok := r.kinds[inGK] 93 if !ok { 94 return ret, fmt.Errorf("no conversion support for type %s", formatGK(inGVK.GroupKind())) 95 } 96 97 outGVK, err := parseAPIVersion(targetVersion, inGK.Kind) 98 if err != nil { 99 return ret, err 100 } 101 102 inZygote, ok := conv.Zygotes[inGVK.Version] 103 if !ok { 104 return ret, fmt.Errorf("conversion not supported for type %s", formatGVK(inGVK)) 105 } 106 outZygote, ok := conv.Zygotes[outGVK.Version] 107 if !ok { 108 return ret, fmt.Errorf("conversion not supported for type %s", formatGVK(outGVK)) 109 } 110 hubZygote, ok := conv.Zygotes[conv.HubVersion] 111 if !ok { 112 return ret, fmt.Errorf("conversion not supported for type %s", formatGK(inGVK.GroupKind())) 113 } 114 115 in := inZygote.DeepCopyObject().(ConvertibleObject) 116 hub := hubZygote.DeepCopyObject().(ConvertibleObject) 117 out := outZygote.DeepCopyObject().(ConvertibleObject) 118 119 hubGVK := inGVK.GroupKind().WithVersion(conv.HubVersion) 120 121 logger = logger.With( 122 zap.String("inputType", formatGVK(inGVK)), 123 zap.String("outputType", formatGVK(outGVK)), 124 zap.String("hubType", formatGVK(hubGVK)), 125 ) 126 127 // TODO(dprotaso) - potentially error on unknown fields 128 if err = json.Unmarshal(inRaw.Raw, &in); err != nil { 129 return ret, fmt.Errorf("unable to unmarshal input: %w", err) 130 } 131 132 if acc, err := kmeta.DeletionHandlingAccessor(in); err == nil { 133 // TODO: right now we don't convert any non-namespaced objects. If we ever do that 134 // this needs to updated to deal with it. 135 logger = logger.With(zap.String(logkey.Key, acc.GetNamespace()+"/"+acc.GetName())) 136 } else { 137 logger.Infof("Could not get Accessor for %s: %v", formatGK(inGVK.GroupKind()), err) 138 } 139 ctx = logging.WithLogger(ctx, logger) 140 141 if inGVK.Version == conv.HubVersion { 142 hub = in 143 } else if err = hub.ConvertFrom(ctx, in); err != nil { 144 return ret, fmt.Errorf("conversion failed to version %s for type %s - %w", outGVK.Version, formatGVK(inGVK), err) 145 } 146 147 if outGVK.Version == conv.HubVersion { 148 out = hub 149 } else if err = hub.ConvertTo(ctx, out); err != nil { 150 return ret, fmt.Errorf("conversion failed to version %s for type %s - %w", outGVK.Version, formatGVK(inGVK), err) 151 } 152 153 out.GetObjectKind().SetGroupVersionKind(outGVK) 154 155 if defaultable, ok := out.(apis.Defaultable); ok { 156 defaultable.SetDefaults(ctx) 157 } 158 159 if ret.Raw, err = json.Marshal(out); err != nil { 160 return ret, fmt.Errorf("unable to marshal output: %w", err) 161 } 162 return ret, nil 163 } 164 165 func parseGVK(in runtime.RawExtension) (schema.GroupVersionKind, error) { 166 var ( 167 typeMeta metav1.TypeMeta 168 gvk schema.GroupVersionKind 169 ) 170 171 if err := json.Unmarshal(in.Raw, &typeMeta); err != nil { 172 return gvk, fmt.Errorf("error parsing type meta %q - %w", string(in.Raw), err) 173 } 174 175 gv, err := schema.ParseGroupVersion(typeMeta.APIVersion) 176 if err != nil { 177 return gvk, fmt.Errorf("error parsing GV %q: %w", typeMeta.APIVersion, err) 178 } 179 gvk = gv.WithKind(typeMeta.Kind) 180 181 if gvk.Group == "" || gvk.Version == "" || gvk.Kind == "" { 182 return gvk, fmt.Errorf("invalid GroupVersionKind %v", gvk) 183 } 184 185 return gvk, nil 186 } 187 188 func parseAPIVersion(apiVersion string, kind string) (schema.GroupVersionKind, error) { 189 gv, err := schema.ParseGroupVersion(apiVersion) 190 if err != nil { 191 err = fmt.Errorf("desired API version %q is not valid", apiVersion) 192 return schema.GroupVersionKind{}, err 193 } 194 195 if !isValidGV(gv) { 196 err = fmt.Errorf("desired API version %q is not valid", apiVersion) 197 return schema.GroupVersionKind{}, err 198 } 199 200 return gv.WithKind(kind), nil 201 } 202 203 func formatGVK(gvk schema.GroupVersionKind) string { 204 return fmt.Sprintf("[kind=%s group=%s version=%s]", gvk.Kind, gvk.Group, gvk.Version) 205 } 206 207 func formatGK(gk schema.GroupKind) string { 208 return fmt.Sprintf("[kind=%s group=%s]", gk.Kind, gk.Group) 209 } 210 211 func isValidGV(gk schema.GroupVersion) bool { 212 return gk.Group != "" && gk.Version != "" 213 }