1 /* 2 Copyright 2021 The Kubernetes Authors. 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 cli 18 19 import ( 20 "fmt" 21 "math/rand" 22 "os" 23 "time" 24 25 "github.com/spf13/cobra" 26 27 cliflag "k8s.io/component-base/cli/flag" 28 "k8s.io/component-base/logs" 29 "k8s.io/klog/v2" 30 ) 31 32 // Run provides the common boilerplate code around executing a cobra command. 33 // For example, it ensures that logging is set up properly. Logging 34 // flags get added to the command line if not added already. Flags get normalized 35 // so that help texts show them with hyphens. Underscores are accepted 36 // as alternative for the command parameters. 37 // 38 // Run tries to be smart about how to print errors that are returned by the 39 // command: before logging is known to be set up, it prints them as plain text 40 // to stderr. This covers command line flag parse errors and unknown commands. 41 // Afterwards it logs them. This covers runtime errors. 42 // 43 // Commands like kubectl where logging is not normally part of the runtime output 44 // should use RunNoErrOutput instead and deal with the returned error themselves. 45 func Run(cmd *cobra.Command) int { 46 if logsInitialized, err := run(cmd); err != nil { 47 // If the error is about flag parsing, then printing that error 48 // with the decoration that klog would add ("E0923 49 // 23:02:03.219216 4168816 run.go:61] unknown shorthand flag") 50 // is less readable. Using klog.Fatal is even worse because it 51 // dumps a stack trace that isn't about the error. 52 // 53 // But if it is some other error encountered at runtime, then 54 // we want to log it as error, at least in most commands because 55 // their output is a log event stream. 56 // 57 // We can distinguish these two cases depending on whether 58 // we got to logs.InitLogs() above. 59 // 60 // This heuristic might be problematic for command line 61 // tools like kubectl where the output is carefully controlled 62 // and not a log by default. They should use RunNoErrOutput 63 // instead. 64 // 65 // The usage of klog is problematic also because we don't know 66 // whether the command has managed to configure it. This cannot 67 // be checked right now, but may become possible when the early 68 // logging proposal from 69 // https://github.com/kubernetes/enhancements/pull/3078 70 // ("contextual logging") is implemented. 71 if !logsInitialized { 72 fmt.Fprintf(os.Stderr, "Error: %v\n", err) 73 } else { 74 klog.ErrorS(err, "command failed") 75 } 76 return 1 77 } 78 return 0 79 } 80 81 // RunNoErrOutput is a version of Run which returns the cobra command error 82 // instead of printing it. 83 func RunNoErrOutput(cmd *cobra.Command) error { 84 _, err := run(cmd) 85 return err 86 } 87 88 func run(cmd *cobra.Command) (logsInitialized bool, err error) { 89 rand.Seed(time.Now().UnixNano()) 90 defer logs.FlushLogs() 91 92 cmd.SetGlobalNormalizationFunc(cliflag.WordSepNormalizeFunc) 93 94 // When error printing is enabled for the Cobra command, a flag parse 95 // error gets printed first, then optionally the often long usage 96 // text. This is very unreadable in a console because the last few 97 // lines that will be visible on screen don't include the error. 98 // 99 // The recommendation from #sig-cli was to print the usage text, then 100 // the error. We implement this consistently for all commands here. 101 // However, we don't want to print the usage text when command 102 // execution fails for other reasons than parsing. We detect this via 103 // the FlagParseError callback. 104 // 105 // Some commands, like kubectl, already deal with this themselves. 106 // We don't change the behavior for those. 107 if !cmd.SilenceUsage { 108 cmd.SilenceUsage = true 109 cmd.SetFlagErrorFunc(func(c *cobra.Command, err error) error { 110 // Re-enable usage printing. 111 c.SilenceUsage = false 112 return err 113 }) 114 } 115 116 // In all cases error printing is done below. 117 cmd.SilenceErrors = true 118 119 // This is idempotent. 120 logs.AddFlags(cmd.PersistentFlags()) 121 122 // Inject logs.InitLogs after command line parsing into one of the 123 // PersistentPre* functions. 124 switch { 125 case cmd.PersistentPreRun != nil: 126 pre := cmd.PersistentPreRun 127 cmd.PersistentPreRun = func(cmd *cobra.Command, args []string) { 128 logs.InitLogs() 129 logsInitialized = true 130 pre(cmd, args) 131 } 132 case cmd.PersistentPreRunE != nil: 133 pre := cmd.PersistentPreRunE 134 cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { 135 logs.InitLogs() 136 logsInitialized = true 137 return pre(cmd, args) 138 } 139 default: 140 cmd.PersistentPreRun = func(cmd *cobra.Command, args []string) { 141 logs.InitLogs() 142 logsInitialized = true 143 } 144 } 145 146 err = cmd.Execute() 147 return 148 } 149