Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automated cherry pick of #39486 #39895 #39038 #39954

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 41 additions & 0 deletions hack/make-rules/test-cmd.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1251,6 +1251,47 @@ __EOF__
kube::test::if_has_string "${output_message}" "/apis/extensions/v1beta1/namespaces/default/deployments 200 OK"
kube::test::if_has_string "${output_message}" "/apis/extensions/v1beta1/namespaces/default/replicasets 200 OK"

### Test --allow-missing-template-keys
# Pre-condition: no POD exists
create_and_use_new_namespace
kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" ''
# Command
kubectl create -f test/fixtures/doc-yaml/admin/limitrange/valid-pod.yaml "${kube_flags[@]}"
# Post-condition: valid-pod POD is created
kubectl get "${kube_flags[@]}" pods -o json
kube::test::get_object_assert pods "{{range.items}}{{$id_field}}:{{end}}" 'valid-pod:'

## check --allow-missing-template-keys defaults to true for jsonpath templates
kubectl get "${kube_flags[@]}" pod valid-pod -o jsonpath='{.missing}'

## check --allow-missing-template-keys defaults to true for go templates
kubectl get "${kube_flags[@]}" pod valid-pod -o go-template='{{.missing}}'

## check --allow-missing-template-keys=false results in an error for a missing key with jsonpath
output_message=$(! kubectl get pod valid-pod --allow-missing-template-keys=false -o jsonpath='{.missing}' "${kube_flags[@]}")
kube::test::if_has_string "${output_message}" 'missing is not found'

## check --allow-missing-template-keys=false results in an error for a missing key with go
output_message=$(! kubectl get pod valid-pod --allow-missing-template-keys=false -o go-template='{{.missing}}' "${kube_flags[@]}")
kube::test::if_has_string "${output_message}" 'map has no entry for key "missing"'

# cleanup
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[@]}"


##################
# Global timeout #
Expand Down
1 change: 1 addition & 0 deletions hack/verify-flags/known-flags.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ advertised-address
algorithm-provider
all-namespaces
allocate-node-cidrs
allow-missing-template-keys
allow-privileged
allowed-not-ready-nodes
anonymous-auth
Expand Down
6 changes: 3 additions & 3 deletions pkg/kubectl/cmd/annotate.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,14 +196,14 @@ func (o AnnotateOptions) RunAnnotate(f cmdutil.Factory, cmd *cobra.Command) erro
return err
}

var singularResource bool
r.IntoSingular(&singularResource)
var singleItemImpliedResource bool
r.IntoSingleItemImplied(&singleItemImpliedResource)

// only apply resource version locking on a single resource.
// we must perform this check after o.builder.Do() as
// []o.resources can not not accurately return the proper number
// 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")
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/kubectl/cmd/clusterinfo_dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func dumpClusterInfo(f cmdutil.Factory, cmd *cobra.Command, args []string, out i
return err
}

printer, _, err := kubectl.GetPrinter("json", "", false)
printer, _, err := kubectl.GetPrinter("json", "", false, true)
if err != nil {
return err
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/kubectl/cmd/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ func (o *ConvertOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.C
}
}
o.encoder = f.JSONEncoder()
o.printer, _, err = kubectl.GetPrinter(outputFormat, templateFile, false)
o.printer, _, err = kubectl.GetPrinter(outputFormat, templateFile, false, cmdutil.GetFlagBool(cmd, "allow-missing-template-keys"))
if err != nil {
return err
}
Expand All @@ -162,8 +162,8 @@ func (o *ConvertOptions) RunConvert() error {
return err
}

singular := false
infos, err := r.IntoSingular(&singular).Infos()
singleItemImplied := false
infos, err := r.IntoSingleItemImplied(&singleItemImplied).Infos()
if err != nil {
return err
}
Expand All @@ -172,7 +172,7 @@ func (o *ConvertOptions) RunConvert() error {
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 {
return err
}
Expand Down
12 changes: 6 additions & 6 deletions pkg/kubectl/cmd/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,10 +310,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
// 3. if there is a single item and it is not a a list, leave it as a single item
var errs []error
singular := false
infos, err := r.IntoSingular(&singular).Infos()
singleItemImplied := false
infos, err := r.IntoSingleItemImplied(&singleItemImplied).Infos()
if err != nil {
if singular {
if singleItemImplied {
return err
}
errs = append(errs, err)
Expand All @@ -328,9 +328,7 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [
}

var obj runtime.Object
if singular {
obj = infos[0].Object
} else {
if !singleItemImplied || len(infos) > 1 {
// we have more than one item, so coerce all items into a list
list := &runtime.UnstructuredList{
Object: map[string]interface{}{
Expand All @@ -343,6 +341,8 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [
list.Items = append(list.Items, info.Object.(*runtime.Unstructured))
}
obj = list
} else {
obj = infos[0].Object
}

isList := meta.IsListType(obj)
Expand Down
2 changes: 1 addition & 1 deletion pkg/kubectl/cmd/label.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ func (o *LabelOptions) RunLabel(f cmdutil.Factory, cmd *cobra.Command) error {
Latest()
}
one := false
r := b.Do().IntoSingular(&one)
r := b.Do().IntoSingleItemImplied(&one)
if err := r.Err(); err != nil {
return err
}
Expand Down
9 changes: 8 additions & 1 deletion pkg/kubectl/cmd/util/printing.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func AddOutputFlagsForMutation(cmd *cobra.Command) {
// AddOutputFlags adds output related flags to a command.
func AddOutputFlags(cmd *cobra.Command) {
cmd.Flags().StringP("output", "o", "", "Output format. One of: json|yaml|wide|name|custom-columns=...|custom-columns-file=...|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=... See custom columns [http://kubernetes.io/docs/user-guide/kubectl-overview/#custom-columns], golang template [http://golang.org/pkg/text/template/#pkg-overview] and jsonpath template [http://kubernetes.io/docs/user-guide/jsonpath].")
cmd.Flags().Bool("allow-missing-template-keys", true, "If true, ignore any errors in templates when a field or map key is missing in the template. Only applies to golang and jsonpath output formats.")
}

// AddNoHeadersFlags adds no-headers flags to a command.
Expand Down Expand Up @@ -125,7 +126,13 @@ func PrinterForCommand(cmd *cobra.Command) (kubectl.ResourcePrinter, bool, error
}
}

printer, generic, err := kubectl.GetPrinter(outputFormat, templateFile, GetFlagBool(cmd, "no-headers"))
// this function may be invoked by a command that did not call AddPrinterFlags first, so we need
// to be safe about how we access the allow-missing-template-keys flag
allowMissingTemplateKeys := false
if cmd.Flags().Lookup("allow-missing-template-keys") != nil {
allowMissingTemplateKeys = GetFlagBool(cmd, "allow-missing-template-keys")
}
printer, generic, err := kubectl.GetPrinter(outputFormat, templateFile, GetFlagBool(cmd, "no-headers"), allowMissingTemplateKeys)
if err != nil {
return nil, generic, err
}
Expand Down
46 changes: 23 additions & 23 deletions pkg/kubectl/resource/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ type Builder struct {
singleResourceType bool
continueOnError bool

singular bool
singleItemImplied bool

export bool

Expand Down Expand Up @@ -139,7 +139,7 @@ func (b *Builder) FilenameParam(enforceNamespace bool, filenameOptions *Filename
b.URL(defaultHttpGetAttempts, url)
default:
if !recursive {
b.singular = true
b.singleItemImplied = true
}
b.Path(recursive, s)
}
Expand Down Expand Up @@ -600,21 +600,21 @@ func (b *Builder) visitBySelector() *Result {
}

func (b *Builder) visitByResource() *Result {
// if b.singular is false, this could be by default, so double-check length
// of resourceTuples to determine if in fact it is singular or not
isSingular := b.singular
if !isSingular {
isSingular = len(b.resourceTuples) == 1
// 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{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
mappings, err := b.resourceTupleMappings()
if err != nil {
return &Result{singular: isSingular, err: err}
return &Result{singleItemImplied: isSingleItemImplied, err: err}
}
clients := make(map[string]RESTClient)
for _, mapping := range mappings {
Expand All @@ -633,12 +633,12 @@ func (b *Builder) visitByResource() *Result {
for _, tuple := range b.resourceTuples {
mapping, ok := mappings[tuple.Resource]
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)
client, ok := clients[s]
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
Expand All @@ -650,7 +650,7 @@ func (b *Builder) visitByResource() *Result {
if b.allNamespace {
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)}
}
}

Expand All @@ -664,25 +664,25 @@ func (b *Builder) visitByResource() *Result {
} else {
visitors = VisitorList(items)
}
return &Result{singular: isSingular, visitor: visitors, sources: items}
return &Result{singleItemImplied: isSingleItemImplied, visitor: visitors, sources: items}
}

func (b *Builder) visitByName() *Result {
isSingular := len(b.names) == 1
isSingleItemImplied := len(b.names) == 1

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 {
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 {
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()
if err != nil {
return &Result{singular: isSingular, err: err}
return &Result{singleItemImplied: isSingleItemImplied, err: err}
}
mapping := mappings[0]

Expand All @@ -700,7 +700,7 @@ func (b *Builder) visitByName() *Result {
if b.allNamespace {
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)}
}
}

Expand All @@ -709,13 +709,13 @@ func (b *Builder) visitByName() *Result {
info := NewInfo(client, mapping, selectorNamespace, name, b.export)
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 {
singular := !b.dir && !b.stream && len(b.paths) == 1
singleItemImplied := !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")}
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")}
Expand Down Expand Up @@ -746,7 +746,7 @@ func (b *Builder) visitByPaths() *Result {
if b.selector != nil {
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.
Expand Down