From ecbb91cc085146ea4041914694579144c3cf0370 Mon Sep 17 00:00:00 2001 From: Karl Beecher Date: Thu, 19 Feb 2015 15:17:23 +0100 Subject: [PATCH] Adds support for multiple resources to kubectl You can specify multiple resources by name when using the delete, get and stop commands. --- hack/test-cmd.sh | 21 +++++++++ pkg/kubectl/resource/builder.go | 76 +++++++++++++++++++-------------- 2 files changed, 66 insertions(+), 31 deletions(-) diff --git a/hack/test-cmd.sh b/hack/test-cmd.sh index 683b1837e70..d5dc8832785 100755 --- a/hack/test-cmd.sh +++ b/hack/test-cmd.sh @@ -153,6 +153,15 @@ for version in "${kube_api_versions[@]}"; do kubectl get pods "${kube_flags[@]}" -lname=redis-master | grep -q 'redis-master' [ ! $(delete pods --all pods -l name=redis-master) ] # not --all and label selector together kubectl delete --all pods "${kube_flags[@]}" # --all remove all the pods + kubectl create -f examples/guestbook/redis-master.json "${kube_flags[@]}" + kubectl create -f examples/redis/redis-proxy.yaml "${kube_flags[@]}" + kubectl get pods redis-master redis-proxy "${kube_flags[@]}" + kubectl delete pods redis-master redis-proxy # delete multiple pods at once + howmanypods="$(kubectl get pods -o template -t "{{ len .items }}" "${kube_flags[@]}")" + [ "$howmanypods" -eq 0 ] + kubectl create -f examples/guestbook/redis-master.json "${kube_flags[@]}" + kubectl create -f examples/redis/redis-proxy.yaml "${kube_flags[@]}" + kubectl stop pods redis-master redis-proxy # stop multiple pods at once howmanypods="$(kubectl get pods -o template -t "{{ len .items }}" "${kube_flags[@]}")" [ "$howmanypods" -eq 0 ] @@ -185,6 +194,12 @@ __EOF__ kubectl get services "${kube_flags[@]}" kubectl get services "service-${version}-test" "${kube_flags[@]}" kubectl delete service frontend "${kube_flags[@]}" + servicesbefore="$(kubectl get services -o template -t "{{ len .items }}" "${kube_flags[@]}")" + kubectl create -f examples/guestbook/frontend-service.json "${kube_flags[@]}" + kubectl create -f examples/guestbook/redis-slave-service.json "${kube_flags[@]}" + kubectl delete services frontend redisslave # delete multiple services at once + servicesafter="$(kubectl get services -o template -t "{{ len .items }}" "${kube_flags[@]}")" + [ "$((${servicesafter} - ${servicesbefore}))" -eq 0 ] kube::log::status "Testing kubectl(${version}:replicationcontrollers)" kubectl get replicationcontrollers "${kube_flags[@]}" @@ -192,6 +207,12 @@ __EOF__ kubectl get replicationcontrollers "${kube_flags[@]}" kubectl describe replicationcontroller frontend-controller "${kube_flags[@]}" | grep -q 'Replicas:.*3 desired' kubectl delete rc frontend-controller "${kube_flags[@]}" + rcsbefore="$(kubectl get replicationcontrollers -o template -t "{{ len .items }}" "${kube_flags[@]}")" + kubectl create -f examples/guestbook/frontend-controller.json "${kube_flags[@]}" + kubectl create -f examples/guestbook/redis-slave-controller.json "${kube_flags[@]}" + kubectl delete rc frontend-controller redis-slave-controller "${kube_flags[@]}" # delete multiple controllers at once + rcsafter="$(kubectl get replicationcontrollers -o template -t "{{ len .items }}" "${kube_flags[@]}")" + [ "$((${rcsafter} - ${rcsbefore}))" -eq 0 ] kube::log::status "Testing kubectl(${version}:nodes)" kubectl get nodes "${kube_flags[@]}" diff --git a/pkg/kubectl/resource/builder.go b/pkg/kubectl/resource/builder.go index 10dadfdcb2d..39ec285b849 100644 --- a/pkg/kubectl/resource/builder.go +++ b/pkg/kubectl/resource/builder.go @@ -48,7 +48,7 @@ type Builder struct { resources []string namespace string - name string + names []string defaultNamespace bool requireNamespace bool @@ -217,22 +217,25 @@ func (b *Builder) SelectAllParam(selectAll bool) *Builder { return b } -// ResourceTypeOrNameArgs indicates that the builder should accept one or two arguments -// of the form `([,,...]| )`. 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. The allowEmptySelector permits to select all the resources (via Everything func). +// ResourceTypeOrNameArgs indicates that the builder should accept arguments +// of the form `([,,...]| [,,...])`. 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 { - switch len(args) { - case 2: - b.name = args[1] + switch { + case len(args) > 2: + b.names = append(b.names, args[1:]...) b.ResourceTypes(SplitResourceArgument(args[0])...) - case 1: + 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 0: + case len(args) == 0: default: b.errs = append(b.errs, fmt.Errorf("when passing arguments, must be resource or resource and name")) } @@ -244,7 +247,7 @@ func (b *Builder) ResourceTypeOrNameArgs(allowEmptySelector bool, args ...string func (b *Builder) ResourceTypeAndNameArgs(args ...string) *Builder { switch len(args) { case 2: - b.name = args[1] + b.names = append(b.names, args[1]) b.ResourceTypes(SplitResourceArgument(args[0])...) case 0: default: @@ -312,7 +315,7 @@ func (b *Builder) visitorResult() *Result { // visit selectors if b.selector != nil { - if len(b.name) != 0 { + if len(b.names) != 0 { return &Result{err: fmt.Errorf("name cannot be provided when a selector is specified")} } if len(b.resources) == 0 { @@ -349,38 +352,49 @@ func (b *Builder) visitorResult() *Result { return &Result{visitor: VisitorList(visitors), sources: visitors} } - // visit single item specified by name - if len(b.name) != 0 { + // visit items specified by name + if len(b.names) != 0 { + isSingular := len(b.names) == 1 + 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")} + return &Result{singular: isSingular, 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")} + return &Result{singular: isSingular, 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")} + return &Result{singular: isSingular, err: fmt.Errorf("you must specify only one resource")} } + mappings, err := b.resourceMappings() if err != nil { - return &Result{singular: true, err: err} + return &Result{singular: isSingular, err: err} } mapping := mappings[0] - if mapping.Scope.Name() != meta.RESTScopeNameNamespace { - b.namespace = "" - } else { - if len(b.namespace) == 0 { - return &Result{singular: true, err: fmt.Errorf("namespace may not be empty when retrieving a resource by name")} - } - } + client, err := b.mapper.ClientForMapping(mapping) if err != nil { - return &Result{singular: true, err: err} + return &Result{err: err} } - info := NewInfo(client, mappings[0], b.namespace, b.name) - if err := info.Get(); err != nil { - return &Result{singular: true, err: err} + + selectorNamespace := b.namespace + if mapping.Scope.Name() != meta.RESTScopeNameNamespace { + selectorNamespace = "" + } else { + if len(b.namespace) == 0 { + return &Result{singular: isSingular, err: fmt.Errorf("namespace may not be empty when retrieving a resource by name")} + } } - return &Result{singular: true, visitor: info, sources: []Visitor{info}} + + visitors := []Visitor{} + for _, name := range b.names { + info := NewInfo(client, mapping, selectorNamespace, name) + if err := info.Get(); err != nil { + return &Result{singular: isSingular, err: err} + } + visitors = append(visitors, info) + } + return &Result{singular: isSingular, visitor: VisitorList(visitors), sources: visitors} } // visit items specified by paths