mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-03 19:58:17 +00:00 
			
		
		
		
	Move Resource functionality to its own package
Create a unified Builder object for working with files, selectors, types, and items that makes it easier to get multi-object functionality. Supports all of the behaviors previously in resource.go, but with additional flexibility to allow multi-type retrieval and access, directories, URLs, nested objects, and lists.
This commit is contained in:
		@@ -105,6 +105,7 @@ func FirstNonEmptyString(args ...string) string {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Return a list of file names of a certain type within a given directory.
 | 
					// Return a list of file names of a certain type within a given directory.
 | 
				
			||||||
 | 
					// TODO: replace with resource.Builder
 | 
				
			||||||
func GetFilesFromDir(directory string, fileType string) []string {
 | 
					func GetFilesFromDir(directory string, fileType string) []string {
 | 
				
			||||||
	files := []string{}
 | 
						files := []string{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -121,6 +122,7 @@ func GetFilesFromDir(directory string, fileType string) []string {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// ReadConfigData reads the bytes from the specified filesytem or network
 | 
					// ReadConfigData reads the bytes from the specified filesytem or network
 | 
				
			||||||
// location or from stdin if location == "-".
 | 
					// location or from stdin if location == "-".
 | 
				
			||||||
 | 
					// TODO: replace with resource.Builder
 | 
				
			||||||
func ReadConfigData(location string) ([]byte, error) {
 | 
					func ReadConfigData(location string) ([]byte, error) {
 | 
				
			||||||
	if len(location) == 0 {
 | 
						if len(location) == 0 {
 | 
				
			||||||
		return nil, fmt.Errorf("location given but empty")
 | 
							return nil, fmt.Errorf("location given but empty")
 | 
				
			||||||
@@ -144,6 +146,7 @@ func ReadConfigData(location string) ([]byte, error) {
 | 
				
			|||||||
	return ReadConfigDataFromLocation(location)
 | 
						return ReadConfigDataFromLocation(location)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO: replace with resource.Builder
 | 
				
			||||||
func ReadConfigDataFromLocation(location string) ([]byte, error) {
 | 
					func ReadConfigDataFromLocation(location string) ([]byte, error) {
 | 
				
			||||||
	// we look for http:// or https:// to determine if valid URL, otherwise do normal file IO
 | 
						// we look for http:// or https:// to determine if valid URL, otherwise do normal file IO
 | 
				
			||||||
	if strings.Index(location, "http://") == 0 || strings.Index(location, "https://") == 0 {
 | 
						if strings.Index(location, "http://") == 0 || strings.Index(location, "https://") == 0 {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										563
									
								
								pkg/kubectl/resource/builder.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										563
									
								
								pkg/kubectl/resource/builder.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,563 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2014 Google Inc. All rights reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package resource
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"net/url"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Builder provides convenience functions for taking arguments and parameters
 | 
				
			||||||
 | 
					// 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						resources []string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						namespace string
 | 
				
			||||||
 | 
						name      string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						defaultNamespace bool
 | 
				
			||||||
 | 
						requireNamespace bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						flatten bool
 | 
				
			||||||
 | 
						latest  bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						singleResourceType bool
 | 
				
			||||||
 | 
						continueOnError    bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewBuilder creates a builder that operates on generic objects.
 | 
				
			||||||
 | 
					func NewBuilder(mapper meta.RESTMapper, typer runtime.ObjectTyper, clientMapper ClientMapper) *Builder {
 | 
				
			||||||
 | 
						return &Builder{
 | 
				
			||||||
 | 
							mapper: &Mapper{typer, mapper, clientMapper},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Filename is parameters passed via a filename argument which may be URLs, the "-" argument indicating
 | 
				
			||||||
 | 
					// STDIN, or paths to files or directories. 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) FilenameParam(paths ...string) *Builder {
 | 
				
			||||||
 | 
						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(url)
 | 
				
			||||||
 | 
							default:
 | 
				
			||||||
 | 
								b.Path(s)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return b
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// URL accepts a number of URLs directly.
 | 
				
			||||||
 | 
					func (b *Builder) URL(urls ...*url.URL) *Builder {
 | 
				
			||||||
 | 
						for _, u := range urls {
 | 
				
			||||||
 | 
							b.paths = append(b.paths, &URLVisitor{
 | 
				
			||||||
 | 
								Mapper: b.mapper,
 | 
				
			||||||
 | 
								URL:    u,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						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 {
 | 
				
			||||||
 | 
						return b.Stream(os.Stdin, "STDIN")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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.continueOnError))
 | 
				
			||||||
 | 
						return b
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Path is a set of filesystem paths that may be files containing one or more
 | 
				
			||||||
 | 
					// resources. 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(paths ...string) *Builder {
 | 
				
			||||||
 | 
						for _, p := range paths {
 | 
				
			||||||
 | 
							i, 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
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							var visitor Visitor
 | 
				
			||||||
 | 
							if i.IsDir() {
 | 
				
			||||||
 | 
								b.dir = true
 | 
				
			||||||
 | 
								visitor = &DirectoryVisitor{
 | 
				
			||||||
 | 
									Mapper:       b.mapper,
 | 
				
			||||||
 | 
									Path:         p,
 | 
				
			||||||
 | 
									Extensions:   []string{".json", ".yaml"},
 | 
				
			||||||
 | 
									Recursive:    false,
 | 
				
			||||||
 | 
									IgnoreErrors: b.continueOnError,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								visitor = &PathVisitor{
 | 
				
			||||||
 | 
									Mapper:       b.mapper,
 | 
				
			||||||
 | 
									Path:         p,
 | 
				
			||||||
 | 
									IgnoreErrors: b.continueOnError,
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							b.paths = append(b.paths, visitor)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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.ParseSelector(s)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							b.errs = append(b.errs, fmt.Errorf("the provided selector %q is not valid: %v", s, err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if selector.Empty() {
 | 
				
			||||||
 | 
							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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// The namespace that these resources should be assumed to under - 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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ResourceTypeOrNameArgs indicates that the builder should accept one or two arguments
 | 
				
			||||||
 | 
					// of the form `(<type1>[,<type2>,...]|<type> <name>)`. When one argument is received, the types
 | 
				
			||||||
 | 
					// provided will be retrieved from the server (and be comma delimited).  When two arguments are
 | 
				
			||||||
 | 
					// received, they must be a single type and name. If more than two arguments are provided an
 | 
				
			||||||
 | 
					// error is set.
 | 
				
			||||||
 | 
					func (b *Builder) ResourceTypeOrNameArgs(args ...string) *Builder {
 | 
				
			||||||
 | 
						switch len(args) {
 | 
				
			||||||
 | 
						case 2:
 | 
				
			||||||
 | 
							b.name = args[1]
 | 
				
			||||||
 | 
							b.ResourceTypes(SplitResourceArgument(args[0])...)
 | 
				
			||||||
 | 
						case 1:
 | 
				
			||||||
 | 
							b.ResourceTypes(SplitResourceArgument(args[0])...)
 | 
				
			||||||
 | 
							if b.selector == nil {
 | 
				
			||||||
 | 
								b.selector = labels.Everything()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						case 0:
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							b.errs = append(b.errs, fmt.Errorf("when passing arguments, must be resource or resource and name"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return b
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ResourceTypeAndNameArgs expects two arguments, a resource type, and a resource name. The resource
 | 
				
			||||||
 | 
					// matching that type and and name will be retrieved from the server.
 | 
				
			||||||
 | 
					func (b *Builder) ResourceTypeAndNameArgs(args ...string) *Builder {
 | 
				
			||||||
 | 
						switch len(args) {
 | 
				
			||||||
 | 
						case 2:
 | 
				
			||||||
 | 
							b.name = args[1]
 | 
				
			||||||
 | 
							b.ResourceTypes(SplitResourceArgument(args[0])...)
 | 
				
			||||||
 | 
						case 0:
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							b.errs = append(b.errs, fmt.Errorf("when passing arguments, must be resource and name"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return b
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (b *Builder) SingleResourceType() *Builder {
 | 
				
			||||||
 | 
						b.singleResourceType = true
 | 
				
			||||||
 | 
						return b
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 {
 | 
				
			||||||
 | 
							version, kind, err := b.mapper.VersionAndKindForResource(r)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							mapping, err := b.mapper.RESTMapping(kind, version)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							mappings = append(mappings, mapping)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return mappings, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (b *Builder) visitorResult() *Result {
 | 
				
			||||||
 | 
						if len(b.errs) > 0 {
 | 
				
			||||||
 | 
							return &Result{err: errors.NewAggregate(b.errs)}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// visit selectors
 | 
				
			||||||
 | 
						if b.selector != nil {
 | 
				
			||||||
 | 
							if len(b.name) != 0 {
 | 
				
			||||||
 | 
								return &Result{err: fmt.Errorf("name cannot be provided when a selector is specified")}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if len(b.resources) == 0 {
 | 
				
			||||||
 | 
								return &Result{err: fmt.Errorf("at least one resource must be specified to use a selector")}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// empty selector has different error message for paths being provided
 | 
				
			||||||
 | 
							if len(b.paths) != 0 {
 | 
				
			||||||
 | 
								if b.selector.Empty() {
 | 
				
			||||||
 | 
									return &Result{err: fmt.Errorf("when paths, URLs, or stdin is provided as input, you may not specify a resource by arguments as well")}
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									return &Result{err: fmt.Errorf("a selector may not be specified when path, URL, or stdin is provided as input")}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							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}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								visitors = append(visitors, NewSelector(client, mapping, b.namespace, b.selector))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if b.continueOnError {
 | 
				
			||||||
 | 
								return &Result{visitor: EagerVisitorList(visitors), sources: visitors}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return &Result{visitor: VisitorList(visitors), sources: visitors}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// visit single item specified by name
 | 
				
			||||||
 | 
						if len(b.name) != 0 {
 | 
				
			||||||
 | 
							if len(b.paths) != 0 {
 | 
				
			||||||
 | 
								return &Result{singular: true, 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{singular: true, err: fmt.Errorf("you must provide a resource and a resource name together")}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if len(b.resources) > 1 {
 | 
				
			||||||
 | 
								return &Result{singular: true, err: fmt.Errorf("you must specify only one resource")}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if len(b.namespace) == 0 {
 | 
				
			||||||
 | 
								return &Result{singular: true, err: fmt.Errorf("namespace may not be empty when retrieving a resource by name")}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							mappings, err := b.resourceMappings()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return &Result{singular: true, err: err}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							client, err := b.mapper.ClientForMapping(mappings[0])
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return &Result{singular: true, err: err}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							info := NewInfo(client, mappings[0], b.namespace, b.name)
 | 
				
			||||||
 | 
							if err := info.Get(); err != nil {
 | 
				
			||||||
 | 
								return &Result{singular: true, err: err}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return &Result{singular: true, visitor: info, sources: []Visitor{info}}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// visit items specified by paths
 | 
				
			||||||
 | 
						if len(b.paths) != 0 {
 | 
				
			||||||
 | 
							singular := !b.dir && !b.stream && len(b.paths) == 1
 | 
				
			||||||
 | 
							if len(b.resources) != 0 {
 | 
				
			||||||
 | 
								return &Result{singular: singular, err: fmt.Errorf("when paths, URLs, or stdin is provided as input, you may not specify resource arguments as well")}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							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)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								visitors = NewDecoratedVisitor(visitors, RetrieveLatest)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return &Result{singular: singular, visitor: visitors, sources: b.paths}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &Result{err: fmt.Errorf("you must provide one or more resources by argument or filename")}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						r.visitor = NewDecoratedVisitor(r.visitor, helpers...)
 | 
				
			||||||
 | 
						return r
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Result contains helper methods for dealing with the outcome of a Builder.
 | 
				
			||||||
 | 
					type Result struct {
 | 
				
			||||||
 | 
						err     error
 | 
				
			||||||
 | 
						visitor Visitor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						sources  []Visitor
 | 
				
			||||||
 | 
						singular bool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// populated by a call to Infos
 | 
				
			||||||
 | 
						info []*Info
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Err returns one or more errors (via a util.ErrorList) that occurred prior
 | 
				
			||||||
 | 
					// to visiting the elements in the visitor. To see all errors including those
 | 
				
			||||||
 | 
					// that occur during visitation, invoke Infos().
 | 
				
			||||||
 | 
					func (r *Result) Err() error {
 | 
				
			||||||
 | 
						return r.err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Visit implements the Visitor interface on the items described in the Builder.
 | 
				
			||||||
 | 
					// Note that some visitor sources are not traversable more than once, or may
 | 
				
			||||||
 | 
					// return different results.  If you wish to operate on the same set of resources
 | 
				
			||||||
 | 
					// multiple times, use the Infos() method.
 | 
				
			||||||
 | 
					func (r *Result) Visit(fn VisitorFunc) error {
 | 
				
			||||||
 | 
						if r.err != nil {
 | 
				
			||||||
 | 
							return r.err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return r.visitor.Visit(fn)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IntoSingular sets the provided boolean pointer to true if the Builder input
 | 
				
			||||||
 | 
					// reflected a single item, or multiple.
 | 
				
			||||||
 | 
					func (r *Result) IntoSingular(b *bool) *Result {
 | 
				
			||||||
 | 
						*b = r.singular
 | 
				
			||||||
 | 
						return r
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Infos returns an array of all of the resource infos retrieved via traversal.
 | 
				
			||||||
 | 
					// Will attempt to traverse the entire set of visitors only once, and will return
 | 
				
			||||||
 | 
					// a cached list on subsequent calls.
 | 
				
			||||||
 | 
					func (r *Result) Infos() ([]*Info, error) {
 | 
				
			||||||
 | 
						if r.err != nil {
 | 
				
			||||||
 | 
							return nil, r.err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if r.info != nil {
 | 
				
			||||||
 | 
							return r.info, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						infos := []*Info{}
 | 
				
			||||||
 | 
						err := r.visitor.Visit(func(info *Info) error {
 | 
				
			||||||
 | 
							infos = append(infos, info)
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						r.info, r.err = infos, err
 | 
				
			||||||
 | 
						return infos, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Object returns a single object representing the output of a single visit to all
 | 
				
			||||||
 | 
					// found resources.  If the Builder was a singular context (expected to return a
 | 
				
			||||||
 | 
					// single resource by user input) and only a single resource was found, the resource
 | 
				
			||||||
 | 
					// will be returned as is.  Otherwise, the returned resources will be part of an
 | 
				
			||||||
 | 
					// api.List. The ResourceVersion of the api.List will be set only if it is identical
 | 
				
			||||||
 | 
					// across all infos returned.
 | 
				
			||||||
 | 
					func (r *Result) Object() (runtime.Object, error) {
 | 
				
			||||||
 | 
						infos, err := r.Infos()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						versions := util.StringSet{}
 | 
				
			||||||
 | 
						objects := []runtime.Object{}
 | 
				
			||||||
 | 
						for _, info := range infos {
 | 
				
			||||||
 | 
							if info.Object != nil {
 | 
				
			||||||
 | 
								objects = append(objects, info.Object)
 | 
				
			||||||
 | 
								versions.Insert(info.ResourceVersion)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(objects) == 1 {
 | 
				
			||||||
 | 
							if r.singular {
 | 
				
			||||||
 | 
								return objects[0], nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// if the item is a list already, don't create another list
 | 
				
			||||||
 | 
							if _, err := runtime.GetItemsPtr(objects[0]); err == nil {
 | 
				
			||||||
 | 
								return objects[0], nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						version := ""
 | 
				
			||||||
 | 
						if len(versions) == 1 {
 | 
				
			||||||
 | 
							version = versions.List()[0]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &api.List{
 | 
				
			||||||
 | 
							ListMeta: api.ListMeta{
 | 
				
			||||||
 | 
								ResourceVersion: version,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Items: objects,
 | 
				
			||||||
 | 
						}, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ResourceMapping returns a single meta.RESTMapping representing the
 | 
				
			||||||
 | 
					// resources located by the builder, or an error if more than one
 | 
				
			||||||
 | 
					// mapping was found.
 | 
				
			||||||
 | 
					func (r *Result) ResourceMapping() (*meta.RESTMapping, error) {
 | 
				
			||||||
 | 
						if r.err != nil {
 | 
				
			||||||
 | 
							return nil, r.err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						mappings := map[string]*meta.RESTMapping{}
 | 
				
			||||||
 | 
						for i := range r.sources {
 | 
				
			||||||
 | 
							m, ok := r.sources[i].(ResourceMapping)
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("a resource mapping could not be loaded from %v", reflect.TypeOf(r.sources[i]))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							mapping := m.ResourceMapping()
 | 
				
			||||||
 | 
							mappings[mapping.Resource] = mapping
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(mappings) != 1 {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("expected only a single resource type")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, mapping := range mappings {
 | 
				
			||||||
 | 
							return mapping, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Watch retrieves changes that occur on the server to the specified resource.
 | 
				
			||||||
 | 
					// It currently supports watching a single source - if the resource source
 | 
				
			||||||
 | 
					// (selectors or pure types) can be watched, they will be, otherwise the list
 | 
				
			||||||
 | 
					// will be visited (equivalent to the Infos() call) and if there is a single
 | 
				
			||||||
 | 
					// resource present, it will be watched, otherwise an error will be returned.
 | 
				
			||||||
 | 
					func (r *Result) Watch(resourceVersion string) (watch.Interface, error) {
 | 
				
			||||||
 | 
						if r.err != nil {
 | 
				
			||||||
 | 
							return nil, r.err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(r.sources) != 1 {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("you may only watch a single resource or type of resource at a time")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						w, ok := r.sources[0].(Watchable)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							info, err := r.Infos()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if len(info) != 1 {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("watch is only supported on a single resource - %d resources were found", len(info))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return info[0].Watch(resourceVersion)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return w.Watch(resourceVersion)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func SplitResourceArgument(arg string) []string {
 | 
				
			||||||
 | 
						set := util.NewStringSet()
 | 
				
			||||||
 | 
						set.Insert(strings.Split(arg, ",")...)
 | 
				
			||||||
 | 
						return set.List()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										619
									
								
								pkg/kubectl/resource/builder_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										619
									
								
								pkg/kubectl/resource/builder_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,619 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2014 Google Inc. All rights reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package resource
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"net/http/httptest"
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
 | 
				
			||||||
 | 
						watchjson "github.com/GoogleCloudPlatform/kubernetes/pkg/watch/json"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func stringBody(body string) io.ReadCloser {
 | 
				
			||||||
 | 
						return ioutil.NopCloser(bytes.NewReader([]byte(body)))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func watchBody(events ...watch.Event) string {
 | 
				
			||||||
 | 
						buf := &bytes.Buffer{}
 | 
				
			||||||
 | 
						enc := watchjson.NewEncoder(buf, latest.Codec)
 | 
				
			||||||
 | 
						for _, e := range events {
 | 
				
			||||||
 | 
							enc.Encode(&e)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return buf.String()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fakeClient() ClientMapper {
 | 
				
			||||||
 | 
						return ClientMapperFunc(func(*meta.RESTMapping) (RESTClient, error) {
 | 
				
			||||||
 | 
							return &client.FakeRESTClient{}, nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func fakeClientWith(t *testing.T, data map[string]string) ClientMapper {
 | 
				
			||||||
 | 
						return ClientMapperFunc(func(*meta.RESTMapping) (RESTClient, error) {
 | 
				
			||||||
 | 
							return &client.FakeRESTClient{
 | 
				
			||||||
 | 
								Codec: latest.Codec,
 | 
				
			||||||
 | 
								Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
 | 
				
			||||||
 | 
									p := req.URL.Path
 | 
				
			||||||
 | 
									q := req.URL.RawQuery
 | 
				
			||||||
 | 
									if len(q) != 0 {
 | 
				
			||||||
 | 
										p = p + "?" + q
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									body, ok := data[p]
 | 
				
			||||||
 | 
									if !ok {
 | 
				
			||||||
 | 
										t.Fatalf("unexpected request: %s (%s)\n%#v", p, req.URL, req)
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return &http.Response{
 | 
				
			||||||
 | 
										StatusCode: http.StatusOK,
 | 
				
			||||||
 | 
										Body:       stringBody(body),
 | 
				
			||||||
 | 
									}, nil
 | 
				
			||||||
 | 
								}),
 | 
				
			||||||
 | 
							}, nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func testData() (*api.PodList, *api.ServiceList) {
 | 
				
			||||||
 | 
						pods := &api.PodList{
 | 
				
			||||||
 | 
							ListMeta: api.ListMeta{
 | 
				
			||||||
 | 
								ResourceVersion: "15",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Items: []api.Pod{
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "10"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									ObjectMeta: api.ObjectMeta{Name: "bar", Namespace: "test", ResourceVersion: "11"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						svc := &api.ServiceList{
 | 
				
			||||||
 | 
							ListMeta: api.ListMeta{
 | 
				
			||||||
 | 
								ResourceVersion: "16",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							Items: []api.Service{
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									ObjectMeta: api.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "12"},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return pods, svc
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func streamTestData() (io.Reader, *api.PodList, *api.ServiceList) {
 | 
				
			||||||
 | 
						pods, svc := testData()
 | 
				
			||||||
 | 
						r, w := io.Pipe()
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							defer w.Close()
 | 
				
			||||||
 | 
							w.Write([]byte(runtime.EncodeOrDie(latest.Codec, pods)))
 | 
				
			||||||
 | 
							w.Write([]byte(runtime.EncodeOrDie(latest.Codec, svc)))
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
						return r, pods, svc
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type testVisitor struct {
 | 
				
			||||||
 | 
						InjectErr error
 | 
				
			||||||
 | 
						Infos     []*Info
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (v *testVisitor) Handle(info *Info) error {
 | 
				
			||||||
 | 
						v.Infos = append(v.Infos, info)
 | 
				
			||||||
 | 
						return v.InjectErr
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (v *testVisitor) Objects() []runtime.Object {
 | 
				
			||||||
 | 
						objects := []runtime.Object{}
 | 
				
			||||||
 | 
						for i := range v.Infos {
 | 
				
			||||||
 | 
							objects = append(objects, v.Infos[i].Object)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return objects
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestPathBuilder(t *testing.T) {
 | 
				
			||||||
 | 
						b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()).
 | 
				
			||||||
 | 
							FilenameParam("../../../examples/guestbook/redis-master.json")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						test := &testVisitor{}
 | 
				
			||||||
 | 
						singular := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := b.Do().IntoSingular(&singular).Visit(test.Handle)
 | 
				
			||||||
 | 
						if err != nil || !singular || len(test.Infos) != 1 {
 | 
				
			||||||
 | 
							t.Fatalf("unexpected response: %v %f %#v", err, singular, test.Infos)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						info := test.Infos[0]
 | 
				
			||||||
 | 
						if info.Name != "redis-master" || info.Namespace != "" || info.Object == nil {
 | 
				
			||||||
 | 
							t.Errorf("unexpected info: %#v", info)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestPathBuilderWithMultiple(t *testing.T) {
 | 
				
			||||||
 | 
						b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()).
 | 
				
			||||||
 | 
							FilenameParam("../../../examples/guestbook/redis-master.json").
 | 
				
			||||||
 | 
							FilenameParam("../../../examples/guestbook/redis-master.json").
 | 
				
			||||||
 | 
							NamespaceParam("test").DefaultNamespace()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						test := &testVisitor{}
 | 
				
			||||||
 | 
						singular := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := b.Do().IntoSingular(&singular).Visit(test.Handle)
 | 
				
			||||||
 | 
						if err != nil || singular || len(test.Infos) != 2 {
 | 
				
			||||||
 | 
							t.Fatalf("unexpected response: %v %f %#v", err, singular, test.Infos)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						info := test.Infos[1]
 | 
				
			||||||
 | 
						if info.Name != "redis-master" || info.Namespace != "test" || info.Object == nil {
 | 
				
			||||||
 | 
							t.Errorf("unexpected info: %#v", info)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestDirectoryBuilder(t *testing.T) {
 | 
				
			||||||
 | 
						b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()).
 | 
				
			||||||
 | 
							FilenameParam("../../../examples/guestbook").
 | 
				
			||||||
 | 
							NamespaceParam("test").DefaultNamespace()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						test := &testVisitor{}
 | 
				
			||||||
 | 
						singular := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := b.Do().IntoSingular(&singular).Visit(test.Handle)
 | 
				
			||||||
 | 
						if err != nil || singular || len(test.Infos) < 4 {
 | 
				
			||||||
 | 
							t.Fatalf("unexpected response: %v %f %#v", err, singular, test.Infos)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						found := false
 | 
				
			||||||
 | 
						for _, info := range test.Infos {
 | 
				
			||||||
 | 
							if info.Name == "redis-master" && info.Namespace == "test" && info.Object != nil {
 | 
				
			||||||
 | 
								found = true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !found {
 | 
				
			||||||
 | 
							t.Errorf("unexpected responses: %#v", test.Infos)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestURLBuilder(t *testing.T) {
 | 
				
			||||||
 | 
						s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusOK)
 | 
				
			||||||
 | 
							w.Write([]byte(runtime.EncodeOrDie(latest.Codec, &api.Pod{ObjectMeta: api.ObjectMeta{Namespace: "foo", Name: "test"}})))
 | 
				
			||||||
 | 
						}))
 | 
				
			||||||
 | 
						defer s.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()).
 | 
				
			||||||
 | 
							FilenameParam(s.URL).
 | 
				
			||||||
 | 
							NamespaceParam("test")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						test := &testVisitor{}
 | 
				
			||||||
 | 
						singular := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := b.Do().IntoSingular(&singular).Visit(test.Handle)
 | 
				
			||||||
 | 
						if err != nil || !singular || len(test.Infos) != 1 {
 | 
				
			||||||
 | 
							t.Fatalf("unexpected response: %v %f %#v", err, singular, test.Infos)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						info := test.Infos[0]
 | 
				
			||||||
 | 
						if info.Name != "test" || info.Namespace != "foo" || info.Object == nil {
 | 
				
			||||||
 | 
							t.Errorf("unexpected info: %#v", info)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestURLBuilderRequireNamespace(t *testing.T) {
 | 
				
			||||||
 | 
						s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 | 
				
			||||||
 | 
							w.WriteHeader(http.StatusOK)
 | 
				
			||||||
 | 
							w.Write([]byte(runtime.EncodeOrDie(latest.Codec, &api.Pod{ObjectMeta: api.ObjectMeta{Namespace: "foo", Name: "test"}})))
 | 
				
			||||||
 | 
						}))
 | 
				
			||||||
 | 
						defer s.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()).
 | 
				
			||||||
 | 
							FilenameParam(s.URL).
 | 
				
			||||||
 | 
							NamespaceParam("test").RequireNamespace()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						test := &testVisitor{}
 | 
				
			||||||
 | 
						singular := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := b.Do().IntoSingular(&singular).Visit(test.Handle)
 | 
				
			||||||
 | 
						if err == nil || !singular || len(test.Infos) != 0 {
 | 
				
			||||||
 | 
							t.Fatalf("unexpected response: %v %f %#v", err, singular, test.Infos)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestResourceByName(t *testing.T) {
 | 
				
			||||||
 | 
						pods, _ := testData()
 | 
				
			||||||
 | 
						b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
 | 
				
			||||||
 | 
							"/ns/test/pods/foo": runtime.EncodeOrDie(latest.Codec, &pods.Items[0]),
 | 
				
			||||||
 | 
						})).
 | 
				
			||||||
 | 
							NamespaceParam("test")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						test := &testVisitor{}
 | 
				
			||||||
 | 
						singular := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if b.Do().Err() == nil {
 | 
				
			||||||
 | 
							t.Errorf("unexpected non-error")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						b.ResourceTypeOrNameArgs("pods", "foo")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := b.Do().IntoSingular(&singular).Visit(test.Handle)
 | 
				
			||||||
 | 
						if err != nil || !singular || len(test.Infos) != 1 {
 | 
				
			||||||
 | 
							t.Fatalf("unexpected response: %v %f %#v", err, singular, test.Infos)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !reflect.DeepEqual(&pods.Items[0], test.Objects()[0]) {
 | 
				
			||||||
 | 
							t.Errorf("unexpected object: %#v", test.Objects())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mapping, err := b.Do().ResourceMapping()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("unexpected error: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if mapping.Resource != "pods" {
 | 
				
			||||||
 | 
							t.Errorf("unexpected resource mapping: %#v", mapping)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestResourceByNameAndEmptySelector(t *testing.T) {
 | 
				
			||||||
 | 
						pods, _ := testData()
 | 
				
			||||||
 | 
						b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
 | 
				
			||||||
 | 
							"/ns/test/pods/foo": runtime.EncodeOrDie(latest.Codec, &pods.Items[0]),
 | 
				
			||||||
 | 
						})).
 | 
				
			||||||
 | 
							NamespaceParam("test").
 | 
				
			||||||
 | 
							SelectorParam("").
 | 
				
			||||||
 | 
							ResourceTypeOrNameArgs("pods", "foo")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						singular := false
 | 
				
			||||||
 | 
						infos, err := b.Do().IntoSingular(&singular).Infos()
 | 
				
			||||||
 | 
						if err != nil || !singular || len(infos) != 1 {
 | 
				
			||||||
 | 
							t.Fatalf("unexpected response: %v %f %#v", err, singular, infos)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !reflect.DeepEqual(&pods.Items[0], infos[0].Object) {
 | 
				
			||||||
 | 
							t.Errorf("unexpected object: %#v", infos[0])
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mapping, err := b.Do().ResourceMapping()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("unexpected error: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if mapping.Resource != "pods" {
 | 
				
			||||||
 | 
							t.Errorf("unexpected resource mapping: %#v", mapping)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestSelector(t *testing.T) {
 | 
				
			||||||
 | 
						pods, svc := testData()
 | 
				
			||||||
 | 
						b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
 | 
				
			||||||
 | 
							"/ns/test/pods?labels=a%3Db":     runtime.EncodeOrDie(latest.Codec, pods),
 | 
				
			||||||
 | 
							"/ns/test/services?labels=a%3Db": runtime.EncodeOrDie(latest.Codec, svc),
 | 
				
			||||||
 | 
						})).
 | 
				
			||||||
 | 
							SelectorParam("a=b").
 | 
				
			||||||
 | 
							NamespaceParam("test").
 | 
				
			||||||
 | 
							Flatten()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						test := &testVisitor{}
 | 
				
			||||||
 | 
						singular := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if b.Do().Err() == nil {
 | 
				
			||||||
 | 
							t.Errorf("unexpected non-error")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						b.ResourceTypeOrNameArgs("pods,service")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := b.Do().IntoSingular(&singular).Visit(test.Handle)
 | 
				
			||||||
 | 
						if err != nil || singular || len(test.Infos) != 3 {
 | 
				
			||||||
 | 
							t.Fatalf("unexpected response: %v %f %#v", err, singular, test.Infos)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !reflect.DeepEqual([]runtime.Object{&pods.Items[0], &pods.Items[1], &svc.Items[0]}, test.Objects()) {
 | 
				
			||||||
 | 
							t.Errorf("unexpected visited objects: %#v", test.Objects())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if _, err := b.Do().ResourceMapping(); err == nil {
 | 
				
			||||||
 | 
							t.Errorf("unexpected non-error")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestSelectorRequiresKnownTypes(t *testing.T) {
 | 
				
			||||||
 | 
						b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()).
 | 
				
			||||||
 | 
							SelectorParam("a=b").
 | 
				
			||||||
 | 
							NamespaceParam("test").
 | 
				
			||||||
 | 
							ResourceTypes("unknown")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if b.Do().Err() == nil {
 | 
				
			||||||
 | 
							t.Errorf("unexpected non-error")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestSingleResourceType(t *testing.T) {
 | 
				
			||||||
 | 
						b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()).
 | 
				
			||||||
 | 
							SelectorParam("a=b").
 | 
				
			||||||
 | 
							SingleResourceType().
 | 
				
			||||||
 | 
							ResourceTypeOrNameArgs("pods,services")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if b.Do().Err() == nil {
 | 
				
			||||||
 | 
							t.Errorf("unexpected non-error")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestStream(t *testing.T) {
 | 
				
			||||||
 | 
						r, pods, rc := streamTestData()
 | 
				
			||||||
 | 
						b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()).
 | 
				
			||||||
 | 
							NamespaceParam("test").Stream(r, "STDIN").Flatten()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						test := &testVisitor{}
 | 
				
			||||||
 | 
						singular := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := b.Do().IntoSingular(&singular).Visit(test.Handle)
 | 
				
			||||||
 | 
						if err != nil || singular || len(test.Infos) != 3 {
 | 
				
			||||||
 | 
							t.Fatalf("unexpected response: %v %f %#v", err, singular, test.Infos)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !reflect.DeepEqual([]runtime.Object{&pods.Items[0], &pods.Items[1], &rc.Items[0]}, test.Objects()) {
 | 
				
			||||||
 | 
							t.Errorf("unexpected visited objects: %#v", test.Objects())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestMultipleObject(t *testing.T) {
 | 
				
			||||||
 | 
						r, pods, svc := streamTestData()
 | 
				
			||||||
 | 
						obj, err := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()).
 | 
				
			||||||
 | 
							NamespaceParam("test").Stream(r, "STDIN").Flatten().
 | 
				
			||||||
 | 
							Do().Object()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("unexpected error: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						expected := &api.List{
 | 
				
			||||||
 | 
							Items: []runtime.Object{
 | 
				
			||||||
 | 
								&pods.Items[0],
 | 
				
			||||||
 | 
								&pods.Items[1],
 | 
				
			||||||
 | 
								&svc.Items[0],
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !reflect.DeepEqual(expected, obj) {
 | 
				
			||||||
 | 
							t.Errorf("unexpected visited objects: %#v", obj)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestSingularObject(t *testing.T) {
 | 
				
			||||||
 | 
						obj, err := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()).
 | 
				
			||||||
 | 
							NamespaceParam("test").DefaultNamespace().
 | 
				
			||||||
 | 
							FilenameParam("../../../examples/guestbook/redis-master.json").
 | 
				
			||||||
 | 
							Flatten().
 | 
				
			||||||
 | 
							Do().Object()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("unexpected error: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pod, ok := obj.(*api.Pod)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							t.Fatalf("unexpected object: %#v", obj)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if pod.Name != "redis-master" || pod.Namespace != "test" {
 | 
				
			||||||
 | 
							t.Errorf("unexpected pod: %#v", pod)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestListObject(t *testing.T) {
 | 
				
			||||||
 | 
						pods, _ := testData()
 | 
				
			||||||
 | 
						b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
 | 
				
			||||||
 | 
							"/ns/test/pods?labels=a%3Db": runtime.EncodeOrDie(latest.Codec, pods),
 | 
				
			||||||
 | 
						})).
 | 
				
			||||||
 | 
							SelectorParam("a=b").
 | 
				
			||||||
 | 
							NamespaceParam("test").
 | 
				
			||||||
 | 
							ResourceTypeOrNameArgs("pods").
 | 
				
			||||||
 | 
							Flatten()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						obj, err := b.Do().Object()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("unexpected error: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						list, ok := obj.(*api.List)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							t.Fatalf("unexpected object: %#v", obj)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if list.ResourceVersion != pods.ResourceVersion || len(list.Items) != 2 {
 | 
				
			||||||
 | 
							t.Errorf("unexpected list: %#v", list)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mapping, err := b.Do().ResourceMapping()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("unexpected error: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if mapping.Resource != "pods" {
 | 
				
			||||||
 | 
							t.Errorf("unexpected resource mapping: %#v", mapping)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestListObjectWithDifferentVersions(t *testing.T) {
 | 
				
			||||||
 | 
						pods, svc := testData()
 | 
				
			||||||
 | 
						obj, err := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
 | 
				
			||||||
 | 
							"/ns/test/pods?labels=a%3Db":     runtime.EncodeOrDie(latest.Codec, pods),
 | 
				
			||||||
 | 
							"/ns/test/services?labels=a%3Db": runtime.EncodeOrDie(latest.Codec, svc),
 | 
				
			||||||
 | 
						})).
 | 
				
			||||||
 | 
							SelectorParam("a=b").
 | 
				
			||||||
 | 
							NamespaceParam("test").
 | 
				
			||||||
 | 
							ResourceTypeOrNameArgs("pods,services").
 | 
				
			||||||
 | 
							Flatten().
 | 
				
			||||||
 | 
							Do().Object()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("unexpected error: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						list, ok := obj.(*api.List)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							t.Fatalf("unexpected object: %#v", obj)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// resource version differs between type lists, so it's not possible to get a single version.
 | 
				
			||||||
 | 
						if list.ResourceVersion != "" || len(list.Items) != 3 {
 | 
				
			||||||
 | 
							t.Errorf("unexpected list: %#v", list)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestWatch(t *testing.T) {
 | 
				
			||||||
 | 
						pods, _ := testData()
 | 
				
			||||||
 | 
						w, err := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
 | 
				
			||||||
 | 
							"/watch/ns/test/pods/redis-master?resourceVersion=10": watchBody(watch.Event{
 | 
				
			||||||
 | 
								Type:   watch.Added,
 | 
				
			||||||
 | 
								Object: &pods.Items[0],
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
						})).
 | 
				
			||||||
 | 
							NamespaceParam("test").DefaultNamespace().
 | 
				
			||||||
 | 
							FilenameParam("../../../examples/guestbook/redis-master.json").Flatten().
 | 
				
			||||||
 | 
							Do().Watch("10")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatalf("unexpected error: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						defer w.Stop()
 | 
				
			||||||
 | 
						ch := w.ResultChan()
 | 
				
			||||||
 | 
						select {
 | 
				
			||||||
 | 
						case obj := <-ch:
 | 
				
			||||||
 | 
							if obj.Type != watch.Added {
 | 
				
			||||||
 | 
								t.Fatalf("unexpected watch event", obj)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							pod, ok := obj.Object.(*api.Pod)
 | 
				
			||||||
 | 
							if !ok {
 | 
				
			||||||
 | 
								t.Fatalf("unexpected object: %#v", obj)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if pod.Name != "foo" || pod.ResourceVersion != "10" {
 | 
				
			||||||
 | 
								t.Errorf("unexpected pod: %#v", pod)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestWatchMultipleError(t *testing.T) {
 | 
				
			||||||
 | 
						_, err := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()).
 | 
				
			||||||
 | 
							NamespaceParam("test").DefaultNamespace().
 | 
				
			||||||
 | 
							FilenameParam("../../../examples/guestbook/redis-master.json").Flatten().
 | 
				
			||||||
 | 
							FilenameParam("../../../examples/guestbook/redis-master.json").Flatten().
 | 
				
			||||||
 | 
							Do().Watch("")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err == nil {
 | 
				
			||||||
 | 
							t.Fatalf("unexpected non-error")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestLatest(t *testing.T) {
 | 
				
			||||||
 | 
						r, _, _ := streamTestData()
 | 
				
			||||||
 | 
						newPod := &api.Pod{
 | 
				
			||||||
 | 
							ObjectMeta: api.ObjectMeta{Name: "foo", Namespace: "test", ResourceVersion: "13"},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						newPod2 := &api.Pod{
 | 
				
			||||||
 | 
							ObjectMeta: api.ObjectMeta{Name: "bar", Namespace: "test", ResourceVersion: "14"},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						newSvc := &api.Service{
 | 
				
			||||||
 | 
							ObjectMeta: api.ObjectMeta{Name: "baz", Namespace: "test", ResourceVersion: "15"},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClientWith(t, map[string]string{
 | 
				
			||||||
 | 
							"/ns/test/pods/foo":     runtime.EncodeOrDie(latest.Codec, newPod),
 | 
				
			||||||
 | 
							"/ns/test/pods/bar":     runtime.EncodeOrDie(latest.Codec, newPod2),
 | 
				
			||||||
 | 
							"/ns/test/services/baz": runtime.EncodeOrDie(latest.Codec, newSvc),
 | 
				
			||||||
 | 
						})).
 | 
				
			||||||
 | 
							NamespaceParam("other").Stream(r, "STDIN").Flatten().Latest()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						test := &testVisitor{}
 | 
				
			||||||
 | 
						singular := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := b.Do().IntoSingular(&singular).Visit(test.Handle)
 | 
				
			||||||
 | 
						if err != nil || singular || len(test.Infos) != 3 {
 | 
				
			||||||
 | 
							t.Fatalf("unexpected response: %v %f %#v", err, singular, test.Infos)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !reflect.DeepEqual([]runtime.Object{newPod, newPod2, newSvc}, test.Objects()) {
 | 
				
			||||||
 | 
							t.Errorf("unexpected visited objects: %#v", test.Objects())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestIgnoreStreamErrors(t *testing.T) {
 | 
				
			||||||
 | 
						pods, svc := testData()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						r, w := io.Pipe()
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							defer w.Close()
 | 
				
			||||||
 | 
							w.Write([]byte(`{}`))
 | 
				
			||||||
 | 
							w.Write([]byte(runtime.EncodeOrDie(latest.Codec, &pods.Items[0])))
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						r2, w2 := io.Pipe()
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							defer w2.Close()
 | 
				
			||||||
 | 
							w2.Write([]byte(`{}`))
 | 
				
			||||||
 | 
							w2.Write([]byte(runtime.EncodeOrDie(latest.Codec, &svc.Items[0])))
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()).
 | 
				
			||||||
 | 
							ContinueOnError(). // TODO: order seems bad, but allows clients to determine what they want...
 | 
				
			||||||
 | 
							Stream(r, "1").Stream(r2, "2")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						test := &testVisitor{}
 | 
				
			||||||
 | 
						singular := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := b.Do().IntoSingular(&singular).Visit(test.Handle)
 | 
				
			||||||
 | 
						if err != nil || singular || len(test.Infos) != 2 {
 | 
				
			||||||
 | 
							t.Fatalf("unexpected response: %v %f %#v", err, singular, test.Infos)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !reflect.DeepEqual([]runtime.Object{&pods.Items[0], &svc.Items[0]}, test.Objects()) {
 | 
				
			||||||
 | 
							t.Errorf("unexpected visited objects: %#v", test.Objects())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestReceiveMultipleErrors(t *testing.T) {
 | 
				
			||||||
 | 
						pods, svc := testData()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						r, w := io.Pipe()
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							defer w.Close()
 | 
				
			||||||
 | 
							w.Write([]byte(`{}`))
 | 
				
			||||||
 | 
							w.Write([]byte(runtime.EncodeOrDie(latest.Codec, &pods.Items[0])))
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						r2, w2 := io.Pipe()
 | 
				
			||||||
 | 
						go func() {
 | 
				
			||||||
 | 
							defer w2.Close()
 | 
				
			||||||
 | 
							w2.Write([]byte(`{}`))
 | 
				
			||||||
 | 
							w2.Write([]byte(runtime.EncodeOrDie(latest.Codec, &svc.Items[0])))
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						b := NewBuilder(latest.RESTMapper, api.Scheme, fakeClient()).
 | 
				
			||||||
 | 
							Stream(r, "1").Stream(r2, "2").
 | 
				
			||||||
 | 
							ContinueOnError()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						test := &testVisitor{}
 | 
				
			||||||
 | 
						singular := false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						err := b.Do().IntoSingular(&singular).Visit(test.Handle)
 | 
				
			||||||
 | 
						if err == nil || singular || len(test.Infos) != 0 {
 | 
				
			||||||
 | 
							t.Fatalf("unexpected response: %v %f %#v", err, singular, test.Infos)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						errs, ok := err.(errors.Aggregate)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							t.Fatalf("unexpected error: %v", reflect.TypeOf(err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if len(errs.Errors()) != 2 {
 | 
				
			||||||
 | 
							t.Errorf("unexpected errors", errs)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										24
									
								
								pkg/kubectl/resource/doc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								pkg/kubectl/resource/doc.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2014 Google Inc. All rights reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Package resource 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
 | 
				
			||||||
							
								
								
									
										172
									
								
								pkg/kubectl/resource/helper.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										172
									
								
								pkg/kubectl/resource/helper.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,172 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2014 Google Inc. All rights reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package resource
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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
 | 
				
			||||||
 | 
						// A codec for decoding and encoding objects of this resource type.
 | 
				
			||||||
 | 
						Codec runtime.Codec
 | 
				
			||||||
 | 
						// An interface for reading or writing the resource version of this
 | 
				
			||||||
 | 
						// type.
 | 
				
			||||||
 | 
						Versioner runtime.ResourceVersioner
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewHelper creates a Helper from a ResourceMapping
 | 
				
			||||||
 | 
					func NewHelper(client RESTClient, mapping *meta.RESTMapping) *Helper {
 | 
				
			||||||
 | 
						return &Helper{
 | 
				
			||||||
 | 
							RESTClient: client,
 | 
				
			||||||
 | 
							Resource:   mapping.Resource,
 | 
				
			||||||
 | 
							Codec:      mapping.Codec,
 | 
				
			||||||
 | 
							Versioner:  mapping.MetadataAccessor,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *Helper) Get(namespace, name string) (runtime.Object, error) {
 | 
				
			||||||
 | 
						return m.RESTClient.Get().
 | 
				
			||||||
 | 
							Namespace(namespace).
 | 
				
			||||||
 | 
							Resource(m.Resource).
 | 
				
			||||||
 | 
							Name(name).
 | 
				
			||||||
 | 
							Do().
 | 
				
			||||||
 | 
							Get()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *Helper) List(namespace string, selector labels.Selector) (runtime.Object, error) {
 | 
				
			||||||
 | 
						return m.RESTClient.Get().
 | 
				
			||||||
 | 
							Namespace(namespace).
 | 
				
			||||||
 | 
							Resource(m.Resource).
 | 
				
			||||||
 | 
							SelectorParam("labels", selector).
 | 
				
			||||||
 | 
							Do().
 | 
				
			||||||
 | 
							Get()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *Helper) Watch(namespace, resourceVersion string, labelSelector, fieldSelector labels.Selector) (watch.Interface, error) {
 | 
				
			||||||
 | 
						return m.RESTClient.Get().
 | 
				
			||||||
 | 
							Prefix("watch").
 | 
				
			||||||
 | 
							Namespace(namespace).
 | 
				
			||||||
 | 
							Resource(m.Resource).
 | 
				
			||||||
 | 
							Param("resourceVersion", resourceVersion).
 | 
				
			||||||
 | 
							SelectorParam("labels", labelSelector).
 | 
				
			||||||
 | 
							SelectorParam("fields", fieldSelector).
 | 
				
			||||||
 | 
							Watch()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *Helper) WatchSingle(namespace, name, resourceVersion string) (watch.Interface, error) {
 | 
				
			||||||
 | 
						return m.RESTClient.Get().
 | 
				
			||||||
 | 
							Prefix("watch").
 | 
				
			||||||
 | 
							Namespace(namespace).
 | 
				
			||||||
 | 
							Resource(m.Resource).
 | 
				
			||||||
 | 
							Name(name).
 | 
				
			||||||
 | 
							Param("resourceVersion", resourceVersion).
 | 
				
			||||||
 | 
							Watch()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *Helper) Delete(namespace, name string) error {
 | 
				
			||||||
 | 
						return m.RESTClient.Delete().
 | 
				
			||||||
 | 
							Namespace(namespace).
 | 
				
			||||||
 | 
							Resource(m.Resource).
 | 
				
			||||||
 | 
							Name(name).
 | 
				
			||||||
 | 
							Do().
 | 
				
			||||||
 | 
							Error()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *Helper) Create(namespace string, modify bool, data []byte) error {
 | 
				
			||||||
 | 
						if modify {
 | 
				
			||||||
 | 
							obj, err := m.Codec.Decode(data)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								// We don't know how to check a version on this object, but create it anyway
 | 
				
			||||||
 | 
								return createResource(m.RESTClient, m.Resource, namespace, data)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 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 createResource(m.RESTClient, m.Resource, namespace, data)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if version != "" {
 | 
				
			||||||
 | 
								if err := m.Versioner.SetResourceVersion(obj, ""); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								newData, err := m.Codec.Encode(obj)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								data = newData
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return createResource(m.RESTClient, m.Resource, namespace, data)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func createResource(c RESTClient, resource, namespace string, data []byte) error {
 | 
				
			||||||
 | 
						return c.Post().Namespace(namespace).Resource(resource).Body(data).Do().Error()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (m *Helper) Update(namespace, name string, overwrite bool, data []byte) error {
 | 
				
			||||||
 | 
						c := m.RESTClient
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						obj, err := m.Codec.Decode(data)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							// We don't know how to handle this object, but update it anyway
 | 
				
			||||||
 | 
							return updateResource(c, m.Resource, namespace, name, data)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 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 updateResource(c, m.Resource, namespace, name, data)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if version == "" && overwrite {
 | 
				
			||||||
 | 
							// Retrieve the current version of the object to overwrite the server object
 | 
				
			||||||
 | 
							serverObj, err := c.Get().Namespace(namespace).Resource(m.Resource).Name(name).Do().Get()
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								// The object does not exist, but we want it to be created
 | 
				
			||||||
 | 
								return updateResource(c, m.Resource, namespace, name, data)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							serverVersion, err := m.Versioner.ResourceVersion(serverObj)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err := m.Versioner.SetResourceVersion(obj, serverVersion); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							newData, err := m.Codec.Encode(obj)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							data = newData
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return updateResource(c, m.Resource, namespace, name, data)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func updateResource(c RESTClient, resource, namespace, name string, data []byte) error {
 | 
				
			||||||
 | 
						return c.Put().Namespace(namespace).Resource(resource).Name(name).Body(data).Do().Error()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										463
									
								
								pkg/kubectl/resource/helper_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										463
									
								
								pkg/kubectl/resource/helper_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,463 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2014 Google Inc. All rights reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package resource
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func objBody(obj runtime.Object) io.ReadCloser {
 | 
				
			||||||
 | 
						return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(testapi.Codec(), obj))))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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,
 | 
				
			||||||
 | 
									Body:       objBody(&api.Status{Status: api.StatusFailure}),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Err: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Resp: &http.Response{
 | 
				
			||||||
 | 
									StatusCode: http.StatusOK,
 | 
				
			||||||
 | 
									Body:       objBody(&api.Status{Status: api.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 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 := &client.FakeRESTClient{
 | 
				
			||||||
 | 
								Codec: testapi.Codec(),
 | 
				
			||||||
 | 
								Resp:  test.Resp,
 | 
				
			||||||
 | 
								Err:   test.HttpErr,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							modifier := &Helper{
 | 
				
			||||||
 | 
								RESTClient: client,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							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
 | 
				
			||||||
 | 
							RespFunc client.HTTPClientFunc
 | 
				
			||||||
 | 
							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,
 | 
				
			||||||
 | 
									Body:       objBody(&api.Status{Status: api.StatusFailure}),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Err: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Resp: &http.Response{
 | 
				
			||||||
 | 
									StatusCode: http.StatusOK,
 | 
				
			||||||
 | 
									Body:       objBody(&api.Status{Status: api.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, Body: objBody(&api.Status{Status: api.StatusSuccess})},
 | 
				
			||||||
 | 
								Req:          expectPost,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Modify:       true,
 | 
				
			||||||
 | 
								Object:       &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
 | 
				
			||||||
 | 
								ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
 | 
				
			||||||
 | 
								Resp:         &http.Response{StatusCode: http.StatusOK, Body: objBody(&api.Status{Status: api.StatusSuccess})},
 | 
				
			||||||
 | 
								Req:          expectPost,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for i, test := range tests {
 | 
				
			||||||
 | 
							client := &client.FakeRESTClient{
 | 
				
			||||||
 | 
								Codec: testapi.Codec(),
 | 
				
			||||||
 | 
								Resp:  test.Resp,
 | 
				
			||||||
 | 
								Err:   test.HttpErr,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if test.RespFunc != nil {
 | 
				
			||||||
 | 
								client.Client = test.RespFunc
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							modifier := &Helper{
 | 
				
			||||||
 | 
								RESTClient: client,
 | 
				
			||||||
 | 
								Codec:      testapi.Codec(),
 | 
				
			||||||
 | 
								Versioner:  testapi.MetadataAccessor(),
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							data := []byte{}
 | 
				
			||||||
 | 
							if test.Object != nil {
 | 
				
			||||||
 | 
								data = []byte(runtime.EncodeOrDie(testapi.Codec(), test.Object))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							err := modifier.Create("bar", test.Modify, data)
 | 
				
			||||||
 | 
							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.Codec(), test.ExpectObject))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if !reflect.DeepEqual(expect, body) {
 | 
				
			||||||
 | 
								t.Errorf("%d: unexpected body: %s", i, string(body))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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,
 | 
				
			||||||
 | 
									Body:       objBody(&api.Status{Status: api.StatusFailure}),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Err: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Resp: &http.Response{
 | 
				
			||||||
 | 
									StatusCode: http.StatusOK,
 | 
				
			||||||
 | 
									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 := &client.FakeRESTClient{
 | 
				
			||||||
 | 
								Codec: testapi.Codec(),
 | 
				
			||||||
 | 
								Resp:  test.Resp,
 | 
				
			||||||
 | 
								Err:   test.HttpErr,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							modifier := &Helper{
 | 
				
			||||||
 | 
								RESTClient: client,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							obj, err := modifier.Get("bar", "foo")
 | 
				
			||||||
 | 
							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,
 | 
				
			||||||
 | 
									Body:       objBody(&api.Status{Status: api.StatusFailure}),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Err: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Resp: &http.Response{
 | 
				
			||||||
 | 
									StatusCode: http.StatusOK,
 | 
				
			||||||
 | 
									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 != "/ns/bar" {
 | 
				
			||||||
 | 
										t.Errorf("url doesn't contain name: %#v", req.URL)
 | 
				
			||||||
 | 
										return false
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if req.URL.Query().Get("labels") != 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 := &client.FakeRESTClient{
 | 
				
			||||||
 | 
								Codec: testapi.Codec(),
 | 
				
			||||||
 | 
								Resp:  test.Resp,
 | 
				
			||||||
 | 
								Err:   test.HttpErr,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							modifier := &Helper{
 | 
				
			||||||
 | 
								RESTClient: client,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							obj, err := modifier.List("bar", labels.SelectorFromSet(labels.Set{"foo": "baz"}))
 | 
				
			||||||
 | 
							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 TestHelperUpdate(t *testing.T) {
 | 
				
			||||||
 | 
						expectPut := func(req *http.Request) bool {
 | 
				
			||||||
 | 
							if req.Method != "PUT" {
 | 
				
			||||||
 | 
								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.URL)
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if parts[2] != "foo" {
 | 
				
			||||||
 | 
								t.Errorf("url doesn't contain name: %#v", req)
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tests := []struct {
 | 
				
			||||||
 | 
							Resp      *http.Response
 | 
				
			||||||
 | 
							RespFunc  client.HTTPClientFunc
 | 
				
			||||||
 | 
							HttpErr   error
 | 
				
			||||||
 | 
							Overwrite bool
 | 
				
			||||||
 | 
							Object    runtime.Object
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							ExpectObject runtime.Object
 | 
				
			||||||
 | 
							Err          bool
 | 
				
			||||||
 | 
							Req          func(*http.Request) bool
 | 
				
			||||||
 | 
						}{
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								HttpErr: errors.New("failure"),
 | 
				
			||||||
 | 
								Err:     true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Object: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
 | 
				
			||||||
 | 
								Resp: &http.Response{
 | 
				
			||||||
 | 
									StatusCode: http.StatusNotFound,
 | 
				
			||||||
 | 
									Body:       objBody(&api.Status{Status: api.StatusFailure}),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Err: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Object:       &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
 | 
				
			||||||
 | 
								ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
 | 
				
			||||||
 | 
								Resp: &http.Response{
 | 
				
			||||||
 | 
									StatusCode: http.StatusOK,
 | 
				
			||||||
 | 
									Body:       objBody(&api.Status{Status: api.StatusSuccess}),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Req: expectPut,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Object:       &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}},
 | 
				
			||||||
 | 
								ExpectObject: &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								Overwrite: true,
 | 
				
			||||||
 | 
								RespFunc: func(req *http.Request) (*http.Response, error) {
 | 
				
			||||||
 | 
									if req.Method == "PUT" {
 | 
				
			||||||
 | 
										return &http.Response{StatusCode: http.StatusOK, Body: objBody(&api.Status{Status: api.StatusSuccess})}, nil
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return &http.Response{StatusCode: http.StatusOK, Body: objBody(&api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "10"}})}, nil
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								Req: expectPut,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								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, Body: objBody(&api.Status{Status: api.StatusSuccess})},
 | 
				
			||||||
 | 
								Req:          expectPut,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for i, test := range tests {
 | 
				
			||||||
 | 
							client := &client.FakeRESTClient{
 | 
				
			||||||
 | 
								Codec: testapi.Codec(),
 | 
				
			||||||
 | 
								Resp:  test.Resp,
 | 
				
			||||||
 | 
								Err:   test.HttpErr,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if test.RespFunc != nil {
 | 
				
			||||||
 | 
								client.Client = test.RespFunc
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							modifier := &Helper{
 | 
				
			||||||
 | 
								RESTClient: client,
 | 
				
			||||||
 | 
								Codec:      testapi.Codec(),
 | 
				
			||||||
 | 
								Versioner:  testapi.MetadataAccessor(),
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							data := []byte{}
 | 
				
			||||||
 | 
							if test.Object != nil {
 | 
				
			||||||
 | 
								data = []byte(runtime.EncodeOrDie(testapi.Codec(), test.Object))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							err := modifier.Update("bar", "foo", test.Overwrite, data)
 | 
				
			||||||
 | 
							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.Codec(), test.ExpectObject))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if !reflect.DeepEqual(expect, body) {
 | 
				
			||||||
 | 
								t.Errorf("%d: unexpected body: %s", i, string(body))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										44
									
								
								pkg/kubectl/resource/interfaces.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								pkg/kubectl/resource/interfaces.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2014 Google Inc. All rights reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package resource
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// RESTClient is a client helper for dealing with RESTful resources
 | 
				
			||||||
 | 
					// in a generic way.
 | 
				
			||||||
 | 
					type RESTClient interface {
 | 
				
			||||||
 | 
						Get() *client.Request
 | 
				
			||||||
 | 
						Post() *client.Request
 | 
				
			||||||
 | 
						Delete() *client.Request
 | 
				
			||||||
 | 
						Put() *client.Request
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ClientMapper retrieves a client object for a given mapping
 | 
				
			||||||
 | 
					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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										97
									
								
								pkg/kubectl/resource/mapper.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								pkg/kubectl/resource/mapper.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2014 Google Inc. All rights reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package resource
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"reflect"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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) {
 | 
				
			||||||
 | 
						version, kind, err := m.DataVersionAndKind(data)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("unable to get type info from %q: %v", source, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						mapping, err := m.RESTMapping(kind, version)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("unable to recognize %q: %v", source, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						obj, err := mapping.Codec.Decode(data)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("unable to load %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,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Object:          obj,
 | 
				
			||||||
 | 
							ResourceVersion: resourceVersion,
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// InfoForData 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) (*Info, error) {
 | 
				
			||||||
 | 
						version, kind, err := m.ObjectVersionAndKind(obj)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("unable to get type info from the object %q: %v", reflect.TypeOf(obj), err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						mapping, err := m.RESTMapping(kind, version)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("unable to recognize %q: %v", kind, 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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										80
									
								
								pkg/kubectl/resource/selector.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								pkg/kubectl/resource/selector.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,80 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2014 Google Inc. All rights reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package resource
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/golang/glog"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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) *Selector {
 | 
				
			||||||
 | 
						return &Selector{
 | 
				
			||||||
 | 
							Client:    client,
 | 
				
			||||||
 | 
							Mapping:   mapping,
 | 
				
			||||||
 | 
							Namespace: namespace,
 | 
				
			||||||
 | 
							Selector:  selector,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Visit implements Visitor
 | 
				
			||||||
 | 
					func (r *Selector) Visit(fn VisitorFunc) error {
 | 
				
			||||||
 | 
						list, err := NewHelper(r.Client, r.Mapping).List(r.Namespace, r.Selector)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if errors.IsBadRequest(err) || errors.IsNotFound(err) {
 | 
				
			||||||
 | 
								if r.Selector.Empty() {
 | 
				
			||||||
 | 
									glog.V(2).Infof("Unable to list %q: %v", r.Mapping.Resource, err)
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									glog.V(2).Infof("Unable to find %q that match the selector %q: %v", r.Mapping.Resource, r.Selector, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							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)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *Selector) Watch(resourceVersion string) (watch.Interface, error) {
 | 
				
			||||||
 | 
						return NewHelper(r.Client, r.Mapping).Watch(r.Namespace, resourceVersion, r.Selector, labels.Everything())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ResourceMapping returns the mapping for this resource and implements ResourceMapping
 | 
				
			||||||
 | 
					func (r *Selector) ResourceMapping() *meta.RESTMapping {
 | 
				
			||||||
 | 
						return r.Mapping
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										422
									
								
								pkg/kubectl/resource/visitor.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										422
									
								
								pkg/kubectl/resource/visitor.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,422 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					Copyright 2014 Google Inc. All rights reserved.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Licensed under the Apache License, Version 2.0 (the "License");
 | 
				
			||||||
 | 
					you may not use this file except in compliance with the License.
 | 
				
			||||||
 | 
					You may obtain a copy of the License at
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					distributed under the License is distributed on an "AS IS" BASIS,
 | 
				
			||||||
 | 
					WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
				
			||||||
 | 
					See the License for the specific language governing permissions and
 | 
				
			||||||
 | 
					limitations under the License.
 | 
				
			||||||
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package resource
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"net/url"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/golang/glog"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/util/errors"
 | 
				
			||||||
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Visitor lets clients walk a list of resources.
 | 
				
			||||||
 | 
					type Visitor interface {
 | 
				
			||||||
 | 
						Visit(VisitorFunc) error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// VisitorFunc implements the Visitor interface for a matching function
 | 
				
			||||||
 | 
					type VisitorFunc func(*Info) 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, this is the most recent value returned by the server if available
 | 
				
			||||||
 | 
						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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewInfo returns a new info object
 | 
				
			||||||
 | 
					func NewInfo(client RESTClient, mapping *meta.RESTMapping, namespace, name string) *Info {
 | 
				
			||||||
 | 
						return &Info{
 | 
				
			||||||
 | 
							Client:    client,
 | 
				
			||||||
 | 
							Mapping:   mapping,
 | 
				
			||||||
 | 
							Namespace: namespace,
 | 
				
			||||||
 | 
							Name:      name,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Visit implements Visitor
 | 
				
			||||||
 | 
					func (i *Info) Visit(fn VisitorFunc) error {
 | 
				
			||||||
 | 
						return fn(i)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (i *Info) Get() error {
 | 
				
			||||||
 | 
						obj, err := NewHelper(i.Client, i.Mapping).Get(i.Namespace, i.Name)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						i.Object = obj
 | 
				
			||||||
 | 
						i.ResourceVersion, _ = i.Mapping.MetadataAccessor.ResourceVersion(obj)
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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) error {
 | 
				
			||||||
 | 
								if err := fn(info); err != nil {
 | 
				
			||||||
 | 
									errs = append(errs, err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}); err != nil {
 | 
				
			||||||
 | 
								errs = append(errs, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return errors.NewAggregate(errs)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// PathVisitor visits a given path and returns an object representing the file
 | 
				
			||||||
 | 
					// at that path.
 | 
				
			||||||
 | 
					type PathVisitor struct {
 | 
				
			||||||
 | 
						*Mapper
 | 
				
			||||||
 | 
						// The file path to load
 | 
				
			||||||
 | 
						Path string
 | 
				
			||||||
 | 
						// Whether to ignore files that are not recognized as API objects
 | 
				
			||||||
 | 
						IgnoreErrors bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (v *PathVisitor) Visit(fn VisitorFunc) error {
 | 
				
			||||||
 | 
						data, err := ioutil.ReadFile(v.Path)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("unable to read %q: %v", v.Path, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						info, err := v.Mapper.InfoForData(data, v.Path)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if v.IgnoreErrors {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							glog.V(2).Infof("Unable to load file %q: %v", v.Path, err)
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return fn(info)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// DirectoryVisitor loads the specified files from a directory and passes them
 | 
				
			||||||
 | 
					// to visitors.
 | 
				
			||||||
 | 
					type DirectoryVisitor struct {
 | 
				
			||||||
 | 
						*Mapper
 | 
				
			||||||
 | 
						// The directory or file to start from
 | 
				
			||||||
 | 
						Path string
 | 
				
			||||||
 | 
						// Whether directories are recursed
 | 
				
			||||||
 | 
						Recursive bool
 | 
				
			||||||
 | 
						// The file extensions to include. If empty, all files are read.
 | 
				
			||||||
 | 
						Extensions []string
 | 
				
			||||||
 | 
						// Whether to ignore files that are not recognized as API objects
 | 
				
			||||||
 | 
						IgnoreErrors bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (v *DirectoryVisitor) ignoreFile(path string) bool {
 | 
				
			||||||
 | 
						if len(v.Extensions) == 0 {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ext := filepath.Ext(path)
 | 
				
			||||||
 | 
						for _, s := range v.Extensions {
 | 
				
			||||||
 | 
							if s == ext {
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (v *DirectoryVisitor) Visit(fn VisitorFunc) error {
 | 
				
			||||||
 | 
						return filepath.Walk(v.Path, func(path string, fi os.FileInfo, err error) error {
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if fi.IsDir() {
 | 
				
			||||||
 | 
								if path != v.Path && !v.Recursive {
 | 
				
			||||||
 | 
									return filepath.SkipDir
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if v.ignoreFile(path) {
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							data, err := ioutil.ReadFile(path)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("unable to read %q: %v", path, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							info, err := v.Mapper.InfoForData(data, path)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								if v.IgnoreErrors {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								glog.V(2).Infof("Unable to load file %q: %v", path, err)
 | 
				
			||||||
 | 
								return nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return fn(info)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// URLVisitor downloads the contents of a URL, and if successful, returns
 | 
				
			||||||
 | 
					// an info object representing the downloaded object.
 | 
				
			||||||
 | 
					type URLVisitor struct {
 | 
				
			||||||
 | 
						*Mapper
 | 
				
			||||||
 | 
						URL *url.URL
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (v *URLVisitor) Visit(fn VisitorFunc) error {
 | 
				
			||||||
 | 
						res, err := http.Get(v.URL.String())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("unable to access URL %q: %v\n", v.URL, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer res.Body.Close()
 | 
				
			||||||
 | 
						if res.StatusCode != 200 {
 | 
				
			||||||
 | 
							return fmt.Errorf("unable to read URL %q, server reported %d %s", v.URL, res.StatusCode, res.Status)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						data, err := ioutil.ReadAll(res.Body)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("unable to read URL %q: %v\n", v.URL, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						info, err := v.Mapper.InfoForData(data, v.URL.String())
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return fn(info)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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) error {
 | 
				
			||||||
 | 
							for i := range v.decorators {
 | 
				
			||||||
 | 
								if err := v.decorators[i](info); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return fn(info)
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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) error {
 | 
				
			||||||
 | 
							if info.Object == nil {
 | 
				
			||||||
 | 
								return fn(info)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							items, err := runtime.ExtractList(info.Object)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fn(info)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for i := range items {
 | 
				
			||||||
 | 
								item, err := v.InfoForObject(items[i])
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if len(info.ResourceVersion) != 0 {
 | 
				
			||||||
 | 
									item.ResourceVersion = info.ResourceVersion
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if err := fn(item); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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
 | 
				
			||||||
 | 
						IgnoreErrors bool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewStreamVisitor creates a visitor that will return resources that were encoded into the provided
 | 
				
			||||||
 | 
					// stream. If ignoreErrors is set, unrecognized or invalid objects will be skipped and logged.
 | 
				
			||||||
 | 
					func NewStreamVisitor(r io.Reader, mapper *Mapper, source string, ignoreErrors bool) Visitor {
 | 
				
			||||||
 | 
						return &StreamVisitor{r, mapper, source, ignoreErrors}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Visit implements Visitor over a stream.
 | 
				
			||||||
 | 
					func (v *StreamVisitor) Visit(fn VisitorFunc) error {
 | 
				
			||||||
 | 
						d := json.NewDecoder(v.Reader)
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							ext := runtime.RawExtension{}
 | 
				
			||||||
 | 
							if err := d.Decode(&ext); err != nil {
 | 
				
			||||||
 | 
								if err == io.EOF {
 | 
				
			||||||
 | 
									return nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							info, err := v.InfoForData(ext.RawJSON, v.Source)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								if v.IgnoreErrors {
 | 
				
			||||||
 | 
									glog.V(2).Infof("Unable to read item from stream %q: %v", err)
 | 
				
			||||||
 | 
									glog.V(4).Infof("Unreadable: %s", string(ext.RawJSON))
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err := fn(info); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func UpdateObjectNamespace(info *Info) error {
 | 
				
			||||||
 | 
						if info.Object != nil {
 | 
				
			||||||
 | 
							return info.Mapping.MetadataAccessor.SetNamespace(info.Object, info.Namespace)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						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) error {
 | 
				
			||||||
 | 
							if len(info.Namespace) == 0 {
 | 
				
			||||||
 | 
								info.Namespace = namespace
 | 
				
			||||||
 | 
								UpdateObjectNamespace(info)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							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) error {
 | 
				
			||||||
 | 
							if len(info.Namespace) == 0 {
 | 
				
			||||||
 | 
								info.Namespace = namespace
 | 
				
			||||||
 | 
								UpdateObjectNamespace(info)
 | 
				
			||||||
 | 
								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) error {
 | 
				
			||||||
 | 
						if len(info.Name) == 0 || len(info.Namespace) == 0 {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						obj, err := NewHelper(info.Client, info.Mapping).Get(info.Namespace, info.Name)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						info.Object = obj
 | 
				
			||||||
 | 
						info.ResourceVersion, _ = info.Mapping.MetadataAccessor.ResourceVersion(obj)
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user