@@ -29,47 +29,18 @@ func Shell(ctx context.Context, p printer, sshArgs []string, port int, destinati
2929 return cmd .Run ()
3030}
3131
32- // Copy runs an scp command over the specified port. The arguments may
33- // include flags and non-flags, optionally separated by "--" .
32+ // Copy runs an scp command over the specified port. scpArgs should contain both scp flags
33+ // as well as the list of files to copy, with the flags first .
3434//
3535// Remote files indicated by a "remote:" prefix are resolved relative
3636// to the remote user's home directory, and are subject to shell expansion
3737// on the remote host; see https://lwn.net/Articles/835962/.
3838func Copy (ctx context.Context , scpArgs []string , port int , destination string ) error {
39- // Beware: invalid syntax causes scp to exit 1 with
40- // no error message, so don't let that happen.
41- connArgs := []string {
42- "-P" , strconv .Itoa (port ),
43- "-o" , "NoHostAuthenticationForLocalhost=yes" ,
44- "-C" , // compression
45- }
46-
47- cmdArgs , command , err := parseSCPArgs (scpArgs )
39+ cmd , err := newSCPCommand (ctx , port , destination , scpArgs )
4840 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 )
63- }
41+ return fmt .Errorf ("failed to create scp command: %w" , err )
6442 }
6543
66- fmt .Println (cmdArgs )
67-
68- cmd := exec .CommandContext (ctx , "scp" , cmdArgs ... )
69-
70- cmd .Stdin = nil
71- cmd .Stdout = os .Stderr
72- cmd .Stderr = os .Stderr
7344 return cmd .Run ()
7445}
7546
@@ -116,11 +87,49 @@ func parseSSHArgs(args []string) (cmdArgs, command []string, err error) {
11687 return parseArgs (args , "bcDeFIiLlmOopRSWw" )
11788}
11889
90+ // newSCPCommand populates an exec.Cmd to run an scp command for the files specified in cmdArgs.
91+ // cmdArgs is parsed such that scp flags and the files to copy are separated by a "--" in the command.
92+ // For example: scp -F ./config -- local/file remote:file
93+ func newSCPCommand (ctx context.Context , port int , dst string , cmdArgs []string ) (* exec.Cmd , error ) {
94+ // Beware: invalid syntax causes scp to exit 1 with
95+ // no error message, so don't let that happen.
96+ connArgs := []string {
97+ "-P" , strconv .Itoa (port ),
98+ "-o" , "NoHostAuthenticationForLocalhost=yes" ,
99+ "-C" , // compression
100+ }
101+
102+ cmdArgs , command , err := parseSCPArgs (cmdArgs )
103+ if err != nil {
104+ return nil , err
105+ }
106+
107+ cmdArgs = append (cmdArgs , connArgs ... )
108+
109+ cmdArgs = append (cmdArgs , "--" )
110+
111+ for _ , arg := range command {
112+ // Replace "remote:" prefix with (e.g.) "root@localhost:".
113+ if rest := strings .TrimPrefix (arg , "remote:" ); rest != arg {
114+ arg = dst + ":" + rest
115+ }
116+ cmdArgs = append (cmdArgs , arg )
117+ }
118+
119+ cmd := exec .CommandContext (ctx , "scp" , cmdArgs ... )
120+
121+ cmd .Stdin = nil
122+ cmd .Stdout = os .Stderr
123+ cmd .Stderr = os .Stderr
124+
125+ return cmd , nil
126+ }
127+
119128func parseSCPArgs (args []string ) (cmdArgs , command []string , err error ) {
120129 return parseArgs (args , "cFiJloPS" )
121130}
122131
123- // parseArgs parses arguments into two distinct slices of flags and command. Parsing stops
132+ // parseArgs parses arguments into two distinct slices of flags and command. Parsing stops
124133// as soon as a non-flag argument is found assuming the remaining arguments are the command.
125134// It returns an error if a unary flag is provided without an argument.
126135func parseArgs (args []string , unaryFlags string ) (cmdArgs , command []string , err error ) {
0 commit comments