Skip to content

Commit

Permalink
kubectl: allow to preselect interesting container in logs
Browse files Browse the repository at this point in the history
  • Loading branch information
mfojtik committed Feb 4, 2020
1 parent d52ecd5 commit 439f93c
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 0 deletions.
14 changes: 14 additions & 0 deletions staging/src/k8s.io/kubectl/pkg/polymorphichelpers/logsforobject.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down

0 comments on commit 439f93c

Please sign in to comment.