github.com/latiif/helm@v2.15.0+incompatible/pkg/kube/client_test.go (about) 1 /* 2 Copyright The Helm 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 kube 18 19 import ( 20 "bytes" 21 "io" 22 "io/ioutil" 23 "net/http" 24 "sort" 25 "strings" 26 "testing" 27 "time" 28 29 "k8s.io/api/core/v1" 30 apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/runtime" 33 "k8s.io/apimachinery/pkg/runtime/schema" 34 "k8s.io/cli-runtime/pkg/resource" 35 "k8s.io/client-go/kubernetes/scheme" 36 "k8s.io/client-go/rest/fake" 37 cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" 38 kubectlscheme "k8s.io/kubernetes/pkg/kubectl/scheme" 39 ) 40 41 func init() { 42 err := apiextv1beta1.AddToScheme(scheme.Scheme) 43 if err != nil { 44 panic(err) 45 } 46 47 // Tiller use the scheme from go-client, but the cmdtesting 48 // package used here is hardcoded to use the scheme from 49 // kubectl. So for testing, we need to add the CustomResourceDefinition 50 // type to both schemes. 51 err = apiextv1beta1.AddToScheme(kubectlscheme.Scheme) 52 if err != nil { 53 panic(err) 54 } 55 } 56 57 var ( 58 unstructuredSerializer = resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer 59 ) 60 61 func getCodec() runtime.Codec { 62 return scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...) 63 } 64 65 func objBody(obj runtime.Object) io.ReadCloser { 66 return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(getCodec(), obj)))) 67 } 68 69 func newPod(name string) v1.Pod { 70 return newPodWithStatus(name, v1.PodStatus{}, "") 71 } 72 73 func newPodWithStatus(name string, status v1.PodStatus, namespace string) v1.Pod { 74 ns := v1.NamespaceDefault 75 if namespace != "" { 76 ns = namespace 77 } 78 return v1.Pod{ 79 ObjectMeta: metav1.ObjectMeta{ 80 Name: name, 81 Namespace: ns, 82 SelfLink: "/api/v1/namespaces/default/pods/" + name, 83 }, 84 Spec: v1.PodSpec{ 85 Containers: []v1.Container{{ 86 Name: "app:v4", 87 Image: "abc/app:v4", 88 Ports: []v1.ContainerPort{{Name: "http", ContainerPort: 80}}, 89 }}, 90 }, 91 Status: status, 92 } 93 } 94 95 func newPodList(names ...string) v1.PodList { 96 var list v1.PodList 97 for _, name := range names { 98 list.Items = append(list.Items, newPod(name)) 99 } 100 return list 101 } 102 103 func newService(name string) v1.Service { 104 ns := v1.NamespaceDefault 105 return v1.Service{ 106 ObjectMeta: metav1.ObjectMeta{ 107 Name: name, 108 Namespace: ns, 109 SelfLink: "/api/v1/namespaces/default/services/" + name, 110 }, 111 Spec: v1.ServiceSpec{}, 112 } 113 } 114 115 func notFoundBody() *metav1.Status { 116 return &metav1.Status{ 117 Code: http.StatusNotFound, 118 Status: metav1.StatusFailure, 119 Reason: metav1.StatusReasonNotFound, 120 Message: " \"\" not found", 121 Details: &metav1.StatusDetails{}, 122 } 123 } 124 125 func newResponse(code int, obj runtime.Object) (*http.Response, error) { 126 header := http.Header{} 127 header.Set("Content-Type", runtime.ContentTypeJSON) 128 body := ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(getCodec(), obj)))) 129 return &http.Response{StatusCode: code, Header: header, Body: body}, nil 130 } 131 132 type testClient struct { 133 *Client 134 *cmdtesting.TestFactory 135 } 136 137 func newTestClient() *testClient { 138 tf := cmdtesting.NewTestFactory() 139 c := &Client{ 140 Factory: tf, 141 Log: nopLogger, 142 } 143 return &testClient{ 144 Client: c, 145 TestFactory: tf, 146 } 147 } 148 149 func TestUpdate(t *testing.T) { 150 listA := newPodList("starfish", "otter", "squid") 151 listB := newPodList("starfish", "otter", "dolphin") 152 listC := newPodList("starfish", "otter", "dolphin") 153 listB.Items[0].Spec.Containers[0].Ports = []v1.ContainerPort{{Name: "https", ContainerPort: 443}} 154 listC.Items[0].Spec.Containers[0].Ports = []v1.ContainerPort{{Name: "https", ContainerPort: 443}} 155 156 var actions []string 157 158 tf := cmdtesting.NewTestFactory() 159 defer tf.Cleanup() 160 161 tf.UnstructuredClient = &fake.RESTClient{ 162 NegotiatedSerializer: unstructuredSerializer, 163 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { 164 p, m := req.URL.Path, req.Method 165 actions = append(actions, p+":"+m) 166 t.Logf("got request %s %s", p, m) 167 switch { 168 case p == "/namespaces/default/pods/starfish" && m == "GET": 169 return newResponse(200, &listA.Items[0]) 170 case p == "/namespaces/default/pods/otter" && m == "GET": 171 return newResponse(200, &listA.Items[1]) 172 case p == "/namespaces/default/pods/dolphin" && m == "GET": 173 return newResponse(404, notFoundBody()) 174 case p == "/namespaces/default/pods/starfish" && m == "PATCH": 175 data, err := ioutil.ReadAll(req.Body) 176 if err != nil { 177 t.Fatalf("could not dump request: %s", err) 178 } 179 req.Body.Close() 180 expected := `{"spec":{"$setElementOrder/containers":[{"name":"app:v4"}],"containers":[{"$setElementOrder/ports":[{"containerPort":443}],"name":"app:v4","ports":[{"containerPort":443,"name":"https"},{"$patch":"delete","containerPort":80}]}]}}` 181 if string(data) != expected { 182 t.Errorf("expected patch\n%s\ngot\n%s", expected, string(data)) 183 } 184 return newResponse(200, &listB.Items[0]) 185 case p == "/namespaces/default/pods" && m == "POST": 186 return newResponse(200, &listB.Items[1]) 187 case p == "/namespaces/default/pods/squid" && m == "DELETE": 188 return newResponse(200, &listB.Items[1]) 189 case p == "/namespaces/default/pods/squid" && m == "GET": 190 return newResponse(200, &listA.Items[2]) 191 default: 192 t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path) 193 return nil, nil 194 } 195 }), 196 } 197 198 c := &Client{ 199 Factory: tf, 200 Log: nopLogger, 201 } 202 203 if err := c.Update(v1.NamespaceDefault, objBody(&listA), objBody(&listB), false, false, 0, false); err != nil { 204 t.Fatal(err) 205 } 206 // TODO: Find a way to test methods that use Client Set 207 // Test with a wait 208 // if err := c.Update("test", objBody(&listB), objBody(&listC), false, 300, true); err != nil { 209 // t.Fatal(err) 210 // } 211 // Test with a wait should fail 212 // TODO: A way to make this not based off of an extremely short timeout? 213 // if err := c.Update("test", objBody(&listC), objBody(&listA), false, 2, true); err != nil { 214 // t.Fatal(err) 215 // } 216 expectedActions := []string{ 217 "/namespaces/default/pods/starfish:GET", 218 "/namespaces/default/pods/starfish:PATCH", 219 "/namespaces/default/pods/otter:GET", 220 "/namespaces/default/pods/otter:GET", 221 "/namespaces/default/pods/dolphin:GET", 222 "/namespaces/default/pods:POST", 223 "/namespaces/default/pods/squid:GET", 224 "/namespaces/default/pods/squid:DELETE", 225 } 226 if len(expectedActions) != len(actions) { 227 t.Errorf("unexpected number of requests, expected %d, got %d", len(expectedActions), len(actions)) 228 return 229 } 230 for k, v := range expectedActions { 231 if actions[k] != v { 232 t.Errorf("expected %s request got %s", v, actions[k]) 233 } 234 } 235 236 // Test resource policy is respected 237 actions = nil 238 listA.Items[2].ObjectMeta.Annotations = map[string]string{ResourcePolicyAnno: "keep"} 239 if err := c.Update(v1.NamespaceDefault, objBody(&listA), objBody(&listB), false, false, 0, false); err != nil { 240 t.Fatal(err) 241 } 242 for _, v := range actions { 243 if v == "/namespaces/default/pods/squid:DELETE" { 244 t.Errorf("should not have deleted squid - it has helm.sh/resource-policy=keep") 245 } 246 } 247 } 248 249 func TestUpdateNonManagedResourceError(t *testing.T) { 250 actual := newPodList("starfish") 251 current := newPodList() 252 target := newPodList("starfish") 253 254 tf := cmdtesting.NewTestFactory() 255 defer tf.Cleanup() 256 257 tf.UnstructuredClient = &fake.RESTClient{ 258 NegotiatedSerializer: unstructuredSerializer, 259 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { 260 p, m := req.URL.Path, req.Method 261 t.Logf("got request %s %s", p, m) 262 switch { 263 case p == "/namespaces/default/pods/starfish" && m == "GET": 264 return newResponse(200, &actual.Items[0]) 265 default: 266 t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path) 267 return nil, nil 268 } 269 }), 270 } 271 272 c := &Client{ 273 Factory: tf, 274 Log: nopLogger, 275 } 276 277 if err := c.Update(v1.NamespaceDefault, objBody(¤t), objBody(&target), false, false, 0, false); err != nil { 278 if err.Error() != "kind Pod with the name \"starfish\" already exists in the cluster and wasn't defined in the previous release. Before upgrading, please either delete the resource from the cluster or remove it from the chart" { 279 t.Fatal(err) 280 } 281 } else { 282 t.Fatalf("error expected") 283 } 284 } 285 286 func TestDeleteWithTimeout(t *testing.T) { 287 testCases := map[string]struct { 288 deleteTimeout int64 289 deleteAfter time.Duration 290 success bool 291 }{ 292 "resource is deleted within timeout period": { 293 int64((2 * time.Minute).Seconds()), 294 10 * time.Second, 295 true, 296 }, 297 "resource is not deleted within the timeout period": { 298 int64((10 * time.Second).Seconds()), 299 20 * time.Second, 300 false, 301 }, 302 } 303 304 for tn, tc := range testCases { 305 t.Run(tn, func(t *testing.T) { 306 c := newTestClient() 307 defer c.Cleanup() 308 309 service := newService("my-service") 310 startTime := time.Now() 311 c.TestFactory.UnstructuredClient = &fake.RESTClient{ 312 GroupVersion: schema.GroupVersion{Version: "v1"}, 313 NegotiatedSerializer: unstructuredSerializer, 314 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { 315 currentTime := time.Now() 316 if startTime.Add(tc.deleteAfter).Before(currentTime) { 317 return newResponse(404, notFoundBody()) 318 } 319 return newResponse(200, &service) 320 }), 321 } 322 323 err := c.DeleteWithTimeout(metav1.NamespaceDefault, strings.NewReader(testServiceManifest), tc.deleteTimeout, true) 324 if err != nil && tc.success { 325 t.Errorf("expected no error, but got %v", err) 326 } 327 if err == nil && !tc.success { 328 t.Errorf("expected error, but didn't get one") 329 } 330 }) 331 } 332 } 333 334 func TestBuild(t *testing.T) { 335 tests := []struct { 336 name string 337 namespace string 338 reader io.Reader 339 count int 340 err bool 341 }{ 342 { 343 name: "Valid input", 344 namespace: "test", 345 reader: strings.NewReader(guestbookManifest), 346 count: 6, 347 }, { 348 name: "Invalid schema", 349 namespace: "test", 350 reader: strings.NewReader(testInvalidServiceManifest), 351 err: true, 352 }, 353 } 354 355 c := newTestClient() 356 for _, tt := range tests { 357 t.Run(tt.name, func(t *testing.T) { 358 c.Cleanup() 359 360 // Test for an invalid manifest 361 infos, err := c.Build(tt.namespace, tt.reader) 362 if err != nil && !tt.err { 363 t.Errorf("Got error message when no error should have occurred: %v", err) 364 } else if err != nil && strings.Contains(err.Error(), "--validate=false") { 365 t.Error("error message was not scrubbed") 366 } 367 368 if len(infos) != tt.count { 369 t.Errorf("expected %d result objects, got %d", tt.count, len(infos)) 370 } 371 }) 372 } 373 } 374 375 func TestGet(t *testing.T) { 376 list := newPodList("starfish", "otter") 377 c := newTestClient() 378 defer c.Cleanup() 379 c.TestFactory.UnstructuredClient = &fake.RESTClient{ 380 GroupVersion: schema.GroupVersion{Version: "v1"}, 381 NegotiatedSerializer: unstructuredSerializer, 382 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { 383 p, m := req.URL.Path, req.Method 384 t.Logf("got request %s %s", p, m) 385 switch { 386 case p == "/namespaces/default/pods/starfish" && m == "GET": 387 return newResponse(404, notFoundBody()) 388 case p == "/namespaces/default/pods/otter" && m == "GET": 389 return newResponse(200, &list.Items[1]) 390 default: 391 t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path) 392 return nil, nil 393 } 394 }), 395 } 396 397 // Test Success 398 data := strings.NewReader("kind: Pod\napiVersion: v1\nmetadata:\n name: otter") 399 o, err := c.Get("default", data) 400 if err != nil { 401 t.Errorf("Expected missing results, got %q", err) 402 } 403 if !strings.Contains(o, "==> v1/Pod") && !strings.Contains(o, "otter") { 404 t.Errorf("Expected v1/Pod otter, got %s", o) 405 } 406 407 // Test failure 408 data = strings.NewReader("kind: Pod\napiVersion: v1\nmetadata:\n name: starfish") 409 o, err = c.Get("default", data) 410 if err != nil { 411 t.Errorf("Expected missing results, got %q", err) 412 } 413 if !strings.Contains(o, "MISSING") && !strings.Contains(o, "pods\t\tstarfish") { 414 t.Errorf("Expected missing starfish, got %s", o) 415 } 416 } 417 418 func TestResourceTypeSortOrder(t *testing.T) { 419 pod := newPod("my-pod") 420 service := newService("my-service") 421 c := newTestClient() 422 defer c.Cleanup() 423 c.TestFactory.UnstructuredClient = &fake.RESTClient{ 424 GroupVersion: schema.GroupVersion{Version: "v1"}, 425 NegotiatedSerializer: unstructuredSerializer, 426 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { 427 p, m := req.URL.Path, req.Method 428 t.Logf("got request %s %s", p, m) 429 switch { 430 case p == "/namespaces/default/pods/my-pod" && m == "GET": 431 return newResponse(200, &pod) 432 case p == "/namespaces/default/services/my-service" && m == "GET": 433 return newResponse(200, &service) 434 default: 435 t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path) 436 return nil, nil 437 } 438 }), 439 } 440 441 // Test sorting order 442 data := strings.NewReader(testResourceTypeSortOrder) 443 o, err := c.Get("default", data) 444 if err != nil { 445 t.Errorf("Expected missing results, got %q", err) 446 } 447 podIndex := strings.Index(o, "my-pod") 448 serviceIndex := strings.Index(o, "my-service") 449 if podIndex == -1 { 450 t.Errorf("Expected v1/Pod my-pod, got %s", o) 451 } 452 if serviceIndex == -1 { 453 t.Errorf("Expected v1/Service my-service, got %s", o) 454 } 455 if !sort.IntsAreSorted([]int{podIndex, serviceIndex}) { 456 t.Errorf("Expected order: [v1/Pod v1/Service], got %s", o) 457 } 458 } 459 460 func TestResourceSortOrder(t *testing.T) { 461 list := newPodList("albacore", "coral", "beluga") 462 c := newTestClient() 463 defer c.Cleanup() 464 c.TestFactory.UnstructuredClient = &fake.RESTClient{ 465 GroupVersion: schema.GroupVersion{Version: "v1"}, 466 NegotiatedSerializer: unstructuredSerializer, 467 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { 468 p, m := req.URL.Path, req.Method 469 t.Logf("got request %s %s", p, m) 470 switch { 471 case p == "/namespaces/default/pods/albacore" && m == "GET": 472 return newResponse(200, &list.Items[0]) 473 case p == "/namespaces/default/pods/coral" && m == "GET": 474 return newResponse(200, &list.Items[1]) 475 case p == "/namespaces/default/pods/beluga" && m == "GET": 476 return newResponse(200, &list.Items[2]) 477 default: 478 t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path) 479 return nil, nil 480 } 481 }), 482 } 483 484 // Test sorting order 485 data := strings.NewReader(testResourceSortOrder) 486 o, err := c.Get("default", data) 487 if err != nil { 488 t.Errorf("Expected missing results, got %q", err) 489 } 490 albacoreIndex := strings.Index(o, "albacore") 491 belugaIndex := strings.Index(o, "beluga") 492 coralIndex := strings.Index(o, "coral") 493 if albacoreIndex == -1 { 494 t.Errorf("Expected v1/Pod albacore, got %s", o) 495 } 496 if belugaIndex == -1 { 497 t.Errorf("Expected v1/Pod beluga, got %s", o) 498 } 499 if coralIndex == -1 { 500 t.Errorf("Expected v1/Pod coral, got %s", o) 501 } 502 if !sort.IntsAreSorted([]int{albacoreIndex, belugaIndex, coralIndex}) { 503 t.Errorf("Expected order: [albacore beluga coral], got %s", o) 504 } 505 } 506 507 func TestWaitUntilCRDEstablished(t *testing.T) { 508 testCases := map[string]struct { 509 conditions []apiextv1beta1.CustomResourceDefinitionCondition 510 returnConditionsAfter int 511 success bool 512 }{ 513 "crd reaches established state after 2 requests": { 514 conditions: []apiextv1beta1.CustomResourceDefinitionCondition{ 515 { 516 Type: apiextv1beta1.Established, 517 Status: apiextv1beta1.ConditionTrue, 518 }, 519 }, 520 returnConditionsAfter: 2, 521 success: true, 522 }, 523 "crd does not reach established state before timeout": { 524 conditions: []apiextv1beta1.CustomResourceDefinitionCondition{}, 525 returnConditionsAfter: 100, 526 success: false, 527 }, 528 "crd name is not accepted": { 529 conditions: []apiextv1beta1.CustomResourceDefinitionCondition{ 530 { 531 Type: apiextv1beta1.NamesAccepted, 532 Status: apiextv1beta1.ConditionFalse, 533 }, 534 }, 535 returnConditionsAfter: 1, 536 success: false, 537 }, 538 } 539 540 for tn, tc := range testCases { 541 func(name string) { 542 c := newTestClient() 543 defer c.Cleanup() 544 545 crdWithoutConditions := newCrdWithStatus("name", apiextv1beta1.CustomResourceDefinitionStatus{}) 546 crdWithConditions := newCrdWithStatus("name", apiextv1beta1.CustomResourceDefinitionStatus{ 547 Conditions: tc.conditions, 548 }) 549 550 requestCount := 0 551 c.TestFactory.UnstructuredClient = &fake.RESTClient{ 552 GroupVersion: schema.GroupVersion{Version: "v1"}, 553 NegotiatedSerializer: unstructuredSerializer, 554 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { 555 var crd apiextv1beta1.CustomResourceDefinition 556 if requestCount < tc.returnConditionsAfter { 557 crd = crdWithoutConditions 558 } else { 559 crd = crdWithConditions 560 } 561 requestCount++ 562 return newResponse(200, &crd) 563 }), 564 } 565 566 err := c.WaitUntilCRDEstablished(strings.NewReader(crdManifest), 5*time.Second) 567 if err != nil && tc.success { 568 t.Errorf("%s: expected no error, but got %v", name, err) 569 } 570 if err == nil && !tc.success { 571 t.Errorf("%s: expected error, but didn't get one", name) 572 } 573 }(tn) 574 } 575 } 576 577 func newCrdWithStatus(name string, status apiextv1beta1.CustomResourceDefinitionStatus) apiextv1beta1.CustomResourceDefinition { 578 crd := apiextv1beta1.CustomResourceDefinition{ 579 ObjectMeta: metav1.ObjectMeta{ 580 Name: name, 581 Namespace: metav1.NamespaceDefault, 582 }, 583 Spec: apiextv1beta1.CustomResourceDefinitionSpec{}, 584 Status: status, 585 } 586 return crd 587 } 588 589 func TestPerform(t *testing.T) { 590 tests := []struct { 591 name string 592 namespace string 593 reader io.Reader 594 count int 595 err bool 596 errMessage string 597 }{ 598 { 599 name: "Valid input", 600 namespace: "test", 601 reader: strings.NewReader(guestbookManifest), 602 count: 6, 603 }, { 604 name: "Empty manifests", 605 namespace: "test", 606 reader: strings.NewReader(""), 607 err: true, 608 errMessage: "no objects visited", 609 }, 610 } 611 612 for _, tt := range tests { 613 t.Run(tt.name, func(t *testing.T) { 614 results := []*resource.Info{} 615 616 fn := func(info *resource.Info) error { 617 results = append(results, info) 618 619 if info.Namespace != tt.namespace { 620 t.Errorf("expected namespace to be '%s', got %s", tt.namespace, info.Namespace) 621 } 622 return nil 623 } 624 625 c := newTestClient() 626 defer c.Cleanup() 627 infos, err := c.Build(tt.namespace, tt.reader) 628 if err != nil && err.Error() != tt.errMessage { 629 t.Errorf("Error while building manifests: %v", err) 630 } 631 632 err = perform(infos, fn) 633 if (err != nil) != tt.err { 634 t.Errorf("expected error: %v, got %v", tt.err, err) 635 } 636 if err != nil && err.Error() != tt.errMessage { 637 t.Errorf("expected error message: %v, got %v", tt.errMessage, err) 638 } 639 640 if len(results) != tt.count { 641 t.Errorf("expected %d result objects, got %d", tt.count, len(results)) 642 } 643 }) 644 } 645 } 646 647 func TestReal(t *testing.T) { 648 t.Skip("This is a live test, comment this line to run") 649 c := New(nil) 650 if err := c.Create("test", strings.NewReader(guestbookManifest), 300, false); err != nil { 651 t.Fatal(err) 652 } 653 654 testSvcEndpointManifest := testServiceManifest + "\n---\n" + testEndpointManifest 655 c = New(nil) 656 if err := c.Create("test-delete", strings.NewReader(testSvcEndpointManifest), 300, false); err != nil { 657 t.Fatal(err) 658 } 659 660 if err := c.Delete("test-delete", strings.NewReader(testEndpointManifest)); err != nil { 661 t.Fatal(err) 662 } 663 664 // ensures that delete does not fail if a resource is not found 665 if err := c.Delete("test-delete", strings.NewReader(testSvcEndpointManifest)); err != nil { 666 t.Fatal(err) 667 } 668 } 669 670 const testResourceTypeSortOrder = ` 671 kind: Service 672 apiVersion: v1 673 metadata: 674 name: my-service 675 --- 676 kind: Pod 677 apiVersion: v1 678 metadata: 679 name: my-pod 680 ` 681 682 const testResourceSortOrder = ` 683 kind: Pod 684 apiVersion: v1 685 metadata: 686 name: albacore 687 --- 688 kind: Pod 689 apiVersion: v1 690 metadata: 691 name: coral 692 --- 693 kind: Pod 694 apiVersion: v1 695 metadata: 696 name: beluga 697 ` 698 699 const testServiceManifest = ` 700 kind: Service 701 apiVersion: v1 702 metadata: 703 name: my-service 704 spec: 705 selector: 706 app: myapp 707 ports: 708 - port: 80 709 protocol: TCP 710 targetPort: 9376 711 ` 712 713 const testInvalidServiceManifest = ` 714 kind: Service 715 apiVersion: v1 716 spec: 717 ports: 718 - port: "80" 719 ` 720 721 const testEndpointManifest = ` 722 kind: Endpoints 723 apiVersion: v1 724 metadata: 725 name: my-service 726 subsets: 727 - addresses: 728 - ip: "1.2.3.4" 729 ports: 730 - port: 9376 731 ` 732 733 const guestbookManifest = ` 734 apiVersion: v1 735 kind: Service 736 metadata: 737 name: redis-master 738 labels: 739 app: redis 740 tier: backend 741 role: master 742 spec: 743 ports: 744 - port: 6379 745 targetPort: 6379 746 selector: 747 app: redis 748 tier: backend 749 role: master 750 --- 751 apiVersion: apps/v1 752 kind: Deployment 753 metadata: 754 name: redis-master 755 spec: 756 replicas: 1 757 template: 758 metadata: 759 labels: 760 app: redis 761 role: master 762 tier: backend 763 spec: 764 containers: 765 - name: master 766 image: k8s.gcr.io/redis:e2e # or just image: redis 767 resources: 768 requests: 769 cpu: 100m 770 memory: 100Mi 771 ports: 772 - containerPort: 6379 773 --- 774 apiVersion: v1 775 kind: Service 776 metadata: 777 name: redis-slave 778 labels: 779 app: redis 780 tier: backend 781 role: slave 782 spec: 783 ports: 784 # the port that this service should serve on 785 - port: 6379 786 selector: 787 app: redis 788 tier: backend 789 role: slave 790 --- 791 apiVersion: apps/v1 792 kind: Deployment 793 metadata: 794 name: redis-slave 795 spec: 796 replicas: 2 797 template: 798 metadata: 799 labels: 800 app: redis 801 role: slave 802 tier: backend 803 spec: 804 containers: 805 - name: slave 806 image: gcr.io/google_samples/gb-redisslave:v1 807 resources: 808 requests: 809 cpu: 100m 810 memory: 100Mi 811 env: 812 - name: GET_HOSTS_FROM 813 value: dns 814 ports: 815 - containerPort: 6379 816 --- 817 apiVersion: v1 818 kind: Service 819 metadata: 820 name: frontend 821 labels: 822 app: guestbook 823 tier: frontend 824 spec: 825 ports: 826 - port: 80 827 selector: 828 app: guestbook 829 tier: frontend 830 --- 831 apiVersion: apps/v1 832 kind: Deployment 833 metadata: 834 name: frontend 835 spec: 836 replicas: 3 837 template: 838 metadata: 839 labels: 840 app: guestbook 841 tier: frontend 842 spec: 843 containers: 844 - name: php-redis 845 image: gcr.io/google-samples/gb-frontend:v4 846 resources: 847 requests: 848 cpu: 100m 849 memory: 100Mi 850 env: 851 - name: GET_HOSTS_FROM 852 value: dns 853 ports: 854 - containerPort: 80 855 ` 856 857 const crdManifest = ` 858 apiVersion: apiextensions.k8s.io/v1beta1 859 kind: CustomResourceDefinition 860 metadata: 861 creationTimestamp: null 862 labels: 863 controller-tools.k8s.io: "1.0" 864 name: applications.app.k8s.io 865 spec: 866 group: app.k8s.io 867 names: 868 kind: Application 869 plural: applications 870 scope: Namespaced 871 validation: 872 openAPIV3Schema: 873 properties: 874 apiVersion: 875 description: 'Description' 876 type: string 877 kind: 878 description: 'Kind' 879 type: string 880 metadata: 881 type: object 882 spec: 883 type: object 884 status: 885 type: object 886 version: v1beta1 887 status: 888 acceptedNames: 889 kind: "" 890 plural: "" 891 conditions: [] 892 storedVersions: [] 893 `