Merge pull request #124732 from cmwylie19/1520
feat: add all-pods log flag to kubectl Kubernetes-commit: 9f39314eb9b1f0127500cf7f0983fb00f90d49d1
This commit is contained in:
commit
b9d568d7e4
|
@ -99,6 +99,7 @@ type LogsOptions struct {
|
|||
Namespace string
|
||||
ResourceArg string
|
||||
AllContainers bool
|
||||
AllPods bool
|
||||
Options runtime.Object
|
||||
Resources []string
|
||||
|
||||
|
@ -122,10 +123,11 @@ type LogsOptions struct {
|
|||
MaxFollowConcurrency int
|
||||
Prefix bool
|
||||
|
||||
Object runtime.Object
|
||||
GetPodTimeout time.Duration
|
||||
RESTClientGetter genericclioptions.RESTClientGetter
|
||||
LogsForObject polymorphichelpers.LogsForObjectFunc
|
||||
Object runtime.Object
|
||||
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,7 +108,9 @@ 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 {
|
||||
fmt.Fprintf(os.Stderr, "Defaulted container %q out of: %s\n", currOpts.Container, podcmd.AllContainerNames(t))
|
||||
if !allPods {
|
||||
fmt.Fprintf(os.Stderr, "Defaulted container %q out of: %s\n", currOpts.Container, podcmd.AllContainerNames(t))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
fmt.Fprintf(os.Stderr, "Found %v pods, using pod/%v\n", numPods, pod.Name)
|
||||
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 {
|
||||
|
|
Loading…
Reference in New Issue