1/* 2Copyright 2018 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package conversion 18 19import ( 20 "fmt" 21 22 autoscalingv1 "k8s.io/api/autoscaling/v1" 23 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" 24 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 25 "k8s.io/apimachinery/pkg/runtime" 26 "k8s.io/apimachinery/pkg/runtime/schema" 27 "k8s.io/apiserver/pkg/util/webhook" 28 typedscheme "k8s.io/client-go/kubernetes/scheme" 29) 30 31// CRConverterFactory is the factory for all CR converters. 32type CRConverterFactory struct { 33 // webhookConverterFactory is the factory for webhook converters. 34 // This field should not be used if CustomResourceWebhookConversion feature is disabled. 35 webhookConverterFactory *webhookConverterFactory 36} 37 38// converterMetricFactorySingleton protects us from reregistration of metrics on repeated 39// apiextensions-apiserver runs. 40var converterMetricFactorySingleton = newConverterMertricFactory() 41 42// NewCRConverterFactory creates a new CRConverterFactory 43func NewCRConverterFactory(serviceResolver webhook.ServiceResolver, authResolverWrapper webhook.AuthenticationInfoResolverWrapper) (*CRConverterFactory, error) { 44 converterFactory := &CRConverterFactory{} 45 webhookConverterFactory, err := newWebhookConverterFactory(serviceResolver, authResolverWrapper) 46 if err != nil { 47 return nil, err 48 } 49 converterFactory.webhookConverterFactory = webhookConverterFactory 50 return converterFactory, nil 51} 52 53// NewConverter returns a new CR converter based on the conversion settings in crd object. 54func (m *CRConverterFactory) NewConverter(crd *apiextensionsv1.CustomResourceDefinition) (safe, unsafe runtime.ObjectConvertor, err error) { 55 validVersions := map[schema.GroupVersion]bool{} 56 for _, version := range crd.Spec.Versions { 57 validVersions[schema.GroupVersion{Group: crd.Spec.Group, Version: version.Name}] = true 58 } 59 60 var converter crConverterInterface 61 switch crd.Spec.Conversion.Strategy { 62 case apiextensionsv1.NoneConverter: 63 converter = &nopConverter{} 64 case apiextensionsv1.WebhookConverter: 65 converter, err = m.webhookConverterFactory.NewWebhookConverter(crd) 66 if err != nil { 67 return nil, nil, err 68 } 69 converter, err = converterMetricFactorySingleton.addMetrics("webhook", crd.Name, converter) 70 if err != nil { 71 return nil, nil, err 72 } 73 default: 74 return nil, nil, fmt.Errorf("unknown conversion strategy %q for CRD %s", crd.Spec.Conversion.Strategy, crd.Name) 75 } 76 77 // Determine whether we should expect to be asked to "convert" autoscaling/v1 Scale types 78 convertScale := false 79 for _, version := range crd.Spec.Versions { 80 if version.Subresources != nil && version.Subresources.Scale != nil { 81 convertScale = true 82 } 83 } 84 85 unsafe = &crConverter{ 86 convertScale: convertScale, 87 validVersions: validVersions, 88 clusterScoped: crd.Spec.Scope == apiextensionsv1.ClusterScoped, 89 converter: converter, 90 } 91 return &safeConverterWrapper{unsafe}, unsafe, nil 92} 93 94// crConverterInterface is the interface all cr converters must implement 95type crConverterInterface interface { 96 // Convert converts in object to the given gvk and returns the converted object. 97 // Note that the function may mutate in object and return it. A safe wrapper will make sure 98 // a safe converter will be returned. 99 Convert(in runtime.Object, targetGVK schema.GroupVersion) (runtime.Object, error) 100} 101 102// crConverter extends the delegate converter with generic CR conversion behaviour. The delegate will implement the 103// user defined conversion strategy given in the CustomResourceDefinition. 104type crConverter struct { 105 convertScale bool 106 converter crConverterInterface 107 validVersions map[schema.GroupVersion]bool 108 clusterScoped bool 109} 110 111func (c *crConverter) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) { 112 // We currently only support metadata.namespace and metadata.name. 113 switch { 114 case label == "metadata.name": 115 return label, value, nil 116 case !c.clusterScoped && label == "metadata.namespace": 117 return label, value, nil 118 default: 119 return "", "", fmt.Errorf("field label not supported: %s", label) 120 } 121} 122 123func (c *crConverter) Convert(in, out, context interface{}) error { 124 // Special-case typed scale conversion if this custom resource supports a scale endpoint 125 if c.convertScale { 126 _, isInScale := in.(*autoscalingv1.Scale) 127 _, isOutScale := out.(*autoscalingv1.Scale) 128 if isInScale || isOutScale { 129 return typedscheme.Scheme.Convert(in, out, context) 130 } 131 } 132 133 unstructIn, ok := in.(*unstructured.Unstructured) 134 if !ok { 135 return fmt.Errorf("input type %T in not valid for unstructured conversion to %T", in, out) 136 } 137 138 unstructOut, ok := out.(*unstructured.Unstructured) 139 if !ok { 140 return fmt.Errorf("output type %T in not valid for unstructured conversion from %T", out, in) 141 } 142 143 outGVK := unstructOut.GroupVersionKind() 144 converted, err := c.ConvertToVersion(unstructIn, outGVK.GroupVersion()) 145 if err != nil { 146 return err 147 } 148 unstructuredConverted, ok := converted.(runtime.Unstructured) 149 if !ok { 150 // this should not happened 151 return fmt.Errorf("CR conversion failed") 152 } 153 unstructOut.SetUnstructuredContent(unstructuredConverted.UnstructuredContent()) 154 return nil 155} 156 157// ConvertToVersion converts in object to the given gvk in place and returns the same `in` object. 158// The in object can be a single object or a UnstructuredList. CRD storage implementation creates an 159// UnstructuredList with the request's GV, populates it from storage, then calls conversion to convert 160// the individual items. This function assumes it never gets a v1.List. 161func (c *crConverter) ConvertToVersion(in runtime.Object, target runtime.GroupVersioner) (runtime.Object, error) { 162 fromGVK := in.GetObjectKind().GroupVersionKind() 163 toGVK, ok := target.KindForGroupVersionKinds([]schema.GroupVersionKind{fromGVK}) 164 if !ok { 165 // TODO: should this be a typed error? 166 return nil, fmt.Errorf("%v is unstructured and is not suitable for converting to %q", fromGVK.String(), target) 167 } 168 if !c.validVersions[toGVK.GroupVersion()] { 169 return nil, fmt.Errorf("request to convert CR to an invalid group/version: %s", toGVK.GroupVersion().String()) 170 } 171 // Note that even if the request is for a list, the GV of the request UnstructuredList is what 172 // is expected to convert to. As mentioned in the function's document, it is not expected to 173 // get a v1.List. 174 if !c.validVersions[fromGVK.GroupVersion()] { 175 return nil, fmt.Errorf("request to convert CR from an invalid group/version: %s", fromGVK.GroupVersion().String()) 176 } 177 // Check list item's apiVersion 178 if list, ok := in.(*unstructured.UnstructuredList); ok { 179 for i := range list.Items { 180 expectedGV := list.Items[i].GroupVersionKind().GroupVersion() 181 if !c.validVersions[expectedGV] { 182 return nil, fmt.Errorf("request to convert CR list failed, list index %d has invalid group/version: %s", i, expectedGV.String()) 183 } 184 } 185 } 186 return c.converter.Convert(in, toGVK.GroupVersion()) 187} 188 189// safeConverterWrapper is a wrapper over an unsafe object converter that makes copy of the input and then delegate to the unsafe converter. 190type safeConverterWrapper struct { 191 unsafe runtime.ObjectConvertor 192} 193 194var _ runtime.ObjectConvertor = &safeConverterWrapper{} 195 196// ConvertFieldLabel delegate the call to the unsafe converter. 197func (c *safeConverterWrapper) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) { 198 return c.unsafe.ConvertFieldLabel(gvk, label, value) 199} 200 201// Convert makes a copy of in object and then delegate the call to the unsafe converter. 202func (c *safeConverterWrapper) Convert(in, out, context interface{}) error { 203 inObject, ok := in.(runtime.Object) 204 if !ok { 205 return fmt.Errorf("input type %T in not valid for object conversion", in) 206 } 207 return c.unsafe.Convert(inObject.DeepCopyObject(), out, context) 208} 209 210// ConvertToVersion makes a copy of in object and then delegate the call to the unsafe converter. 211func (c *safeConverterWrapper) ConvertToVersion(in runtime.Object, target runtime.GroupVersioner) (runtime.Object, error) { 212 return c.unsafe.ConvertToVersion(in.DeepCopyObject(), target) 213} 214