1
16
17 package cmd
18
19 import (
20 "fmt"
21 "net/http"
22 "os"
23 "os/exec"
24 "path/filepath"
25 "runtime"
26 "strings"
27 "syscall"
28
29 "github.com/spf13/cobra"
30
31 "k8s.io/cli-runtime/pkg/genericiooptions"
32 "k8s.io/client-go/rest"
33 "k8s.io/client-go/tools/clientcmd"
34 cliflag "k8s.io/component-base/cli/flag"
35 "k8s.io/klog/v2"
36 "k8s.io/kubectl/pkg/cmd/annotate"
37 "k8s.io/kubectl/pkg/cmd/apiresources"
38 "k8s.io/kubectl/pkg/cmd/apply"
39 "k8s.io/kubectl/pkg/cmd/attach"
40 "k8s.io/kubectl/pkg/cmd/auth"
41 "k8s.io/kubectl/pkg/cmd/autoscale"
42 "k8s.io/kubectl/pkg/cmd/certificates"
43 "k8s.io/kubectl/pkg/cmd/clusterinfo"
44 "k8s.io/kubectl/pkg/cmd/completion"
45 cmdconfig "k8s.io/kubectl/pkg/cmd/config"
46 "k8s.io/kubectl/pkg/cmd/cp"
47 "k8s.io/kubectl/pkg/cmd/create"
48 "k8s.io/kubectl/pkg/cmd/debug"
49 "k8s.io/kubectl/pkg/cmd/delete"
50 "k8s.io/kubectl/pkg/cmd/describe"
51 "k8s.io/kubectl/pkg/cmd/diff"
52 "k8s.io/kubectl/pkg/cmd/drain"
53 "k8s.io/kubectl/pkg/cmd/edit"
54 "k8s.io/kubectl/pkg/cmd/events"
55 cmdexec "k8s.io/kubectl/pkg/cmd/exec"
56 "k8s.io/kubectl/pkg/cmd/explain"
57 "k8s.io/kubectl/pkg/cmd/expose"
58 "k8s.io/kubectl/pkg/cmd/get"
59 "k8s.io/kubectl/pkg/cmd/label"
60 "k8s.io/kubectl/pkg/cmd/logs"
61 "k8s.io/kubectl/pkg/cmd/options"
62 "k8s.io/kubectl/pkg/cmd/patch"
63 "k8s.io/kubectl/pkg/cmd/plugin"
64 "k8s.io/kubectl/pkg/cmd/portforward"
65 "k8s.io/kubectl/pkg/cmd/proxy"
66 "k8s.io/kubectl/pkg/cmd/replace"
67 "k8s.io/kubectl/pkg/cmd/rollout"
68 "k8s.io/kubectl/pkg/cmd/run"
69 "k8s.io/kubectl/pkg/cmd/scale"
70 "k8s.io/kubectl/pkg/cmd/set"
71 "k8s.io/kubectl/pkg/cmd/taint"
72 "k8s.io/kubectl/pkg/cmd/top"
73 cmdutil "k8s.io/kubectl/pkg/cmd/util"
74 "k8s.io/kubectl/pkg/cmd/version"
75 "k8s.io/kubectl/pkg/cmd/wait"
76 utilcomp "k8s.io/kubectl/pkg/util/completion"
77 "k8s.io/kubectl/pkg/util/i18n"
78 "k8s.io/kubectl/pkg/util/templates"
79 "k8s.io/kubectl/pkg/util/term"
80
81 "k8s.io/cli-runtime/pkg/genericclioptions"
82 "k8s.io/kubectl/pkg/cmd/kustomize"
83 )
84
85 const kubectlCmdHeaders = "KUBECTL_COMMAND_HEADERS"
86
87 type KubectlOptions struct {
88 PluginHandler PluginHandler
89 Arguments []string
90 ConfigFlags *genericclioptions.ConfigFlags
91
92 genericiooptions.IOStreams
93 }
94
95 func defaultConfigFlags() *genericclioptions.ConfigFlags {
96 return genericclioptions.NewConfigFlags(true).WithDeprecatedPasswordFlag().WithDiscoveryBurst(300).WithDiscoveryQPS(50.0)
97 }
98
99
100 func NewDefaultKubectlCommand() *cobra.Command {
101 ioStreams := genericiooptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
102 return NewDefaultKubectlCommandWithArgs(KubectlOptions{
103 PluginHandler: NewDefaultPluginHandler(plugin.ValidPluginFilenamePrefixes),
104 Arguments: os.Args,
105 ConfigFlags: defaultConfigFlags().WithWarningPrinter(ioStreams),
106 IOStreams: ioStreams,
107 })
108 }
109
110
111 func NewDefaultKubectlCommandWithArgs(o KubectlOptions) *cobra.Command {
112 cmd := NewKubectlCommand(o)
113
114 if o.PluginHandler == nil {
115 return cmd
116 }
117
118 if len(o.Arguments) > 1 {
119 cmdPathPieces := o.Arguments[1:]
120
121
122
123 if foundCmd, foundArgs, err := cmd.Find(cmdPathPieces); err != nil {
124
125
126
127 var cmdName string
128 for _, arg := range cmdPathPieces {
129 if !strings.HasPrefix(arg, "-") {
130 cmdName = arg
131 break
132 }
133 }
134
135 switch cmdName {
136 case "help", cobra.ShellCompRequestCmd, cobra.ShellCompNoDescRequestCmd:
137
138 default:
139 if err := HandlePluginCommand(o.PluginHandler, cmdPathPieces, false); err != nil {
140 fmt.Fprintf(o.IOStreams.ErrOut, "Error: %v\n", err)
141 os.Exit(1)
142 }
143 }
144 } else if err == nil {
145 if !cmdutil.CmdPluginAsSubcommand.IsDisabled() {
146
147
148
149 if IsSubcommandPluginAllowed(foundCmd.Name()) && len(foundArgs) >= 1 && !strings.HasPrefix(foundArgs[0], "-") {
150 subcommand := foundArgs[0]
151 builtinSubcmdExist := false
152 for _, subcmd := range foundCmd.Commands() {
153 if subcmd.Name() == subcommand {
154 builtinSubcmdExist = true
155 break
156 }
157 }
158
159 if !builtinSubcmdExist {
160 if err := HandlePluginCommand(o.PluginHandler, cmdPathPieces, true); err != nil {
161 fmt.Fprintf(o.IOStreams.ErrOut, "Error: %v\n", err)
162 os.Exit(1)
163 }
164 }
165 }
166 }
167 }
168 }
169
170 return cmd
171 }
172
173
174
175 func IsSubcommandPluginAllowed(foundCmd string) bool {
176 allowedCmds := map[string]struct{}{"create": {}}
177 _, ok := allowedCmds[foundCmd]
178 return ok
179 }
180
181
182
183
184 type PluginHandler interface {
185
186
187
188
189 Lookup(filename string) (string, bool)
190
191
192
193 Execute(executablePath string, cmdArgs, environment []string) error
194 }
195
196
197 type DefaultPluginHandler struct {
198 ValidPrefixes []string
199 }
200
201
202
203 func NewDefaultPluginHandler(validPrefixes []string) *DefaultPluginHandler {
204 return &DefaultPluginHandler{
205 ValidPrefixes: validPrefixes,
206 }
207 }
208
209
210 func (h *DefaultPluginHandler) Lookup(filename string) (string, bool) {
211 for _, prefix := range h.ValidPrefixes {
212 path, err := exec.LookPath(fmt.Sprintf("%s-%s", prefix, filename))
213 if shouldSkipOnLookPathErr(err) || len(path) == 0 {
214 continue
215 }
216 return path, true
217 }
218 return "", false
219 }
220
221 func Command(name string, arg ...string) *exec.Cmd {
222 cmd := &exec.Cmd{
223 Path: name,
224 Args: append([]string{name}, arg...),
225 }
226 if filepath.Base(name) == name {
227 lp, err := exec.LookPath(name)
228 if lp != "" && !shouldSkipOnLookPathErr(err) {
229
230
231
232 cmd.Path = lp
233 }
234 }
235 return cmd
236 }
237
238
239 func (h *DefaultPluginHandler) Execute(executablePath string, cmdArgs, environment []string) error {
240
241
242 if runtime.GOOS == "windows" {
243 cmd := Command(executablePath, cmdArgs...)
244 cmd.Stdout = os.Stdout
245 cmd.Stderr = os.Stderr
246 cmd.Stdin = os.Stdin
247 cmd.Env = environment
248 err := cmd.Run()
249 if err == nil {
250 os.Exit(0)
251 }
252 return err
253 }
254
255
256
257 return syscall.Exec(executablePath, append([]string{executablePath}, cmdArgs...), environment)
258 }
259
260
261
262 func HandlePluginCommand(pluginHandler PluginHandler, cmdArgs []string, exactMatch bool) error {
263 var remainingArgs []string
264 for _, arg := range cmdArgs {
265 if strings.HasPrefix(arg, "-") {
266 break
267 }
268 remainingArgs = append(remainingArgs, strings.Replace(arg, "-", "_", -1))
269 }
270
271 if len(remainingArgs) == 0 {
272
273 return fmt.Errorf("flags cannot be placed before plugin name: %s", cmdArgs[0])
274 }
275
276 foundBinaryPath := ""
277
278
279 for len(remainingArgs) > 0 {
280 path, found := pluginHandler.Lookup(strings.Join(remainingArgs, "-"))
281 if !found {
282 if exactMatch {
283
284
285
286 break
287 }
288 remainingArgs = remainingArgs[:len(remainingArgs)-1]
289 continue
290 }
291
292 foundBinaryPath = path
293 break
294 }
295
296 if len(foundBinaryPath) == 0 {
297 return nil
298 }
299
300
301 if err := pluginHandler.Execute(foundBinaryPath, cmdArgs[len(remainingArgs):], os.Environ()); err != nil {
302 return err
303 }
304
305 return nil
306 }
307
308
309 func NewKubectlCommand(o KubectlOptions) *cobra.Command {
310 warningHandler := rest.NewWarningWriter(o.IOStreams.ErrOut, rest.WarningWriterOptions{Deduplicate: true, Color: term.AllowsColorOutput(o.IOStreams.ErrOut)})
311 warningsAsErrors := false
312
313 cmds := &cobra.Command{
314 Use: "kubectl",
315 Short: i18n.T("kubectl controls the Kubernetes cluster manager"),
316 Long: templates.LongDesc(`
317 kubectl controls the Kubernetes cluster manager.
318
319 Find more information at:
320 https://kubernetes.io/docs/reference/kubectl/`),
321 Run: runHelp,
322
323
324 PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
325 rest.SetDefaultWarningHandler(warningHandler)
326
327 if cmd.Name() == cobra.ShellCompRequestCmd {
328
329
330 plugin.SetupPluginCompletion(cmd, args)
331 }
332
333 return initProfiling()
334 },
335 PersistentPostRunE: func(*cobra.Command, []string) error {
336 if err := flushProfiling(); err != nil {
337 return err
338 }
339 if warningsAsErrors {
340 count := warningHandler.WarningCount()
341 switch count {
342 case 0:
343
344 case 1:
345 return fmt.Errorf("%d warning received", count)
346 default:
347 return fmt.Errorf("%d warnings received", count)
348 }
349 }
350 return nil
351 },
352 }
353
354
355 cmds.SetGlobalNormalizationFunc(cliflag.WarnWordSepNormalizeFunc)
356
357 flags := cmds.PersistentFlags()
358
359 addProfilingFlags(flags)
360
361 flags.BoolVar(&warningsAsErrors, "warnings-as-errors", warningsAsErrors, "Treat warnings received from the server as errors and exit with a non-zero exit code")
362
363 kubeConfigFlags := o.ConfigFlags
364 if kubeConfigFlags == nil {
365 kubeConfigFlags = defaultConfigFlags().WithWarningPrinter(o.IOStreams)
366 }
367 kubeConfigFlags.AddFlags(flags)
368 matchVersionKubeConfigFlags := cmdutil.NewMatchVersionFlags(kubeConfigFlags)
369 matchVersionKubeConfigFlags.AddFlags(flags)
370
371 addCmdHeaderHooks(cmds, kubeConfigFlags)
372
373 f := cmdutil.NewFactory(matchVersionKubeConfigFlags)
374
375
376
377 proxyCmd := proxy.NewCmdProxy(f, o.IOStreams)
378 proxyCmd.PreRun = func(cmd *cobra.Command, args []string) {
379 kubeConfigFlags.WrapConfigFn = nil
380 }
381
382
383 getCmd := get.NewCmdGet("kubectl", f, o.IOStreams)
384 getCmd.ValidArgsFunction = utilcomp.ResourceTypeAndNameCompletionFunc(f)
385
386 groups := templates.CommandGroups{
387 {
388 Message: "Basic Commands (Beginner):",
389 Commands: []*cobra.Command{
390 create.NewCmdCreate(f, o.IOStreams),
391 expose.NewCmdExposeService(f, o.IOStreams),
392 run.NewCmdRun(f, o.IOStreams),
393 set.NewCmdSet(f, o.IOStreams),
394 },
395 },
396 {
397 Message: "Basic Commands (Intermediate):",
398 Commands: []*cobra.Command{
399 explain.NewCmdExplain("kubectl", f, o.IOStreams),
400 getCmd,
401 edit.NewCmdEdit(f, o.IOStreams),
402 delete.NewCmdDelete(f, o.IOStreams),
403 },
404 },
405 {
406 Message: "Deploy Commands:",
407 Commands: []*cobra.Command{
408 rollout.NewCmdRollout(f, o.IOStreams),
409 scale.NewCmdScale(f, o.IOStreams),
410 autoscale.NewCmdAutoscale(f, o.IOStreams),
411 },
412 },
413 {
414 Message: "Cluster Management Commands:",
415 Commands: []*cobra.Command{
416 certificates.NewCmdCertificate(f, o.IOStreams),
417 clusterinfo.NewCmdClusterInfo(f, o.IOStreams),
418 top.NewCmdTop(f, o.IOStreams),
419 drain.NewCmdCordon(f, o.IOStreams),
420 drain.NewCmdUncordon(f, o.IOStreams),
421 drain.NewCmdDrain(f, o.IOStreams),
422 taint.NewCmdTaint(f, o.IOStreams),
423 },
424 },
425 {
426 Message: "Troubleshooting and Debugging Commands:",
427 Commands: []*cobra.Command{
428 describe.NewCmdDescribe("kubectl", f, o.IOStreams),
429 logs.NewCmdLogs(f, o.IOStreams),
430 attach.NewCmdAttach(f, o.IOStreams),
431 cmdexec.NewCmdExec(f, o.IOStreams),
432 portforward.NewCmdPortForward(f, o.IOStreams),
433 proxyCmd,
434 cp.NewCmdCp(f, o.IOStreams),
435 auth.NewCmdAuth(f, o.IOStreams),
436 debug.NewCmdDebug(f, o.IOStreams),
437 events.NewCmdEvents(f, o.IOStreams),
438 },
439 },
440 {
441 Message: "Advanced Commands:",
442 Commands: []*cobra.Command{
443 diff.NewCmdDiff(f, o.IOStreams),
444 apply.NewCmdApply("kubectl", f, o.IOStreams),
445 patch.NewCmdPatch(f, o.IOStreams),
446 replace.NewCmdReplace(f, o.IOStreams),
447 wait.NewCmdWait(f, o.IOStreams),
448 kustomize.NewCmdKustomize(o.IOStreams),
449 },
450 },
451 {
452 Message: "Settings Commands:",
453 Commands: []*cobra.Command{
454 label.NewCmdLabel(f, o.IOStreams),
455 annotate.NewCmdAnnotate("kubectl", f, o.IOStreams),
456 completion.NewCmdCompletion(o.IOStreams.Out, ""),
457 },
458 },
459 }
460 groups.Add(cmds)
461
462 filters := []string{"options"}
463
464
465 alpha := NewCmdAlpha(f, o.IOStreams)
466 if !alpha.HasSubCommands() {
467 filters = append(filters, alpha.Name())
468 }
469
470
471
472
473 pluginCommandGroup := plugin.GetPluginCommandGroup(cmds)
474 groups = append(groups, pluginCommandGroup)
475
476 templates.ActsAsRootCommand(cmds, filters, groups...)
477
478 utilcomp.SetFactoryForCompletion(f)
479 registerCompletionFuncForGlobalFlags(cmds, f)
480
481 cmds.AddCommand(alpha)
482 cmds.AddCommand(cmdconfig.NewCmdConfig(clientcmd.NewDefaultPathOptions(), o.IOStreams))
483 cmds.AddCommand(plugin.NewCmdPlugin(o.IOStreams))
484 cmds.AddCommand(version.NewCmdVersion(f, o.IOStreams))
485 cmds.AddCommand(apiresources.NewCmdAPIVersions(f, o.IOStreams))
486 cmds.AddCommand(apiresources.NewCmdAPIResources(f, o.IOStreams))
487 cmds.AddCommand(options.NewCmdOptions(o.IOStreams.Out))
488
489
490
491 cmds.SetGlobalNormalizationFunc(cliflag.WordSepNormalizeFunc)
492 return cmds
493 }
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508 func addCmdHeaderHooks(cmds *cobra.Command, kubeConfigFlags *genericclioptions.ConfigFlags) {
509
510 if value, exists := os.LookupEnv(kubectlCmdHeaders); exists {
511 if value == "false" || value == "0" {
512 klog.V(5).Infoln("kubectl command headers turned off")
513 return
514 }
515 }
516 klog.V(5).Infoln("kubectl command headers turned on")
517 crt := &genericclioptions.CommandHeaderRoundTripper{}
518 existingPreRunE := cmds.PersistentPreRunE
519
520 cmds.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
521 crt.ParseCommandHeaders(cmd, args)
522 return existingPreRunE(cmd, args)
523 }
524 wrapConfigFn := kubeConfigFlags.WrapConfigFn
525
526 kubeConfigFlags.WrapConfigFn = func(c *rest.Config) *rest.Config {
527 if wrapConfigFn != nil {
528 c = wrapConfigFn(c)
529 }
530 c.Wrap(func(rt http.RoundTripper) http.RoundTripper {
531
532
533 return &genericclioptions.CommandHeaderRoundTripper{
534 Delegate: rt,
535 Headers: crt.Headers,
536 }
537 })
538 return c
539 }
540 }
541
542 func runHelp(cmd *cobra.Command, args []string) {
543 cmd.Help()
544 }
545
546 func registerCompletionFuncForGlobalFlags(cmd *cobra.Command, f cmdutil.Factory) {
547 cmdutil.CheckErr(cmd.RegisterFlagCompletionFunc(
548 "namespace",
549 func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
550 return utilcomp.CompGetResource(f, "namespace", toComplete), cobra.ShellCompDirectiveNoFileComp
551 }))
552 cmdutil.CheckErr(cmd.RegisterFlagCompletionFunc(
553 "context",
554 func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
555 return utilcomp.ListContextsInConfig(toComplete), cobra.ShellCompDirectiveNoFileComp
556 }))
557 cmdutil.CheckErr(cmd.RegisterFlagCompletionFunc(
558 "cluster",
559 func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
560 return utilcomp.ListClustersInConfig(toComplete), cobra.ShellCompDirectiveNoFileComp
561 }))
562 cmdutil.CheckErr(cmd.RegisterFlagCompletionFunc(
563 "user",
564 func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
565 return utilcomp.ListUsersInConfig(toComplete), cobra.ShellCompDirectiveNoFileComp
566 }))
567 }
568
View as plain text