mirror of https://github.com/k3s-io/k3s
243 lines
7.3 KiB
Go
243 lines
7.3 KiB
Go
|
/*
|
||
|
Copyright 2014 The Kubernetes Authors.
|
||
|
|
||
|
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"
|
||
|
|
||
|
"k8s.io/api/core/v1"
|
||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||
|
"k8s.io/apimachinery/pkg/runtime"
|
||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||
|
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||
|
"k8s.io/apimachinery/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
|
||
|
singleItemImplied bool
|
||
|
targetsSingleItems bool
|
||
|
|
||
|
mapper *mapper
|
||
|
ignoreErrors []utilerrors.Matcher
|
||
|
|
||
|
// populated by a call to Infos
|
||
|
info []*Info
|
||
|
}
|
||
|
|
||
|
// withError allows a fluent style for internal result code.
|
||
|
func (r *Result) withError(err error) *Result {
|
||
|
r.err = err
|
||
|
return r
|
||
|
}
|
||
|
|
||
|
// TargetsSingleItems returns true if any of the builder arguments pointed
|
||
|
// to non-list calls (if the user explicitly asked for any object by name).
|
||
|
// This includes directories, streams, URLs, and resource name tuples.
|
||
|
func (r *Result) TargetsSingleItems() bool {
|
||
|
return r.targetsSingleItems
|
||
|
}
|
||
|
|
||
|
// 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, utilerrors.Matcher(fn))
|
||
|
}
|
||
|
return r
|
||
|
}
|
||
|
|
||
|
// Mapper returns a copy of the builder's mapper.
|
||
|
func (r *Result) Mapper() *mapper {
|
||
|
return r.mapper
|
||
|
}
|
||
|
|
||
|
// 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 utilerrors.FilterOut(err, r.ignoreErrors...)
|
||
|
}
|
||
|
|
||
|
// IntoSingleItemImplied sets the provided boolean pointer to true if the Builder input
|
||
|
// implies a single item, or multiple.
|
||
|
func (r *Result) IntoSingleItemImplied(b *bool) *Result {
|
||
|
*b = r.singleItemImplied
|
||
|
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, err error) error {
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
infos = append(infos, info)
|
||
|
return nil
|
||
|
})
|
||
|
err = utilerrors.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
|
||
|
// v1.List. The ResourceVersion of the v1.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 := sets.String{}
|
||
|
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.singleItemImplied {
|
||
|
return objects[0], nil
|
||
|
}
|
||
|
// if the item is a list already, don't create another list
|
||
|
if meta.IsListType(objects[0]) {
|
||
|
return objects[0], nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
version := ""
|
||
|
if len(versions) == 1 {
|
||
|
version = versions.List()[0]
|
||
|
}
|
||
|
|
||
|
return toV1List(objects, version), err
|
||
|
}
|
||
|
|
||
|
// Compile time check to enforce that list implements the necessary interface
|
||
|
var _ metav1.ListInterface = &v1.List{}
|
||
|
var _ metav1.ListMetaAccessor = &v1.List{}
|
||
|
|
||
|
// toV1List takes a slice of Objects + their version, and returns
|
||
|
// a v1.List Object containing the objects in the Items field
|
||
|
func toV1List(objects []runtime.Object, version string) runtime.Object {
|
||
|
raw := []runtime.RawExtension{}
|
||
|
for _, o := range objects {
|
||
|
raw = append(raw, runtime.RawExtension{Object: o})
|
||
|
}
|
||
|
return &v1.List{
|
||
|
ListMeta: metav1.ListMeta{
|
||
|
ResourceVersion: version,
|
||
|
},
|
||
|
Items: raw,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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[schema.GroupVersionResource]*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 individual resources and resource collections - %d resources were found", len(info))
|
||
|
}
|
||
|
return info[0].Watch(resourceVersion)
|
||
|
}
|
||
|
return w.Watch(resourceVersion)
|
||
|
}
|