Skip to content

Commit

Permalink
cli: Improved autocomplete support for job dispatch and operator debug (
Browse files Browse the repository at this point in the history
#11270)

* Add autocomplete to nomad job dispatch
* Add autocomplete to nomad operator debug
* Update incorrect comment
* Update test to verify autocomplete
* Add changelog
* Apply lint suggestions
* Create dynamic slices instead of specific length
* Align style across predictors
  • Loading branch information
davemay99 authored Oct 13, 2021
1 parent 8332476 commit 6852f21
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 13 deletions.
3 changes: 3 additions & 0 deletions .changelog/11270.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
cli: Improved autocomplete support for job dispatch and operator debug
```
14 changes: 11 additions & 3 deletions command/job_dispatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"os"
"strings"

"github.com/hashicorp/nomad/api/contexts"
flaghelper "github.com/hashicorp/nomad/helper/flags"
"github.com/posener/complete"
)
Expand Down Expand Up @@ -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

})
}

Expand Down
23 changes: 18 additions & 5 deletions command/job_dispatch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
Expand All @@ -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])
}
84 changes: 79 additions & 5 deletions command/operator_debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand All @@ -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 {
Expand Down

0 comments on commit 6852f21

Please sign in to comment.