From f0acaeb4289af4b30cc0051936fa9fd802f334c9 Mon Sep 17 00:00:00 2001 From: Michael Anthony Knyszek Date: Mon, 18 Jul 2022 20:55:08 +0000 Subject: [PATCH] cmd/gomote: add groups This change adds the concept of gomote groups into the CLI, where the user may manage one or more gomotes with a shorter set of commands. The gomote command manages the necessary state for each group. This change adds the group subcommand which allows for creating and destroying groups, and also adds global group tracking across all the other subcommands. For now, this CL does essentially nothing other than add groups to the CLI. The groups are effectively non-functional as all subcommands fail when there's an active group specified. Follow-up CLs will incrementally enable group support across these subcommands. For golang/go#53956. Change-Id: I4d4d7c4f8aebfe8640fd68a9c6f8ec1211693efd Reviewed-on: https://go-review.googlesource.com/c/build/+/418154 Reviewed-by: Carlos Amedee Run-TryBot: Michael Knyszek Auto-Submit: Michael Knyszek TryBot-Result: Gopher Robot --- cmd/gomote/create.go | 8 ++ cmd/gomote/destroy.go | 8 ++ cmd/gomote/get.go | 8 ++ cmd/gomote/gomote.go | 41 +++++- cmd/gomote/group.go | 292 ++++++++++++++++++++++++++++++++++++++++++ cmd/gomote/ls.go | 8 ++ cmd/gomote/ping.go | 8 ++ cmd/gomote/push.go | 52 ++++---- cmd/gomote/put.go | 24 ++++ cmd/gomote/rdp.go | 4 + cmd/gomote/rm.go | 8 ++ cmd/gomote/run.go | 8 ++ cmd/gomote/ssh.go | 8 ++ 13 files changed, 451 insertions(+), 26 deletions(-) create mode 100644 cmd/gomote/group.go diff --git a/cmd/gomote/create.go b/cmd/gomote/create.go index 8db5f31db3..87ddb2ae0e 100644 --- a/cmd/gomote/create.go +++ b/cmd/gomote/create.go @@ -78,6 +78,10 @@ func builders() (bt []builderType) { } func legacyCreate(args []string) error { + if activeGroup != nil { + return fmt.Errorf("command does not support groups") + } + fs := flag.NewFlagSet("create", flag.ContinueOnError) fs.Usage = func() { @@ -133,6 +137,10 @@ func legacyCreate(args []string) error { } func create(args []string) error { + if activeGroup != nil { + return fmt.Errorf("command does not yet support groups") + } + fs := flag.NewFlagSet("create", flag.ContinueOnError) fs.Usage = func() { diff --git a/cmd/gomote/destroy.go b/cmd/gomote/destroy.go index 2337b3edf2..db485ff2bc 100644 --- a/cmd/gomote/destroy.go +++ b/cmd/gomote/destroy.go @@ -16,6 +16,10 @@ import ( ) func legacyDestroy(args []string) error { + if activeGroup != nil { + return fmt.Errorf("command does not support groups") + } + fs := flag.NewFlagSet("destroy", flag.ContinueOnError) fs.Usage = func() { fmt.Fprintln(os.Stderr, "destroy usage: gomote destroy ") @@ -53,6 +57,10 @@ func legacyDestroy(args []string) error { } func destroy(args []string) error { + if activeGroup != nil { + return fmt.Errorf("command does not yet support groups") + } + fs := flag.NewFlagSet("destroy", flag.ContinueOnError) fs.Usage = func() { fmt.Fprintln(os.Stderr, "destroy usage: gomote destroy ") diff --git a/cmd/gomote/get.go b/cmd/gomote/get.go index 98058c6d1b..e9d7aafb0f 100644 --- a/cmd/gomote/get.go +++ b/cmd/gomote/get.go @@ -18,6 +18,10 @@ import ( // legacyGetTar a .tar.gz func legacyGetTar(args []string) error { + if activeGroup != nil { + return fmt.Errorf("command does not support groups") + } + fs := flag.NewFlagSet("get", flag.ContinueOnError) fs.Usage = func() { fmt.Fprintln(os.Stderr, "gettar usage: gomote gettar [get-opts] ") @@ -48,6 +52,10 @@ func legacyGetTar(args []string) error { // getTar a .tar.gz func getTar(args []string) error { + if activeGroup != nil { + return fmt.Errorf("command does not yet support groups") + } + fs := flag.NewFlagSet("get", flag.ContinueOnError) fs.Usage = func() { fmt.Fprintln(os.Stderr, "gettar usage: gomote gettar [get-opts] ") diff --git a/cmd/gomote/gomote.go b/cmd/gomote/gomote.go index 193abe48ad..c50d1435b5 100644 --- a/cmd/gomote/gomote.go +++ b/cmd/gomote/gomote.go @@ -36,6 +36,7 @@ To list the subcommands, run "gomote" without arguments: rm delete files or directories rdp RDP (Remote Desktop Protocol) to a Windows buildlet run run a command on a buildlet + group manage gomote groups (v2 only) ssh ssh to a buildlet To list all the builder types available, run "create" with no arguments: @@ -71,6 +72,7 @@ The "gomote run" command has many of its own flags: -system run inside the system, and not inside the workdir; this is implicit if cmd starts with '/' + # Debugging buildlets directly Using "gomote create" contacts the build coordinator @@ -95,11 +97,13 @@ import ( "golang.org/x/build/buildlet" "golang.org/x/build/internal/gomote/protos" "golang.org/x/build/internal/iapclient" + "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) var ( - buildEnv *buildenv.Environment + buildEnv *buildenv.Environment + activeGroup *groupData ) type command struct { @@ -148,6 +152,7 @@ func registerCommands(version int) { registerCommand("create", "create a buildlet; with no args, list types of buildlets", create) registerCommand("destroy", "destroy a buildlet", destroy) registerCommand("gettar", "extract a tar.gz from a buildlet", getTar) + registerCommand("group", "manage groups of instances", group) registerCommand("ls", "list the contents of a directory on a buildlet", ls) registerCommand("list", "list active buildlets", list) registerCommand("ping", "test whether a buildlet is alive and reachable ", ping) @@ -174,6 +179,7 @@ func registerCommands(version int) { registerCommand("rdp", "RDP (Remote Desktop Protocol) to a Windows buildlet", rdp) registerCommand("rm", "delete files or directories", legacyRm) registerCommand("run", "run a command on a buildlet", legacyRun) + registerCommand("group", "manage gomote groups (v2 only)", group) registerCommand("ssh", "ssh to a buildlet", legacySSH) } @@ -182,6 +188,8 @@ var ( ) func main() { + // Set up and parse global flags. + groupName := flag.String("group", os.Getenv("GOMOTE_GROUP"), "name of the gomote group to apply commands to (default is $GOMOTE_GROUP)") buildlet.RegisterFlags() version := 2 if vs := os.Getenv("GOMOTE_VERSION"); vs != "" { @@ -196,19 +204,40 @@ func main() { registerCommands(version) flag.Usage = usage flag.Parse() - buildEnv = buildenv.FromFlags() args := flag.Args() if len(args) == 0 { usage() } + + // Set up globals. + buildEnv = buildenv.FromFlags() + if *groupName != "" { + var err error + activeGroup, err = loadGroup(*groupName) + if os.Getenv("GOMOTE_GROUP") != *groupName { + // Only fail hard since it was specified by the flag. + if err != nil { + fmt.Fprintf(os.Stderr, "Failure: %v\n", err) + usage() + } + } else { + // With a valid group from GOMOTE_GROUP, + // make it explicit to the user that we're going + // ahead with it. We don't need this with the flag + // because it's explicit. + if err == nil { + fmt.Fprintf(os.Stderr, "# Using group %q from GOMOTE_GROUP\n", *groupName) + } + } + } + cmdName := args[0] cmd, ok := commands[cmdName] if !ok { fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmdName) usage() } - err := cmd.run(args[1:]) - if err != nil { + if err := cmd.run(args[1:]); err != nil { logAndExitf("Error running %s: %v\n", cmdName, err) } } @@ -233,3 +262,7 @@ func logAndExitf(format string, v ...interface{}) { func statusFromError(err error) string { return status.Convert(err).Message() } + +func instanceDoesNotExist(err error) bool { + return status.Code(err) == codes.NotFound +} diff --git a/cmd/gomote/group.go b/cmd/gomote/group.go new file mode 100644 index 0000000000..6ec40fa8a9 --- /dev/null +++ b/cmd/gomote/group.go @@ -0,0 +1,292 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "sort" + + "golang.org/x/build/internal/gomote/protos" +) + +func group(args []string) error { + cm := map[string]struct { + run func([]string) error + desc string + }{ + "create": {createGroup, "create a new group"}, + "destroy": {destroyGroup, "destroy an existing group (does not destroy gomotes)"}, + "add": {addToGroup, "add an existing instance to a group"}, + "remove": {removeFromGroup, "remove an existing instance from a group"}, + "list": {listGroups, "list existing groups and their details"}, + } + if len(args) == 0 { + var cmds []string + for cmd := range cm { + cmds = append(cmds, cmd) + } + sort.Strings(cmds) + fmt.Fprintf(os.Stderr, "Usage of gomote group: gomote [global-flags] group [cmd-flags]\n\n") + fmt.Fprintf(os.Stderr, "Commands:\n\n") + for _, name := range cmds { + fmt.Fprintf(os.Stderr, " %-8s %s\n", name, cm[name].desc) + } + fmt.Fprintln(os.Stderr) + os.Exit(1) + } + subCmd := args[0] + sc, ok := cm[subCmd] + if !ok { + return fmt.Errorf("unknown sub-command %q\n", subCmd) + } + return sc.run(args[1:]) +} + +func createGroup(args []string) error { + usage := func() { + fmt.Fprintln(os.Stderr, "group create usage: gomote group create ") + os.Exit(1) + } + if len(args) != 1 { + usage() + } + name := args[0] + if _, err := loadGroup(name); err == nil { + return fmt.Errorf("group %q already exists", name) + } + if err := storeGroup(&groupData{ + Name: name, + }); err != nil { + return err + } + return nil +} + +func destroyGroup(args []string) error { + usage := func() { + fmt.Fprintln(os.Stderr, "group destroy usage: gomote group destroy ") + os.Exit(1) + } + if len(args) != 1 { + usage() + } + name := args[0] + _, err := loadGroup(name) + if errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("group %q does not exist", name) + } else if err != nil { + return fmt.Errorf("loading group %q: %w", name, err) + } + if err := deleteGroup(name); err != nil { + return err + } + if os.Getenv("GOMOTE_GROUP") == name { + fmt.Fprintln(os.Stderr, "You may wish to now clear GOMOTE_GROUP.") + } + return nil +} + +func addToGroup(args []string) error { + usage := func() { + fmt.Fprintln(os.Stderr, "group add usage: gomote group add [instances ...]") + os.Exit(1) + } + if len(args) == 0 { + usage() + } + if activeGroup == nil { + fmt.Fprintln(os.Stderr, "No active group found. Use -group or GOMOTE_GROUP.") + usage() + } + for _, inst := range args { + ctx := context.Background() + client := gomoteServerClient(ctx) + _, err := client.InstanceAlive(ctx, &protos.InstanceAliveRequest{ + GomoteId: inst, + }) + if err != nil { + return fmt.Errorf("instance %q: %s", inst, statusFromError(err)) + } + activeGroup.Instances = append(activeGroup.Instances, inst) + } + return storeGroup(activeGroup) +} + +func removeFromGroup(args []string) error { + usage := func() { + fmt.Fprintln(os.Stderr, "group add usage: gomote group add [instances ...]") + os.Exit(1) + } + if len(args) == 0 { + usage() + } + if activeGroup == nil { + fmt.Fprintln(os.Stderr, "No active group found. Use -group or GOMOTE_GROUP.") + usage() + } + newInstances := make([]string, 0, len(activeGroup.Instances)) + for _, inst := range activeGroup.Instances { + remove := false + for _, rmInst := range args { + if inst == rmInst { + remove = true + break + } + } + if remove { + continue + } + newInstances = append(newInstances, inst) + } + activeGroup.Instances = newInstances + return storeGroup(activeGroup) +} + +func listGroups(args []string) error { + usage := func() { + fmt.Fprintln(os.Stderr, "group list usage: gomote group list") + os.Exit(1) + } + if len(args) != 0 { + usage() + } + dir, err := groupDir() + if err != nil { + return fmt.Errorf("acquiring group directory: %w", err) + } + matches, _ := filepath.Glob(filepath.Join(dir, "*.json")) + // N.B. Glob ignores I/O errors, so no matches also means the directory + // does not exist. + emit := func(name, inst string) { + fmt.Printf("%s\t%s\t\n", name, inst) + } + emit("Name", "Instances") + for _, match := range matches { + g, err := loadGroupFromFile(match) + if err != nil { + return fmt.Errorf("reading group file for %q: %w", match, err) + } + sort.Strings(g.Instances) + emitted := false + for _, inst := range g.Instances { + if !emitted { + emit(g.Name, inst) + } else { + emit("", inst) + } + emitted = true + } + if !emitted { + emit(g.Name, "(none)") + } + } + if len(matches) == 0 { + fmt.Println("(none)") + } + return nil +} + +type groupData struct { + // User-provided name of the group. + Name string `json:"name"` + + // Instances is a list of instances in the group. + Instances []string `json:"instances"` +} + +func loadGroup(name string) (*groupData, error) { + fname, err := groupFilePath(name) + if err != nil { + return nil, fmt.Errorf("loading group %q: %w", name, err) + } + g, err := loadGroupFromFile(fname) + if err != nil { + return nil, fmt.Errorf("loading group %q: %w", name, err) + } + return g, nil +} + +func loadGroupFromFile(fname string) (*groupData, error) { + f, err := os.Open(fname) + if err != nil { + return nil, err + } + defer f.Close() + g := new(groupData) + if err := json.NewDecoder(f).Decode(g); err != nil { + return nil, err + } + // On every load, ping for liveness and prune. + // + // Otherwise, we can get into situations where we sometimes + // don't have an accurate record. + newInstances := make([]string, 0, len(g.Instances)) + for _, inst := range g.Instances { + ctx := context.Background() + client := gomoteServerClient(ctx) + _, err := client.InstanceAlive(ctx, &protos.InstanceAliveRequest{ + GomoteId: inst, + }) + if instanceDoesNotExist(err) { + continue + } else if err != nil { + return nil, err + } + newInstances = append(newInstances, inst) + } + g.Instances = newInstances + return g, storeGroup(g) +} + +func storeGroup(data *groupData) error { + fname, err := groupFilePath(data.Name) + if err != nil { + return fmt.Errorf("storing group %q: %w", data.Name, err) + } + if err := os.MkdirAll(filepath.Dir(fname), 0755); err != nil { + return fmt.Errorf("storing group %q: %w", data.Name, err) + } + f, err := os.Create(fname) + if err != nil { + return fmt.Errorf("storing group %q: %w", data.Name, err) + } + defer f.Close() + if err := json.NewEncoder(f).Encode(data); err != nil { + return fmt.Errorf("storing group %q: %w", data.Name, err) + } + return nil +} + +func deleteGroup(name string) error { + fname, err := groupFilePath(name) + if err != nil { + return fmt.Errorf("deleting group %q: %w", name, err) + } + if err := os.Remove(fname); err != nil { + return fmt.Errorf("deleting group %q: %w", name, err) + } + return nil +} + +func groupFilePath(name string) (string, error) { + dir, err := groupDir() + if err != nil { + return "", err + } + return filepath.Join(dir, fmt.Sprintf("%s.json", name)), nil +} + +func groupDir() (string, error) { + cfgDir, err := os.UserConfigDir() + if err != nil { + return "", err + } + return filepath.Join(cfgDir, "gomote", "groups"), nil +} diff --git a/cmd/gomote/ls.go b/cmd/gomote/ls.go index ae7831b21d..f1f6ca6feb 100644 --- a/cmd/gomote/ls.go +++ b/cmd/gomote/ls.go @@ -16,6 +16,10 @@ import ( ) func legacyLs(args []string) error { + if activeGroup != nil { + return fmt.Errorf("command does not support groups") + } + fs := flag.NewFlagSet("ls", flag.ContinueOnError) fs.Usage = func() { fmt.Fprintln(os.Stderr, "ls usage: gomote ls [-R] [dir]") @@ -52,6 +56,10 @@ func legacyLs(args []string) error { } func ls(args []string) error { + if activeGroup != nil { + return fmt.Errorf("command does not yet support groups") + } + fs := flag.NewFlagSet("ls", flag.ContinueOnError) fs.Usage = func() { fmt.Fprintln(os.Stderr, "ls usage: gomote ls [-R] [dir]") diff --git a/cmd/gomote/ping.go b/cmd/gomote/ping.go index 879df5c897..2ea417d20a 100644 --- a/cmd/gomote/ping.go +++ b/cmd/gomote/ping.go @@ -14,6 +14,10 @@ import ( ) func legacyPing(args []string) error { + if activeGroup != nil { + return fmt.Errorf("command does not support groups") + } + fs := flag.NewFlagSet("ping", flag.ContinueOnError) fs.Usage = func() { fmt.Fprintln(os.Stderr, "ping usage: gomote ping [--status] ") @@ -44,6 +48,10 @@ func legacyPing(args []string) error { } func ping(args []string) error { + if activeGroup != nil { + return fmt.Errorf("command does not yet support groups") + } + fs := flag.NewFlagSet("ping", flag.ContinueOnError) fs.Usage = func() { fmt.Fprintln(os.Stderr, "ping usage: gomote ping [--status] ") diff --git a/cmd/gomote/push.go b/cmd/gomote/push.go index f914d0f384..520b29ec24 100644 --- a/cmd/gomote/push.go +++ b/cmd/gomote/push.go @@ -27,6 +27,10 @@ import ( ) func legacyPush(args []string) error { + if activeGroup != nil { + return fmt.Errorf("command does not support groups") + } + fs := flag.NewFlagSet("push", flag.ContinueOnError) var dryRun bool fs.BoolVar(&dryRun, "dry-run", false, "print what would be done only") @@ -37,18 +41,10 @@ func legacyPush(args []string) error { } fs.Parse(args) - goroot := os.Getenv("GOROOT") - if goroot == "" { - slurp, err := exec.Command("go", "env", "GOROOT").Output() - if err != nil { - return fmt.Errorf("failed to get GOROOT from go env: %v", err) - } - goroot = strings.TrimSpace(string(slurp)) - if goroot == "" { - return errors.New("Failed to get $GOROOT from environment or go env") - } + goroot, err := getGOROOT() + if err != nil { + return err } - goroot = filepath.Clean(goroot) if fs.NArg() != 1 { fs.Usage() @@ -294,6 +290,10 @@ func legacyPush(args []string) error { } func push(args []string) error { + if activeGroup != nil { + return fmt.Errorf("command does not yet support groups") + } + fs := flag.NewFlagSet("push", flag.ContinueOnError) var dryRun bool fs.BoolVar(&dryRun, "dry-run", false, "print what would be done only") @@ -304,18 +304,10 @@ func push(args []string) error { } fs.Parse(args) - goroot := os.Getenv("GOROOT") - if goroot == "" { - slurp, err := exec.Command("go", "env", "GOROOT").Output() - if err != nil { - return fmt.Errorf("failed to get GOROOT from go env: %v", err) - } - goroot = strings.TrimSpace(string(slurp)) - if goroot == "" { - return errors.New("Failed to get $GOROOT from environment or go env") - } + goroot, err := getGOROOT() + if err != nil { + return err } - goroot = filepath.Clean(goroot) if fs.NArg() != 1 { fs.Usage() @@ -669,3 +661,19 @@ func fileSHA1(path string) (string, error) { } return fmt.Sprintf("%x", s1.Sum(nil)), nil } + +func getGOROOT() (string, error) { + goroot := os.Getenv("GOROOT") + if goroot == "" { + slurp, err := exec.Command("go", "env", "GOROOT").Output() + if err != nil { + return "", fmt.Errorf("failed to get GOROOT from go env: %v", err) + } + goroot = strings.TrimSpace(string(slurp)) + if goroot == "" { + return "", errors.New("Failed to get $GOROOT from environment or go env") + } + } + goroot = filepath.Clean(goroot) + return goroot, nil +} diff --git a/cmd/gomote/put.go b/cmd/gomote/put.go index dd272720c1..b6d03cf08c 100644 --- a/cmd/gomote/put.go +++ b/cmd/gomote/put.go @@ -25,6 +25,10 @@ import ( // legacyPutTar a .tar.gz func legacyPutTar(args []string) error { + if activeGroup != nil { + return fmt.Errorf("command does not support groups") + } + fs := flag.NewFlagSet("put", flag.ContinueOnError) fs.Usage = func() { fmt.Fprintln(os.Stderr, "puttar usage: gomote puttar [put-opts] [tar.gz file or '-' for stdin]") @@ -95,6 +99,10 @@ func legacyPutTar(args []string) error { // putTar a .tar.gz func putTar(args []string) error { + if activeGroup != nil { + return fmt.Errorf("command does not yet support groups") + } + fs := flag.NewFlagSet("put", flag.ContinueOnError) fs.Usage = func() { fmt.Fprintln(os.Stderr, "puttar usage: gomote puttar [put-opts] [tar.gz file or '-' for stdin]") @@ -195,6 +203,10 @@ func putTar(args []string) error { // put go1.4 in the workdir func put14(args []string) error { + if activeGroup != nil { + return fmt.Errorf("command does not support groups") + } + fs := flag.NewFlagSet("put14", flag.ContinueOnError) fs.Usage = func() { fmt.Fprintln(os.Stderr, "put14 usage: gomote put14 ") @@ -221,6 +233,10 @@ func put14(args []string) error { // putBootstrap places the bootstrap version of go in the workdir func putBootstrap(args []string) error { + if activeGroup != nil { + return fmt.Errorf("command does not yet support groups") + } + fs := flag.NewFlagSet("putbootstrap", flag.ContinueOnError) fs.Usage = func() { fmt.Fprintln(os.Stderr, "putbootstrap usage: gomote putbootstrap ") @@ -248,6 +264,10 @@ func putBootstrap(args []string) error { // legacyPut single file func legacyPut(args []string) error { + if activeGroup != nil { + return fmt.Errorf("command does not support groups") + } + fs := flag.NewFlagSet("put", flag.ContinueOnError) fs.Usage = func() { fmt.Fprintln(os.Stderr, "put usage: gomote put [put-opts] [destination]") @@ -310,6 +330,10 @@ func legacyPut(args []string) error { // put single file func put(args []string) error { + if activeGroup != nil { + return fmt.Errorf("command does not yet support groups") + } + fs := flag.NewFlagSet("put", flag.ContinueOnError) fs.Usage = func() { fmt.Fprintln(os.Stderr, "put usage: gomote put [put-opts] [destination]") diff --git a/cmd/gomote/rdp.go b/cmd/gomote/rdp.go index 84ef85b434..170660a681 100644 --- a/cmd/gomote/rdp.go +++ b/cmd/gomote/rdp.go @@ -21,6 +21,10 @@ import ( const rdpPort = 3389 func rdp(args []string) error { + if activeGroup != nil { + return fmt.Errorf("command does not support groups") + } + fs := flag.NewFlagSet("rdp", flag.ContinueOnError) fs.Usage = func() { fmt.Fprintln(os.Stderr, "rdp usage: gomote rdp [--listen=...] ") diff --git a/cmd/gomote/rm.go b/cmd/gomote/rm.go index 9b427dff6d..545c89ede2 100644 --- a/cmd/gomote/rm.go +++ b/cmd/gomote/rm.go @@ -14,6 +14,10 @@ import ( ) func legacyRm(args []string) error { + if activeGroup != nil { + return fmt.Errorf("command does not support groups") + } + fs := flag.NewFlagSet("rm", flag.ContinueOnError) fs.Usage = func() { fmt.Fprintln(os.Stderr, "rm usage: gomote rm +") @@ -37,6 +41,10 @@ func legacyRm(args []string) error { } func rm(args []string) error { + if activeGroup != nil { + return fmt.Errorf("command does not yet support groups") + } + fs := flag.NewFlagSet("rm", flag.ContinueOnError) fs.Usage = func() { fmt.Fprintln(os.Stderr, "rm usage: gomote rm +") diff --git a/cmd/gomote/run.go b/cmd/gomote/run.go index e3dce9f7e6..d84e06e321 100644 --- a/cmd/gomote/run.go +++ b/cmd/gomote/run.go @@ -21,6 +21,10 @@ import ( ) func legacyRun(args []string) error { + if activeGroup != nil { + return fmt.Errorf("command does not support groups") + } + fs := flag.NewFlagSet("run", flag.ContinueOnError) fs.Usage = func() { fmt.Fprintln(os.Stderr, "run usage: gomote run [run-opts] [args...]") @@ -108,6 +112,10 @@ func (ss *stringSlice) Set(v string) error { } func run(args []string) error { + if activeGroup != nil { + return fmt.Errorf("command does not yet support groups") + } + fs := flag.NewFlagSet("run", flag.ContinueOnError) fs.Usage = func() { fmt.Fprintln(os.Stderr, "run usage: gomote run [run-opts] [args...]") diff --git a/cmd/gomote/ssh.go b/cmd/gomote/ssh.go index 21f9f113d9..f71586c3b7 100644 --- a/cmd/gomote/ssh.go +++ b/cmd/gomote/ssh.go @@ -22,6 +22,10 @@ import ( ) func legacySSH(args []string) error { + if activeGroup != nil { + return fmt.Errorf("command does not support groups") + } + fs := flag.NewFlagSet("ssh", flag.ContinueOnError) fs.Usage = func() { fmt.Fprintln(os.Stderr, "ssh usage: gomote ssh ") @@ -60,6 +64,10 @@ func legacySSH(args []string) error { } func ssh(args []string) error { + if activeGroup != nil { + return fmt.Errorf("command does not support groups") + } + fs := flag.NewFlagSet("ssh", flag.ContinueOnError) fs.Usage = func() { fmt.Fprintln(os.Stderr, "ssh usage: gomote ssh ")