@@ -32,6 +32,14 @@ type options struct {
3232func sshCode (host , dir string , o options ) error {
3333 flog .Info ("ensuring code-server is updated..." )
3434
35+ host , extraSSHFlags , err := parseIP (host )
36+ if err != nil {
37+ return xerrors .Errorf ("failed to parse host IP: %w" , err )
38+ }
39+ if extraSSHFlags != "" {
40+ o .sshFlags = strings .Join ([]string {extraSSHFlags , o .sshFlags }, " " )
41+ }
42+
3543 dlScript := downloadScript (codeServerPath )
3644
3745 // Downloads the latest code-server and allows it to be executed.
@@ -41,7 +49,7 @@ func sshCode(host, dir string, o options) error {
4149 sshCmd .Stdout = os .Stdout
4250 sshCmd .Stderr = os .Stderr
4351 sshCmd .Stdin = strings .NewReader (dlScript )
44- err : = sshCmd .Run ()
52+ err = sshCmd .Run ()
4553 if err != nil {
4654 return xerrors .Errorf ("failed to update code-server: \n ---ssh cmd---\n %s\n ---download script---\n %s: %w" ,
4755 sshCmdStr ,
@@ -341,3 +349,55 @@ func ensureDir(path string) error {
341349
342350 return nil
343351}
352+
353+ // parseIP parses the host to a valid IP address. If 'gcp:' is prefixed to the
354+ // host then a lookup is done using gcloud to determine the external IP and any
355+ // additional SSH arguments that should be used for ssh commands.
356+ func parseIP (host string ) (ip string , additionalFlags string , err error ) {
357+ host = strings .TrimSpace (host )
358+ switch {
359+ case strings .HasPrefix (host , "gcp:" ):
360+ instance := strings .TrimPrefix (host , "gcp:" )
361+ return parseGCPSSHCmd (instance )
362+ default :
363+ if net .ParseIP (host ) == nil {
364+ return "" , "" , xerrors .New ("host argument is not a valid IP address" )
365+ }
366+ return host , "" , nil
367+ }
368+ }
369+
370+ // parseGCPSSHCmd parses the IP address and flags used by 'gcloud' when
371+ // ssh'ing to an instance.
372+ func parseGCPSSHCmd (instance string ) (ip , sshFlags string , err error ) {
373+ dryRunCmd := fmt .Sprintf ("gcloud compute ssh --dry-run %v" , instance )
374+
375+ out , err := exec .Command ("sh" , "-c" , dryRunCmd ).CombinedOutput ()
376+ if err != nil {
377+ return "" , "" , xerrors .Errorf ("%s: %w" , out , err )
378+ }
379+
380+ toks := strings .Split (string (out ), " " )
381+ if len (toks ) < 2 {
382+ return "" , "" , xerrors .Errorf ("unexpected output for '%v' command, %s" , dryRunCmd , out )
383+ }
384+
385+ // Slice off the '/usr/bin/ssh' prefix and the '<user>@<ip>' suffix.
386+ sshFlags = strings .Join (toks [1 :len (toks )- 1 ], " " )
387+
388+ // E.g. foo@1.2.3.4.
389+ userIP := toks [len (toks )- 1 ]
390+ toks = strings .Split (userIP , "@" )
391+ // Assume the '<user>@' is missing.
392+ if len (toks ) < 2 {
393+ ip = strings .TrimSpace (toks [0 ])
394+ } else {
395+ ip = strings .TrimSpace (toks [1 ])
396+ }
397+
398+ if net .ParseIP (ip ) == nil {
399+ return "" , "" , xerrors .Errorf ("parsed invalid ip address %v" , ip )
400+ }
401+
402+ return ip , sshFlags , nil
403+ }
0 commit comments