Fix iteration order dependency.
[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.curoverse.com/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         Run  = externalCmd{"arv-run"}
26
27         Keep = cmd.Multi(map[string]cmd.Handler{
28                 "get":       externalCmd{"arv-get"},
29                 "put":       externalCmd{"arv-put"},
30                 "ls":        externalCmd{"arv-ls"},
31                 "normalize": externalCmd{"arv-normalize"},
32                 "docker":    externalCmd{"arv-keepdocker"},
33         })
34         Pipeline = cmd.Multi(map[string]cmd.Handler{
35                 "run": externalCmd{"arv-run-pipeline-instance"},
36         })
37         // user, group, container, specimen, etc.
38         APICall = apiCallCmd{}
39 )
40
41 // When using the ruby "arv" command, flags must come before the
42 // subcommand: "arv --format=yaml get foo" works, but "arv get
43 // --format=yaml foo" does not work.
44 func legacyFlagsToFront(subcommand string, argsin []string) (argsout []string) {
45         flags, _ := LegacyFlagSet()
46         flags.SetOutput(ioutil.Discard)
47         flags.Parse(argsin)
48         narg := flags.NArg()
49         argsout = append(argsout, argsin[:len(argsin)-narg]...)
50         argsout = append(argsout, subcommand)
51         argsout = append(argsout, argsin[len(argsin)-narg:]...)
52         return
53 }
54
55 type apiCallCmd struct{}
56
57 func (cmd apiCallCmd) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
58         split := strings.Split(prog, " ")
59         if len(split) < 2 {
60                 fmt.Fprintf(stderr, "internal error: no api model in %q\n", prog)
61                 return 2
62         }
63         model := split[len(split)-1]
64         return externalCmd{"arv"}.RunCommand("arv", legacyFlagsToFront(model, args), stdin, stdout, stderr)
65 }
66
67 type rubyArvCmd struct {
68         subcommand string
69 }
70
71 func (rc rubyArvCmd) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
72         return externalCmd{"arv"}.RunCommand("arv", legacyFlagsToFront(rc.subcommand, args), stdin, stdout, stderr)
73 }
74
75 type externalCmd struct {
76         prog string
77 }
78
79 func (ec externalCmd) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
80         cmd := exec.Command(ec.prog, args...)
81         cmd.Stdin = stdin
82         cmd.Stdout = stdout
83         cmd.Stderr = stderr
84         err := cmd.Run()
85         switch err := err.(type) {
86         case nil:
87                 return 0
88         case *exec.ExitError:
89                 status := err.Sys().(syscall.WaitStatus)
90                 if status.Exited() {
91                         return status.ExitStatus()
92                 }
93                 fmt.Fprintf(stderr, "%s failed: %s\n", ec.prog, err)
94                 return 1
95         case *exec.Error:
96                 fmt.Fprintln(stderr, err)
97                 if ec.prog == "arv" || ec.prog == "arv-run-pipeline-instance" {
98                         fmt.Fprint(stderr, rubyInstallHints)
99                 } else if strings.HasPrefix(ec.prog, "arv-") {
100                         fmt.Fprint(stderr, pythonInstallHints)
101                 }
102                 return 1
103         default:
104                 fmt.Fprintf(stderr, "error running %s: %s\n", ec.prog, err)
105                 return 1
106         }
107 }
108
109 var (
110         rubyInstallHints = `
111 Note: This subcommand uses the arvados-cli Ruby gem. If that is not
112 installed, try "gem install arvados-cli", or see
113 https://doc.arvados.org/install for more details.
114
115 `
116         pythonInstallHints = `
117 Note: This subcommand uses the "arvados" Python module. If that is
118 not installed, try:
119 * "pip install arvados" (either as root or in a virtualenv), or
120 * "sudo apt-get install python-arvados-python-client", or
121 * see https://doc.arvados.org/install for more details.
122
123 `
124 )