Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli: Improved autocomplete support for job dispatch and operator debug #11270

Merged
merged 8 commits into from
Oct 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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