Merge branch '16265-security-updates' into dependabot/bundler/apps/workbench/loofah...
[arvados.git] / lib / cmd / cmd.go
index d04006586f307ee4636ebabfbaaae8c084654480..611c95d2340a3b2da47b8a7cbcfff2a3aad9af8c 100644 (file)
@@ -11,6 +11,9 @@ import (
        "fmt"
        "io"
        "io/ioutil"
+       "path/filepath"
+       "regexp"
+       "runtime"
        "sort"
        "strings"
 )
@@ -25,8 +28,28 @@ func (f HandlerFunc) RunCommand(prog string, args []string, stdin io.Reader, std
        return f(prog, args, stdin, stdout, stderr)
 }
 
-// Multi is a Handler that looks up its first argument in a map, and
-// invokes the resulting Handler with the remaining args.
+// Version is a Handler that prints the package version (set at build
+// time using -ldflags) and Go runtime version to stdout, and returns
+// 0.
+var Version versionCommand
+
+var version = "dev"
+
+type versionCommand struct{}
+
+func (versionCommand) String() string {
+       return fmt.Sprintf("%s (%s)", version, runtime.Version())
+}
+
+func (versionCommand) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
+       prog = regexp.MustCompile(` -*version$`).ReplaceAllLiteralString(prog, "")
+       fmt.Fprintf(stdout, "%s %s (%s)\n", prog, version, runtime.Version())
+       return 0
+}
+
+// Multi is a Handler that looks up its first argument in a map (after
+// stripping any "arvados-" or "crunch-" prefix), and invokes the
+// resulting Handler with the remaining args.
 //
 // Example:
 //
@@ -41,21 +64,42 @@ func (f HandlerFunc) RunCommand(prog string, args []string, stdin io.Reader, std
 type Multi map[string]Handler
 
 func (m Multi) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
-       if len(args) < 1 {
+       _, basename := filepath.Split(prog)
+       if i := strings.Index(basename, "~"); i >= 0 {
+               // drop "~anything" suffix (arvados-dispatch-cloud's
+               // DeployRunnerBinary feature relies on this)
+               basename = basename[:i]
+       }
+       cmd, ok := m[basename]
+       if !ok {
+               // "controller" command exists, and binary is named "arvados-controller"
+               cmd, ok = m[strings.TrimPrefix(basename, "arvados-")]
+       }
+       if !ok {
+               // "dispatch-slurm" command exists, and binary is named "crunch-dispatch-slurm"
+               cmd, ok = m[strings.TrimPrefix(basename, "crunch-")]
+       }
+       if ok {
+               return cmd.RunCommand(prog, args, stdin, stdout, stderr)
+       } else if len(args) < 1 {
                fmt.Fprintf(stderr, "usage: %s command [args]\n", prog)
                m.Usage(stderr)
                return 2
-       }
-       if cmd, ok := m[args[0]]; !ok {
-               fmt.Fprintf(stderr, "unrecognized command %q\n", args[0])
+       } else if cmd, ok = m[args[0]]; ok {
+               return cmd.RunCommand(prog+" "+args[0], args[1:], stdin, stdout, stderr)
+       } else {
+               fmt.Fprintf(stderr, "%s: unrecognized command %q\n", prog, args[0])
                m.Usage(stderr)
                return 2
-       } else {
-               return cmd.RunCommand(prog+" "+args[0], args[1:], stdin, stdout, stderr)
        }
 }
 
 func (m Multi) Usage(stderr io.Writer) {
+       fmt.Fprintf(stderr, "\nAvailable commands:\n")
+       m.listSubcommands(stderr, "")
+}
+
+func (m Multi) listSubcommands(out io.Writer, prefix string) {
        var subcommands []string
        for sc := range m {
                if strings.HasPrefix(sc, "-") {
@@ -67,9 +111,13 @@ func (m Multi) Usage(stderr io.Writer) {
                subcommands = append(subcommands, sc)
        }
        sort.Strings(subcommands)
-       fmt.Fprintf(stderr, "\nAvailable commands:\n")
        for _, sc := range subcommands {
-               fmt.Fprintf(stderr, "    %s\n", sc)
+               switch cmd := m[sc].(type) {
+               case Multi:
+                       cmd.listSubcommands(out, prefix+sc+" ")
+               default:
+                       fmt.Fprintf(out, "    %s%s\n", prefix, sc)
+               }
        }
 }