1
16
17 package converter
18
19 import (
20 "fmt"
21 "io"
22 "net/http"
23 "strings"
24
25 "github.com/munnerz/goautoneg"
26
27 "k8s.io/klog/v2"
28
29 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
30 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
33 "k8s.io/apimachinery/pkg/runtime"
34 "k8s.io/apimachinery/pkg/runtime/serializer/json"
35 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
36 )
37
38
39
40 type convertFunc func(Object *unstructured.Unstructured, version string) (*unstructured.Unstructured, metav1.Status)
41
42 func statusErrorWithMessage(msg string, params ...interface{}) metav1.Status {
43 return metav1.Status{
44 Message: fmt.Sprintf(msg, params...),
45 Status: metav1.StatusFailure,
46 }
47 }
48
49 func statusSucceed() metav1.Status {
50 return metav1.Status{
51 Status: metav1.StatusSuccess,
52 }
53 }
54
55
56
57 func doConversionV1beta1(convertRequest *v1beta1.ConversionRequest, convert convertFunc) *v1beta1.ConversionResponse {
58 var convertedObjects []runtime.RawExtension
59 for _, obj := range convertRequest.Objects {
60 cr := unstructured.Unstructured{}
61 if err := cr.UnmarshalJSON(obj.Raw); err != nil {
62 klog.Error(err)
63 return &v1beta1.ConversionResponse{
64 Result: metav1.Status{
65 Message: fmt.Sprintf("failed to unmarshall object (%v) with error: %v", string(obj.Raw), err),
66 Status: metav1.StatusFailure,
67 },
68 }
69 }
70 convertedCR, status := convert(&cr, convertRequest.DesiredAPIVersion)
71 if status.Status != metav1.StatusSuccess {
72 klog.Error(status.String())
73 return &v1beta1.ConversionResponse{
74 Result: status,
75 }
76 }
77 convertedCR.SetAPIVersion(convertRequest.DesiredAPIVersion)
78 convertedObjects = append(convertedObjects, runtime.RawExtension{Object: convertedCR})
79 }
80 return &v1beta1.ConversionResponse{
81 ConvertedObjects: convertedObjects,
82 Result: statusSucceed(),
83 }
84 }
85
86
87
88 func doConversionV1(convertRequest *v1.ConversionRequest, convert convertFunc) *v1.ConversionResponse {
89 var convertedObjects []runtime.RawExtension
90 for _, obj := range convertRequest.Objects {
91 cr := unstructured.Unstructured{}
92 if err := cr.UnmarshalJSON(obj.Raw); err != nil {
93 klog.Error(err)
94 return &v1.ConversionResponse{
95 Result: metav1.Status{
96 Message: fmt.Sprintf("failed to unmarshall object (%v) with error: %v", string(obj.Raw), err),
97 Status: metav1.StatusFailure,
98 },
99 }
100 }
101 convertedCR, status := convert(&cr, convertRequest.DesiredAPIVersion)
102 if status.Status != metav1.StatusSuccess {
103 klog.Error(status.String())
104 return &v1.ConversionResponse{
105 Result: status,
106 }
107 }
108 convertedCR.SetAPIVersion(convertRequest.DesiredAPIVersion)
109 convertedObjects = append(convertedObjects, runtime.RawExtension{Object: convertedCR})
110 }
111 return &v1.ConversionResponse{
112 ConvertedObjects: convertedObjects,
113 Result: statusSucceed(),
114 }
115 }
116
117 func serve(w http.ResponseWriter, r *http.Request, convert convertFunc) {
118 var body []byte
119 if r.Body != nil {
120 if data, err := io.ReadAll(r.Body); err == nil {
121 body = data
122 }
123 }
124
125 contentType := r.Header.Get("Content-Type")
126 serializer := getInputSerializer(contentType)
127 if serializer == nil {
128 msg := fmt.Sprintf("invalid Content-Type header `%s`", contentType)
129 klog.Errorf(msg)
130 http.Error(w, msg, http.StatusBadRequest)
131 return
132 }
133
134 klog.V(2).Infof("handling request: %v", body)
135 obj, gvk, err := serializer.Decode(body, nil, nil)
136 if err != nil {
137 msg := fmt.Sprintf("failed to deserialize body (%v) with error %v", string(body), err)
138 klog.Error(err)
139 http.Error(w, msg, http.StatusBadRequest)
140 return
141 }
142
143 var responseObj runtime.Object
144 switch *gvk {
145 case v1beta1.SchemeGroupVersion.WithKind("ConversionReview"):
146 convertReview, ok := obj.(*v1beta1.ConversionReview)
147 if !ok {
148 msg := fmt.Sprintf("Expected v1beta1.ConversionReview but got: %T", obj)
149 klog.Errorf(msg)
150 http.Error(w, msg, http.StatusBadRequest)
151 return
152 }
153 convertReview.Response = doConversionV1beta1(convertReview.Request, convert)
154 convertReview.Response.UID = convertReview.Request.UID
155 klog.V(2).Info(fmt.Sprintf("sending response: %v", convertReview.Response))
156
157
158 convertReview.Request = &v1beta1.ConversionRequest{}
159 responseObj = convertReview
160 case v1.SchemeGroupVersion.WithKind("ConversionReview"):
161 convertReview, ok := obj.(*v1.ConversionReview)
162 if !ok {
163 msg := fmt.Sprintf("Expected v1.ConversionReview but got: %T", obj)
164 klog.Errorf(msg)
165 http.Error(w, msg, http.StatusBadRequest)
166 return
167 }
168 convertReview.Response = doConversionV1(convertReview.Request, convert)
169 convertReview.Response.UID = convertReview.Request.UID
170 klog.V(2).Info(fmt.Sprintf("sending response: %v", convertReview.Response))
171
172
173 convertReview.Request = &v1.ConversionRequest{}
174 responseObj = convertReview
175 default:
176 msg := fmt.Sprintf("Unsupported group version kind: %v", gvk)
177 klog.Error(err)
178 http.Error(w, msg, http.StatusBadRequest)
179 return
180 }
181
182 accept := r.Header.Get("Accept")
183 outSerializer := getOutputSerializer(accept)
184 if outSerializer == nil {
185 msg := fmt.Sprintf("invalid accept header `%s`", accept)
186 klog.Errorf(msg)
187 http.Error(w, msg, http.StatusBadRequest)
188 return
189 }
190 err = outSerializer.Encode(responseObj, w)
191 if err != nil {
192 klog.Error(err)
193 http.Error(w, err.Error(), http.StatusInternalServerError)
194 return
195 }
196 }
197
198
199 func ServeExampleConvert(w http.ResponseWriter, r *http.Request) {
200 serve(w, r, convertExampleCRD)
201 }
202
203 type mediaType struct {
204 Type, SubType string
205 }
206
207 var scheme = runtime.NewScheme()
208
209 func init() {
210 addToScheme(scheme)
211 }
212
213 func addToScheme(scheme *runtime.Scheme) {
214 utilruntime.Must(v1.AddToScheme(scheme))
215 utilruntime.Must(v1beta1.AddToScheme(scheme))
216 }
217
218 var serializers = map[mediaType]runtime.Serializer{
219 {"application", "json"}: json.NewSerializerWithOptions(json.DefaultMetaFactory, scheme, scheme, json.SerializerOptions{Pretty: false}),
220 {"application", "yaml"}: json.NewSerializerWithOptions(json.DefaultMetaFactory, scheme, scheme, json.SerializerOptions{Yaml: true}),
221 }
222
223 func getInputSerializer(contentType string) runtime.Serializer {
224 parts := strings.SplitN(contentType, "/", 2)
225 if len(parts) != 2 {
226 return nil
227 }
228 return serializers[mediaType{parts[0], parts[1]}]
229 }
230
231 func getOutputSerializer(accept string) runtime.Serializer {
232 if len(accept) == 0 {
233 return serializers[mediaType{"application", "json"}]
234 }
235
236 clauses := goautoneg.ParseAccept(accept)
237 for _, clause := range clauses {
238 for k, v := range serializers {
239 switch {
240 case clause.Type == k.Type && clause.SubType == k.SubType,
241 clause.Type == k.Type && clause.SubType == "*",
242 clause.Type == "*" && clause.SubType == "*":
243 return v
244 }
245 }
246 }
247
248 return nil
249 }
250
View as plain text