Fix host resolution and concurrent cache updates
This commit is contained in:
1 parent
92b1c40cb8
commit
fa7446e5f7
11 files changed
+648
-113
No files matched your search
+42
-38
@@ -4,7 +4,7 @@ import "strings"
|
||||
|
||||
// flagsWithArg lists all SSH flags that consume the following argument.
|
||||
var flagsWithArg = map[byte]bool{
|
||||
'b': true, 'c': true, 'D': true, 'E': true, 'e': true,
|
||||
'B': true, 'P': true, 'b': true, 'c': true, 'D': true, 'E': true, 'e': true,
|
||||
'F': true, 'I': true, 'i': true, 'J': true, 'L': true,
|
||||
'l': true, 'm': true, 'o': true, 'O': true, 'p': true,
|
||||
'Q': true, 'R': true, 'S': true, 'w': true, 'W': true,
|
||||
@@ -32,18 +32,16 @@ func Parse(args []string) *ParsedArgs {
|
||||
}
|
||||
|
||||
if strings.HasPrefix(arg, "-") && len(arg) > 1 {
|
||||
flag := arg[1]
|
||||
if flagsWithArg[flag] {
|
||||
if len(arg) > 2 {
|
||||
// argument is attached, e.g. -p2222
|
||||
i++
|
||||
} else {
|
||||
// argument is the next element, e.g. -p 2222
|
||||
i += 2
|
||||
// OpenSSH accepts clusters such as -vp 2222 and -vladmin.
|
||||
for j := 1; j < len(arg); j++ {
|
||||
if flagsWithArg[arg[j]] {
|
||||
if j == len(arg)-1 {
|
||||
i++
|
||||
}
|
||||
break
|
||||
}
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -86,35 +84,41 @@ func ReplaceHost(args []string, destIdx int, newHost string) []string {
|
||||
return result
|
||||
}
|
||||
|
||||
// HasPortFlag reports whether a port was specified via -p in args.
|
||||
func HasPortFlag(args []string) bool {
|
||||
if parsed := Parse(args); parsed != nil {
|
||||
args = args[:parsed.DestIdx]
|
||||
}
|
||||
for i, a := range args {
|
||||
if a == "-p" && i+1 < len(args) {
|
||||
return true
|
||||
}
|
||||
if len(a) > 2 && a[0] == '-' && a[1] == 'p' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
// HasPortFlag reports whether a port was explicitly supplied.
|
||||
func HasPortFlag(args []string) bool { return hasOption(args, 'p', "Port") }
|
||||
|
||||
// HasUserFlag reports whether a user was specified via -l in args.
|
||||
// Used to avoid overriding an explicit -l with the configured default user.
|
||||
func HasUserFlag(args []string) bool {
|
||||
if parsed := Parse(args); parsed != nil {
|
||||
args = args[:parsed.DestIdx]
|
||||
}
|
||||
for i, a := range args {
|
||||
if a == "-l" && i+1 < len(args) {
|
||||
return true
|
||||
// HasUserFlag reports whether a login user was explicitly supplied.
|
||||
func HasUserFlag(args []string) bool { return hasOption(args, 'l', "User") }
|
||||
|
||||
func hasOption(args []string, wanted byte, keyword string) bool {
|
||||
for i := 0; i < len(args); i++ {
|
||||
arg := args[i]
|
||||
if arg == "--" || len(arg) < 2 || arg[0] != '-' {
|
||||
break
|
||||
}
|
||||
// handle attached form: -lroot
|
||||
if len(a) > 2 && a[0] == '-' && a[1] == 'l' {
|
||||
return true
|
||||
for j := 1; j < len(arg); j++ {
|
||||
flag := arg[j]
|
||||
if !flagsWithArg[flag] {
|
||||
continue
|
||||
}
|
||||
value := arg[j+1:]
|
||||
if value == "" {
|
||||
i++
|
||||
if i >= len(args) {
|
||||
return false
|
||||
}
|
||||
value = args[i]
|
||||
}
|
||||
if flag == wanted {
|
||||
return true
|
||||
}
|
||||
if flag == 'o' {
|
||||
fields := strings.FieldsFunc(value, func(r rune) bool { return r == '=' || r == ' ' || r == '\t' })
|
||||
if len(fields) >= 2 && strings.EqualFold(fields[0], keyword) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package ssh
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestParseOptionClusters(t *testing.T) {
|
||||
for _, args := range [][]string{
|
||||
{"-vp", "2222", "server"},
|
||||
{"-vp2222", "server"},
|
||||
{"-vAl", "admin", "server"},
|
||||
{"-vladmin", "server"},
|
||||
{"-B", "eth0", "server"},
|
||||
{"-P", "production", "server"},
|
||||
} {
|
||||
t.Run(args[0], func(t *testing.T) {
|
||||
assertParsed(t, Parse(args), "server", "", len(args)-1)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExplicitSSHOptions(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
args []string
|
||||
user, port bool
|
||||
}{
|
||||
{"cluster port separated", []string{"-vp", "2222", "server"}, false, true},
|
||||
{"cluster user attached", []string{"-vladmin", "server"}, true, false},
|
||||
{"o separated", []string{"-o", "User=admin", "-o", "Port=2222", "server"}, true, true},
|
||||
{"o attached and case insensitive", []string{"-ouSeR=admin", "-oport=2222", "server"}, true, true},
|
||||
{"o whitespace", []string{"-o", " User = admin", "-o", "Port\t2222", "server"}, true, true},
|
||||
{"cluster o", []string{"-voUser=admin", "server"}, true, false},
|
||||
{"option value looks like flag", []string{"-i", "-lroot", "-F", "-p2222", "server"}, false, false},
|
||||
{"remote flags", []string{"server", "-p2222", "-o", "User=admin"}, false, false},
|
||||
{"after double dash", []string{"--", "-lroot", "-p2222"}, false, false},
|
||||
{"missing value", []string{"-vp"}, false, false},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := HasUserFlag(tc.args); got != tc.user {
|
||||
t.Errorf("HasUserFlag(%v) = %v, want %v", tc.args, got, tc.user)
|
||||
}
|
||||
if got := HasPortFlag(tc.args); got != tc.port {
|
||||
t.Errorf("HasPortFlag(%v) = %v, want %v", tc.args, got, tc.port)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user