github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/service/runtime/kubernetes/client/client_test.go (about) 1 package client 2 3 import ( 4 "io/ioutil" 5 "net/http" 6 "strings" 7 "testing" 8 9 "github.com/tickoalcantara12/micro/v3/service/runtime" 10 11 "github.com/tickoalcantara12/micro/v3/service/runtime/kubernetes/api" 12 "github.com/tickoalcantara12/micro/v3/test/fakes" 13 14 . "github.com/onsi/gomega" 15 ) 16 17 func TestCreate(t *testing.T) { 18 tcs := []struct { 19 name string 20 namespace string 21 resource *Resource 22 expectedBody string 23 expectedURL string 24 }{ 25 { 26 name: "deployment", 27 namespace: "foo-bar-baz", 28 resource: NewDeployment(&runtime.Service{ 29 Name: "svc1", 30 Version: "latest", 31 Source: "source", 32 Metadata: map[string]string{"foo": "bar", "hello": "world"}, 33 }, &runtime.CreateOptions{ 34 Command: []string{"cmd", "arg"}, 35 Args: []string{"arg1", "arg2"}, 36 Env: []string{"FOO=BAR", "HELLO=WORLD"}, 37 Type: "service", 38 Image: "DefaultImage", 39 Namespace: DefaultNamespace, 40 Resources: &runtime.Resources{ 41 CPU: 200, 42 Mem: 200, 43 Disk: 2000, 44 }, 45 ServiceAccount: "serviceAcc", 46 }, 47 ), 48 49 expectedBody: ` 50 apiVersion: apps/v1 51 kind: Deployment 52 metadata: 53 name: "svc1-latest" 54 namespace: "default" 55 labels: 56 micro: "service" 57 name: "svc1" 58 version: "latest" 59 annotations: 60 foo: "bar" 61 hello: "world" 62 name: "svc1" 63 source: "source" 64 version: "latest" 65 spec: 66 replicas: 1 67 selector: 68 matchLabels: 69 micro: "service" 70 name: "svc1" 71 version: "latest" 72 template: 73 metadata: 74 labels: 75 micro: "service" 76 name: "svc1" 77 version: "latest" 78 annotations: 79 foo: "bar" 80 hello: "world" 81 name: "svc1" 82 source: "source" 83 version: "latest" 84 spec: 85 runtimeClassName: 86 serviceAccountName: serviceAcc 87 containers: 88 - name: svc1 89 env: 90 - name: "FOO" 91 value: "BAR" 92 - name: "HELLO" 93 value: "WORLD" 94 args: 95 - arg1 96 - arg2 97 command: 98 - cmd 99 - arg 100 image: DefaultImage 101 imagePullPolicy: IfNotPresent 102 ports: 103 - containerPort: 8080 104 name: service-port 105 readinessProbe: 106 tcpSocket: 107 port: 8080 108 initialDelaySeconds: 10 109 periodSeconds: 10 110 resources: 111 limits: 112 memory: 200Mi 113 cpu: 200m 114 ephemeral-storage: 2000Mi 115 volumeMounts: 116 volumes:`, 117 expectedURL: `example.com/apis/apps/v1/namespaces/foo-bar-baz/deployments/`, 118 }, 119 { 120 name: "service", 121 namespace: "foo-bar-baz", 122 resource: NewService(&runtime.Service{ 123 Name: "svc1", 124 Version: "latest", 125 Source: "source", 126 Metadata: map[string]string{"foo": "bar", "hello": "world"}, 127 }, &runtime.CreateOptions{ 128 Command: []string{"cmd", "arg"}, 129 Args: []string{"arg1", "arg2"}, 130 Env: []string{"FOO=BAR", "HELLO=WORLD"}, 131 Type: "service", 132 Image: "DefaultImage", 133 Namespace: DefaultNamespace, 134 Resources: &runtime.Resources{ 135 CPU: 200, 136 Mem: 200, 137 Disk: 2000, 138 }, 139 ServiceAccount: "serviceAcc", 140 }), 141 expectedBody: ` 142 apiVersion: v1 143 kind: Service 144 metadata: 145 name: "svc1" 146 namespace: "default" 147 labels: 148 micro: "service" 149 name: "svc1" 150 version: "latest" 151 spec: 152 selector: 153 micro: "service" 154 name: "svc1" 155 version: "latest" 156 ports: 157 - name: "service-port" 158 port: 8080 159 protocol: 160 `, 161 expectedURL: "example.com/api/v1/namespaces/foo-bar-baz/services/", 162 }, 163 { 164 name: "secrets", 165 namespace: "foo-bar-baz", 166 resource: &Resource{ 167 Kind: "secret", 168 Name: "svc1", 169 Value: &Secret{ 170 Type: "Opaque", 171 Data: map[string]string{ 172 "key1": "val1", 173 "key2": "val2", 174 }, 175 Metadata: &Metadata{ 176 Name: "svc1", 177 Namespace: "foo-bar-baz", 178 }, 179 }, 180 }, 181 expectedBody: ` 182 apiVersion: v1 183 kind: Secret 184 type: "Opaque" 185 metadata: 186 name: "svc1" 187 namespace: "foo-bar-baz" 188 labels: 189 data: 190 key1: "val1" 191 key2: "val2" 192 `, 193 expectedURL: "example.com/api/v1/namespaces/foo-bar-baz/secrets/", 194 }, 195 { 196 name: "serviceaccount", 197 namespace: "foo-bar-baz", 198 resource: &Resource{ 199 Name: "svcacc", 200 Kind: "serviceaccount", 201 Value: &ServiceAccount{ 202 Metadata: &Metadata{ 203 Name: "svcacc", 204 Namespace: "foo-bar-baz", 205 }, 206 ImagePullSecrets: []ImagePullSecret{ 207 { 208 Name: "pullme", 209 }, 210 }, 211 }, 212 }, 213 expectedBody: ` 214 apiVersion: v1 215 kind: ServiceAccount 216 metadata: 217 name: "svcacc" 218 labels: 219 imagePullSecrets: 220 - name: "pullme" 221 `, 222 expectedURL: "example.com/api/v1/namespaces/foo-bar-baz/serviceaccounts/", 223 }, 224 { 225 name: "networkpolicy", 226 namespace: "foo-bar-baz", 227 resource: &Resource{ 228 Kind: "networkpolicy", 229 Value: NewNetworkPolicy("np1", "foo-bar-baz", map[string]string{ 230 "foo": "bar", 231 "hello": "world", 232 })}, 233 expectedBody: ` 234 apiVersion: networking.k8s.io/v1 235 kind: NetworkPolicy 236 metadata: 237 name: "np1" 238 namespace: "foo-bar-baz" 239 labels: 240 spec: 241 podSelector: 242 matchLabels: 243 ingress: 244 - from: # Allow pods in this namespace to talk to each other 245 - podSelector: {} 246 - from: # Allow pods in the namespaces bearing the specified labels to talk to pods in this namespace: 247 - namespaceSelector: 248 matchLabels: 249 foo: "bar" 250 hello: "world"`, 251 expectedURL: "example.com/apis/networking.k8s.io/v1/namespaces/foo-bar-baz/networkpolicies/", 252 }, 253 { 254 name: "resourcequota", 255 namespace: "foo-bar-baz", 256 resource: &Resource{ 257 Kind: "resourcequota", 258 Value: NewResourceQuota(&runtime.ResourceQuota{ 259 Name: "rq1", 260 Namespace: "foo-bar-baz", 261 Requests: &runtime.Resources{ 262 CPU: 1000, 263 Mem: 2000, 264 Disk: 3000, 265 }, 266 Limits: &runtime.Resources{ 267 CPU: 4000, 268 Mem: 5000, 269 Disk: 6000, 270 }, 271 }), 272 }, 273 expectedBody: ` 274 apiVersion: v1 275 kind: ResourceQuota 276 metadata: 277 name: "rq1" 278 namespace: "foo-bar-baz" 279 labels: 280 spec: 281 hard: 282 limits.memory: 5000Mi 283 limits.cpu: 4000m 284 limits.ephemeral-storage: 6000Mi 285 requests.memory: 2000Mi 286 requests.cpu: 1000m 287 requests.ephemeral-storage: 3000Mi 288 `, 289 expectedURL: "example.com/api/v1/namespaces/foo-bar-baz/resourcequotas/", 290 }, 291 } 292 for _, tc := range tcs { 293 t.Run(tc.name, func(t *testing.T) { 294 rt := fakes.FakeRoundTripper{} 295 httpClient := &http.Client{ 296 Transport: &rt, 297 } 298 client := &client{ 299 opts: &api.Options{ 300 Host: "example.com", 301 Namespace: DefaultNamespace, 302 Client: httpClient, 303 }, 304 } 305 rt.RoundTripReturns(&http.Response{StatusCode: 200}, nil) 306 307 g := NewWithT(t) 308 err := client.Create(tc.resource, CreateNamespace(tc.namespace)) 309 g.Expect(err).ToNot(HaveOccurred()) 310 req := rt.RoundTripArgsForCall(0) 311 g.Expect(req.URL.String()).To(Equal(tc.expectedURL)) 312 b, err := ioutil.ReadAll(req.Body) 313 g.Expect(err).ToNot(HaveOccurred()) 314 g.Expect(strings.TrimSpace(string(b))).To(Equal(strings.TrimSpace(tc.expectedBody))) 315 g.Expect(req.Method).To(Equal(http.MethodPost)) 316 g.Expect(req.Header.Get("Content-Type")).To(Equal("application/yaml")) 317 }) 318 } 319 320 } 321 322 func TestUpdate(t *testing.T) { 323 tcs := []struct { 324 name string 325 namespace string 326 resource *Resource 327 expectedBody string 328 expectedURL string 329 expectedErr bool 330 }{ 331 { 332 name: "deployment", 333 namespace: "foo-bar-baz", 334 resource: NewDeployment(&runtime.Service{ 335 Name: "svc1", 336 Version: "latest", 337 Source: "source", 338 Metadata: map[string]string{"foo": "bar", "hello": "world"}, 339 }, &runtime.CreateOptions{ 340 Command: []string{"cmd", "arg"}, 341 Args: []string{"arg1", "arg2"}, 342 Env: []string{"FOO=BAR", "HELLO=WORLD"}, 343 Type: "service", 344 Image: "DefaultImage", 345 Namespace: DefaultNamespace, 346 Resources: &runtime.Resources{ 347 CPU: 200, 348 Mem: 200, 349 Disk: 2000, 350 }, 351 ServiceAccount: "serviceAcc", 352 }, 353 ), 354 355 expectedBody: `{"metadata":{"name":"svc1-latest","namespace":"default","version":"latest","labels":{"micro":"service","name":"svc1","version":"latest"},"annotations":{"foo":"bar","hello":"world","name":"svc1","source":"source","version":"latest"}},"spec":{"replicas":1,"selector":{"matchLabels":{"micro":"service","name":"svc1","version":"latest"}},"template":{"metadata":{"name":"svc1-latest","namespace":"default","version":"latest","labels":{"micro":"service","name":"svc1","version":"latest"},"annotations":{"foo":"bar","hello":"world","name":"svc1","source":"source","version":"latest"}},"spec":{"containers":[{"name":"svc1","image":"DefaultImage","env":[{"name":"FOO","value":"BAR"},{"name":"HELLO","value":"WORLD"}],"command":["cmd","arg"],"args":["arg1","arg2"],"ports":[{"name":"service-port","containerPort":8080}],"readinessProbe":{"tcpSocket":{"port":8080},"periodSeconds":10,"initialDelaySeconds":10},"resources":{"limits":{"memory":"200Mi","cpu":"200m","ephemeral-storage":"2000Mi"}}}],"serviceAccountName":"serviceAcc"}}}}`, 356 expectedURL: `example.com/apis/apps/v1/namespaces/foo-bar-baz/deployments/svc1-latest`, 357 }, 358 { 359 name: "service", 360 namespace: "foo-bar-baz", 361 resource: NewService(&runtime.Service{ 362 Name: "svc1", 363 Version: "latest", 364 Source: "source", 365 Metadata: map[string]string{"foo": "bar", "hello": "world"}, 366 }, &runtime.CreateOptions{ 367 Command: []string{"cmd", "arg"}, 368 Args: []string{"arg1", "arg2"}, 369 Env: []string{"FOO=BAR", "HELLO=WORLD"}, 370 Type: "service", 371 Image: "DefaultImage", 372 Namespace: DefaultNamespace, 373 Resources: &runtime.Resources{ 374 CPU: 200, 375 Mem: 200, 376 Disk: 2000, 377 }, 378 ServiceAccount: "serviceAcc", 379 }), 380 expectedBody: `{"metadata":{"name":"svc1","namespace":"default","version":"latest","labels":{"micro":"service","name":"svc1","version":"latest"}},"spec":{"clusterIP":"","type":"ClusterIP","selector":{"micro":"service","name":"svc1","version":"latest"},"ports":[{"name":"service-port","port":8080}]}}`, 381 expectedURL: "example.com/api/v1/namespaces/foo-bar-baz/services/svc1", 382 }, 383 { 384 name: "secrets", 385 namespace: "foo-bar-baz", 386 resource: &Resource{ 387 Kind: "secret", 388 Name: "svc1", 389 Value: &Secret{ 390 Type: "Opaque", 391 Data: map[string]string{ 392 "key1": "val1", 393 "key2": "val2", 394 }, 395 Metadata: &Metadata{ 396 Name: "svc1", 397 Namespace: "foo-bar-baz", 398 }, 399 }, 400 }, 401 expectedBody: ``, 402 expectedURL: "example.com/api/v1/namespaces/foo-bar-baz/secrets/svc1", 403 expectedErr: true, 404 }, 405 { 406 name: "serviceaccount", 407 namespace: "foo-bar-baz", 408 resource: &Resource{ 409 Name: "svcacc", 410 Kind: "serviceaccount", 411 Value: &ServiceAccount{ 412 Metadata: &Metadata{ 413 Name: "svcacc", 414 Namespace: "foo-bar-baz", 415 }, 416 ImagePullSecrets: []ImagePullSecret{ 417 { 418 Name: "pullme", 419 }, 420 }, 421 }, 422 }, 423 expectedBody: ``, 424 expectedURL: "example.com/api/v1/namespaces/foo-bar-baz/serviceaccounts/svcacc", 425 expectedErr: true, 426 }, 427 { 428 name: "networkpolicy", 429 namespace: "foo-bar-baz", 430 resource: &Resource{ 431 Name: "np1", 432 Kind: "networkpolicy", 433 Value: NewNetworkPolicy("np1", "foo-bar-baz", map[string]string{ 434 "foo": "bar", 435 "hello": "world", 436 })}, 437 expectedBody: `{"metadata":{"name":"np1","namespace":"foo-bar-baz"},"spec":{"ingress":[{"from":[{"podSelector":{}}]},{"from":[{"namespaceSelector":{"matchLabels":{"foo":"bar","hello":"world"}}}]}],"podSelector":{},"policyTypes":["Ingress"]}}`, 438 expectedURL: "example.com/apis/networking.k8s.io/v1/namespaces/foo-bar-baz/networkpolicies/np1", 439 }, 440 { 441 name: "resourcequota", 442 namespace: "foo-bar-baz", 443 resource: &Resource{ 444 Name: "rq1", 445 Kind: "resourcequota", 446 Value: NewResourceQuota(&runtime.ResourceQuota{ 447 Name: "rq1", 448 Namespace: "foo-bar-baz", 449 Requests: &runtime.Resources{ 450 CPU: 1000, 451 Mem: 2000, 452 Disk: 3000, 453 }, 454 Limits: &runtime.Resources{ 455 CPU: 4000, 456 Mem: 5000, 457 Disk: 6000, 458 }, 459 }), 460 }, 461 expectedBody: `{"metadata":{"name":"rq1","namespace":"foo-bar-baz"},"spec":{"hard":{"limits.cpu":"4000m","limits.ephemeral-storage":"6000Mi","limits.memory":"5000Mi","requests.cpu":"1000m","requests.ephemeral-storage":"3000Mi","requests.memory":"2000Mi"}}}`, 462 expectedURL: "example.com/api/v1/namespaces/foo-bar-baz/resourcequotas/rq1", 463 }, 464 } 465 466 for _, tc := range tcs { 467 t.Run(tc.name, func(t *testing.T) { 468 rt := fakes.FakeRoundTripper{} 469 httpClient := &http.Client{ 470 Transport: &rt, 471 } 472 client := &client{ 473 opts: &api.Options{ 474 Host: "example.com", 475 Namespace: DefaultNamespace, 476 Client: httpClient, 477 }, 478 } 479 rt.RoundTripReturns(&http.Response{StatusCode: 200}, nil) 480 481 g := NewWithT(t) 482 err := client.Update(tc.resource, UpdateNamespace(tc.namespace)) 483 if tc.expectedErr { 484 // these ones haven't been implemented yet 485 g.Expect(err).To(HaveOccurred()) 486 return 487 } 488 g.Expect(err).ToNot(HaveOccurred()) 489 req := rt.RoundTripArgsForCall(0) 490 g.Expect(req.URL.String()).To(Equal(tc.expectedURL)) 491 b, err := ioutil.ReadAll(req.Body) 492 g.Expect(err).ToNot(HaveOccurred()) 493 g.Expect(strings.TrimSpace(string(b))).To(Equal(strings.TrimSpace(tc.expectedBody))) 494 g.Expect(req.Method).To(Equal(http.MethodPatch)) 495 g.Expect(req.Header.Get("Content-Type")).To(Equal("application/strategic-merge-patch+json")) 496 }) 497 } 498 } 499 500 func TestDelete(t *testing.T) { 501 tcs := []struct { 502 name string 503 namespace string 504 resource *Resource 505 expectedURL string 506 }{ 507 { 508 name: "deployment", 509 namespace: "foo-bar-baz", 510 resource: &Resource{ 511 Name: "svc1-latest", 512 Kind: "deployment", 513 }, 514 expectedURL: `example.com/apis/apps/v1/namespaces/foo-bar-baz/deployments/svc1-latest`, 515 }, 516 { 517 name: "service", 518 namespace: "foo-bar-baz", 519 resource: &Resource{ 520 Name: "svc1", 521 Kind: "service", 522 }, 523 expectedURL: "example.com/api/v1/namespaces/foo-bar-baz/services/svc1", 524 }, 525 { 526 name: "secrets", 527 namespace: "foo-bar-baz", 528 resource: &Resource{ 529 Kind: "secret", 530 Name: "svc1", 531 }, 532 expectedURL: "example.com/api/v1/namespaces/foo-bar-baz/secrets/svc1", 533 }, 534 { 535 name: "serviceaccount", 536 namespace: "foo-bar-baz", 537 resource: &Resource{ 538 Name: "svcacc", 539 Kind: "serviceaccount", 540 }, 541 expectedURL: "example.com/api/v1/namespaces/foo-bar-baz/serviceaccounts/svcacc", 542 }, 543 { 544 name: "networkpolicy", 545 namespace: "foo-bar-baz", 546 resource: &Resource{ 547 Name: "np1", 548 Kind: "networkpolicy", 549 }, 550 expectedURL: "example.com/apis/networking.k8s.io/v1/namespaces/foo-bar-baz/networkpolicies/np1", 551 }, 552 { 553 name: "resourcequota", 554 namespace: "foo-bar-baz", 555 resource: &Resource{ 556 Name: "rq1", 557 Kind: "resourcequota", 558 }, 559 expectedURL: "example.com/api/v1/namespaces/foo-bar-baz/resourcequotas/rq1", 560 }, 561 } 562 563 for _, tc := range tcs { 564 t.Run(tc.name, func(t *testing.T) { 565 rt := fakes.FakeRoundTripper{} 566 httpClient := &http.Client{ 567 Transport: &rt, 568 } 569 client := &client{ 570 opts: &api.Options{ 571 Host: "example.com", 572 Namespace: DefaultNamespace, 573 Client: httpClient, 574 }, 575 } 576 rt.RoundTripReturns(&http.Response{StatusCode: 200}, nil) 577 578 g := NewWithT(t) 579 err := client.Delete(tc.resource, DeleteNamespace(tc.namespace)) 580 g.Expect(err).ToNot(HaveOccurred()) 581 req := rt.RoundTripArgsForCall(0) 582 g.Expect(req.URL.String()).To(Equal(tc.expectedURL)) 583 g.Expect(req.Method).To(Equal(http.MethodDelete)) 584 }) 585 } 586 587 }