Skip to content

Commit eefaafc

Browse files
committed
Prompt the user to select a container if they don't provide one
and we can't infer it.
1 parent 74c0dd1 commit eefaafc

File tree

4 files changed

+187
-4
lines changed

4 files changed

+187
-4
lines changed

docs/kubectl-log.md

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ $ kubectl log -f 123456-7890 ruby-container
2424

2525
```
2626
-f, --follow=false: Specify if the logs should be streamed.
27+
--interactive=true: If true, prompt the user for input when required. Default true.
2728
```
2829

2930
### Options inherrited from parent commands

docs/man/man1/kubectl-log.1

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ Print the logs for a container in a pod. If the pod has only one container, the
2121
\fB\-f\fP, \fB\-\-follow\fP=false
2222
Specify if the logs should be streamed.
2323

24+
.PP
25+
\fB\-\-interactive\fP=true
26+
If true, prompt the user for input when required. Default true.
27+
2428

2529
.SH OPTIONS INHERITED FROM PARENT COMMANDS
2630
.PP

pkg/kubectl/cmd/log.go

+35-4
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,14 @@ limitations under the License.
1717
package cmd
1818

1919
import (
20+
"fmt"
2021
"io"
22+
"os"
2123
"strconv"
2224

25+
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
2326
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util"
27+
libutil "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
2428
"github.com/spf13/cobra"
2529
)
2630

@@ -32,6 +36,28 @@ $ kubectl log 123456-7890 ruby-container
3236
$ kubectl log -f 123456-7890 ruby-container`
3337
)
3438

39+
func selectContainer(pod *api.Pod, in io.Reader, out io.Writer) string {
40+
fmt.Fprintf(out, "Please select a container:\n")
41+
options := libutil.StringSet{}
42+
for ix := range pod.Spec.Containers {
43+
fmt.Fprintf(out, "[%d] %s\n", ix+1, pod.Spec.Containers[ix].Name)
44+
options.Insert(pod.Spec.Containers[ix].Name)
45+
}
46+
for {
47+
var input string
48+
fmt.Fprintf(out, "> ")
49+
fmt.Fscanln(in, &input)
50+
if options.Has(input) {
51+
return input
52+
}
53+
ix, err := strconv.Atoi(input)
54+
if err == nil && ix > 0 && ix <= len(pod.Spec.Containers) {
55+
return pod.Spec.Containers[ix-1].Name
56+
}
57+
fmt.Fprintf(out, "Invalid input: %s", input)
58+
}
59+
}
60+
3561
func (f *Factory) NewCmdLog(out io.Writer) *cobra.Command {
3662
cmd := &cobra.Command{
3763
Use: "log [-f] <pod> [<container>]",
@@ -60,11 +86,15 @@ func (f *Factory) NewCmdLog(out io.Writer) *cobra.Command {
6086
var container string
6187
if len(args) == 1 {
6288
if len(pod.Spec.Containers) != 1 {
63-
usageError(cmd, "<container> is required for pods with multiple containers")
89+
if !util.GetFlagBool(cmd, "interactive") {
90+
usageError(cmd, "<container> is required for pods with multiple containers")
91+
} else {
92+
container = selectContainer(pod, os.Stdin, out)
93+
}
94+
} else {
95+
// Get logs for the only container in the pod
96+
container = pod.Spec.Containers[0].Name
6497
}
65-
66-
// Get logs for the only container in the pod
67-
container = pod.Spec.Containers[0].Name
6898
} else {
6999
container = args[1]
70100
}
@@ -89,5 +119,6 @@ func (f *Factory) NewCmdLog(out io.Writer) *cobra.Command {
89119
},
90120
}
91121
cmd.Flags().BoolP("follow", "f", false, "Specify if the logs should be streamed.")
122+
cmd.Flags().Bool("interactive", true, "If true, prompt the user for input when required. Default true.")
92123
return cmd
93124
}

pkg/kubectl/cmd/log_test.go

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/*
2+
Copyright 2014 Google Inc. All rights reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package cmd
18+
19+
import (
20+
"bytes"
21+
"testing"
22+
23+
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
24+
)
25+
26+
func TestSelectContainer(t *testing.T) {
27+
tests := []struct {
28+
input string
29+
pod api.Pod
30+
expectedContainer string
31+
}{
32+
{
33+
input: "1\n",
34+
pod: api.Pod{
35+
Spec: api.PodSpec{
36+
Containers: []api.Container{
37+
{
38+
Name: "foo",
39+
},
40+
},
41+
},
42+
},
43+
expectedContainer: "foo",
44+
},
45+
{
46+
input: "foo\n",
47+
pod: api.Pod{
48+
Spec: api.PodSpec{
49+
Containers: []api.Container{
50+
{
51+
Name: "foo",
52+
},
53+
},
54+
},
55+
},
56+
expectedContainer: "foo",
57+
},
58+
{
59+
input: "foo\n",
60+
pod: api.Pod{
61+
Spec: api.PodSpec{
62+
Containers: []api.Container{
63+
{
64+
Name: "bar",
65+
},
66+
{
67+
Name: "foo",
68+
},
69+
},
70+
},
71+
},
72+
expectedContainer: "foo",
73+
},
74+
{
75+
input: "2\n",
76+
pod: api.Pod{
77+
Spec: api.PodSpec{
78+
Containers: []api.Container{
79+
{
80+
Name: "bar",
81+
},
82+
{
83+
Name: "foo",
84+
},
85+
},
86+
},
87+
},
88+
expectedContainer: "foo",
89+
},
90+
{
91+
input: "-1\n2\n",
92+
pod: api.Pod{
93+
Spec: api.PodSpec{
94+
Containers: []api.Container{
95+
{
96+
Name: "bar",
97+
},
98+
{
99+
Name: "foo",
100+
},
101+
},
102+
},
103+
},
104+
expectedContainer: "foo",
105+
},
106+
{
107+
input: "3\n2\n",
108+
pod: api.Pod{
109+
Spec: api.PodSpec{
110+
Containers: []api.Container{
111+
{
112+
Name: "bar",
113+
},
114+
{
115+
Name: "foo",
116+
},
117+
},
118+
},
119+
},
120+
expectedContainer: "foo",
121+
},
122+
{
123+
input: "baz\n2\n",
124+
pod: api.Pod{
125+
Spec: api.PodSpec{
126+
Containers: []api.Container{
127+
{
128+
Name: "bar",
129+
},
130+
{
131+
Name: "foo",
132+
},
133+
},
134+
},
135+
},
136+
expectedContainer: "foo",
137+
},
138+
}
139+
140+
for _, test := range tests {
141+
var buff bytes.Buffer
142+
container := selectContainer(&test.pod, bytes.NewBufferString(test.input), &buff)
143+
if container != test.expectedContainer {
144+
t.Errorf("unexpected output: %s for input: %s", container, test.input)
145+
}
146+
}
147+
}

0 commit comments

Comments
 (0)