1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
18 "git.arvados.org/arvados.git/lib/controller/rpc"
19 "git.arvados.org/arvados.git/sdk/go/arvados"
22 // shellCommand connects the terminal to an interactive shell on a
24 type shellCommand struct{}
26 func (shellCommand) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
27 f := flag.NewFlagSet(prog, flag.ContinueOnError)
30 fmt.Fprint(stderr, prog+`: open an interactive shell on a running container.
32 Usage: `+prog+` [options] [username@]container-uuid [ssh-options] [remote-command [args...]]
38 detachKeys := f.String("detach-keys", "ctrl-],ctrl-]", "set detach key sequence, as in docker-attach(1)")
41 fmt.Fprintln(stderr, err)
50 if !strings.Contains(target, "@") {
51 target = "root@" + target
53 sshargs := f.Args()[1:]
55 selfbin, err := os.Readlink("/proc/self/exe")
57 fmt.Fprintln(stderr, err)
60 sshargs = append([]string{
62 "-o", "ProxyCommand " + selfbin + " connect-ssh -detach-keys=" + shellescape(*detachKeys) + " " + shellescape(target),
63 "-o", "StrictHostKeyChecking no",
66 sshbin, err := exec.LookPath("ssh")
68 fmt.Fprintln(stderr, err)
71 err = syscall.Exec(sshbin, sshargs, os.Environ())
72 fmt.Fprintf(stderr, "exec(%q) failed: %s\n", sshbin, err)
76 // connectSSHCommand connects stdin/stdout to a container's gateway
77 // server (see lib/crunchrun/ssh.go).
79 // It is intended to be invoked with OpenSSH client's ProxyCommand
81 type connectSSHCommand struct{}
83 func (connectSSHCommand) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
84 f := flag.NewFlagSet(prog, flag.ContinueOnError)
87 fmt.Fprint(stderr, prog+`: connect to the gateway service for a running container.
89 Usage: `+prog+` [options] [username@]container-uuid
95 detachKeys := f.String("detach-keys", "", "set detach key sequence, as in docker-attach(1)")
96 if err := f.Parse(args); err != nil {
97 fmt.Fprintln(stderr, err)
99 } else if f.NArg() != 1 {
103 targetUUID := f.Args()[0]
104 loginUsername := "root"
105 if i := strings.Index(targetUUID, "@"); i >= 0 {
106 loginUsername = targetUUID[:i]
107 targetUUID = targetUUID[i+1:]
109 insecure := os.Getenv("ARVADOS_API_HOST_INSECURE")
110 rpcconn := rpc.NewConn("",
113 Host: os.Getenv("ARVADOS_API_HOST"),
115 insecure == "1" || insecure == "yes" || insecure == "true",
116 func(context.Context) ([]string, error) {
117 return []string{os.Getenv("ARVADOS_API_TOKEN")}, nil
119 if strings.Contains(targetUUID, "-xvhdp-") {
120 crs, err := rpcconn.ContainerRequestList(context.TODO(), arvados.ListOptions{Limit: -1, Filters: []arvados.Filter{{"uuid", "=", targetUUID}}})
122 fmt.Fprintln(stderr, err)
125 if len(crs.Items) < 1 {
126 fmt.Fprintf(stderr, "container request %q not found\n", targetUUID)
130 if cr.ContainerUUID == "" {
131 fmt.Fprintf(stderr, "no container assigned, container request state is %s\n", strings.ToLower(string(cr.State)))
134 targetUUID = cr.ContainerUUID
135 fmt.Fprintln(stderr, "connecting to container", targetUUID)
136 } else if !strings.Contains(targetUUID, "-dz642-") {
137 fmt.Fprintf(stderr, "target UUID is not a container or container request UUID: %s\n", targetUUID)
140 sshconn, err := rpcconn.ContainerSSH(context.TODO(), arvados.ContainerSSHOptions{
142 DetachKeys: *detachKeys,
143 LoginUsername: loginUsername,
146 fmt.Fprintln(stderr, "error setting up tunnel:", err)
149 defer sshconn.Conn.Close()
151 ctx, cancel := context.WithCancel(context.Background())
154 _, err := io.Copy(stdout, sshconn.Conn)
155 if err != nil && ctx.Err() == nil {
156 fmt.Fprintf(stderr, "receive: %v\n", err)
161 _, err := io.Copy(sshconn.Conn, stdin)
162 if err != nil && ctx.Err() == nil {
163 fmt.Fprintf(stderr, "send: %v\n", err)
170 func shellescape(s string) string {
171 return "'" + strings.Replace(s, "'", "'\\''", -1) + "'"