adds any resource autocomplete

defaults to listing jobs if no id is provided
This commit is contained in:
Chelsea Holland Komlo
2017-08-14 17:35:13 +00:00
parent d93daec7a1
commit 9e3be518e3
6 changed files with 351 additions and 2 deletions

View File

@@ -145,8 +145,6 @@ func (s *HTTPServer) registerHandlers(enableDebug bool) {
s.mux.HandleFunc("/v1/evaluations", s.wrap(s.EvalsRequest))
s.mux.HandleFunc("/v1/evaluation/", s.wrap(s.EvalSpecificRequest))
s.mux.HandleFunc("/v1/search", s.wrap(s.SearchRequest))
s.mux.HandleFunc("/v1/deployments", s.wrap(s.DeploymentsRequest))
s.mux.HandleFunc("/v1/deployment/", s.wrap(s.DeploymentSpecificRequest))
@@ -169,6 +167,8 @@ func (s *HTTPServer) registerHandlers(enableDebug bool) {
s.mux.HandleFunc("/v1/status/leader", s.wrap(s.StatusLeaderRequest))
s.mux.HandleFunc("/v1/status/peers", s.wrap(s.StatusPeersRequest))
s.mux.HandleFunc("/v1/search", s.wrap(s.SearchRequest))
s.mux.HandleFunc("/v1/operator/", s.wrap(s.OperatorRequest))
s.mux.HandleFunc("/v1/system/gc", s.wrap(s.GarbageCollectRequest))

View File

@@ -138,6 +138,7 @@ func (c *JobStatusCommand) Run(args []string) int {
// Try querying the job
jobID := args[0]
jobs, _, err := client.Jobs().PrefixList(jobID)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error querying job: %s", err))

View File

@@ -56,6 +56,18 @@ type Meta struct {
insecure bool
}
func (m *Meta) Copy(dest *Meta) {
dest.Ui = m.Ui
dest.flagAddress = m.flagAddress
dest.noColor = m.noColor
dest.region = m.region
dest.caCert = m.caCert
dest.caPath = m.caPath
dest.clientCert = m.clientCert
dest.clientKey = m.clientKey
dest.insecure = m.insecure
}
// FlagSet returns a FlagSet with the common flags that every
// command implements. The exact behavior of FlagSet can be configured
// using the flags as the second parameter, for example to disable
@@ -152,6 +164,11 @@ func (m *Meta) Colorize() *colorstring.Colorize {
}
}
var (
// flagOptions is a list of all available flags that can be used via the cli
flagOptions = []string{"address", "region", "no-color", "ca-cert", "ca-path", "client-cert", "client-key", "tls-skip-verify"}
)
// generalOptionsUsage returns the help string for the global options.
func generalOptionsUsage() string {
helpText := `

112
command/status.go Normal file
View File

@@ -0,0 +1,112 @@
package command
import (
"fmt"
"strings"
"github.com/hashicorp/nomad/api/contexts"
"github.com/mitchellh/cli"
"github.com/posener/complete"
)
type StatusCommand struct {
Meta
}
func (c *StatusCommand) Run(args []string) int {
// Get the HTTP client
client, err := c.Meta.Client()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
return 1
}
id := ""
// Assume the last argument will be the id to search
if len(args) > 0 {
id = args[len(args)-1]
}
// Check that the last argument provided is not setting a flag
for _, flag := range flagOptions {
arg := strings.Replace(id, "-", "", 1) // strip leading '-' from flag
if strings.HasPrefix(arg, flag) {
cmd := &JobStatusCommand{Meta: c.Meta}
return cmd.Run(args)
}
}
// Try querying for the context associated with the id
res, err := client.Search().PrefixSearch(id, contexts.All)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error querying search with id: %s", err))
return 1
}
if res.Matches == nil {
c.Ui.Error(fmt.Sprintf("No matches returned for query %s", err))
return 1
}
var match contexts.Context
matchCount := 0
for ctx, vers := range res.Matches {
if len(vers) == 1 {
match = ctx
matchCount++
}
// Only a single match should return, as this is a match against a full id
if matchCount > 1 || len(vers) > 1 {
c.Ui.Error(fmt.Sprintf("Multiple matches found for id %s", err))
return 1
}
}
var cmd cli.Command
switch match {
case contexts.Evals:
cmd = &EvalStatusCommand{Meta: c.Meta}
case contexts.Nodes:
cmd = &NodeStatusCommand{Meta: c.Meta}
case contexts.Allocs:
cmd = &AllocStatusCommand{Meta: c.Meta}
case contexts.Jobs:
cmd = &JobStatusCommand{Meta: c.Meta}
default:
c.Ui.Error(fmt.Sprintf("Expected a specific context for id : %s", id))
return 1
}
return cmd.Run(args)
}
func (s *StatusCommand) Help() string {
helpText := `
Usage: nomad status <identifier>
Display information about an existing resource. Job names, node ids,
allocation ids, and evaluation ids are all valid identifiers.
`
return helpText
}
func (s *StatusCommand) AutocompleteFlags() complete.Flags {
return nil
}
func (s *StatusCommand) AutocompleteArgs() complete.Predictor {
client, _ := s.Meta.Client()
return complete.PredictFunc(func(a complete.Args) []string {
resp, err := client.Search().PrefixSearch(a.Last, contexts.All)
if err != nil {
return []string{}
}
return resp.Matches[contexts.All]
})
}
func (c *StatusCommand) Synopsis() string {
return "Display status information and metadata"
}

214
command/status_test.go Normal file
View File

@@ -0,0 +1,214 @@
package command
import (
"fmt"
"strings"
"testing"
"github.com/hashicorp/nomad/command/agent"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/hashicorp/nomad/testutil"
"github.com/mitchellh/cli"
)
func TestStatusCommand_Run_JobStatus(t *testing.T) {
t.Parallel()
srv, client, url := testServer(t, true, nil)
defer srv.Shutdown()
ui := new(cli.MockUi)
cmd := &StatusCommand{Meta: Meta{Ui: ui, flagAddress: url}}
// Register a job
job1 := testJob("job1_sfx")
resp, _, err := client.Jobs().Register(job1, nil)
if err != nil {
t.Fatalf("err: %s", err)
}
if code := waitForSuccess(ui, client, fullId, t, resp.EvalID); code != 0 {
t.Fatalf("status code non zero saw %d", code)
}
// Query to check the job status
if code := cmd.Run([]string{"-address=" + url, "job1_sfx"}); code != 0 {
t.Fatalf("expected exit 0, got: %d", code)
}
out := ui.OutputWriter.String()
if !strings.Contains(out, "job1_sfx") {
t.Fatalf("expected job1_sfx, got: %s", out)
}
ui.OutputWriter.Reset()
}
func TestStatusCommand_Run_EvalStatus(t *testing.T) {
t.Parallel()
srv, client, url := testServer(t, true, nil)
defer srv.Shutdown()
ui := new(cli.MockUi)
cmd := &StatusCommand{Meta: Meta{Ui: ui, flagAddress: url}}
jobID := "job1_sfx"
job1 := testJob(jobID)
resp, _, err := client.Jobs().Register(job1, nil)
if err != nil {
t.Fatalf("err: %s", err)
}
if code := waitForSuccess(ui, client, fullId, t, resp.EvalID); code != 0 {
t.Fatalf("status code non zero saw %d", code)
}
// get an eval id
evalID := ""
if evals, _, err := client.Jobs().Evaluations(jobID, nil); err == nil {
if len(evals) > 0 {
evalID = evals[0].ID
}
}
if evalID == "" {
t.Fatal("unable to find an evaluation")
}
// Query to check the eval status
if code := cmd.Run([]string{"-address=" + url, evalID}); code != 0 {
t.Fatalf("expected exit 0, got: %d", code)
}
out := ui.OutputWriter.String()
if !strings.Contains(out, evalID) {
t.Fatalf("expected eval id, got: %s", out)
}
ui.OutputWriter.Reset()
}
func TestStatusCommand_Run_NodeStatus(t *testing.T) {
t.Parallel()
// Start in dev mode so we get a node registration
srv, client, url := testServer(t, true, func(c *agent.Config) {
c.NodeName = "mynode"
})
defer srv.Shutdown()
ui := new(cli.MockUi)
cmd := &StatusCommand{Meta: Meta{Ui: ui, flagAddress: url}}
// Wait for a node to appear
var nodeID string
testutil.WaitForResult(func() (bool, error) {
nodes, _, err := client.Nodes().List(nil)
if err != nil {
return false, err
}
if len(nodes) == 0 {
return false, fmt.Errorf("missing node")
}
nodeID = nodes[0].ID
return true, nil
}, func(err error) {
t.Fatalf("err: %s", err)
})
// Query to check the node status
if code := cmd.Run([]string{"-address=" + url, nodeID}); code != 0 {
t.Fatalf("expected exit 0, got: %d", code)
}
out := ui.OutputWriter.String()
if !strings.Contains(out, "mynode") {
t.Fatalf("expected node id (mynode), got: %s", out)
}
ui.OutputWriter.Reset()
}
func TestStatusCommand_Run_AllocStatus(t *testing.T) {
t.Parallel()
srv, client, url := testServer(t, true, nil)
defer srv.Shutdown()
// Wait for a node to be ready
testutil.WaitForResult(func() (bool, error) {
nodes, _, err := client.Nodes().List(nil)
if err != nil {
return false, err
}
for _, node := range nodes {
if node.Status == structs.NodeStatusReady {
return true, nil
}
}
return false, fmt.Errorf("no ready nodes")
}, func(err error) {
t.Fatalf("err: %v", err)
})
ui := new(cli.MockUi)
cmd := &StatusCommand{Meta: Meta{Ui: ui, flagAddress: url}}
jobID := "job1_sfx"
job1 := testJob(jobID)
resp, _, err := client.Jobs().Register(job1, nil)
if err != nil {
t.Fatalf("err: %s", err)
}
if code := waitForSuccess(ui, client, fullId, t, resp.EvalID); code != 0 {
t.Fatalf("status code non zero saw %d", code)
}
// get an alloc id
allocId1 := ""
if allocs, _, err := client.Jobs().Allocations(jobID, false, nil); err == nil {
if len(allocs) > 0 {
allocId1 = allocs[0].ID
}
}
if allocId1 == "" {
t.Fatal("unable to find an allocation")
}
if code := cmd.Run([]string{"-address=" + url, allocId1}); code != 0 {
t.Fatalf("expected exit 0, got: %d", code)
}
out := ui.OutputWriter.String()
if !strings.Contains(out, allocId1) {
t.Fatal("expected to find alloc id in output")
}
ui.OutputWriter.Reset()
}
func TestStatusCommand_Run_NoPrefix(t *testing.T) {
t.Parallel()
srv, client, url := testServer(t, true, nil)
defer srv.Shutdown()
ui := new(cli.MockUi)
cmd := &StatusCommand{Meta: Meta{Ui: ui, flagAddress: url}}
// Register a job
job1 := testJob("job1_sfx")
resp, _, err := client.Jobs().Register(job1, nil)
if err != nil {
t.Fatalf("err: %s", err)
}
if code := waitForSuccess(ui, client, fullId, t, resp.EvalID); code != 0 {
t.Fatalf("status code non zero saw %d", code)
}
// Query to check status
if code := cmd.Run([]string{"-address=" + url}); code != 0 {
t.Fatalf("expected exit 0, got: %d", code)
}
out := ui.OutputWriter.String()
if !strings.Contains(out, "job1_sfx") {
t.Fatalf("expected job1_sfx, got: %s", out)
}
ui.OutputWriter.Reset()
}

View File

@@ -224,6 +224,11 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory {
Meta: meta,
}, nil
},
"status": func() (cli.Command, error) {
return &command.StatusCommand{
Meta: meta,
}, nil
},
"stop": func() (cli.Command, error) {
return &command.StopCommand{
Meta: meta,