X Tutup
Skip to content

Commit f22320a

Browse files
authored
Parse scp args
1 parent 65d90aa commit f22320a

File tree

3 files changed

+114
-40
lines changed

3 files changed

+114
-40
lines changed

internal/codespaces/ssh.go

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,35 @@ func Shell(ctx context.Context, p printer, sshArgs []string, port int, destinati
3838
func Copy(ctx context.Context, scpArgs []string, port int, destination string) error {
3939
// Beware: invalid syntax causes scp to exit 1 with
4040
// no error message, so don't let that happen.
41-
cmd := exec.CommandContext(ctx, "scp",
41+
connArgs := []string{
4242
"-P", strconv.Itoa(port),
4343
"-o", "NoHostAuthenticationForLocalhost=yes",
4444
"-C", // compression
45-
)
46-
for _, arg := range scpArgs {
47-
// Replace "remote:" prefix with (e.g.) "root@localhost:".
48-
if rest := strings.TrimPrefix(arg, "remote:"); rest != arg {
49-
arg = destination + ":" + rest
45+
}
46+
47+
cmdArgs, command, err := parseSCPArgs(scpArgs)
48+
if err != nil {
49+
return err
50+
}
51+
52+
cmdArgs = append(cmdArgs, connArgs...)
53+
54+
if len(command) > 0 {
55+
cmdArgs = append(cmdArgs, "--")
56+
57+
for _, arg := range command {
58+
// Replace "remote:" prefix with (e.g.) "root@localhost:".
59+
if rest := strings.TrimPrefix(arg, "remote:"); rest != arg {
60+
arg = destination + ":" + rest
61+
}
62+
cmdArgs = append(cmdArgs, arg)
5063
}
51-
cmd.Args = append(cmd.Args, arg)
5264
}
65+
66+
fmt.Println(cmdArgs)
67+
68+
cmd := exec.CommandContext(ctx, "scp", cmdArgs...)
69+
5370
cmd.Stdin = nil
5471
cmd.Stdout = os.Stderr
5572
cmd.Stderr = os.Stderr
@@ -95,9 +112,18 @@ func newSSHCommand(ctx context.Context, port int, dst string, cmdArgs []string)
95112
return cmd, connArgs, nil
96113
}
97114

98-
// parseSSHArgs parses SSH arguments into two distinct slices of flags and command.
99-
// It returns an error if a unary flag is provided without an argument.
100115
func parseSSHArgs(args []string) (cmdArgs, command []string, err error) {
116+
return parseArgs(args, "bcDeFIiLlmOopRSWw")
117+
}
118+
119+
func parseSCPArgs(args []string) (cmdArgs, command []string, err error) {
120+
return parseArgs(args, "cFiJloPS")
121+
}
122+
123+
// parseArgs parses arguments into two distinct slices of flags and command. Parsing stops
124+
// as soon as a non-flag argument is found assuming the remaining arguments are the command.
125+
// It returns an error if a unary flag is provided without an argument.
126+
func parseArgs(args []string, unaryFlags string) (cmdArgs, command []string, err error) {
101127
for i := 0; i < len(args); i++ {
102128
arg := args[i]
103129

@@ -108,9 +134,9 @@ func parseSSHArgs(args []string) (cmdArgs, command []string, err error) {
108134
}
109135

110136
cmdArgs = append(cmdArgs, arg)
111-
if len(arg) == 2 && strings.Contains("bcDeFIiLlmOopRSWw", arg[1:2]) {
137+
if len(arg) == 2 && strings.Contains(unaryFlags, arg[1:2]) {
112138
if i++; i == len(args) {
113-
return nil, nil, fmt.Errorf("ssh flag: %s requires an argument", arg)
139+
return nil, nil, fmt.Errorf("flag: %s requires an argument", arg)
114140
}
115141

116142
cmdArgs = append(cmdArgs, args[i])

internal/codespaces/ssh_test.go

Lines changed: 76 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ import (
55
"testing"
66
)
77

8-
func TestParseSSHArgs(t *testing.T) {
9-
type testCase struct {
10-
Args []string
11-
ParsedArgs []string
12-
Command []string
13-
Error string
14-
}
8+
type parseTestCase struct {
9+
Args []string
10+
ParsedArgs []string
11+
Command []string
12+
Error string
13+
}
1514

16-
testCases := []testCase{
15+
func TestParseSSHArgs(t *testing.T) {
16+
testCases := []parseTestCase{
1717
{}, // empty test case
1818
{
1919
Args: []string{"-X", "-Y"},
@@ -69,37 +69,85 @@ func TestParseSSHArgs(t *testing.T) {
6969
Args: []string{"-b"},
7070
ParsedArgs: nil,
7171
Command: nil,
72-
Error: "ssh flag: -b requires an argument",
72+
Error: "flag: -b requires an argument",
7373
},
7474
}
7575

7676
for _, tcase := range testCases {
7777
args, command, err := parseSSHArgs(tcase.Args)
78-
if tcase.Error != "" {
79-
if err == nil {
80-
t.Errorf("expected error and got nil: %#v", tcase)
81-
}
8278

83-
if err.Error() != tcase.Error {
84-
t.Errorf("error does not match expected error, got: '%s', expected: '%s'", err.Error(), tcase.Error)
85-
}
79+
checkParseResult(t, tcase, args, command, err)
80+
}
81+
}
8682

87-
continue
88-
}
83+
func TestParseSCPArgs(t *testing.T) {
84+
testCases := []parseTestCase{
85+
{}, // empty test case
86+
{
87+
Args: []string{"-X", "-Y"},
88+
ParsedArgs: []string{"-X", "-Y"},
89+
Command: nil,
90+
},
91+
{
92+
Args: []string{"-X", "-Y", "-o", "someoption=test"},
93+
ParsedArgs: []string{"-X", "-Y", "-o", "someoption=test"},
94+
Command: nil,
95+
},
96+
{
97+
Args: []string{"-X", "-Y", "-o", "someoption=test", "local/file", "remote:file"},
98+
ParsedArgs: []string{"-X", "-Y", "-o", "someoption=test"},
99+
Command: []string{"local/file", "remote:file"},
100+
},
101+
{
102+
Args: []string{"-X", "-Y", "-o", "someoption=test", "local/file", "remote:file"},
103+
ParsedArgs: []string{"-X", "-Y", "-o", "someoption=test"},
104+
Command: []string{"local/file", "remote:file"},
105+
},
106+
{
107+
Args: []string{"local/file", "remote:file"},
108+
ParsedArgs: []string{},
109+
Command: []string{"local/file", "remote:file"},
110+
},
111+
{
112+
Args: []string{"-c"},
113+
ParsedArgs: nil,
114+
Command: nil,
115+
Error: "flag: -c requires an argument",
116+
},
117+
}
89118

90-
if err != nil {
91-
t.Errorf("unexpected error: %v on test case: %#v", err, tcase)
92-
continue
93-
}
119+
for _, tcase := range testCases {
120+
args, command, err := parseSCPArgs(tcase.Args)
121+
122+
checkParseResult(t, tcase, args, command, err)
123+
}
124+
}
94125

95-
argsStr, parsedArgsStr := fmt.Sprintf("%s", args), fmt.Sprintf("%s", tcase.ParsedArgs)
96-
if argsStr != parsedArgsStr {
97-
t.Errorf("args do not match parsed args. got: '%s', expected: '%s'", argsStr, parsedArgsStr)
126+
func checkParseResult(t *testing.T, tcase parseTestCase, gotArgs, gotCmd []string, gotErr error) {
127+
if tcase.Error != "" {
128+
if gotErr == nil {
129+
t.Errorf("expected error and got nil: %#v", tcase)
98130
}
99131

100-
commandStr, parsedCommandStr := fmt.Sprintf("%s", command), fmt.Sprintf("%s", tcase.Command)
101-
if commandStr != parsedCommandStr {
102-
t.Errorf("command does not match parsed command. got: '%s', expected: '%s'", commandStr, parsedCommandStr)
132+
if gotErr.Error() != tcase.Error {
133+
t.Errorf("error does not match expected error, got: '%s', expected: '%s'", gotErr.Error(), tcase.Error)
103134
}
135+
136+
return
137+
}
138+
139+
if gotErr != nil {
140+
t.Errorf("unexpected error: %v on test case: %#v", gotErr, tcase)
141+
return
142+
}
143+
144+
argsStr, parsedArgsStr := fmt.Sprintf("%s", gotArgs), fmt.Sprintf("%s", tcase.ParsedArgs)
145+
if argsStr != parsedArgsStr {
146+
t.Errorf("args do not match parsed args. got: '%s', expected: '%s'", argsStr, parsedArgsStr)
147+
}
148+
149+
commandStr, parsedCommandStr := fmt.Sprintf("%s", gotCmd), fmt.Sprintf("%s", tcase.Command)
150+
if commandStr != parsedCommandStr {
151+
t.Errorf("command does not match parsed command. got: '%s', expected: '%s'", commandStr, parsedCommandStr)
104152
}
105153
}

pkg/cmd/codespace/ssh.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,7 +392,7 @@ func (a *App) Copy(ctx context.Context, args []string, opts cpOptions) error {
392392
if opts.recursive {
393393
opts.scpArgs = append(opts.scpArgs, "-r")
394394
}
395-
opts.scpArgs = append(opts.scpArgs, "--")
395+
396396
hasRemote := false
397397
for _, arg := range args {
398398
if rest := strings.TrimPrefix(arg, "remote:"); rest != arg {

0 commit comments

Comments
 (0)
X Tutup