mirror of
https://github.com/kemko/nomad.git
synced 2026-01-07 19:05:42 +03:00
adds any resource autocomplete
defaults to listing jobs if no id is provided
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
112
command/status.go
Normal 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
214
command/status_test.go
Normal 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()
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user