17344: Install ruby arv as arv-ruby, and arvados-client as arv.
[arvados.git] / lib / cli / external.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: Apache-2.0
4
5 package cli
6
7 import (
8         "fmt"
9         "io"
10         "io/ioutil"
11         "os/exec"
12         "strings"
13         "syscall"
14
15         "git.arvados.org/arvados.git/lib/cmd"
16 )
17
18 var (
19         Create = rubyArvCmd{"create"}
20         Edit   = rubyArvCmd{"edit"}
21
22         Copy = externalCmd{"arv-copy"}
23         Tag  = externalCmd{"arv-tag"}
24         Ws   = externalCmd{"arv-ws"}
25
26         Keep = cmd.Multi(map[string]cmd.Handler{
27                 "get":       externalCmd{"arv-get"},
28                 "put":       externalCmd{"arv-put"},
29                 "ls":        externalCmd{"arv-ls"},
30                 "normalize": externalCmd{"arv-normalize"},
31                 "docker":    externalCmd{"arv-keepdocker"},
32         })
33         // user, group, container, specimen, etc.
34         APICall = apiCallCmd{}
35 )
36
37 // When using the ruby "arv" command, flags must come before the
38 // subcommand: "arv --format=yaml get foo" works, but "arv get
39 // --format=yaml foo" does not work.
40 func legacyFlagsToFront(subcommand string, argsin []string) (argsout []string) {
41         flags, _ := LegacyFlagSet()
42         flags.SetOutput(ioutil.Discard)
43         flags.Parse(argsin)
44         narg := flags.NArg()
45         argsout = append(argsout, argsin[:len(argsin)-narg]...)
46         argsout = append(argsout, subcommand)
47         argsout = append(argsout, argsin[len(argsin)-narg:]...)
48         return
49 }
50
51 type apiCallCmd struct{}
52
53 func (cmd apiCallCmd) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
54         split := strings.Split(prog, " ")
55         if len(split) < 2 {
56                 fmt.Fprintf(stderr, "internal error: no api model in %q\n", prog)
57                 return 2
58         }
59         model := split[len(split)-1]
60         return rubyArvCmd{model}.RunCommand(prog, args, stdin, stdout, stderr)
61 }
62
63 type rubyArvCmd struct {
64         subcommand string
65 }
66
67 func (rc rubyArvCmd) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
68         wrapprog := "arv-ruby"
69         if _, err := exec.LookPath(wrapprog); err != nil && !strings.Contains(prog, "arv ") {
70                 // arv-ruby isn't in PATH (i.e., installation method
71                 // wasn't a recent "arvados-server install", which
72                 // symlinks /usr/bin/arv-ruby ->
73                 // /var/lib/arvados/bin/arv), so fall back to looking
74                 // for the arvados-cli program as "arv". (But don't do
75                 // this if we are being run as "arv" -- that would
76                 // probably cause a recursive fork bomb.)
77                 wrapprog = "arv"
78         }
79         return externalCmd{wrapprog}.RunCommand(wrapprog, legacyFlagsToFront(rc.subcommand, args), stdin, stdout, stderr)
80 }
81
82 type externalCmd struct {
83         prog string
84 }
85
86 func (ec externalCmd) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
87         cmd := exec.Command(ec.prog, args...)
88         cmd.Stdin = stdin
89         cmd.Stdout = stdout
90         cmd.Stderr = stderr
91         err := cmd.Run()
92         switch err := err.(type) {
93         case nil:
94                 return 0
95         case *exec.ExitError:
96                 status := err.Sys().(syscall.WaitStatus)
97                 if status.Exited() {
98                         return status.ExitStatus()
99                 }
100                 fmt.Fprintf(stderr, "%s failed: %s\n", ec.prog, err)
101                 return 1
102         case *exec.Error:
103                 fmt.Fprintln(stderr, err)
104                 if ec.prog == "arv" || ec.prog == "arv-ruby" {
105                         fmt.Fprint(stderr, rubyInstallHints)
106                 } else if strings.HasPrefix(ec.prog, "arv-") {
107                         fmt.Fprint(stderr, pythonInstallHints)
108                 }
109                 return 1
110         default:
111                 fmt.Fprintf(stderr, "error running %s: %s\n", ec.prog, err)
112                 return 1
113         }
114 }
115
116 var (
117         rubyInstallHints = `
118 Note: This subcommand uses the arvados-cli Ruby gem. If that is not
119 installed, try "gem install arvados-cli", or see
120 https://doc.arvados.org/install for more details.
121
122 `
123         pythonInstallHints = `
124 Note: This subcommand uses the "arvados" Python module. If that is
125 not installed, try:
126 * "pip install arvados" (either as root or in a virtualenv), or
127 * "sudo apt-get install python3-arvados-python-client", or
128 * see https://doc.arvados.org/install for more details.
129
130 `
131 )