diff --git a/.changelog/11270.txt b/.changelog/11270.txt new file mode 100644 index 00000000000..964ac391b16 --- /dev/null +++ b/.changelog/11270.txt @@ -0,0 +1,3 @@ +```release-note:improvement +cli: Improved autocomplete support for job dispatch and operator debug +``` diff --git a/command/job_dispatch.go b/command/job_dispatch.go index 022c24a5165..68e9863ab38 100644 --- a/command/job_dispatch.go +++ b/command/job_dispatch.go @@ -6,7 +6,6 @@ import ( "os" "strings" - "github.com/hashicorp/nomad/api/contexts" flaghelper "github.com/hashicorp/nomad/helper/flags" "github.com/posener/complete" ) @@ -75,11 +74,20 @@ func (c *JobDispatchCommand) AutocompleteArgs() complete.Predictor { return nil } - resp, _, err := client.Search().PrefixSearch(a.Last, contexts.Jobs, nil) + resp, _, err := client.Jobs().PrefixList(a.Last) if err != nil { return []string{} } - return resp.Matches[contexts.Jobs] + + // filter by parameterized jobs + matches := make([]string, 0, len(resp)) + for _, job := range resp { + if job.ParameterizedJob { + matches = append(matches, job.ID) + } + } + return matches + }) } diff --git a/command/job_dispatch_test.go b/command/job_dispatch_test.go index c1a4e406031..37e4a39264b 100644 --- a/command/job_dispatch_test.go +++ b/command/job_dispatch_test.go @@ -9,7 +9,7 @@ import ( "github.com/hashicorp/nomad/nomad/mock" "github.com/mitchellh/cli" "github.com/posener/complete" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestJobDispatchCommand_Implements(t *testing.T) { @@ -50,7 +50,6 @@ func TestJobDispatchCommand_Fails(t *testing.T) { } func TestJobDispatchCommand_AutocompleteArgs(t *testing.T) { - assert := assert.New(t) t.Parallel() srv, _, url := testServer(t, true, nil) @@ -62,13 +61,27 @@ func TestJobDispatchCommand_AutocompleteArgs(t *testing.T) { // Create a fake job state := srv.Agent.Server().State() j := mock.Job() - assert.Nil(state.UpsertJob(structs.MsgTypeTestSetup, 1000, j)) + require.Nil(t, state.UpsertJob(structs.MsgTypeTestSetup, 1000, j)) prefix := j.ID[:len(j.ID)-5] args := complete.Args{Last: prefix} predictor := cmd.AutocompleteArgs() + // No parameterized jobs, should be 0 results res := predictor.Predict(args) - assert.Equal(1, len(res)) - assert.Equal(j.ID, res[0]) + require.Equal(t, 0, len(res)) + + // Create a fake parameterized job + j1 := mock.Job() + j1.ParameterizedJob = &structs.ParameterizedJobConfig{} + require.Nil(t, state.UpsertJob(structs.MsgTypeTestSetup, 2000, j1)) + + prefix = j1.ID[:len(j1.ID)-5] + args = complete.Args{Last: prefix} + predictor = cmd.AutocompleteArgs() + + // Should return 1 parameterized job + res = predictor.Predict(args) + require.Equal(t, 1, len(res)) + require.Equal(t, j1.ID, res[0]) } diff --git a/command/operator_debug.go b/command/operator_debug.go index 97385bf3ad0..1646f062d8f 100644 --- a/command/operator_debug.go +++ b/command/operator_debug.go @@ -21,6 +21,7 @@ import ( "github.com/hashicorp/go-cleanhttp" "github.com/hashicorp/nomad/api" + "github.com/hashicorp/nomad/api/contexts" "github.com/hashicorp/nomad/helper" "github.com/hashicorp/nomad/nomad/structs" "github.com/posener/complete" @@ -179,12 +180,12 @@ func (c *OperatorDebugCommand) AutocompleteFlags() complete.Flags { complete.Flags{ "-duration": complete.PredictAnything, "-interval": complete.PredictAnything, - "-log-level": complete.PredictAnything, + "-log-level": complete.PredictSet("TRACE", "DEBUG", "INFO", "WARN", "ERROR"), "-max-nodes": complete.PredictAnything, - "-node-class": complete.PredictAnything, - "-node-id": complete.PredictAnything, - "-server-id": complete.PredictAnything, - "-output": complete.PredictAnything, + "-node-class": NodeClassPredictor(c.Client), + "-node-id": NodePredictor(c.Client), + "-server-id": ServerPredictor(c.Client), + "-output": complete.PredictDirs("*"), "-pprof-duration": complete.PredictAnything, "-consul-token": complete.PredictAnything, "-vault-token": complete.PredictAnything, @@ -195,6 +196,79 @@ func (c *OperatorDebugCommand) AutocompleteArgs() complete.Predictor { return complete.PredictNothing } +// NodePredictor returns a client node predictor +func NodePredictor(factory ApiClientFactory) complete.Predictor { + return complete.PredictFunc(func(a complete.Args) []string { + client, err := factory() + if err != nil { + return nil + } + + resp, _, err := client.Search().PrefixSearch(a.Last, contexts.Nodes, nil) + if err != nil { + return []string{} + } + return resp.Matches[contexts.Nodes] + }) +} + +// NodeClassPredictor returns a client node class predictor +// TODO: Consider API options for node class filtering +func NodeClassPredictor(factory ApiClientFactory) complete.Predictor { + return complete.PredictFunc(func(a complete.Args) []string { + client, err := factory() + if err != nil { + return nil + } + + nodes, _, err := client.Nodes().List(nil) // TODO: should be *api.QueryOptions that matches region + if err != nil { + return []string{} + } + + // Build map of unique node classes across all nodes + classes := make(map[string]bool) + for _, node := range nodes { + classes[node.NodeClass] = true + } + + // Iterate over node classes looking for match + filtered := []string{} + for class := range classes { + if strings.HasPrefix(class, a.Last) { + filtered = append(filtered, class) + } + } + + return filtered + }) +} + +// ServerPredictor returns a server member predictor +// TODO: Consider API options for server member filtering +func ServerPredictor(factory ApiClientFactory) complete.Predictor { + return complete.PredictFunc(func(a complete.Args) []string { + client, err := factory() + if err != nil { + return nil + } + members, err := client.Agent().Members() + if err != nil { + return []string{} + } + + // Iterate over server members looking for match + filtered := []string{} + for _, member := range members.Members { + if strings.HasPrefix(member.Name, a.Last) { + filtered = append(filtered, member.Name) + } + } + + return filtered + }) +} + func (c *OperatorDebugCommand) Name() string { return "debug" } func (c *OperatorDebugCommand) Run(args []string) int {