github.com/cloudfoundry-community/cloudfoundry-cli@v6.44.1-0.20240130060226-cda5ed8e89a5+incompatible/main.go (about)

     1  package main
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"os"
     7  	"reflect"
     8  	"strings"
     9  
    10  	"code.cloudfoundry.org/cli/cf/cmd"
    11  	"code.cloudfoundry.org/cli/command"
    12  	"code.cloudfoundry.org/cli/command/common"
    13  	"code.cloudfoundry.org/cli/command/flag"
    14  	"code.cloudfoundry.org/cli/command/translatableerror"
    15  	"code.cloudfoundry.org/cli/util/configv3"
    16  	"code.cloudfoundry.org/cli/util/panichandler"
    17  	"code.cloudfoundry.org/cli/util/ui"
    18  	"github.com/jessevdk/go-flags"
    19  	log "github.com/sirupsen/logrus"
    20  	"golang.org/x/crypto/ssh"
    21  )
    22  
    23  type UI interface {
    24  	DisplayError(err error)
    25  	DisplayWarning(template string, templateValues ...map[string]interface{})
    26  	DisplayText(template string, templateValues ...map[string]interface{})
    27  }
    28  
    29  type DisplayUsage interface {
    30  	DisplayUsage()
    31  }
    32  
    33  type TriggerLegacyMain interface {
    34  	LegacyMain()
    35  	error
    36  }
    37  
    38  const switchToV2 = -3
    39  
    40  var ErrFailed = errors.New("command failed")
    41  var ParseErr = errors.New("incorrect type for arg")
    42  
    43  func main() {
    44  	defer panichandler.HandlePanic()
    45  	exitStatus := parse(os.Args[1:], &common.Commands)
    46  	if exitStatus == switchToV2 {
    47  		exitStatus = parse(os.Args[1:], &common.FallbackCommands)
    48  	}
    49  	if exitStatus != 0 {
    50  		os.Exit(exitStatus)
    51  	}
    52  }
    53  
    54  func parse(args []string, commandList interface{}) int {
    55  	parser := flags.NewParser(commandList, flags.HelpFlag)
    56  	parser.CommandHandler = executionWrapper
    57  	extraArgs, err := parser.ParseArgs(args)
    58  	if err == nil {
    59  		return 0
    60  	} else if _, ok := err.(translatableerror.V3V2SwitchError); ok {
    61  		return switchToV2
    62  	} else if flagErr, ok := err.(*flags.Error); ok {
    63  		return handleFlagErrorAndCommandHelp(flagErr, parser, extraArgs, args, commandList)
    64  	} else if err == ErrFailed {
    65  		return 1
    66  	} else if err == ParseErr {
    67  		fmt.Println()
    68  		parse([]string{"help", args[0]}, commandList)
    69  		return 1
    70  	} else if exitError, ok := err.(*ssh.ExitError); ok {
    71  		return exitError.ExitStatus()
    72  	}
    73  
    74  	fmt.Fprintf(os.Stderr, "Unexpected error: %s\n", err.Error())
    75  	return 1
    76  }
    77  
    78  func handleFlagErrorAndCommandHelp(flagErr *flags.Error, parser *flags.Parser, extraArgs []string, originalArgs []string, commandList interface{}) int {
    79  	switch flagErr.Type {
    80  	case flags.ErrHelp, flags.ErrUnknownFlag, flags.ErrExpectedArgument, flags.ErrInvalidChoice:
    81  		_, found := reflect.TypeOf(common.Commands).FieldByNameFunc(
    82  			func(fieldName string) bool {
    83  				field, _ := reflect.TypeOf(common.Commands).FieldByName(fieldName)
    84  				return parser.Active != nil && parser.Active.Name == field.Tag.Get("command")
    85  			},
    86  		)
    87  
    88  		if found && flagErr.Type == flags.ErrUnknownFlag && (parser.Active.Name == "set-env" || parser.Active.Name == "v3-set-env") {
    89  			newArgs := []string{}
    90  			for _, arg := range originalArgs {
    91  				if arg[0] == '-' {
    92  					newArgs = append(newArgs, fmt.Sprintf("%s%s", flag.WorkAroundPrefix, arg))
    93  				} else {
    94  					newArgs = append(newArgs, arg)
    95  				}
    96  			}
    97  			parse(newArgs, commandList)
    98  			return 0
    99  		}
   100  
   101  		if flagErr.Type == flags.ErrUnknownFlag || flagErr.Type == flags.ErrExpectedArgument || flagErr.Type == flags.ErrInvalidChoice {
   102  			fmt.Fprintf(os.Stderr, "Incorrect Usage: %s\n\n", flagErr.Error())
   103  		}
   104  
   105  		var helpErrored int
   106  		if found {
   107  			helpErrored = parse([]string{"help", parser.Active.Name}, commandList)
   108  		} else {
   109  			switch len(extraArgs) {
   110  			case 0:
   111  				helpErrored = parse([]string{"help"}, commandList)
   112  			case 1:
   113  				if !isOption(extraArgs[0]) || (len(originalArgs) > 1 && extraArgs[0] == "-a") {
   114  					helpErrored = parse([]string{"help", extraArgs[0]}, commandList)
   115  				} else {
   116  					helpErrored = parse([]string{"help"}, commandList)
   117  				}
   118  			default:
   119  				if isCommand(extraArgs[0]) {
   120  					helpErrored = parse([]string{"help", extraArgs[0]}, commandList)
   121  				} else {
   122  					helpErrored = parse(extraArgs[1:], commandList)
   123  				}
   124  			}
   125  		}
   126  
   127  		if helpErrored > 0 || flagErr.Type == flags.ErrUnknownFlag || flagErr.Type == flags.ErrExpectedArgument || flagErr.Type == flags.ErrInvalidChoice {
   128  			return 1
   129  		}
   130  	case flags.ErrRequired, flags.ErrMarshal:
   131  		fmt.Fprintf(os.Stderr, "Incorrect Usage: %s\n\n", flagErr.Error())
   132  		parse([]string{"help", originalArgs[0]}, commandList)
   133  		return 1
   134  	case flags.ErrUnknownCommand:
   135  		cmd.Main(os.Getenv("CF_TRACE"), os.Args)
   136  	case flags.ErrCommandRequired:
   137  		if common.Commands.VerboseOrVersion {
   138  			parse([]string{"version"}, commandList)
   139  		} else {
   140  			parse([]string{"help"}, commandList)
   141  		}
   142  	default:
   143  		fmt.Fprintf(os.Stderr, "Unexpected flag error\ntype: %s\nmessage: %s\n", flagErr.Type, flagErr.Error())
   144  	}
   145  	return 0
   146  }
   147  
   148  func isCommand(s string) bool {
   149  	_, found := reflect.TypeOf(common.Commands).FieldByNameFunc(
   150  		func(fieldName string) bool {
   151  			field, _ := reflect.TypeOf(common.Commands).FieldByName(fieldName)
   152  			return s == field.Tag.Get("command") || s == field.Tag.Get("alias")
   153  		})
   154  
   155  	return found
   156  }
   157  
   158  func isOption(s string) bool {
   159  	return strings.HasPrefix(s, "-")
   160  }
   161  
   162  func executionWrapper(cmd flags.Commander, args []string) error {
   163  	cfConfig, configErr := configv3.LoadConfig(configv3.FlagOverride{
   164  		Verbose: common.Commands.VerboseOrVersion,
   165  	})
   166  	if configErr != nil {
   167  		if _, ok := configErr.(translatableerror.EmptyConfigError); !ok {
   168  			return configErr
   169  		}
   170  	}
   171  
   172  	commandUI, err := ui.NewUI(cfConfig)
   173  	if err != nil {
   174  		return err
   175  	}
   176  
   177  	err = preventExtraArgs(args)
   178  	if err != nil {
   179  		return handleError(err, commandUI)
   180  	}
   181  
   182  	err = cfConfig.CreatePluginHome()
   183  	if err != nil {
   184  		return handleError(err, commandUI)
   185  	}
   186  
   187  	// TODO: when the line in the old code under `cf` which calls
   188  	// configv3.LoadConfig() is finally removed, then we should replace the code
   189  	// path above with the following:
   190  	//
   191  	// var configErrTemplate string
   192  	// if configErr != nil {
   193  	// 	if ce, ok := configErr.(translatableerror.EmptyConfigError); ok {
   194  	// 		configErrTemplate = ce.Error()
   195  	// 	} else {
   196  	// 		return configErr
   197  	// 	}
   198  	// }
   199  
   200  	// commandUI, err := ui.NewUI(cfConfig)
   201  	// if err != nil {
   202  	// 	return err
   203  	// }
   204  
   205  	// if configErr != nil {
   206  	//   commandUI.DisplayWarning(configErrTemplate, map[string]interface{}{
   207  	//   	"FilePath": configv3.ConfigFilePath(),
   208  	//   })
   209  	// }
   210  
   211  	defer func() {
   212  		configWriteErr := configv3.WriteConfig(cfConfig)
   213  		if configWriteErr != nil {
   214  			fmt.Fprintf(os.Stderr, "Error writing config: %s", configWriteErr.Error())
   215  		}
   216  	}()
   217  
   218  	if extendedCmd, ok := cmd.(command.ExtendedCommander); ok {
   219  		log.SetOutput(os.Stderr)
   220  		log.SetLevel(log.Level(cfConfig.LogLevel()))
   221  
   222  		err = extendedCmd.Setup(cfConfig, commandUI)
   223  		if err != nil {
   224  			return handleError(err, commandUI)
   225  		}
   226  		return handleError(extendedCmd.Execute(args), commandUI)
   227  	}
   228  
   229  	return fmt.Errorf("command does not conform to ExtendedCommander")
   230  }
   231  
   232  func handleError(passedErr error, commandUI UI) error {
   233  	if passedErr == nil {
   234  		return nil
   235  	}
   236  
   237  	translatedErr := translatableerror.ConvertToTranslatableError(passedErr)
   238  
   239  	switch typedErr := translatedErr.(type) {
   240  	case translatableerror.V3V2SwitchError:
   241  		log.Info("Received a V3V2SwitchError - switch to the V2 version of the command")
   242  		return passedErr
   243  	case TriggerLegacyMain:
   244  		if typedErr.Error() != "" {
   245  			commandUI.DisplayWarning("")
   246  			commandUI.DisplayWarning(typedErr.Error())
   247  		}
   248  
   249  		cmd.Main(os.Getenv("CF_TRACE"), os.Args)
   250  	case *ssh.ExitError:
   251  		exitStatus := typedErr.ExitStatus()
   252  		if sig := typedErr.Signal(); sig != "" {
   253  			commandUI.DisplayText("Process terminated by signal: {{.Signal}}. Exited with {{.ExitCode}}", map[string]interface{}{
   254  				"Signal":   sig,
   255  				"ExitCode": exitStatus,
   256  			})
   257  		}
   258  		return passedErr
   259  	}
   260  
   261  	commandUI.DisplayError(translatedErr)
   262  
   263  	if _, ok := translatedErr.(DisplayUsage); ok {
   264  		return ParseErr
   265  	}
   266  
   267  	return ErrFailed
   268  }