1
16
17 package conversion
18
19 import (
20 "fmt"
21 "strings"
22
23 autoscalingv1 "k8s.io/api/autoscaling/v1"
24 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
25 apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
26 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
27 "k8s.io/apimachinery/pkg/runtime"
28 "k8s.io/apimachinery/pkg/runtime/schema"
29 "k8s.io/apimachinery/pkg/util/sets"
30 utilfeature "k8s.io/apiserver/pkg/util/feature"
31 "k8s.io/apiserver/pkg/util/webhook"
32 typedscheme "k8s.io/client-go/kubernetes/scheme"
33 )
34
35
36 type CRConverterFactory struct {
37
38
39 webhookConverterFactory *webhookConverterFactory
40 }
41
42
43
44 var converterMetricFactorySingleton = newConverterMetricFactory()
45
46
47 func NewCRConverterFactory(serviceResolver webhook.ServiceResolver, authResolverWrapper webhook.AuthenticationInfoResolverWrapper) (*CRConverterFactory, error) {
48 converterFactory := &CRConverterFactory{}
49 webhookConverterFactory, err := newWebhookConverterFactory(serviceResolver, authResolverWrapper)
50 if err != nil {
51 return nil, err
52 }
53 converterFactory.webhookConverterFactory = webhookConverterFactory
54 return converterFactory, nil
55 }
56
57
58 func (m *CRConverterFactory) NewConverter(crd *apiextensionsv1.CustomResourceDefinition) (safe, unsafe runtime.ObjectConvertor, err error) {
59 validVersions := map[schema.GroupVersion]bool{}
60 for _, version := range crd.Spec.Versions {
61 validVersions[schema.GroupVersion{Group: crd.Spec.Group, Version: version.Name}] = true
62 }
63
64 var converter crConverterInterface
65 switch crd.Spec.Conversion.Strategy {
66 case apiextensionsv1.NoneConverter:
67 converter = &nopConverter{}
68 case apiextensionsv1.WebhookConverter:
69 converter, err = m.webhookConverterFactory.NewWebhookConverter(crd)
70 if err != nil {
71 return nil, nil, err
72 }
73 converter, err = converterMetricFactorySingleton.addMetrics(crd.Name, converter)
74 if err != nil {
75 return nil, nil, err
76 }
77 default:
78 return nil, nil, fmt.Errorf("unknown conversion strategy %q for CRD %s", crd.Spec.Conversion.Strategy, crd.Name)
79 }
80
81
82 convertScale := false
83 selectableFields := map[schema.GroupVersion]sets.Set[string]{}
84 for _, version := range crd.Spec.Versions {
85 gv := schema.GroupVersion{Group: crd.Spec.Group, Version: version.Name}
86 if version.Subresources != nil && version.Subresources.Scale != nil {
87 convertScale = true
88 }
89 if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceFieldSelectors) {
90 fieldPaths := sets.New[string]()
91 for _, sf := range version.SelectableFields {
92 fieldPaths.Insert(strings.TrimPrefix(sf.JSONPath, "."))
93 }
94 selectableFields[gv] = fieldPaths
95 }
96 }
97
98 unsafe = &crConverter{
99 convertScale: convertScale,
100 validVersions: validVersions,
101 clusterScoped: crd.Spec.Scope == apiextensionsv1.ClusterScoped,
102 converter: converter,
103 selectableFields: selectableFields,
104 }
105 return &safeConverterWrapper{unsafe}, unsafe, nil
106 }
107
108
109 type crConverterInterface interface {
110
111
112
113 Convert(in runtime.Object, targetGVK schema.GroupVersion) (runtime.Object, error)
114 }
115
116
117
118 type crConverter struct {
119 convertScale bool
120 converter crConverterInterface
121 validVersions map[schema.GroupVersion]bool
122 clusterScoped bool
123 selectableFields map[schema.GroupVersion]sets.Set[string]
124 }
125
126 func (c *crConverter) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) {
127 switch {
128 case label == "metadata.name":
129 return label, value, nil
130 case !c.clusterScoped && label == "metadata.namespace":
131 return label, value, nil
132 default:
133 if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceFieldSelectors) {
134 groupFields := c.selectableFields[gvk.GroupVersion()]
135 if groupFields != nil && groupFields.Has(label) {
136 return label, value, nil
137 }
138 }
139 return "", "", fmt.Errorf("field label not supported: %s", label)
140 }
141 }
142
143 func (c *crConverter) Convert(in, out, context interface{}) error {
144
145 if c.convertScale {
146 _, isInScale := in.(*autoscalingv1.Scale)
147 _, isOutScale := out.(*autoscalingv1.Scale)
148 if isInScale || isOutScale {
149 return typedscheme.Scheme.Convert(in, out, context)
150 }
151 }
152
153 unstructIn, ok := in.(*unstructured.Unstructured)
154 if !ok {
155 return fmt.Errorf("input type %T in not valid for unstructured conversion to %T", in, out)
156 }
157
158 unstructOut, ok := out.(*unstructured.Unstructured)
159 if !ok {
160 return fmt.Errorf("output type %T in not valid for unstructured conversion from %T", out, in)
161 }
162
163 outGVK := unstructOut.GroupVersionKind()
164 converted, err := c.ConvertToVersion(unstructIn, outGVK.GroupVersion())
165 if err != nil {
166 return err
167 }
168 unstructuredConverted, ok := converted.(runtime.Unstructured)
169 if !ok {
170
171 return fmt.Errorf("CR conversion failed")
172 }
173 unstructOut.SetUnstructuredContent(unstructuredConverted.UnstructuredContent())
174 return nil
175 }
176
177
178
179
180
181 func (c *crConverter) ConvertToVersion(in runtime.Object, target runtime.GroupVersioner) (runtime.Object, error) {
182 fromGVK := in.GetObjectKind().GroupVersionKind()
183 toGVK, ok := target.KindForGroupVersionKinds([]schema.GroupVersionKind{fromGVK})
184 if !ok {
185
186 return nil, fmt.Errorf("%v is unstructured and is not suitable for converting to %q", fromGVK.String(), target)
187 }
188
189 if c.convertScale {
190 if _, isInScale := in.(*autoscalingv1.Scale); isInScale {
191 return typedscheme.Scheme.ConvertToVersion(in, target)
192 }
193 }
194
195 if !c.validVersions[toGVK.GroupVersion()] {
196 return nil, fmt.Errorf("request to convert CR to an invalid group/version: %s", toGVK.GroupVersion().String())
197 }
198
199
200
201 if !c.validVersions[fromGVK.GroupVersion()] {
202 return nil, fmt.Errorf("request to convert CR from an invalid group/version: %s", fromGVK.GroupVersion().String())
203 }
204
205 if list, ok := in.(*unstructured.UnstructuredList); ok {
206 for i := range list.Items {
207 expectedGV := list.Items[i].GroupVersionKind().GroupVersion()
208 if !c.validVersions[expectedGV] {
209 return nil, fmt.Errorf("request to convert CR list failed, list index %d has invalid group/version: %s", i, expectedGV.String())
210 }
211 }
212 }
213 return c.converter.Convert(in, toGVK.GroupVersion())
214 }
215
216
217 type safeConverterWrapper struct {
218 unsafe runtime.ObjectConvertor
219 }
220
221 var _ runtime.ObjectConvertor = &safeConverterWrapper{}
222
223
224 func (c *safeConverterWrapper) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) {
225 return c.unsafe.ConvertFieldLabel(gvk, label, value)
226 }
227
228
229 func (c *safeConverterWrapper) Convert(in, out, context interface{}) error {
230 inObject, ok := in.(runtime.Object)
231 if !ok {
232 return fmt.Errorf("input type %T in not valid for object conversion", in)
233 }
234 return c.unsafe.Convert(inObject.DeepCopyObject(), out, context)
235 }
236
237
238 func (c *safeConverterWrapper) ConvertToVersion(in runtime.Object, target runtime.GroupVersioner) (runtime.Object, error) {
239 return c.unsafe.ConvertToVersion(in.DeepCopyObject(), target)
240 }
241
View as plain text