github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/cmd/cmd.go (about)

     1  // Package cmd defines the CLI interface. It relies heavily on the spf13/cobra
     2  // package. Much of its structure is adapted from kubernetes/kubernetes/tree/master/cmd
     3  // The `help` message for each command uses backticks rather than quotes when
     4  // referring to commands by name, even though it is cumbersome to maintain.
     5  // Using backticks means we can get better formatting when auto generating markdown
     6  // documentation from the command help messages.
     7  package cmd
     8  
     9  import (
    10  	"context"
    11  	"errors"
    12  	"fmt"
    13  	"io"
    14  	"io/ioutil"
    15  	"os"
    16  	"path/filepath"
    17  
    18  	golog "github.com/ipfs/go-log"
    19  	"github.com/qri-io/ioes"
    20  	"github.com/qri-io/qfs/qipfs"
    21  	"github.com/qri-io/qri/auth/key"
    22  	"github.com/qri-io/qri/config/migrate"
    23  	qrierr "github.com/qri-io/qri/errors"
    24  )
    25  
    26  var log = golog.Logger("cmd")
    27  
    28  // Execute adds all child commands to the root command sets flags appropriately.
    29  // This is called by main.main(). It only needs to happen once to the rootCmd.
    30  func Execute() {
    31  	if os.Getenv("QRI_BACKTRACE") == "" {
    32  		// Catch errors & pretty-print.
    33  		defer func() {
    34  			if r := recover(); r != nil {
    35  				if err, ok := r.(error); ok {
    36  					fmt.Println(err.Error())
    37  				} else {
    38  					fmt.Println(r)
    39  				}
    40  			}
    41  		}()
    42  	}
    43  
    44  	ensureLargeNumOpenFiles()
    45  
    46  	// root context
    47  	ctx := context.Background()
    48  	ctors := Constructors{
    49  		CryptoGenerator: key.NewCryptoGenerator(),
    50  		InitIPFS:        qipfs.InitRepo,
    51  	}
    52  	root, shutdown := NewQriCommand(ctx, StandardRepoPath(), ctors, ioes.NewStdIOStreams())
    53  	// If the subcommand hits an error, don't show usage or the error, since we'll show
    54  	// the error message below, on our own. Usage is still shown if the subcommand
    55  	// is missing command-line arguments.
    56  	root.SilenceUsage = true
    57  	root.SilenceErrors = true
    58  	// Execute the subcommand
    59  	if err := root.Execute(); err != nil {
    60  		ErrExit(os.Stderr, err)
    61  	}
    62  
    63  	<-shutdown()
    64  }
    65  
    66  const (
    67  	// ExitCodeOK is a 0 exit code. we're good! success! yay!
    68  	ExitCodeOK = iota
    69  	// ExitCodeErr is a generic error exit code, all non-special errors occur here
    70  	ExitCodeErr
    71  	// ExitCodeNeedMigration indicates a required migration
    72  	ExitCodeNeedMigration
    73  )
    74  
    75  // ErrExit writes an error to the given io.Writer & exits
    76  func ErrExit(w io.Writer, err error) {
    77  	exitCode := ExitCodeErr
    78  
    79  	if errors.Is(err, migrate.ErrMigrationSucceeded) {
    80  		// migration success is a good thing. exit with status 0
    81  		printSuccess(w, "migration succeeded, re-run your command to continue")
    82  		os.Exit(ExitCodeOK)
    83  	} else if errors.Is(err, migrate.ErrNeedMigration) {
    84  		exitCode = ExitCodeNeedMigration
    85  	}
    86  
    87  	log.Debug(err.Error())
    88  	var qerr qrierr.Error
    89  	if errors.As(err, &qerr) {
    90  		printErr(w, fmt.Errorf(qerr.Message()))
    91  	} else {
    92  		printErr(w, err)
    93  	}
    94  	os.Exit(exitCode)
    95  }
    96  
    97  // ExitIfErr only calls ErrExit if there is an error present
    98  func ExitIfErr(w io.Writer, err error) {
    99  	if err != nil {
   100  		ErrExit(w, err)
   101  	}
   102  }
   103  
   104  // GetWd is a convenience method to get the working
   105  // directory or bail.
   106  func GetWd() string {
   107  	dir, err := os.Getwd()
   108  	if err != nil {
   109  		fmt.Printf("Error getting working directory: %s", err.Error())
   110  		os.Exit(1)
   111  	}
   112  
   113  	return dir
   114  }
   115  
   116  func loadFileIfPath(path string) (data []byte, err error) {
   117  	if path == "" {
   118  		return nil, nil
   119  	}
   120  
   121  	path, err = filepath.Abs(path)
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  
   126  	return ioutil.ReadFile(path)
   127  }
   128  
   129  // parseSecrets turns a key,value sequence into a map[string]string
   130  func parseSecrets(secrets ...string) (map[string]string, error) {
   131  	if len(secrets)%2 != 0 {
   132  		return nil, fmt.Errorf("expected even number of (key,value) pairs for secrets")
   133  	}
   134  	s := map[string]string{}
   135  	for i := 0; i < len(secrets); i = i + 2 {
   136  		s[secrets[i]] = secrets[i+1]
   137  	}
   138  	return s, nil
   139  }