Skip to content

Commit f315819

Browse files
Add generic cli options (#283)
1 parent 5445cd5 commit f315819

File tree

10 files changed

+109
-151
lines changed

10 files changed

+109
-151
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,15 @@ Supported Kubernetes resources are `pod`, `replicationcontroller`, `service`, `d
7777
`--config` | `~/.config/stern/config.yaml` | Path to the stern config file
7878
`--container`, `-c` | `.*` | Container name when multiple containers in pod. (regular expression)
7979
`--container-state` | `all` | Tail containers with state in running, waiting, terminated, or all. 'all' matches all container states. To specify multiple states, repeat this or set comma-separated value.
80-
`--context` | | Kubernetes context to use. Default to current context configured in kubeconfig.
80+
`--context` | | The name of the kubeconfig context to use
8181
`--ephemeral-containers` | `true` | Include or exclude ephemeral containers.
8282
`--exclude`, `-e` | `[]` | Log lines to exclude. (regular expression)
8383
`--exclude-container`, `-E` | `[]` | Container name to exclude when multiple containers in pod. (regular expression)
8484
`--exclude-pod` | `[]` | Pod name to exclude. (regular expression)
8585
`--field-selector` | | Selector (field query) to filter on. If present, default to ".*" for the pod-query.
8686
`--include`, `-i` | `[]` | Log lines to include. (regular expression)
8787
`--init-containers` | `true` | Include or exclude init containers.
88-
`--kubeconfig` | | Path to kubeconfig file to use. Default to KUBECONFIG variable then ~/.kube/config path.
88+
`--kubeconfig` | | Path to the kubeconfig file to use for CLI requests.
8989
`--max-log-requests` | `-1` | Maximum number of concurrent logs to request. Defaults to 50, but 5 when specifying --no-follow
9090
`--namespace`, `-n` | | Kubernetes namespace to use. Default to namespace configured in kubernetes context. To specify multiple namespaces, repeat this or set comma-separated value.
9191
`--no-follow` | `false` | Exit when all logs have been shown.
@@ -94,6 +94,7 @@ Supported Kubernetes resources are `pod`, `replicationcontroller`, `service`, `d
9494
`--output`, `-o` | `default` | Specify predefined template. Currently support: [default, raw, json, extjson, ppextjson]
9595
`--prompt`, `-p` | `false` | Toggle interactive prompt for selecting 'app.kubernetes.io/instance' label values.
9696
`--selector`, `-l` | | Selector (label query) to filter on. If present, default to ".*" for the pod-query.
97+
`--show-hidden-options` | `false` | Print a list of hidden options.
9798
`--since`, `-s` | `48h0m0s` | Return logs newer than a relative duration like 5s, 2m, or 3h.
9899
`--tail` | `-1` | The number of lines from the end of the logs to show. Defaults to -1, showing all logs.
99100
`--template` | | Template to use for log lines, leave empty to use --output flag.

cmd/cmd.go

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,19 @@ import (
3838
"k8s.io/apimachinery/pkg/fields"
3939
"k8s.io/apimachinery/pkg/labels"
4040
"k8s.io/cli-runtime/pkg/genericclioptions"
41+
"k8s.io/client-go/kubernetes"
42+
"k8s.io/client-go/tools/clientcmd"
4143
"k8s.io/klog/v2"
44+
45+
// load all auth plugins
46+
_ "k8s.io/client-go/plugin/pkg/client/auth"
4247
)
4348

4449
// Use "~" to avoid exposing the user name in the help message
4550
var defaultConfigFilePath = "~/.config/stern/config.yaml"
4651

4752
type options struct {
53+
configFlags *genericclioptions.ConfigFlags
4854
genericclioptions.IOStreams
4955

5056
excludePod []string
@@ -54,9 +60,7 @@ type options struct {
5460
timestamps string
5561
timezone string
5662
since time.Duration
57-
context string
5863
namespaces []string
59-
kubeConfig string
6064
exclude []string
6165
include []string
6266
initContainers bool
@@ -80,11 +84,20 @@ type options struct {
8084
maxLogRequests int
8185
node string
8286
configFilePath string
87+
showHiddenOptions bool
88+
89+
client kubernetes.Interface
90+
clientConfig clientcmd.ClientConfig
8391
}
8492

8593
func NewOptions(streams genericclioptions.IOStreams) *options {
94+
configFlags := genericclioptions.NewConfigFlags(true)
95+
// stern has its own namespace flag, so disable the one in configFlags
96+
configFlags.Namespace = nil
97+
8698
return &options{
87-
IOStreams: streams,
99+
configFlags: configFlags,
100+
IOStreams: streams,
88101

89102
color: "auto",
90103
container: ".*",
@@ -119,6 +132,23 @@ func (o *options) Complete(args []string) error {
119132
o.configFilePath = envVar
120133
}
121134

135+
o.clientConfig = o.configFlags.ToRawKubeConfigLoader()
136+
137+
restConfig, err := o.configFlags.ToRESTConfig()
138+
if err != nil {
139+
return err
140+
}
141+
142+
o.client = kubernetes.NewForConfigOrDie(restConfig)
143+
144+
if len(o.namespaces) == 0 {
145+
namespace, _, err := o.clientConfig.Namespace()
146+
if err != nil {
147+
return err
148+
}
149+
o.namespaces = []string{namespace}
150+
}
151+
122152
return nil
123153
}
124154

@@ -147,12 +177,12 @@ func (o *options) Run(cmd *cobra.Command) error {
147177
defer cancel()
148178

149179
if o.prompt {
150-
if err := promptHandler(ctx, config, o.Out); err != nil {
180+
if err := promptHandler(ctx, o.client, config, o.Out); err != nil {
151181
return err
152182
}
153183
}
154184

155-
return stern.Run(ctx, config)
185+
return stern.Run(ctx, o.client, config)
156186
}
157187

158188
func (o *options) sternConfig() (*stern.Config, error) {
@@ -257,8 +287,6 @@ func (o *options) sternConfig() (*stern.Config, error) {
257287
}
258288

259289
return &stern.Config{
260-
KubeConfig: o.kubeConfig,
261-
ContextName: o.context,
262290
Namespaces: namespaces,
263291
PodQuery: pod,
264292
ExcludePodQuery: excludePod,
@@ -351,22 +379,20 @@ func (o *options) overrideFlagSetDefaultFromConfig(fs *pflag.FlagSet) error {
351379

352380
// AddFlags adds all the flags used by stern.
353381
func (o *options) AddFlags(fs *pflag.FlagSet) {
382+
o.addKubernetesFlags(fs)
383+
354384
fs.BoolVarP(&o.allNamespaces, "all-namespaces", "A", o.allNamespaces, "If present, tail across all namespaces. A specific namespace is ignored even if specified with --namespace.")
355385
fs.StringVar(&o.color, "color", o.color, "Force set color output. 'auto': colorize if tty attached, 'always': always colorize, 'never': never colorize.")
356386
fs.StringVar(&o.completion, "completion", o.completion, "Output stern command-line completion code for the specified shell. Can be 'bash', 'zsh' or 'fish'.")
357387
fs.StringVarP(&o.container, "container", "c", o.container, "Container name when multiple containers in pod. (regular expression)")
358388
fs.StringSliceVar(&o.containerStates, "container-state", o.containerStates, "Tail containers with state in running, waiting, terminated, or all. 'all' matches all container states. To specify multiple states, repeat this or set comma-separated value.")
359-
fs.StringVar(&o.context, "context", o.context, "Kubernetes context to use. Default to current context configured in kubeconfig.")
360389
fs.StringArrayVarP(&o.exclude, "exclude", "e", o.exclude, "Log lines to exclude. (regular expression)")
361390
fs.StringArrayVarP(&o.excludeContainer, "exclude-container", "E", o.excludeContainer, "Container name to exclude when multiple containers in pod. (regular expression)")
362391
fs.StringArrayVar(&o.excludePod, "exclude-pod", o.excludePod, "Pod name to exclude. (regular expression)")
363392
fs.BoolVar(&o.noFollow, "no-follow", o.noFollow, "Exit when all logs have been shown.")
364393
fs.StringArrayVarP(&o.include, "include", "i", o.include, "Log lines to include. (regular expression)")
365394
fs.BoolVar(&o.initContainers, "init-containers", o.initContainers, "Include or exclude init containers.")
366395
fs.BoolVar(&o.ephemeralContainers, "ephemeral-containers", o.ephemeralContainers, "Include or exclude ephemeral containers.")
367-
fs.StringVar(&o.kubeConfig, "kubeconfig", o.kubeConfig, "Path to kubeconfig file to use. Default to KUBECONFIG variable then ~/.kube/config path.")
368-
fs.StringVar(&o.kubeConfig, "kube-config", o.kubeConfig, "Path to kubeconfig file to use.")
369-
_ = fs.MarkDeprecated("kube-config", "Use --kubeconfig instead.")
370396
fs.StringSliceVarP(&o.namespaces, "namespace", "n", o.namespaces, "Kubernetes namespace to use. Default to namespace configured in kubernetes context. To specify multiple namespaces, repeat this or set comma-separated value.")
371397
fs.StringVar(&o.node, "node", o.node, "Node name to filter on.")
372398
fs.IntVar(&o.maxLogRequests, "max-log-requests", o.maxLogRequests, "Maximum number of concurrent logs to request. Defaults to 50, but 5 when specifying --no-follow")
@@ -384,10 +410,39 @@ func (o *options) AddFlags(fs *pflag.FlagSet) {
384410
fs.StringVar(&o.configFilePath, "config", o.configFilePath, "Path to the stern config file")
385411
fs.IntVar(&o.verbosity, "verbosity", o.verbosity, "Number of the log level verbosity")
386412
fs.BoolVarP(&o.version, "version", "v", o.version, "Print the version and exit.")
413+
fs.BoolVar(&o.showHiddenOptions, "show-hidden-options", o.showHiddenOptions, "Print a list of hidden options.")
387414

388415
fs.Lookup("timestamps").NoOptDefVal = "default"
389416
}
390417

418+
func (o *options) addKubernetesFlags(fs *pflag.FlagSet) {
419+
flagset := pflag.NewFlagSet("", pflag.ExitOnError)
420+
o.configFlags.AddFlags(flagset)
421+
flagset.VisitAll(func(f *pflag.Flag) {
422+
// Hide Kubernetes flags except some
423+
if !(f.Name == "kubeconfig" || f.Name == "context") {
424+
f.Hidden = true
425+
}
426+
427+
// `server` flag in configFlags has `s` shorthand, which is used by stern
428+
// as shorthand for `since` flag, so do not use it.
429+
if f.Name == "server" {
430+
f.Shorthand = ""
431+
}
432+
})
433+
fs.AddFlagSet(flagset)
434+
}
435+
436+
func (o *options) outputHiddenOptions() {
437+
fs := pflag.NewFlagSet("", pflag.ExitOnError)
438+
o.AddFlags(fs)
439+
fs.VisitAll(func(f *pflag.Flag) {
440+
f.Hidden = !f.Hidden
441+
})
442+
fmt.Println("The following options can also be used in stern:")
443+
fs.PrintDefaults()
444+
}
445+
391446
func (o *options) generateTemplate() (*template.Template, error) {
392447
t := o.template
393448
if o.templateFile != "" {
@@ -588,6 +643,11 @@ func NewSternCmd(stream genericclioptions.IOStreams) (*cobra.Command, error) {
588643
return runCompletion(o.completion, cmd, o.Out)
589644
}
590645

646+
if o.showHiddenOptions {
647+
o.outputHiddenOptions()
648+
return nil
649+
}
650+
591651
if err := o.Complete(args); err != nil {
592652
return err
593653
}
@@ -607,6 +667,8 @@ func NewSternCmd(stream genericclioptions.IOStreams) (*cobra.Command, error) {
607667
ValidArgsFunction: queryCompletionFunc(o),
608668
}
609669

670+
cmd.SetUsageTemplate(cmd.UsageTemplate() + "\nUse \"stern --show-hidden-options\" for a list of hidden command-line options.\n")
671+
610672
o.AddFlags(cmd.Flags())
611673

612674
if err := registerCompletionFuncForFlags(cmd, o); err != nil {

cmd/cmd_test.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -448,8 +448,6 @@ func TestOptionsSternConfig(t *testing.T) {
448448

449449
defaultConfig := func() *stern.Config {
450450
return &stern.Config{
451-
KubeConfig: "",
452-
ContextName: "",
453451
Namespaces: []string{},
454452
PodQuery: re(""),
455453
ExcludePodQuery: nil,
@@ -495,8 +493,6 @@ func TestOptionsSternConfig(t *testing.T) {
495493
"change all options",
496494
func() *options {
497495
o := NewOptions(streams)
498-
o.kubeConfig = "kubeconfig1"
499-
o.context = "context1"
500496
o.namespaces = []string{"ns1", "ns2"}
501497
o.podQuery = "query1"
502498
o.excludePod = []string{"exp1", "exp2"}
@@ -524,8 +520,6 @@ func TestOptionsSternConfig(t *testing.T) {
524520
}(),
525521
func() *stern.Config {
526522
c := defaultConfig()
527-
c.KubeConfig = "kubeconfig1"
528-
c.ContextName = "context1"
529523
c.Namespaces = []string{"ns1", "ns2"}
530524
c.PodQuery = re("query1")
531525
c.ExcludePodQuery = []*regexp.Regexp{re("exp1"), re("exp2")}

cmd/flag_completion.go

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,9 @@ import (
2323

2424
"github.com/pkg/errors"
2525
"github.com/spf13/cobra"
26-
"github.com/stern/stern/kubernetes"
2726
"github.com/stern/stern/stern"
2827
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29-
clientset "k8s.io/client-go/kubernetes"
28+
"k8s.io/client-go/kubernetes"
3029
)
3130

3231
var flagChoices = map[string][]string{
@@ -99,13 +98,11 @@ func registerCompletionFuncForFlags(cmd *cobra.Command, o *options) error {
9998
// that match the toComplete prefix.
10099
func namespaceCompletionFunc(o *options) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
101100
return func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
102-
clientConfig := kubernetes.NewClientConfig(o.kubeConfig, o.context)
103-
clientset, err := kubernetes.NewClientSet(clientConfig)
104-
if err != nil {
101+
if err := o.Complete(nil); err != nil {
105102
return compError(err)
106103
}
107104

108-
namespaceList, err := clientset.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
105+
namespaceList, err := o.client.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
109106
if err != nil {
110107
return compError(err)
111108
}
@@ -125,13 +122,11 @@ func namespaceCompletionFunc(o *options) func(cmd *cobra.Command, args []string,
125122
// that match the toComplete prefix.
126123
func nodeCompletionFunc(o *options) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
127124
return func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
128-
clientConfig := kubernetes.NewClientConfig(o.kubeConfig, o.context)
129-
clientset, err := kubernetes.NewClientSet(clientConfig)
130-
if err != nil {
125+
if err := o.Complete(nil); err != nil {
131126
return compError(err)
132127
}
133128

134-
nodeList, err := clientset.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{})
129+
nodeList, err := o.client.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{})
135130
if err != nil {
136131
return compError(err)
137132
}
@@ -151,14 +146,16 @@ func nodeCompletionFunc(o *options) func(cmd *cobra.Command, args []string, toCo
151146
// that match the toComplete prefix.
152147
func contextCompletionFunc(o *options) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
153148
return func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
154-
clientConfig := kubernetes.NewClientConfig(o.kubeConfig, o.context)
155-
config, err := clientConfig.RawConfig()
156-
if err != nil {
149+
if err := o.Complete(nil); err != nil {
157150
return compError(err)
158151
}
159152

160153
var comps []string
161-
for name := range config.Contexts {
154+
kubeConfig, err := o.clientConfig.RawConfig()
155+
if err != nil {
156+
return compError(err)
157+
}
158+
for name := range kubeConfig.Contexts {
162159
if strings.HasPrefix(name, toComplete) {
163160
comps = append(comps, name)
164161
}
@@ -172,6 +169,10 @@ func contextCompletionFunc(o *options) func(cmd *cobra.Command, args []string, t
172169
// that match the toComplete prefix.
173170
func queryCompletionFunc(o *options) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
174171
return func(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
172+
if err := o.Complete(nil); err != nil {
173+
return compError(err)
174+
}
175+
175176
var comps []string
176177
parts := strings.Split(toComplete, "/")
177178
if len(parts) != 2 {
@@ -191,24 +192,19 @@ func queryCompletionFunc(o *options) func(cmd *cobra.Command, args []string, toC
191192
return compError(errors.New("multiple namespaces are not supported"))
192193
}
193194

194-
clientConfig := kubernetes.NewClientConfig(o.kubeConfig, o.context)
195-
clientset, err := kubernetes.NewClientSet(clientConfig)
196-
if err != nil {
197-
return compError(err)
198-
}
199195
var namespace string
200196
if len(uniqueNamespaces) == 1 {
201197
namespace = uniqueNamespaces[0]
202198
} else {
203-
n, _, err := clientConfig.Namespace()
199+
n, _, err := o.clientConfig.Namespace()
204200
if err != nil {
205201
return compError(err)
206202
}
207203
namespace = n
208204
}
209205

210206
kind, name := parts[0], parts[1]
211-
names, err := retrieveNamesFromResource(context.TODO(), clientset, namespace, kind)
207+
names, err := retrieveNamesFromResource(context.TODO(), o.client, namespace, kind)
212208
if err != nil {
213209
return compError(err)
214210
}
@@ -226,7 +222,7 @@ func compError(err error) ([]string, cobra.ShellCompDirective) {
226222
return nil, cobra.ShellCompDirectiveError
227223
}
228224

229-
func retrieveNamesFromResource(ctx context.Context, client clientset.Interface, namespace, kind string) ([]string, error) {
225+
func retrieveNamesFromResource(ctx context.Context, client kubernetes.Interface, namespace, kind string) ([]string, error) {
230226
opt := metav1.ListOptions{}
231227
var names []string
232228
switch {

cmd/flag_prompt.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ import (
1111
"github.com/pkg/errors"
1212
"github.com/stern/stern/stern"
1313
"k8s.io/apimachinery/pkg/labels"
14+
"k8s.io/client-go/kubernetes"
1415
)
1516

1617
// promptHandler invokes the interactive prompt and updates config.LabelSelector with the selected value.
17-
func promptHandler(ctx context.Context, config *stern.Config, out io.Writer) error {
18-
labelsMap, err := stern.List(ctx, config)
18+
func promptHandler(ctx context.Context, client kubernetes.Interface, config *stern.Config, out io.Writer) error {
19+
labelsMap, err := stern.List(ctx, client, config)
1920
if err != nil {
2021
return err
2122
}

hack/update-readme/update-readme.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ func GenerateFlagsMarkdownTable() string {
5959
return
6060
}
6161

62+
if flag.Hidden {
63+
return
64+
}
65+
6266
flagText := ""
6367
if flag.Shorthand != "" {
6468
flagText = fmt.Sprintf(" `--%s`, `-%s` ", flag.Name, flag.Shorthand)

0 commit comments

Comments
 (0)