2017-02-01 00:45:59 +00:00
/ *
Copyright 2014 The Kubernetes Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package resource
import (
"fmt"
"io"
"net/url"
"os"
"strings"
"k8s.io/apimachinery/pkg/api/meta"
2017-02-03 13:41:32 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2017-02-01 00:45:59 +00:00
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kubernetes/pkg/api/validation"
)
var FileExtensions = [ ] string { ".json" , ".yaml" , ".yml" }
var InputExtensions = append ( FileExtensions , "stdin" )
const defaultHttpGetAttempts int = 3
// Builder provides convenience functions for taking arguments and parameters
// from the command line and converting them to a list of resources to iterate
// over using the Visitor interface.
type Builder struct {
mapper * Mapper
errs [ ] error
paths [ ] Visitor
stream bool
dir bool
selector labels . Selector
selectAll bool
resources [ ] string
namespace string
allNamespace bool
names [ ] string
resourceTuples [ ] resourceTuple
defaultNamespace bool
requireNamespace bool
flatten bool
latest bool
requireObject bool
singleResourceType bool
continueOnError bool
singleItemImplied bool
export bool
schema validation . Schema
}
var missingResourceError = fmt . Errorf ( ` You must provide one or more resources by argument or filename .
Example resource specifications include :
' - f rsrc . yaml '
' -- filename = rsrc . json '
' pods my - pod '
' services ' ` )
// TODO: expand this to include other errors.
func IsUsageError ( err error ) bool {
if err == nil {
return false
}
return err == missingResourceError
}
type FilenameOptions struct {
Filenames [ ] string
Recursive bool
}
type resourceTuple struct {
Resource string
Name string
}
// NewBuilder creates a builder that operates on generic objects.
func NewBuilder ( mapper meta . RESTMapper , typer runtime . ObjectTyper , clientMapper ClientMapper , decoder runtime . Decoder ) * Builder {
return & Builder {
mapper : & Mapper { typer , mapper , clientMapper , decoder } ,
requireObject : true ,
}
}
func ( b * Builder ) Schema ( schema validation . Schema ) * Builder {
b . schema = schema
return b
}
// FilenameParam groups input in two categories: URLs and files (files, directories, STDIN)
// If enforceNamespace is false, namespaces in the specs will be allowed to
// override the default namespace. If it is true, namespaces that don't match
// will cause an error.
// If ContinueOnError() is set prior to this method, objects on the path that are not
// recognized will be ignored (but logged at V(2)).
func ( b * Builder ) FilenameParam ( enforceNamespace bool , filenameOptions * FilenameOptions ) * Builder {
recursive := filenameOptions . Recursive
paths := filenameOptions . Filenames
for _ , s := range paths {
switch {
case s == "-" :
b . Stdin ( )
case strings . Index ( s , "http://" ) == 0 || strings . Index ( s , "https://" ) == 0 :
url , err := url . Parse ( s )
if err != nil {
b . errs = append ( b . errs , fmt . Errorf ( "the URL passed to filename %q is not valid: %v" , s , err ) )
continue
}
b . URL ( defaultHttpGetAttempts , url )
default :
if ! recursive {
b . singleItemImplied = true
}
b . Path ( recursive , s )
}
}
if enforceNamespace {
b . RequireNamespace ( )
}
return b
}
// URL accepts a number of URLs directly.
func ( b * Builder ) URL ( httpAttemptCount int , urls ... * url . URL ) * Builder {
for _ , u := range urls {
b . paths = append ( b . paths , & URLVisitor {
URL : u ,
StreamVisitor : NewStreamVisitor ( nil , b . mapper , u . String ( ) , b . schema ) ,
HttpAttemptCount : httpAttemptCount ,
} )
}
return b
}
// Stdin will read objects from the standard input. If ContinueOnError() is set
// prior to this method being called, objects in the stream that are unrecognized
// will be ignored (but logged at V(2)).
func ( b * Builder ) Stdin ( ) * Builder {
b . stream = true
b . paths = append ( b . paths , FileVisitorForSTDIN ( b . mapper , b . schema ) )
return b
}
// Stream will read objects from the provided reader, and if an error occurs will
// include the name string in the error message. If ContinueOnError() is set
// prior to this method being called, objects in the stream that are unrecognized
// will be ignored (but logged at V(2)).
func ( b * Builder ) Stream ( r io . Reader , name string ) * Builder {
b . stream = true
b . paths = append ( b . paths , NewStreamVisitor ( r , b . mapper , name , b . schema ) )
return b
}
// Path accepts a set of paths that may be files, directories (all can containing
// one or more resources). Creates a FileVisitor for each file and then each
// FileVisitor is streaming the content to a StreamVisitor. If ContinueOnError() is set
// prior to this method being called, objects on the path that are unrecognized will be
// ignored (but logged at V(2)).
func ( b * Builder ) Path ( recursive bool , paths ... string ) * Builder {
for _ , p := range paths {
_ , err := os . Stat ( p )
if os . IsNotExist ( err ) {
b . errs = append ( b . errs , fmt . Errorf ( "the path %q does not exist" , p ) )
continue
}
if err != nil {
b . errs = append ( b . errs , fmt . Errorf ( "the path %q cannot be accessed: %v" , p , err ) )
continue
}
visitors , err := ExpandPathsToFileVisitors ( b . mapper , p , recursive , FileExtensions , b . schema )
if err != nil {
b . errs = append ( b . errs , fmt . Errorf ( "error reading %q: %v" , p , err ) )
}
if len ( visitors ) > 1 {
b . dir = true
}
b . paths = append ( b . paths , visitors ... )
}
return b
}
// ResourceTypes is a list of types of resources to operate on, when listing objects on
// the server or retrieving objects that match a selector.
func ( b * Builder ) ResourceTypes ( types ... string ) * Builder {
b . resources = append ( b . resources , types ... )
return b
}
// ResourceNames accepts a default type and one or more names, and creates tuples of
// resources
func ( b * Builder ) ResourceNames ( resource string , names ... string ) * Builder {
for _ , name := range names {
// See if this input string is of type/name format
tuple , ok , err := splitResourceTypeName ( name )
if err != nil {
b . errs = append ( b . errs , err )
return b
}
if ok {
b . resourceTuples = append ( b . resourceTuples , tuple )
continue
}
if len ( resource ) == 0 {
b . errs = append ( b . errs , fmt . Errorf ( "the argument %q must be RESOURCE/NAME" , name ) )
continue
}
// Use the given default type to create a resource tuple
b . resourceTuples = append ( b . resourceTuples , resourceTuple { Resource : resource , Name : name } )
}
return b
}
// SelectorParam defines a selector that should be applied to the object types to load.
// This will not affect files loaded from disk or URL. If the parameter is empty it is
// a no-op - to select all resources invoke `b.Selector(labels.Everything)`.
func ( b * Builder ) SelectorParam ( s string ) * Builder {
selector , err := labels . Parse ( s )
if err != nil {
b . errs = append ( b . errs , fmt . Errorf ( "the provided selector %q is not valid: %v" , s , err ) )
return b
}
if selector . Empty ( ) {
return b
}
if b . selectAll {
b . errs = append ( b . errs , fmt . Errorf ( "found non empty selector %q with previously set 'all' parameter. " , s ) )
return b
}
return b . Selector ( selector )
}
// Selector accepts a selector directly, and if non nil will trigger a list action.
func ( b * Builder ) Selector ( selector labels . Selector ) * Builder {
b . selector = selector
return b
}
// ExportParam accepts the export boolean for these resources
func ( b * Builder ) ExportParam ( export bool ) * Builder {
b . export = export
return b
}
// NamespaceParam accepts the namespace that these resources should be
// considered under from - used by DefaultNamespace() and RequireNamespace()
func ( b * Builder ) NamespaceParam ( namespace string ) * Builder {
b . namespace = namespace
return b
}
// DefaultNamespace instructs the builder to set the namespace value for any object found
// to NamespaceParam() if empty.
func ( b * Builder ) DefaultNamespace ( ) * Builder {
b . defaultNamespace = true
return b
}
2017-02-03 13:41:32 +00:00
// AllNamespaces instructs the builder to metav1.NamespaceAll as a namespace to request resources
2017-02-01 00:45:59 +00:00
// across all of the namespace. This overrides the namespace set by NamespaceParam().
func ( b * Builder ) AllNamespaces ( allNamespace bool ) * Builder {
if allNamespace {
2017-02-03 13:41:32 +00:00
b . namespace = metav1 . NamespaceAll
2017-02-01 00:45:59 +00:00
}
b . allNamespace = allNamespace
return b
}
// RequireNamespace instructs the builder to set the namespace value for any object found
// to NamespaceParam() if empty, and if the value on the resource does not match
// NamespaceParam() an error will be returned.
func ( b * Builder ) RequireNamespace ( ) * Builder {
b . requireNamespace = true
return b
}
// SelectEverythingParam
func ( b * Builder ) SelectAllParam ( selectAll bool ) * Builder {
if selectAll && b . selector != nil {
b . errs = append ( b . errs , fmt . Errorf ( "setting 'all' parameter but found a non empty selector. " ) )
return b
}
b . selectAll = selectAll
return b
}
// ResourceTypeOrNameArgs indicates that the builder should accept arguments
// of the form `(<type1>[,<type2>,...]|<type> <name1>[,<name2>,...])`. When one argument is
// received, the types provided will be retrieved from the server (and be comma delimited).
// When two or more arguments are received, they must be a single type and resource name(s).
// The allowEmptySelector permits to select all the resources (via Everything func).
func ( b * Builder ) ResourceTypeOrNameArgs ( allowEmptySelector bool , args ... string ) * Builder {
args = normalizeMultipleResourcesArgs ( args )
if ok , err := hasCombinedTypeArgs ( args ) ; ok {
if err != nil {
b . errs = append ( b . errs , err )
return b
}
for _ , s := range args {
tuple , ok , err := splitResourceTypeName ( s )
if err != nil {
b . errs = append ( b . errs , err )
return b
}
if ok {
b . resourceTuples = append ( b . resourceTuples , tuple )
}
}
return b
}
if len ( args ) > 0 {
// Try replacing aliases only in types
args [ 0 ] = b . ReplaceAliases ( args [ 0 ] )
}
switch {
case len ( args ) > 2 :
b . names = append ( b . names , args [ 1 : ] ... )
b . ResourceTypes ( SplitResourceArgument ( args [ 0 ] ) ... )
case len ( args ) == 2 :
b . names = append ( b . names , args [ 1 ] )
b . ResourceTypes ( SplitResourceArgument ( args [ 0 ] ) ... )
case len ( args ) == 1 :
b . ResourceTypes ( SplitResourceArgument ( args [ 0 ] ) ... )
if b . selector == nil && allowEmptySelector {
b . selector = labels . Everything ( )
}
case len ( args ) == 0 :
default :
b . errs = append ( b . errs , fmt . Errorf ( "arguments must consist of a resource or a resource and name" ) )
}
return b
}
// ReplaceAliases accepts an argument and tries to expand any existing
// aliases found in it
func ( b * Builder ) ReplaceAliases ( input string ) string {
replaced := [ ] string { }
for _ , arg := range strings . Split ( input , "," ) {
if aliases , ok := b . mapper . AliasesForResource ( arg ) ; ok {
arg = strings . Join ( aliases , "," )
}
replaced = append ( replaced , arg )
}
return strings . Join ( replaced , "," )
}
func hasCombinedTypeArgs ( args [ ] string ) ( bool , error ) {
hasSlash := 0
for _ , s := range args {
if strings . Contains ( s , "/" ) {
hasSlash ++
}
}
switch {
case hasSlash > 0 && hasSlash == len ( args ) :
return true , nil
case hasSlash > 0 && hasSlash != len ( args ) :
baseCmd := "cmd"
if len ( os . Args ) > 0 {
baseCmdSlice := strings . Split ( os . Args [ 0 ] , "/" )
baseCmd = baseCmdSlice [ len ( baseCmdSlice ) - 1 ]
}
return true , fmt . Errorf ( "there is no need to specify a resource type as a separate argument when passing arguments in resource/name form (e.g. '%s get resource/<resource_name>' instead of '%s get resource resource/<resource_name>'" , baseCmd , baseCmd )
default :
return false , nil
}
}
// Normalize args convert multiple resources to resource tuples, a,b,c d
// as a transform to a/d b/d c/d
func normalizeMultipleResourcesArgs ( args [ ] string ) [ ] string {
if len ( args ) >= 2 {
resources := [ ] string { }
resources = append ( resources , SplitResourceArgument ( args [ 0 ] ) ... )
if len ( resources ) > 1 {
names := [ ] string { }
names = append ( names , args [ 1 : ] ... )
newArgs := [ ] string { }
for _ , resource := range resources {
for _ , name := range names {
newArgs = append ( newArgs , strings . Join ( [ ] string { resource , name } , "/" ) )
}
}
return newArgs
}
}
return args
}
// splitResourceTypeName handles type/name resource formats and returns a resource tuple
// (empty or not), whether it successfully found one, and an error
func splitResourceTypeName ( s string ) ( resourceTuple , bool , error ) {
if ! strings . Contains ( s , "/" ) {
return resourceTuple { } , false , nil
}
seg := strings . Split ( s , "/" )
if len ( seg ) != 2 {
return resourceTuple { } , false , fmt . Errorf ( "arguments in resource/name form may not have more than one slash" )
}
resource , name := seg [ 0 ] , seg [ 1 ]
if len ( resource ) == 0 || len ( name ) == 0 || len ( SplitResourceArgument ( resource ) ) != 1 {
return resourceTuple { } , false , fmt . Errorf ( "arguments in resource/name form must have a single resource and name" )
}
return resourceTuple { Resource : resource , Name : name } , true , nil
}
// Flatten will convert any objects with a field named "Items" that is an array of runtime.Object
// compatible types into individual entries and give them their own items. The original object
// is not passed to any visitors.
func ( b * Builder ) Flatten ( ) * Builder {
b . flatten = true
return b
}
// Latest will fetch the latest copy of any objects loaded from URLs or files from the server.
func ( b * Builder ) Latest ( ) * Builder {
b . latest = true
return b
}
// RequireObject ensures that resulting infos have an object set. If false, resulting info may not have an object set.
func ( b * Builder ) RequireObject ( require bool ) * Builder {
b . requireObject = require
return b
}
// ContinueOnError will attempt to load and visit as many objects as possible, even if some visits
// return errors or some objects cannot be loaded. The default behavior is to terminate after
// the first error is returned from a VisitorFunc.
func ( b * Builder ) ContinueOnError ( ) * Builder {
b . continueOnError = true
return b
}
// SingleResourceType will cause the builder to error if the user specifies more than a single type
// of resource.
func ( b * Builder ) SingleResourceType ( ) * Builder {
b . singleResourceType = true
return b
}
// mappingFor returns the RESTMapping for the Kind referenced by the resource.
// prefers a fully specified GroupVersionResource match. If we don't have one match on GroupResource
func ( b * Builder ) mappingFor ( resourceArg string ) ( * meta . RESTMapping , error ) {
fullySpecifiedGVR , groupResource := schema . ParseResourceArg ( resourceArg )
gvk := schema . GroupVersionKind { }
if fullySpecifiedGVR != nil {
gvk , _ = b . mapper . KindFor ( * fullySpecifiedGVR )
}
if gvk . Empty ( ) {
var err error
gvk , err = b . mapper . KindFor ( groupResource . WithVersion ( "" ) )
if err != nil {
return nil , err
}
}
return b . mapper . RESTMapping ( gvk . GroupKind ( ) , gvk . Version )
}
func ( b * Builder ) resourceMappings ( ) ( [ ] * meta . RESTMapping , error ) {
if len ( b . resources ) > 1 && b . singleResourceType {
return nil , fmt . Errorf ( "you may only specify a single resource type" )
}
mappings := [ ] * meta . RESTMapping { }
for _ , r := range b . resources {
mapping , err := b . mappingFor ( r )
if err != nil {
return nil , err
}
mappings = append ( mappings , mapping )
}
return mappings , nil
}
func ( b * Builder ) resourceTupleMappings ( ) ( map [ string ] * meta . RESTMapping , error ) {
mappings := make ( map [ string ] * meta . RESTMapping )
canonical := make ( map [ string ] struct { } )
for _ , r := range b . resourceTuples {
if _ , ok := mappings [ r . Resource ] ; ok {
continue
}
mapping , err := b . mappingFor ( r . Resource )
if err != nil {
return nil , err
}
mappings [ mapping . Resource ] = mapping
mappings [ r . Resource ] = mapping
canonical [ mapping . Resource ] = struct { } { }
}
if len ( canonical ) > 1 && b . singleResourceType {
return nil , fmt . Errorf ( "you may only specify a single resource type" )
}
return mappings , nil
}
func ( b * Builder ) visitorResult ( ) * Result {
if len ( b . errs ) > 0 {
return & Result { err : utilerrors . NewAggregate ( b . errs ) }
}
if b . selectAll {
b . selector = labels . Everything ( )
}
// visit items specified by paths
if len ( b . paths ) != 0 {
return b . visitByPaths ( )
}
// visit selectors
if b . selector != nil {
return b . visitBySelector ( )
}
// visit items specified by resource and name
if len ( b . resourceTuples ) != 0 {
return b . visitByResource ( )
}
// visit items specified by name
if len ( b . names ) != 0 {
return b . visitByName ( )
}
if len ( b . resources ) != 0 {
return & Result { err : fmt . Errorf ( "resource(s) were provided, but no name, label selector, or --all flag specified" ) }
}
return & Result { err : missingResourceError }
}
func ( b * Builder ) visitBySelector ( ) * Result {
if len ( b . names ) != 0 {
return & Result { err : fmt . Errorf ( "name cannot be provided when a selector is specified" ) }
}
if len ( b . resourceTuples ) != 0 {
return & Result { err : fmt . Errorf ( "selectors and the all flag cannot be used when passing resource/name arguments" ) }
}
if len ( b . resources ) == 0 {
return & Result { err : fmt . Errorf ( "at least one resource must be specified to use a selector" ) }
}
mappings , err := b . resourceMappings ( )
if err != nil {
return & Result { err : err }
}
visitors := [ ] Visitor { }
for _ , mapping := range mappings {
client , err := b . mapper . ClientForMapping ( mapping )
if err != nil {
return & Result { err : err }
}
selectorNamespace := b . namespace
if mapping . Scope . Name ( ) != meta . RESTScopeNameNamespace {
selectorNamespace = ""
}
visitors = append ( visitors , NewSelector ( client , mapping , selectorNamespace , b . selector , b . export ) )
}
if b . continueOnError {
return & Result { visitor : EagerVisitorList ( visitors ) , sources : visitors }
}
return & Result { visitor : VisitorList ( visitors ) , sources : visitors }
}
func ( b * Builder ) visitByResource ( ) * Result {
// if b.singleItemImplied is false, this could be by default, so double-check length
// of resourceTuples to determine if in fact it is singleItemImplied or not
isSingleItemImplied := b . singleItemImplied
if ! isSingleItemImplied {
isSingleItemImplied = len ( b . resourceTuples ) == 1
}
if len ( b . resources ) != 0 {
return & Result { singleItemImplied : isSingleItemImplied , err : fmt . Errorf ( "you may not specify individual resources and bulk resources in the same call" ) }
}
// retrieve one client for each resource
mappings , err := b . resourceTupleMappings ( )
if err != nil {
return & Result { singleItemImplied : isSingleItemImplied , err : err }
}
clients := make ( map [ string ] RESTClient )
for _ , mapping := range mappings {
s := fmt . Sprintf ( "%s/%s" , mapping . GroupVersionKind . GroupVersion ( ) . String ( ) , mapping . Resource )
if _ , ok := clients [ s ] ; ok {
continue
}
client , err := b . mapper . ClientForMapping ( mapping )
if err != nil {
return & Result { err : err }
}
clients [ s ] = client
}
items := [ ] Visitor { }
for _ , tuple := range b . resourceTuples {
mapping , ok := mappings [ tuple . Resource ]
if ! ok {
return & Result { singleItemImplied : isSingleItemImplied , err : fmt . Errorf ( "resource %q is not recognized: %v" , tuple . Resource , mappings ) }
}
s := fmt . Sprintf ( "%s/%s" , mapping . GroupVersionKind . GroupVersion ( ) . String ( ) , mapping . Resource )
client , ok := clients [ s ]
if ! ok {
return & Result { singleItemImplied : isSingleItemImplied , err : fmt . Errorf ( "could not find a client for resource %q" , tuple . Resource ) }
}
selectorNamespace := b . namespace
if mapping . Scope . Name ( ) != meta . RESTScopeNameNamespace {
selectorNamespace = ""
} else {
if len ( b . namespace ) == 0 {
errMsg := "namespace may not be empty when retrieving a resource by name"
if b . allNamespace {
errMsg = "a resource cannot be retrieved by name across all namespaces"
}
return & Result { singleItemImplied : isSingleItemImplied , err : fmt . Errorf ( errMsg ) }
}
}
info := NewInfo ( client , mapping , selectorNamespace , tuple . Name , b . export )
items = append ( items , info )
}
var visitors Visitor
if b . continueOnError {
visitors = EagerVisitorList ( items )
} else {
visitors = VisitorList ( items )
}
return & Result { singleItemImplied : isSingleItemImplied , visitor : visitors , sources : items }
}
func ( b * Builder ) visitByName ( ) * Result {
isSingleItemImplied := len ( b . names ) == 1
if len ( b . paths ) != 0 {
return & Result { singleItemImplied : isSingleItemImplied , err : fmt . Errorf ( "when paths, URLs, or stdin is provided as input, you may not specify a resource by arguments as well" ) }
}
if len ( b . resources ) == 0 {
return & Result { singleItemImplied : isSingleItemImplied , err : fmt . Errorf ( "you must provide a resource and a resource name together" ) }
}
if len ( b . resources ) > 1 {
return & Result { singleItemImplied : isSingleItemImplied , err : fmt . Errorf ( "you must specify only one resource" ) }
}
mappings , err := b . resourceMappings ( )
if err != nil {
return & Result { singleItemImplied : isSingleItemImplied , err : err }
}
mapping := mappings [ 0 ]
client , err := b . mapper . ClientForMapping ( mapping )
if err != nil {
return & Result { err : err }
}
selectorNamespace := b . namespace
if mapping . Scope . Name ( ) != meta . RESTScopeNameNamespace {
selectorNamespace = ""
} else {
if len ( b . namespace ) == 0 {
errMsg := "namespace may not be empty when retrieving a resource by name"
if b . allNamespace {
errMsg = "a resource cannot be retrieved by name across all namespaces"
}
return & Result { singleItemImplied : isSingleItemImplied , err : fmt . Errorf ( errMsg ) }
}
}
visitors := [ ] Visitor { }
for _ , name := range b . names {
info := NewInfo ( client , mapping , selectorNamespace , name , b . export )
visitors = append ( visitors , info )
}
return & Result { singleItemImplied : isSingleItemImplied , visitor : VisitorList ( visitors ) , sources : visitors }
}
func ( b * Builder ) visitByPaths ( ) * Result {
singleItemImplied := ! b . dir && ! b . stream && len ( b . paths ) == 1
if len ( b . resources ) != 0 {
return & Result { singleItemImplied : singleItemImplied , err : fmt . Errorf ( "when paths, URLs, or stdin is provided as input, you may not specify resource arguments as well" ) }
}
if len ( b . names ) != 0 {
return & Result { err : fmt . Errorf ( "name cannot be provided when a path is specified" ) }
}
if len ( b . resourceTuples ) != 0 {
return & Result { err : fmt . Errorf ( "resource/name arguments cannot be provided when a path is specified" ) }
}
var visitors Visitor
if b . continueOnError {
visitors = EagerVisitorList ( b . paths )
} else {
visitors = VisitorList ( b . paths )
}
// only items from disk can be refetched
if b . latest {
// must flatten lists prior to fetching
if b . flatten {
visitors = NewFlattenListVisitor ( visitors , b . mapper )
}
// must set namespace prior to fetching
if b . defaultNamespace {
visitors = NewDecoratedVisitor ( visitors , SetNamespace ( b . namespace ) )
}
visitors = NewDecoratedVisitor ( visitors , RetrieveLatest )
}
if b . selector != nil {
visitors = NewFilteredVisitor ( visitors , FilterBySelector ( b . selector ) )
}
return & Result { singleItemImplied : singleItemImplied , visitor : visitors , sources : b . paths }
}
// Do returns a Result object with a Visitor for the resources identified by the Builder.
// The visitor will respect the error behavior specified by ContinueOnError. Note that stream
// inputs are consumed by the first execution - use Infos() or Object() on the Result to capture a list
// for further iteration.
func ( b * Builder ) Do ( ) * Result {
r := b . visitorResult ( )
if r . err != nil {
return r
}
if b . flatten {
r . visitor = NewFlattenListVisitor ( r . visitor , b . mapper )
}
helpers := [ ] VisitorFunc { }
if b . defaultNamespace {
helpers = append ( helpers , SetNamespace ( b . namespace ) )
}
if b . requireNamespace {
helpers = append ( helpers , RequireNamespace ( b . namespace ) )
}
helpers = append ( helpers , FilterNamespace )
if b . requireObject {
helpers = append ( helpers , RetrieveLazy )
}
r . visitor = NewDecoratedVisitor ( r . visitor , helpers ... )
if b . continueOnError {
r . visitor = ContinueOnErrorVisitor { r . visitor }
}
return r
}
// SplitResourceArgument splits the argument with commas and returns unique
// strings in the original order.
func SplitResourceArgument ( arg string ) [ ] string {
out := [ ] string { }
set := sets . NewString ( )
for _ , s := range strings . Split ( arg , "," ) {
if set . Has ( s ) {
continue
}
set . Insert ( s )
out = append ( out , s )
}
return out
}
// HasNames returns true if the provided args contain resource names
func HasNames ( args [ ] string ) ( bool , error ) {
args = normalizeMultipleResourcesArgs ( args )
hasCombinedTypes , err := hasCombinedTypeArgs ( args )
if err != nil {
return false , err
}
return hasCombinedTypes || len ( args ) > 1 , nil
}
// MultipleTypesRequested returns true if the provided args contain multiple resource kinds
func MultipleTypesRequested ( args [ ] string ) bool {
args = normalizeMultipleResourcesArgs ( args )
rKinds := sets . NewString ( )
for _ , arg := range args {
if arg == "all" {
return true
}
rTuple , found , err := splitResourceTypeName ( arg )
if err != nil {
continue
}
// if tuple not found, assume arg is of the form "type1,type2,...".
// Since SplitResourceArgument returns a unique list of kinds,
// return true here if len(uniqueList) > 1
if ! found {
if strings . Contains ( arg , "," ) {
splitArgs := SplitResourceArgument ( arg )
if len ( splitArgs ) > 1 {
return true
}
}
continue
}
if rKinds . Has ( rTuple . Resource ) {
continue
}
rKinds . Insert ( rTuple . Resource )
}
return ( rKinds . Len ( ) > 1 )
}