...

Source file src/k8s.io/component-base/cli/run.go

Documentation: k8s.io/component-base/cli

     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  

View as plain text