From d24c5b145e99362427ac4138f8cc2160a64e7d22 Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Sat, 3 Jan 2015 18:02:25 -0500 Subject: [PATCH 1/2] Allow errors to be ignored by the builder --- pkg/kubectl/resource/builder.go | 153 ------------------------ pkg/kubectl/resource/result.go | 202 ++++++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+), 153 deletions(-) create mode 100644 pkg/kubectl/resource/result.go diff --git a/pkg/kubectl/resource/builder.go b/pkg/kubectl/resource/builder.go index e589aa9325f..5bde220fc6d 100644 --- a/pkg/kubectl/resource/builder.go +++ b/pkg/kubectl/resource/builder.go @@ -21,16 +21,13 @@ import ( "io" "net/url" "os" - "reflect" "strings" - "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors" - "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" ) // Builder provides convenience functions for taking arguments and parameters @@ -408,156 +405,6 @@ func (b *Builder) Do() *Result { return r } -// Result contains helper methods for dealing with the outcome of a Builder. -type Result struct { - err error - visitor Visitor - - sources []Visitor - singular bool - - // populated by a call to Infos - info []*Info -} - -// Err returns one or more errors (via a util.ErrorList) that occurred prior -// to visiting the elements in the visitor. To see all errors including those -// that occur during visitation, invoke Infos(). -func (r *Result) Err() error { - return r.err -} - -// Visit implements the Visitor interface on the items described in the Builder. -// Note that some visitor sources are not traversable more than once, or may -// return different results. If you wish to operate on the same set of resources -// multiple times, use the Infos() method. -func (r *Result) Visit(fn VisitorFunc) error { - if r.err != nil { - return r.err - } - return r.visitor.Visit(fn) -} - -// IntoSingular sets the provided boolean pointer to true if the Builder input -// reflected a single item, or multiple. -func (r *Result) IntoSingular(b *bool) *Result { - *b = r.singular - return r -} - -// Infos returns an array of all of the resource infos retrieved via traversal. -// Will attempt to traverse the entire set of visitors only once, and will return -// a cached list on subsequent calls. -func (r *Result) Infos() ([]*Info, error) { - if r.err != nil { - return nil, r.err - } - if r.info != nil { - return r.info, nil - } - infos := []*Info{} - err := r.visitor.Visit(func(info *Info) error { - infos = append(infos, info) - return nil - }) - r.info, r.err = infos, err - return infos, err -} - -// Object returns a single object representing the output of a single visit to all -// found resources. If the Builder was a singular context (expected to return a -// single resource by user input) and only a single resource was found, the resource -// will be returned as is. Otherwise, the returned resources will be part of an -// api.List. The ResourceVersion of the api.List will be set only if it is identical -// across all infos returned. -func (r *Result) Object() (runtime.Object, error) { - infos, err := r.Infos() - if err != nil { - return nil, err - } - - versions := util.StringSet{} - objects := []runtime.Object{} - for _, info := range infos { - if info.Object != nil { - objects = append(objects, info.Object) - versions.Insert(info.ResourceVersion) - } - } - - if len(objects) == 1 { - if r.singular { - return objects[0], nil - } - // if the item is a list already, don't create another list - if _, err := runtime.GetItemsPtr(objects[0]); err == nil { - return objects[0], nil - } - } - - version := "" - if len(versions) == 1 { - version = versions.List()[0] - } - return &api.List{ - ListMeta: api.ListMeta{ - ResourceVersion: version, - }, - Items: objects, - }, err -} - -// ResourceMapping returns a single meta.RESTMapping representing the -// resources located by the builder, or an error if more than one -// mapping was found. -func (r *Result) ResourceMapping() (*meta.RESTMapping, error) { - if r.err != nil { - return nil, r.err - } - mappings := map[string]*meta.RESTMapping{} - for i := range r.sources { - m, ok := r.sources[i].(ResourceMapping) - if !ok { - return nil, fmt.Errorf("a resource mapping could not be loaded from %v", reflect.TypeOf(r.sources[i])) - } - mapping := m.ResourceMapping() - mappings[mapping.Resource] = mapping - } - if len(mappings) != 1 { - return nil, fmt.Errorf("expected only a single resource type") - } - for _, mapping := range mappings { - return mapping, nil - } - return nil, nil -} - -// Watch retrieves changes that occur on the server to the specified resource. -// It currently supports watching a single source - if the resource source -// (selectors or pure types) can be watched, they will be, otherwise the list -// will be visited (equivalent to the Infos() call) and if there is a single -// resource present, it will be watched, otherwise an error will be returned. -func (r *Result) Watch(resourceVersion string) (watch.Interface, error) { - if r.err != nil { - return nil, r.err - } - if len(r.sources) != 1 { - return nil, fmt.Errorf("you may only watch a single resource or type of resource at a time") - } - w, ok := r.sources[0].(Watchable) - if !ok { - info, err := r.Infos() - if err != nil { - return nil, err - } - if len(info) != 1 { - return nil, fmt.Errorf("watch is only supported on a single resource - %d resources were found", len(info)) - } - return info[0].Watch(resourceVersion) - } - return w.Watch(resourceVersion) -} - func SplitResourceArgument(arg string) []string { set := util.NewStringSet() set.Insert(strings.Split(arg, ",")...) diff --git a/pkg/kubectl/resource/result.go b/pkg/kubectl/resource/result.go new file mode 100644 index 00000000000..9ceeda5a840 --- /dev/null +++ b/pkg/kubectl/resource/result.go @@ -0,0 +1,202 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resource + +import ( + "fmt" + "reflect" + + "github.com/GoogleCloudPlatform/kubernetes/pkg/api" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" + "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors" + "github.com/GoogleCloudPlatform/kubernetes/pkg/watch" +) + +// ErrMatchFunc can be used to filter errors that may not be true failures. +type ErrMatchFunc func(error) bool + +// Result contains helper methods for dealing with the outcome of a Builder. +type Result struct { + err error + visitor Visitor + + sources []Visitor + singular bool + + ignoreErrors []errors.Matcher + + // populated by a call to Infos + info []*Info +} + +// IgnoreErrors will filter errors that occur when by visiting the result +// (but not errors that occur by creating the result in the first place), +// eliminating any that match fns. This is best used in combination with +// Builder.ContinueOnError(), where the visitors accumulate errors and return +// them after visiting as a slice of errors. If no errors remain after +// filtering, the various visitor methods on Result will return nil for +// err. +func (r *Result) IgnoreErrors(fns ...ErrMatchFunc) *Result { + for _, fn := range fns { + r.ignoreErrors = append(r.ignoreErrors, errors.Matcher(fn)) + } + return r +} + +// Err returns one or more errors (via a util.ErrorList) that occurred prior +// to visiting the elements in the visitor. To see all errors including those +// that occur during visitation, invoke Infos(). +func (r *Result) Err() error { + return r.err +} + +// Visit implements the Visitor interface on the items described in the Builder. +// Note that some visitor sources are not traversable more than once, or may +// return different results. If you wish to operate on the same set of resources +// multiple times, use the Infos() method. +func (r *Result) Visit(fn VisitorFunc) error { + if r.err != nil { + return r.err + } + err := r.visitor.Visit(fn) + return errors.FilterOut(err, r.ignoreErrors...) +} + +// IntoSingular sets the provided boolean pointer to true if the Builder input +// reflected a single item, or multiple. +func (r *Result) IntoSingular(b *bool) *Result { + *b = r.singular + return r +} + +// Infos returns an array of all of the resource infos retrieved via traversal. +// Will attempt to traverse the entire set of visitors only once, and will return +// a cached list on subsequent calls. +func (r *Result) Infos() ([]*Info, error) { + if r.err != nil { + return nil, r.err + } + if r.info != nil { + return r.info, nil + } + + infos := []*Info{} + err := r.visitor.Visit(func(info *Info) error { + infos = append(infos, info) + return nil + }) + err = errors.FilterOut(err, r.ignoreErrors...) + + r.info, r.err = infos, err + return infos, err +} + +// Object returns a single object representing the output of a single visit to all +// found resources. If the Builder was a singular context (expected to return a +// single resource by user input) and only a single resource was found, the resource +// will be returned as is. Otherwise, the returned resources will be part of an +// api.List. The ResourceVersion of the api.List will be set only if it is identical +// across all infos returned. +func (r *Result) Object() (runtime.Object, error) { + infos, err := r.Infos() + if err != nil { + return nil, err + } + + versions := util.StringSet{} + objects := []runtime.Object{} + for _, info := range infos { + if info.Object != nil { + objects = append(objects, info.Object) + versions.Insert(info.ResourceVersion) + } + } + + if len(objects) == 1 { + if r.singular { + return objects[0], nil + } + // if the item is a list already, don't create another list + if _, err := runtime.GetItemsPtr(objects[0]); err == nil { + return objects[0], nil + } + } + + version := "" + if len(versions) == 1 { + version = versions.List()[0] + } + return &api.List{ + ListMeta: api.ListMeta{ + ResourceVersion: version, + }, + Items: objects, + }, err +} + +// ResourceMapping returns a single meta.RESTMapping representing the +// resources located by the builder, or an error if more than one +// mapping was found. +func (r *Result) ResourceMapping() (*meta.RESTMapping, error) { + if r.err != nil { + return nil, r.err + } + mappings := map[string]*meta.RESTMapping{} + for i := range r.sources { + m, ok := r.sources[i].(ResourceMapping) + if !ok { + return nil, fmt.Errorf("a resource mapping could not be loaded from %v", reflect.TypeOf(r.sources[i])) + } + mapping := m.ResourceMapping() + mappings[mapping.Resource] = mapping + } + if len(mappings) != 1 { + return nil, fmt.Errorf("expected only a single resource type") + } + for _, mapping := range mappings { + return mapping, nil + } + return nil, nil +} + +// Watch retrieves changes that occur on the server to the specified resource. +// It currently supports watching a single source - if the resource source +// (selectors or pure types) can be watched, they will be, otherwise the list +// will be visited (equivalent to the Infos() call) and if there is a single +// resource present, it will be watched, otherwise an error will be returned. +func (r *Result) Watch(resourceVersion string) (watch.Interface, error) { + if r.err != nil { + return nil, r.err + } + if len(r.sources) != 1 { + return nil, fmt.Errorf("you may only watch a single resource or type of resource at a time") + } + w, ok := r.sources[0].(Watchable) + if !ok { + info, err := r.Infos() + if err != nil { + return nil, err + } + if len(info) != 1 { + return nil, fmt.Errorf("watch is only supported on a single resource - %d resources were found", len(info)) + } + return info[0].Watch(resourceVersion) + } + return w.Watch(resourceVersion) +} From a3ff55e4781dc795f55a330b1ec351f864533a40 Mon Sep 17 00:00:00 2001 From: Clayton Coleman Date: Sat, 3 Jan 2015 18:02:39 -0500 Subject: [PATCH 2/2] Allow delete to work from directories, files, multiple resources Simple refactoring reusing the resource builder changes. --- pkg/kubectl/cmd/cmd_test.go | 4 + pkg/kubectl/cmd/delete.go | 24 ++-- pkg/kubectl/cmd/delete_test.go | 221 +++++++++++++++++++++++++++++++++ 3 files changed, 242 insertions(+), 7 deletions(-) create mode 100644 pkg/kubectl/cmd/delete_test.go diff --git a/pkg/kubectl/cmd/cmd_test.go b/pkg/kubectl/cmd/cmd_test.go index 5331ad06846..317c46c9174 100644 --- a/pkg/kubectl/cmd/cmd_test.go +++ b/pkg/kubectl/cmd/cmd_test.go @@ -153,3 +153,7 @@ func NewAPIFactory() (*Factory, *testFactory, runtime.Codec) { func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser { return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj)))) } + +func stringBody(body string) io.ReadCloser { + return ioutil.NopCloser(bytes.NewReader([]byte(body))) +} diff --git a/pkg/kubectl/cmd/delete.go b/pkg/kubectl/cmd/delete.go index f7a45dc8a8a..a5bbf75291d 100644 --- a/pkg/kubectl/cmd/delete.go +++ b/pkg/kubectl/cmd/delete.go @@ -23,10 +23,15 @@ import ( "github.com/golang/glog" "github.com/spf13/cobra" + "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/resource" + "github.com/GoogleCloudPlatform/kubernetes/pkg/util" ) func (f *Factory) NewCmdDelete(out io.Writer) *cobra.Command { + flags := &struct { + Filenames util.StringList + }{} cmd := &cobra.Command{ Use: "delete ([-f filename] | ( [( | -l