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"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/nomad/api/contexts"
|
|
||||||
flaghelper "github.com/hashicorp/nomad/helper/flags"
|
flaghelper "github.com/hashicorp/nomad/helper/flags"
|
||||||
"github.com/posener/complete"
|
"github.com/posener/complete"
|
||||||
)
|
)
|
||||||
|
@ -75,11 +74,20 @@ func (c *JobDispatchCommand) AutocompleteArgs() complete.Predictor {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, _, err := client.Search().PrefixSearch(a.Last, contexts.Jobs, nil)
|
resp, _, err := client.Jobs().PrefixList(a.Last)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []string{}
|
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/hashicorp/nomad/nomad/mock"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/posener/complete"
|
"github.com/posener/complete"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestJobDispatchCommand_Implements(t *testing.T) {
|
func TestJobDispatchCommand_Implements(t *testing.T) {
|
||||||
|
@ -50,7 +50,6 @@ func TestJobDispatchCommand_Fails(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJobDispatchCommand_AutocompleteArgs(t *testing.T) {
|
func TestJobDispatchCommand_AutocompleteArgs(t *testing.T) {
|
||||||
assert := assert.New(t)
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
srv, _, url := testServer(t, true, nil)
|
srv, _, url := testServer(t, true, nil)
|
||||||
|
@ -62,13 +61,27 @@ func TestJobDispatchCommand_AutocompleteArgs(t *testing.T) {
|
||||||
// Create a fake job
|
// Create a fake job
|
||||||
state := srv.Agent.Server().State()
|
state := srv.Agent.Server().State()
|
||||||
j := mock.Job()
|
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]
|
prefix := j.ID[:len(j.ID)-5]
|
||||||
args := complete.Args{Last: prefix}
|
args := complete.Args{Last: prefix}
|
||||||
predictor := cmd.AutocompleteArgs()
|
predictor := cmd.AutocompleteArgs()
|
||||||
|
|
||||||
|
// No parameterized jobs, should be 0 results
|
||||||
res := predictor.Predict(args)
|
res := predictor.Predict(args)
|
||||||
assert.Equal(1, len(res))
|
require.Equal(t, 0, len(res))
|
||||||
assert.Equal(j.ID, res[0])
|
|
||||||
|
// 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/go-cleanhttp"
|
||||||
"github.com/hashicorp/nomad/api"
|
"github.com/hashicorp/nomad/api"
|
||||||
|
"github.com/hashicorp/nomad/api/contexts"
|
||||||
"github.com/hashicorp/nomad/helper"
|
"github.com/hashicorp/nomad/helper"
|
||||||
"github.com/hashicorp/nomad/nomad/structs"
|
"github.com/hashicorp/nomad/nomad/structs"
|
||||||
"github.com/posener/complete"
|
"github.com/posener/complete"
|
||||||
|
@ -179,12 +180,12 @@ func (c *OperatorDebugCommand) AutocompleteFlags() complete.Flags {
|
||||||
complete.Flags{
|
complete.Flags{
|
||||||
"-duration": complete.PredictAnything,
|
"-duration": complete.PredictAnything,
|
||||||
"-interval": complete.PredictAnything,
|
"-interval": complete.PredictAnything,
|
||||||
"-log-level": complete.PredictAnything,
|
"-log-level": complete.PredictSet("TRACE", "DEBUG", "INFO", "WARN", "ERROR"),
|
||||||
"-max-nodes": complete.PredictAnything,
|
"-max-nodes": complete.PredictAnything,
|
||||||
"-node-class": complete.PredictAnything,
|
"-node-class": NodeClassPredictor(c.Client),
|
||||||
"-node-id": complete.PredictAnything,
|
"-node-id": NodePredictor(c.Client),
|
||||||
"-server-id": complete.PredictAnything,
|
"-server-id": ServerPredictor(c.Client),
|
||||||
"-output": complete.PredictAnything,
|
"-output": complete.PredictDirs("*"),
|
||||||
"-pprof-duration": complete.PredictAnything,
|
"-pprof-duration": complete.PredictAnything,
|
||||||
"-consul-token": complete.PredictAnything,
|
"-consul-token": complete.PredictAnything,
|
||||||
"-vault-token": complete.PredictAnything,
|
"-vault-token": complete.PredictAnything,
|
||||||
|
@ -195,6 +196,79 @@ func (c *OperatorDebugCommand) AutocompleteArgs() complete.Predictor {
|
||||||
return complete.PredictNothing
|
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) Name() string { return "debug" }
|
||||||
|
|
||||||
func (c *OperatorDebugCommand) Run(args []string) int {
|
func (c *OperatorDebugCommand) Run(args []string) int {
|
||||||
|
|
Loading…
Reference in New Issue