github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/cli/cli.go (about)

     1  // Copyright 2015 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package cli
    12  
    13  import (
    14  	"context"
    15  	"flag"
    16  	"fmt"
    17  	"math/rand"
    18  	"os"
    19  	"strings"
    20  	"text/tabwriter"
    21  
    22  	_ "github.com/benesch/cgosymbolizer" // calls runtime.SetCgoTraceback on import
    23  	"github.com/cockroachdb/cockroach/pkg/build"
    24  	"github.com/cockroachdb/cockroach/pkg/util/log"
    25  	"github.com/cockroachdb/cockroach/pkg/util/log/logflags"
    26  	"github.com/cockroachdb/cockroach/pkg/util/randutil"
    27  	// intentionally not all the workloads in pkg/ccl/workloadccl/allccl
    28  	_ "github.com/cockroachdb/cockroach/pkg/workload/bank"       // registers workloads
    29  	_ "github.com/cockroachdb/cockroach/pkg/workload/bulkingest" // registers workloads
    30  	workloadcli "github.com/cockroachdb/cockroach/pkg/workload/cli"
    31  	_ "github.com/cockroachdb/cockroach/pkg/workload/examples" // registers workloads
    32  	_ "github.com/cockroachdb/cockroach/pkg/workload/kv"       // registers workloads
    33  	_ "github.com/cockroachdb/cockroach/pkg/workload/movr"     // registers workloads
    34  	_ "github.com/cockroachdb/cockroach/pkg/workload/tpcc"     // registers workloads
    35  	_ "github.com/cockroachdb/cockroach/pkg/workload/tpch"     // registers workloads
    36  	_ "github.com/cockroachdb/cockroach/pkg/workload/ycsb"     // registers workloads
    37  	"github.com/cockroachdb/errors"
    38  	"github.com/spf13/cobra"
    39  )
    40  
    41  // Main is the entry point for the cli, with a single line calling it intended
    42  // to be the body of an action package main `main` func elsewhere. It is
    43  // abstracted for reuse by duplicated `main` funcs in different distributions.
    44  func Main() {
    45  	// Seed the math/rand RNG from crypto/rand.
    46  	rand.Seed(randutil.NewPseudoSeed())
    47  
    48  	if len(os.Args) == 1 {
    49  		os.Args = append(os.Args, "help")
    50  	}
    51  
    52  	// Change the logging defaults for the main cockroach binary.
    53  	// The value is overridden after command-line parsing.
    54  	if err := flag.Lookup(logflags.LogToStderrName).Value.Set("NONE"); err != nil {
    55  		panic(err)
    56  	}
    57  
    58  	cmdName := commandName(os.Args[1:])
    59  
    60  	log.SetupCrashReporter(
    61  		context.Background(),
    62  		cmdName,
    63  	)
    64  
    65  	defer log.RecoverAndReportPanic(context.Background(), &serverCfg.Settings.SV)
    66  
    67  	err := Run(os.Args[1:])
    68  
    69  	errCode := 0
    70  	if err != nil {
    71  		// Display the error and its details/hints.
    72  		cliOutputError(stderr, err, true /*showSeverity*/, false /*verbose*/)
    73  
    74  		// Remind the user of which command was being run.
    75  		fmt.Fprintf(stderr, "Failed running %q\n", cmdName)
    76  
    77  		// Finally, extract the error code, as optionally specified
    78  		// by the sub-command.
    79  		errCode = 1
    80  		var cliErr *cliError
    81  		if errors.As(err, &cliErr) {
    82  			errCode = cliErr.exitCode
    83  		}
    84  	}
    85  
    86  	os.Exit(errCode)
    87  }
    88  
    89  // commandName computes the name of the command that args would invoke. For
    90  // example, the full name of "cockroach debug zip" is "debug zip". If args
    91  // specify a nonexistent command, commandName returns "cockroach".
    92  func commandName(args []string) string {
    93  	rootName := cockroachCmd.CommandPath()
    94  	// Ask Cobra to find the command so that flags and their arguments are
    95  	// ignored. The name of "cockroach --log-dir foo start" is "start", not
    96  	// "--log-dir" or "foo".
    97  	if cmd, _, _ := cockroachCmd.Find(os.Args[1:]); cmd != nil {
    98  		return strings.TrimPrefix(cmd.CommandPath(), rootName+" ")
    99  	}
   100  	return rootName
   101  }
   102  
   103  type cliError struct {
   104  	exitCode int
   105  	severity log.Severity
   106  	cause    error
   107  }
   108  
   109  func (e *cliError) Error() string { return e.cause.Error() }
   110  
   111  // Cause implements causer.
   112  func (e *cliError) Cause() error { return e.cause }
   113  
   114  // Format implements fmt.Formatter.
   115  func (e *cliError) Format(s fmt.State, verb rune) { errors.FormatError(e, s, verb) }
   116  
   117  // FormatError implements errors.Formatter.
   118  func (e *cliError) FormatError(p errors.Printer) error {
   119  	if p.Detail() {
   120  		p.Printf("error with exit code: %d", e.exitCode)
   121  	}
   122  	return e.cause
   123  }
   124  
   125  // stderr aliases log.OrigStderr; we use an alias here so that tests
   126  // in this package can redirect the output of CLI commands to stdout
   127  // to be captured.
   128  var stderr = log.OrigStderr
   129  
   130  // stdin aliases os.Stdin; we use an alias here so that tests in this
   131  // package can redirect the input of the CLI shell.
   132  var stdin = os.Stdin
   133  
   134  var versionCmd = &cobra.Command{
   135  	Use:   "version",
   136  	Short: "output version information",
   137  	Long: `
   138  Output build version information.
   139  `,
   140  	Args: cobra.NoArgs,
   141  	RunE: func(cmd *cobra.Command, args []string) error {
   142  		info := build.GetInfo()
   143  		tw := tabwriter.NewWriter(os.Stdout, 2, 1, 2, ' ', 0)
   144  		fmt.Fprintf(tw, "Build Tag:    %s\n", info.Tag)
   145  		fmt.Fprintf(tw, "Build Time:   %s\n", info.Time)
   146  		fmt.Fprintf(tw, "Distribution: %s\n", info.Distribution)
   147  		fmt.Fprintf(tw, "Platform:     %s", info.Platform)
   148  		if info.CgoTargetTriple != "" {
   149  			fmt.Fprintf(tw, " (%s)", info.CgoTargetTriple)
   150  		}
   151  		fmt.Fprintln(tw)
   152  		fmt.Fprintf(tw, "Go Version:   %s\n", info.GoVersion)
   153  		fmt.Fprintf(tw, "C Compiler:   %s\n", info.CgoCompiler)
   154  		fmt.Fprintf(tw, "Build SHA-1:  %s\n", info.Revision)
   155  		fmt.Fprintf(tw, "Build Type:   %s\n", info.Type)
   156  		return tw.Flush()
   157  	},
   158  }
   159  
   160  var cockroachCmd = &cobra.Command{
   161  	Use:   "cockroach [command] (flags)",
   162  	Short: "CockroachDB command-line interface and server",
   163  	// TODO(cdo): Add a pointer to the docs in Long.
   164  	Long: `CockroachDB command-line interface and server.`,
   165  	// Disable automatic printing of usage information whenever an error
   166  	// occurs. Many errors are not the result of a bad command invocation,
   167  	// e.g. attempting to start a node on an in-use port, and printing the
   168  	// usage information in these cases obscures the cause of the error.
   169  	// Commands should manually print usage information when the error is,
   170  	// in fact, a result of a bad invocation, e.g. too many arguments.
   171  	SilenceUsage: true,
   172  	// Disable automatic printing of the error. We want to also print
   173  	// details and hints, which cobra does not do for us. Instead
   174  	// we do the printing in Main().
   175  	SilenceErrors: true,
   176  }
   177  
   178  func init() {
   179  	cobra.EnableCommandSorting = false
   180  
   181  	// Set an error function for flag parsing which prints the usage message.
   182  	cockroachCmd.SetFlagErrorFunc(func(c *cobra.Command, err error) error {
   183  		if err := c.Usage(); err != nil {
   184  			return err
   185  		}
   186  		fmt.Fprintln(c.OutOrStderr()) // provide a line break between usage and error
   187  		return err
   188  	})
   189  
   190  	cockroachCmd.AddCommand(
   191  		startCmd,
   192  		startSingleNodeCmd,
   193  		initCmd,
   194  		certCmd,
   195  		quitCmd,
   196  
   197  		sqlShellCmd,
   198  		authCmd,
   199  		nodeCmd,
   200  		dumpCmd,
   201  		nodeLocalCmd,
   202  
   203  		// Miscellaneous commands.
   204  		// TODO(pmattis): stats
   205  		demoCmd,
   206  		genCmd,
   207  		versionCmd,
   208  		DebugCmd,
   209  		sqlfmtCmd,
   210  		workloadcli.WorkloadCmd(true /* userFacing */),
   211  		systemBenchCmd,
   212  	)
   213  }
   214  
   215  // AddCmd adds a command to the cli.
   216  func AddCmd(c *cobra.Command) {
   217  	cockroachCmd.AddCommand(c)
   218  }
   219  
   220  // Run ...
   221  func Run(args []string) error {
   222  	cockroachCmd.SetArgs(args)
   223  	return cockroachCmd.Execute()
   224  }
   225  
   226  // usageAndErr informs the user about the usage of the command
   227  // and returns an error. This ensures that the top-level command
   228  // has a suitable exit status.
   229  func usageAndErr(cmd *cobra.Command, args []string) error {
   230  	if err := cmd.Usage(); err != nil {
   231  		return err
   232  	}
   233  	return fmt.Errorf("unknown sub-command: %q", strings.Join(args, " "))
   234  }