From f342cae630d92e240ea0de3ddc40ef28b5852d99 Mon Sep 17 00:00:00 2001 From: sawka <mike.sawka@gmail.com> Date: Sat, 1 Oct 2022 13:23:36 -0700 Subject: [PATCH] refactor the remote edit argument parsing --- pkg/cmdrunner/cmdrunner.go | 229 +++++++++++++++++++++++-------------- 1 file changed, 140 insertions(+), 89 deletions(-) diff --git a/pkg/cmdrunner/cmdrunner.go b/pkg/cmdrunner/cmdrunner.go index 0fdaa7518..ab44bb0bd 100644 --- a/pkg/cmdrunner/cmdrunner.go +++ b/pkg/cmdrunner/cmdrunner.go @@ -166,7 +166,25 @@ func resolveBool(arg string, def bool) bool { return true } -func resolveInt(arg string, def int) (int, error) { +func resolveFile(arg string) (string, error) { + if arg == "" { + return "", nil + } + fileName := base.ExpandHomeDir(arg) + if !strings.HasPrefix(fileName, "/") { + return "", fmt.Errorf("must be absolute, cannot be a relative path") + } + fd, err := os.Open(fileName) + if fd != nil { + fd.Close() + } + if err != nil { + return "", fmt.Errorf("cannot open file: %v", err) + } + return fileName, nil +} + +func resolvePosInt(arg string, def int) (int, error) { if arg == "" { return def, nil } @@ -174,6 +192,9 @@ func resolveInt(arg string, def int) (int, error) { if err != nil { return 0, err } + if ival <= 0 { + return 0, fmt.Errorf("must be greater than 0") + } return ival, nil } @@ -506,6 +527,108 @@ func makeRemoteEditErrorReturn(visual bool, err error) (sstore.UpdatePacket, err return nil, err } +type RemoteEditArgs struct { + SSHOpts *sstore.SSHOpts + Sudo bool + ConnectMode string + Alias string + AutoInstall bool + UserHost string + SSHPassword string + SSHKeyFile string + Color string +} + +func parseRemoteEditArgs(isNew bool, pk *scpacket.FeCommandPacketType) (*RemoteEditArgs, error) { + var userHost string + var sshOpts *sstore.SSHOpts + var isSudo bool + + if isNew { + userHost = pk.Args[0] + m := userHostRe.FindStringSubmatch(userHost) + if m == nil { + return nil, fmt.Errorf("invalid format of user@host argument") + } + sudoStr, remoteUser, remoteHost := m[1], m[2], m[3] + if sudoStr != "" { + isSudo = true + } + if pk.Kwargs["sudo"] != "" { + sudoArg := resolveBool(pk.Kwargs["sudo"], false) + if isSudo && !sudoArg { + return nil, fmt.Errorf("invalid 'sudo@' argument, with sudo kw arg set to false") + } + if !isSudo && sudoArg { + isSudo = true + userHost = "sudo@" + userHost + } + } + sshOpts = &sstore.SSHOpts{ + Local: false, + SSHHost: remoteHost, + SSHUser: remoteUser, + } + portVal, err := resolvePosInt(pk.Kwargs["port"], 0) + if err != nil { + return nil, fmt.Errorf("invalid port %q: %v", pk.Kwargs["port"], err) + } + sshOpts.SSHPort = portVal + } else { + if pk.Kwargs["sudo"] != "" { + return nil, fmt.Errorf("cannot update 'sudo' value") + } + if pk.Kwargs["port"] != "" { + return nil, fmt.Errorf("cannot update 'port' value") + } + } + alias := pk.Kwargs["alias"] + if alias != "" { + if len(alias) > MaxRemoteAliasLen { + return nil, fmt.Errorf("alias too long, max length = %d", MaxRemoteAliasLen) + } + if !remoteAliasRe.MatchString(alias) { + return nil, fmt.Errorf("invalid alias format") + } + } + connectMode := sstore.ConnectModeAuto + if pk.Kwargs["connectmode"] != "" { + connectMode = pk.Kwargs["connectmode"] + } + if !sstore.IsValidConnectMode(connectMode) { + err := fmt.Errorf("invalid connectmode %q: valid modes are %s", connectMode, formatStrs([]string{sstore.ConnectModeStartup, sstore.ConnectModeAuto, sstore.ConnectModeManual}, "or", false)) + return nil, err + } + autoInstall := resolveBool(pk.Kwargs["autoinstall"], true) + keyFile, err := resolveFile(pk.Kwargs["key"]) + if err != nil { + return nil, fmt.Errorf("invalid ssh keyfile %q: %v", pk.Kwargs["key"], err) + } + color := pk.Kwargs["color"] + if color != "" { + err := validateRemoteColor(color, "remote color") + if err != nil { + return nil, err + } + } + sshPassword := pk.Kwargs["password"] + if sshOpts != nil { + sshOpts.SSHIdentity = keyFile + sshOpts.SSHPassword = sshPassword + } + return &RemoteEditArgs{ + SSHOpts: sshOpts, + Sudo: isSudo, + ConnectMode: connectMode, + Alias: alias, + AutoInstall: autoInstall, + UserHost: userHost, + SSHKeyFile: keyFile, + SSHPassword: sshPassword, + Color: color, + }, nil +} + func RemoteNewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { visualEdit := resolveBool(pk.Kwargs["visual"], false) isSubmitted := resolveBool(pk.Kwargs["submit"], false) @@ -518,99 +641,27 @@ func RemoteNewCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ss }, }, nil } - userHost := pk.Args[0] - m := userHostRe.FindStringSubmatch(userHost) - if m == nil { - return makeRemoteEditErrorReturn(visualEdit, fmt.Errorf("/remote:new invalid format of user@host argument")) - } - sudoStr, remoteUser, remoteHost := m[1], m[2], m[3] - alias := pk.Kwargs["alias"] - if alias != "" { - if len(alias) > MaxRemoteAliasLen { - return makeRemoteEditErrorReturn(visualEdit, fmt.Errorf("alias too long, max length = %d", MaxRemoteAliasLen)) - } - if !remoteAliasRe.MatchString(alias) { - return makeRemoteEditErrorReturn(visualEdit, fmt.Errorf("invalid alias format")) - } - } - connectMode := sstore.ConnectModeAuto - if pk.Kwargs["connectmode"] != "" { - connectMode = pk.Kwargs["connectmode"] - } - if !sstore.IsValidConnectMode(connectMode) { - err := fmt.Errorf("/remote:new invalid connectmode %q: valid modes are %s", connectMode, formatStrs([]string{sstore.ConnectModeStartup, sstore.ConnectModeAuto, sstore.ConnectModeManual}, "or", false)) - return makeRemoteEditErrorReturn(visualEdit, err) - } - autoInstall := resolveBool(pk.Kwargs["autoinstall"], true) - var isSudo bool - if sudoStr != "" { - isSudo = true - } - if pk.Kwargs["sudo"] != "" { - sudoArg := resolveBool(pk.Kwargs["sudo"], false) - if isSudo && !sudoArg { - return makeRemoteEditErrorReturn(visualEdit, fmt.Errorf("/remote:new invalid 'sudo@' argument, with sudo kw arg set to false")) - } - if !isSudo && sudoArg { - isSudo = true - userHost = "sudo@" + userHost - } - } - sshOpts := &sstore.SSHOpts{ - Local: false, - SSHHost: remoteHost, - SSHUser: remoteUser, - } - if pk.Kwargs["key"] != "" { - keyFile := pk.Kwargs["key"] - keyFile = base.ExpandHomeDir(keyFile) - fd, err := os.Open(keyFile) - if fd != nil { - fd.Close() - } - if err != nil { - return makeRemoteEditErrorReturn(visualEdit, fmt.Errorf("/remote:new invalid key %q (cannot read): %v", keyFile, err)) - } - sshOpts.SSHIdentity = keyFile - } - if pk.Kwargs["port"] != "" { - portStr := pk.Kwargs["port"] - portVal, err := strconv.Atoi(portStr) - if err != nil { - return makeRemoteEditErrorReturn(visualEdit, fmt.Errorf("/remote:new invalid port %q: %v", portStr, err)) - } - if portVal <= 0 { - return makeRemoteEditErrorReturn(visualEdit, fmt.Errorf("/remote:new invalid port %d (must be positive)", portVal)) - } - sshOpts.SSHPort = portVal - } - remoteOpts := &sstore.RemoteOptsType{} - if pk.Kwargs["color"] != "" { - color := pk.Kwargs["color"] - err := validateRemoteColor(color, "remote color") - if err != nil { - return makeRemoteEditErrorReturn(visualEdit, err) - } - remoteOpts.Color = color - } - if pk.Kwargs["password"] != "" { - sshOpts.SSHPassword = pk.Kwargs["password"] + editArgs, err := parseRemoteEditArgs(true, pk) + if err != nil { + return makeRemoteEditErrorReturn(visualEdit, fmt.Errorf("/remote:new %v", err)) } r := &sstore.RemoteType{ RemoteId: scbase.GenSCUUID(), PhysicalId: "", RemoteType: sstore.RemoteTypeSsh, - RemoteAlias: alias, - RemoteCanonicalName: userHost, - RemoteSudo: isSudo, - RemoteUser: remoteUser, - RemoteHost: remoteHost, - ConnectMode: connectMode, - AutoInstall: autoInstall, - SSHOpts: sshOpts, - RemoteOpts: remoteOpts, + RemoteAlias: editArgs.Alias, + RemoteCanonicalName: editArgs.UserHost, + RemoteSudo: editArgs.Sudo, + RemoteUser: editArgs.SSHOpts.SSHUser, + RemoteHost: editArgs.SSHOpts.SSHHost, + ConnectMode: editArgs.ConnectMode, + AutoInstall: editArgs.AutoInstall, + SSHOpts: editArgs.SSHOpts, } - err := remote.AddRemote(ctx, r) + if editArgs.Color != "" { + r.RemoteOpts = &sstore.RemoteOptsType{Color: editArgs.Color} + } + err = remote.AddRemote(ctx, r) if err != nil { return makeRemoteEditErrorReturn(visualEdit, fmt.Errorf("cannot create remote %q: %v", r.RemoteCanonicalName, err)) } @@ -1234,7 +1285,7 @@ func HistoryCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (ssto if err != nil { return nil, err } - maxItems, err := resolveInt(pk.Kwargs["maxitems"], DefaultMaxHistoryItems) + maxItems, err := resolvePosInt(pk.Kwargs["maxitems"], DefaultMaxHistoryItems) if err != nil { return nil, fmt.Errorf("invalid maxitems value '%s' (must be a number): %v", pk.Kwargs["maxitems"], err) }