16306: Merge branch 'master'
[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 externalCmd{"arv"}.RunCommand("arv", legacyFlagsToFront(model, 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         return externalCmd{"arv"}.RunCommand("arv", legacyFlagsToFront(rc.subcommand, args), stdin, stdout, stderr)
69 }
70
71 type externalCmd struct {
72         prog string
73 }
74
75 func (ec externalCmd) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
76         cmd := exec.Command(ec.prog, args...)
77         cmd.Stdin = stdin
78         cmd.Stdout = stdout
79         cmd.Stderr = stderr
80         err := cmd.Run()
81         switch err := err.(type) {
82         case nil:
83                 return 0
84         case *exec.ExitError:
85                 status := err.Sys().(syscall.WaitStatus)
86                 if status.Exited() {
87                         return status.ExitStatus()
88                 }
89                 fmt.Fprintf(stderr, "%s failed: %s\n", ec.prog, err)
90                 return 1
91         case *exec.Error:
92                 fmt.Fprintln(stderr, err)
93                 if ec.prog == "arv" {
94                         fmt.Fprint(stderr, rubyInstallHints)
95                 } else if strings.HasPrefix(ec.prog, "arv-") {
96                         fmt.Fprint(stderr, pythonInstallHints)
97                 }
98                 return 1
99         default:
100                 fmt.Fprintf(stderr, "error running %s: %s\n", ec.prog, err)
101                 return 1
102         }
103 }
104
105 var (
106         rubyInstallHints = `
107 Note: This subcommand uses the arvados-cli Ruby gem. If that is not
108 installed, try "gem install arvados-cli", or see
109 https://doc.arvados.org/install for more details.
110
111 `
112         pythonInstallHints = `
113 Note: This subcommand uses the "arvados" Python module. If that is
114 not installed, try:
115 * "pip install arvados" (either as root or in a virtualenv), or
116 * "sudo apt-get install python-arvados-python-client", or
117 * see https://doc.arvados.org/install for more details.
118
119 `
120 )