From 943c57ff4e2da8545161d3964666e54d549de475 Mon Sep 17 00:00:00 2001 From: feihujiang Date: Wed, 19 Aug 2015 16:33:02 +0800 Subject: [PATCH] Allow delete multiple resources with the same name --- docs/man/man1/kubectl-delete.1 | 3 ++ docs/user-guide/kubectl/kubectl_delete.md | 5 +++- pkg/kubectl/cmd/delete.go | 3 ++ pkg/kubectl/cmd/delete_test.go | 35 +++++++++++++++++++++++ pkg/kubectl/resource/builder.go | 17 +++++++++++ pkg/kubectl/resource/builder_test.go | 32 +++++++++++++++++++++ 6 files changed, 94 insertions(+), 1 deletion(-) diff --git a/docs/man/man1/kubectl-delete.1 b/docs/man/man1/kubectl-delete.1 index ff014f0f81..d6ea5a00d2 100644 --- a/docs/man/man1/kubectl-delete.1 +++ b/docs/man/man1/kubectl-delete.1 @@ -174,6 +174,9 @@ $ kubectl delete \-f ./pod.json # Delete a pod based on the type and name in the JSON passed into stdin. $ cat pod.json | kubectl delete \-f \- +# Delete pods and services with same names "baz" and "foo" +$ kubectl delete pod,service baz foo + # Delete pods and services with label name=myLabel. $ kubectl delete pods,services \-l name=myLabel diff --git a/docs/user-guide/kubectl/kubectl_delete.md b/docs/user-guide/kubectl/kubectl_delete.md index e9b3f32e82..fef5d5d063 100644 --- a/docs/user-guide/kubectl/kubectl_delete.md +++ b/docs/user-guide/kubectl/kubectl_delete.md @@ -61,6 +61,9 @@ $ kubectl delete -f ./pod.json # Delete a pod based on the type and name in the JSON passed into stdin. $ cat pod.json | kubectl delete -f - +# Delete pods and services with same names "baz" and "foo" +$ kubectl delete pod,service baz foo + # Delete pods and services with label name=myLabel. $ kubectl delete pods,services -l name=myLabel @@ -118,7 +121,7 @@ $ kubectl delete pods --all * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra at 2015-08-20 22:01:12.476048394 +0000 UTC +###### Auto generated by spf13/cobra at 2015-08-21 06:18:47.444397685 +0000 UTC [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_delete.md?pixel)]() diff --git a/pkg/kubectl/cmd/delete.go b/pkg/kubectl/cmd/delete.go index 4571939fad..b32a9e14cb 100644 --- a/pkg/kubectl/cmd/delete.go +++ b/pkg/kubectl/cmd/delete.go @@ -47,6 +47,9 @@ $ kubectl delete -f ./pod.json # Delete a pod based on the type and name in the JSON passed into stdin. $ cat pod.json | kubectl delete -f - +# Delete pods and services with same names "baz" and "foo" +$ kubectl delete pod,service baz foo + # Delete pods and services with label name=myLabel. $ kubectl delete pods,services -l name=myLabel diff --git a/pkg/kubectl/cmd/delete_test.go b/pkg/kubectl/cmd/delete_test.go index 33d74ccf61..2a9d562ef8 100644 --- a/pkg/kubectl/cmd/delete_test.go +++ b/pkg/kubectl/cmd/delete_test.go @@ -335,6 +335,41 @@ func TestDeleteMultipleObjectContinueOnMissing(t *testing.T) { } } +func TestDeleteMultipleResourcesWithTheSameName(t *testing.T) { + _, svc, rc := testData() + f, tf, codec := NewAPIFactory() + tf.Printer = &testPrinter{} + tf.Client = &client.FakeRESTClient{ + Codec: codec, + Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) { + switch p, m := req.URL.Path, req.Method; { + case p == "/namespaces/test/replicationcontrollers/baz" && m == "DELETE": + return &http.Response{StatusCode: 200, Body: objBody(codec, &rc.Items[0])}, nil + case p == "/namespaces/test/replicationcontrollers/foo" && m == "DELETE": + return &http.Response{StatusCode: 200, Body: objBody(codec, &rc.Items[0])}, nil + case p == "/namespaces/test/services/baz" && m == "DELETE": + return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil + case p == "/namespaces/test/services/foo" && m == "DELETE": + return &http.Response{StatusCode: 200, Body: objBody(codec, &svc.Items[0])}, nil + default: + // Ensures no GET is performed when deleting by name + t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) + return nil, nil + } + }), + } + tf.Namespace = "test" + buf := bytes.NewBuffer([]byte{}) + cmd := NewCmdDelete(f, buf) + cmd.Flags().Set("namespace", "test") + cmd.Flags().Set("cascade", "false") + cmd.Flags().Set("output", "name") + cmd.Run(cmd, []string{"replicationcontrollers,services", "baz", "foo"}) + if buf.String() != "replicationcontroller/baz\nreplicationcontroller/foo\nservice/baz\nservice/foo\n" { + t.Errorf("unexpected output: %s", buf.String()) + } +} + func TestDeleteDirectory(t *testing.T) { _, svc, rc := testData() diff --git a/pkg/kubectl/resource/builder.go b/pkg/kubectl/resource/builder.go index c28146cd21..31bd815d17 100644 --- a/pkg/kubectl/resource/builder.go +++ b/pkg/kubectl/resource/builder.go @@ -256,6 +256,23 @@ func (b *Builder) SelectAllParam(selectAll bool) *Builder { // When two or more arguments are received, they must be a single type and resource name(s). // The allowEmptySelector permits to select all the resources (via Everything func). func (b *Builder) ResourceTypeOrNameArgs(allowEmptySelector bool, args ...string) *Builder { + // convert multiple resources to resource tuples, a,b,c d as a transform to a/d b/d c/d + if len(args) >= 2 { + resources := []string{} + resources = append(resources, SplitResourceArgument(args[0])...) + if len(resources) > 1 { + names := []string{} + names = append(names, args[1:]...) + newArgs := []string{} + for _, resource := range resources { + for _, name := range names { + newArgs = append(newArgs, strings.Join([]string{resource, name}, "/")) + } + } + args = newArgs + } + } + if ok, err := hasCombinedTypeArgs(args); ok { if err != nil { b.errs = append(b.errs, err) diff --git a/pkg/kubectl/resource/builder_test.go b/pkg/kubectl/resource/builder_test.go index 21be792319..e1ae6ddb1f 100644 --- a/pkg/kubectl/resource/builder_test.go +++ b/pkg/kubectl/resource/builder_test.go @@ -380,6 +380,38 @@ func TestResourceByName(t *testing.T) { } } +func TestMultipleResourceByTheSameName(t *testing.T) { + pods, svcs := testData() + b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith("", t, map[string]string{ + "/namespaces/test/pods/foo": runtime.EncodeOrDie(latest.Codec, &pods.Items[0]), + "/namespaces/test/pods/baz": runtime.EncodeOrDie(latest.Codec, &pods.Items[1]), + "/namespaces/test/services/foo": runtime.EncodeOrDie(latest.Codec, &svcs.Items[0]), + "/namespaces/test/services/baz": runtime.EncodeOrDie(latest.Codec, &svcs.Items[0]), + })). + NamespaceParam("test") + + test := &testVisitor{} + singular := false + + if b.Do().Err() == nil { + t.Errorf("unexpected non-error") + } + + b.ResourceTypeOrNameArgs(true, "pods,services", "foo", "baz") + + err := b.Do().IntoSingular(&singular).Visit(test.Handle) + if err != nil || singular || len(test.Infos) != 4 { + t.Fatalf("unexpected response: %v %t %#v", err, singular, test.Infos) + } + if !api.Semantic.DeepDerivative([]runtime.Object{&pods.Items[0], &pods.Items[1], &svcs.Items[0], &svcs.Items[0]}, test.Objects()) { + t.Errorf("unexpected visited objects: %#v", test.Objects()) + } + + if _, err := b.Do().ResourceMapping(); err == nil { + t.Errorf("unexpected non-error") + } +} + func TestResourceByNameWithoutRequireObject(t *testing.T) { b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith("", t, map[string]string{})). NamespaceParam("test")