12876: Pass commands through to ruby/python programs.
[arvados.git] / lib / cli / external.go
diff --git a/lib/cli/external.go b/lib/cli/external.go
new file mode 100644 (file)
index 0000000..ba85aae
--- /dev/null
@@ -0,0 +1,99 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: Apache-2.0
+
+package cli
+
+import (
+       "fmt"
+       "io"
+       "io/ioutil"
+       "os/exec"
+       "strings"
+       "syscall"
+
+       "git.curoverse.com/arvados.git/lib/cmd"
+)
+
+var (
+       Create = rubyArvCmd{"create"}
+       Edit   = rubyArvCmd{"edit"}
+
+       Copy = externalCmd{"arv-copy"}
+       Tag  = externalCmd{"arv-tag"}
+       Ws   = externalCmd{"arv-ws"}
+       Run  = externalCmd{"arv-run"}
+
+       Keep = cmd.Multi(map[string]cmd.Handler{
+               "get":       externalCmd{"arv-get"},
+               "put":       externalCmd{"arv-put"},
+               "ls":        externalCmd{"arv-ls"},
+               "normalize": externalCmd{"arv-normalize"},
+               "docker":    externalCmd{"arv-keepdocker"},
+       })
+       Pipeline = cmd.Multi(map[string]cmd.Handler{
+               "run": externalCmd{"arv-run-pipeline-instance"},
+       })
+       // user, group, container, specimen, etc.
+       APICall = apiCallCmd{}
+)
+
+// When using the ruby "arv" command, flags must come before the
+// subcommand: "arv --format=yaml get foo" works, but "arv get
+// --format=yaml foo" does not work.
+func legacyFlagsToFront(subcommand string, argsin []string) (argsout []string) {
+       flags, _ := LegacyFlagSet()
+       flags.SetOutput(ioutil.Discard)
+       flags.Parse(argsin)
+       narg := flags.NArg()
+       argsout = append(argsout, argsin[:len(argsin)-narg]...)
+       argsout = append(argsout, subcommand)
+       argsout = append(argsout, argsin[len(argsin)-narg:]...)
+       return
+}
+
+type apiCallCmd struct{}
+
+func (cmd apiCallCmd) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
+       split := strings.Split(prog, " ")
+       if len(split) < 2 {
+               fmt.Fprintf(stderr, "internal error: no api model in %q\n", prog)
+               return 2
+       }
+       model := split[len(split)-1]
+       return externalCmd{"arv"}.RunCommand("arv", legacyFlagsToFront(model, args), stdin, stdout, stderr)
+}
+
+type rubyArvCmd struct {
+       subcommand string
+}
+
+func (rc rubyArvCmd) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
+       return externalCmd{"arv"}.RunCommand("arv", legacyFlagsToFront(rc.subcommand, args), stdin, stdout, stderr)
+}
+
+type externalCmd struct {
+       prog string
+}
+
+func (ec externalCmd) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
+       cmd := exec.Command(ec.prog, args...)
+       cmd.Stdin = stdin
+       cmd.Stdout = stdout
+       cmd.Stderr = stderr
+       err := cmd.Run()
+       switch err := err.(type) {
+       case nil:
+               return 0
+       case *exec.ExitError:
+               status := err.Sys().(syscall.WaitStatus)
+               if status.Exited() {
+                       return status.ExitStatus()
+               }
+               fmt.Fprintf(stderr, "%s failed: %s\n", ec.prog, err)
+               return 1
+       default:
+               fmt.Fprintf(stderr, "error running %s: %s\n", ec.prog, err)
+               return 1
+       }
+}