Merge pull request #99004 from smarterclayton/simplify_debug

kubectl: exec and attach break scripting and should honor `--quiet`
This commit is contained in:
Kubernetes Prow Robot 2021-03-05 11:21:53 -08:00 committed by GitHub
commit 9cc3665bd3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 191 additions and 131 deletions

View File

@ -23,7 +23,6 @@ import (
"time" "time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/klog/v2"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -33,6 +32,7 @@ import (
"k8s.io/client-go/tools/remotecommand" "k8s.io/client-go/tools/remotecommand"
"k8s.io/kubectl/pkg/cmd/exec" "k8s.io/kubectl/pkg/cmd/exec"
cmdutil "k8s.io/kubectl/pkg/cmd/util" cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/cmd/util/podcmd"
"k8s.io/kubectl/pkg/polymorphichelpers" "k8s.io/kubectl/pkg/polymorphichelpers"
"k8s.io/kubectl/pkg/scheme" "k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/i18n"
@ -68,9 +68,7 @@ type AttachOptions struct {
// whether to disable use of standard error when streaming output from tty // whether to disable use of standard error when streaming output from tty
DisableStderr bool DisableStderr bool
CommandName string CommandName string
ParentCommandName string
EnableSuggestedCmdUsage bool
Pod *corev1.Pod Pod *corev1.Pod
@ -115,6 +113,7 @@ func NewCmdAttach(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra
cmd.Flags().StringVarP(&o.ContainerName, "container", "c", o.ContainerName, "Container name. If omitted, the first container in the pod will be chosen") cmd.Flags().StringVarP(&o.ContainerName, "container", "c", o.ContainerName, "Container name. If omitted, the first container in the pod will be chosen")
cmd.Flags().BoolVarP(&o.Stdin, "stdin", "i", o.Stdin, "Pass stdin to the container") cmd.Flags().BoolVarP(&o.Stdin, "stdin", "i", o.Stdin, "Pass stdin to the container")
cmd.Flags().BoolVarP(&o.TTY, "tty", "t", o.TTY, "Stdin is a TTY") cmd.Flags().BoolVarP(&o.TTY, "tty", "t", o.TTY, "Stdin is a TTY")
cmd.Flags().BoolVarP(&o.Quiet, "quiet", "q", o.Quiet, "Only print output from the remote session")
return cmd return cmd
} }
@ -184,14 +183,6 @@ func (o *AttachOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []s
o.Resources = args o.Resources = args
o.restClientGetter = f o.restClientGetter = f
cmdParent := cmd.Parent()
if cmdParent != nil {
o.ParentCommandName = cmdParent.CommandPath()
}
if len(o.ParentCommandName) > 0 && cmdutil.IsSiblingCommandExists(cmd, "describe") {
o.EnableSuggestedCmdUsage = true
}
config, err := f.ToRESTConfig() config, err := f.ToRESTConfig()
if err != nil { if err != nil {
return err return err
@ -257,8 +248,8 @@ func (o *AttachOptions) Run() error {
} }
if o.TTY && !containerToAttach.TTY { if o.TTY && !containerToAttach.TTY {
o.TTY = false o.TTY = false
if o.ErrOut != nil { if !o.Quiet && o.ErrOut != nil {
fmt.Fprintf(o.ErrOut, "Unable to use a TTY - container %s did not allocate one\n", containerToAttach.Name) fmt.Fprintf(o.ErrOut, "error: Unable to use a TTY - container %s did not allocate one\n", containerToAttach.Name)
} }
} else if !o.TTY && containerToAttach.TTY { } else if !o.TTY && containerToAttach.TTY {
// the container was launched with a TTY, so we have to force a TTY here, otherwise you'll get // the container was launched with a TTY, so we have to force a TTY here, otherwise you'll get
@ -292,7 +283,7 @@ func (o *AttachOptions) Run() error {
return err return err
} }
if o.Stdin && t.Raw && o.Pod.Spec.RestartPolicy == corev1.RestartPolicyAlways { if !o.Quiet && o.Stdin && t.Raw && o.Pod.Spec.RestartPolicy == corev1.RestartPolicyAlways {
fmt.Fprintf(o.Out, "Session ended, resume using '%s %s -c %s -i -t' command when the pod is running\n", o.CommandName, o.Pod.Name, containerToAttach.Name) fmt.Fprintf(o.Out, "Session ended, resume using '%s %s -c %s -i -t' command when the pod is running\n", o.CommandName, o.Pod.Name, containerToAttach.Name)
} }
return nil return nil
@ -311,32 +302,7 @@ func (o *AttachOptions) findAttachablePod(obj runtime.Object) (*corev1.Pod, erro
// containerToAttach returns a reference to the container to attach to, given // containerToAttach returns a reference to the container to attach to, given
// by name or the first container if name is empty. // by name or the first container if name is empty.
func (o *AttachOptions) containerToAttachTo(pod *corev1.Pod) (*corev1.Container, error) { func (o *AttachOptions) containerToAttachTo(pod *corev1.Pod) (*corev1.Container, error) {
if len(o.ContainerName) > 0 { return podcmd.FindOrDefaultContainerByName(pod, o.ContainerName, o.Quiet, o.ErrOut)
for i := range pod.Spec.Containers {
if pod.Spec.Containers[i].Name == o.ContainerName {
return &pod.Spec.Containers[i], nil
}
}
for i := range pod.Spec.InitContainers {
if pod.Spec.InitContainers[i].Name == o.ContainerName {
return &pod.Spec.InitContainers[i], nil
}
}
for i := range pod.Spec.EphemeralContainers {
if pod.Spec.EphemeralContainers[i].Name == o.ContainerName {
return (*corev1.Container)(&pod.Spec.EphemeralContainers[i].EphemeralContainerCommon), nil
}
}
return nil, fmt.Errorf("container not found (%s)", o.ContainerName)
}
if o.EnableSuggestedCmdUsage {
fmt.Fprintf(o.ErrOut, "Defaulting container name to %s.\n", pod.Spec.Containers[0].Name)
fmt.Fprintf(o.ErrOut, "Use '%s describe pod/%s -n %s' to see all of the containers in this pod.\n", o.ParentCommandName, o.PodName, o.Namespace)
}
klog.V(4).Infof("defaulting container name to %s", pod.Spec.Containers[0].Name)
return &pod.Spec.Containers[0], nil
} }
// GetContainerName returns the name of the container to attach to, with a fallback. // GetContainerName returns the name of the container to attach to, with a fallback.

View File

@ -17,6 +17,7 @@ limitations under the License.
package attach package attach
import ( import (
"bytes"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@ -35,6 +36,7 @@ import (
"k8s.io/client-go/tools/remotecommand" "k8s.io/client-go/tools/remotecommand"
"k8s.io/kubectl/pkg/cmd/exec" "k8s.io/kubectl/pkg/cmd/exec"
cmdtesting "k8s.io/kubectl/pkg/cmd/testing" cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
"k8s.io/kubectl/pkg/cmd/util/podcmd"
"k8s.io/kubectl/pkg/polymorphichelpers" "k8s.io/kubectl/pkg/polymorphichelpers"
"k8s.io/kubectl/pkg/scheme" "k8s.io/kubectl/pkg/scheme"
) )
@ -65,6 +67,7 @@ func TestPodAndContainerAttach(t *testing.T) {
expectError string expectError string
expectedPodName string expectedPodName string
expectedContainerName string expectedContainerName string
expectOut string
obj *corev1.Pod obj *corev1.Pod
}{ }{
{ {
@ -85,6 +88,25 @@ func TestPodAndContainerAttach(t *testing.T) {
expectedPodName: "foo", expectedPodName: "foo",
expectedContainerName: "bar", expectedContainerName: "bar",
obj: attachPod(), obj: attachPod(),
expectOut: `Defaulted container "bar" out of: bar, debugger (ephem), initfoo (init)`,
},
{
name: "no container, no flags, sets default expected container as annotation",
options: &AttachOptions{GetPodTimeout: defaultPodLogsTimeout},
args: []string{"foo"},
expectedPodName: "foo",
expectedContainerName: "bar",
obj: setDefaultContainer(attachPod(), "initfoo"),
expectOut: ``,
},
{
name: "no container, no flags, sets default missing container as annotation",
options: &AttachOptions{GetPodTimeout: defaultPodLogsTimeout},
args: []string{"foo"},
expectedPodName: "foo",
expectedContainerName: "bar",
obj: setDefaultContainer(attachPod(), "does-not-exist"),
expectOut: `Defaulted container "bar" out of: bar, debugger (ephem), initfoo (init)`,
}, },
{ {
name: "container in flag", name: "container in flag",
@ -115,7 +137,7 @@ func TestPodAndContainerAttach(t *testing.T) {
options: &AttachOptions{StreamOptions: exec.StreamOptions{ContainerName: "wrong"}, GetPodTimeout: 10}, options: &AttachOptions{StreamOptions: exec.StreamOptions{ContainerName: "wrong"}, GetPodTimeout: 10},
args: []string{"foo"}, args: []string{"foo"},
expectedPodName: "foo", expectedPodName: "foo",
expectError: "container not found", expectError: "container wrong not found in pod foo",
obj: attachPod(), obj: attachPod(),
}, },
{ {
@ -153,11 +175,23 @@ func TestPodAndContainerAttach(t *testing.T) {
pod, err := test.options.findAttachablePod(&corev1.Pod{ pod, err := test.options.findAttachablePod(&corev1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "test-pod", Namespace: "test"}, ObjectMeta: metav1.ObjectMeta{Name: "test-pod", Namespace: "test"},
Spec: corev1.PodSpec{ Spec: corev1.PodSpec{
InitContainers: []corev1.Container{
{
Name: "initfoo",
},
},
Containers: []corev1.Container{ Containers: []corev1.Container{
{ {
Name: "foobar", Name: "foobar",
}, },
}, },
EphemeralContainers: []corev1.EphemeralContainer{
{
EphemeralContainerCommon: corev1.EphemeralContainerCommon{
Name: "ephemfoo",
},
},
},
}, },
}) })
if err != nil { if err != nil {
@ -171,10 +205,17 @@ func TestPodAndContainerAttach(t *testing.T) {
t.Errorf("unexpected pod name: expected %q, got %q", test.expectedContainerName, pod.Name) t.Errorf("unexpected pod name: expected %q, got %q", test.expectedContainerName, pod.Name)
} }
var buf bytes.Buffer
test.options.ErrOut = &buf
container, err := test.options.containerToAttachTo(attachPod()) container, err := test.options.containerToAttachTo(attachPod())
if len(test.expectOut) > 0 && !strings.Contains(buf.String(), test.expectOut) {
t.Errorf("unexpected output: output did not contain %q\n---\n%s", test.expectOut, buf.String())
}
if err != nil { if err != nil {
if test.expectError == "" || !strings.Contains(err.Error(), test.expectError) { if test.expectError == "" || !strings.Contains(err.Error(), test.expectError) {
t.Errorf("unexpected error: expected %q, got %q", err, test.expectError) t.Errorf("unexpected error: expected %q, got %q", test.expectError, err)
} }
return return
} }
@ -230,7 +271,7 @@ func TestAttach(t *testing.T) {
attachPath: "/api/" + version + "/namespaces/test/pods/foo/attach", attachPath: "/api/" + version + "/namespaces/test/pods/foo/attach",
pod: attachPod(), pod: attachPod(),
container: "foo", container: "foo",
expectedErr: "cannot attach to the container: container not found (foo)", expectedErr: "cannot attach to the container: container foo not found in pod foo",
}, },
} }
for _, test := range tests { for _, test := range tests {
@ -435,3 +476,11 @@ func attachPod() *corev1.Pod {
}, },
} }
} }
func setDefaultContainer(pod *corev1.Pod, name string) *corev1.Pod {
if pod.Annotations == nil {
pod.Annotations = make(map[string]string)
}
pod.Annotations[podcmd.DefaultContainerAnnotationName] = name
return pod
}

View File

@ -34,6 +34,7 @@ import (
"k8s.io/client-go/tools/remotecommand" "k8s.io/client-go/tools/remotecommand"
cmdutil "k8s.io/kubectl/pkg/cmd/util" cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/cmd/util/podcmd"
"k8s.io/kubectl/pkg/polymorphichelpers" "k8s.io/kubectl/pkg/polymorphichelpers"
"k8s.io/kubectl/pkg/scheme" "k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/i18n"
@ -100,6 +101,7 @@ func NewCmdExec(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.C
cmd.Flags().StringVarP(&options.ContainerName, "container", "c", options.ContainerName, "Container name. If omitted, the first container in the pod will be chosen") cmd.Flags().StringVarP(&options.ContainerName, "container", "c", options.ContainerName, "Container name. If omitted, the first container in the pod will be chosen")
cmd.Flags().BoolVarP(&options.Stdin, "stdin", "i", options.Stdin, "Pass stdin to the container") cmd.Flags().BoolVarP(&options.Stdin, "stdin", "i", options.Stdin, "Pass stdin to the container")
cmd.Flags().BoolVarP(&options.TTY, "tty", "t", options.TTY, "Stdin is a TTY") cmd.Flags().BoolVarP(&options.TTY, "tty", "t", options.TTY, "Stdin is a TTY")
cmd.Flags().BoolVarP(&options.Quiet, "quiet", "q", options.Quiet, "Only print output from the remote session")
return cmd return cmd
} }
@ -152,9 +154,6 @@ type ExecOptions struct {
Command []string Command []string
EnforceNamespace bool EnforceNamespace bool
ParentCommandName string
EnableSuggestedCmdUsage bool
Builder func() *resource.Builder Builder func() *resource.Builder
ExecutablePodFn polymorphichelpers.AttachablePodForObjectFunc ExecutablePodFn polymorphichelpers.AttachablePodForObjectFunc
restClientGetter genericclioptions.RESTClientGetter restClientGetter genericclioptions.RESTClientGetter
@ -174,10 +173,14 @@ func (p *ExecOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, argsIn []s
if argsLenAtDash > -1 { if argsLenAtDash > -1 {
p.Command = argsIn[argsLenAtDash:] p.Command = argsIn[argsLenAtDash:]
} else if len(argsIn) > 1 { } else if len(argsIn) > 1 {
fmt.Fprint(p.ErrOut, "kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.\n") if !p.Quiet {
fmt.Fprint(p.ErrOut, "kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.\n")
}
p.Command = argsIn[1:] p.Command = argsIn[1:]
} else if len(argsIn) > 0 && len(p.FilenameOptions.Filenames) != 0 { } else if len(argsIn) > 0 && len(p.FilenameOptions.Filenames) != 0 {
fmt.Fprint(p.ErrOut, "kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.\n") if !p.Quiet {
fmt.Fprint(p.ErrOut, "kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.\n")
}
p.Command = argsIn[0:] p.Command = argsIn[0:]
p.ResourceName = "" p.ResourceName = ""
} }
@ -198,14 +201,6 @@ func (p *ExecOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, argsIn []s
p.Builder = f.NewBuilder p.Builder = f.NewBuilder
p.restClientGetter = f p.restClientGetter = f
cmdParent := cmd.Parent()
if cmdParent != nil {
p.ParentCommandName = cmdParent.CommandPath()
}
if len(p.ParentCommandName) > 0 && cmdutil.IsSiblingCommandExists(cmd, "describe") {
p.EnableSuggestedCmdUsage = true
}
p.Config, err = f.ToRESTConfig() p.Config, err = f.ToRESTConfig()
if err != nil { if err != nil {
return err return err
@ -260,7 +255,7 @@ func (o *StreamOptions) SetupTTY() term.TTY {
if !o.isTerminalIn(t) { if !o.isTerminalIn(t) {
o.TTY = false o.TTY = false
if o.ErrOut != nil { if !o.Quiet && o.ErrOut != nil {
fmt.Fprintln(o.ErrOut, "Unable to use a TTY - input is not a terminal or the right kind of file") fmt.Fprintln(o.ErrOut, "Unable to use a TTY - input is not a terminal or the right kind of file")
} }
@ -323,7 +318,14 @@ func (p *ExecOptions) Run() error {
return fmt.Errorf("cannot exec into a container in a completed pod; current phase is %s", pod.Status.Phase) return fmt.Errorf("cannot exec into a container in a completed pod; current phase is %s", pod.Status.Phase)
} }
containerName := getDefaultContainerName(p, pod) containerName := p.ContainerName
if len(containerName) == 0 {
container, err := podcmd.FindOrDefaultContainerByName(pod, containerName, p.Quiet, p.ErrOut)
if err != nil {
return err
}
containerName = container.Name
}
// ensure we can recover the terminal while attached // ensure we can recover the terminal while attached
t := p.SetupTTY() t := p.SetupTTY()
@ -368,12 +370,3 @@ func (p *ExecOptions) Run() error {
return nil return nil
} }
func getDefaultContainerName(p *ExecOptions, pod *corev1.Pod) string {
containerName := p.ContainerName
if len(containerName) != 0 {
return containerName
}
return cmdutil.GetDefaultContainerName(pod, p.EnableSuggestedCmdUsage, p.ErrOut)
}

View File

@ -30,7 +30,6 @@ import (
jsonpatch "github.com/evanphx/json-patch" jsonpatch "github.com/evanphx/json-patch"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "github.com/spf13/pflag"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -45,7 +44,6 @@ import (
"k8s.io/client-go/scale" "k8s.io/client-go/scale"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
"k8s.io/klog/v2" "k8s.io/klog/v2"
"k8s.io/kubectl/pkg/util/podutils"
utilexec "k8s.io/utils/exec" utilexec "k8s.io/utils/exec"
) )
@ -746,28 +744,3 @@ func Warning(cmdErr io.Writer, newGeneratorName, oldGeneratorName string) {
oldGeneratorName, oldGeneratorName,
) )
} }
// GetDefaultContainerName returns the default container name for a pod.
// it checks the annotation kubectl.kubernetes.io/default-container at first and then the first container name.
func GetDefaultContainerName(pod *corev1.Pod, enableSuggestedCmdUsage bool, w io.Writer) string {
if len(pod.Spec.Containers) > 1 {
// in case the "kubectl.kubernetes.io/default-container" annotation is present, we preset the opts.Containers to default to selected
// container. This gives users ability to preselect the most interesting container in pod.
if annotations := pod.Annotations; annotations != nil && len(annotations[podutils.DefaultContainerAnnotationName]) > 0 {
containerName := annotations[podutils.DefaultContainerAnnotationName]
if exists, _ := podutils.FindContainerByName(pod, containerName); exists != nil {
fmt.Fprintf(w, "Defaulting container name to container %s.\n", containerName)
return containerName
} else {
fmt.Fprintf(w, "Default container name %q in annotation not found in a pod\n", containerName)
}
}
fmt.Fprintf(w, "Defaulting container name to first container %s.\n", pod.Spec.Containers[0].Name)
if enableSuggestedCmdUsage {
fmt.Fprintf(w, "Use 'kubectl describe pod/%s -n %s' to see all of the containers in this pod.\n", pod.Name, pod.Namespace)
}
}
return pod.Spec.Containers[0].Name
}

View File

@ -0,0 +1,104 @@
/*
Copyright 2021 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 podcmd
import (
"fmt"
"io"
"strings"
v1 "k8s.io/api/core/v1"
"k8s.io/klog/v2"
)
// DefaultContainerAnnotationName is an annotation name that can be used to preselect the interesting container
// from a pod when running kubectl.
const DefaultContainerAnnotationName = "kubectl.kubernetes.io/default-container"
// FindContainerByName selects the named container from the spec of
// the provided pod or return nil if no such container exists.
func FindContainerByName(pod *v1.Pod, name string) (*v1.Container, string) {
for i := range pod.Spec.Containers {
if pod.Spec.Containers[i].Name == name {
return &pod.Spec.Containers[i], fmt.Sprintf("spec.containers{%s}", name)
}
}
for i := range pod.Spec.InitContainers {
if pod.Spec.InitContainers[i].Name == name {
return &pod.Spec.InitContainers[i], fmt.Sprintf("spec.initContainers{%s}", name)
}
}
for i := range pod.Spec.EphemeralContainers {
if pod.Spec.EphemeralContainers[i].Name == name {
return (*v1.Container)(&pod.Spec.EphemeralContainers[i].EphemeralContainerCommon), fmt.Sprintf("spec.ephemeralContainers{%s}", name)
}
}
return nil, ""
}
// FindOrDefaultContainerByName defaults a container for a pod to the first container if any
// exists, or returns an error. It will print a message to the user indicating a default was
// selected if there was more than one container.
func FindOrDefaultContainerByName(pod *v1.Pod, name string, quiet bool, warn io.Writer) (*v1.Container, error) {
var container *v1.Container
if len(name) > 0 {
container, _ = FindContainerByName(pod, name)
if container == nil {
return nil, fmt.Errorf("container %s not found in pod %s", name, pod.Name)
}
return container, nil
}
// this should never happen, but just in case
if len(pod.Spec.Containers) == 0 {
return nil, fmt.Errorf("pod %s/%s does not have any containers", pod.Namespace, pod.Name)
}
// read the default container the annotation as per
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-cli/2227-kubectl-default-container
if name := pod.Annotations[DefaultContainerAnnotationName]; len(name) > 0 {
if container, _ = FindContainerByName(pod, name); container != nil {
klog.V(4).Infof("Defaulting container name from annotation %s", container.Name)
return container, nil
}
klog.V(4).Infof("Default container name from annotation %s was not found in the pod", name)
}
// pick the first container as per existing behavior
container = &pod.Spec.Containers[0]
if !quiet && (len(pod.Spec.Containers) > 1 || len(pod.Spec.InitContainers) > 0 || len(pod.Spec.EphemeralContainers) > 0) {
fmt.Fprintf(warn, "Defaulted container %q out of: %s\n", container.Name, allContainerNames(pod))
}
klog.V(4).Infof("Defaulting container name to %s", container.Name)
return &pod.Spec.Containers[0], nil
}
func allContainerNames(pod *v1.Pod) string {
var containers []string
for _, container := range pod.Spec.Containers {
containers = append(containers, container.Name)
}
for _, container := range pod.Spec.EphemeralContainers {
containers = append(containers, fmt.Sprintf("%s (ephem)", container.Name))
}
for _, container := range pod.Spec.InitContainers {
containers = append(containers, fmt.Sprintf("%s (init)", container.Name))
}
return strings.Join(containers, ", ")
}

View File

@ -30,6 +30,7 @@ import (
corev1client "k8s.io/client-go/kubernetes/typed/core/v1" corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
"k8s.io/client-go/tools/reference" "k8s.io/client-go/tools/reference"
"k8s.io/kubectl/pkg/cmd/util/podcmd"
"k8s.io/kubectl/pkg/scheme" "k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/podutils" "k8s.io/kubectl/pkg/util/podutils"
) )
@ -79,8 +80,8 @@ func logsForObjectWithClient(clientset corev1client.CoreV1Interface, object, opt
// container. This gives users ability to preselect the most interesting container in pod. // container. This gives users ability to preselect the most interesting container in pod.
if annotations := t.GetAnnotations(); annotations != nil && len(opts.Container) == 0 { if annotations := t.GetAnnotations(); annotations != nil && len(opts.Container) == 0 {
var containerName string var containerName string
if len(annotations[podutils.DefaultContainerAnnotationName]) > 0 { if len(annotations[podcmd.DefaultContainerAnnotationName]) > 0 {
containerName = annotations[podutils.DefaultContainerAnnotationName] containerName = annotations[podcmd.DefaultContainerAnnotationName]
} else if len(annotations[defaultLogsContainerAnnotationName]) > 0 { } else if len(annotations[defaultLogsContainerAnnotationName]) > 0 {
// Only log deprecation if we have only the old annotation. This allows users to // Only log deprecation if we have only the old annotation. This allows users to
// set both to support multiple versions of kubectl; if they are setting both // set both to support multiple versions of kubectl; if they are setting both
@ -90,7 +91,7 @@ func logsForObjectWithClient(clientset corev1client.CoreV1Interface, object, opt
fmt.Fprintf(os.Stderr, "Using deprecated annotation `kubectl.kubernetes.io/default-logs-container` in pod/%v. Please use `kubectl.kubernetes.io/default-container` instead\n", t.Name) fmt.Fprintf(os.Stderr, "Using deprecated annotation `kubectl.kubernetes.io/default-logs-container` in pod/%v. Please use `kubectl.kubernetes.io/default-container` instead\n", t.Name)
} }
if len(containerName) > 0 { if len(containerName) > 0 {
if exists, _ := podutils.FindContainerByName(t, containerName); exists != nil { if exists, _ := podcmd.FindContainerByName(t, containerName); exists != nil {
opts.Container = containerName opts.Container = containerName
} else { } else {
fmt.Fprintf(os.Stderr, "Default container name %q not found in a pod\n", containerName) fmt.Fprintf(os.Stderr, "Default container name %q not found in a pod\n", containerName)
@ -121,7 +122,7 @@ func logsForObjectWithClient(clientset corev1client.CoreV1Interface, object, opt
containerName = opts.Container containerName = opts.Container
} }
container, fieldPath := podutils.FindContainerByName(t, containerName) container, fieldPath := podcmd.FindContainerByName(t, containerName)
if container == nil { if container == nil {
return nil, fmt.Errorf("container %s is not valid for pod %s", opts.Container, t.Name) return nil, fmt.Errorf("container %s is not valid for pod %s", opts.Container, t.Name)
} }

View File

@ -32,7 +32,7 @@ import (
"k8s.io/apimachinery/pkg/util/diff" "k8s.io/apimachinery/pkg/util/diff"
fakeexternal "k8s.io/client-go/kubernetes/fake" fakeexternal "k8s.io/client-go/kubernetes/fake"
testclient "k8s.io/client-go/testing" testclient "k8s.io/client-go/testing"
"k8s.io/kubectl/pkg/util/podutils" "k8s.io/kubectl/pkg/cmd/util/podcmd"
) )
var ( var (
@ -430,7 +430,7 @@ func TestLogsForObjectWithClient(t *testing.T) {
name: "two container pod with default container selected", name: "two container pod with default container selected",
podFn: func() *corev1.Pod { podFn: func() *corev1.Pod {
pod := testPodWithTwoContainers() pod := testPodWithTwoContainers()
pod.Annotations = map[string]string{podutils.DefaultContainerAnnotationName: "foo-2-c1"} pod.Annotations = map[string]string{podcmd.DefaultContainerAnnotationName: "foo-2-c1"}
return pod return pod
}, },
podLogOptions: &corev1.PodLogOptions{}, podLogOptions: &corev1.PodLogOptions{},
@ -440,7 +440,7 @@ func TestLogsForObjectWithClient(t *testing.T) {
name: "two container pod with default container selected but also container set explicitly", name: "two container pod with default container selected but also container set explicitly",
podFn: func() *corev1.Pod { podFn: func() *corev1.Pod {
pod := testPodWithTwoContainers() pod := testPodWithTwoContainers()
pod.Annotations = map[string]string{podutils.DefaultContainerAnnotationName: "foo-2-c1"} pod.Annotations = map[string]string{podcmd.DefaultContainerAnnotationName: "foo-2-c1"}
return pod return pod
}, },
podLogOptions: &corev1.PodLogOptions{ podLogOptions: &corev1.PodLogOptions{
@ -452,7 +452,7 @@ func TestLogsForObjectWithClient(t *testing.T) {
name: "two container pod with non-existing default container selected", name: "two container pod with non-existing default container selected",
podFn: func() *corev1.Pod { podFn: func() *corev1.Pod {
pod := testPodWithTwoContainers() pod := testPodWithTwoContainers()
pod.Annotations = map[string]string{podutils.DefaultContainerAnnotationName: "non-existing"} pod.Annotations = map[string]string{podcmd.DefaultContainerAnnotationName: "non-existing"}
return pod return pod
}, },
podLogOptions: &corev1.PodLogOptions{}, podLogOptions: &corev1.PodLogOptions{},
@ -462,7 +462,7 @@ func TestLogsForObjectWithClient(t *testing.T) {
name: "two container pod with default container set, but allContainers also set", name: "two container pod with default container set, but allContainers also set",
podFn: func() *corev1.Pod { podFn: func() *corev1.Pod {
pod := testPodWithTwoContainers() pod := testPodWithTwoContainers()
pod.Annotations = map[string]string{podutils.DefaultContainerAnnotationName: "foo-2-c1"} pod.Annotations = map[string]string{podcmd.DefaultContainerAnnotationName: "foo-2-c1"}
return pod return pod
}, },
allContainers: true, allContainers: true,

View File

@ -17,7 +17,6 @@ limitations under the License.
package podutils package podutils
import ( import (
"fmt"
"time" "time"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
@ -25,10 +24,6 @@ import (
"k8s.io/utils/integer" "k8s.io/utils/integer"
) )
// DefaultContainerAnnotationName is an annotation name that can be used to preselect the interesting container
// from a pod when running kubectl.
const DefaultContainerAnnotationName = "kubectl.kubernetes.io/default-container"
// IsPodAvailable returns true if a pod is available; false otherwise. // IsPodAvailable returns true if a pod is available; false otherwise.
// Precondition for an available pod is that it must be ready. On top // Precondition for an available pod is that it must be ready. On top
// of that, there are two cases when a pod can be considered available: // of that, there are two cases when a pod can be considered available:
@ -191,25 +186,3 @@ func maxContainerRestarts(pod *corev1.Pod) int {
} }
return maxRestarts return maxRestarts
} }
// FindContainerByName searches for a container by name amongst all containers in a pod.
// Returns a pointer to a container and a field path.
func FindContainerByName(pod *corev1.Pod, name string) (container *corev1.Container, fieldPath string) {
for _, c := range pod.Spec.InitContainers {
if c.Name == name {
return &c, fmt.Sprintf("spec.initContainers{%s}", c.Name)
}
}
for _, c := range pod.Spec.Containers {
if c.Name == name {
return &c, fmt.Sprintf("spec.containers{%s}", c.Name)
}
}
for _, c := range pod.Spec.EphemeralContainers {
if c.Name == name {
containerCommon := corev1.Container(c.EphemeralContainerCommon)
return &containerCommon, fmt.Sprintf("spec.ephemeralContainers{%s}", containerCommon.Name)
}
}
return nil, ""
}

1
vendor/modules.txt vendored
View File

@ -2407,6 +2407,7 @@ k8s.io/kubectl/pkg/cmd/top
k8s.io/kubectl/pkg/cmd/util k8s.io/kubectl/pkg/cmd/util
k8s.io/kubectl/pkg/cmd/util/editor k8s.io/kubectl/pkg/cmd/util/editor
k8s.io/kubectl/pkg/cmd/util/editor/crlf k8s.io/kubectl/pkg/cmd/util/editor/crlf
k8s.io/kubectl/pkg/cmd/util/podcmd
k8s.io/kubectl/pkg/cmd/util/sanity k8s.io/kubectl/pkg/cmd/util/sanity
k8s.io/kubectl/pkg/cmd/version k8s.io/kubectl/pkg/cmd/version
k8s.io/kubectl/pkg/cmd/wait k8s.io/kubectl/pkg/cmd/wait