17170: Add "arvados-client shell" subcommand and backend support.
[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] 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         targetUUID := f.Args()[0]
51         sshargs := f.Args()[1:]
52
53         selfbin, err := os.Readlink("/proc/self/exe")
54         if err != nil {
55                 fmt.Fprintln(stderr, err)
56                 return 2
57         }
58         sshargs = append([]string{
59                 "-o", "ProxyCommand " + selfbin + " connect-ssh -detach-keys='" + strings.Replace(*detachKeys, "'", "'\\''", -1) + "' " + targetUUID,
60                 "-o", "StrictHostKeyChecking no",
61                 "root@" + targetUUID},
62                 sshargs...)
63         cmd := exec.Command("ssh", sshargs...)
64         cmd.Stdin = stdin
65         cmd.Stdout = stdout
66         cmd.Stderr = stderr
67         err = cmd.Run()
68         if err == nil {
69                 return 0
70         } else if exiterr, ok := err.(*exec.ExitError); !ok {
71                 fmt.Fprintln(stderr, err)
72                 return 1
73         } else if status, ok := exiterr.Sys().(syscall.WaitStatus); !ok {
74                 fmt.Fprintln(stderr, err)
75                 return 1
76         } else {
77                 return status.ExitStatus()
78         }
79 }
80
81 // connectSSHCommand connects stdin/stdout to a container's gateway
82 // server (see lib/crunchrun/ssh.go).
83 //
84 // It is intended to be invoked with OpenSSH client's ProxyCommand
85 // config.
86 type connectSSHCommand struct{}
87
88 func (connectSSHCommand) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
89         f := flag.NewFlagSet(prog, flag.ContinueOnError)
90         f.SetOutput(stderr)
91         f.Usage = func() {
92                 fmt.Fprint(stderr, prog+`: connect to the gateway service for a running container.
93
94 Usage: `+prog+` [options] container-uuid
95
96 Options:
97 `)
98                 f.PrintDefaults()
99         }
100         detachKeys := f.String("detach-keys", "", "set detach key sequence, as in docker-attach(1)")
101         if err := f.Parse(args); err != nil {
102                 fmt.Fprintln(stderr, err)
103                 f.Usage()
104                 return 2
105         } else if f.NArg() != 1 {
106                 f.Usage()
107                 return 2
108         }
109         targetUUID := f.Args()[0]
110         insecure := os.Getenv("ARVADOS_API_HOST_INSECURE")
111         rpcconn := rpc.NewConn("",
112                 &url.URL{
113                         Scheme: "https",
114                         Host:   os.Getenv("ARVADOS_API_HOST"),
115                 },
116                 insecure == "1" || insecure == "yes" || insecure == "true",
117                 func(context.Context) ([]string, error) {
118                         return []string{os.Getenv("ARVADOS_API_TOKEN")}, nil
119                 })
120         // if strings.Contains(targetUUID, "-xvhdp-") {
121         //      cr, err := rpcconn.ContainerRequestGet(context.TODO(), arvados.GetOptions{UUID: targetUUID})
122         //      if err != nil {
123         //              fmt.Fprintln(stderr, err)
124         //              return 1
125         //      }
126         //      if cr.ContainerUUID == "" {
127         //              fmt.Fprintf(stderr, "no container assigned, container request state is %s\n", strings.ToLower(cr.State))
128         //              return 1
129         //      }
130         //      targetUUID = cr.ContainerUUID
131         // }
132         sshconn, err := rpcconn.ContainerSSH(context.TODO(), arvados.ContainerSSHOptions{
133                 UUID:       targetUUID,
134                 DetachKeys: *detachKeys,
135         })
136         if err != nil {
137                 fmt.Fprintln(stderr, err)
138                 return 1
139         }
140         defer sshconn.Conn.Close()
141
142         ctx, cancel := context.WithCancel(context.Background())
143         go func() {
144                 defer cancel()
145                 _, err := io.Copy(stdout, sshconn.Conn)
146                 if err != nil && ctx.Err() == nil {
147                         fmt.Fprintf(stderr, "receive: %v\n", err)
148                 }
149         }()
150         go func() {
151                 defer cancel()
152                 _, err := io.Copy(sshconn.Conn, stdin)
153                 if err != nil && ctx.Err() == nil {
154                         fmt.Fprintf(stderr, "send: %v\n", err)
155                 }
156         }()
157         <-ctx.Done()
158         return 0
159 }