Commit 6e5b455b authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #39038 from ncdc/fix-kubectl-get-list

Automatic merge from submit-queue Fix kubectl get -f <file> -o <nondefault printer> so it prints all items in the file **What this PR does / why we need it**: Fix kubectl get -f <file> -o <nondefault printer> so it prints all the objects in the file, instead of just the first one. Also add a test for this feature. **Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes #38907 **Special notes for your reviewer**: **Release note**: ```release-note ``` cc @AdoHe @deads2k @liggitt @fabianofranz @kubernetes/kubectl @kubernetes/sig-cli-misc
parents 55bee3ad 613ada4c
...@@ -1072,6 +1072,20 @@ run_kubectl_get_tests() { ...@@ -1072,6 +1072,20 @@ run_kubectl_get_tests() {
# cleanup # cleanup
kubectl delete pods valid-pod "${kube_flags[@]}" kubectl delete pods valid-pod "${kube_flags[@]}"
### Test 'kubectl get -f <file> -o <non default printer>' prints all the items in the file's list
# Pre-condition: no POD exists
kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" ''
# Command
kubectl create -f test/fixtures/doc-yaml/user-guide/multi-pod.yaml "${kube_flags[@]}"
# Post-condition: PODs redis-master and redis-proxy exist
# Check that all items in the list are printed
output_message=$(kubectl get -f test/fixtures/doc-yaml/user-guide/multi-pod.yaml -o jsonpath="{..metadata.name}" "${kube_flags[@]}")
kube::test::if_has_string "${output_message}" "redis-master redis-proxy"
# cleanup
kubectl delete pods redis-master redis-proxy "${kube_flags[@]}"
} }
run_kubectl_request_timeout_tests() { run_kubectl_request_timeout_tests() {
......
...@@ -197,14 +197,14 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro ...@@ -197,14 +197,14 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro
return err return err
} }
var singularResource bool var singleItemImpliedResource bool
r.IntoSingular(&singularResource) r.IntoSingleItemImplied(&singleItemImpliedResource)
// only apply resource version locking on a single resource. // only apply resource version locking on a single resource.
// we must perform this check after o.builder.Do() as // we must perform this check after o.builder.Do() as
// []o.resources can not not accurately return the proper number // []o.resources can not not accurately return the proper number
// of resources when they are not passed in "resource/name" format. // of resources when they are not passed in "resource/name" format.
if !singularResource && len(o.resourceVersion) > 0 { if !singleItemImpliedResource && len(o.resourceVersion) > 0 {
return fmt.Errorf("--resource-version may only be used with a single resource") return fmt.Errorf("--resource-version may only be used with a single resource")
} }
......
...@@ -162,8 +162,8 @@ func (o *ConvertOptions) RunConvert() error { ...@@ -162,8 +162,8 @@ func (o *ConvertOptions) RunConvert() error {
return err return err
} }
singular := false singleItemImplied := false
infos, err := r.IntoSingular(&singular).Infos() infos, err := r.IntoSingleItemImplied(&singleItemImplied).Infos()
if err != nil { if err != nil {
return err return err
} }
...@@ -172,7 +172,7 @@ func (o *ConvertOptions) RunConvert() error { ...@@ -172,7 +172,7 @@ func (o *ConvertOptions) RunConvert() error {
return fmt.Errorf("no objects passed to convert") return fmt.Errorf("no objects passed to convert")
} }
objects, err := resource.AsVersionedObject(infos, !singular, o.outputVersion, o.encoder) objects, err := resource.AsVersionedObject(infos, !singleItemImplied, o.outputVersion, o.encoder)
if err != nil { if err != nil {
return err return err
} }
......
...@@ -307,10 +307,10 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [ ...@@ -307,10 +307,10 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [
// 2. if there is a single item and that item is a list, leave it as its specific list // 2. if there is a single item and that item is a list, leave it as its specific list
// 3. if there is a single item and it is not a a list, leave it as a single item // 3. if there is a single item and it is not a a list, leave it as a single item
var errs []error var errs []error
singular := false singleItemImplied := false
infos, err := r.IntoSingular(&singular).Infos() infos, err := r.IntoSingleItemImplied(&singleItemImplied).Infos()
if err != nil { if err != nil {
if singular { if singleItemImplied {
return err return err
} }
errs = append(errs, err) errs = append(errs, err)
...@@ -325,9 +325,7 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [ ...@@ -325,9 +325,7 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [
} }
var obj runtime.Object var obj runtime.Object
if singular { if !singleItemImplied || len(infos) > 1 {
obj = infos[0].Object
} else {
// we have more than one item, so coerce all items into a list // we have more than one item, so coerce all items into a list
list := &unstructured.UnstructuredList{ list := &unstructured.UnstructuredList{
Object: map[string]interface{}{ Object: map[string]interface{}{
...@@ -340,6 +338,8 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [ ...@@ -340,6 +338,8 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [
list.Items = append(list.Items, info.Object.(*unstructured.Unstructured)) list.Items = append(list.Items, info.Object.(*unstructured.Unstructured))
} }
obj = list obj = list
} else {
obj = infos[0].Object
} }
isList := meta.IsListType(obj) isList := meta.IsListType(obj)
......
...@@ -187,7 +187,7 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error { ...@@ -187,7 +187,7 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error {
Latest() Latest()
} }
one := false one := false
r := b.Do().IntoSingular(&one) r := b.Do().IntoSingleItemImplied(&one)
if err := r.Err(); err != nil { if err := r.Err(); err != nil {
return err return err
} }
......
...@@ -72,7 +72,7 @@ type Builder struct { ...@@ -72,7 +72,7 @@ type Builder struct {
singleResourceType bool singleResourceType bool
continueOnError bool continueOnError bool
singular bool singleItemImplied bool
export bool export bool
...@@ -139,7 +139,7 @@ func (b *Builder) FilenameParam(enforceNamespace bool, filenameOptions *Filename ...@@ -139,7 +139,7 @@ func (b *Builder) FilenameParam(enforceNamespace bool, filenameOptions *Filename
b.URL(defaultHttpGetAttempts, url) b.URL(defaultHttpGetAttempts, url)
default: default:
if !recursive { if !recursive {
b.singular = true b.singleItemImplied = true
} }
b.Path(recursive, s) b.Path(recursive, s)
} }
...@@ -600,21 +600,21 @@ func (b *Builder) visitBySelector() *Result { ...@@ -600,21 +600,21 @@ func (b *Builder) visitBySelector() *Result {
} }
func (b *Builder) visitByResource() *Result { func (b *Builder) visitByResource() *Result {
// if b.singular is false, this could be by default, so double-check length // if b.singleItemImplied is false, this could be by default, so double-check length
// of resourceTuples to determine if in fact it is singular or not // of resourceTuples to determine if in fact it is singleItemImplied or not
isSingular := b.singular isSingleItemImplied := b.singleItemImplied
if !isSingular { if !isSingleItemImplied {
isSingular = len(b.resourceTuples) == 1 isSingleItemImplied = len(b.resourceTuples) == 1
} }
if len(b.resources) != 0 { if len(b.resources) != 0 {
return &Result{singular: isSingular, err: fmt.Errorf("you may not specify individual resources and bulk resources in the same call")} 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 // retrieve one client for each resource
mappings, err := b.resourceTupleMappings() mappings, err := b.resourceTupleMappings()
if err != nil { if err != nil {
return &Result{singular: isSingular, err: err} return &Result{singleItemImplied: isSingleItemImplied, err: err}
} }
clients := make(map[string]RESTClient) clients := make(map[string]RESTClient)
for _, mapping := range mappings { for _, mapping := range mappings {
...@@ -633,12 +633,12 @@ func (b *Builder) visitByResource() *Result { ...@@ -633,12 +633,12 @@ func (b *Builder) visitByResource() *Result {
for _, tuple := range b.resourceTuples { for _, tuple := range b.resourceTuples {
mapping, ok := mappings[tuple.Resource] mapping, ok := mappings[tuple.Resource]
if !ok { if !ok {
return &Result{singular: isSingular, err: fmt.Errorf("resource %q is not recognized: %v", tuple.Resource, mappings)} 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) s := fmt.Sprintf("%s/%s", mapping.GroupVersionKind.GroupVersion().String(), mapping.Resource)
client, ok := clients[s] client, ok := clients[s]
if !ok { if !ok {
return &Result{singular: isSingular, err: fmt.Errorf("could not find a client for resource %q", tuple.Resource)} return &Result{singleItemImplied: isSingleItemImplied, err: fmt.Errorf("could not find a client for resource %q", tuple.Resource)}
} }
selectorNamespace := b.namespace selectorNamespace := b.namespace
...@@ -650,7 +650,7 @@ func (b *Builder) visitByResource() *Result { ...@@ -650,7 +650,7 @@ func (b *Builder) visitByResource() *Result {
if b.allNamespace { if b.allNamespace {
errMsg = "a resource cannot be retrieved by name across all namespaces" errMsg = "a resource cannot be retrieved by name across all namespaces"
} }
return &Result{singular: isSingular, err: fmt.Errorf(errMsg)} return &Result{singleItemImplied: isSingleItemImplied, err: fmt.Errorf(errMsg)}
} }
} }
...@@ -664,25 +664,25 @@ func (b *Builder) visitByResource() *Result { ...@@ -664,25 +664,25 @@ func (b *Builder) visitByResource() *Result {
} else { } else {
visitors = VisitorList(items) visitors = VisitorList(items)
} }
return &Result{singular: isSingular, visitor: visitors, sources: items} return &Result{singleItemImplied: isSingleItemImplied, visitor: visitors, sources: items}
} }
func (b *Builder) visitByName() *Result { func (b *Builder) visitByName() *Result {
isSingular := len(b.names) == 1 isSingleItemImplied := len(b.names) == 1
if len(b.paths) != 0 { if len(b.paths) != 0 {
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")} 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 { if len(b.resources) == 0 {
return &Result{singular: isSingular, err: fmt.Errorf("you must provide a resource and a resource name together")} return &Result{singleItemImplied: isSingleItemImplied, err: fmt.Errorf("you must provide a resource and a resource name together")}
} }
if len(b.resources) > 1 { if len(b.resources) > 1 {
return &Result{singular: isSingular, err: fmt.Errorf("you must specify only one resource")} return &Result{singleItemImplied: isSingleItemImplied, err: fmt.Errorf("you must specify only one resource")}
} }
mappings, err := b.resourceMappings() mappings, err := b.resourceMappings()
if err != nil { if err != nil {
return &Result{singular: isSingular, err: err} return &Result{singleItemImplied: isSingleItemImplied, err: err}
} }
mapping := mappings[0] mapping := mappings[0]
...@@ -700,7 +700,7 @@ func (b *Builder) visitByName() *Result { ...@@ -700,7 +700,7 @@ func (b *Builder) visitByName() *Result {
if b.allNamespace { if b.allNamespace {
errMsg = "a resource cannot be retrieved by name across all namespaces" errMsg = "a resource cannot be retrieved by name across all namespaces"
} }
return &Result{singular: isSingular, err: fmt.Errorf(errMsg)} return &Result{singleItemImplied: isSingleItemImplied, err: fmt.Errorf(errMsg)}
} }
} }
...@@ -709,13 +709,13 @@ func (b *Builder) visitByName() *Result { ...@@ -709,13 +709,13 @@ func (b *Builder) visitByName() *Result {
info := NewInfo(client, mapping, selectorNamespace, name, b.export) info := NewInfo(client, mapping, selectorNamespace, name, b.export)
visitors = append(visitors, info) visitors = append(visitors, info)
} }
return &Result{singular: isSingular, visitor: VisitorList(visitors), sources: visitors} return &Result{singleItemImplied: isSingleItemImplied, visitor: VisitorList(visitors), sources: visitors}
} }
func (b *Builder) visitByPaths() *Result { func (b *Builder) visitByPaths() *Result {
singular := !b.dir && !b.stream && len(b.paths) == 1 singleItemImplied := !b.dir && !b.stream && len(b.paths) == 1
if len(b.resources) != 0 { 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")} 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 { if len(b.names) != 0 {
return &Result{err: fmt.Errorf("name cannot be provided when a path is specified")} return &Result{err: fmt.Errorf("name cannot be provided when a path is specified")}
...@@ -746,7 +746,7 @@ func (b *Builder) visitByPaths() *Result { ...@@ -746,7 +746,7 @@ func (b *Builder) visitByPaths() *Result {
if b.selector != nil { if b.selector != nil {
visitors = NewFilteredVisitor(visitors, FilterBySelector(b.selector)) visitors = NewFilteredVisitor(visitors, FilterBySelector(b.selector))
} }
return &Result{singular: singular, visitor: visitors, sources: b.paths} return &Result{singleItemImplied: singleItemImplied, visitor: visitors, sources: b.paths}
} }
// Do returns a Result object with a Visitor for the resources identified by the Builder. // Do returns a Result object with a Visitor for the resources identified by the Builder.
......
...@@ -40,8 +40,8 @@ type Result struct { ...@@ -40,8 +40,8 @@ type Result struct {
err error err error
visitor Visitor visitor Visitor
sources []Visitor sources []Visitor
singular bool singleItemImplied bool
ignoreErrors []utilerrors.Matcher ignoreErrors []utilerrors.Matcher
...@@ -82,10 +82,10 @@ func (r *Result) Visit(fn VisitorFunc) error { ...@@ -82,10 +82,10 @@ func (r *Result) Visit(fn VisitorFunc) error {
return utilerrors.FilterOut(err, r.ignoreErrors...) return utilerrors.FilterOut(err, r.ignoreErrors...)
} }
// IntoSingular sets the provided boolean pointer to true if the Builder input // IntoSingleItemImplied sets the provided boolean pointer to true if the Builder input
// reflected a single item, or multiple. // implies a single item, or multiple.
func (r *Result) IntoSingular(b *bool) *Result { func (r *Result) IntoSingleItemImplied(b *bool) *Result {
*b = r.singular *b = r.singleItemImplied
return r return r
} }
...@@ -136,7 +136,7 @@ func (r *Result) Object() (runtime.Object, error) { ...@@ -136,7 +136,7 @@ func (r *Result) Object() (runtime.Object, error) {
} }
if len(objects) == 1 { if len(objects) == 1 {
if r.singular { if r.singleItemImplied {
return objects[0], nil return objects[0], nil
} }
// if the item is a list already, don't create another list // if the item is a list already, don't create another list
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment