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