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