...

Source file src/k8s.io/cli-runtime/pkg/resource/result.go

Documentation: k8s.io/cli-runtime/pkg/resource

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package resource
    18  
    19  import (
    20  	"fmt"
    21  	"reflect"
    22  
    23  	"k8s.io/api/core/v1"
    24  	"k8s.io/apimachinery/pkg/api/meta"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/apimachinery/pkg/runtime"
    27  	"k8s.io/apimachinery/pkg/runtime/schema"
    28  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
    29  	"k8s.io/apimachinery/pkg/util/sets"
    30  	"k8s.io/apimachinery/pkg/watch"
    31  )
    32  
    33  // ErrMatchFunc can be used to filter errors that may not be true failures.
    34  type ErrMatchFunc func(error) bool
    35  
    36  // Result contains helper methods for dealing with the outcome of a Builder.
    37  type Result struct {
    38  	err     error
    39  	visitor Visitor
    40  
    41  	sources            []Visitor
    42  	singleItemImplied  bool
    43  	targetsSingleItems bool
    44  
    45  	mapper       *mapper
    46  	ignoreErrors []utilerrors.Matcher
    47  
    48  	// populated by a call to Infos
    49  	info []*Info
    50  }
    51  
    52  // withError allows a fluent style for internal result code.
    53  func (r *Result) withError(err error) *Result {
    54  	r.err = err
    55  	return r
    56  }
    57  
    58  // TargetsSingleItems returns true if any of the builder arguments pointed
    59  // to non-list calls (if the user explicitly asked for any object by name).
    60  // This includes directories, streams, URLs, and resource name tuples.
    61  func (r *Result) TargetsSingleItems() bool {
    62  	return r.targetsSingleItems
    63  }
    64  
    65  // IgnoreErrors will filter errors that occur when by visiting the result
    66  // (but not errors that occur by creating the result in the first place),
    67  // eliminating any that match fns. This is best used in combination with
    68  // Builder.ContinueOnError(), where the visitors accumulate errors and return
    69  // them after visiting as a slice of errors. If no errors remain after
    70  // filtering, the various visitor methods on Result will return nil for
    71  // err.
    72  func (r *Result) IgnoreErrors(fns ...ErrMatchFunc) *Result {
    73  	for _, fn := range fns {
    74  		r.ignoreErrors = append(r.ignoreErrors, utilerrors.Matcher(fn))
    75  	}
    76  	return r
    77  }
    78  
    79  // Mapper returns a copy of the builder's mapper.
    80  func (r *Result) Mapper() *mapper {
    81  	return r.mapper
    82  }
    83  
    84  // Err returns one or more errors (via a util.ErrorList) that occurred prior
    85  // to visiting the elements in the visitor. To see all errors including those
    86  // that occur during visitation, invoke Infos().
    87  func (r *Result) Err() error {
    88  	return r.err
    89  }
    90  
    91  // Visit implements the Visitor interface on the items described in the Builder.
    92  // Note that some visitor sources are not traversable more than once, or may
    93  // return different results.  If you wish to operate on the same set of resources
    94  // multiple times, use the Infos() method.
    95  func (r *Result) Visit(fn VisitorFunc) error {
    96  	if r.err != nil {
    97  		return r.err
    98  	}
    99  	err := r.visitor.Visit(fn)
   100  	return utilerrors.FilterOut(err, r.ignoreErrors...)
   101  }
   102  
   103  // IntoSingleItemImplied sets the provided boolean pointer to true if the Builder input
   104  // implies a single item, or multiple.
   105  func (r *Result) IntoSingleItemImplied(b *bool) *Result {
   106  	*b = r.singleItemImplied
   107  	return r
   108  }
   109  
   110  // Infos returns an array of all of the resource infos retrieved via traversal.
   111  // Will attempt to traverse the entire set of visitors only once, and will return
   112  // a cached list on subsequent calls.
   113  func (r *Result) Infos() ([]*Info, error) {
   114  	if r.err != nil {
   115  		return nil, r.err
   116  	}
   117  	if r.info != nil {
   118  		return r.info, nil
   119  	}
   120  
   121  	infos := []*Info{}
   122  	err := r.visitor.Visit(func(info *Info, err error) error {
   123  		if err != nil {
   124  			return err
   125  		}
   126  		infos = append(infos, info)
   127  		return nil
   128  	})
   129  	err = utilerrors.FilterOut(err, r.ignoreErrors...)
   130  
   131  	r.info, r.err = infos, err
   132  	return infos, err
   133  }
   134  
   135  // Object returns a single object representing the output of a single visit to all
   136  // found resources.  If the Builder was a singular context (expected to return a
   137  // single resource by user input) and only a single resource was found, the resource
   138  // will be returned as is.  Otherwise, the returned resources will be part of an
   139  // v1.List. The ResourceVersion of the v1.List will be set only if it is identical
   140  // across all infos returned.
   141  func (r *Result) Object() (runtime.Object, error) {
   142  	infos, err := r.Infos()
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  
   147  	versions := sets.String{}
   148  	objects := []runtime.Object{}
   149  	for _, info := range infos {
   150  		if info.Object != nil {
   151  			objects = append(objects, info.Object)
   152  			versions.Insert(info.ResourceVersion)
   153  		}
   154  	}
   155  
   156  	if len(objects) == 1 {
   157  		if r.singleItemImplied {
   158  			return objects[0], nil
   159  		}
   160  		// if the item is a list already, don't create another list
   161  		if meta.IsListType(objects[0]) {
   162  			return objects[0], nil
   163  		}
   164  	}
   165  
   166  	version := ""
   167  	if len(versions) == 1 {
   168  		version = versions.List()[0]
   169  	}
   170  
   171  	return toV1List(objects, version), err
   172  }
   173  
   174  // Compile time check to enforce that list implements the necessary interface
   175  var _ metav1.ListInterface = &v1.List{}
   176  var _ metav1.ListMetaAccessor = &v1.List{}
   177  
   178  // toV1List takes a slice of Objects + their version, and returns
   179  // a v1.List Object containing the objects in the Items field
   180  func toV1List(objects []runtime.Object, version string) runtime.Object {
   181  	raw := []runtime.RawExtension{}
   182  	for _, o := range objects {
   183  		raw = append(raw, runtime.RawExtension{Object: o})
   184  	}
   185  	return &v1.List{
   186  		ListMeta: metav1.ListMeta{
   187  			ResourceVersion: version,
   188  		},
   189  		Items: raw,
   190  	}
   191  }
   192  
   193  // ResourceMapping returns a single meta.RESTMapping representing the
   194  // resources located by the builder, or an error if more than one
   195  // mapping was found.
   196  func (r *Result) ResourceMapping() (*meta.RESTMapping, error) {
   197  	if r.err != nil {
   198  		return nil, r.err
   199  	}
   200  	mappings := map[schema.GroupVersionResource]*meta.RESTMapping{}
   201  	for i := range r.sources {
   202  		m, ok := r.sources[i].(ResourceMapping)
   203  		if !ok {
   204  			return nil, fmt.Errorf("a resource mapping could not be loaded from %v", reflect.TypeOf(r.sources[i]))
   205  		}
   206  		mapping := m.ResourceMapping()
   207  		mappings[mapping.Resource] = mapping
   208  	}
   209  	if len(mappings) != 1 {
   210  		return nil, fmt.Errorf("expected only a single resource type")
   211  	}
   212  	for _, mapping := range mappings {
   213  		return mapping, nil
   214  	}
   215  	return nil, nil
   216  }
   217  
   218  // Watch retrieves changes that occur on the server to the specified resource.
   219  // It currently supports watching a single source - if the resource source
   220  // (selectors or pure types) can be watched, they will be, otherwise the list
   221  // will be visited (equivalent to the Infos() call) and if there is a single
   222  // resource present, it will be watched, otherwise an error will be returned.
   223  func (r *Result) Watch(resourceVersion string) (watch.Interface, error) {
   224  	if r.err != nil {
   225  		return nil, r.err
   226  	}
   227  	if len(r.sources) != 1 {
   228  		return nil, fmt.Errorf("you may only watch a single resource or type of resource at a time")
   229  	}
   230  	w, ok := r.sources[0].(Watchable)
   231  	if !ok {
   232  		info, err := r.Infos()
   233  		if err != nil {
   234  			return nil, err
   235  		}
   236  		if len(info) != 1 {
   237  			return nil, fmt.Errorf("watch is only supported on individual resources and resource collections - %d resources were found", len(info))
   238  		}
   239  		return info[0].Watch(resourceVersion)
   240  	}
   241  	return w.Watch(resourceVersion)
   242  }
   243  

View as plain text