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 }