sigs.k8s.io/controller-runtime@v0.18.2/pkg/webhook/conversion/conversion_test.go (about) 1 /* 2 Copyright 2018 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 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 // request conversion for different group 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 })