kubectl: allow to preselect interesting container in logs
Kubernetes-commit: 439f93c91b81eee29f2aa5c4cf6fff911e26e684
This commit is contained in:
parent
154f6764ba
commit
cbb6f76ae7
|
@ -34,6 +34,10 @@ import (
|
|||
"k8s.io/kubectl/pkg/util/podutils"
|
||||
)
|
||||
|
||||
// defaultLogsContainerAnnotationName is an annotation name that can be used to preselect the interesting container
|
||||
// from a pod when running kubectl logs.
|
||||
const defaultLogsContainerAnnotationName = "kubectl.kubernetes.io/default-logs-container"
|
||||
|
||||
func logsForObject(restClientGetter genericclioptions.RESTClientGetter, object, options runtime.Object, timeout time.Duration, allContainers bool) (map[corev1.ObjectReference]rest.ResponseWrapper, error) {
|
||||
clientConfig, err := restClientGetter.ToRESTConfig()
|
||||
if err != nil {
|
||||
|
@ -69,6 +73,16 @@ func logsForObjectWithClient(clientset corev1client.CoreV1Interface, object, opt
|
|||
return ret, nil
|
||||
|
||||
case *corev1.Pod:
|
||||
// in case the "kubectl.kubernetes.io/default-logs-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 := t.GetAnnotations(); annotations != nil && len(opts.Container) == 0 && len(annotations[defaultLogsContainerAnnotationName]) > 0 {
|
||||
containerName := annotations[defaultLogsContainerAnnotationName]
|
||||
if exists, _ := findContainerByName(t, containerName); exists != nil {
|
||||
opts.Container = containerName
|
||||
} else {
|
||||
fmt.Fprintf(os.Stderr, "Default container name %q not found in a pod\n", containerName)
|
||||
}
|
||||
}
|
||||
// if allContainers is true, then we're going to locate all containers and then iterate through them. At that point, "allContainers" is false
|
||||
if !allContainers {
|
||||
var containerName string
|
||||
|
|
|
@ -410,6 +410,107 @@ func testPodWithTwoContainersAndTwoInitContainers() *corev1.Pod {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLogsForObjectWithClient(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
podFn func() *corev1.Pod
|
||||
podLogOptions *corev1.PodLogOptions
|
||||
expectedFieldPath string
|
||||
allContainers bool
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "two container pod without default container selected",
|
||||
podFn: testPodWithTwoContainers,
|
||||
podLogOptions: &corev1.PodLogOptions{},
|
||||
expectedError: `a container name must be specified for pod foo-two-containers, choose one of: [foo-2-c1 foo-2-c2]`,
|
||||
},
|
||||
{
|
||||
name: "two container pod with default container selected",
|
||||
podFn: func() *corev1.Pod {
|
||||
pod := testPodWithTwoContainers()
|
||||
pod.Annotations = map[string]string{defaultLogsContainerAnnotationName: "foo-2-c1"}
|
||||
return pod
|
||||
},
|
||||
podLogOptions: &corev1.PodLogOptions{},
|
||||
expectedFieldPath: `spec.containers{foo-2-c1}`,
|
||||
},
|
||||
{
|
||||
name: "two container pod with default container selected but also container set explicitly",
|
||||
podFn: func() *corev1.Pod {
|
||||
pod := testPodWithTwoContainers()
|
||||
pod.Annotations = map[string]string{defaultLogsContainerAnnotationName: "foo-2-c1"}
|
||||
return pod
|
||||
},
|
||||
podLogOptions: &corev1.PodLogOptions{
|
||||
Container: "foo-2-c2",
|
||||
},
|
||||
expectedFieldPath: `spec.containers{foo-2-c2}`,
|
||||
},
|
||||
{
|
||||
name: "two container pod with non-existing default container selected",
|
||||
podFn: func() *corev1.Pod {
|
||||
pod := testPodWithTwoContainers()
|
||||
pod.Annotations = map[string]string{defaultLogsContainerAnnotationName: "non-existing"}
|
||||
return pod
|
||||
},
|
||||
podLogOptions: &corev1.PodLogOptions{},
|
||||
expectedError: `a container name must be specified for pod foo-two-containers, choose one of: [foo-2-c1 foo-2-c2]`,
|
||||
},
|
||||
{
|
||||
name: "two container pod with default container set, but allContainers also set",
|
||||
podFn: func() *corev1.Pod {
|
||||
pod := testPodWithTwoContainers()
|
||||
pod.Annotations = map[string]string{defaultLogsContainerAnnotationName: "foo-2-c1"}
|
||||
return pod
|
||||
},
|
||||
allContainers: true,
|
||||
podLogOptions: &corev1.PodLogOptions{},
|
||||
expectedFieldPath: `spec.containers{foo-2-c2}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
pod := tc.podFn()
|
||||
fakeClientset := fakeexternal.NewSimpleClientset(pod)
|
||||
responses, err := logsForObjectWithClient(fakeClientset.CoreV1(), pod, tc.podLogOptions, 20*time.Second, tc.allContainers)
|
||||
if err != nil {
|
||||
if len(tc.expectedError) > 0 {
|
||||
if err.Error() == tc.expectedError {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
if len(tc.expectedError) > 0 {
|
||||
t.Errorf("expected error %q, got none", tc.expectedError)
|
||||
return
|
||||
}
|
||||
if !tc.allContainers && len(responses) != 1 {
|
||||
t.Errorf("expected one response, got %d", len(responses))
|
||||
return
|
||||
}
|
||||
if tc.allContainers && len(responses) != 2 {
|
||||
t.Errorf("expected 2 responses for allContainers, got %d", len(responses))
|
||||
return
|
||||
}
|
||||
// do not check actual responses in this case as we know there are at least two, which means the preselected
|
||||
// container was not used (which is desired).
|
||||
if tc.allContainers {
|
||||
return
|
||||
}
|
||||
for r := range responses {
|
||||
if r.FieldPath != tc.expectedFieldPath {
|
||||
t.Errorf("expected %q container to be preselected, got %q", tc.expectedFieldPath, r.FieldPath)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func testPodWithTwoContainersAndTwoInitAndOneEphemeralContainers() *corev1.Pod {
|
||||
return &corev1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
|
|
Loading…
Reference in New Issue