mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	Merge pull request #124732 from cmwylie19/1520
feat: add all-pods log flag to kubectl
This commit is contained in:
		@@ -99,6 +99,7 @@ type LogsOptions struct {
 | 
			
		||||
	Namespace     string
 | 
			
		||||
	ResourceArg   string
 | 
			
		||||
	AllContainers bool
 | 
			
		||||
	AllPods       bool
 | 
			
		||||
	Options       runtime.Object
 | 
			
		||||
	Resources     []string
 | 
			
		||||
 | 
			
		||||
@@ -126,6 +127,7 @@ type LogsOptions struct {
 | 
			
		||||
	GetPodTimeout       time.Duration
 | 
			
		||||
	RESTClientGetter    genericclioptions.RESTClientGetter
 | 
			
		||||
	LogsForObject       polymorphichelpers.LogsForObjectFunc
 | 
			
		||||
	AllPodLogsForObject polymorphichelpers.AllPodLogsForObjectFunc
 | 
			
		||||
 | 
			
		||||
	genericiooptions.IOStreams
 | 
			
		||||
 | 
			
		||||
@@ -134,10 +136,9 @@ type LogsOptions struct {
 | 
			
		||||
	containerNameFromRefSpecRegexp *regexp.Regexp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewLogsOptions(streams genericiooptions.IOStreams, allContainers bool) *LogsOptions {
 | 
			
		||||
func NewLogsOptions(streams genericiooptions.IOStreams) *LogsOptions {
 | 
			
		||||
	return &LogsOptions{
 | 
			
		||||
		IOStreams:            streams,
 | 
			
		||||
		AllContainers:        allContainers,
 | 
			
		||||
		Tail:                 -1,
 | 
			
		||||
		MaxFollowConcurrency: 5,
 | 
			
		||||
 | 
			
		||||
@@ -147,7 +148,7 @@ func NewLogsOptions(streams genericiooptions.IOStreams, allContainers bool) *Log
 | 
			
		||||
 | 
			
		||||
// NewCmdLogs creates a new pod logs command
 | 
			
		||||
func NewCmdLogs(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
 | 
			
		||||
	o := NewLogsOptions(streams, false)
 | 
			
		||||
	o := NewLogsOptions(streams)
 | 
			
		||||
 | 
			
		||||
	cmd := &cobra.Command{
 | 
			
		||||
		Use:                   logsUsageStr,
 | 
			
		||||
@@ -167,6 +168,7 @@ func NewCmdLogs(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Co
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *LogsOptions) AddFlags(cmd *cobra.Command) {
 | 
			
		||||
	cmd.Flags().BoolVar(&o.AllPods, "all-pods", o.AllPods, "Get logs from all pod(s). Sets prefix to true.")
 | 
			
		||||
	cmd.Flags().BoolVar(&o.AllContainers, "all-containers", o.AllContainers, "Get all containers' logs in the pod(s).")
 | 
			
		||||
	cmd.Flags().BoolVarP(&o.Follow, "follow", "f", o.Follow, "Specify if the logs should be streamed.")
 | 
			
		||||
	cmd.Flags().BoolVar(&o.Timestamps, "timestamps", o.Timestamps, "Include timestamps on each line in the log output")
 | 
			
		||||
@@ -243,6 +245,11 @@ func (o *LogsOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []str
 | 
			
		||||
	default:
 | 
			
		||||
		return cmdutil.UsageErrorf(cmd, "%s", logsUsageErrStr)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if o.AllPods {
 | 
			
		||||
		o.Prefix = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var err error
 | 
			
		||||
	o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -263,6 +270,7 @@ func (o *LogsOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []str
 | 
			
		||||
 | 
			
		||||
	o.RESTClientGetter = f
 | 
			
		||||
	o.LogsForObject = polymorphichelpers.LogsForObjectFn
 | 
			
		||||
	o.AllPodLogsForObject = polymorphichelpers.AllPodLogsForObjectFn
 | 
			
		||||
 | 
			
		||||
	if o.Object == nil {
 | 
			
		||||
		builder := f.NewBuilder().
 | 
			
		||||
@@ -328,7 +336,13 @@ func (o LogsOptions) Validate() error {
 | 
			
		||||
 | 
			
		||||
// RunLogs retrieves a pod log
 | 
			
		||||
func (o LogsOptions) RunLogs() error {
 | 
			
		||||
	requests, err := o.LogsForObject(o.RESTClientGetter, o.Object, o.Options, o.GetPodTimeout, o.AllContainers)
 | 
			
		||||
	var requests map[corev1.ObjectReference]rest.ResponseWrapper
 | 
			
		||||
	var err error
 | 
			
		||||
	if o.AllPods {
 | 
			
		||||
		requests, err = o.AllPodLogsForObject(o.RESTClientGetter, o.Object, o.Options, o.GetPodTimeout, o.AllContainers)
 | 
			
		||||
	} else {
 | 
			
		||||
		requests, err = o.LogsForObject(o.RESTClientGetter, o.Object, o.Options, o.GetPodTimeout, o.AllContainers)
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -29,6 +29,7 @@ import (
 | 
			
		||||
	"testing/iotest"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	appsv1 "k8s.io/api/apps/v1"
 | 
			
		||||
	corev1 "k8s.io/api/core/v1"
 | 
			
		||||
	apierrors "k8s.io/apimachinery/pkg/api/errors"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
@@ -62,7 +63,7 @@ func TestLog(t *testing.T) {
 | 
			
		||||
					},
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				o := NewLogsOptions(streams, false)
 | 
			
		||||
				o := NewLogsOptions(streams)
 | 
			
		||||
				o.LogsForObject = mock.mockLogsForObject
 | 
			
		||||
				o.ConsumeRequestFn = mock.mockConsumeRequest
 | 
			
		||||
 | 
			
		||||
@@ -83,7 +84,7 @@ func TestLog(t *testing.T) {
 | 
			
		||||
					},
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				o := NewLogsOptions(streams, false)
 | 
			
		||||
				o := NewLogsOptions(streams)
 | 
			
		||||
				o.LogsForObject = mock.mockLogsForObject
 | 
			
		||||
				o.ConsumeRequestFn = mock.mockConsumeRequest
 | 
			
		||||
				o.Prefix = true
 | 
			
		||||
@@ -92,6 +93,32 @@ func TestLog(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
			expectedOutSubstrings: []string{"[pod/test-pod/test-container] test log content\n"},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "stateful set logs with all pods",
 | 
			
		||||
			opts: func(streams genericiooptions.IOStreams) *LogsOptions {
 | 
			
		||||
				mock := &logTestMock{
 | 
			
		||||
					logsForObjectRequests: map[corev1.ObjectReference]restclient.ResponseWrapper{
 | 
			
		||||
						{
 | 
			
		||||
							Kind:      "Pod",
 | 
			
		||||
							Name:      "test-sts-0",
 | 
			
		||||
							FieldPath: "spec.containers{test-container}",
 | 
			
		||||
						}: &responseWrapperMock{data: strings.NewReader("test log content for pod test-sts-0\n")},
 | 
			
		||||
						{
 | 
			
		||||
							Kind:      "Pod",
 | 
			
		||||
							Name:      "test-sts-1",
 | 
			
		||||
							FieldPath: "spec.containers{test-container}",
 | 
			
		||||
						}: &responseWrapperMock{data: strings.NewReader("test log content for pod test-sts-1\n")},
 | 
			
		||||
					},
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				o := NewLogsOptions(streams)
 | 
			
		||||
				o.LogsForObject = mock.mockLogsForObject
 | 
			
		||||
				o.ConsumeRequestFn = mock.mockConsumeRequest
 | 
			
		||||
				o.Prefix = true
 | 
			
		||||
				return o
 | 
			
		||||
			},
 | 
			
		||||
			expectedOutSubstrings: []string{"[pod/test-sts-0/test-container] test log content for pod test-sts-0\n[pod/test-sts-1/test-container] test log content for pod test-sts-1\n"},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "pod logs with prefix: init container",
 | 
			
		||||
			opts: func(streams genericiooptions.IOStreams) *LogsOptions {
 | 
			
		||||
@@ -105,7 +132,7 @@ func TestLog(t *testing.T) {
 | 
			
		||||
					},
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				o := NewLogsOptions(streams, false)
 | 
			
		||||
				o := NewLogsOptions(streams)
 | 
			
		||||
				o.LogsForObject = mock.mockLogsForObject
 | 
			
		||||
				o.ConsumeRequestFn = mock.mockConsumeRequest
 | 
			
		||||
				o.Prefix = true
 | 
			
		||||
@@ -127,7 +154,7 @@ func TestLog(t *testing.T) {
 | 
			
		||||
					},
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				o := NewLogsOptions(streams, false)
 | 
			
		||||
				o := NewLogsOptions(streams)
 | 
			
		||||
				o.LogsForObject = mock.mockLogsForObject
 | 
			
		||||
				o.ConsumeRequestFn = mock.mockConsumeRequest
 | 
			
		||||
				o.Prefix = true
 | 
			
		||||
@@ -159,7 +186,7 @@ func TestLog(t *testing.T) {
 | 
			
		||||
					},
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				o := NewLogsOptions(streams, false)
 | 
			
		||||
				o := NewLogsOptions(streams)
 | 
			
		||||
				o.LogsForObject = mock.mockLogsForObject
 | 
			
		||||
				o.ConsumeRequestFn = mock.mockConsumeRequest
 | 
			
		||||
				return o
 | 
			
		||||
@@ -196,7 +223,7 @@ func TestLog(t *testing.T) {
 | 
			
		||||
				}
 | 
			
		||||
				wg.Add(3)
 | 
			
		||||
 | 
			
		||||
				o := NewLogsOptions(streams, false)
 | 
			
		||||
				o := NewLogsOptions(streams)
 | 
			
		||||
				o.LogsForObject = mock.mockLogsForObject
 | 
			
		||||
				o.ConsumeRequestFn = mock.mockConsumeRequest
 | 
			
		||||
				o.Follow = true
 | 
			
		||||
@@ -234,7 +261,7 @@ func TestLog(t *testing.T) {
 | 
			
		||||
				}
 | 
			
		||||
				wg.Add(3)
 | 
			
		||||
 | 
			
		||||
				o := NewLogsOptions(streams, false)
 | 
			
		||||
				o := NewLogsOptions(streams)
 | 
			
		||||
				o.LogsForObject = mock.mockLogsForObject
 | 
			
		||||
				o.ConsumeRequestFn = mock.mockConsumeRequest
 | 
			
		||||
				o.MaxFollowConcurrency = 2
 | 
			
		||||
@@ -246,7 +273,7 @@ func TestLog(t *testing.T) {
 | 
			
		||||
		{
 | 
			
		||||
			name: "fail if LogsForObject fails",
 | 
			
		||||
			opts: func(streams genericiooptions.IOStreams) *LogsOptions {
 | 
			
		||||
				o := NewLogsOptions(streams, false)
 | 
			
		||||
				o := NewLogsOptions(streams)
 | 
			
		||||
				o.LogsForObject = func(restClientGetter genericclioptions.RESTClientGetter, object, options runtime.Object, timeout time.Duration, allContainers bool) (map[corev1.ObjectReference]restclient.ResponseWrapper, error) {
 | 
			
		||||
					return nil, errors.New("Error from the LogsForObject")
 | 
			
		||||
				}
 | 
			
		||||
@@ -272,7 +299,7 @@ func TestLog(t *testing.T) {
 | 
			
		||||
					},
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				o := NewLogsOptions(streams, false)
 | 
			
		||||
				o := NewLogsOptions(streams)
 | 
			
		||||
				o.LogsForObject = mock.mockLogsForObject
 | 
			
		||||
				o.ConsumeRequestFn = func(req restclient.ResponseWrapper, out io.Writer) error {
 | 
			
		||||
					return errors.New("Error from the ConsumeRequestFn")
 | 
			
		||||
@@ -307,7 +334,7 @@ func TestLog(t *testing.T) {
 | 
			
		||||
				}
 | 
			
		||||
				wg.Add(3)
 | 
			
		||||
 | 
			
		||||
				o := NewLogsOptions(streams, false)
 | 
			
		||||
				o := NewLogsOptions(streams)
 | 
			
		||||
				o.LogsForObject = mock.mockLogsForObject
 | 
			
		||||
				o.ConsumeRequestFn = mock.mockConsumeRequest
 | 
			
		||||
				o.Follow = true
 | 
			
		||||
@@ -346,7 +373,7 @@ func TestLog(t *testing.T) {
 | 
			
		||||
				}
 | 
			
		||||
				wg.Add(3)
 | 
			
		||||
 | 
			
		||||
				o := NewLogsOptions(streams, false)
 | 
			
		||||
				o := NewLogsOptions(streams)
 | 
			
		||||
				o.LogsForObject = mock.mockLogsForObject
 | 
			
		||||
				o.ConsumeRequestFn = func(req restclient.ResponseWrapper, out io.Writer) error {
 | 
			
		||||
					return errors.New("Error from the ConsumeRequestFn")
 | 
			
		||||
@@ -369,7 +396,7 @@ func TestLog(t *testing.T) {
 | 
			
		||||
					},
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				o := NewLogsOptions(streams, false)
 | 
			
		||||
				o := NewLogsOptions(streams)
 | 
			
		||||
				o.LogsForObject = mock.mockLogsForObject
 | 
			
		||||
				o.ConsumeRequestFn = func(req restclient.ResponseWrapper, out io.Writer) error {
 | 
			
		||||
					return errors.New("Error from the ConsumeRequestFn")
 | 
			
		||||
@@ -402,7 +429,7 @@ func TestLog(t *testing.T) {
 | 
			
		||||
					},
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				o := NewLogsOptions(streams, false)
 | 
			
		||||
				o := NewLogsOptions(streams)
 | 
			
		||||
				o.LogsForObject = mock.mockLogsForObject
 | 
			
		||||
				o.ConsumeRequestFn = mock.mockConsumeRequest
 | 
			
		||||
				o.IgnoreLogErrors = true
 | 
			
		||||
@@ -432,7 +459,7 @@ func TestLog(t *testing.T) {
 | 
			
		||||
					},
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				o := NewLogsOptions(streams, false)
 | 
			
		||||
				o := NewLogsOptions(streams)
 | 
			
		||||
				o.LogsForObject = mock.mockLogsForObject
 | 
			
		||||
				o.ConsumeRequestFn = mock.mockConsumeRequest
 | 
			
		||||
				return o
 | 
			
		||||
@@ -462,7 +489,7 @@ func TestLog(t *testing.T) {
 | 
			
		||||
					},
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				o := NewLogsOptions(streams, false)
 | 
			
		||||
				o := NewLogsOptions(streams)
 | 
			
		||||
				o.LogsForObject = mock.mockLogsForObject
 | 
			
		||||
				o.ConsumeRequestFn = mock.mockConsumeRequest
 | 
			
		||||
				o.IgnoreLogErrors = true
 | 
			
		||||
@@ -493,7 +520,7 @@ func TestLog(t *testing.T) {
 | 
			
		||||
					},
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				o := NewLogsOptions(streams, false)
 | 
			
		||||
				o := NewLogsOptions(streams)
 | 
			
		||||
				o.LogsForObject = mock.mockLogsForObject
 | 
			
		||||
				o.ConsumeRequestFn = mock.mockConsumeRequest
 | 
			
		||||
				o.Follow = true
 | 
			
		||||
@@ -564,7 +591,7 @@ func TestValidateLogOptions(t *testing.T) {
 | 
			
		||||
		{
 | 
			
		||||
			name: "since & since-time",
 | 
			
		||||
			opts: func(streams genericiooptions.IOStreams) *LogsOptions {
 | 
			
		||||
				o := NewLogsOptions(streams, false)
 | 
			
		||||
				o := NewLogsOptions(streams)
 | 
			
		||||
				o.SinceSeconds = time.Hour
 | 
			
		||||
				o.SinceTime = "2006-01-02T15:04:05Z"
 | 
			
		||||
 | 
			
		||||
@@ -582,7 +609,7 @@ func TestValidateLogOptions(t *testing.T) {
 | 
			
		||||
		{
 | 
			
		||||
			name: "negative since-time",
 | 
			
		||||
			opts: func(streams genericiooptions.IOStreams) *LogsOptions {
 | 
			
		||||
				o := NewLogsOptions(streams, false)
 | 
			
		||||
				o := NewLogsOptions(streams)
 | 
			
		||||
				o.SinceSeconds = -1 * time.Second
 | 
			
		||||
 | 
			
		||||
				var err error
 | 
			
		||||
@@ -599,7 +626,7 @@ func TestValidateLogOptions(t *testing.T) {
 | 
			
		||||
		{
 | 
			
		||||
			name: "negative limit-bytes",
 | 
			
		||||
			opts: func(streams genericiooptions.IOStreams) *LogsOptions {
 | 
			
		||||
				o := NewLogsOptions(streams, false)
 | 
			
		||||
				o := NewLogsOptions(streams)
 | 
			
		||||
				o.LimitBytes = -100
 | 
			
		||||
 | 
			
		||||
				var err error
 | 
			
		||||
@@ -616,7 +643,7 @@ func TestValidateLogOptions(t *testing.T) {
 | 
			
		||||
		{
 | 
			
		||||
			name: "negative tail",
 | 
			
		||||
			opts: func(streams genericiooptions.IOStreams) *LogsOptions {
 | 
			
		||||
				o := NewLogsOptions(streams, false)
 | 
			
		||||
				o := NewLogsOptions(streams)
 | 
			
		||||
				o.Tail = -100
 | 
			
		||||
 | 
			
		||||
				var err error
 | 
			
		||||
@@ -633,7 +660,8 @@ func TestValidateLogOptions(t *testing.T) {
 | 
			
		||||
		{
 | 
			
		||||
			name: "container name combined with --all-containers",
 | 
			
		||||
			opts: func(streams genericiooptions.IOStreams) *LogsOptions {
 | 
			
		||||
				o := NewLogsOptions(streams, true)
 | 
			
		||||
				o := NewLogsOptions(streams)
 | 
			
		||||
				o.AllContainers = true
 | 
			
		||||
				o.Container = "my-container"
 | 
			
		||||
 | 
			
		||||
				var err error
 | 
			
		||||
@@ -650,7 +678,7 @@ func TestValidateLogOptions(t *testing.T) {
 | 
			
		||||
		{
 | 
			
		||||
			name: "container name combined with second argument",
 | 
			
		||||
			opts: func(streams genericiooptions.IOStreams) *LogsOptions {
 | 
			
		||||
				o := NewLogsOptions(streams, false)
 | 
			
		||||
				o := NewLogsOptions(streams)
 | 
			
		||||
				o.Container = "my-container"
 | 
			
		||||
				o.ContainerNameSpecified = true
 | 
			
		||||
 | 
			
		||||
@@ -697,7 +725,7 @@ func TestLogComplete(t *testing.T) {
 | 
			
		||||
			name: "One args case",
 | 
			
		||||
			args: []string{"foo"},
 | 
			
		||||
			opts: func(streams genericiooptions.IOStreams) *LogsOptions {
 | 
			
		||||
				o := NewLogsOptions(streams, false)
 | 
			
		||||
				o := NewLogsOptions(streams)
 | 
			
		||||
				o.Selector = "foo"
 | 
			
		||||
				return o
 | 
			
		||||
			},
 | 
			
		||||
@@ -816,7 +844,7 @@ func TestNoResourceFoundMessage(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	streams, _, buf, errbuf := genericiooptions.NewTestIOStreams()
 | 
			
		||||
	cmd := NewCmdLogs(tf, streams)
 | 
			
		||||
	o := NewLogsOptions(streams, false)
 | 
			
		||||
	o := NewLogsOptions(streams)
 | 
			
		||||
	o.Selector = "foo"
 | 
			
		||||
	err := o.Complete(tf, cmd, []string{})
 | 
			
		||||
 | 
			
		||||
@@ -864,7 +892,7 @@ func TestNoPodInNamespaceFoundMessage(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	streams, _, _, _ := genericiooptions.NewTestIOStreams()
 | 
			
		||||
	cmd := NewCmdLogs(tf, streams)
 | 
			
		||||
	o := NewLogsOptions(streams, false)
 | 
			
		||||
	o := NewLogsOptions(streams)
 | 
			
		||||
	err := o.Complete(tf, cmd, []string{podName})
 | 
			
		||||
 | 
			
		||||
	if err == nil {
 | 
			
		||||
@@ -919,6 +947,13 @@ func (l *logTestMock) mockConsumeRequest(request restclient.ResponseWrapper, out
 | 
			
		||||
 | 
			
		||||
func (l *logTestMock) mockLogsForObject(restClientGetter genericclioptions.RESTClientGetter, object, options runtime.Object, timeout time.Duration, allContainers bool) (map[corev1.ObjectReference]restclient.ResponseWrapper, error) {
 | 
			
		||||
	switch object.(type) {
 | 
			
		||||
	case *appsv1.Deployment:
 | 
			
		||||
		_, ok := options.(*corev1.PodLogOptions)
 | 
			
		||||
		if !ok {
 | 
			
		||||
			return nil, errors.New("provided options object is not a PodLogOptions")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return l.logsForObjectRequests, nil
 | 
			
		||||
	case *corev1.Pod:
 | 
			
		||||
		_, ok := options.(*corev1.PodLogOptions)
 | 
			
		||||
		if !ok {
 | 
			
		||||
 
 | 
			
		||||
@@ -36,15 +36,15 @@ import (
 | 
			
		||||
	watchtools "k8s.io/client-go/tools/watch"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// GetFirstPod returns a pod matching the namespace and label selector
 | 
			
		||||
// and the number of all pods that match the label selector.
 | 
			
		||||
func GetFirstPod(client coreclient.PodsGetter, namespace string, selector string, timeout time.Duration, sortBy func([]*corev1.Pod) sort.Interface) (*corev1.Pod, int, error) {
 | 
			
		||||
// GetPodList returns a PodList matching the namespace and label selector
 | 
			
		||||
func GetPodList(client coreclient.PodsGetter, namespace string, selector string, timeout time.Duration, sortBy func([]*corev1.Pod) sort.Interface) (*corev1.PodList, error) {
 | 
			
		||||
	options := metav1.ListOptions{LabelSelector: selector}
 | 
			
		||||
 | 
			
		||||
	podList, err := client.Pods(namespace).List(context.TODO(), options)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, 0, err
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pods := []*corev1.Pod{}
 | 
			
		||||
	for i := range podList.Items {
 | 
			
		||||
		pod := podList.Items[i]
 | 
			
		||||
@@ -52,14 +52,17 @@ func GetFirstPod(client coreclient.PodsGetter, namespace string, selector string
 | 
			
		||||
	}
 | 
			
		||||
	if len(pods) > 0 {
 | 
			
		||||
		sort.Sort(sortBy(pods))
 | 
			
		||||
		return pods[0], len(podList.Items), nil
 | 
			
		||||
		for i, pod := range pods {
 | 
			
		||||
			podList.Items[i] = *pod
 | 
			
		||||
		}
 | 
			
		||||
		return podList, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Watch until we observe a pod
 | 
			
		||||
	options.ResourceVersion = podList.ResourceVersion
 | 
			
		||||
	w, err := client.Pods(namespace).Watch(context.TODO(), options)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, 0, err
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	defer w.Stop()
 | 
			
		||||
 | 
			
		||||
@@ -70,14 +73,30 @@ func GetFirstPod(client coreclient.PodsGetter, namespace string, selector string
 | 
			
		||||
	ctx, cancel := watchtools.ContextWithOptionalTimeout(context.Background(), timeout)
 | 
			
		||||
	defer cancel()
 | 
			
		||||
	event, err := watchtools.UntilWithoutRetry(ctx, w, condition)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	po, ok := event.Object.(*corev1.Pod)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("%#v is not a pod event", event)
 | 
			
		||||
	} else {
 | 
			
		||||
		podList.Items = append(podList.Items, *po)
 | 
			
		||||
	}
 | 
			
		||||
	return podList, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetFirstPod returns a pod matching the namespace and label selector
 | 
			
		||||
// and the number of all pods that match the label selector.
 | 
			
		||||
func GetFirstPod(client coreclient.PodsGetter, namespace string, selector string, timeout time.Duration, sortBy func([]*corev1.Pod) sort.Interface) (*corev1.Pod, int, error) {
 | 
			
		||||
 | 
			
		||||
	podList, err := GetPodList(client, namespace, selector, timeout, sortBy)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, 0, err
 | 
			
		||||
	}
 | 
			
		||||
	pod, ok := event.Object.(*corev1.Pod)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, 0, fmt.Errorf("%#v is not a pod event", event)
 | 
			
		||||
	}
 | 
			
		||||
	return pod, 1, nil
 | 
			
		||||
 | 
			
		||||
	return &podList.Items[0], len(podList.Items), nil
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SelectorsForObject returns the pod label selector for a given object
 | 
			
		||||
 
 | 
			
		||||
@@ -32,6 +32,98 @@ import (
 | 
			
		||||
	"k8s.io/kubectl/pkg/util/podutils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestGetPodList(t *testing.T) {
 | 
			
		||||
	labelSet := map[string]string{"test": "selector"}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
 | 
			
		||||
		podList  *corev1.PodList
 | 
			
		||||
		watching []watch.Event
 | 
			
		||||
		sortBy   func([]*corev1.Pod) sort.Interface
 | 
			
		||||
 | 
			
		||||
		expected    *corev1.PodList
 | 
			
		||||
		expectedNum int
 | 
			
		||||
		expectedErr bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:    "kubectl logs - two ready pods",
 | 
			
		||||
			podList: newPodList(2, -1, -1, labelSet),
 | 
			
		||||
			sortBy:  func(pods []*corev1.Pod) sort.Interface { return podutils.ByLogging(pods) },
 | 
			
		||||
			expected: &corev1.PodList{
 | 
			
		||||
				Items: []corev1.Pod{
 | 
			
		||||
					{
 | 
			
		||||
						ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
							Name:              "pod-1",
 | 
			
		||||
							Namespace:         metav1.NamespaceDefault,
 | 
			
		||||
							CreationTimestamp: metav1.Date(2016, time.April, 1, 1, 0, 0, 0, time.UTC),
 | 
			
		||||
							Labels:            map[string]string{"test": "selector"},
 | 
			
		||||
						},
 | 
			
		||||
						Status: corev1.PodStatus{
 | 
			
		||||
							Conditions: []corev1.PodCondition{
 | 
			
		||||
								{
 | 
			
		||||
									Status: corev1.ConditionTrue,
 | 
			
		||||
									Type:   corev1.PodReady,
 | 
			
		||||
								},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					{
 | 
			
		||||
						ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
							Name:              "pod-2",
 | 
			
		||||
							Namespace:         metav1.NamespaceDefault,
 | 
			
		||||
							CreationTimestamp: metav1.Date(2016, time.April, 1, 1, 0, 1, 0, time.UTC),
 | 
			
		||||
							Labels:            map[string]string{"test": "selector"},
 | 
			
		||||
						},
 | 
			
		||||
						Status: corev1.PodStatus{
 | 
			
		||||
							Conditions: []corev1.PodCondition{
 | 
			
		||||
								{
 | 
			
		||||
									Status: corev1.ConditionTrue,
 | 
			
		||||
									Type:   corev1.PodReady,
 | 
			
		||||
								},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			expectedNum: 2,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := range tests {
 | 
			
		||||
		test := tests[i]
 | 
			
		||||
		fake := fakeexternal.NewSimpleClientset(test.podList)
 | 
			
		||||
		if len(test.watching) > 0 {
 | 
			
		||||
			watcher := watch.NewFake()
 | 
			
		||||
			for _, event := range test.watching {
 | 
			
		||||
				switch event.Type {
 | 
			
		||||
				case watch.Added:
 | 
			
		||||
					go watcher.Add(event.Object)
 | 
			
		||||
				case watch.Modified:
 | 
			
		||||
					go watcher.Modify(event.Object)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			fake.PrependWatchReactor("pods", testcore.DefaultWatchReactor(watcher, nil))
 | 
			
		||||
		}
 | 
			
		||||
		selector := labels.Set(labelSet).AsSelector()
 | 
			
		||||
		podList, err := GetPodList(fake.CoreV1(), metav1.NamespaceDefault, selector.String(), 1*time.Minute, test.sortBy)
 | 
			
		||||
 | 
			
		||||
		if !test.expectedErr && err != nil {
 | 
			
		||||
			t.Errorf("%s: unexpected error: %v", test.name, err)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if test.expectedErr && err == nil {
 | 
			
		||||
			t.Errorf("%s: expected an error", test.name)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if test.expectedNum != len(podList.Items) {
 | 
			
		||||
			t.Errorf("%s: expected %d pods, got %d", test.name, test.expectedNum, len(podList.Items))
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if !apiequality.Semantic.DeepEqual(test.expected, podList) {
 | 
			
		||||
			t.Errorf("%s:\nexpected podList:\n%#v\ngot:\n%#v\n\n", test.name, test.expected, podList)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
func TestGetFirstPod(t *testing.T) {
 | 
			
		||||
	labelSet := map[string]string{"test": "selector"}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,12 @@ import (
 | 
			
		||||
	"k8s.io/client-go/rest"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// AllPodLogsForObjectFunc is a function type that can tell you how to get logs for a runtime.object
 | 
			
		||||
type AllPodLogsForObjectFunc func(restClientGetter genericclioptions.RESTClientGetter, object, options runtime.Object, timeout time.Duration, allContainers bool) (map[v1.ObjectReference]rest.ResponseWrapper, error)
 | 
			
		||||
 | 
			
		||||
// AllPodLogsForObjectFn gives a way to easily override the function for unit testing if needed.
 | 
			
		||||
var AllPodLogsForObjectFn AllPodLogsForObjectFunc = allPodLogsForObject
 | 
			
		||||
 | 
			
		||||
// LogsForObjectFunc is a function type that can tell you how to get logs for a runtime.object
 | 
			
		||||
type LogsForObjectFunc func(restClientGetter genericclioptions.RESTClientGetter, object, options runtime.Object, timeout time.Duration, allContainers bool) (map[v1.ObjectReference]rest.ResponseWrapper, error)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,19 @@ import (
 | 
			
		||||
	"k8s.io/kubectl/pkg/util/podutils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func allPodLogsForObject(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 {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	clientset, err := corev1client.NewForConfig(clientConfig)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return logsForObjectWithClient(clientset, object, options, timeout, allContainers, true)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
@@ -44,11 +57,11 @@ func logsForObject(restClientGetter genericclioptions.RESTClientGetter, object,
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return logsForObjectWithClient(clientset, object, options, timeout, allContainers)
 | 
			
		||||
	return logsForObjectWithClient(clientset, object, options, timeout, allContainers, false)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// this is split for easy test-ability
 | 
			
		||||
func logsForObjectWithClient(clientset corev1client.CoreV1Interface, object, options runtime.Object, timeout time.Duration, allContainers bool) (map[corev1.ObjectReference]rest.ResponseWrapper, error) {
 | 
			
		||||
func logsForObjectWithClient(clientset corev1client.CoreV1Interface, object, options runtime.Object, timeout time.Duration, allContainers bool, allPods bool) (map[corev1.ObjectReference]rest.ResponseWrapper, error) {
 | 
			
		||||
	opts, ok := options.(*corev1.PodLogOptions)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, errors.New("provided options object is not a PodLogOptions")
 | 
			
		||||
@@ -58,7 +71,7 @@ func logsForObjectWithClient(clientset corev1client.CoreV1Interface, object, opt
 | 
			
		||||
	case *corev1.PodList:
 | 
			
		||||
		ret := make(map[corev1.ObjectReference]rest.ResponseWrapper)
 | 
			
		||||
		for i := range t.Items {
 | 
			
		||||
			currRet, err := logsForObjectWithClient(clientset, &t.Items[i], options, timeout, allContainers)
 | 
			
		||||
			currRet, err := logsForObjectWithClient(clientset, &t.Items[i], options, timeout, allContainers, allPods)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
@@ -95,9 +108,11 @@ func logsForObjectWithClient(clientset corev1client.CoreV1Interface, object, opt
 | 
			
		||||
				// Default to the first container name(aligning behavior with `kubectl exec').
 | 
			
		||||
				currOpts.Container = t.Spec.Containers[0].Name
 | 
			
		||||
				if len(t.Spec.Containers) > 1 || len(t.Spec.InitContainers) > 0 || len(t.Spec.EphemeralContainers) > 0 {
 | 
			
		||||
					if !allPods {
 | 
			
		||||
						fmt.Fprintf(os.Stderr, "Defaulted container %q out of: %s\n", currOpts.Container, podcmd.AllContainerNames(t))
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			container, fieldPath := podcmd.FindContainerByName(t, currOpts.Container)
 | 
			
		||||
			if container == nil {
 | 
			
		||||
@@ -117,7 +132,7 @@ func logsForObjectWithClient(clientset corev1client.CoreV1Interface, object, opt
 | 
			
		||||
		for _, c := range t.Spec.InitContainers {
 | 
			
		||||
			currOpts := opts.DeepCopy()
 | 
			
		||||
			currOpts.Container = c.Name
 | 
			
		||||
			currRet, err := logsForObjectWithClient(clientset, t, currOpts, timeout, false)
 | 
			
		||||
			currRet, err := logsForObjectWithClient(clientset, t, currOpts, timeout, false, false)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
@@ -128,7 +143,7 @@ func logsForObjectWithClient(clientset corev1client.CoreV1Interface, object, opt
 | 
			
		||||
		for _, c := range t.Spec.Containers {
 | 
			
		||||
			currOpts := opts.DeepCopy()
 | 
			
		||||
			currOpts.Container = c.Name
 | 
			
		||||
			currRet, err := logsForObjectWithClient(clientset, t, currOpts, timeout, false)
 | 
			
		||||
			currRet, err := logsForObjectWithClient(clientset, t, currOpts, timeout, false, false)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
@@ -139,7 +154,7 @@ func logsForObjectWithClient(clientset corev1client.CoreV1Interface, object, opt
 | 
			
		||||
		for _, c := range t.Spec.EphemeralContainers {
 | 
			
		||||
			currOpts := opts.DeepCopy()
 | 
			
		||||
			currOpts.Container = c.Name
 | 
			
		||||
			currRet, err := logsForObjectWithClient(clientset, t, currOpts, timeout, false)
 | 
			
		||||
			currRet, err := logsForObjectWithClient(clientset, t, currOpts, timeout, false, false)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
@@ -161,9 +176,18 @@ func logsForObjectWithClient(clientset corev1client.CoreV1Interface, object, opt
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	var targetObj runtime.Object = pod
 | 
			
		||||
 | 
			
		||||
	if numPods > 1 {
 | 
			
		||||
		if allPods {
 | 
			
		||||
			targetObj, err = GetPodList(clientset, namespace, selector.String(), timeout, sortBy)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			fmt.Fprintf(os.Stderr, "Found %v pods, using pod/%v\n", numPods, pod.Name)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return logsForObjectWithClient(clientset, pod, options, timeout, allContainers)
 | 
			
		||||
	return logsForObjectWithClient(clientset, targetObj, options, timeout, allContainers, allPods)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -46,6 +46,7 @@ func TestLogsForObject(t *testing.T) {
 | 
			
		||||
		obj           runtime.Object
 | 
			
		||||
		opts          *corev1.PodLogOptions
 | 
			
		||||
		allContainers bool
 | 
			
		||||
		allPods       bool
 | 
			
		||||
		clientsetPods []runtime.Object
 | 
			
		||||
		actions       []testclient.Action
 | 
			
		||||
 | 
			
		||||
@@ -73,6 +74,7 @@ func TestLogsForObject(t *testing.T) {
 | 
			
		||||
			obj:           testPodWithTwoContainersAndTwoInitAndOneEphemeralContainers(),
 | 
			
		||||
			opts:          &corev1.PodLogOptions{},
 | 
			
		||||
			allContainers: true,
 | 
			
		||||
			allPods:       false,
 | 
			
		||||
			actions: []testclient.Action{
 | 
			
		||||
				getLogsAction("test", &corev1.PodLogOptions{Container: "foo-2-and-2-and-1-initc1"}),
 | 
			
		||||
				getLogsAction("test", &corev1.PodLogOptions{Container: "foo-2-and-2-and-1-initc2"}),
 | 
			
		||||
@@ -221,6 +223,7 @@ func TestLogsForObject(t *testing.T) {
 | 
			
		||||
			},
 | 
			
		||||
			opts:          &corev1.PodLogOptions{},
 | 
			
		||||
			allContainers: true,
 | 
			
		||||
			allPods:       false,
 | 
			
		||||
			actions: []testclient.Action{
 | 
			
		||||
				getLogsAction("test", &corev1.PodLogOptions{Container: "foo-2-and-2-initc1"}),
 | 
			
		||||
				getLogsAction("test", &corev1.PodLogOptions{Container: "foo-2-and-2-initc2"}),
 | 
			
		||||
@@ -385,7 +388,7 @@ func TestLogsForObject(t *testing.T) {
 | 
			
		||||
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		fakeClientset := fakeexternal.NewSimpleClientset(test.clientsetPods...)
 | 
			
		||||
		responses, err := logsForObjectWithClient(fakeClientset.CoreV1(), test.obj, test.opts, 20*time.Second, test.allContainers)
 | 
			
		||||
		responses, err := logsForObjectWithClient(fakeClientset.CoreV1(), test.obj, test.opts, 20*time.Second, test.allContainers, test.allPods)
 | 
			
		||||
		if test.expectedErr == "" && err != nil {
 | 
			
		||||
			t.Errorf("%s: unexpected error: %v", test.name, err)
 | 
			
		||||
			continue
 | 
			
		||||
@@ -504,6 +507,7 @@ func TestLogsForObjectWithClient(t *testing.T) {
 | 
			
		||||
		podLogOptions     *corev1.PodLogOptions
 | 
			
		||||
		expectedFieldPath string
 | 
			
		||||
		allContainers     bool
 | 
			
		||||
		allPods           bool
 | 
			
		||||
		expectedError     string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
@@ -552,6 +556,7 @@ func TestLogsForObjectWithClient(t *testing.T) {
 | 
			
		||||
				return pod
 | 
			
		||||
			},
 | 
			
		||||
			allContainers:     true,
 | 
			
		||||
			allPods:           false,
 | 
			
		||||
			podLogOptions:     &corev1.PodLogOptions{},
 | 
			
		||||
			expectedFieldPath: `spec.containers{foo-2-c2}`,
 | 
			
		||||
		},
 | 
			
		||||
@@ -561,7 +566,7 @@ func TestLogsForObjectWithClient(t *testing.T) {
 | 
			
		||||
		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)
 | 
			
		||||
			responses, err := logsForObjectWithClient(fakeClientset.CoreV1(), pod, tc.podLogOptions, 20*time.Second, tc.allContainers, tc.allPods)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				if len(tc.expectedError) > 0 {
 | 
			
		||||
					if err.Error() == tc.expectedError {
 | 
			
		||||
 
 | 
			
		||||
@@ -20,26 +20,74 @@ package kubectl
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/onsi/ginkgo/v2"
 | 
			
		||||
	"github.com/onsi/gomega"
 | 
			
		||||
 | 
			
		||||
	appsv1 "k8s.io/api/apps/v1"
 | 
			
		||||
	v1 "k8s.io/api/core/v1"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	"k8s.io/apimachinery/pkg/util/uuid"
 | 
			
		||||
	clientset "k8s.io/client-go/kubernetes"
 | 
			
		||||
	"k8s.io/kubectl/pkg/cmd/util/podcmd"
 | 
			
		||||
	"k8s.io/kubernetes/test/e2e/framework"
 | 
			
		||||
	e2edeployment "k8s.io/kubernetes/test/e2e/framework/deployment"
 | 
			
		||||
	e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl"
 | 
			
		||||
	e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
 | 
			
		||||
	e2eoutput "k8s.io/kubernetes/test/e2e/framework/pod/output"
 | 
			
		||||
	imageutils "k8s.io/kubernetes/test/utils/image"
 | 
			
		||||
	admissionapi "k8s.io/pod-security-admission/api"
 | 
			
		||||
 | 
			
		||||
	"github.com/onsi/ginkgo/v2"
 | 
			
		||||
	"github.com/onsi/gomega"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func testingDeployment(name, ns string, numberOfPods int32) appsv1.Deployment {
 | 
			
		||||
	return appsv1.Deployment{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
			Name:      name,
 | 
			
		||||
			Namespace: ns,
 | 
			
		||||
			Labels: map[string]string{
 | 
			
		||||
				"name": name,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		Spec: appsv1.DeploymentSpec{
 | 
			
		||||
			Replicas: &numberOfPods,
 | 
			
		||||
			Selector: &metav1.LabelSelector{
 | 
			
		||||
				MatchLabels: map[string]string{
 | 
			
		||||
					"name": name,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			Template: v1.PodTemplateSpec{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
					Labels: map[string]string{
 | 
			
		||||
						"name": name,
 | 
			
		||||
					},
 | 
			
		||||
					Annotations: map[string]string{
 | 
			
		||||
						podcmd.DefaultContainerAnnotationName: "container-2",
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				Spec: v1.PodSpec{
 | 
			
		||||
					Containers: []v1.Container{
 | 
			
		||||
						{
 | 
			
		||||
							Name:  "container-1",
 | 
			
		||||
							Image: imageutils.GetE2EImage(imageutils.Agnhost),
 | 
			
		||||
							Args:  []string{"logs-generator", "--log-lines-total", "10", "--run-duration", "5s"},
 | 
			
		||||
						},
 | 
			
		||||
						{
 | 
			
		||||
							Name:  "container-2",
 | 
			
		||||
							Image: imageutils.GetE2EImage(imageutils.Agnhost),
 | 
			
		||||
							Args:  []string{"logs-generator", "--log-lines-total", "20", "--run-duration", "5s"},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					RestartPolicy: v1.RestartPolicyAlways,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func testingPod(name, value, defaultContainerName string) v1.Pod {
 | 
			
		||||
	return v1.Pod{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
@@ -93,7 +141,7 @@ var _ = SIGDescribe("Kubectl logs", func() {
 | 
			
		||||
		podName := "logs-generator"
 | 
			
		||||
		containerName := "logs-generator"
 | 
			
		||||
		ginkgo.BeforeEach(func() {
 | 
			
		||||
			ginkgo.By("creating an pod")
 | 
			
		||||
			ginkgo.By("creating a pod")
 | 
			
		||||
			// Agnhost image generates logs for a total of 100 lines over 20s.
 | 
			
		||||
			e2ekubectl.RunKubectlOrDie(ns, "run", podName, "--image="+imageutils.GetE2EImage(imageutils.Agnhost), "--restart=Never", podRunningTimeoutArg, "--", "logs-generator", "--log-lines-total", "100", "--run-duration", "20s")
 | 
			
		||||
		})
 | 
			
		||||
@@ -209,4 +257,100 @@ var _ = SIGDescribe("Kubectl logs", func() {
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	ginkgo.Describe("all pod logs", func() {
 | 
			
		||||
		ginkgo.Describe("the Deployment has 2 replicas and each pod has 2 containers", func() {
 | 
			
		||||
			var deploy *appsv1.Deployment
 | 
			
		||||
			deployName := "deploy-" + string(uuid.NewUUID())
 | 
			
		||||
			numberReplicas := int32(2)
 | 
			
		||||
			ginkgo.BeforeEach(func(ctx context.Context) {
 | 
			
		||||
				deployClient := c.AppsV1().Deployments(ns)
 | 
			
		||||
				ginkgo.By("constructing the Deployment")
 | 
			
		||||
				deployCopy := testingDeployment(deployName, ns, numberReplicas)
 | 
			
		||||
				deploy = &deployCopy
 | 
			
		||||
				ginkgo.By("creating the Deployment")
 | 
			
		||||
				var err error
 | 
			
		||||
				deploy, err = deployClient.Create(ctx, deploy, metav1.CreateOptions{})
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					framework.Failf("Failed to create Deployment: %v", err)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if err = e2edeployment.WaitForDeploymentComplete(c, deploy); err != nil {
 | 
			
		||||
					framework.Failf("Failed to wait for Deployment to complete: %v", err)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			ginkgo.AfterEach(func() {
 | 
			
		||||
				e2ekubectl.RunKubectlOrDie(ns, "delete", "deploy", deployName)
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			ginkgo.It("should get logs from all pods based on default container", func(ctx context.Context) {
 | 
			
		||||
				ginkgo.By("Waiting for Deployment pods to be running.")
 | 
			
		||||
 | 
			
		||||
				// get the pod names
 | 
			
		||||
				pods, err := e2edeployment.GetPodsForDeployment(ctx, c, deploy)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					framework.Failf("Failed to get pods for Deployment: %v", err)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				podOne := pods.Items[0].GetName()
 | 
			
		||||
				podTwo := pods.Items[1].GetName()
 | 
			
		||||
 | 
			
		||||
				ginkgo.By("expecting logs from both replicas in Deployment")
 | 
			
		||||
				out := e2ekubectl.RunKubectlOrDie(ns, "logs", fmt.Sprintf("deploy/%s", deployName), "--all-pods")
 | 
			
		||||
				framework.Logf("got output %q", out)
 | 
			
		||||
				logLines := strings.Split(out, "\n")
 | 
			
		||||
				logFound := false
 | 
			
		||||
				for _, line := range logLines {
 | 
			
		||||
					var deployPod bool
 | 
			
		||||
					if line != "" {
 | 
			
		||||
						if strings.Contains(line, fmt.Sprintf("[pod/%s/container-2]", podOne)) || strings.Contains(line, fmt.Sprintf("[pod/%s/container-2]", podTwo)) {
 | 
			
		||||
							logFound = true
 | 
			
		||||
							deployPod = true
 | 
			
		||||
						}
 | 
			
		||||
						gomega.Expect(deployPod).To(gomega.BeTrueBecause("each log should be from the default container from each pod in the Deployment"))
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
				}
 | 
			
		||||
				gomega.Expect(logFound).To(gomega.BeTrueBecause("log should be present"))
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			ginkgo.It("should get logs from each pod and each container in Deployment", func(ctx context.Context) {
 | 
			
		||||
				ginkgo.By("Waiting for Deployment pods to be running.")
 | 
			
		||||
 | 
			
		||||
				pods, err := e2edeployment.GetPodsForDeployment(ctx, c, deploy)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					framework.Failf("Failed to get pods for Deployment: %v", err)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				podOne := pods.Items[0].GetName()
 | 
			
		||||
				podTwo := pods.Items[1].GetName()
 | 
			
		||||
 | 
			
		||||
				ginkgo.By("all containers and all containers")
 | 
			
		||||
				out := e2ekubectl.RunKubectlOrDie(ns, "logs", fmt.Sprintf("deploy/%s", deployName), "--all-pods", "--all-containers")
 | 
			
		||||
				framework.Logf("got output %q", out)
 | 
			
		||||
				logLines := strings.Split(out, "\n")
 | 
			
		||||
				logFound := false
 | 
			
		||||
				for _, line := range logLines {
 | 
			
		||||
					if line != "" {
 | 
			
		||||
						var deployPodContainer bool
 | 
			
		||||
						if strings.Contains(line, fmt.Sprintf("[pod/%s/container-1]", podOne)) || strings.Contains(line, fmt.Sprintf("[pod/%s/container-1]", podTwo)) || strings.Contains(line, fmt.Sprintf("[pod/%s/container-2]", podOne)) || strings.Contains(line, fmt.Sprintf("[pod/%s/container-2]", podTwo)) {
 | 
			
		||||
							logFound = true
 | 
			
		||||
							deployPodContainer = true
 | 
			
		||||
						}
 | 
			
		||||
						gomega.Expect(deployPodContainer).To(gomega.BeTrueBecause("each log should be from all containers from all pods in the Deployment"))
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				gomega.Expect(logFound).To(gomega.BeTrueBecause("log should be present"))
 | 
			
		||||
				gomega.Expect(strings.Contains(out, fmt.Sprintf("[pod/%s/container-1]", podOne))).To(gomega.BeTrueBecause("pod 1 container 1 log should be present"))
 | 
			
		||||
				gomega.Expect(strings.Contains(out, fmt.Sprintf("[pod/%s/container-2]", podOne))).To(gomega.BeTrueBecause("pod 1 container 2 log should be present"))
 | 
			
		||||
				gomega.Expect(strings.Contains(out, fmt.Sprintf("[pod/%s/container-1]", podTwo))).To(gomega.BeTrueBecause("pod 2 container 1 log should be present"))
 | 
			
		||||
				gomega.Expect(strings.Contains(out, fmt.Sprintf("[pod/%s/container-2]", podTwo))).To(gomega.BeTrueBecause("pod 2 container 2 log should be present"))
 | 
			
		||||
				gomega.Expect(out).NotTo(gomega.BeEmpty())
 | 
			
		||||
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user