github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+incompatible/main.go (about)

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