github.com/jenspinney/cli@v6.42.1-0.20190207184520-7450c600020e+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 }