1
16
17 package internal
18
19 import (
20 "fmt"
21
22 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
23 "k8s.io/apimachinery/pkg/runtime"
24 "k8s.io/apimachinery/pkg/runtime/schema"
25 "k8s.io/kube-openapi/pkg/schemaconv"
26 "k8s.io/kube-openapi/pkg/validation/spec"
27 smdschema "sigs.k8s.io/structured-merge-diff/v4/schema"
28 "sigs.k8s.io/structured-merge-diff/v4/typed"
29 "sigs.k8s.io/structured-merge-diff/v4/value"
30 )
31
32
33
34 type TypeConverter interface {
35 ObjectToTyped(runtime.Object, ...typed.ValidationOptions) (*typed.TypedValue, error)
36 TypedToObject(*typed.TypedValue) (runtime.Object, error)
37 }
38
39 type typeConverter struct {
40 parser map[schema.GroupVersionKind]*typed.ParseableType
41 }
42
43 var _ TypeConverter = &typeConverter{}
44
45 func NewTypeConverter(openapiSpec map[string]*spec.Schema, preserveUnknownFields bool) (TypeConverter, error) {
46 typeSchema, err := schemaconv.ToSchemaFromOpenAPI(openapiSpec, preserveUnknownFields)
47 if err != nil {
48 return nil, fmt.Errorf("failed to convert models to schema: %v", err)
49 }
50
51 typeParser := typed.Parser{Schema: smdschema.Schema{Types: typeSchema.Types}}
52 tr := indexModels(&typeParser, openapiSpec)
53
54 return &typeConverter{parser: tr}, nil
55 }
56
57 func (c *typeConverter) ObjectToTyped(obj runtime.Object, opts ...typed.ValidationOptions) (*typed.TypedValue, error) {
58 gvk := obj.GetObjectKind().GroupVersionKind()
59 t := c.parser[gvk]
60 if t == nil {
61 return nil, NewNoCorrespondingTypeError(gvk)
62 }
63 switch o := obj.(type) {
64 case *unstructured.Unstructured:
65 return t.FromUnstructured(o.UnstructuredContent(), opts...)
66 default:
67 return t.FromStructured(obj, opts...)
68 }
69 }
70
71 func (c *typeConverter) TypedToObject(value *typed.TypedValue) (runtime.Object, error) {
72 return valueToObject(value.AsValue())
73 }
74
75 type deducedTypeConverter struct{}
76
77
78
79
80
81
82 func NewDeducedTypeConverter() TypeConverter {
83 return deducedTypeConverter{}
84 }
85
86
87 func (deducedTypeConverter) ObjectToTyped(obj runtime.Object, opts ...typed.ValidationOptions) (*typed.TypedValue, error) {
88 switch o := obj.(type) {
89 case *unstructured.Unstructured:
90 return typed.DeducedParseableType.FromUnstructured(o.UnstructuredContent(), opts...)
91 default:
92 return typed.DeducedParseableType.FromStructured(obj, opts...)
93 }
94 }
95
96
97
98 func (deducedTypeConverter) TypedToObject(value *typed.TypedValue) (runtime.Object, error) {
99 return valueToObject(value.AsValue())
100 }
101
102 func valueToObject(val value.Value) (runtime.Object, error) {
103 vu := val.Unstructured()
104 switch o := vu.(type) {
105 case map[string]interface{}:
106 return &unstructured.Unstructured{Object: o}, nil
107 default:
108 return nil, fmt.Errorf("failed to convert value to unstructured for type %T", vu)
109 }
110 }
111
112 func indexModels(
113 typeParser *typed.Parser,
114 openAPISchemas map[string]*spec.Schema,
115 ) map[schema.GroupVersionKind]*typed.ParseableType {
116 tr := map[schema.GroupVersionKind]*typed.ParseableType{}
117 for modelName, model := range openAPISchemas {
118 gvkList := parseGroupVersionKind(model.Extensions)
119 if len(gvkList) == 0 {
120 continue
121 }
122
123 parsedType := typeParser.Type(modelName)
124 for _, gvk := range gvkList {
125 if len(gvk.Kind) > 0 {
126 tr[schema.GroupVersionKind(gvk)] = &parsedType
127 }
128 }
129 }
130 return tr
131 }
132
133
134 func parseGroupVersionKind(extensions map[string]interface{}) []schema.GroupVersionKind {
135 gvkListResult := []schema.GroupVersionKind{}
136
137
138 gvkExtension, ok := extensions["x-kubernetes-group-version-kind"]
139 if !ok {
140 return []schema.GroupVersionKind{}
141 }
142
143
144 gvkList, ok := gvkExtension.([]interface{})
145 if !ok {
146 return []schema.GroupVersionKind{}
147 }
148
149 for _, gvk := range gvkList {
150 var group, version, kind string
151
152
153
154 if gvkMap, ok := gvk.(map[interface{}]interface{}); ok {
155 group, ok = gvkMap["group"].(string)
156 if !ok {
157 continue
158 }
159 version, ok = gvkMap["version"].(string)
160 if !ok {
161 continue
162 }
163 kind, ok = gvkMap["kind"].(string)
164 if !ok {
165 continue
166 }
167
168 } else if gvkMap, ok := gvk.(map[string]interface{}); ok {
169 group, ok = gvkMap["group"].(string)
170 if !ok {
171 continue
172 }
173 version, ok = gvkMap["version"].(string)
174 if !ok {
175 continue
176 }
177 kind, ok = gvkMap["kind"].(string)
178 if !ok {
179 continue
180 }
181 } else {
182 continue
183 }
184
185 gvkListResult = append(gvkListResult, schema.GroupVersionKind{
186 Group: group,
187 Version: version,
188 Kind: kind,
189 })
190 }
191
192 return gvkListResult
193 }
194
View as plain text