/
conversion.go
122 lines (103 loc) · 3.4 KB
/
conversion.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package conversion
import (
"encoding/json"
"fmt"
"net/http"
"github.com/sirupsen/logrus"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// Converter is a interface to convert object to the desired version
type Converter interface {
GroupResource() schema.GroupResource
Convert(Object *unstructured.Unstructured, desiredAPIVersion string) (*unstructured.Unstructured, error)
}
// Handler is a http handler for multiple converters
type Handler struct {
restMapper meta.RESTMapper
converters map[schema.GroupResource]Converter
}
func NewHandler(converters []Converter, restMapper meta.RESTMapper) *Handler {
h := &Handler{
restMapper: restMapper,
converters: map[schema.GroupResource]Converter{},
}
for _, c := range converters {
h.converters[c.GroupResource()] = c
}
return h
}
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
review := &apiextensionsv1.ConversionReview{}
err := json.NewDecoder(r.Body).Decode(review)
if err != nil {
sendError(w, review, err)
return
}
if review.Request == nil {
sendError(w, review, fmt.Errorf("request is not set"))
return
}
review.Response = h.doConversion(review.Request)
review.Request = nil
writeResponse(w, review)
}
func (h *Handler) doConversion(request *apiextensionsv1.ConversionRequest) *apiextensionsv1.ConversionResponse {
response := &apiextensionsv1.ConversionResponse{
UID: request.UID,
}
for _, obj := range request.Objects {
convertedObj, err := h.convertObject(&obj, request.DesiredAPIVersion)
if err != nil {
response.Result = errors.NewInternalError(err).ErrStatus
return response
}
response.ConvertedObjects = append(response.ConvertedObjects, *convertedObj)
}
response.Result = metav1.Status{
Status: metav1.StatusSuccess,
}
return response
}
func (h *Handler) convertObject(obj *runtime.RawExtension, desiredAPIVersion string) (*runtime.RawExtension, error) {
cr := &unstructured.Unstructured{}
if err := cr.UnmarshalJSON(obj.Raw); err != nil {
return nil, err
}
// use RESTMapping to get the group resource of the object
mapper, err := h.restMapper.RESTMapping(cr.GroupVersionKind().GroupKind())
if err != nil {
return nil, err
}
groupResource := mapper.Resource.GroupResource()
converter, ok := h.converters[groupResource]
if !ok {
return nil, fmt.Errorf("converter for %s is not existing", groupResource.String())
}
convertedObj, err := converter.Convert(cr, desiredAPIVersion)
if err != nil {
return nil, err
}
convertedObj.SetAPIVersion(desiredAPIVersion)
return &runtime.RawExtension{Object: convertedObj}, nil
}
func writeResponse(w http.ResponseWriter, review *apiextensionsv1.ConversionReview) {
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(review); err != nil {
logrus.Errorf("encode review failed, review: %+v, error: %v", review, err)
}
}
func sendError(w http.ResponseWriter, review *apiextensionsv1.ConversionReview, err error) {
logrus.Error(err)
if review == nil || review.Request == nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
review.Response.Result = errors.NewInternalError(err).ErrStatus
writeResponse(w, review)
}