Merge branch '17389-keepproxy-storage-classes-confirmed'
[arvados.git] / cmd / arvados-client / container_gateway.go
index a638000ae625e89d56f4784702a0250570045265..5359e00c66052d25512e89bd85906b268b1360e1 100644 (file)
@@ -5,6 +5,7 @@
 package main
 
 import (
+       "bytes"
        "context"
        "flag"
        "fmt"
@@ -12,6 +13,7 @@ import (
        "net/url"
        "os"
        "os/exec"
+       "path/filepath"
        "strings"
        "syscall"
 
@@ -27,6 +29,7 @@ func (shellCommand) RunCommand(prog string, args []string, stdin io.Reader, stdo
        f := flag.NewFlagSet(prog, flag.ContinueOnError)
        f.SetOutput(stderr)
        f.Usage = func() {
+               _, prog := filepath.Split(prog)
                fmt.Fprint(stderr, prog+`: open an interactive shell on a running container.
 
 Usage: `+prog+` [options] [username@]container-uuid [ssh-options] [remote-command [args...]]
@@ -52,6 +55,32 @@ Options:
        }
        sshargs := f.Args()[1:]
 
+       // Try setting up a tunnel, and exit right away if it
+       // fails. This tunnel won't get used -- we'll set up a new
+       // tunnel when running as SSH client's ProxyCommand child --
+       // but in most cases where the real tunnel setup would fail,
+       // we catch the problem earlier here. This makes it less
+       // likely that an error message about tunnel setup will get
+       // hidden behind noisy errors from SSH client like this:
+       //
+       // [useful tunnel setup error message here]
+       // kex_exchange_identification: Connection closed by remote host
+       // Connection closed by UNKNOWN port 65535
+       // exit status 255
+       //
+       // In case our target is a container request, the probe also
+       // resolves it to a container, so we don't connect to two
+       // different containers in a race.
+       var probetarget bytes.Buffer
+       exitcode := connectSSHCommand{}.RunCommand(
+               "arvados-client connect-ssh",
+               []string{"-detach-keys=" + *detachKeys, "-probe-only=true", target},
+               &bytes.Buffer{}, &probetarget, stderr)
+       if exitcode != 0 {
+               return exitcode
+       }
+       target = strings.Trim(probetarget.String(), "\n")
+
        selfbin, err := os.Readlink("/proc/self/exe")
        if err != nil {
                fmt.Fprintln(stderr, err)
@@ -84,14 +113,19 @@ func (connectSSHCommand) RunCommand(prog string, args []string, stdin io.Reader,
        f := flag.NewFlagSet(prog, flag.ContinueOnError)
        f.SetOutput(stderr)
        f.Usage = func() {
+               _, prog := filepath.Split(prog)
                fmt.Fprint(stderr, prog+`: connect to the gateway service for a running container.
 
+NOTE: You almost certainly don't want to use this command directly. It
+is meant to be used internally. Use "arvados-client shell" instead.
+
 Usage: `+prog+` [options] [username@]container-uuid
 
 Options:
 `)
                f.PrintDefaults()
        }
+       probeOnly := f.Bool("probe-only", false, "do not transfer IO, just setup tunnel, print target UUID, and exit")
        detachKeys := f.String("detach-keys", "", "set detach key sequence, as in docker-attach(1)")
        if err := f.Parse(args); err != nil {
                fmt.Fprintln(stderr, err)
@@ -106,6 +140,10 @@ Options:
                loginUsername = targetUUID[:i]
                targetUUID = targetUUID[i+1:]
        }
+       if os.Getenv("ARVADOS_API_HOST") == "" || os.Getenv("ARVADOS_API_TOKEN") == "" {
+               fmt.Fprintln(stderr, "fatal: ARVADOS_API_HOST and ARVADOS_API_TOKEN environment variables are not set")
+               return 1
+       }
        insecure := os.Getenv("ARVADOS_API_HOST_INSECURE")
        rpcconn := rpc.NewConn("",
                &url.URL{
@@ -148,6 +186,11 @@ Options:
        }
        defer sshconn.Conn.Close()
 
+       if *probeOnly {
+               fmt.Fprintln(stdout, targetUUID)
+               return 0
+       }
+
        ctx, cancel := context.WithCancel(context.Background())
        go func() {
                defer cancel()