Switch to github.com/golang/dep for vendoring
Signed-off-by: Mrunal Patel <mrunalp@gmail.com>
This commit is contained in:
parent
d6ab91be27
commit
8e5b17cf13
15431 changed files with 3971413 additions and 8881 deletions
91
vendor/k8s.io/kubernetes/pkg/kubectl/resource/BUILD
generated
vendored
Normal file
91
vendor/k8s.io/kubernetes/pkg/kubectl/resource/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,91 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"builder.go",
|
||||
"doc.go",
|
||||
"helper.go",
|
||||
"interfaces.go",
|
||||
"mapper.go",
|
||||
"result.go",
|
||||
"selector.go",
|
||||
"visitor.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/validation:go_default_library",
|
||||
"//pkg/apis/extensions:go_default_library",
|
||||
"//pkg/client/restclient:go_default_library",
|
||||
"//vendor:golang.org/x/text/encoding/unicode",
|
||||
"//vendor:golang.org/x/text/transform",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/errors",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/meta",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/labels",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/errors",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/sets",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/yaml",
|
||||
"//vendor:k8s.io/apimachinery/pkg/watch",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"builder_test.go",
|
||||
"helper_test.go",
|
||||
"visitor_test.go",
|
||||
],
|
||||
data = [
|
||||
"//examples:config",
|
||||
"//test/fixtures",
|
||||
],
|
||||
library = ":go_default_library",
|
||||
tags = [
|
||||
"automanaged",
|
||||
],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/resource:go_default_library",
|
||||
"//pkg/api/testapi:go_default_library",
|
||||
"//pkg/api/testing:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/client/restclient/fake:go_default_library",
|
||||
"//pkg/client/restclient/watch:go_default_library",
|
||||
"//pkg/util/testing:go_default_library",
|
||||
"//vendor:github.com/ghodss/yaml",
|
||||
"//vendor:github.com/stretchr/testify/assert",
|
||||
"//vendor:k8s.io/apimachinery/pkg/api/meta",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/labels",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime/serializer/streaming",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/errors",
|
||||
"//vendor:k8s.io/apimachinery/pkg/watch",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
838
vendor/k8s.io/kubernetes/pkg/kubectl/resource/builder.go
generated
vendored
Normal file
838
vendor/k8s.io/kubernetes/pkg/kubectl/resource/builder.go
generated
vendored
Normal file
|
@ -0,0 +1,838 @@
|
|||
/*
|
||||
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"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"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/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/validation"
|
||||
)
|
||||
|
||||
var FileExtensions = []string{".json", ".yaml", ".yml"}
|
||||
var InputExtensions = append(FileExtensions, "stdin")
|
||||
|
||||
const defaultHttpGetAttempts int = 3
|
||||
|
||||
// Builder provides convenience functions for taking arguments and parameters
|
||||
// from the command line and converting them to a list of resources to iterate
|
||||
// over using the Visitor interface.
|
||||
type Builder struct {
|
||||
mapper *Mapper
|
||||
|
||||
errs []error
|
||||
|
||||
paths []Visitor
|
||||
stream bool
|
||||
dir bool
|
||||
|
||||
selector labels.Selector
|
||||
selectAll bool
|
||||
|
||||
resources []string
|
||||
|
||||
namespace string
|
||||
allNamespace bool
|
||||
names []string
|
||||
|
||||
resourceTuples []resourceTuple
|
||||
|
||||
defaultNamespace bool
|
||||
requireNamespace bool
|
||||
|
||||
flatten bool
|
||||
latest bool
|
||||
|
||||
requireObject bool
|
||||
|
||||
singleResourceType bool
|
||||
continueOnError bool
|
||||
|
||||
singleItemImplied bool
|
||||
|
||||
export bool
|
||||
|
||||
schema validation.Schema
|
||||
}
|
||||
|
||||
var missingResourceError = fmt.Errorf(`You must provide one or more resources by argument or filename.
|
||||
Example resource specifications include:
|
||||
'-f rsrc.yaml'
|
||||
'--filename=rsrc.json'
|
||||
'pods my-pod'
|
||||
'services'`)
|
||||
|
||||
// TODO: expand this to include other errors.
|
||||
func IsUsageError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
return err == missingResourceError
|
||||
}
|
||||
|
||||
type FilenameOptions struct {
|
||||
Filenames []string
|
||||
Recursive bool
|
||||
}
|
||||
|
||||
type resourceTuple struct {
|
||||
Resource string
|
||||
Name string
|
||||
}
|
||||
|
||||
// NewBuilder creates a builder that operates on generic objects.
|
||||
func NewBuilder(mapper meta.RESTMapper, typer runtime.ObjectTyper, clientMapper ClientMapper, decoder runtime.Decoder) *Builder {
|
||||
return &Builder{
|
||||
mapper: &Mapper{typer, mapper, clientMapper, decoder},
|
||||
requireObject: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Builder) Schema(schema validation.Schema) *Builder {
|
||||
b.schema = schema
|
||||
return b
|
||||
}
|
||||
|
||||
// FilenameParam groups input in two categories: URLs and files (files, directories, STDIN)
|
||||
// If enforceNamespace is false, namespaces in the specs will be allowed to
|
||||
// override the default namespace. If it is true, namespaces that don't match
|
||||
// will cause an error.
|
||||
// If ContinueOnError() is set prior to this method, objects on the path that are not
|
||||
// recognized will be ignored (but logged at V(2)).
|
||||
func (b *Builder) FilenameParam(enforceNamespace bool, filenameOptions *FilenameOptions) *Builder {
|
||||
recursive := filenameOptions.Recursive
|
||||
paths := filenameOptions.Filenames
|
||||
for _, s := range paths {
|
||||
switch {
|
||||
case s == "-":
|
||||
b.Stdin()
|
||||
case strings.Index(s, "http://") == 0 || strings.Index(s, "https://") == 0:
|
||||
url, err := url.Parse(s)
|
||||
if err != nil {
|
||||
b.errs = append(b.errs, fmt.Errorf("the URL passed to filename %q is not valid: %v", s, err))
|
||||
continue
|
||||
}
|
||||
b.URL(defaultHttpGetAttempts, url)
|
||||
default:
|
||||
if !recursive {
|
||||
b.singleItemImplied = true
|
||||
}
|
||||
b.Path(recursive, s)
|
||||
}
|
||||
}
|
||||
|
||||
if enforceNamespace {
|
||||
b.RequireNamespace()
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// URL accepts a number of URLs directly.
|
||||
func (b *Builder) URL(httpAttemptCount int, urls ...*url.URL) *Builder {
|
||||
for _, u := range urls {
|
||||
b.paths = append(b.paths, &URLVisitor{
|
||||
URL: u,
|
||||
StreamVisitor: NewStreamVisitor(nil, b.mapper, u.String(), b.schema),
|
||||
HttpAttemptCount: httpAttemptCount,
|
||||
})
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Stdin will read objects from the standard input. If ContinueOnError() is set
|
||||
// prior to this method being called, objects in the stream that are unrecognized
|
||||
// will be ignored (but logged at V(2)).
|
||||
func (b *Builder) Stdin() *Builder {
|
||||
b.stream = true
|
||||
b.paths = append(b.paths, FileVisitorForSTDIN(b.mapper, b.schema))
|
||||
return b
|
||||
}
|
||||
|
||||
// Stream will read objects from the provided reader, and if an error occurs will
|
||||
// include the name string in the error message. If ContinueOnError() is set
|
||||
// prior to this method being called, objects in the stream that are unrecognized
|
||||
// will be ignored (but logged at V(2)).
|
||||
func (b *Builder) Stream(r io.Reader, name string) *Builder {
|
||||
b.stream = true
|
||||
b.paths = append(b.paths, NewStreamVisitor(r, b.mapper, name, b.schema))
|
||||
return b
|
||||
}
|
||||
|
||||
// Path accepts a set of paths that may be files, directories (all can containing
|
||||
// one or more resources). Creates a FileVisitor for each file and then each
|
||||
// FileVisitor is streaming the content to a StreamVisitor. If ContinueOnError() is set
|
||||
// prior to this method being called, objects on the path that are unrecognized will be
|
||||
// ignored (but logged at V(2)).
|
||||
func (b *Builder) Path(recursive bool, paths ...string) *Builder {
|
||||
for _, p := range paths {
|
||||
_, err := os.Stat(p)
|
||||
if os.IsNotExist(err) {
|
||||
b.errs = append(b.errs, fmt.Errorf("the path %q does not exist", p))
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
b.errs = append(b.errs, fmt.Errorf("the path %q cannot be accessed: %v", p, err))
|
||||
continue
|
||||
}
|
||||
|
||||
visitors, err := ExpandPathsToFileVisitors(b.mapper, p, recursive, FileExtensions, b.schema)
|
||||
if err != nil {
|
||||
b.errs = append(b.errs, fmt.Errorf("error reading %q: %v", p, err))
|
||||
}
|
||||
if len(visitors) > 1 {
|
||||
b.dir = true
|
||||
}
|
||||
|
||||
b.paths = append(b.paths, visitors...)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// ResourceTypes is a list of types of resources to operate on, when listing objects on
|
||||
// the server or retrieving objects that match a selector.
|
||||
func (b *Builder) ResourceTypes(types ...string) *Builder {
|
||||
b.resources = append(b.resources, types...)
|
||||
return b
|
||||
}
|
||||
|
||||
// ResourceNames accepts a default type and one or more names, and creates tuples of
|
||||
// resources
|
||||
func (b *Builder) ResourceNames(resource string, names ...string) *Builder {
|
||||
for _, name := range names {
|
||||
// See if this input string is of type/name format
|
||||
tuple, ok, err := splitResourceTypeName(name)
|
||||
if err != nil {
|
||||
b.errs = append(b.errs, err)
|
||||
return b
|
||||
}
|
||||
|
||||
if ok {
|
||||
b.resourceTuples = append(b.resourceTuples, tuple)
|
||||
continue
|
||||
}
|
||||
if len(resource) == 0 {
|
||||
b.errs = append(b.errs, fmt.Errorf("the argument %q must be RESOURCE/NAME", name))
|
||||
continue
|
||||
}
|
||||
|
||||
// Use the given default type to create a resource tuple
|
||||
b.resourceTuples = append(b.resourceTuples, resourceTuple{Resource: resource, Name: name})
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// SelectorParam defines a selector that should be applied to the object types to load.
|
||||
// This will not affect files loaded from disk or URL. If the parameter is empty it is
|
||||
// a no-op - to select all resources invoke `b.Selector(labels.Everything)`.
|
||||
func (b *Builder) SelectorParam(s string) *Builder {
|
||||
selector, err := labels.Parse(s)
|
||||
if err != nil {
|
||||
b.errs = append(b.errs, fmt.Errorf("the provided selector %q is not valid: %v", s, err))
|
||||
return b
|
||||
}
|
||||
if selector.Empty() {
|
||||
return b
|
||||
}
|
||||
if b.selectAll {
|
||||
b.errs = append(b.errs, fmt.Errorf("found non empty selector %q with previously set 'all' parameter. ", s))
|
||||
return b
|
||||
}
|
||||
return b.Selector(selector)
|
||||
}
|
||||
|
||||
// Selector accepts a selector directly, and if non nil will trigger a list action.
|
||||
func (b *Builder) Selector(selector labels.Selector) *Builder {
|
||||
b.selector = selector
|
||||
return b
|
||||
}
|
||||
|
||||
// ExportParam accepts the export boolean for these resources
|
||||
func (b *Builder) ExportParam(export bool) *Builder {
|
||||
b.export = export
|
||||
return b
|
||||
}
|
||||
|
||||
// NamespaceParam accepts the namespace that these resources should be
|
||||
// considered under from - used by DefaultNamespace() and RequireNamespace()
|
||||
func (b *Builder) NamespaceParam(namespace string) *Builder {
|
||||
b.namespace = namespace
|
||||
return b
|
||||
}
|
||||
|
||||
// DefaultNamespace instructs the builder to set the namespace value for any object found
|
||||
// to NamespaceParam() if empty.
|
||||
func (b *Builder) DefaultNamespace() *Builder {
|
||||
b.defaultNamespace = true
|
||||
return b
|
||||
}
|
||||
|
||||
// AllNamespaces instructs the builder to use NamespaceAll as a namespace to request resources
|
||||
// across all of the namespace. This overrides the namespace set by NamespaceParam().
|
||||
func (b *Builder) AllNamespaces(allNamespace bool) *Builder {
|
||||
if allNamespace {
|
||||
b.namespace = api.NamespaceAll
|
||||
}
|
||||
b.allNamespace = allNamespace
|
||||
return b
|
||||
}
|
||||
|
||||
// RequireNamespace instructs the builder to set the namespace value for any object found
|
||||
// to NamespaceParam() if empty, and if the value on the resource does not match
|
||||
// NamespaceParam() an error will be returned.
|
||||
func (b *Builder) RequireNamespace() *Builder {
|
||||
b.requireNamespace = true
|
||||
return b
|
||||
}
|
||||
|
||||
// SelectEverythingParam
|
||||
func (b *Builder) SelectAllParam(selectAll bool) *Builder {
|
||||
if selectAll && b.selector != nil {
|
||||
b.errs = append(b.errs, fmt.Errorf("setting 'all' parameter but found a non empty selector. "))
|
||||
return b
|
||||
}
|
||||
b.selectAll = selectAll
|
||||
return b
|
||||
}
|
||||
|
||||
// ResourceTypeOrNameArgs indicates that the builder should accept arguments
|
||||
// of the form `(<type1>[,<type2>,...]|<type> <name1>[,<name2>,...])`. When one argument is
|
||||
// received, the types provided will be retrieved from the server (and be comma delimited).
|
||||
// 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 {
|
||||
args = normalizeMultipleResourcesArgs(args)
|
||||
if ok, err := hasCombinedTypeArgs(args); ok {
|
||||
if err != nil {
|
||||
b.errs = append(b.errs, err)
|
||||
return b
|
||||
}
|
||||
for _, s := range args {
|
||||
tuple, ok, err := splitResourceTypeName(s)
|
||||
if err != nil {
|
||||
b.errs = append(b.errs, err)
|
||||
return b
|
||||
}
|
||||
if ok {
|
||||
b.resourceTuples = append(b.resourceTuples, tuple)
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
if len(args) > 0 {
|
||||
// Try replacing aliases only in types
|
||||
args[0] = b.ReplaceAliases(args[0])
|
||||
}
|
||||
switch {
|
||||
case len(args) > 2:
|
||||
b.names = append(b.names, args[1:]...)
|
||||
b.ResourceTypes(SplitResourceArgument(args[0])...)
|
||||
case len(args) == 2:
|
||||
b.names = append(b.names, args[1])
|
||||
b.ResourceTypes(SplitResourceArgument(args[0])...)
|
||||
case len(args) == 1:
|
||||
b.ResourceTypes(SplitResourceArgument(args[0])...)
|
||||
if b.selector == nil && allowEmptySelector {
|
||||
b.selector = labels.Everything()
|
||||
}
|
||||
case len(args) == 0:
|
||||
default:
|
||||
b.errs = append(b.errs, fmt.Errorf("arguments must consist of a resource or a resource and name"))
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// ReplaceAliases accepts an argument and tries to expand any existing
|
||||
// aliases found in it
|
||||
func (b *Builder) ReplaceAliases(input string) string {
|
||||
replaced := []string{}
|
||||
for _, arg := range strings.Split(input, ",") {
|
||||
if aliases, ok := b.mapper.AliasesForResource(arg); ok {
|
||||
arg = strings.Join(aliases, ",")
|
||||
}
|
||||
replaced = append(replaced, arg)
|
||||
}
|
||||
return strings.Join(replaced, ",")
|
||||
}
|
||||
|
||||
func hasCombinedTypeArgs(args []string) (bool, error) {
|
||||
hasSlash := 0
|
||||
for _, s := range args {
|
||||
if strings.Contains(s, "/") {
|
||||
hasSlash++
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case hasSlash > 0 && hasSlash == len(args):
|
||||
return true, nil
|
||||
case hasSlash > 0 && hasSlash != len(args):
|
||||
baseCmd := "cmd"
|
||||
if len(os.Args) > 0 {
|
||||
baseCmdSlice := strings.Split(os.Args[0], "/")
|
||||
baseCmd = baseCmdSlice[len(baseCmdSlice)-1]
|
||||
}
|
||||
return true, fmt.Errorf("there is no need to specify a resource type as a separate argument when passing arguments in resource/name form (e.g. '%s get resource/<resource_name>' instead of '%s get resource resource/<resource_name>'", baseCmd, baseCmd)
|
||||
default:
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize args convert multiple resources to resource tuples, a,b,c d
|
||||
// as a transform to a/d b/d c/d
|
||||
func normalizeMultipleResourcesArgs(args []string) []string {
|
||||
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}, "/"))
|
||||
}
|
||||
}
|
||||
return newArgs
|
||||
}
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
// splitResourceTypeName handles type/name resource formats and returns a resource tuple
|
||||
// (empty or not), whether it successfully found one, and an error
|
||||
func splitResourceTypeName(s string) (resourceTuple, bool, error) {
|
||||
if !strings.Contains(s, "/") {
|
||||
return resourceTuple{}, false, nil
|
||||
}
|
||||
seg := strings.Split(s, "/")
|
||||
if len(seg) != 2 {
|
||||
return resourceTuple{}, false, fmt.Errorf("arguments in resource/name form may not have more than one slash")
|
||||
}
|
||||
resource, name := seg[0], seg[1]
|
||||
if len(resource) == 0 || len(name) == 0 || len(SplitResourceArgument(resource)) != 1 {
|
||||
return resourceTuple{}, false, fmt.Errorf("arguments in resource/name form must have a single resource and name")
|
||||
}
|
||||
return resourceTuple{Resource: resource, Name: name}, true, nil
|
||||
}
|
||||
|
||||
// Flatten will convert any objects with a field named "Items" that is an array of runtime.Object
|
||||
// compatible types into individual entries and give them their own items. The original object
|
||||
// is not passed to any visitors.
|
||||
func (b *Builder) Flatten() *Builder {
|
||||
b.flatten = true
|
||||
return b
|
||||
}
|
||||
|
||||
// Latest will fetch the latest copy of any objects loaded from URLs or files from the server.
|
||||
func (b *Builder) Latest() *Builder {
|
||||
b.latest = true
|
||||
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.
|
||||
func (b *Builder) ContinueOnError() *Builder {
|
||||
b.continueOnError = true
|
||||
return b
|
||||
}
|
||||
|
||||
// SingleResourceType will cause the builder to error if the user specifies more than a single type
|
||||
// of resource.
|
||||
func (b *Builder) SingleResourceType() *Builder {
|
||||
b.singleResourceType = true
|
||||
return b
|
||||
}
|
||||
|
||||
// mappingFor returns the RESTMapping for the Kind referenced by the resource.
|
||||
// prefers a fully specified GroupVersionResource match. If we don't have one match on GroupResource
|
||||
func (b *Builder) mappingFor(resourceArg string) (*meta.RESTMapping, error) {
|
||||
fullySpecifiedGVR, groupResource := schema.ParseResourceArg(resourceArg)
|
||||
gvk := schema.GroupVersionKind{}
|
||||
if fullySpecifiedGVR != nil {
|
||||
gvk, _ = b.mapper.KindFor(*fullySpecifiedGVR)
|
||||
}
|
||||
if gvk.Empty() {
|
||||
var err error
|
||||
gvk, err = b.mapper.KindFor(groupResource.WithVersion(""))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return b.mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
|
||||
}
|
||||
|
||||
func (b *Builder) resourceMappings() ([]*meta.RESTMapping, error) {
|
||||
if len(b.resources) > 1 && b.singleResourceType {
|
||||
return nil, fmt.Errorf("you may only specify a single resource type")
|
||||
}
|
||||
mappings := []*meta.RESTMapping{}
|
||||
for _, r := range b.resources {
|
||||
mapping, err := b.mappingFor(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mappings = append(mappings, mapping)
|
||||
}
|
||||
return mappings, nil
|
||||
}
|
||||
|
||||
func (b *Builder) resourceTupleMappings() (map[string]*meta.RESTMapping, error) {
|
||||
mappings := make(map[string]*meta.RESTMapping)
|
||||
canonical := make(map[string]struct{})
|
||||
for _, r := range b.resourceTuples {
|
||||
if _, ok := mappings[r.Resource]; ok {
|
||||
continue
|
||||
}
|
||||
mapping, err := b.mappingFor(r.Resource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mappings[mapping.Resource] = mapping
|
||||
mappings[r.Resource] = mapping
|
||||
canonical[mapping.Resource] = struct{}{}
|
||||
}
|
||||
if len(canonical) > 1 && b.singleResourceType {
|
||||
return nil, fmt.Errorf("you may only specify a single resource type")
|
||||
}
|
||||
return mappings, nil
|
||||
}
|
||||
|
||||
func (b *Builder) visitorResult() *Result {
|
||||
if len(b.errs) > 0 {
|
||||
return &Result{err: utilerrors.NewAggregate(b.errs)}
|
||||
}
|
||||
|
||||
if b.selectAll {
|
||||
b.selector = labels.Everything()
|
||||
}
|
||||
|
||||
// visit items specified by paths
|
||||
if len(b.paths) != 0 {
|
||||
return b.visitByPaths()
|
||||
}
|
||||
|
||||
// visit selectors
|
||||
if b.selector != nil {
|
||||
return b.visitBySelector()
|
||||
}
|
||||
|
||||
// visit items specified by resource and name
|
||||
if len(b.resourceTuples) != 0 {
|
||||
return b.visitByResource()
|
||||
}
|
||||
|
||||
// visit items specified by name
|
||||
if len(b.names) != 0 {
|
||||
return b.visitByName()
|
||||
}
|
||||
|
||||
if len(b.resources) != 0 {
|
||||
return &Result{err: fmt.Errorf("resource(s) were provided, but no name, label selector, or --all flag specified")}
|
||||
}
|
||||
return &Result{err: missingResourceError}
|
||||
}
|
||||
|
||||
func (b *Builder) visitBySelector() *Result {
|
||||
if len(b.names) != 0 {
|
||||
return &Result{err: fmt.Errorf("name cannot be provided when a selector is specified")}
|
||||
}
|
||||
if len(b.resourceTuples) != 0 {
|
||||
return &Result{err: fmt.Errorf("selectors and the all flag cannot be used when passing resource/name arguments")}
|
||||
}
|
||||
if len(b.resources) == 0 {
|
||||
return &Result{err: fmt.Errorf("at least one resource must be specified to use a selector")}
|
||||
}
|
||||
mappings, err := b.resourceMappings()
|
||||
if err != nil {
|
||||
return &Result{err: err}
|
||||
}
|
||||
|
||||
visitors := []Visitor{}
|
||||
for _, mapping := range mappings {
|
||||
client, err := b.mapper.ClientForMapping(mapping)
|
||||
if err != nil {
|
||||
return &Result{err: err}
|
||||
}
|
||||
selectorNamespace := b.namespace
|
||||
if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
|
||||
selectorNamespace = ""
|
||||
}
|
||||
visitors = append(visitors, NewSelector(client, mapping, selectorNamespace, b.selector, b.export))
|
||||
}
|
||||
if b.continueOnError {
|
||||
return &Result{visitor: EagerVisitorList(visitors), sources: visitors}
|
||||
}
|
||||
return &Result{visitor: VisitorList(visitors), sources: visitors}
|
||||
}
|
||||
|
||||
func (b *Builder) visitByResource() *Result {
|
||||
// if b.singleItemImplied is false, this could be by default, so double-check length
|
||||
// of resourceTuples to determine if in fact it is singleItemImplied or not
|
||||
isSingleItemImplied := b.singleItemImplied
|
||||
if !isSingleItemImplied {
|
||||
isSingleItemImplied = len(b.resourceTuples) == 1
|
||||
}
|
||||
|
||||
if len(b.resources) != 0 {
|
||||
return &Result{singleItemImplied: isSingleItemImplied, err: fmt.Errorf("you may not specify individual resources and bulk resources in the same call")}
|
||||
}
|
||||
|
||||
// retrieve one client for each resource
|
||||
mappings, err := b.resourceTupleMappings()
|
||||
if err != nil {
|
||||
return &Result{singleItemImplied: isSingleItemImplied, err: err}
|
||||
}
|
||||
clients := make(map[string]RESTClient)
|
||||
for _, mapping := range mappings {
|
||||
s := fmt.Sprintf("%s/%s", mapping.GroupVersionKind.GroupVersion().String(), mapping.Resource)
|
||||
if _, ok := clients[s]; ok {
|
||||
continue
|
||||
}
|
||||
client, err := b.mapper.ClientForMapping(mapping)
|
||||
if err != nil {
|
||||
return &Result{err: err}
|
||||
}
|
||||
clients[s] = client
|
||||
}
|
||||
|
||||
items := []Visitor{}
|
||||
for _, tuple := range b.resourceTuples {
|
||||
mapping, ok := mappings[tuple.Resource]
|
||||
if !ok {
|
||||
return &Result{singleItemImplied: isSingleItemImplied, err: fmt.Errorf("resource %q is not recognized: %v", tuple.Resource, mappings)}
|
||||
}
|
||||
s := fmt.Sprintf("%s/%s", mapping.GroupVersionKind.GroupVersion().String(), mapping.Resource)
|
||||
client, ok := clients[s]
|
||||
if !ok {
|
||||
return &Result{singleItemImplied: isSingleItemImplied, err: fmt.Errorf("could not find a client for resource %q", tuple.Resource)}
|
||||
}
|
||||
|
||||
selectorNamespace := b.namespace
|
||||
if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
|
||||
selectorNamespace = ""
|
||||
} else {
|
||||
if len(b.namespace) == 0 {
|
||||
errMsg := "namespace may not be empty when retrieving a resource by name"
|
||||
if b.allNamespace {
|
||||
errMsg = "a resource cannot be retrieved by name across all namespaces"
|
||||
}
|
||||
return &Result{singleItemImplied: isSingleItemImplied, err: fmt.Errorf(errMsg)}
|
||||
}
|
||||
}
|
||||
|
||||
info := NewInfo(client, mapping, selectorNamespace, tuple.Name, b.export)
|
||||
items = append(items, info)
|
||||
}
|
||||
|
||||
var visitors Visitor
|
||||
if b.continueOnError {
|
||||
visitors = EagerVisitorList(items)
|
||||
} else {
|
||||
visitors = VisitorList(items)
|
||||
}
|
||||
return &Result{singleItemImplied: isSingleItemImplied, visitor: visitors, sources: items}
|
||||
}
|
||||
|
||||
func (b *Builder) visitByName() *Result {
|
||||
isSingleItemImplied := len(b.names) == 1
|
||||
|
||||
if len(b.paths) != 0 {
|
||||
return &Result{singleItemImplied: isSingleItemImplied, err: fmt.Errorf("when paths, URLs, or stdin is provided as input, you may not specify a resource by arguments as well")}
|
||||
}
|
||||
if len(b.resources) == 0 {
|
||||
return &Result{singleItemImplied: isSingleItemImplied, err: fmt.Errorf("you must provide a resource and a resource name together")}
|
||||
}
|
||||
if len(b.resources) > 1 {
|
||||
return &Result{singleItemImplied: isSingleItemImplied, err: fmt.Errorf("you must specify only one resource")}
|
||||
}
|
||||
|
||||
mappings, err := b.resourceMappings()
|
||||
if err != nil {
|
||||
return &Result{singleItemImplied: isSingleItemImplied, err: err}
|
||||
}
|
||||
mapping := mappings[0]
|
||||
|
||||
client, err := b.mapper.ClientForMapping(mapping)
|
||||
if err != nil {
|
||||
return &Result{err: err}
|
||||
}
|
||||
|
||||
selectorNamespace := b.namespace
|
||||
if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
|
||||
selectorNamespace = ""
|
||||
} else {
|
||||
if len(b.namespace) == 0 {
|
||||
errMsg := "namespace may not be empty when retrieving a resource by name"
|
||||
if b.allNamespace {
|
||||
errMsg = "a resource cannot be retrieved by name across all namespaces"
|
||||
}
|
||||
return &Result{singleItemImplied: isSingleItemImplied, err: fmt.Errorf(errMsg)}
|
||||
}
|
||||
}
|
||||
|
||||
visitors := []Visitor{}
|
||||
for _, name := range b.names {
|
||||
info := NewInfo(client, mapping, selectorNamespace, name, b.export)
|
||||
visitors = append(visitors, info)
|
||||
}
|
||||
return &Result{singleItemImplied: isSingleItemImplied, visitor: VisitorList(visitors), sources: visitors}
|
||||
}
|
||||
|
||||
func (b *Builder) visitByPaths() *Result {
|
||||
singleItemImplied := !b.dir && !b.stream && len(b.paths) == 1
|
||||
if len(b.resources) != 0 {
|
||||
return &Result{singleItemImplied: singleItemImplied, err: fmt.Errorf("when paths, URLs, or stdin is provided as input, you may not specify resource arguments as well")}
|
||||
}
|
||||
if len(b.names) != 0 {
|
||||
return &Result{err: fmt.Errorf("name cannot be provided when a path is specified")}
|
||||
}
|
||||
if len(b.resourceTuples) != 0 {
|
||||
return &Result{err: fmt.Errorf("resource/name arguments cannot be provided when a path is specified")}
|
||||
}
|
||||
|
||||
var visitors Visitor
|
||||
if b.continueOnError {
|
||||
visitors = EagerVisitorList(b.paths)
|
||||
} else {
|
||||
visitors = VisitorList(b.paths)
|
||||
}
|
||||
|
||||
// only items from disk can be refetched
|
||||
if b.latest {
|
||||
// must flatten lists prior to fetching
|
||||
if b.flatten {
|
||||
visitors = NewFlattenListVisitor(visitors, b.mapper)
|
||||
}
|
||||
// must set namespace prior to fetching
|
||||
if b.defaultNamespace {
|
||||
visitors = NewDecoratedVisitor(visitors, SetNamespace(b.namespace))
|
||||
}
|
||||
visitors = NewDecoratedVisitor(visitors, RetrieveLatest)
|
||||
}
|
||||
if b.selector != nil {
|
||||
visitors = NewFilteredVisitor(visitors, FilterBySelector(b.selector))
|
||||
}
|
||||
return &Result{singleItemImplied: singleItemImplied, visitor: visitors, sources: b.paths}
|
||||
}
|
||||
|
||||
// Do returns a Result object with a Visitor for the resources identified by the Builder.
|
||||
// The visitor will respect the error behavior specified by ContinueOnError. Note that stream
|
||||
// inputs are consumed by the first execution - use Infos() or Object() on the Result to capture a list
|
||||
// for further iteration.
|
||||
func (b *Builder) Do() *Result {
|
||||
r := b.visitorResult()
|
||||
if r.err != nil {
|
||||
return r
|
||||
}
|
||||
if b.flatten {
|
||||
r.visitor = NewFlattenListVisitor(r.visitor, b.mapper)
|
||||
}
|
||||
helpers := []VisitorFunc{}
|
||||
if b.defaultNamespace {
|
||||
helpers = append(helpers, SetNamespace(b.namespace))
|
||||
}
|
||||
if b.requireNamespace {
|
||||
helpers = append(helpers, RequireNamespace(b.namespace))
|
||||
}
|
||||
helpers = append(helpers, FilterNamespace)
|
||||
if b.requireObject {
|
||||
helpers = append(helpers, RetrieveLazy)
|
||||
}
|
||||
r.visitor = NewDecoratedVisitor(r.visitor, helpers...)
|
||||
if b.continueOnError {
|
||||
r.visitor = ContinueOnErrorVisitor{r.visitor}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// SplitResourceArgument splits the argument with commas and returns unique
|
||||
// strings in the original order.
|
||||
func SplitResourceArgument(arg string) []string {
|
||||
out := []string{}
|
||||
set := sets.NewString()
|
||||
for _, s := range strings.Split(arg, ",") {
|
||||
if set.Has(s) {
|
||||
continue
|
||||
}
|
||||
set.Insert(s)
|
||||
out = append(out, s)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// HasNames returns true if the provided args contain resource names
|
||||
func HasNames(args []string) (bool, error) {
|
||||
args = normalizeMultipleResourcesArgs(args)
|
||||
hasCombinedTypes, err := hasCombinedTypeArgs(args)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return hasCombinedTypes || len(args) > 1, nil
|
||||
}
|
||||
|
||||
// MultipleTypesRequested returns true if the provided args contain multiple resource kinds
|
||||
func MultipleTypesRequested(args []string) bool {
|
||||
args = normalizeMultipleResourcesArgs(args)
|
||||
rKinds := sets.NewString()
|
||||
for _, arg := range args {
|
||||
if arg == "all" {
|
||||
return true
|
||||
}
|
||||
rTuple, found, err := splitResourceTypeName(arg)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// if tuple not found, assume arg is of the form "type1,type2,...".
|
||||
// Since SplitResourceArgument returns a unique list of kinds,
|
||||
// return true here if len(uniqueList) > 1
|
||||
if !found {
|
||||
if strings.Contains(arg, ",") {
|
||||
splitArgs := SplitResourceArgument(arg)
|
||||
if len(splitArgs) > 1 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if rKinds.Has(rTuple.Resource) {
|
||||
continue
|
||||
}
|
||||
rKinds.Insert(rTuple.Resource)
|
||||
}
|
||||
return (rKinds.Len() > 1)
|
||||
}
|
1292
vendor/k8s.io/kubernetes/pkg/kubectl/resource/builder_test.go
generated
vendored
Normal file
1292
vendor/k8s.io/kubernetes/pkg/kubectl/resource/builder_test.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
24
vendor/k8s.io/kubernetes/pkg/kubectl/resource/doc.go
generated
vendored
Normal file
24
vendor/k8s.io/kubernetes/pkg/kubectl/resource/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
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 assists clients in dealing with RESTful objects that match the
|
||||
// Kubernetes API conventions. The Helper object provides simple CRUD operations
|
||||
// on resources. The Visitor interface makes it easy to deal with multiple resources
|
||||
// in bulk for retrieval and operation. The Builder object simplifies converting
|
||||
// standard command line arguments and parameters into a Visitor that can iterate
|
||||
// over all of the identified resources, whether on the server or on the local
|
||||
// filesystem.
|
||||
package resource // import "k8s.io/kubernetes/pkg/kubectl/resource"
|
166
vendor/k8s.io/kubernetes/pkg/kubectl/resource/helper.go
generated
vendored
Normal file
166
vendor/k8s.io/kubernetes/pkg/kubectl/resource/helper.go
generated
vendored
Normal file
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
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 (
|
||||
"strconv"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
// Helper provides methods for retrieving or mutating a RESTful
|
||||
// resource.
|
||||
type Helper struct {
|
||||
// The name of this resource as the server would recognize it
|
||||
Resource string
|
||||
// A RESTClient capable of mutating this resource.
|
||||
RESTClient RESTClient
|
||||
// An interface for reading or writing the resource version of this
|
||||
// type.
|
||||
Versioner runtime.ResourceVersioner
|
||||
// True if the resource type is scoped to namespaces
|
||||
NamespaceScoped bool
|
||||
}
|
||||
|
||||
// NewHelper creates a Helper from a ResourceMapping
|
||||
func NewHelper(client RESTClient, mapping *meta.RESTMapping) *Helper {
|
||||
return &Helper{
|
||||
Resource: mapping.Resource,
|
||||
RESTClient: client,
|
||||
Versioner: mapping.MetadataAccessor,
|
||||
NamespaceScoped: mapping.Scope.Name() == meta.RESTScopeNameNamespace,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Helper) Get(namespace, name string, export bool) (runtime.Object, error) {
|
||||
req := m.RESTClient.Get().
|
||||
NamespaceIfScoped(namespace, m.NamespaceScoped).
|
||||
Resource(m.Resource).
|
||||
Name(name)
|
||||
if export {
|
||||
req.Param("export", strconv.FormatBool(export))
|
||||
}
|
||||
return req.Do().Get()
|
||||
}
|
||||
|
||||
// TODO: add field selector
|
||||
func (m *Helper) List(namespace, apiVersion string, selector labels.Selector, export bool) (runtime.Object, error) {
|
||||
req := m.RESTClient.Get().
|
||||
NamespaceIfScoped(namespace, m.NamespaceScoped).
|
||||
Resource(m.Resource).
|
||||
LabelsSelectorParam(selector)
|
||||
if export {
|
||||
req.Param("export", strconv.FormatBool(export))
|
||||
}
|
||||
return req.Do().Get()
|
||||
}
|
||||
|
||||
func (m *Helper) Watch(namespace, resourceVersion, apiVersion string, labelSelector labels.Selector) (watch.Interface, error) {
|
||||
return m.RESTClient.Get().
|
||||
Prefix("watch").
|
||||
NamespaceIfScoped(namespace, m.NamespaceScoped).
|
||||
Resource(m.Resource).
|
||||
Param("resourceVersion", resourceVersion).
|
||||
LabelsSelectorParam(labelSelector).
|
||||
Watch()
|
||||
}
|
||||
|
||||
func (m *Helper) WatchSingle(namespace, name, resourceVersion string) (watch.Interface, error) {
|
||||
return m.RESTClient.Get().
|
||||
Prefix("watch").
|
||||
NamespaceIfScoped(namespace, m.NamespaceScoped).
|
||||
Resource(m.Resource).
|
||||
Name(name).
|
||||
Param("resourceVersion", resourceVersion).
|
||||
Watch()
|
||||
}
|
||||
|
||||
func (m *Helper) Delete(namespace, name string) error {
|
||||
return m.RESTClient.Delete().
|
||||
NamespaceIfScoped(namespace, m.NamespaceScoped).
|
||||
Resource(m.Resource).
|
||||
Name(name).
|
||||
Do().
|
||||
Error()
|
||||
}
|
||||
|
||||
func (m *Helper) Create(namespace string, modify bool, obj runtime.Object) (runtime.Object, error) {
|
||||
if modify {
|
||||
// Attempt to version the object based on client logic.
|
||||
version, err := m.Versioner.ResourceVersion(obj)
|
||||
if err != nil {
|
||||
// We don't know how to clear the version on this object, so send it to the server as is
|
||||
return m.createResource(m.RESTClient, m.Resource, namespace, obj)
|
||||
}
|
||||
if version != "" {
|
||||
if err := m.Versioner.SetResourceVersion(obj, ""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return m.createResource(m.RESTClient, m.Resource, namespace, obj)
|
||||
}
|
||||
|
||||
func (m *Helper) createResource(c RESTClient, resource, namespace string, obj runtime.Object) (runtime.Object, error) {
|
||||
return c.Post().NamespaceIfScoped(namespace, m.NamespaceScoped).Resource(resource).Body(obj).Do().Get()
|
||||
}
|
||||
func (m *Helper) Patch(namespace, name string, pt api.PatchType, data []byte) (runtime.Object, error) {
|
||||
return m.RESTClient.Patch(pt).
|
||||
NamespaceIfScoped(namespace, m.NamespaceScoped).
|
||||
Resource(m.Resource).
|
||||
Name(name).
|
||||
Body(data).
|
||||
Do().
|
||||
Get()
|
||||
}
|
||||
|
||||
func (m *Helper) Replace(namespace, name string, overwrite bool, obj runtime.Object) (runtime.Object, error) {
|
||||
c := m.RESTClient
|
||||
|
||||
// Attempt to version the object based on client logic.
|
||||
version, err := m.Versioner.ResourceVersion(obj)
|
||||
if err != nil {
|
||||
// We don't know how to version this object, so send it to the server as is
|
||||
return m.replaceResource(c, m.Resource, namespace, name, obj)
|
||||
}
|
||||
if version == "" && overwrite {
|
||||
// Retrieve the current version of the object to overwrite the server object
|
||||
serverObj, err := c.Get().NamespaceIfScoped(namespace, m.NamespaceScoped).Resource(m.Resource).Name(name).Do().Get()
|
||||
if err != nil {
|
||||
// The object does not exist, but we want it to be created
|
||||
return m.replaceResource(c, m.Resource, namespace, name, obj)
|
||||
}
|
||||
serverVersion, err := m.Versioner.ResourceVersion(serverObj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := m.Versioner.SetResourceVersion(obj, serverVersion); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return m.replaceResource(c, m.Resource, namespace, name, obj)
|
||||
}
|
||||
|
||||
func (m *Helper) replaceResource(c RESTClient, resource, namespace, name string, obj runtime.Object) (runtime.Object, error) {
|
||||
return c.Put().NamespaceIfScoped(namespace, m.NamespaceScoped).Resource(resource).Name(name).Body(obj).Do().Get()
|
||||
}
|
514
vendor/k8s.io/kubernetes/pkg/kubectl/resource/helper_test.go
generated
vendored
Normal file
514
vendor/k8s.io/kubernetes/pkg/kubectl/resource/helper_test.go
generated
vendored
Normal file
|
@ -0,0 +1,514 @@
|
|||
/*
|
||||
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 (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
apitesting "k8s.io/kubernetes/pkg/api/testing"
|
||||
"k8s.io/kubernetes/pkg/client/restclient/fake"
|
||||
)
|
||||
|
||||
func objBody(obj runtime.Object) io.ReadCloser {
|
||||
return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), obj))))
|
||||
}
|
||||
|
||||
func header() http.Header {
|
||||
header := http.Header{}
|
||||
header.Set("Content-Type", runtime.ContentTypeJSON)
|
||||
return header
|
||||
}
|
||||
|
||||
// splitPath returns the segments for a URL path.
|
||||
func splitPath(path string) []string {
|
||||
path = strings.Trim(path, "/")
|
||||
if path == "" {
|
||||
return []string{}
|
||||
}
|
||||
return strings.Split(path, "/")
|
||||
}
|
||||
|
||||
func TestHelperDelete(t *testing.T) {
|
||||
tests := []struct {
|
||||
Err bool
|
||||
Req func(*http.Request) bool
|
||||
Resp *http.Response
|
||||
HttpErr error
|
||||
}{
|
||||
{
|
||||
HttpErr: errors.New("failure"),
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusNotFound,
|
||||
Header: header(),
|
||||
Body: objBody(&metav1.Status{Status: metav1.StatusFailure}),
|
||||
},
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: header(),
|
||||
Body: objBody(&metav1.Status{Status: metav1.StatusSuccess}),
|
||||
},
|
||||
Req: func(req *http.Request) bool {
|
||||
if req.Method != "DELETE" {
|
||||
t.Errorf("unexpected method: %#v", req)
|
||||
return false
|
||||
}
|
||||
parts := splitPath(req.URL.Path)
|
||||
if len(parts) < 3 {
|
||||
t.Errorf("expected URL path to have 3 parts: %s", req.URL.Path)
|
||||
return false
|
||||
}
|
||||
if parts[1] != "bar" {
|
||||
t.Errorf("url doesn't contain namespace: %#v", req)
|
||||
return false
|
||||
}
|
||||
if parts[2] != "foo" {
|
||||
t.Errorf("url doesn't contain name: %#v", req)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
client := &fake.RESTClient{
|
||||
NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
|
||||
Resp: test.Resp,
|
||||
Err: test.HttpErr,
|
||||
}
|
||||
modifier := &Helper{
|
||||
RESTClient: client,
|
||||
NamespaceScoped: true,
|
||||
}
|
||||
err := modifier.Delete("bar", "foo")
|
||||
if (err != nil) != test.Err {
|
||||
t.Errorf("unexpected error: %t %v", test.Err, err)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if test.Req != nil && !test.Req(client.Req) {
|
||||
t.Errorf("unexpected request: %#v", client.Req)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperCreate(t *testing.T) {
|
||||
expectPost := func(req *http.Request) bool {
|
||||
if req.Method != "POST" {
|
||||
t.Errorf("unexpected method: %#v", req)
|
||||
return false
|
||||
}
|
||||
parts := splitPath(req.URL.Path)
|
||||
if parts[1] != "bar" {
|
||||
t.Errorf("url doesn't contain namespace: %#v", req)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
Resp *http.Response
|
||||
HttpErr error
|
||||
Modify bool
|
||||
Object runtime.Object
|
||||
|
||||
ExpectObject runtime.Object
|
||||
Err bool
|
||||
Req func(*http.Request) bool
|
||||
}{
|
||||
{
|
||||
HttpErr: errors.New("failure"),
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusNotFound,
|
||||
Header: header(),
|
||||
Body: objBody(&metav1.Status{Status: metav1.StatusFailure}),
|
||||
},
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: header(),
|
||||
Body: objBody(&metav1.Status{Status: metav1.StatusSuccess}),
|
||||
},
|
||||
Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
|
||||
ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
|
||||
Req: expectPost,
|
||||
},
|
||||
{
|
||||
Modify: false,
|
||||
Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
|
||||
ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
|
||||
Resp: &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess})},
|
||||
Req: expectPost,
|
||||
},
|
||||
{
|
||||
Modify: true,
|
||||
Object: &api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"},
|
||||
Spec: apitesting.DeepEqualSafePodSpec(),
|
||||
},
|
||||
ExpectObject: &api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||
Spec: apitesting.DeepEqualSafePodSpec(),
|
||||
},
|
||||
Resp: &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess})},
|
||||
Req: expectPost,
|
||||
},
|
||||
}
|
||||
for i, test := range tests {
|
||||
client := &fake.RESTClient{
|
||||
NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
|
||||
Resp: test.Resp,
|
||||
Err: test.HttpErr,
|
||||
}
|
||||
modifier := &Helper{
|
||||
RESTClient: client,
|
||||
Versioner: testapi.Default.MetadataAccessor(),
|
||||
NamespaceScoped: true,
|
||||
}
|
||||
_, err := modifier.Create("bar", test.Modify, test.Object)
|
||||
if (err != nil) != test.Err {
|
||||
t.Errorf("%d: unexpected error: %t %v", i, test.Err, err)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if test.Req != nil && !test.Req(client.Req) {
|
||||
t.Errorf("%d: unexpected request: %#v", i, client.Req)
|
||||
}
|
||||
body, err := ioutil.ReadAll(client.Req.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("%d: unexpected error: %#v", i, err)
|
||||
}
|
||||
t.Logf("got body: %s", string(body))
|
||||
expect := []byte{}
|
||||
if test.ExpectObject != nil {
|
||||
expect = []byte(runtime.EncodeOrDie(testapi.Default.Codec(), test.ExpectObject))
|
||||
}
|
||||
if !reflect.DeepEqual(expect, body) {
|
||||
t.Errorf("%d: unexpected body: %s (expected %s)", i, string(body), string(expect))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperGet(t *testing.T) {
|
||||
tests := []struct {
|
||||
Err bool
|
||||
Req func(*http.Request) bool
|
||||
Resp *http.Response
|
||||
HttpErr error
|
||||
}{
|
||||
{
|
||||
HttpErr: errors.New("failure"),
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusNotFound,
|
||||
Header: header(),
|
||||
Body: objBody(&metav1.Status{Status: metav1.StatusFailure}),
|
||||
},
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: header(),
|
||||
Body: objBody(&api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}),
|
||||
},
|
||||
Req: func(req *http.Request) bool {
|
||||
if req.Method != "GET" {
|
||||
t.Errorf("unexpected method: %#v", req)
|
||||
return false
|
||||
}
|
||||
parts := splitPath(req.URL.Path)
|
||||
if parts[1] != "bar" {
|
||||
t.Errorf("url doesn't contain namespace: %#v", req)
|
||||
return false
|
||||
}
|
||||
if parts[2] != "foo" {
|
||||
t.Errorf("url doesn't contain name: %#v", req)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
client := &fake.RESTClient{
|
||||
NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
|
||||
Resp: test.Resp,
|
||||
Err: test.HttpErr,
|
||||
}
|
||||
modifier := &Helper{
|
||||
RESTClient: client,
|
||||
NamespaceScoped: true,
|
||||
}
|
||||
obj, err := modifier.Get("bar", "foo", false)
|
||||
if (err != nil) != test.Err {
|
||||
t.Errorf("unexpected error: %t %v", test.Err, err)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if obj.(*api.Pod).Name != "foo" {
|
||||
t.Errorf("unexpected object: %#v", obj)
|
||||
}
|
||||
if test.Req != nil && !test.Req(client.Req) {
|
||||
t.Errorf("unexpected request: %#v", client.Req)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperList(t *testing.T) {
|
||||
tests := []struct {
|
||||
Err bool
|
||||
Req func(*http.Request) bool
|
||||
Resp *http.Response
|
||||
HttpErr error
|
||||
}{
|
||||
{
|
||||
HttpErr: errors.New("failure"),
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusNotFound,
|
||||
Header: header(),
|
||||
Body: objBody(&metav1.Status{Status: metav1.StatusFailure}),
|
||||
},
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: header(),
|
||||
Body: objBody(&api.PodList{
|
||||
Items: []api.Pod{{
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
Req: func(req *http.Request) bool {
|
||||
if req.Method != "GET" {
|
||||
t.Errorf("unexpected method: %#v", req)
|
||||
return false
|
||||
}
|
||||
if req.URL.Path != "/namespaces/bar" {
|
||||
t.Errorf("url doesn't contain name: %#v", req.URL)
|
||||
return false
|
||||
}
|
||||
if req.URL.Query().Get(metav1.LabelSelectorQueryParam(api.Registry.GroupOrDie(api.GroupName).GroupVersion.String())) != labels.SelectorFromSet(labels.Set{"foo": "baz"}).String() {
|
||||
t.Errorf("url doesn't contain query parameters: %#v", req.URL)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
client := &fake.RESTClient{
|
||||
NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
|
||||
Resp: test.Resp,
|
||||
Err: test.HttpErr,
|
||||
}
|
||||
modifier := &Helper{
|
||||
RESTClient: client,
|
||||
NamespaceScoped: true,
|
||||
}
|
||||
obj, err := modifier.List("bar", api.Registry.GroupOrDie(api.GroupName).GroupVersion.String(), labels.SelectorFromSet(labels.Set{"foo": "baz"}), false)
|
||||
if (err != nil) != test.Err {
|
||||
t.Errorf("unexpected error: %t %v", test.Err, err)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if obj.(*api.PodList).Items[0].Name != "foo" {
|
||||
t.Errorf("unexpected object: %#v", obj)
|
||||
}
|
||||
if test.Req != nil && !test.Req(client.Req) {
|
||||
t.Errorf("unexpected request: %#v", client.Req)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHelperReplace(t *testing.T) {
|
||||
expectPut := func(path string, req *http.Request) bool {
|
||||
if req.Method != "PUT" {
|
||||
t.Errorf("unexpected method: %#v", req)
|
||||
return false
|
||||
}
|
||||
if req.URL.Path != path {
|
||||
t.Errorf("unexpected url: %v", req.URL)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
Resp *http.Response
|
||||
HTTPClient *http.Client
|
||||
HttpErr error
|
||||
Overwrite bool
|
||||
Object runtime.Object
|
||||
Namespace string
|
||||
NamespaceScoped bool
|
||||
|
||||
ExpectPath string
|
||||
ExpectObject runtime.Object
|
||||
Err bool
|
||||
Req func(string, *http.Request) bool
|
||||
}{
|
||||
{
|
||||
Namespace: "bar",
|
||||
NamespaceScoped: true,
|
||||
HttpErr: errors.New("failure"),
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Namespace: "bar",
|
||||
NamespaceScoped: true,
|
||||
Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusNotFound,
|
||||
Header: header(),
|
||||
Body: objBody(&metav1.Status{Status: metav1.StatusFailure}),
|
||||
},
|
||||
Err: true,
|
||||
},
|
||||
{
|
||||
Namespace: "bar",
|
||||
NamespaceScoped: true,
|
||||
Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
|
||||
ExpectPath: "/namespaces/bar/foo",
|
||||
ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
|
||||
Resp: &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: header(),
|
||||
Body: objBody(&metav1.Status{Status: metav1.StatusSuccess}),
|
||||
},
|
||||
Req: expectPut,
|
||||
},
|
||||
// namespace scoped resource
|
||||
{
|
||||
Namespace: "bar",
|
||||
NamespaceScoped: true,
|
||||
Object: &api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||
Spec: apitesting.DeepEqualSafePodSpec(),
|
||||
},
|
||||
ExpectPath: "/namespaces/bar/foo",
|
||||
ExpectObject: &api.Pod{
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"},
|
||||
Spec: apitesting.DeepEqualSafePodSpec(),
|
||||
},
|
||||
Overwrite: true,
|
||||
HTTPClient: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
if req.Method == "PUT" {
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess})}, nil
|
||||
}
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}})}, nil
|
||||
}),
|
||||
Req: expectPut,
|
||||
},
|
||||
// cluster scoped resource
|
||||
{
|
||||
Object: &api.Node{
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
||||
},
|
||||
ExpectObject: &api.Node{
|
||||
ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"},
|
||||
},
|
||||
Overwrite: true,
|
||||
ExpectPath: "/foo",
|
||||
HTTPClient: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
if req.Method == "PUT" {
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess})}, nil
|
||||
}
|
||||
return &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&api.Node{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}})}, nil
|
||||
}),
|
||||
Req: expectPut,
|
||||
},
|
||||
{
|
||||
Namespace: "bar",
|
||||
NamespaceScoped: true,
|
||||
Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
|
||||
ExpectPath: "/namespaces/bar/foo",
|
||||
ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
|
||||
Resp: &http.Response{StatusCode: http.StatusOK, Header: header(), Body: objBody(&metav1.Status{Status: metav1.StatusSuccess})},
|
||||
Req: expectPut,
|
||||
},
|
||||
}
|
||||
for i, test := range tests {
|
||||
client := &fake.RESTClient{
|
||||
Client: test.HTTPClient,
|
||||
NegotiatedSerializer: testapi.Default.NegotiatedSerializer(),
|
||||
Resp: test.Resp,
|
||||
Err: test.HttpErr,
|
||||
}
|
||||
modifier := &Helper{
|
||||
RESTClient: client,
|
||||
Versioner: testapi.Default.MetadataAccessor(),
|
||||
NamespaceScoped: test.NamespaceScoped,
|
||||
}
|
||||
_, err := modifier.Replace(test.Namespace, "foo", test.Overwrite, test.Object)
|
||||
if (err != nil) != test.Err {
|
||||
t.Errorf("%d: unexpected error: %t %v", i, test.Err, err)
|
||||
}
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if test.Req != nil && !test.Req(test.ExpectPath, client.Req) {
|
||||
t.Errorf("%d: unexpected request: %#v", i, client.Req)
|
||||
}
|
||||
body, err := ioutil.ReadAll(client.Req.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("%d: unexpected error: %#v", i, err)
|
||||
}
|
||||
expect := []byte{}
|
||||
if test.ExpectObject != nil {
|
||||
expect = []byte(runtime.EncodeOrDie(testapi.Default.Codec(), test.ExpectObject))
|
||||
}
|
||||
if !reflect.DeepEqual(expect, body) {
|
||||
t.Errorf("%d: unexpected body: %s", i, string(body))
|
||||
}
|
||||
}
|
||||
}
|
46
vendor/k8s.io/kubernetes/pkg/kubectl/resource/interfaces.go
generated
vendored
Normal file
46
vendor/k8s.io/kubernetes/pkg/kubectl/resource/interfaces.go
generated
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
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 (
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
client "k8s.io/kubernetes/pkg/client/restclient"
|
||||
)
|
||||
|
||||
// RESTClient is a client helper for dealing with RESTful resources
|
||||
// in a generic way.
|
||||
type RESTClient interface {
|
||||
Get() *client.Request
|
||||
Post() *client.Request
|
||||
Patch(api.PatchType) *client.Request
|
||||
Delete() *client.Request
|
||||
Put() *client.Request
|
||||
}
|
||||
|
||||
// ClientMapper abstracts retrieving a Client for mapped objects.
|
||||
type ClientMapper interface {
|
||||
ClientForMapping(mapping *meta.RESTMapping) (RESTClient, error)
|
||||
}
|
||||
|
||||
// ClientMapperFunc implements ClientMapper for a function
|
||||
type ClientMapperFunc func(mapping *meta.RESTMapping) (RESTClient, error)
|
||||
|
||||
// ClientForMapping implements ClientMapper
|
||||
func (f ClientMapperFunc) ClientForMapping(mapping *meta.RESTMapping) (RESTClient, error) {
|
||||
return f(mapping)
|
||||
}
|
154
vendor/k8s.io/kubernetes/pkg/kubectl/resource/mapper.go
generated
vendored
Normal file
154
vendor/k8s.io/kubernetes/pkg/kubectl/resource/mapper.go
generated
vendored
Normal file
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
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/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// DisabledClientForMapping allows callers to avoid allowing remote calls when handling
|
||||
// resources.
|
||||
type DisabledClientForMapping struct {
|
||||
ClientMapper
|
||||
}
|
||||
|
||||
func (f DisabledClientForMapping) ClientForMapping(mapping *meta.RESTMapping) (RESTClient, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Mapper is a convenience struct for holding references to the three interfaces
|
||||
// needed to create Info for arbitrary objects.
|
||||
type Mapper struct {
|
||||
runtime.ObjectTyper
|
||||
meta.RESTMapper
|
||||
ClientMapper
|
||||
runtime.Decoder
|
||||
}
|
||||
|
||||
// InfoForData creates an Info object for the given data. An error is returned
|
||||
// if any of the decoding or client lookup steps fail. Name and namespace will be
|
||||
// set into Info if the mapping's MetadataAccessor can retrieve them.
|
||||
func (m *Mapper) InfoForData(data []byte, source string) (*Info, error) {
|
||||
versions := &runtime.VersionedObjects{}
|
||||
_, gvk, err := m.Decode(data, nil, versions)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to decode %q: %v", source, err)
|
||||
}
|
||||
|
||||
obj, versioned := versions.Last(), versions.First()
|
||||
mapping, err := m.RESTMapping(gvk.GroupKind(), gvk.Version)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to recognize %q: %v", source, err)
|
||||
}
|
||||
|
||||
client, err := m.ClientForMapping(mapping)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to connect to a server to handle %q: %v", mapping.Resource, err)
|
||||
}
|
||||
|
||||
name, _ := mapping.MetadataAccessor.Name(obj)
|
||||
namespace, _ := mapping.MetadataAccessor.Namespace(obj)
|
||||
resourceVersion, _ := mapping.MetadataAccessor.ResourceVersion(obj)
|
||||
|
||||
return &Info{
|
||||
Mapping: mapping,
|
||||
Client: client,
|
||||
Namespace: namespace,
|
||||
Name: name,
|
||||
Source: source,
|
||||
VersionedObject: versioned,
|
||||
Object: obj,
|
||||
ResourceVersion: resourceVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// InfoForObject creates an Info object for the given Object. An error is returned
|
||||
// if the object cannot be introspected. Name and namespace will be set into Info
|
||||
// if the mapping's MetadataAccessor can retrieve them.
|
||||
func (m *Mapper) InfoForObject(obj runtime.Object, preferredGVKs []schema.GroupVersionKind) (*Info, error) {
|
||||
groupVersionKinds, _, err := m.ObjectKinds(obj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get type info from the object %q: %v", reflect.TypeOf(obj), err)
|
||||
}
|
||||
|
||||
groupVersionKind := groupVersionKinds[0]
|
||||
if len(groupVersionKinds) > 1 && len(preferredGVKs) > 0 {
|
||||
groupVersionKind = preferredObjectKind(groupVersionKinds, preferredGVKs)
|
||||
}
|
||||
|
||||
mapping, err := m.RESTMapping(groupVersionKind.GroupKind(), groupVersionKind.Version)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to recognize %v: %v", groupVersionKind, err)
|
||||
}
|
||||
client, err := m.ClientForMapping(mapping)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to connect to a server to handle %q: %v", mapping.Resource, err)
|
||||
}
|
||||
name, _ := mapping.MetadataAccessor.Name(obj)
|
||||
namespace, _ := mapping.MetadataAccessor.Namespace(obj)
|
||||
resourceVersion, _ := mapping.MetadataAccessor.ResourceVersion(obj)
|
||||
return &Info{
|
||||
Mapping: mapping,
|
||||
Client: client,
|
||||
Namespace: namespace,
|
||||
Name: name,
|
||||
|
||||
Object: obj,
|
||||
ResourceVersion: resourceVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// preferredObjectKind picks the possibility that most closely matches the priority list in this order:
|
||||
// GroupVersionKind matches (exact match)
|
||||
// GroupKind matches
|
||||
// Group matches
|
||||
func preferredObjectKind(possibilities []schema.GroupVersionKind, preferences []schema.GroupVersionKind) schema.GroupVersionKind {
|
||||
// Exact match
|
||||
for _, priority := range preferences {
|
||||
for _, possibility := range possibilities {
|
||||
if possibility == priority {
|
||||
return possibility
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GroupKind match
|
||||
for _, priority := range preferences {
|
||||
for _, possibility := range possibilities {
|
||||
if possibility.GroupKind() == priority.GroupKind() {
|
||||
return possibility
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Group match
|
||||
for _, priority := range preferences {
|
||||
for _, possibility := range possibilities {
|
||||
if possibility.Group == priority.Group {
|
||||
return possibility
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Just pick the first
|
||||
return possibilities[0]
|
||||
}
|
291
vendor/k8s.io/kubernetes/pkg/kubectl/resource/result.go
generated
vendored
Normal file
291
vendor/k8s.io/kubernetes/pkg/kubectl/resource/result.go
generated
vendored
Normal file
|
@ -0,0 +1,291 @@
|
|||
/*
|
||||
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/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"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
ignoreErrors []utilerrors.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, utilerrors.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 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
|
||||
// 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 := 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 &api.List{
|
||||
ListMeta: metav1.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 individual resources and resource collections - %d resources were found", len(info))
|
||||
}
|
||||
return info[0].Watch(resourceVersion)
|
||||
}
|
||||
return w.Watch(resourceVersion)
|
||||
}
|
||||
|
||||
// AsVersionedObject converts a list of infos into a single object - either a List containing
|
||||
// the objects as children, or if only a single Object is present, as that object. The provided
|
||||
// version will be preferred as the conversion target, but the Object's mapping version will be
|
||||
// used if that version is not present.
|
||||
func AsVersionedObject(infos []*Info, forceList bool, version schema.GroupVersion, encoder runtime.Encoder) (runtime.Object, error) {
|
||||
objects, err := AsVersionedObjects(infos, version, encoder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var object runtime.Object
|
||||
if len(objects) == 1 && !forceList {
|
||||
object = objects[0]
|
||||
} else {
|
||||
object = &api.List{Items: objects}
|
||||
converted, err := TryConvert(api.Scheme, object, version, api.Registry.GroupOrDie(api.GroupName).GroupVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
object = converted
|
||||
}
|
||||
return object, nil
|
||||
}
|
||||
|
||||
// AsVersionedObjects converts a list of infos into versioned objects. The provided
|
||||
// version will be preferred as the conversion target, but the Object's mapping version will be
|
||||
// used if that version is not present.
|
||||
func AsVersionedObjects(infos []*Info, version schema.GroupVersion, encoder runtime.Encoder) ([]runtime.Object, error) {
|
||||
objects := []runtime.Object{}
|
||||
for _, info := range infos {
|
||||
if info.Object == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: use info.VersionedObject as the value?
|
||||
switch obj := info.Object.(type) {
|
||||
case *extensions.ThirdPartyResourceData:
|
||||
objects = append(objects, &runtime.Unknown{Raw: obj.Data})
|
||||
continue
|
||||
}
|
||||
|
||||
// objects that are not part of api.Scheme must be converted to JSON
|
||||
// TODO: convert to map[string]interface{}, attach to runtime.Unknown?
|
||||
if !version.Empty() {
|
||||
if _, _, err := api.Scheme.ObjectKinds(info.Object); runtime.IsNotRegisteredError(err) {
|
||||
// TODO: ideally this would encode to version, but we don't expose multiple codecs here.
|
||||
data, err := runtime.Encode(encoder, info.Object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: Set ContentEncoding and ContentType.
|
||||
objects = append(objects, &runtime.Unknown{Raw: data})
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
converted, err := TryConvert(info.Mapping.ObjectConvertor, info.Object, version, info.Mapping.GroupVersionKind.GroupVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
objects = append(objects, converted)
|
||||
}
|
||||
return objects, nil
|
||||
}
|
||||
|
||||
// TryConvert attempts to convert the given object to the provided versions in order. This function assumes
|
||||
// the object is in internal version.
|
||||
func TryConvert(converter runtime.ObjectConvertor, object runtime.Object, versions ...schema.GroupVersion) (runtime.Object, error) {
|
||||
var last error
|
||||
for _, version := range versions {
|
||||
if version.Empty() {
|
||||
return object, nil
|
||||
}
|
||||
obj, err := converter.ConvertToVersion(object, version)
|
||||
if err != nil {
|
||||
last = err
|
||||
continue
|
||||
}
|
||||
return obj, nil
|
||||
}
|
||||
return nil, last
|
||||
}
|
90
vendor/k8s.io/kubernetes/pkg/kubectl/resource/selector.go
generated
vendored
Normal file
90
vendor/k8s.io/kubernetes/pkg/kubectl/resource/selector.go
generated
vendored
Normal file
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
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"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
)
|
||||
|
||||
// Selector is a Visitor for resources that match a label selector.
|
||||
type Selector struct {
|
||||
Client RESTClient
|
||||
Mapping *meta.RESTMapping
|
||||
Namespace string
|
||||
Selector labels.Selector
|
||||
Export bool
|
||||
}
|
||||
|
||||
// NewSelector creates a resource selector which hides details of getting items by their label selector.
|
||||
func NewSelector(client RESTClient, mapping *meta.RESTMapping, namespace string, selector labels.Selector, export bool) *Selector {
|
||||
return &Selector{
|
||||
Client: client,
|
||||
Mapping: mapping,
|
||||
Namespace: namespace,
|
||||
Selector: selector,
|
||||
Export: export,
|
||||
}
|
||||
}
|
||||
|
||||
// Visit implements Visitor
|
||||
func (r *Selector) Visit(fn VisitorFunc) error {
|
||||
list, err := NewHelper(r.Client, r.Mapping).List(r.Namespace, r.ResourceMapping().GroupVersionKind.GroupVersion().String(), r.Selector, r.Export)
|
||||
if err != nil {
|
||||
if errors.IsBadRequest(err) || errors.IsNotFound(err) {
|
||||
if se, ok := err.(*errors.StatusError); ok {
|
||||
// modify the message without hiding this is an API error
|
||||
if r.Selector.Empty() {
|
||||
se.ErrStatus.Message = fmt.Sprintf("Unable to list %q: %v", r.Mapping.Resource, se.ErrStatus.Message)
|
||||
} else {
|
||||
se.ErrStatus.Message = fmt.Sprintf("Unable to find %q that match the selector %q: %v", r.Mapping.Resource, r.Selector, se.ErrStatus.Message)
|
||||
}
|
||||
return se
|
||||
}
|
||||
if r.Selector.Empty() {
|
||||
return fmt.Errorf("Unable to list %q: %v", r.Mapping.Resource, err)
|
||||
} else {
|
||||
return fmt.Errorf("Unable to find %q that match the selector %q: %v", r.Mapping.Resource, r.Selector, err)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
accessor := r.Mapping.MetadataAccessor
|
||||
resourceVersion, _ := accessor.ResourceVersion(list)
|
||||
info := &Info{
|
||||
Client: r.Client,
|
||||
Mapping: r.Mapping,
|
||||
Namespace: r.Namespace,
|
||||
|
||||
Object: list,
|
||||
ResourceVersion: resourceVersion,
|
||||
}
|
||||
return fn(info, nil)
|
||||
}
|
||||
|
||||
func (r *Selector) Watch(resourceVersion string) (watch.Interface, error) {
|
||||
return NewHelper(r.Client, r.Mapping).Watch(r.Namespace, resourceVersion, r.ResourceMapping().GroupVersionKind.GroupVersion().String(), r.Selector)
|
||||
}
|
||||
|
||||
// ResourceMapping returns the mapping for this resource and implements ResourceMapping
|
||||
func (r *Selector) ResourceMapping() *meta.RESTMapping {
|
||||
return r.Mapping
|
||||
}
|
697
vendor/k8s.io/kubernetes/pkg/kubectl/resource/visitor.go
generated
vendored
Normal file
697
vendor/k8s.io/kubernetes/pkg/kubectl/resource/visitor.go
generated
vendored
Normal file
|
@ -0,0 +1,697 @@
|
|||
/*
|
||||
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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"golang.org/x/text/transform"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/validation"
|
||||
)
|
||||
|
||||
const (
|
||||
constSTDINstr string = "STDIN"
|
||||
stopValidateMessage = "if you choose to ignore these errors, turn validation off with --validate=false"
|
||||
)
|
||||
|
||||
// Visitor lets clients walk a list of resources.
|
||||
type Visitor interface {
|
||||
Visit(VisitorFunc) error
|
||||
}
|
||||
|
||||
// VisitorFunc implements the Visitor interface for a matching function.
|
||||
// If there was a problem walking a list of resources, the incoming error
|
||||
// will describe the problem and the function can decide how to handle that error.
|
||||
// A nil returned indicates to accept an error to continue loops even when errors happen.
|
||||
// This is useful for ignoring certain kinds of errors or aggregating errors in some way.
|
||||
type VisitorFunc func(*Info, error) error
|
||||
|
||||
// Watchable describes a resource that can be watched for changes that occur on the server,
|
||||
// beginning after the provided resource version.
|
||||
type Watchable interface {
|
||||
Watch(resourceVersion string) (watch.Interface, error)
|
||||
}
|
||||
|
||||
// ResourceMapping allows an object to return the resource mapping associated with
|
||||
// the resource or resources it represents.
|
||||
type ResourceMapping interface {
|
||||
ResourceMapping() *meta.RESTMapping
|
||||
}
|
||||
|
||||
// Info contains temporary info to execute a REST call, or show the results
|
||||
// of an already completed REST call.
|
||||
type Info struct {
|
||||
Client RESTClient
|
||||
Mapping *meta.RESTMapping
|
||||
Namespace string
|
||||
Name string
|
||||
|
||||
// Optional, Source is the filename or URL to template file (.json or .yaml),
|
||||
// or stdin to use to handle the resource
|
||||
Source string
|
||||
// Optional, this is the provided object in a versioned type before defaulting
|
||||
// and conversions into its corresponding internal type. This is useful for
|
||||
// reflecting on user intent which may be lost after defaulting and conversions.
|
||||
VersionedObject runtime.Object
|
||||
// Optional, this is the most recent value returned by the server if available
|
||||
Object runtime.Object
|
||||
// Optional, this is the most recent resource version the server knows about for
|
||||
// this type of resource. It may not match the resource version of the object,
|
||||
// but if set it should be equal to or newer than the resource version of the
|
||||
// object (however the server defines resource version).
|
||||
ResourceVersion string
|
||||
// Optional, should this resource be exported, stripped of cluster-specific and instance specific fields
|
||||
Export bool
|
||||
}
|
||||
|
||||
// NewInfo returns a new info object
|
||||
func NewInfo(client RESTClient, mapping *meta.RESTMapping, namespace, name string, export bool) *Info {
|
||||
return &Info{
|
||||
Client: client,
|
||||
Mapping: mapping,
|
||||
Namespace: namespace,
|
||||
Name: name,
|
||||
Export: export,
|
||||
}
|
||||
}
|
||||
|
||||
// Visit implements Visitor
|
||||
func (i *Info) Visit(fn VisitorFunc) error {
|
||||
return fn(i, nil)
|
||||
}
|
||||
|
||||
// Get retrieves the object from the Namespace and Name fields
|
||||
func (i *Info) Get() (err error) {
|
||||
obj, err := NewHelper(i.Client, i.Mapping).Get(i.Namespace, i.Name, i.Export)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) && len(i.Namespace) > 0 && i.Namespace != api.NamespaceDefault && i.Namespace != api.NamespaceAll {
|
||||
err2 := i.Client.Get().AbsPath("api", "v1", "namespaces", i.Namespace).Do().Error()
|
||||
if err2 != nil && errors.IsNotFound(err2) {
|
||||
return err2
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
i.Object = obj
|
||||
i.ResourceVersion, _ = i.Mapping.MetadataAccessor.ResourceVersion(obj)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Refresh updates the object with another object. If ignoreError is set
|
||||
// the Object will be updated even if name, namespace, or resourceVersion
|
||||
// attributes cannot be loaded from the object.
|
||||
func (i *Info) Refresh(obj runtime.Object, ignoreError bool) error {
|
||||
name, err := i.Mapping.MetadataAccessor.Name(obj)
|
||||
if err != nil {
|
||||
if !ignoreError {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
i.Name = name
|
||||
}
|
||||
namespace, err := i.Mapping.MetadataAccessor.Namespace(obj)
|
||||
if err != nil {
|
||||
if !ignoreError {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
i.Namespace = namespace
|
||||
}
|
||||
version, err := i.Mapping.MetadataAccessor.ResourceVersion(obj)
|
||||
if err != nil {
|
||||
if !ignoreError {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
i.ResourceVersion = version
|
||||
}
|
||||
i.Object = obj
|
||||
return nil
|
||||
}
|
||||
|
||||
// Namespaced returns true if the object belongs to a namespace
|
||||
func (i *Info) Namespaced() bool {
|
||||
return i.Mapping != nil && i.Mapping.Scope.Name() == meta.RESTScopeNameNamespace
|
||||
}
|
||||
|
||||
// Watch returns server changes to this object after it was retrieved.
|
||||
func (i *Info) Watch(resourceVersion string) (watch.Interface, error) {
|
||||
return NewHelper(i.Client, i.Mapping).WatchSingle(i.Namespace, i.Name, resourceVersion)
|
||||
}
|
||||
|
||||
// ResourceMapping returns the mapping for this resource and implements ResourceMapping
|
||||
func (i *Info) ResourceMapping() *meta.RESTMapping {
|
||||
return i.Mapping
|
||||
}
|
||||
|
||||
// VisitorList implements Visit for the sub visitors it contains. The first error
|
||||
// returned from a child Visitor will terminate iteration.
|
||||
type VisitorList []Visitor
|
||||
|
||||
// Visit implements Visitor
|
||||
func (l VisitorList) Visit(fn VisitorFunc) error {
|
||||
for i := range l {
|
||||
if err := l[i].Visit(fn); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EagerVisitorList implements Visit for the sub visitors it contains. All errors
|
||||
// will be captured and returned at the end of iteration.
|
||||
type EagerVisitorList []Visitor
|
||||
|
||||
// Visit implements Visitor, and gathers errors that occur during processing until
|
||||
// all sub visitors have been visited.
|
||||
func (l EagerVisitorList) Visit(fn VisitorFunc) error {
|
||||
errs := []error(nil)
|
||||
for i := range l {
|
||||
if err := l[i].Visit(func(info *Info, err error) error {
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
return nil
|
||||
}
|
||||
if err := fn(info, nil); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
func ValidateSchema(data []byte, schema validation.Schema) error {
|
||||
if schema == nil {
|
||||
return nil
|
||||
}
|
||||
if err := schema.ValidateBytes(data); err != nil {
|
||||
return fmt.Errorf("error validating data: %v; %s", err, stopValidateMessage)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// URLVisitor downloads the contents of a URL, and if successful, returns
|
||||
// an info object representing the downloaded object.
|
||||
type URLVisitor struct {
|
||||
URL *url.URL
|
||||
*StreamVisitor
|
||||
HttpAttemptCount int
|
||||
}
|
||||
|
||||
func (v *URLVisitor) Visit(fn VisitorFunc) error {
|
||||
body, err := readHttpWithRetries(httpgetImpl, time.Second, v.URL.String(), v.HttpAttemptCount)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer body.Close()
|
||||
v.StreamVisitor.Reader = body
|
||||
return v.StreamVisitor.Visit(fn)
|
||||
}
|
||||
|
||||
// readHttpWithRetries tries to http.Get the v.URL retries times before giving up.
|
||||
func readHttpWithRetries(get httpget, duration time.Duration, u string, attempts int) (io.ReadCloser, error) {
|
||||
var err error
|
||||
var body io.ReadCloser
|
||||
if attempts <= 0 {
|
||||
return nil, fmt.Errorf("http attempts must be greater than 0, was %d", attempts)
|
||||
}
|
||||
for i := 0; i < attempts; i++ {
|
||||
var statusCode int
|
||||
var status string
|
||||
if i > 0 {
|
||||
time.Sleep(duration)
|
||||
}
|
||||
|
||||
// Try to get the URL
|
||||
statusCode, status, body, err = get(u)
|
||||
|
||||
// Retry Errors
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Error - Set the error condition from the StatusCode
|
||||
if statusCode != http.StatusOK {
|
||||
err = fmt.Errorf("unable to read URL %q, server reported %s, status code=%d", u, status, statusCode)
|
||||
}
|
||||
|
||||
if statusCode >= 500 && statusCode < 600 {
|
||||
// Retry 500's
|
||||
continue
|
||||
} else {
|
||||
// Don't retry other StatusCodes
|
||||
break
|
||||
}
|
||||
}
|
||||
return body, err
|
||||
}
|
||||
|
||||
// httpget Defines function to retrieve a url and return the results. Exists for unit test stubbing.
|
||||
type httpget func(url string) (int, string, io.ReadCloser, error)
|
||||
|
||||
// httpgetImpl Implements a function to retrieve a url and return the results.
|
||||
func httpgetImpl(url string) (int, string, io.ReadCloser, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return 0, "", nil, err
|
||||
}
|
||||
return resp.StatusCode, resp.Status, resp.Body, nil
|
||||
}
|
||||
|
||||
// DecoratedVisitor will invoke the decorators in order prior to invoking the visitor function
|
||||
// passed to Visit. An error will terminate the visit.
|
||||
type DecoratedVisitor struct {
|
||||
visitor Visitor
|
||||
decorators []VisitorFunc
|
||||
}
|
||||
|
||||
// NewDecoratedVisitor will create a visitor that invokes the provided visitor functions before
|
||||
// the user supplied visitor function is invoked, giving them the opportunity to mutate the Info
|
||||
// object or terminate early with an error.
|
||||
func NewDecoratedVisitor(v Visitor, fn ...VisitorFunc) Visitor {
|
||||
if len(fn) == 0 {
|
||||
return v
|
||||
}
|
||||
return DecoratedVisitor{v, fn}
|
||||
}
|
||||
|
||||
// Visit implements Visitor
|
||||
func (v DecoratedVisitor) Visit(fn VisitorFunc) error {
|
||||
return v.visitor.Visit(func(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range v.decorators {
|
||||
if err := v.decorators[i](info, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return fn(info, nil)
|
||||
})
|
||||
}
|
||||
|
||||
// ContinueOnErrorVisitor visits each item and, if an error occurs on
|
||||
// any individual item, returns an aggregate error after all items
|
||||
// are visited.
|
||||
type ContinueOnErrorVisitor struct {
|
||||
Visitor
|
||||
}
|
||||
|
||||
// Visit returns nil if no error occurs during traversal, a regular
|
||||
// error if one occurs, or if multiple errors occur, an aggregate
|
||||
// error. If the provided visitor fails on any individual item it
|
||||
// will not prevent the remaining items from being visited. An error
|
||||
// returned by the visitor directly may still result in some items
|
||||
// not being visited.
|
||||
func (v ContinueOnErrorVisitor) Visit(fn VisitorFunc) error {
|
||||
errs := []error{}
|
||||
err := v.Visitor.Visit(func(info *Info, err error) error {
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
return nil
|
||||
}
|
||||
if err := fn(info, nil); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if len(errs) == 1 {
|
||||
return errs[0]
|
||||
}
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
// FlattenListVisitor flattens any objects that runtime.ExtractList recognizes as a list
|
||||
// - has an "Items" public field that is a slice of runtime.Objects or objects satisfying
|
||||
// that interface - into multiple Infos. An error on any sub item (for instance, if a List
|
||||
// contains an object that does not have a registered client or resource) will terminate
|
||||
// the visit.
|
||||
// TODO: allow errors to be aggregated?
|
||||
type FlattenListVisitor struct {
|
||||
Visitor
|
||||
*Mapper
|
||||
}
|
||||
|
||||
// NewFlattenListVisitor creates a visitor that will expand list style runtime.Objects
|
||||
// into individual items and then visit them individually.
|
||||
func NewFlattenListVisitor(v Visitor, mapper *Mapper) Visitor {
|
||||
return FlattenListVisitor{v, mapper}
|
||||
}
|
||||
|
||||
func (v FlattenListVisitor) Visit(fn VisitorFunc) error {
|
||||
return v.Visitor.Visit(func(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Object == nil {
|
||||
return fn(info, nil)
|
||||
}
|
||||
items, err := meta.ExtractList(info.Object)
|
||||
if err != nil {
|
||||
return fn(info, nil)
|
||||
}
|
||||
if errs := runtime.DecodeList(items, struct {
|
||||
runtime.ObjectTyper
|
||||
runtime.Decoder
|
||||
}{v.Mapper, v.Mapper.Decoder}); len(errs) > 0 {
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
// If we have a GroupVersionKind on the list, prioritize that when asking for info on the objects contained in the list
|
||||
var preferredGVKs []schema.GroupVersionKind
|
||||
if info.Mapping != nil && !info.Mapping.GroupVersionKind.Empty() {
|
||||
preferredGVKs = append(preferredGVKs, info.Mapping.GroupVersionKind)
|
||||
}
|
||||
|
||||
for i := range items {
|
||||
item, err := v.InfoForObject(items[i], preferredGVKs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(info.ResourceVersion) != 0 {
|
||||
item.ResourceVersion = info.ResourceVersion
|
||||
}
|
||||
if err := fn(item, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func ignoreFile(path string, extensions []string) bool {
|
||||
if len(extensions) == 0 {
|
||||
return false
|
||||
}
|
||||
ext := filepath.Ext(path)
|
||||
for _, s := range extensions {
|
||||
if s == ext {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// FileVisitorForSTDIN return a special FileVisitor just for STDIN
|
||||
func FileVisitorForSTDIN(mapper *Mapper, schema validation.Schema) Visitor {
|
||||
return &FileVisitor{
|
||||
Path: constSTDINstr,
|
||||
StreamVisitor: NewStreamVisitor(nil, mapper, constSTDINstr, schema),
|
||||
}
|
||||
}
|
||||
|
||||
// ExpandPathsToFileVisitors will return a slice of FileVisitors that will handle files from the provided path.
|
||||
// After FileVisitors open the files, they will pass an io.Reader to a StreamVisitor to do the reading. (stdin
|
||||
// is also taken care of). Paths argument also accepts a single file, and will return a single visitor
|
||||
func ExpandPathsToFileVisitors(mapper *Mapper, paths string, recursive bool, extensions []string, schema validation.Schema) ([]Visitor, error) {
|
||||
var visitors []Visitor
|
||||
err := filepath.Walk(paths, func(path string, fi os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if fi.IsDir() {
|
||||
if path != paths && !recursive {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// Don't check extension if the filepath was passed explicitly
|
||||
if path != paths && ignoreFile(path, extensions) {
|
||||
return nil
|
||||
}
|
||||
|
||||
visitor := &FileVisitor{
|
||||
Path: path,
|
||||
StreamVisitor: NewStreamVisitor(nil, mapper, path, schema),
|
||||
}
|
||||
|
||||
visitors = append(visitors, visitor)
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return visitors, nil
|
||||
}
|
||||
|
||||
// FileVisitor is wrapping around a StreamVisitor, to handle open/close files
|
||||
type FileVisitor struct {
|
||||
Path string
|
||||
*StreamVisitor
|
||||
}
|
||||
|
||||
// Visit in a FileVisitor is just taking care of opening/closing files
|
||||
func (v *FileVisitor) Visit(fn VisitorFunc) error {
|
||||
var f *os.File
|
||||
if v.Path == constSTDINstr {
|
||||
f = os.Stdin
|
||||
} else {
|
||||
var err error
|
||||
if f, err = os.Open(v.Path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// TODO: Consider adding a flag to force to UTF16, apparently some
|
||||
// Windows tools don't write the BOM
|
||||
utf16bom := unicode.BOMOverride(unicode.UTF8.NewDecoder())
|
||||
v.StreamVisitor.Reader = transform.NewReader(f, utf16bom)
|
||||
|
||||
return v.StreamVisitor.Visit(fn)
|
||||
}
|
||||
|
||||
// StreamVisitor reads objects from an io.Reader and walks them. A stream visitor can only be
|
||||
// visited once.
|
||||
// TODO: depends on objects being in JSON format before being passed to decode - need to implement
|
||||
// a stream decoder method on runtime.Codec to properly handle this.
|
||||
type StreamVisitor struct {
|
||||
io.Reader
|
||||
*Mapper
|
||||
|
||||
Source string
|
||||
Schema validation.Schema
|
||||
}
|
||||
|
||||
// NewStreamVisitor is a helper function that is useful when we want to change the fields of the struct but keep calls the same.
|
||||
func NewStreamVisitor(r io.Reader, mapper *Mapper, source string, schema validation.Schema) *StreamVisitor {
|
||||
return &StreamVisitor{
|
||||
Reader: r,
|
||||
Mapper: mapper,
|
||||
Source: source,
|
||||
Schema: schema,
|
||||
}
|
||||
}
|
||||
|
||||
// Visit implements Visitor over a stream. StreamVisitor is able to distinct multiple resources in one stream.
|
||||
func (v *StreamVisitor) Visit(fn VisitorFunc) error {
|
||||
d := yaml.NewYAMLOrJSONDecoder(v.Reader, 4096)
|
||||
for {
|
||||
ext := runtime.RawExtension{}
|
||||
if err := d.Decode(&ext); err != nil {
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
// TODO: This needs to be able to handle object in other encodings and schemas.
|
||||
ext.Raw = bytes.TrimSpace(ext.Raw)
|
||||
if len(ext.Raw) == 0 || bytes.Equal(ext.Raw, []byte("null")) {
|
||||
continue
|
||||
}
|
||||
if err := ValidateSchema(ext.Raw, v.Schema); err != nil {
|
||||
return fmt.Errorf("error validating %q: %v", v.Source, err)
|
||||
}
|
||||
info, err := v.InfoForData(ext.Raw, v.Source)
|
||||
if err != nil {
|
||||
if fnErr := fn(info, err); fnErr != nil {
|
||||
return fnErr
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err := fn(info, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateObjectNamespace(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Object != nil {
|
||||
return info.Mapping.MetadataAccessor.SetNamespace(info.Object, info.Namespace)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FilterNamespace omits the namespace if the object is not namespace scoped
|
||||
func FilterNamespace(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.Namespaced() {
|
||||
info.Namespace = ""
|
||||
UpdateObjectNamespace(info, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetNamespace ensures that every Info object visited will have a namespace
|
||||
// set. If info.Object is set, it will be mutated as well.
|
||||
func SetNamespace(namespace string) VisitorFunc {
|
||||
return func(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.Namespaced() {
|
||||
return nil
|
||||
}
|
||||
if len(info.Namespace) == 0 {
|
||||
info.Namespace = namespace
|
||||
UpdateObjectNamespace(info, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// RequireNamespace will either set a namespace if none is provided on the
|
||||
// Info object, or if the namespace is set and does not match the provided
|
||||
// value, returns an error. This is intended to guard against administrators
|
||||
// accidentally operating on resources outside their namespace.
|
||||
func RequireNamespace(namespace string) VisitorFunc {
|
||||
return func(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.Namespaced() {
|
||||
return nil
|
||||
}
|
||||
if len(info.Namespace) == 0 {
|
||||
info.Namespace = namespace
|
||||
UpdateObjectNamespace(info, nil)
|
||||
return nil
|
||||
}
|
||||
if info.Namespace != namespace {
|
||||
return fmt.Errorf("the namespace from the provided object %q does not match the namespace %q. You must pass '--namespace=%s' to perform this operation.", info.Namespace, namespace, info.Namespace)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// RetrieveLatest updates the Object on each Info by invoking a standard client
|
||||
// Get.
|
||||
func RetrieveLatest(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if meta.IsListType(info.Object) {
|
||||
return fmt.Errorf("watch is only supported on individual resources and resource collections, but a list of resources is found")
|
||||
}
|
||||
if len(info.Name) == 0 {
|
||||
return nil
|
||||
}
|
||||
if info.Namespaced() && len(info.Namespace) == 0 {
|
||||
return fmt.Errorf("no namespace set on resource %s %q", info.Mapping.Resource, info.Name)
|
||||
}
|
||||
return info.Get()
|
||||
}
|
||||
|
||||
// RetrieveLazy updates the object if it has not been loaded yet.
|
||||
func RetrieveLazy(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Object == nil {
|
||||
return info.Get()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type FilterFunc func(info *Info, err error) (bool, error)
|
||||
|
||||
type FilteredVisitor struct {
|
||||
visitor Visitor
|
||||
filters []FilterFunc
|
||||
}
|
||||
|
||||
func NewFilteredVisitor(v Visitor, fn ...FilterFunc) Visitor {
|
||||
if len(fn) == 0 {
|
||||
return v
|
||||
}
|
||||
return FilteredVisitor{v, fn}
|
||||
}
|
||||
|
||||
func (v FilteredVisitor) Visit(fn VisitorFunc) error {
|
||||
return v.visitor.Visit(func(info *Info, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, filter := range v.filters {
|
||||
ok, err := filter(info, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fn(info, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func FilterBySelector(s labels.Selector) FilterFunc {
|
||||
return func(info *Info, err error) (bool, error) {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
a, err := meta.Accessor(info.Object)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !s.Matches(labels.Set(a.GetLabels())) {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
}
|
102
vendor/k8s.io/kubernetes/pkg/kubectl/resource/visitor_test.go
generated
vendored
Normal file
102
vendor/k8s.io/kubernetes/pkg/kubectl/resource/visitor_test.go
generated
vendored
Normal file
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
Copyright 2016 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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestVisitorHttpGet(t *testing.T) {
|
||||
// Test retries on errors
|
||||
i := 0
|
||||
expectedErr := fmt.Errorf("Failed to get http")
|
||||
actualBytes, actualErr := readHttpWithRetries(func(url string) (int, string, io.ReadCloser, error) {
|
||||
assert.Equal(t, "hello", url)
|
||||
i++
|
||||
if i > 2 {
|
||||
return 0, "", nil, expectedErr
|
||||
}
|
||||
return 0, "", nil, fmt.Errorf("Unexpected error")
|
||||
}, 0, "hello", 3)
|
||||
assert.Equal(t, expectedErr, actualErr)
|
||||
assert.Nil(t, actualBytes)
|
||||
assert.Equal(t, 3, i)
|
||||
|
||||
// Test that 500s are retried.
|
||||
i = 0
|
||||
actualBytes, actualErr = readHttpWithRetries(func(url string) (int, string, io.ReadCloser, error) {
|
||||
assert.Equal(t, "hello", url)
|
||||
i++
|
||||
return 501, "Status", nil, nil
|
||||
}, 0, "hello", 3)
|
||||
assert.Error(t, actualErr)
|
||||
assert.Nil(t, actualBytes)
|
||||
assert.Equal(t, 3, i)
|
||||
|
||||
// Test that 300s are not retried
|
||||
i = 0
|
||||
actualBytes, actualErr = readHttpWithRetries(func(url string) (int, string, io.ReadCloser, error) {
|
||||
assert.Equal(t, "hello", url)
|
||||
i++
|
||||
return 300, "Status", nil, nil
|
||||
}, 0, "hello", 3)
|
||||
assert.Error(t, actualErr)
|
||||
assert.Nil(t, actualBytes)
|
||||
assert.Equal(t, 1, i)
|
||||
|
||||
// Test attempt count is respected
|
||||
i = 0
|
||||
actualBytes, actualErr = readHttpWithRetries(func(url string) (int, string, io.ReadCloser, error) {
|
||||
assert.Equal(t, "hello", url)
|
||||
i++
|
||||
return 501, "Status", nil, nil
|
||||
}, 0, "hello", 1)
|
||||
assert.Error(t, actualErr)
|
||||
assert.Nil(t, actualBytes)
|
||||
assert.Equal(t, 1, i)
|
||||
|
||||
// Test attempts less than 1 results in an error
|
||||
i = 0
|
||||
b := bytes.Buffer{}
|
||||
actualBytes, actualErr = readHttpWithRetries(func(url string) (int, string, io.ReadCloser, error) {
|
||||
return 200, "Status", ioutil.NopCloser(&b), nil
|
||||
}, 0, "hello", 0)
|
||||
assert.Error(t, actualErr)
|
||||
assert.Nil(t, actualBytes)
|
||||
assert.Equal(t, 0, i)
|
||||
|
||||
// Test Success
|
||||
i = 0
|
||||
b = bytes.Buffer{}
|
||||
actualBytes, actualErr = readHttpWithRetries(func(url string) (int, string, io.ReadCloser, error) {
|
||||
assert.Equal(t, "hello", url)
|
||||
i++
|
||||
if i > 1 {
|
||||
return 200, "Status", ioutil.NopCloser(&b), nil
|
||||
}
|
||||
return 501, "Status", nil, nil
|
||||
}, 0, "hello", 3)
|
||||
assert.Nil(t, actualErr)
|
||||
assert.NotNil(t, actualBytes)
|
||||
assert.Equal(t, 2, i)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue