diff --git a/pkg/kubectl/cmd/delete.go b/pkg/kubectl/cmd/delete.go index f5a2ddf3601..545f6c8f584 100644 --- a/pkg/kubectl/cmd/delete.go +++ b/pkg/kubectl/cmd/delete.go @@ -87,7 +87,7 @@ func RunDelete(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []str FilenameParam(filenames...). SelectorParam(cmdutil.GetFlagString(cmd, "selector")). SelectAllParam(cmdutil.GetFlagBool(cmd, "all")). - ResourceTypeOrNameArgs(false, args...). + ResourceTypeOrNameArgs(false, args...).RequireObject(false). Flatten(). Do() err = r.Err() diff --git a/pkg/kubectl/cmd/delete_test.go b/pkg/kubectl/cmd/delete_test.go index 44a6b45aa0d..98fa13b904a 100644 --- a/pkg/kubectl/cmd/delete_test.go +++ b/pkg/kubectl/cmd/delete_test.go @@ -27,6 +27,66 @@ import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/client" ) +func TestDeleteObjectByTuple(t *testing.T) { + _, _, 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/redis-master-controller" && m == "DELETE": + return &http.Response{StatusCode: 200, Body: objBody(codec, &rc.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.Run(cmd, []string{"replicationcontrollers/redis-master-controller"}) + + if buf.String() != "replicationControllers/redis-master-controller\n" { + t.Errorf("unexpected output: %s", buf.String()) + } +} + +func TestDeleteNamedObject(t *testing.T) { + _, _, 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/redis-master-controller" && m == "DELETE": + return &http.Response{StatusCode: 200, Body: objBody(codec, &rc.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.Run(cmd, []string{"replicationcontrollers", "redis-master-controller"}) + + if buf.String() != "replicationControllers/redis-master-controller\n" { + t.Errorf("unexpected output: %s", buf.String()) + } +} + func TestDeleteObject(t *testing.T) { _, _, rc := testData() diff --git a/pkg/kubectl/resource/builder.go b/pkg/kubectl/resource/builder.go index 5ce4d1c77cc..43dc3883a7c 100644 --- a/pkg/kubectl/resource/builder.go +++ b/pkg/kubectl/resource/builder.go @@ -58,6 +58,8 @@ type Builder struct { flatten bool latest bool + requireObject bool + singleResourceType bool continueOnError bool } @@ -70,7 +72,8 @@ type resourceTuple struct { // NewBuilder creates a builder that operates on generic objects. func NewBuilder(mapper meta.RESTMapper, typer runtime.ObjectTyper, clientMapper ClientMapper) *Builder { return &Builder{ - mapper: &Mapper{typer, mapper, clientMapper}, + mapper: &Mapper{typer, mapper, clientMapper}, + requireObject: true, } } @@ -327,6 +330,12 @@ func (b *Builder) Latest() *Builder { return b } +// RequireObject ensures that resulting infos have an object set. If false, resulting info may not have an object set. +func (b *Builder) RequireObject(require bool) *Builder { + b.requireObject = require + return b +} + // ContinueOnError will attempt to load and visit as many objects as possible, even if some visits // return errors or some objects cannot be loaded. The default behavior is to terminate after // the first error is returned from a VisitorFunc. @@ -537,9 +546,6 @@ func (b *Builder) visitorResult() *Result { visitors := []Visitor{} for _, name := range b.names { info := NewInfo(client, mapping, selectorNamespace, name) - if err := info.Get(); err != nil { - return &Result{singular: isSingular, err: err} - } visitors = append(visitors, info) } return &Result{singular: isSingular, visitor: VisitorList(visitors), sources: visitors} @@ -593,7 +599,7 @@ func (b *Builder) Do() *Result { helpers = append(helpers, RequireNamespace(b.namespace)) } helpers = append(helpers, FilterNamespace) - if b.latest { + if b.requireObject { helpers = append(helpers, RetrieveLazy) } r.visitor = NewDecoratedVisitor(r.visitor, helpers...) diff --git a/pkg/kubectl/resource/builder_test.go b/pkg/kubectl/resource/builder_test.go index a7a63736f35..39d185520bd 100644 --- a/pkg/kubectl/resource/builder_test.go +++ b/pkg/kubectl/resource/builder_test.go @@ -58,7 +58,7 @@ func fakeClient() ClientMapper { }) } -func fakeClientWith(t *testing.T, data map[string]string) ClientMapper { +func fakeClientWith(testName string, t *testing.T, data map[string]string) ClientMapper { return ClientMapperFunc(func(*meta.RESTMapping) (RESTClient, error) { return &client.FakeRESTClient{ Codec: latest.Codec, @@ -70,7 +70,7 @@ func fakeClientWith(t *testing.T, data map[string]string) ClientMapper { } body, ok := data[p] if !ok { - t.Fatalf("unexpected request: %s (%s)\n%#v", p, req.URL, req) + t.Fatalf("%s: unexpected request: %s (%s)\n%#v", testName, p, req.URL, req) } return &http.Response{ StatusCode: http.StatusOK, @@ -305,7 +305,7 @@ func TestURLBuilderRequireNamespace(t *testing.T) { func TestResourceByName(t *testing.T) { pods, _ := testData() - b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{ + b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith("", t, map[string]string{ "/namespaces/test/pods/foo": runtime.EncodeOrDie(latest.Codec, &pods.Items[0]), })). NamespaceParam("test") @@ -336,9 +336,42 @@ func TestResourceByName(t *testing.T) { } } +func TestResourceByNameWithoutRequireObject(t *testing.T) { + b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith("", t, map[string]string{})). + NamespaceParam("test") + + test := &testVisitor{} + singular := false + + if b.Do().Err() == nil { + t.Errorf("unexpected non-error") + } + + b.ResourceTypeOrNameArgs(true, "pods", "foo").RequireObject(false) + + err := b.Do().IntoSingular(&singular).Visit(test.Handle) + if err != nil || !singular || len(test.Infos) != 1 { + t.Fatalf("unexpected response: %v %t %#v", err, singular, test.Infos) + } + if test.Infos[0].Name != "foo" { + t.Errorf("unexpected name: %#v", test.Infos[0].Name) + } + if test.Infos[0].Object != nil { + t.Errorf("unexpected object: %#v", test.Infos[0].Object) + } + + mapping, err := b.Do().ResourceMapping() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if mapping.Kind != "Pod" || mapping.Resource != "pods" { + t.Errorf("unexpected resource mapping: %#v", mapping) + } +} + func TestResourceByNameAndEmptySelector(t *testing.T) { pods, _ := testData() - b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{ + b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith("", t, map[string]string{ "/namespaces/test/pods/foo": runtime.EncodeOrDie(latest.Codec, &pods.Items[0]), })). NamespaceParam("test"). @@ -366,7 +399,7 @@ func TestResourceByNameAndEmptySelector(t *testing.T) { func TestSelector(t *testing.T) { pods, svc := testData() labelKey := api.LabelSelectorQueryParam(testapi.Version()) - b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{ + b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith("", t, map[string]string{ "/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(latest.Codec, pods), "/namespaces/test/services?" + labelKey + "=a%3Db": runtime.EncodeOrDie(latest.Codec, svc), })). @@ -467,35 +500,43 @@ func TestResourceTuple(t *testing.T) { }, } for k, testCase := range testCases { - pods, _ := 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/bar": runtime.EncodeOrDie(latest.Codec, &pods.Items[0]), - "/nodes/foo": runtime.EncodeOrDie(latest.Codec, &api.Node{ObjectMeta: api.ObjectMeta{Name: "foo"}}), - })). - NamespaceParam("test").DefaultNamespace(). - ResourceTypeOrNameArgs(true, testCase.args...) + for _, requireObject := range []bool{true, false} { + expectedRequests := map[string]string{} + if requireObject { + pods, _ := testData() + expectedRequests = map[string]string{ + "/namespaces/test/pods/foo": runtime.EncodeOrDie(latest.Codec, &pods.Items[0]), + "/namespaces/test/pods/bar": runtime.EncodeOrDie(latest.Codec, &pods.Items[0]), + "/nodes/foo": runtime.EncodeOrDie(latest.Codec, &api.Node{ObjectMeta: api.ObjectMeta{Name: "foo"}}), + "/minions/foo": runtime.EncodeOrDie(latest.Codec, &api.Node{ObjectMeta: api.ObjectMeta{Name: "foo"}}), + } + } - r := b.Do() + b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(k, t, expectedRequests)). + NamespaceParam("test").DefaultNamespace(). + ResourceTypeOrNameArgs(true, testCase.args...).RequireObject(requireObject) - if !testCase.errFn(r.Err()) { - t.Errorf("%s: unexpected error: %v", k, r.Err()) - } - if r.Err() != nil { - continue - } - switch { - case (r.singular && len(testCase.args) != 1), - (!r.singular && len(testCase.args) == 1): - t.Errorf("%s: result had unexpected singular value", k) - } - info, err := r.Infos() - if err != nil { - // test error - continue - } - if len(info) != len(testCase.args) { - t.Errorf("%s: unexpected number of infos returned: %#v", k, info) + r := b.Do() + + if !testCase.errFn(r.Err()) { + t.Errorf("%s: unexpected error: %v", k, r.Err()) + } + if r.Err() != nil { + continue + } + switch { + case (r.singular && len(testCase.args) != 1), + (!r.singular && len(testCase.args) == 1): + t.Errorf("%s: result had unexpected singular value", k) + } + info, err := r.Infos() + if err != nil { + // test error + continue + } + if len(info) != len(testCase.args) { + t.Errorf("%s: unexpected number of infos returned: %#v", k, info) + } } } } @@ -579,7 +620,7 @@ func TestSingularObject(t *testing.T) { func TestListObject(t *testing.T) { pods, _ := testData() labelKey := api.LabelSelectorQueryParam(testapi.Version()) - b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{ + b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith("", t, map[string]string{ "/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(latest.Codec, pods), })). SelectorParam("a=b"). @@ -612,7 +653,7 @@ func TestListObject(t *testing.T) { func TestListObjectWithDifferentVersions(t *testing.T) { pods, svc := testData() labelKey := api.LabelSelectorQueryParam(testapi.Version()) - obj, err := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{ + obj, err := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith("", t, map[string]string{ "/namespaces/test/pods?" + labelKey + "=a%3Db": runtime.EncodeOrDie(latest.Codec, pods), "/namespaces/test/services?" + labelKey + "=a%3Db": runtime.EncodeOrDie(latest.Codec, svc), })). @@ -638,7 +679,7 @@ func TestListObjectWithDifferentVersions(t *testing.T) { func TestWatch(t *testing.T) { _, svc := testData() - w, err := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{ + w, err := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith("", t, map[string]string{ "/watch/namespaces/test/services/redis-master?resourceVersion=12": watchBody(watch.Event{ Type: watch.Added, Object: &svc.Items[0], @@ -693,7 +734,7 @@ func TestLatest(t *testing.T) { ObjectMeta: api.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "15"}, } - b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{ + b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith("", t, map[string]string{ "/namespaces/test/pods/foo": runtime.EncodeOrDie(latest.Codec, newPod), "/namespaces/test/pods/bar": runtime.EncodeOrDie(latest.Codec, newPod2), "/namespaces/test/services/baz": runtime.EncodeOrDie(latest.Codec, newSvc),