mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-10-30 17:58:14 +00:00 
			
		
		
		
	kubectl: allow to preselect interesting container in logs
This commit is contained in:
		| @@ -34,6 +34,10 @@ import ( | |||||||
| 	"k8s.io/kubectl/pkg/util/podutils" | 	"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) { | func logsForObject(restClientGetter genericclioptions.RESTClientGetter, object, options runtime.Object, timeout time.Duration, allContainers bool) (map[corev1.ObjectReference]rest.ResponseWrapper, error) { | ||||||
| 	clientConfig, err := restClientGetter.ToRESTConfig() | 	clientConfig, err := restClientGetter.ToRESTConfig() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -69,6 +73,16 @@ func logsForObjectWithClient(clientset corev1client.CoreV1Interface, object, opt | |||||||
| 		return ret, nil | 		return ret, nil | ||||||
|  |  | ||||||
| 	case *corev1.Pod: | 	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 is true, then we're going to locate all containers and then iterate through them. At that point, "allContainers" is false | ||||||
| 		if !allContainers { | 		if !allContainers { | ||||||
| 			var containerName string | 			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 { | func testPodWithTwoContainersAndTwoInitAndOneEphemeralContainers() *corev1.Pod { | ||||||
| 	return &corev1.Pod{ | 	return &corev1.Pod{ | ||||||
| 		TypeMeta: metav1.TypeMeta{ | 		TypeMeta: metav1.TypeMeta{ | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Michal Fojtik
					Michal Fojtik