Switch to github.com/golang/dep for vendoring

Signed-off-by: Mrunal Patel <mrunalp@gmail.com>
This commit is contained in:
Mrunal Patel 2017-01-31 16:45:59 -08:00
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
View 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"],
)

View 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)
}

File diff suppressed because it is too large Load diff

24
vendor/k8s.io/kubernetes/pkg/kubectl/resource/doc.go generated vendored Normal file
View 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
View 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()
}

View 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))
}
}
}

View 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
View 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
View 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
}

View 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
}

View 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
}
}

View 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)
}