1
16
17 package conversion_test
18
19 import (
20 "bytes"
21 "encoding/json"
22 "io"
23 "net/http"
24 "net/http/httptest"
25
26 . "github.com/onsi/ginkgo/v2"
27 . "github.com/onsi/gomega"
28
29 appsv1beta1 "k8s.io/api/apps/v1beta1"
30 apix "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
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 kscheme "k8s.io/client-go/kubernetes/scheme"
35
36 "sigs.k8s.io/controller-runtime/pkg/webhook/conversion"
37 jobsv1 "sigs.k8s.io/controller-runtime/pkg/webhook/conversion/testdata/api/v1"
38 jobsv2 "sigs.k8s.io/controller-runtime/pkg/webhook/conversion/testdata/api/v2"
39 jobsv3 "sigs.k8s.io/controller-runtime/pkg/webhook/conversion/testdata/api/v3"
40 )
41
42 var _ = Describe("Conversion Webhook", func() {
43
44 var respRecorder *httptest.ResponseRecorder
45 var decoder *conversion.Decoder
46 var scheme *runtime.Scheme
47 var wh http.Handler
48
49 BeforeEach(func() {
50 respRecorder = &httptest.ResponseRecorder{
51 Body: bytes.NewBuffer(nil),
52 }
53
54 scheme = runtime.NewScheme()
55 Expect(kscheme.AddToScheme(scheme)).To(Succeed())
56 Expect(jobsv1.AddToScheme(scheme)).To(Succeed())
57 Expect(jobsv2.AddToScheme(scheme)).To(Succeed())
58 Expect(jobsv3.AddToScheme(scheme)).To(Succeed())
59
60 decoder = conversion.NewDecoder(scheme)
61 wh = conversion.NewWebhookHandler(scheme)
62 })
63
64 doRequest := func(convReq *apix.ConversionReview) *apix.ConversionReview {
65 var payload bytes.Buffer
66
67 Expect(json.NewEncoder(&payload).Encode(convReq)).Should(Succeed())
68
69 convReview := &apix.ConversionReview{}
70 req := &http.Request{
71 Body: io.NopCloser(bytes.NewReader(payload.Bytes())),
72 }
73 wh.ServeHTTP(respRecorder, req)
74 Expect(json.NewDecoder(respRecorder.Result().Body).Decode(convReview)).To(Succeed())
75 return convReview
76 }
77
78 makeV1Obj := func() *jobsv1.ExternalJob {
79 return &jobsv1.ExternalJob{
80 TypeMeta: metav1.TypeMeta{
81 Kind: "ExternalJob",
82 APIVersion: "jobs.testprojects.kb.io/v1",
83 },
84 ObjectMeta: metav1.ObjectMeta{
85 Namespace: "default",
86 Name: "obj-1",
87 },
88 Spec: jobsv1.ExternalJobSpec{
89 RunAt: "every 2 seconds",
90 },
91 }
92 }
93
94 makeV2Obj := func() *jobsv2.ExternalJob {
95 return &jobsv2.ExternalJob{
96 TypeMeta: metav1.TypeMeta{
97 Kind: "ExternalJob",
98 APIVersion: "jobs.testprojects.kb.io/v2",
99 },
100 ObjectMeta: metav1.ObjectMeta{
101 Namespace: "default",
102 Name: "obj-1",
103 },
104 Spec: jobsv2.ExternalJobSpec{
105 ScheduleAt: "every 2 seconds",
106 },
107 }
108 }
109
110 It("should convert spoke to hub successfully", func() {
111
112 v1Obj := makeV1Obj()
113
114 expected := &jobsv2.ExternalJob{
115 TypeMeta: metav1.TypeMeta{
116 Kind: "ExternalJob",
117 APIVersion: "jobs.testprojects.kb.io/v2",
118 },
119 ObjectMeta: metav1.ObjectMeta{
120 Namespace: "default",
121 Name: "obj-1",
122 },
123 Spec: jobsv2.ExternalJobSpec{
124 ScheduleAt: "every 2 seconds",
125 },
126 }
127
128 convReq := &apix.ConversionReview{
129 TypeMeta: metav1.TypeMeta{},
130 Request: &apix.ConversionRequest{
131 DesiredAPIVersion: "jobs.testprojects.kb.io/v2",
132 Objects: []runtime.RawExtension{
133 {
134 Object: v1Obj,
135 },
136 },
137 },
138 }
139
140 convReview := doRequest(convReq)
141
142 Expect(convReview.Response.ConvertedObjects).To(HaveLen(1))
143 Expect(convReview.Response.Result.Status).To(Equal(metav1.StatusSuccess))
144 got, _, err := decoder.Decode(convReview.Response.ConvertedObjects[0].Raw)
145 Expect(err).NotTo(HaveOccurred())
146 Expect(got).To(Equal(expected))
147 })
148
149 It("should convert hub to spoke successfully", func() {
150
151 v2Obj := makeV2Obj()
152
153 expected := &jobsv1.ExternalJob{
154 TypeMeta: metav1.TypeMeta{
155 Kind: "ExternalJob",
156 APIVersion: "jobs.testprojects.kb.io/v1",
157 },
158 ObjectMeta: metav1.ObjectMeta{
159 Namespace: "default",
160 Name: "obj-1",
161 },
162 Spec: jobsv1.ExternalJobSpec{
163 RunAt: "every 2 seconds",
164 },
165 }
166
167 convReq := &apix.ConversionReview{
168 TypeMeta: metav1.TypeMeta{},
169 Request: &apix.ConversionRequest{
170 DesiredAPIVersion: "jobs.testprojects.kb.io/v1",
171 Objects: []runtime.RawExtension{
172 {
173 Object: v2Obj,
174 },
175 },
176 },
177 }
178
179 convReview := doRequest(convReq)
180
181 Expect(convReview.Response.ConvertedObjects).To(HaveLen(1))
182 Expect(convReview.Response.Result.Status).To(Equal(metav1.StatusSuccess))
183 got, _, err := decoder.Decode(convReview.Response.ConvertedObjects[0].Raw)
184 Expect(err).NotTo(HaveOccurred())
185 Expect(got).To(Equal(expected))
186 })
187
188 It("should convert spoke to spoke successfully", func() {
189
190 v1Obj := makeV1Obj()
191
192 expected := &jobsv3.ExternalJob{
193 TypeMeta: metav1.TypeMeta{
194 Kind: "ExternalJob",
195 APIVersion: "jobs.testprojects.kb.io/v3",
196 },
197 ObjectMeta: metav1.ObjectMeta{
198 Namespace: "default",
199 Name: "obj-1",
200 },
201 Spec: jobsv3.ExternalJobSpec{
202 DeferredAt: "every 2 seconds",
203 },
204 }
205
206 convReq := &apix.ConversionReview{
207 TypeMeta: metav1.TypeMeta{},
208 Request: &apix.ConversionRequest{
209 DesiredAPIVersion: "jobs.testprojects.kb.io/v3",
210 Objects: []runtime.RawExtension{
211 {
212 Object: v1Obj,
213 },
214 },
215 },
216 }
217
218 convReview := doRequest(convReq)
219
220 Expect(convReview.Response.ConvertedObjects).To(HaveLen(1))
221 Expect(convReview.Response.Result.Status).To(Equal(metav1.StatusSuccess))
222 got, _, err := decoder.Decode(convReview.Response.ConvertedObjects[0].Raw)
223 Expect(err).NotTo(HaveOccurred())
224 Expect(got).To(Equal(expected))
225 })
226
227 It("should return error when dest/src objects belong to different API groups", func() {
228 v1Obj := makeV1Obj()
229
230 convReq := &apix.ConversionReview{
231 TypeMeta: metav1.TypeMeta{},
232 Request: &apix.ConversionRequest{
233
234 DesiredAPIVersion: "jobss.example.org/v2",
235 Objects: []runtime.RawExtension{
236 {
237 Object: v1Obj,
238 },
239 },
240 },
241 }
242
243 convReview := doRequest(convReq)
244 Expect(convReview.Response.Result.Status).To(Equal("Failure"))
245 Expect(convReview.Response.ConvertedObjects).To(BeEmpty())
246 })
247
248 It("should return error when dest/src objects are of same type", func() {
249
250 v1Obj := makeV1Obj()
251
252 convReq := &apix.ConversionReview{
253 TypeMeta: metav1.TypeMeta{},
254 Request: &apix.ConversionRequest{
255 DesiredAPIVersion: "jobs.testprojects.kb.io/v1",
256 Objects: []runtime.RawExtension{
257 {
258 Object: v1Obj,
259 },
260 },
261 },
262 }
263
264 convReview := doRequest(convReq)
265 Expect(convReview.Response.Result.Status).To(Equal("Failure"))
266 Expect(convReview.Response.ConvertedObjects).To(BeEmpty())
267 })
268
269 It("should return error when the API group does not have a hub defined", func() {
270
271 v1Obj := &appsv1beta1.Deployment{
272 TypeMeta: metav1.TypeMeta{
273 Kind: "Deployment",
274 APIVersion: "apps/v1beta1",
275 },
276 ObjectMeta: metav1.ObjectMeta{
277 Namespace: "default",
278 Name: "obj-1",
279 },
280 }
281
282 convReq := &apix.ConversionReview{
283 TypeMeta: metav1.TypeMeta{},
284 Request: &apix.ConversionRequest{
285 DesiredAPIVersion: "apps/v1",
286 Objects: []runtime.RawExtension{
287 {
288 Object: v1Obj,
289 },
290 },
291 },
292 }
293
294 convReview := doRequest(convReq)
295 Expect(convReview.Response.Result.Status).To(Equal("Failure"))
296 Expect(convReview.Response.ConvertedObjects).To(BeEmpty())
297 })
298
299 })
300
301 var _ = Describe("IsConvertible", func() {
302
303 var scheme *runtime.Scheme
304
305 BeforeEach(func() {
306 scheme = runtime.NewScheme()
307
308 Expect(kscheme.AddToScheme(scheme)).To(Succeed())
309 Expect(jobsv1.AddToScheme(scheme)).To(Succeed())
310 Expect(jobsv2.AddToScheme(scheme)).To(Succeed())
311 Expect(jobsv3.AddToScheme(scheme)).To(Succeed())
312 })
313
314 It("should not error for uninitialized types", func() {
315 obj := &jobsv2.ExternalJob{}
316
317 ok, err := conversion.IsConvertible(scheme, obj)
318 Expect(err).NotTo(HaveOccurred())
319 Expect(ok).To(BeTrue())
320 })
321
322 It("should not error for unstructured types", func() {
323 obj := &unstructured.Unstructured{
324 Object: map[string]interface{}{
325 "kind": "ExternalJob",
326 "apiVersion": "jobs.testprojects.kb.io/v2",
327 },
328 }
329
330 ok, err := conversion.IsConvertible(scheme, obj)
331 Expect(err).NotTo(HaveOccurred())
332 Expect(ok).To(BeTrue())
333 })
334
335 It("should return true for convertible types", func() {
336 obj := &jobsv2.ExternalJob{
337 TypeMeta: metav1.TypeMeta{
338 Kind: "ExternalJob",
339 APIVersion: "jobs.testprojects.kb.io/v2",
340 },
341 }
342
343 ok, err := conversion.IsConvertible(scheme, obj)
344 Expect(err).NotTo(HaveOccurred())
345 Expect(ok).To(BeTrue())
346 })
347
348 It("should return false for a non convertible type", func() {
349 obj := &appsv1beta1.Deployment{
350 TypeMeta: metav1.TypeMeta{
351 Kind: "Deployment",
352 APIVersion: "apps/v1beta1",
353 },
354 }
355
356 ok, err := conversion.IsConvertible(scheme, obj)
357 Expect(err).NotTo(HaveOccurred())
358 Expect(ok).ToNot(BeTrue())
359 })
360 })
361
View as plain text