17170: Specify login username. Improve logging.
[arvados.git] / cmd / arvados-client / container_gateway.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: Apache-2.0
4
5 package main
6
7 import (
8         "context"
9         "flag"
10         "fmt"
11         "io"
12         "net/url"
13         "os"
14         "os/exec"
15         "strings"
16         "syscall"
17
18         "git.arvados.org/arvados.git/lib/controller/rpc"
19         "git.arvados.org/arvados.git/sdk/go/arvados"
20 )
21
22 // shellCommand connects the terminal to an interactive shell on a
23 // running container.
24 type shellCommand struct{}
25
26 func (shellCommand) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
27         f := flag.NewFlagSet(prog, flag.ContinueOnError)
28         f.SetOutput(stderr)
29         f.Usage = func() {
30                 fmt.Print(stderr, prog+`: open an interactive shell on a running container.
31
32 Usage: `+prog+` [options] [username@]container-uuid [ssh-options] [remote-command [args...]]
33
34 Options:
35 `)
36                 f.PrintDefaults()
37         }
38         detachKeys := f.String("detach-keys", "ctrl-],ctrl-]", "set detach key sequence, as in docker-attach(1)")
39         err := f.Parse(args)
40         if err != nil {
41                 fmt.Println(stderr, err)
42                 f.Usage()
43                 return 2
44         }
45
46         if f.NArg() < 1 {
47                 f.Usage()
48                 return 2
49         }
50         target := f.Args()[0]
51         if !strings.Contains(target, "@") {
52                 target = "root@" + target
53         }
54         sshargs := f.Args()[1:]
55
56         selfbin, err := os.Readlink("/proc/self/exe")
57         if err != nil {
58                 fmt.Fprintln(stderr, err)
59                 return 2
60         }
61         sshargs = append([]string{
62                 "-o", "ProxyCommand " + selfbin + " connect-ssh -detach-keys=" + shellescape(*detachKeys) + " " + shellescape(target),
63                 "-o", "StrictHostKeyChecking no",
64                 target},
65                 sshargs...)
66         cmd := exec.Command("ssh", sshargs...)
67         cmd.Stdin = stdin
68         cmd.Stdout = stdout
69         cmd.Stderr = stderr
70         err = cmd.Run()
71         if err == nil {
72                 return 0
73         } else if exiterr, ok := err.(*exec.ExitError); !ok {
74                 fmt.Fprintln(stderr, err)
75                 return 1
76         } else if status, ok := exiterr.Sys().(syscall.WaitStatus); !ok {
77                 fmt.Fprintln(stderr, err)
78                 return 1
79         } else {
80                 return status.ExitStatus()
81         }
82 }
83
84 // connectSSHCommand connects stdin/stdout to a container's gateway
85 // server (see lib/crunchrun/ssh.go).
86 //
87 // It is intended to be invoked with OpenSSH client's ProxyCommand
88 // config.
89 type connectSSHCommand struct{}
90
91 func (connectSSHCommand) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
92         f := flag.NewFlagSet(prog, flag.ContinueOnError)
93         f.SetOutput(stderr)
94         f.Usage = func() {
95                 fmt.Fprint(stderr, prog+`: connect to the gateway service for a running container.
96
97 Usage: `+prog+` [options] [username@]container-uuid
98
99 Options:
100 `)
101                 f.PrintDefaults()
102         }
103         detachKeys := f.String("detach-keys", "", "set detach key sequence, as in docker-attach(1)")
104         if err := f.Parse(args); err != nil {
105                 fmt.Fprintln(stderr, err)
106                 f.Usage()
107                 return 2
108         } else if f.NArg() != 1 {
109                 f.Usage()
110                 return 2
111         }
112         targetUUID := f.Args()[0]
113         loginUsername := "root"
114         if i := strings.Index(targetUUID, "@"); i >= 0 {
115                 loginUsername = targetUUID[:i]
116                 targetUUID = targetUUID[i+1:]
117         }
118         insecure := os.Getenv("ARVADOS_API_HOST_INSECURE")
119         rpcconn := rpc.NewConn("",
120                 &url.URL{
121                         Scheme: "https",
122                         Host:   os.Getenv("ARVADOS_API_HOST"),
123                 },
124                 insecure == "1" || insecure == "yes" || insecure == "true",
125                 func(context.Context) ([]string, error) {
126                         return []string{os.Getenv("ARVADOS_API_TOKEN")}, nil
127                 })
128         // if strings.Contains(targetUUID, "-xvhdp-") {
129         //      cr, err := rpcconn.ContainerRequestGet(context.TODO(), arvados.GetOptions{UUID: targetUUID})
130         //      if err != nil {
131         //              fmt.Fprintln(stderr, err)
132         //              return 1
133         //      }
134         //      if cr.ContainerUUID == "" {
135         //              fmt.Fprintf(stderr, "no container assigned, container request state is %s\n", strings.ToLower(cr.State))
136         //              return 1
137         //      }
138         //      targetUUID = cr.ContainerUUID
139         // }
140         sshconn, err := rpcconn.ContainerSSH(context.TODO(), arvados.ContainerSSHOptions{
141                 UUID:          targetUUID,
142                 DetachKeys:    *detachKeys,
143                 LoginUsername: loginUsername,
144         })
145         if err != nil {
146                 fmt.Fprintln(stderr, err)
147                 return 1
148         }
149         defer sshconn.Conn.Close()
150
151         ctx, cancel := context.WithCancel(context.Background())
152         go func() {
153                 defer cancel()
154                 _, err := io.Copy(stdout, sshconn.Conn)
155                 if err != nil && ctx.Err() == nil {
156                         fmt.Fprintf(stderr, "receive: %v\n", err)
157                 }
158         }()
159         go func() {
160                 defer cancel()
161                 _, err := io.Copy(sshconn.Conn, stdin)
162                 if err != nil && ctx.Err() == nil {
163                         fmt.Fprintf(stderr, "send: %v\n", err)
164                 }
165         }()
166         <-ctx.Done()
167         return 0
168 }
169
170 func shellescape(s string) string {
171         return "'" + strings.Replace(s, "'", "'\\''", -1) + "'"
172 }