1
16
17 package resource
18
19 import (
20 "bytes"
21 "errors"
22 "fmt"
23 "io"
24 "net/http"
25 "net/http/httptest"
26 "os"
27 "path/filepath"
28 "reflect"
29 "strings"
30 "testing"
31
32 "sigs.k8s.io/yaml"
33
34 apiequality "k8s.io/apimachinery/pkg/api/equality"
35 "k8s.io/apimachinery/pkg/api/meta"
36 "k8s.io/apimachinery/pkg/api/meta/testrestmapper"
37 "k8s.io/apimachinery/pkg/api/resource"
38 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
39 "k8s.io/apimachinery/pkg/runtime"
40 "k8s.io/apimachinery/pkg/runtime/schema"
41 "k8s.io/apimachinery/pkg/runtime/serializer/streaming"
42 "k8s.io/apimachinery/pkg/util/dump"
43 utilerrors "k8s.io/apimachinery/pkg/util/errors"
44 "k8s.io/apimachinery/pkg/watch"
45 "k8s.io/client-go/rest"
46 "k8s.io/client-go/rest/fake"
47 restclientwatch "k8s.io/client-go/rest/watch"
48 "k8s.io/client-go/restmapper"
49
50 v1 "k8s.io/api/core/v1"
51 "k8s.io/client-go/kubernetes/scheme"
52 )
53
54 var (
55 corev1GV = schema.GroupVersion{Version: "v1"}
56 corev1Codec = scheme.Codecs.CodecForVersions(scheme.Codecs.LegacyCodec(corev1GV), scheme.Codecs.UniversalDecoder(corev1GV), corev1GV, corev1GV)
57 )
58
59 func stringBody(body string) io.ReadCloser {
60 return io.NopCloser(bytes.NewReader([]byte(body)))
61 }
62
63 func watchBody(events ...watch.Event) string {
64 buf := &bytes.Buffer{}
65 codec := corev1Codec
66 enc := restclientwatch.NewEncoder(streaming.NewEncoder(buf, codec), codec)
67 for _, e := range events {
68 enc.Encode(&e)
69 }
70 return buf.String()
71 }
72
73 func fakeClient() FakeClientFunc {
74 return func(version schema.GroupVersion) (RESTClient, error) {
75 return &fake.RESTClient{}, nil
76 }
77 }
78
79 func fakeClientWith(testName string, t *testing.T, data map[string]string) FakeClientFunc {
80 return func(version schema.GroupVersion) (RESTClient, error) {
81 return &fake.RESTClient{
82 GroupVersion: corev1GV,
83 NegotiatedSerializer: scheme.Codecs.WithoutConversion(),
84 Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
85 p := req.URL.Path
86 q := req.URL.RawQuery
87 if len(q) != 0 {
88 p = p + "?" + q
89 }
90 body, ok := data[p]
91 if !ok {
92 t.Fatalf("%s: unexpected request: %s (%s)\n%#v", testName, p, req.URL, req)
93 }
94 header := http.Header{}
95 header.Set("Content-Type", runtime.ContentTypeJSON)
96 return &http.Response{
97 StatusCode: http.StatusOK,
98 Header: header,
99 Body: stringBody(body),
100 }, nil
101 }),
102 }, nil
103 }
104 }
105
106 func testData() (*v1.PodList, *v1.ServiceList) {
107 pods := &v1.PodList{
108 ListMeta: metav1.ListMeta{
109 ResourceVersion: "15",
110 },
111 Items: []v1.Pod{
112 {
113 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"},
114 Spec: V1DeepEqualSafePodSpec(),
115 },
116 {
117 ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "test", ResourceVersion: "11"},
118 Spec: V1DeepEqualSafePodSpec(),
119 },
120 },
121 }
122 svc := &v1.ServiceList{
123 ListMeta: metav1.ListMeta{
124 ResourceVersion: "16",
125 },
126 Items: []v1.Service{
127 {
128 ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
129 Spec: v1.ServiceSpec{
130 Type: "ClusterIP",
131 SessionAffinity: "None",
132 },
133 },
134 },
135 }
136 return pods, svc
137 }
138
139 func streamTestData() (io.Reader, *v1.PodList, *v1.ServiceList) {
140 pods, svc := testData()
141 r, w := io.Pipe()
142 go func() {
143 defer w.Close()
144 w.Write([]byte(runtime.EncodeOrDie(corev1Codec, pods)))
145 w.Write([]byte(runtime.EncodeOrDie(corev1Codec, svc)))
146 }()
147 return r, pods, svc
148 }
149
150 func subresourceTestData(name string) *v1.Pod {
151 return &v1.Pod{
152 ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: "test", ResourceVersion: "10"},
153 Spec: V1DeepEqualSafePodSpec(),
154 Status: V1DeepEqualSafePodStatus(),
155 }
156 }
157
158 func JSONToYAMLOrDie(in []byte) []byte {
159 data, err := yaml.JSONToYAML(in)
160 if err != nil {
161 panic(err)
162 }
163 return data
164 }
165
166 func streamYAMLTestData() (io.Reader, *v1.PodList, *v1.ServiceList) {
167 pods, svc := testData()
168 r, w := io.Pipe()
169 go func() {
170 defer w.Close()
171 w.Write(JSONToYAMLOrDie([]byte(runtime.EncodeOrDie(corev1Codec, pods))))
172 w.Write([]byte("\n---\n"))
173 w.Write(JSONToYAMLOrDie([]byte(runtime.EncodeOrDie(corev1Codec, svc))))
174 }()
175 return r, pods, svc
176 }
177
178 func streamTestObject(obj runtime.Object) io.Reader {
179 r, w := io.Pipe()
180 go func() {
181 defer w.Close()
182 w.Write([]byte(runtime.EncodeOrDie(corev1Codec, obj)))
183 }()
184 return r
185 }
186
187 type testVisitor struct {
188 InjectErr error
189 Infos []*Info
190 }
191
192 func (v *testVisitor) Handle(info *Info, err error) error {
193 if err != nil {
194 return err
195 }
196 v.Infos = append(v.Infos, info)
197 return v.InjectErr
198 }
199
200 func (v *testVisitor) Objects() []runtime.Object {
201 objects := []runtime.Object{}
202 for i := range v.Infos {
203 objects = append(objects, v.Infos[i].Object)
204 }
205 return objects
206 }
207
208 var aPod string = `
209 {
210 "kind": "Pod",
211 "apiVersion": "` + corev1GV.String() + `",
212 "metadata": {
213 "name": "busybox{id}",
214 "labels": {
215 "name": "busybox{id}"
216 }
217 },
218 "spec": {
219 "containers": [
220 {
221 "name": "busybox",
222 "image": "busybox",
223 "command": [
224 "sleep",
225 "3600"
226 ],
227 "imagePullPolicy": "IfNotPresent"
228 }
229 ],
230 "restartPolicy": "Always"
231 }
232 }
233 `
234 var aPodBadAnnotations string = `
235 {
236 "kind": "Pod",
237 "apiVersion": "` + corev1GV.String() + `",
238 "metadata": {
239 "name": "busybox{id}",
240 "labels": {
241 "name": "busybox{id}"
242 },
243 "annotations": {
244 "name": 0
245 }
246 },
247 "spec": {
248 "containers": [
249 {
250 "name": "busybox",
251 "image": "busybox",
252 "command": [
253 "sleep",
254 "3600"
255 ],
256 "imagePullPolicy": "IfNotPresent"
257 }
258 ],
259 "restartPolicy": "Always"
260 }
261 }
262 `
263
264 var aRC string = `
265 {
266 "kind": "ReplicationController",
267 "apiVersion": "` + corev1GV.String() + `",
268 "metadata": {
269 "name": "busybox{id}",
270 "labels": {
271 "app": "busybox"
272 }
273 },
274 "spec": {
275 "replicas": 1,
276 "template": {
277 "metadata": {
278 "name": "busybox{id}",
279 "labels": {
280 "app": "busybox{id}"
281 }
282 },
283 "spec": {
284 "containers": [
285 {
286 "name": "busybox",
287 "image": "busybox",
288 "command": [
289 "sleep",
290 "3600"
291 ],
292 "imagePullPolicy": "IfNotPresent"
293 }
294 ],
295 "restartPolicy": "Always"
296 }
297 }
298 }
299 }
300 `
301
302 func newDefaultBuilder() *Builder {
303 return newDefaultBuilderWith(fakeClient())
304 }
305
306 func newDefaultBuilderWith(fakeClientFn FakeClientFunc) *Builder {
307 return NewFakeBuilder(
308 fakeClientFn,
309 func() (meta.RESTMapper, error) {
310 return testrestmapper.TestOnlyStaticRESTMapper(scheme.Scheme), nil
311 },
312 func() (restmapper.CategoryExpander, error) {
313 return FakeCategoryExpander, nil
314 }).
315 WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...)
316 }
317
318 func newUnstructuredDefaultBuilder() *Builder {
319 return newUnstructuredDefaultBuilderWith(fakeClient())
320 }
321
322 func newUnstructuredDefaultBuilderWith(fakeClientFn FakeClientFunc) *Builder {
323 return NewFakeBuilder(
324 fakeClientFn,
325 func() (meta.RESTMapper, error) {
326 return testrestmapper.TestOnlyStaticRESTMapper(scheme.Scheme), nil
327 },
328 func() (restmapper.CategoryExpander, error) {
329 return FakeCategoryExpander, nil
330 }).
331 Unstructured()
332 }
333
334 type errorRestMapper struct {
335 meta.RESTMapper
336 err error
337 }
338
339 func (l *errorRestMapper) RESTMapping(gk schema.GroupKind, versions ...string) (*meta.RESTMapping, error) {
340 return nil, l.err
341 }
342
343 func (l *errorRestMapper) Reset() {
344 meta.MaybeResetRESTMapper(l.RESTMapper)
345 }
346
347 func newDefaultBuilderWithMapperError(fakeClientFn FakeClientFunc, err error) *Builder {
348 return NewFakeBuilder(
349 fakeClientFn,
350 func() (meta.RESTMapper, error) {
351 return &errorRestMapper{
352 RESTMapper: testrestmapper.TestOnlyStaticRESTMapper(scheme.Scheme),
353 err: err,
354 }, nil
355 },
356 func() (restmapper.CategoryExpander, error) {
357 return FakeCategoryExpander, nil
358 }).
359 WithScheme(scheme.Scheme, scheme.Scheme.PrioritizedVersionsAllGroups()...)
360 }
361
362 func TestPathBuilderAndVersionedObjectNotDefaulted(t *testing.T) {
363 b := newDefaultBuilder().
364 FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../artifacts/kitten-rc.yaml"}})
365
366 test := &testVisitor{}
367 singleItemImplied := false
368
369 err := b.Do().IntoSingleItemImplied(&singleItemImplied).Visit(test.Handle)
370 if err != nil || !singleItemImplied || len(test.Infos) != 1 {
371 t.Fatalf("unexpected response: %v %t %#v", err, singleItemImplied, test.Infos)
372 }
373
374 info := test.Infos[0]
375 if info.Name != "update-demo-kitten" || info.Namespace != "" || info.Object == nil {
376 t.Errorf("unexpected info: %#v", info)
377 }
378
379 obj := info.Object
380 version, ok := obj.(*v1.ReplicationController)
381
382 if obj == nil || !ok || version.Spec.Replicas != nil {
383 t.Errorf("unexpected versioned object: %#v", obj)
384 }
385 }
386
387 func TestNodeBuilder(t *testing.T) {
388 node := &v1.Node{
389 ObjectMeta: metav1.ObjectMeta{Name: "node1", Namespace: "should-not-have", ResourceVersion: "10"},
390 Spec: v1.NodeSpec{},
391 Status: v1.NodeStatus{
392 Capacity: v1.ResourceList{
393 v1.ResourceCPU: resource.MustParse("1000m"),
394 v1.ResourceMemory: resource.MustParse("1Mi"),
395 },
396 },
397 }
398 r, w := io.Pipe()
399 go func() {
400 defer w.Close()
401 w.Write([]byte(runtime.EncodeOrDie(corev1Codec, node)))
402 }()
403
404 b := newDefaultBuilder().NamespaceParam("test").Stream(r, "STDIN")
405
406 test := &testVisitor{}
407
408 err := b.Do().Visit(test.Handle)
409 if err != nil || len(test.Infos) != 1 {
410 t.Fatalf("unexpected response: %v %#v", err, test.Infos)
411 }
412 info := test.Infos[0]
413 if info.Name != "node1" || info.Namespace != "" || info.Object == nil {
414 t.Errorf("unexpected info: %#v", info)
415 }
416 }
417
418 func createTestDir(t *testing.T, path string) {
419 if err := os.MkdirAll(path, 0750); err != nil {
420 t.Fatalf("error creating test dir: %v", err)
421 }
422 }
423
424 func writeTestFile(t *testing.T, path string, contents string) {
425 if err := os.WriteFile(path, []byte(contents), 0644); err != nil {
426 t.Fatalf("error creating test file %#v", err)
427 }
428 }
429
430 func TestFilenameOptionsValidate(t *testing.T) {
431 testcases := []struct {
432 filenames []string
433 kustomize string
434 recursive bool
435 errExp bool
436 msgExp string
437 }{
438 {
439 filenames: []string{"file"},
440 kustomize: "dir",
441 errExp: true,
442 msgExp: "only one of -f or -k can be specified",
443 },
444 {
445 kustomize: "dir",
446 recursive: true,
447 errExp: true,
448 msgExp: "the -k flag can't be used with -f or -R",
449 },
450 {
451 filenames: []string{"file"},
452 errExp: false,
453 },
454 {
455 filenames: []string{"dir"},
456 recursive: true,
457 errExp: false,
458 },
459 {
460 kustomize: "dir",
461 errExp: false,
462 },
463 }
464 for _, testcase := range testcases {
465 o := &FilenameOptions{
466 Kustomize: testcase.kustomize,
467 Filenames: testcase.filenames,
468 Recursive: testcase.recursive,
469 }
470 errs := o.validate()
471 if testcase.errExp {
472 if len(errs) == 0 {
473 t.Fatalf("expected error not happened")
474 }
475 if errs[0].Error() != testcase.msgExp {
476 t.Fatalf("expected %s, but got %#v", testcase.msgExp, errs[0])
477 }
478 } else {
479 if len(errs) > 0 {
480 t.Fatalf("Unexpected error %#v", errs)
481 }
482 }
483 }
484 }
485
486 func TestPathBuilderWithMultiple(t *testing.T) {
487
488 tmpDir, err := os.MkdirTemp("", "recursive_test_multiple")
489 if err != nil {
490 t.Fatalf("error creating temp dir: %v", err)
491 }
492
493 createTestDir(t, fmt.Sprintf("%s/%s", tmpDir, "recursive/pod/pod_1"))
494 createTestDir(t, fmt.Sprintf("%s/%s", tmpDir, "recursive/rc/rc_1"))
495 createTestDir(t, fmt.Sprintf("%s/%s", tmpDir, "inode/hardlink"))
496 defer os.RemoveAll(tmpDir)
497
498
499 writeTestFile(t, fmt.Sprintf("%s/recursive/pod/busybox.json", tmpDir), strings.Replace(aPod, "{id}", "0", -1))
500 writeTestFile(t, fmt.Sprintf("%s/recursive/pod/pod_1/busybox.json", tmpDir), strings.Replace(aPod, "{id}", "1", -1))
501 writeTestFile(t, fmt.Sprintf("%s/recursive/rc/busybox.json", tmpDir), strings.Replace(aRC, "{id}", "0", -1))
502 writeTestFile(t, fmt.Sprintf("%s/recursive/rc/rc_1/busybox.json", tmpDir), strings.Replace(aRC, "{id}", "1", -1))
503 writeTestFile(t, fmt.Sprintf("%s/inode/hardlink/busybox.json", tmpDir), strings.Replace(aPod, "{id}", "0", -1))
504 if err := os.Link(fmt.Sprintf("%s/inode/hardlink/busybox.json", tmpDir), fmt.Sprintf("%s/inode/hardlink/busybox-link.json", tmpDir)); err != nil {
505 t.Fatalf("error creating test file: %v", err)
506 }
507
508 tests := []struct {
509 name string
510 object runtime.Object
511 recursive bool
512 directory string
513 expectedNames []string
514 }{
515 {"pod", &v1.Pod{}, false, "../../artifacts/pod.yaml", []string{"nginx"}},
516 {"recursive-pod", &v1.Pod{}, true, fmt.Sprintf("%s/recursive/pod", tmpDir), []string{"busybox0", "busybox1"}},
517 {"rc", &v1.ReplicationController{}, false, "../../artifacts/redis-master-controller.yaml", []string{"redis-master"}},
518 {"recursive-rc", &v1.ReplicationController{}, true, fmt.Sprintf("%s/recursive/rc", tmpDir), []string{"busybox0", "busybox1"}},
519 {"hardlink", &v1.Pod{}, false, fmt.Sprintf("%s/inode/hardlink/busybox-link.json", tmpDir), []string{"busybox0"}},
520 {"hardlink", &v1.Pod{}, true, fmt.Sprintf("%s/inode/hardlink/busybox-link.json", tmpDir), []string{"busybox0"}},
521 }
522
523 for _, tt := range tests {
524 t.Run(tt.name, func(t *testing.T) {
525 b := newDefaultBuilder().
526 FilenameParam(false, &FilenameOptions{Recursive: tt.recursive, Filenames: []string{tt.directory}}).
527 NamespaceParam("test").DefaultNamespace()
528
529 testVisitor := &testVisitor{}
530 singleItemImplied := false
531
532 err := b.Do().IntoSingleItemImplied(&singleItemImplied).Visit(testVisitor.Handle)
533 if err != nil {
534 t.Fatalf("unexpected response: %v %t %#v %s", err, singleItemImplied, testVisitor.Infos, tt.name)
535 }
536
537 info := testVisitor.Infos
538
539 for i, v := range info {
540 switch tt.object.(type) {
541 case *v1.Pod:
542 if _, ok := v.Object.(*v1.Pod); !ok || v.Name != tt.expectedNames[i] || v.Namespace != "test" {
543 t.Errorf("unexpected info: %v", dump.Pretty(v.Object))
544 }
545 case *v1.ReplicationController:
546 if _, ok := v.Object.(*v1.ReplicationController); !ok || v.Name != tt.expectedNames[i] || v.Namespace != "test" {
547 t.Errorf("unexpected info: %v", dump.Pretty(v.Object))
548 }
549 }
550 }
551 })
552 }
553 }
554
555 func TestPathBuilderWithMultipleInvalid(t *testing.T) {
556
557 tmpDir, err := os.MkdirTemp("", "recursive_test_multiple_invalid")
558 if err != nil {
559 t.Fatalf("error creating temp dir: %v", err)
560 }
561
562 createTestDir(t, fmt.Sprintf("%s/%s", tmpDir, "inode/symlink/pod"))
563 defer os.RemoveAll(tmpDir)
564
565
566 writeTestFile(t, fmt.Sprintf("%s/inode/symlink/pod/busybox.json", tmpDir), strings.Replace(aPod, "{id}", "0", -1))
567 if err := os.Symlink(fmt.Sprintf("%s/inode/symlink/pod", tmpDir), fmt.Sprintf("%s/inode/symlink/pod-link", tmpDir)); err != nil {
568 t.Fatalf("error creating test file: %v", err)
569 }
570 if err := os.Symlink(fmt.Sprintf("%s/inode/symlink/loop", tmpDir), fmt.Sprintf("%s/inode/symlink/loop", tmpDir)); err != nil {
571 t.Fatalf("error creating test file: %v", err)
572 }
573
574 tests := []struct {
575 name string
576 recursive bool
577 directory string
578 }{
579 {"symlink", false, fmt.Sprintf("%s/inode/symlink/pod-link", tmpDir)},
580 {"symlink", true, fmt.Sprintf("%s/inode/symlink/pod-link", tmpDir)},
581 {"loop", false, fmt.Sprintf("%s/inode/symlink/loop", tmpDir)},
582 {"loop", true, fmt.Sprintf("%s/inode/symlink/loop", tmpDir)},
583 }
584
585 for _, tt := range tests {
586 t.Run(tt.name, func(t *testing.T) {
587 b := newDefaultBuilder().
588 FilenameParam(false, &FilenameOptions{Recursive: tt.recursive, Filenames: []string{tt.directory}}).
589 NamespaceParam("test").DefaultNamespace()
590
591 testVisitor := &testVisitor{}
592 singleItemImplied := false
593
594 err := b.Do().IntoSingleItemImplied(&singleItemImplied).Visit(testVisitor.Handle)
595 if err == nil {
596 t.Fatalf("unexpected response: %v %t %#v %s", err, singleItemImplied, testVisitor.Infos, tt.name)
597 }
598 })
599 }
600 }
601
602 func TestDirectoryBuilder(t *testing.T) {
603 b := newDefaultBuilder().
604 FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../artifacts/guestbook"}}).
605 NamespaceParam("test").DefaultNamespace()
606
607 test := &testVisitor{}
608 singleItemImplied := false
609
610 err := b.Do().IntoSingleItemImplied(&singleItemImplied).Visit(test.Handle)
611 if err != nil || singleItemImplied || len(test.Infos) < 3 {
612 t.Fatalf("unexpected response: %v %t %#v", err, singleItemImplied, test.Infos)
613 }
614
615 found := false
616 for _, info := range test.Infos {
617 if info.Name == "redis-master" && info.Namespace == "test" && info.Object != nil {
618 found = true
619 break
620 }
621 }
622 if !found {
623 t.Errorf("unexpected responses: %#v", test.Infos)
624 }
625 }
626
627 func TestFilePatternBuilderWhenFileLiteralExists(t *testing.T) {
628 const pathPattern = "../../artifacts/oddly-named-file[x].yaml"
629 b := newDefaultBuilder().
630 FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{pathPattern}}).
631 NamespaceParam("test").DefaultNamespace()
632
633 test := &testVisitor{}
634 singleItemImplied := false
635
636 err := b.Do().IntoSingleItemImplied(&singleItemImplied).Visit(test.Handle)
637 if err != nil || !singleItemImplied || len(test.Infos) != 1 {
638 t.Fatalf("unexpected result: %v %t %#v", err, singleItemImplied, test.Infos)
639 }
640 if !strings.Contains(test.Infos[0].Source, "oddly-named-file[x]") {
641 t.Errorf("unexpected file name: %#v", test.Infos[0].Source)
642 }
643 }
644
645 func TestFilePatternBuilder(t *testing.T) {
646 b := newDefaultBuilder().
647 FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../artifacts/guestbook/redis-*.yaml"}}).
648 NamespaceParam("test").DefaultNamespace()
649
650 test := &testVisitor{}
651 singleItemImplied := false
652
653 err := b.Do().IntoSingleItemImplied(&singleItemImplied).Visit(test.Handle)
654 if err != nil || singleItemImplied || len(test.Infos) < 3 {
655 t.Fatalf("unexpected response: %v %t %#v", err, singleItemImplied, test.Infos)
656 }
657
658 for _, info := range test.Infos {
659 if strings.Index(info.Name, "redis-") != 0 {
660 t.Errorf("unexpected response: %#v", info.Name)
661 }
662 }
663 }
664
665 func TestErrorFilePatternBuilder(t *testing.T) {
666 testCases := map[string]struct {
667 input string
668 expectedErr string
669 inputInError bool
670 }{
671 "invalid pattern": {
672 input: "[a-z*.yaml",
673 expectedErr: "syntax error in pattern",
674 inputInError: true,
675 },
676 "file does not exist": {
677 input: "../../artifacts/guestbook/notexist.yaml",
678 expectedErr: "does not exist",
679 inputInError: true,
680 },
681 "directory does not exist and valid glob": {
682 input: "../../artifacts/_does_not_exist_/*.yaml",
683 expectedErr: "does not exist",
684 inputInError: true,
685 },
686 }
687 for name, tc := range testCases {
688 t.Run(name, func(t *testing.T) {
689 b := newDefaultBuilder().
690 FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{tc.input}}).
691 NamespaceParam("test").DefaultNamespace()
692
693 test := &testVisitor{}
694 singleItemImplied := false
695
696 err := b.Do().IntoSingleItemImplied(&singleItemImplied).Visit(test.Handle)
697 if err == nil || len(test.Infos) != 0 || !strings.Contains(err.Error(), tc.expectedErr) ||
698 (tc.inputInError && !strings.Contains(err.Error(), tc.input)) {
699 t.Errorf("unexpected response: %v %#v", err, test.Infos)
700 }
701 })
702 }
703 }
704
705 func setupKustomizeDirectory() (string, error) {
706 path, err := os.MkdirTemp("", "")
707 if err != nil {
708 return "", err
709 }
710
711 contents := map[string]string{
712 "configmap.yaml": `
713 apiVersion: v1
714 kind: ConfigMap
715 metadata:
716 name: the-map
717 data:
718 altGreeting: "Good Morning!"
719 enableRisky: "false"
720 `,
721 "deployment.yaml": `
722 apiVersion: apps/v1
723 kind: Deployment
724 metadata:
725 name: the-deployment
726 spec:
727 replicas: 3
728 template:
729 metadata:
730 labels:
731 deployment: hello
732 spec:
733 containers:
734 - name: the-container
735 image: monopole/hello:1
736 command: ["/hello",
737 "--port=8080",
738 "--enableRiskyFeature=$(ENABLE_RISKY)"]
739 ports:
740 - containerPort: 8080
741 env:
742 - name: ALT_GREETING
743 valueFrom:
744 configMapKeyRef:
745 name: the-map
746 key: altGreeting
747 - name: ENABLE_RISKY
748 valueFrom:
749 configMapKeyRef:
750 name: the-map
751 key: enableRisky
752 `,
753 "service.yaml": `
754 kind: Service
755 apiVersion: v1
756 metadata:
757 name: the-service
758 spec:
759 selector:
760 deployment: hello
761 type: LoadBalancer
762 ports:
763 - protocol: TCP
764 port: 8666
765 targetPort: 8080
766 `,
767 "kustomization.yaml": `
768 nameprefix: test-
769 resources:
770 - deployment.yaml
771 - service.yaml
772 - configmap.yaml
773 `,
774 }
775
776 for filename, content := range contents {
777 err = os.WriteFile(filepath.Join(path, filename), []byte(content), 0660)
778 if err != nil {
779 return "", err
780 }
781 }
782 return path, nil
783 }
784
785 func TestKustomizeDirectoryBuilder(t *testing.T) {
786 dir, err := setupKustomizeDirectory()
787 if err != nil {
788 t.Fatalf("unexpected error %v", err)
789 }
790 defer os.RemoveAll(dir)
791
792 tests := []struct {
793 directory string
794 expectErr bool
795 errMsg string
796 number int
797 expectedNames []string
798 }{
799 {
800 directory: "../../artifacts/guestbook",
801 expectErr: true,
802 errMsg: "unable to find one of 'kustomization.yaml', 'kustomization.yml' or 'Kustomization'",
803 },
804 {
805 directory: dir,
806 expectErr: false,
807 expectedNames: []string{"test-the-map", "test-the-deployment", "test-the-service"},
808 },
809 {
810 directory: filepath.Join(dir, "kustomization.yaml"),
811 expectErr: true,
812 errMsg: "must build at directory",
813 },
814 {
815 directory: "../../artifacts/kustomization/should-not-load.yaml",
816 expectErr: true,
817 errMsg: "must build at directory",
818 },
819 }
820 for _, tt := range tests {
821 b := newDefaultBuilder().
822 FilenameParam(false, &FilenameOptions{Kustomize: tt.directory}).
823 NamespaceParam("test").DefaultNamespace()
824 test := &testVisitor{}
825 err := b.Do().Visit(test.Handle)
826 if tt.expectErr {
827 if err == nil {
828 t.Fatalf("expected error unhappened")
829 }
830 if !strings.Contains(err.Error(), tt.errMsg) {
831 t.Fatalf("expected %s but got %s", tt.errMsg, err.Error())
832 }
833 } else {
834 if err != nil || len(test.Infos) < tt.number {
835 t.Fatalf("unexpected response: %v %#v", err, test.Infos)
836 }
837 contained := func(name string) bool {
838 for _, info := range test.Infos {
839 if info.Name == name && info.Namespace == "test" && info.Object != nil {
840 return true
841 }
842 }
843 return false
844 }
845
846 allFound := true
847 for _, name := range tt.expectedNames {
848 if !contained(name) {
849 allFound = false
850 }
851 }
852 if !allFound {
853 t.Errorf("unexpected responses: %#v", test.Infos)
854 }
855 }
856 }
857 }
858
859 func TestNamespaceOverride(t *testing.T) {
860 s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
861 w.WriteHeader(http.StatusOK)
862 w.Write([]byte(runtime.EncodeOrDie(corev1Codec, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "test"}})))
863 }))
864 defer s.Close()
865
866 b := newDefaultBuilder().
867 FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{s.URL}}).
868 NamespaceParam("test")
869
870 test := &testVisitor{}
871
872 err := b.Do().Visit(test.Handle)
873 if err != nil || len(test.Infos) != 1 && test.Infos[0].Namespace != "foo" {
874 t.Fatalf("unexpected response: %v %#v", err, test.Infos)
875 }
876
877 b = newDefaultBuilder().
878 FilenameParam(true, &FilenameOptions{Recursive: false, Filenames: []string{s.URL}}).
879 NamespaceParam("test")
880
881 test = &testVisitor{}
882
883 err = b.Do().Visit(test.Handle)
884 if err == nil {
885 t.Fatalf("expected namespace error. got: %#v", test.Infos)
886 }
887 }
888
889 func TestURLBuilder(t *testing.T) {
890 s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
891 w.WriteHeader(http.StatusOK)
892 w.Write([]byte(runtime.EncodeOrDie(corev1Codec, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "test"}})))
893 w.Write([]byte(runtime.EncodeOrDie(corev1Codec, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "test1"}})))
894 }))
895 defer s.Close()
896
897 b := newDefaultBuilder().
898 FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{s.URL}}).
899 NamespaceParam("foo")
900
901 test := &testVisitor{}
902
903 err := b.Do().Visit(test.Handle)
904 if err != nil || len(test.Infos) != 2 {
905 t.Fatalf("unexpected response: %v %#v", err, test.Infos)
906 }
907 info := test.Infos[0]
908 if info.Name != "test" || info.Namespace != "foo" || info.Object == nil {
909 t.Errorf("unexpected info: %#v", info)
910 }
911
912 info = test.Infos[1]
913 if info.Name != "test1" || info.Namespace != "foo" || info.Object == nil {
914 t.Errorf("unexpected info: %#v", info)
915 }
916
917 }
918
919 func TestURLBuilderRequireNamespace(t *testing.T) {
920 s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
921 w.WriteHeader(http.StatusOK)
922 w.Write([]byte(runtime.EncodeOrDie(corev1Codec, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "test"}})))
923 }))
924 defer s.Close()
925
926 b := newDefaultBuilder().
927 FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{s.URL}}).
928 NamespaceParam("test").RequireNamespace()
929
930 test := &testVisitor{}
931 singleItemImplied := false
932
933 err := b.Do().IntoSingleItemImplied(&singleItemImplied).Visit(test.Handle)
934 if err == nil || !singleItemImplied || len(test.Infos) != 0 {
935 t.Fatalf("unexpected response: %v %t %#v", err, singleItemImplied, test.Infos)
936 }
937 }
938
939 func TestReplaceAliases(t *testing.T) {
940 tests := []struct {
941 name string
942 arg string
943 expected string
944 }{
945 {
946 name: "no-replacement",
947 arg: "service",
948 expected: "service",
949 },
950 {
951 name: "all-replacement",
952 arg: "all",
953 expected: "pods,replicationcontrollers,services,statefulsets.apps,horizontalpodautoscalers.autoscaling,jobs.batch,cronjobs.batch,daemonsets.extensions,deployments.extensions,replicasets.extensions",
954 },
955 {
956 name: "alias-in-comma-separated-arg",
957 arg: "all,secrets",
958 expected: "pods,replicationcontrollers,services,statefulsets.apps,horizontalpodautoscalers.autoscaling,jobs.batch,cronjobs.batch,daemonsets.extensions,deployments.extensions,replicasets.extensions,secrets",
959 },
960 }
961
962 b := newDefaultBuilder()
963
964 for _, test := range tests {
965 replaced := b.ReplaceAliases(test.arg)
966 if replaced != test.expected {
967 t.Errorf("%s: unexpected argument: expected %s, got %s", test.name, test.expected, replaced)
968 }
969 }
970 }
971
972 func TestResourceByName(t *testing.T) {
973 pods, _ := testData()
974 b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{
975 "/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]),
976 })).NamespaceParam("test")
977
978 test := &testVisitor{}
979 singleItemImplied := false
980
981 if b.Do().Err() == nil {
982 t.Errorf("unexpected non-error")
983 }
984
985 b.ResourceTypeOrNameArgs(true, "pods", "foo")
986
987 err := b.Do().IntoSingleItemImplied(&singleItemImplied).Visit(test.Handle)
988 if err != nil || !singleItemImplied || len(test.Infos) != 1 {
989 t.Fatalf("unexpected response: %v %t %#v", err, singleItemImplied, test.Infos)
990 }
991 if !apiequality.Semantic.DeepEqual(&pods.Items[0], test.Objects()[0]) {
992 t.Errorf("unexpected object: %#v", test.Objects()[0])
993 }
994
995 mapping, err := b.Do().ResourceMapping()
996 if err != nil {
997 t.Fatalf("unexpected error: %v", err)
998 }
999 if mapping.Resource != (schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}) {
1000 t.Errorf("unexpected resource mapping: %#v", mapping)
1001 }
1002 }
1003 func TestSubresourceByName(t *testing.T) {
1004 pod := subresourceTestData("foo")
1005 b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{
1006 "/namespaces/test/pods/foo/status": runtime.EncodeOrDie(corev1Codec, pod),
1007 })).NamespaceParam("test")
1008
1009 test := &testVisitor{}
1010 singleItemImplied := false
1011
1012 if b.Do().Err() == nil {
1013 t.Errorf("unexpected non-error")
1014 }
1015
1016 b.ResourceTypeOrNameArgs(true, "pods", "foo").Subresource("status")
1017
1018 err := b.Do().IntoSingleItemImplied(&singleItemImplied).Visit(test.Handle)
1019 if err != nil || !singleItemImplied || len(test.Infos) != 1 {
1020 t.Fatalf("unexpected response: %v %t %#v", err, singleItemImplied, test.Infos)
1021 }
1022 if !apiequality.Semantic.DeepEqual(pod, test.Objects()[0]) {
1023 t.Errorf("unexpected object: %#v", test.Objects()[0])
1024 }
1025
1026 mapping, err := b.Do().ResourceMapping()
1027 if err != nil {
1028 t.Fatalf("unexpected error: %v", err)
1029 }
1030 if mapping.Resource != (schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}) {
1031 t.Errorf("unexpected resource mapping: %#v", mapping)
1032 }
1033 }
1034
1035 func TestRestMappingErrors(t *testing.T) {
1036 pods, _ := testData()
1037 b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{
1038 "/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]),
1039 })).NamespaceParam("test")
1040
1041 if b.Do().Err() == nil {
1042 t.Errorf("unexpected non-error")
1043 }
1044
1045 test := &testVisitor{}
1046 singleItemImplied := false
1047
1048 b.ResourceTypeOrNameArgs(true, "foo", "bar")
1049
1050
1051 err := b.Do().IntoSingleItemImplied(&singleItemImplied).Visit(test.Handle)
1052 if err != nil {
1053 if !strings.Contains(err.Error(), "server doesn't have a resource type \"foo\"") {
1054 t.Fatalf("unexpected error: %v", err)
1055 }
1056 }
1057
1058 expectedErr := fmt.Errorf("expected error")
1059 b = newDefaultBuilderWithMapperError(fakeClientWith("", t, map[string]string{
1060 "/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]),
1061 }), expectedErr).NamespaceParam("test")
1062
1063 if b.Do().Err() == nil {
1064 t.Errorf("unexpected non-error")
1065 }
1066
1067
1068
1069 b.ResourceTypeOrNameArgs(true, "foo", "bar")
1070
1071
1072 err = b.Do().IntoSingleItemImplied(&singleItemImplied).Visit(test.Handle)
1073 if err != nil && !strings.Contains(err.Error(), expectedErr.Error()) {
1074 t.Fatalf("unexpected error: %v", err)
1075 }
1076 }
1077
1078 func TestMultipleResourceByTheSameName(t *testing.T) {
1079 pods, svcs := testData()
1080 b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{
1081 "/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]),
1082 "/namespaces/test/pods/baz": runtime.EncodeOrDie(corev1Codec, &pods.Items[1]),
1083 "/namespaces/test/services/foo": runtime.EncodeOrDie(corev1Codec, &svcs.Items[0]),
1084 "/namespaces/test/services/baz": runtime.EncodeOrDie(corev1Codec, &svcs.Items[0]),
1085 })).
1086 NamespaceParam("test")
1087
1088 test := &testVisitor{}
1089 singleItemImplied := false
1090
1091 if b.Do().Err() == nil {
1092 t.Errorf("unexpected non-error")
1093 }
1094
1095 b.ResourceTypeOrNameArgs(true, "pods,services", "foo", "baz")
1096
1097 err := b.Do().IntoSingleItemImplied(&singleItemImplied).Visit(test.Handle)
1098 if err != nil || singleItemImplied || len(test.Infos) != 4 {
1099 t.Fatalf("unexpected response: %v %t %#v", err, singleItemImplied, test.Infos)
1100 }
1101 if !apiequality.Semantic.DeepDerivative([]runtime.Object{&pods.Items[0], &pods.Items[1], &svcs.Items[0], &svcs.Items[0]}, test.Objects()) {
1102 t.Errorf("unexpected visited objects: %#v", test.Objects())
1103 }
1104
1105 if _, err := b.Do().ResourceMapping(); err == nil {
1106 t.Errorf("unexpected non-error")
1107 }
1108 }
1109
1110 func TestRequestModifier(t *testing.T) {
1111 for _, tc := range []struct {
1112 name string
1113 f func(t *testing.T, got **rest.Request) *Builder
1114 }{
1115 {
1116 name: "simple",
1117 f: func(t *testing.T, got **rest.Request) *Builder {
1118 return newDefaultBuilderWith(fakeClientWith(t.Name(), t, nil)).
1119 NamespaceParam("foo").
1120 TransformRequests(func(req *rest.Request) {
1121 *got = req
1122 }).
1123 ResourceNames("", "services/baz").
1124 RequireObject(false)
1125 },
1126 },
1127 {
1128 name: "flatten",
1129 f: func(t *testing.T, got **rest.Request) *Builder {
1130 pods, _ := testData()
1131 return newDefaultBuilderWith(fakeClientWith(t.Name(), t, map[string]string{
1132 "/namespaces/foo/pods": runtime.EncodeOrDie(corev1Codec, pods),
1133 })).
1134 NamespaceParam("foo").
1135 TransformRequests(func(req *rest.Request) {
1136 *got = req
1137 }).
1138 ResourceTypeOrNameArgs(true, "pods").
1139 Flatten()
1140 },
1141 },
1142 } {
1143 t.Run(tc.name, func(t *testing.T) {
1144 var got *rest.Request
1145 b := tc.f(t, &got)
1146 i, err := b.Do().Infos()
1147 if err != nil {
1148 t.Fatal(err)
1149 }
1150 req := i[0].Client.Get()
1151 if got != req {
1152 t.Fatalf("request was not received by modifier: %#v", req)
1153 }
1154 })
1155 }
1156 }
1157
1158 func TestResourceNames(t *testing.T) {
1159 pods, svc := testData()
1160 b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{
1161 "/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]),
1162 "/namespaces/test/services/baz": runtime.EncodeOrDie(corev1Codec, &svc.Items[0]),
1163 })).NamespaceParam("test")
1164
1165 test := &testVisitor{}
1166
1167 if b.Do().Err() == nil {
1168 t.Errorf("unexpected non-error")
1169 }
1170
1171 b.ResourceNames("pods", "foo", "services/baz")
1172
1173 err := b.Do().Visit(test.Handle)
1174 if err != nil || len(test.Infos) != 2 {
1175 t.Fatalf("unexpected response: %v %#v", err, test.Infos)
1176 }
1177 if !apiequality.Semantic.DeepEqual(&pods.Items[0], test.Objects()[0]) {
1178 t.Errorf("unexpected object: \n%#v, expected: \n%#v", test.Objects()[0], &pods.Items[0])
1179 }
1180 if !apiequality.Semantic.DeepEqual(&svc.Items[0], test.Objects()[1]) {
1181 t.Errorf("unexpected object: \n%#v, expected: \n%#v", test.Objects()[1], &svc.Items[0])
1182 }
1183 }
1184
1185 func TestResourceNamesWithoutResource(t *testing.T) {
1186 pods, svc := testData()
1187 b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{
1188 "/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]),
1189 "/namespaces/test/services/baz": runtime.EncodeOrDie(corev1Codec, &svc.Items[0]),
1190 })).NamespaceParam("test")
1191
1192 test := &testVisitor{}
1193
1194 if b.Do().Err() == nil {
1195 t.Errorf("unexpected non-error")
1196 }
1197
1198 b.ResourceNames("", "foo", "services/baz")
1199
1200 err := b.Do().Visit(test.Handle)
1201 if err == nil || !strings.Contains(err.Error(), "must be RESOURCE/NAME") {
1202 t.Fatalf("unexpected response: %v", err)
1203 }
1204 }
1205
1206 func TestResourceByNameWithoutRequireObject(t *testing.T) {
1207 b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{})).NamespaceParam("test")
1208
1209 test := &testVisitor{}
1210 singleItemImplied := false
1211
1212 if b.Do().Err() == nil {
1213 t.Errorf("unexpected non-error")
1214 }
1215
1216 b.ResourceTypeOrNameArgs(true, "pods", "foo").RequireObject(false)
1217
1218 err := b.Do().IntoSingleItemImplied(&singleItemImplied).Visit(test.Handle)
1219 if err != nil || !singleItemImplied || len(test.Infos) != 1 {
1220 t.Fatalf("unexpected response: %v %t %#v", err, singleItemImplied, test.Infos)
1221 }
1222 if test.Infos[0].Name != "foo" {
1223 t.Errorf("unexpected name: %#v", test.Infos[0].Name)
1224 }
1225 if test.Infos[0].Object != nil {
1226 t.Errorf("unexpected object: %#v", test.Infos[0].Object)
1227 }
1228
1229 mapping, err := b.Do().ResourceMapping()
1230 if err != nil {
1231 t.Fatalf("unexpected error: %v", err)
1232 }
1233 if mapping.GroupVersionKind.Kind != "Pod" || mapping.Resource != (schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}) {
1234 t.Errorf("unexpected resource mapping: %#v", mapping)
1235 }
1236 }
1237
1238 func TestResourceByNameAndEmptySelector(t *testing.T) {
1239 pods, _ := testData()
1240 b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{
1241 "/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]),
1242 })).
1243 NamespaceParam("test").
1244 LabelSelectorParam("").
1245 ResourceTypeOrNameArgs(true, "pods", "foo")
1246
1247 singleItemImplied := false
1248 infos, err := b.Do().IntoSingleItemImplied(&singleItemImplied).Infos()
1249 if err != nil || !singleItemImplied || len(infos) != 1 {
1250 t.Fatalf("unexpected response: %v %t %#v", err, singleItemImplied, infos)
1251 }
1252 if !apiequality.Semantic.DeepEqual(&pods.Items[0], infos[0].Object) {
1253 t.Errorf("unexpected object: %#v", infos[0])
1254 }
1255
1256 mapping, err := b.Do().ResourceMapping()
1257 if err != nil {
1258 t.Fatalf("unexpected error: %v", err)
1259 }
1260 if mapping.Resource != (schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}) {
1261 t.Errorf("unexpected resource mapping: %#v", mapping)
1262 }
1263 }
1264
1265 func TestLabelSelector(t *testing.T) {
1266 pods, svc := testData()
1267 labelKey := metav1.LabelSelectorQueryParam(corev1GV.String())
1268 b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{
1269 "/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, pods),
1270 "/namespaces/test/services?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, svc),
1271 })).
1272 LabelSelectorParam("a=b").
1273 NamespaceParam("test").
1274 Flatten()
1275
1276 test := &testVisitor{}
1277 singleItemImplied := false
1278
1279 if b.Do().Err() == nil {
1280 t.Errorf("unexpected non-error")
1281 }
1282
1283 b.ResourceTypeOrNameArgs(true, "pods,service")
1284
1285 err := b.Do().IntoSingleItemImplied(&singleItemImplied).Visit(test.Handle)
1286 if err != nil || singleItemImplied || len(test.Infos) != 3 {
1287 t.Fatalf("unexpected response: %v %t %#v", err, singleItemImplied, test.Infos)
1288 }
1289 if !apiequality.Semantic.DeepDerivative([]runtime.Object{&pods.Items[0], &pods.Items[1], &svc.Items[0]}, test.Objects()) {
1290 t.Errorf("unexpected visited objects: %#v", test.Objects())
1291 }
1292
1293 if _, err := b.Do().ResourceMapping(); err == nil {
1294 t.Errorf("unexpected non-error")
1295 }
1296 }
1297
1298 func TestLabelSelectorRequiresKnownTypes(t *testing.T) {
1299 b := newDefaultBuilder().
1300 LabelSelectorParam("a=b").
1301 NamespaceParam("test").
1302 ResourceTypes("unknown")
1303
1304 if b.Do().Err() == nil {
1305 t.Errorf("unexpected non-error")
1306 }
1307 }
1308
1309 func TestFieldSelector(t *testing.T) {
1310 pods, svc := testData()
1311 fieldKey := metav1.FieldSelectorQueryParam(corev1GV.String())
1312 b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{
1313 "/namespaces/test/pods?" + fieldKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, pods),
1314 "/namespaces/test/services?" + fieldKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, svc),
1315 })).
1316 FieldSelectorParam("a=b").
1317 NamespaceParam("test").
1318 Flatten()
1319
1320 test := &testVisitor{}
1321 singleItemImplied := false
1322
1323 if b.Do().Err() == nil {
1324 t.Errorf("unexpected non-error")
1325 }
1326
1327 b.ResourceTypeOrNameArgs(true, "pods,service")
1328
1329 err := b.Do().IntoSingleItemImplied(&singleItemImplied).Visit(test.Handle)
1330 if err != nil || singleItemImplied || len(test.Infos) != 3 {
1331 t.Fatalf("unexpected response: %v %t %#v", err, singleItemImplied, test.Infos)
1332 }
1333 if !apiequality.Semantic.DeepDerivative([]runtime.Object{&pods.Items[0], &pods.Items[1], &svc.Items[0]}, test.Objects()) {
1334 t.Errorf("unexpected visited objects: %#v", test.Objects())
1335 }
1336
1337 if _, err := b.Do().ResourceMapping(); err == nil {
1338 t.Errorf("unexpected non-error")
1339 }
1340 }
1341
1342 func TestFieldSelectorRequiresKnownTypes(t *testing.T) {
1343 b := newDefaultBuilder().
1344 FieldSelectorParam("a=b").
1345 NamespaceParam("test").
1346 ResourceTypes("unknown")
1347
1348 if b.Do().Err() == nil {
1349 t.Errorf("unexpected non-error")
1350 }
1351 }
1352 func TestNoSelectorUnknowResourceType(t *testing.T) {
1353 b := newDefaultBuilder().
1354 NamespaceParam("test").
1355 ResourceTypeOrNameArgs(false, "unknown")
1356
1357 err := b.Do().Err()
1358 if err != nil {
1359 if !strings.Contains(err.Error(), "server doesn't have a resource type \"unknown\"") {
1360 t.Fatalf("unexpected error: %v", err)
1361 }
1362 }
1363 }
1364 func TestSingleResourceType(t *testing.T) {
1365 b := newDefaultBuilder().
1366 LabelSelectorParam("a=b").
1367 SingleResourceType().
1368 ResourceTypeOrNameArgs(true, "pods,services")
1369
1370 if b.Do().Err() == nil {
1371 t.Errorf("unexpected non-error")
1372 }
1373 }
1374
1375 func TestResourceTuple(t *testing.T) {
1376 expectNoErr := func(err error) bool { return err == nil }
1377 expectErr := func(err error) bool { return err != nil }
1378 testCases := map[string]struct {
1379 args []string
1380 subresource string
1381 errFn func(error) bool
1382 }{
1383 "valid": {
1384 args: []string{"pods/foo"},
1385 errFn: expectNoErr,
1386 },
1387 "valid multiple with name indirection": {
1388 args: []string{"pods/foo", "pod/bar"},
1389 errFn: expectNoErr,
1390 },
1391 "valid multiple with namespaced and non-namespaced types": {
1392 args: []string{"nodes/foo", "pod/bar"},
1393 errFn: expectNoErr,
1394 },
1395 "mixed arg types": {
1396 args: []string{"pods/foo", "bar"},
1397 errFn: expectErr,
1398 },
1399
1403 "comma in resource": {
1404 args: []string{",pods/foo"},
1405 errFn: expectErr,
1406 },
1407 "multiple types in resource": {
1408 args: []string{"pods,services/foo"},
1409 errFn: expectErr,
1410 },
1411 "unknown resource type": {
1412 args: []string{"unknown/foo"},
1413 errFn: expectErr,
1414 },
1415 "leading slash": {
1416 args: []string{"/bar"},
1417 errFn: expectErr,
1418 },
1419 "trailing slash": {
1420 args: []string{"bar/"},
1421 errFn: expectErr,
1422 },
1423 "valid status subresource": {
1424 args: []string{"pods/foo"},
1425 subresource: "status",
1426 errFn: expectNoErr,
1427 },
1428 "valid status subresource for multiple with name indirection": {
1429 args: []string{"pods/foo", "pod/bar"},
1430 subresource: "status",
1431 errFn: expectNoErr,
1432 },
1433 }
1434 for k, tt := range testCases {
1435 t.Run("using default namespace", func(t *testing.T) {
1436 for _, requireObject := range []bool{true, false} {
1437 expectedRequests := map[string]string{}
1438 if requireObject {
1439 pods, _ := testData()
1440 expectedRequests = map[string]string{
1441 "/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]),
1442 "/namespaces/test/pods/bar": runtime.EncodeOrDie(corev1Codec, &pods.Items[0]),
1443 "/namespaces/test/pods/foo/status": runtime.EncodeOrDie(corev1Codec, subresourceTestData("foo")),
1444 "/namespaces/test/pods/bar/status": runtime.EncodeOrDie(corev1Codec, subresourceTestData("bar")),
1445 "/nodes/foo": runtime.EncodeOrDie(corev1Codec, &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "foo"}}),
1446 }
1447 }
1448 b := newDefaultBuilderWith(fakeClientWith(k, t, expectedRequests)).
1449 NamespaceParam("test").DefaultNamespace().
1450 ResourceTypeOrNameArgs(true, tt.args...).
1451 RequireObject(requireObject).
1452 Subresource(tt.subresource)
1453
1454 r := b.Do()
1455
1456 if !tt.errFn(r.Err()) {
1457 t.Errorf("%s: unexpected error: %v", k, r.Err())
1458 }
1459 if r.Err() != nil {
1460 continue
1461 }
1462 switch {
1463 case (r.singleItemImplied && len(tt.args) != 1),
1464 (!r.singleItemImplied && len(tt.args) == 1):
1465 t.Errorf("%s: result had unexpected singleItemImplied value", k)
1466 }
1467 info, err := r.Infos()
1468 if err != nil {
1469
1470 continue
1471 }
1472 if len(info) != len(tt.args) {
1473 t.Errorf("%s: unexpected number of infos returned: %#v", k, info)
1474 }
1475 }
1476 })
1477 }
1478 }
1479
1480 func TestStream(t *testing.T) {
1481 r, pods, rc := streamTestData()
1482 b := newDefaultBuilder().
1483 NamespaceParam("test").Stream(r, "STDIN").Flatten()
1484
1485 test := &testVisitor{}
1486 singleItemImplied := false
1487
1488 err := b.Do().IntoSingleItemImplied(&singleItemImplied).Visit(test.Handle)
1489 if err != nil || singleItemImplied || len(test.Infos) != 3 {
1490 t.Fatalf("unexpected response: %v %t %#v", err, singleItemImplied, test.Infos)
1491 }
1492 if !apiequality.Semantic.DeepDerivative([]runtime.Object{&pods.Items[0], &pods.Items[1], &rc.Items[0]}, test.Objects()) {
1493 t.Errorf("unexpected visited objects: %#v", test.Objects())
1494 }
1495 }
1496
1497 func TestYAMLStream(t *testing.T) {
1498 r, pods, rc := streamYAMLTestData()
1499 b := newDefaultBuilder().
1500 NamespaceParam("test").Stream(r, "STDIN").Flatten()
1501
1502 test := &testVisitor{}
1503 singleItemImplied := false
1504
1505 err := b.Do().IntoSingleItemImplied(&singleItemImplied).Visit(test.Handle)
1506 if err != nil || singleItemImplied || len(test.Infos) != 3 {
1507 t.Fatalf("unexpected response: %v %t %#v", err, singleItemImplied, test.Infos)
1508 }
1509 if !apiequality.Semantic.DeepDerivative([]runtime.Object{&pods.Items[0], &pods.Items[1], &rc.Items[0]}, test.Objects()) {
1510 t.Errorf("unexpected visited objects: %#v", test.Objects())
1511 }
1512 }
1513
1514 func TestMultipleObject(t *testing.T) {
1515 r, pods, svc := streamTestData()
1516 obj, err := newDefaultBuilder().
1517 NamespaceParam("test").Stream(r, "STDIN").Flatten().
1518 Do().Object()
1519
1520 if err != nil {
1521 t.Fatalf("unexpected error: %v", err)
1522 }
1523
1524 expected := &v1.List{
1525 Items: []runtime.RawExtension{
1526 {Object: &pods.Items[0]},
1527 {Object: &pods.Items[1]},
1528 {Object: &svc.Items[0]},
1529 },
1530 }
1531 if !apiequality.Semantic.DeepDerivative(expected, obj) {
1532 t.Errorf("unexpected visited objects: %#v", obj)
1533 }
1534 }
1535
1536 func TestContinueOnErrorVisitor(t *testing.T) {
1537 r, _, _ := streamTestData()
1538 req := newDefaultBuilder().
1539 ContinueOnError().
1540 NamespaceParam("test").Stream(r, "STDIN").Flatten().
1541 Do()
1542 count := 0
1543 testErr := fmt.Errorf("test error")
1544 err := req.Visit(func(_ *Info, _ error) error {
1545 count++
1546 if count > 1 {
1547 return testErr
1548 }
1549 return nil
1550 })
1551 if err == nil {
1552 t.Fatalf("unexpected error: %v", err)
1553 }
1554 if count != 3 {
1555 t.Fatalf("did not visit all infos: %d", count)
1556 }
1557 agg, ok := err.(utilerrors.Aggregate)
1558 if !ok {
1559 t.Fatalf("unexpected error: %v", err)
1560 }
1561 if len(agg.Errors()) != 2 || agg.Errors()[0] != testErr || agg.Errors()[1] != testErr {
1562 t.Fatalf("unexpected error: %v", err)
1563 }
1564 }
1565
1566 func TestSingleItemImpliedObject(t *testing.T) {
1567 obj, err := newDefaultBuilder().
1568 NamespaceParam("test").DefaultNamespace().
1569 FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../artifacts/guestbook/redis-master-controller.yaml"}}).
1570 Flatten().
1571 Do().Object()
1572
1573 if err != nil {
1574 t.Fatalf("unexpected error: %v", err)
1575 }
1576
1577 rc, ok := obj.(*v1.ReplicationController)
1578 if !ok {
1579 t.Fatalf("unexpected object: %#v", obj)
1580 }
1581 if rc.Name != "redis-master" || rc.Namespace != "test" {
1582 t.Errorf("unexpected controller: %#v", rc)
1583 }
1584 }
1585
1586 func TestSingleItemImpliedObjectNoExtension(t *testing.T) {
1587 obj, err := newDefaultBuilder().
1588 NamespaceParam("test").DefaultNamespace().
1589 FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../artifacts/pod.yaml"}}).
1590 Flatten().
1591 Do().Object()
1592
1593 if err != nil {
1594 t.Fatalf("unexpected error: %v", err)
1595 }
1596
1597 pod, ok := obj.(*v1.Pod)
1598 if !ok {
1599 t.Fatalf("unexpected object: %#v", obj)
1600 }
1601 if pod.Name != "nginx" || pod.Namespace != "test" {
1602 t.Errorf("unexpected pod: %#v", pod)
1603 }
1604 }
1605
1606 func TestSingleItemImpliedRootScopedObject(t *testing.T) {
1607 node := &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "test"}}
1608 r := streamTestObject(node)
1609 infos, err := newDefaultBuilder().
1610 NamespaceParam("test").DefaultNamespace().
1611 Stream(r, "STDIN").
1612 Flatten().
1613 Do().Infos()
1614
1615 if err != nil || len(infos) != 1 {
1616 t.Fatalf("unexpected error: %v", err)
1617 }
1618
1619 if infos[0].Namespace != "" {
1620 t.Errorf("namespace should be empty: %#v", infos[0])
1621 }
1622 n, ok := infos[0].Object.(*v1.Node)
1623 if !ok {
1624 t.Fatalf("unexpected object: %#v", infos[0].Object)
1625 }
1626 if n.Name != "test" || n.Namespace != "" {
1627 t.Errorf("unexpected object: %#v", n)
1628 }
1629 }
1630
1631 func TestListObject(t *testing.T) {
1632 pods, _ := testData()
1633 labelKey := metav1.LabelSelectorQueryParam(corev1GV.String())
1634 b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{
1635 "/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, pods),
1636 })).
1637 LabelSelectorParam("a=b").
1638 NamespaceParam("test").
1639 ResourceTypeOrNameArgs(true, "pods").
1640 Flatten()
1641
1642 obj, err := b.Do().Object()
1643 if err != nil {
1644 t.Fatalf("unexpected error: %v", err)
1645 }
1646
1647 list, ok := obj.(*v1.List)
1648 if !ok {
1649 t.Fatalf("unexpected object: %#v", obj)
1650 }
1651 if list.ResourceVersion != pods.ResourceVersion || len(list.Items) != 2 {
1652 t.Errorf("unexpected list: %#v", list)
1653 }
1654
1655 mapping, err := b.Do().ResourceMapping()
1656 if err != nil {
1657 t.Fatalf("unexpected error: %v", err)
1658 }
1659 if mapping.Resource != (schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}) {
1660 t.Errorf("unexpected resource mapping: %#v", mapping)
1661 }
1662 }
1663
1664 func TestListObjectWithDifferentVersions(t *testing.T) {
1665 pods, svc := testData()
1666 labelKey := metav1.LabelSelectorQueryParam(corev1GV.String())
1667 obj, err := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{
1668 "/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, pods),
1669 "/namespaces/test/services?" + labelKey + "=a%3Db": runtime.EncodeOrDie(corev1Codec, svc),
1670 })).
1671 LabelSelectorParam("a=b").
1672 NamespaceParam("test").
1673 ResourceTypeOrNameArgs(true, "pods,services").
1674 Flatten().
1675 Do().Object()
1676
1677 if err != nil {
1678 t.Fatalf("unexpected error: %v", err)
1679 }
1680
1681 list, ok := obj.(*v1.List)
1682 if !ok {
1683 t.Fatalf("unexpected object: %#v", obj)
1684 }
1685
1686 if list.ResourceVersion != "" || len(list.Items) != 3 {
1687 t.Errorf("unexpected list: %#v", list)
1688 }
1689 }
1690
1691 func TestListObjectSubresource(t *testing.T) {
1692 pods, _ := testData()
1693 labelKey := metav1.LabelSelectorQueryParam(corev1GV.String())
1694 b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{
1695 "/namespaces/test/pods?" + labelKey: runtime.EncodeOrDie(corev1Codec, pods),
1696 })).
1697 NamespaceParam("test").
1698 ResourceTypeOrNameArgs(true, "pods").
1699 Subresource("status").
1700 Flatten()
1701
1702 _, err := b.Do().Object()
1703 if err == nil || !strings.Contains(err.Error(), "subresource cannot be used when bulk resources are specified") {
1704 t.Fatalf("unexpected response: %v", err)
1705 }
1706 }
1707
1708 func TestWatch(t *testing.T) {
1709 _, svc := testData()
1710 w, err := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{
1711 "/namespaces/test/services?fieldSelector=metadata.name%3Dredis-master&resourceVersion=12&watch=true": watchBody(watch.Event{
1712 Type: watch.Added,
1713 Object: &svc.Items[0],
1714 }),
1715 })).
1716 NamespaceParam("test").DefaultNamespace().
1717 FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../artifacts/guestbook/redis-master-service.yaml"}}).Flatten().
1718 Do().Watch("12")
1719
1720 if err != nil {
1721 t.Fatalf("unexpected error: %v", err)
1722 }
1723
1724 defer w.Stop()
1725 ch := w.ResultChan()
1726 select {
1727 case obj := <-ch:
1728 if obj.Type != watch.Added {
1729 t.Fatalf("unexpected watch event %#v", obj)
1730 }
1731 service, ok := obj.Object.(*v1.Service)
1732 if !ok {
1733 t.Fatalf("unexpected object: %#v", obj)
1734 }
1735 if service.Name != "baz" || service.ResourceVersion != "12" {
1736 t.Errorf("unexpected service: %#v", service)
1737 }
1738 }
1739 }
1740
1741 func TestWatchMultipleError(t *testing.T) {
1742 _, err := newDefaultBuilder().
1743 NamespaceParam("test").DefaultNamespace().
1744 FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../artifacts/guestbook/redis-master-controller.yaml"}}).Flatten().
1745 FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{"../../artifacts/guestbook/redis-master-controller.yaml"}}).Flatten().
1746 Do().Watch("")
1747
1748 if err == nil {
1749 t.Fatalf("unexpected non-error")
1750 }
1751 }
1752
1753 func TestLatest(t *testing.T) {
1754 r, _, _ := streamTestData()
1755 newPod := &v1.Pod{
1756 ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "13"},
1757 }
1758 newPod2 := &v1.Pod{
1759 ObjectMeta: metav1.ObjectMeta{Name: "bar", Namespace: "test", ResourceVersion: "14"},
1760 }
1761 newSvc := &v1.Service{
1762 ObjectMeta: metav1.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "15"},
1763 }
1764
1765 b := newDefaultBuilderWith(fakeClientWith("", t, map[string]string{
1766 "/namespaces/test/pods/foo": runtime.EncodeOrDie(corev1Codec, newPod),
1767 "/namespaces/test/pods/bar": runtime.EncodeOrDie(corev1Codec, newPod2),
1768 "/namespaces/test/services/baz": runtime.EncodeOrDie(corev1Codec, newSvc),
1769 })).
1770 NamespaceParam("other").Stream(r, "STDIN").Flatten().Latest()
1771
1772 test := &testVisitor{}
1773 singleItemImplied := false
1774
1775 err := b.Do().IntoSingleItemImplied(&singleItemImplied).Visit(test.Handle)
1776 if err != nil || singleItemImplied || len(test.Infos) != 3 {
1777 t.Fatalf("unexpected response: %v %t %#v", err, singleItemImplied, test.Infos)
1778 }
1779 if !apiequality.Semantic.DeepDerivative([]runtime.Object{newPod, newPod2, newSvc}, test.Objects()) {
1780 t.Errorf("unexpected visited objects: %#v", test.Objects())
1781 }
1782 }
1783
1784 func TestReceiveMultipleErrors(t *testing.T) {
1785 pods, svc := testData()
1786
1787 r, w := io.Pipe()
1788 go func() {
1789 defer w.Close()
1790 w.Write([]byte(`{}`))
1791 w.Write([]byte(runtime.EncodeOrDie(corev1Codec, &pods.Items[0])))
1792 }()
1793
1794 r2, w2 := io.Pipe()
1795 go func() {
1796 defer w2.Close()
1797 w2.Write([]byte(`{}`))
1798 w2.Write([]byte(runtime.EncodeOrDie(corev1Codec, &svc.Items[0])))
1799 }()
1800
1801 b := newDefaultBuilder().
1802 Stream(r, "1").Stream(r2, "2").
1803 ContinueOnError()
1804
1805 test := &testVisitor{}
1806 singleItemImplied := false
1807
1808 err := b.Do().IntoSingleItemImplied(&singleItemImplied).Visit(test.Handle)
1809 if err == nil || singleItemImplied || len(test.Infos) != 2 {
1810 t.Fatalf("unexpected response: %v %t %#v", err, singleItemImplied, test.Infos)
1811 }
1812
1813 errs, ok := err.(utilerrors.Aggregate)
1814 if !ok {
1815 t.Fatalf("unexpected error: %v", reflect.TypeOf(err))
1816 }
1817 if len(errs.Errors()) != 2 {
1818 t.Errorf("unexpected errors %v", errs)
1819 }
1820 }
1821
1822 func TestHasNames(t *testing.T) {
1823 basename := filepath.Base(os.Args[0])
1824 tests := []struct {
1825 name string
1826 args []string
1827 expectedHasName bool
1828 expectedError error
1829 }{
1830 {
1831 name: "test1",
1832 args: []string{""},
1833 expectedHasName: false,
1834 expectedError: nil,
1835 },
1836 {
1837 name: "test2",
1838 args: []string{"rc"},
1839 expectedHasName: false,
1840 expectedError: nil,
1841 },
1842 {
1843 name: "test3",
1844 args: []string{"rc,pod,svc"},
1845 expectedHasName: false,
1846 expectedError: nil,
1847 },
1848 {
1849 name: "test4",
1850 args: []string{"rc/foo"},
1851 expectedHasName: true,
1852 expectedError: nil,
1853 },
1854 {
1855 name: "test5",
1856 args: []string{"rc", "foo"},
1857 expectedHasName: true,
1858 expectedError: nil,
1859 },
1860 {
1861 name: "test6",
1862 args: []string{"rc,pod,svc", "foo"},
1863 expectedHasName: true,
1864 expectedError: nil,
1865 },
1866 {
1867 name: "test7",
1868 args: []string{"rc/foo", "rc/bar", "rc/zee"},
1869 expectedHasName: true,
1870 expectedError: nil,
1871 },
1872 {
1873 name: "test8",
1874 args: []string{"rc/foo", "bar"},
1875 expectedHasName: false,
1876 expectedError: fmt.Errorf("there is no need to specify a resource type as a separate argument when passing arguments in resource/name form (e.g. '" + basename + " get resource/<resource_name>' instead of '" + basename + " get resource resource/<resource_name>'"),
1877 },
1878 }
1879 for _, tt := range tests {
1880 t.Run(tt.name, func(t *testing.T) {
1881 hasNames, err := HasNames(tt.args)
1882 if !reflect.DeepEqual(tt.expectedError, err) {
1883 t.Errorf("expected HasName to error:\n%s\tgot:\n%s", tt.expectedError, err)
1884 }
1885 if hasNames != tt.expectedHasName {
1886 t.Errorf("expected HasName to return %v for %s", tt.expectedHasName, tt.args)
1887 }
1888 })
1889 }
1890 }
1891
1892 func TestUnstructured(t *testing.T) {
1893
1894 tmpDir, err := os.MkdirTemp(os.TempDir(), "unstructured_test")
1895 if err != nil {
1896 t.Fatalf("error creating temp dir: %v", err)
1897 }
1898 defer os.RemoveAll(tmpDir)
1899
1900
1901 writeTestFile(t, fmt.Sprintf("%s/pod.json", tmpDir), aPod)
1902 writeTestFile(t, fmt.Sprintf("%s/badpod.json", tmpDir), aPodBadAnnotations)
1903
1904 tests := []struct {
1905 name string
1906 file string
1907 expectedError string
1908 }{
1909 {
1910 name: "pod",
1911 file: "pod.json",
1912 expectedError: "",
1913 },
1914 {
1915 name: "badpod",
1916 file: "badpod.json",
1917 expectedError: "ObjectMeta.",
1918 },
1919 }
1920
1921 for _, tc := range tests {
1922 t.Run(tc.name, func(t *testing.T) {
1923 result := newUnstructuredDefaultBuilder().
1924 ContinueOnError().
1925 FilenameParam(false, &FilenameOptions{Recursive: false, Filenames: []string{fmt.Sprintf("%s/%s", tmpDir, tc.file)}}).
1926 Flatten().
1927 Do()
1928
1929 err := result.Err()
1930 if err == nil {
1931 _, err = result.Infos()
1932 }
1933
1934 if len(tc.expectedError) == 0 {
1935 if err != nil {
1936 t.Errorf("unexpected error: %v", err)
1937 }
1938 } else {
1939 if err == nil {
1940 t.Errorf("expected error, got none")
1941 } else if !strings.Contains(err.Error(), tc.expectedError) {
1942 t.Errorf("expected error with '%s', got: %v", tc.expectedError, err)
1943 }
1944 }
1945
1946 })
1947 }
1948 }
1949
1950 func TestStdinMultiUseError(t *testing.T) {
1951 if got, want := newUnstructuredDefaultBuilder().Stdin().StdinInUse().Do().Err(), StdinMultiUseError; !errors.Is(got, want) {
1952 t.Errorf("got: %q, want: %q", got, want)
1953 }
1954 if got, want := newUnstructuredDefaultBuilder().StdinInUse().Stdin().Do().Err(), StdinMultiUseError; !errors.Is(got, want) {
1955 t.Errorf("got: %q, want: %q", got, want)
1956 }
1957 }
1958
View as plain text