@@ -32,6 +32,14 @@ type options struct {
32
32
func sshCode (host , dir string , o options ) error {
33
33
flog .Info ("ensuring code-server is updated..." )
34
34
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
+
35
43
dlScript := downloadScript (codeServerPath )
36
44
37
45
// Downloads the latest code-server and allows it to be executed.
@@ -41,7 +49,7 @@ func sshCode(host, dir string, o options) error {
41
49
sshCmd .Stdout = os .Stdout
42
50
sshCmd .Stderr = os .Stderr
43
51
sshCmd .Stdin = strings .NewReader (dlScript )
44
- err : = sshCmd .Run ()
52
+ err = sshCmd .Run ()
45
53
if err != nil {
46
54
return xerrors .Errorf ("failed to update code-server: \n ---ssh cmd---\n %s\n ---download script---\n %s: %w" ,
47
55
sshCmdStr ,
@@ -341,3 +349,55 @@ func ensureDir(path string) error {
341
349
342
350
return nil
343
351
}
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
+
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