cli: Improved autocomplete support for job dispatch and operator debug (#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
This commit is contained in:
parent
2af0422bca
commit
305e8e98bf
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
cli: Improved autocomplete support for job dispatch and operator debug
|
||||
```
|
|
@ -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
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue