Skip to content

Commit

Permalink
Merge pull request #952 from hashicorp/f-nomad-inspect
Browse files Browse the repository at this point in the history
cli: Add nomad inspect command
  • Loading branch information
dadgar committed Mar 21, 2016
2 parents 7e1e8a4 + eec0e3e commit b0d10b1
Show file tree
Hide file tree
Showing 7 changed files with 239 additions and 29 deletions.
30 changes: 30 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
Expand Down Expand Up @@ -34,6 +35,9 @@ type QueryOptions struct {

// If set, used as prefix for resource list searches
Prefix string

// If set, pretty print the response json.
Pretty bool
}

// WriteOptions are used to parameterize a write
Expand Down Expand Up @@ -156,6 +160,9 @@ func (r *request) setQueryOptions(q *QueryOptions) {
if q.Prefix != "" {
r.params.Set("prefix", q.Prefix)
}
if q.Pretty {
r.params.Set("pretty", "true")
}
}

// durToMsec converts a duration to a millisecond specified string
Expand Down Expand Up @@ -265,6 +272,29 @@ func (c *Client) query(endpoint string, out interface{}, q *QueryOptions) (*Quer
return qm, nil
}

// rawQuery is used to do a GET request against an endpoint and return the raw
// string result.
func (c *Client) rawQuery(endpoint string, q *QueryOptions) (string, *QueryMeta, error) {
r := c.newRequest("GET", endpoint)
r.setQueryOptions(q)
rtt, resp, err := requireOK(c.doRequest(r))
if err != nil {
return "", nil, err
}
defer resp.Body.Close()

qm := &QueryMeta{}
parseQueryMeta(resp, qm)
qm.RequestTime = rtt

raw, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", nil, err
}

return string(raw), qm, nil
}

// write is used to do a PUT request against an endpoint
// and serialize/deserialized using the standard Nomad conventions.
func (c *Client) write(endpoint string, in, out interface{}, q *WriteOptions) (*WriteMeta, error) {
Expand Down
14 changes: 14 additions & 0 deletions api/jobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,20 @@ func (j *Jobs) Info(jobID string, q *QueryOptions) (*Job, *QueryMeta, error) {
return &resp, qm, nil
}

// RawJob is used to retrieve information about a particular
// job given its unique ID and return the raw json.
func (j *Jobs) RawJob(jobID string, q *QueryOptions) (string, *QueryMeta, error) {
if q == nil {
q = &QueryOptions{}
}
q.Pretty = true
raw, qm, err := j.client.rawQuery("/v1/job/"+jobID, q)
if err != nil {
return "", nil, err
}
return raw, qm, nil
}

// Allocations is used to return the allocs for a given job ID.
func (j *Jobs) Allocations(jobID string, q *QueryOptions) ([]*AllocationListStub, *QueryMeta, error) {
var resp []*AllocationListStub
Expand Down
33 changes: 33 additions & 0 deletions api/jobs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,39 @@ func TestJobs_Info(t *testing.T) {
}
}

func TestJobs_RawJob(t *testing.T) {
c, s := makeClient(t, nil, nil)
defer s.Stop()
jobs := c.Jobs()

// Trying to retrieve a job by ID before it exists
// returns an error
_, _, err := jobs.RawJob("job1", nil)
if err == nil || !strings.Contains(err.Error(), "not found") {
t.Fatalf("expected not found error, got: %#v", err)
}

// Register the job
job := testJob()
_, wm, err := jobs.Register(job, nil)
if err != nil {
t.Fatalf("err: %s", err)
}
assertWriteMeta(t, wm)

// Query the job again and ensure it exists
result, qm, err := jobs.RawJob("job1", nil)
if err != nil {
t.Fatalf("err: %s", err)
}
assertQueryMeta(t, qm)

// Check that the result is what we expect
if result == "" || !strings.Contains(result, job.ID) {
t.Fatalf("expect: %#v, got: %#v", job, result)
}
}

func TestJobs_PrefixList(t *testing.T) {
c, s := makeClient(t, nil, nil)
defer s.Stop()
Expand Down
86 changes: 86 additions & 0 deletions command/inspect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package command

import (
"fmt"
"strings"
)

type InspectCommand struct {
Meta
}

func (c *InspectCommand) Help() string {
helpText := `
Usage: nomad inspect [options] <job>
Inspect is used to see the specification of a submitted job.
General Options:
` + generalOptionsUsage()

return strings.TrimSpace(helpText)
}

func (c *InspectCommand) Synopsis() string {
return "Inspect a submitted job"
}

func (c *InspectCommand) Run(args []string) int {
flags := c.Meta.FlagSet("inspect", FlagSetClient)
flags.Usage = func() { c.Ui.Output(c.Help()) }

if err := flags.Parse(args); err != nil {
return 1
}

// Check that we got exactly one job
args = flags.Args()
if len(args) != 1 {
c.Ui.Error(c.Help())
return 1
}
jobID := args[0]

// Get the HTTP client
client, err := c.Meta.Client()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
return 1
}

// Check if the job exists
jobs, _, err := client.Jobs().PrefixList(jobID)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error inspecting job: %s", err))
return 1
}
if len(jobs) == 0 {
c.Ui.Error(fmt.Sprintf("No job(s) with prefix or id %q found", jobID))
return 1
}
if len(jobs) > 1 {
out := make([]string, len(jobs)+1)
out[0] = "ID|Type|Priority|Status"
for i, job := range jobs {
out[i+1] = fmt.Sprintf("%s|%s|%d|%s",
job.ID,
job.Type,
job.Priority,
job.Status)
}
c.Ui.Output(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", formatList(out)))
return 0
}

// Prefix lookup matched a single job
job, _, err := client.Jobs().RawJob(jobs[0].ID, nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error inspecting job: %s", err))
return 1
}

// Print the contents of the job
c.Ui.Output(job)
return 0
}
46 changes: 46 additions & 0 deletions command/inspect_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package command

import (
"strings"
"testing"

"github.com/mitchellh/cli"
)

func TestInspectCommand_Implements(t *testing.T) {
var _ cli.Command = &InspectCommand{}
}

func TestInspectCommand_Fails(t *testing.T) {
srv, _, url := testServer(t, nil)
defer srv.Stop()

ui := new(cli.MockUi)
cmd := &InspectCommand{Meta: Meta{Ui: ui}}

// Fails on misuse
if code := cmd.Run([]string{"some", "bad", "args"}); code != 1 {
t.Fatalf("expected exit code 1, got: %d", code)
}
if out := ui.ErrorWriter.String(); !strings.Contains(out, cmd.Help()) {
t.Fatalf("expected help output, got: %s", out)
}
ui.ErrorWriter.Reset()

// Fails on non-existent job ID
if code := cmd.Run([]string{"-address=" + url, "nope"}); code != 1 {
t.Fatalf("expect exit 1, got: %d", code)
}
if out := ui.ErrorWriter.String(); !strings.Contains(out, "No job(s) with prefix or id") {
t.Fatalf("expect not found error, got: %s", out)
}
ui.ErrorWriter.Reset()

// Fails on connection failure
if code := cmd.Run([]string{"-address=nope", "nope"}); code != 1 {
t.Fatalf("expected exit code 1, got: %d", code)
}
if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error inspecting job") {
t.Fatalf("expected failed query error, got: %s", out)
}
}
53 changes: 25 additions & 28 deletions command/stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,36 +75,33 @@ func (c *StopCommand) Run(args []string) int {
}

// Check if the job exists
job, _, err := client.Jobs().Info(jobID, nil)
jobs, _, err := client.Jobs().PrefixList(jobID)
if err != nil {
jobs, _, err := client.Jobs().PrefixList(jobID)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error deregistering job: %s", err))
return 1
}
if len(jobs) == 0 {
c.Ui.Error(fmt.Sprintf("No job(s) with prefix or id %q found", jobID))
return 1
}
if len(jobs) > 1 {
out := make([]string, len(jobs)+1)
out[0] = "ID|Type|Priority|Status"
for i, job := range jobs {
out[i+1] = fmt.Sprintf("%s|%s|%d|%s",
job.ID,
job.Type,
job.Priority,
job.Status)
}
c.Ui.Output(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", formatList(out)))
return 0
}
// Prefix lookup matched a single job
job, _, err = client.Jobs().Info(jobs[0].ID, nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error deregistering job: %s", err))
return 1
c.Ui.Error(fmt.Sprintf("Error deregistering job: %s", err))
return 1
}
if len(jobs) == 0 {
c.Ui.Error(fmt.Sprintf("No job(s) with prefix or id %q found", jobID))
return 1
}
if len(jobs) > 1 {
out := make([]string, len(jobs)+1)
out[0] = "ID|Type|Priority|Status"
for i, job := range jobs {
out[i+1] = fmt.Sprintf("%s|%s|%d|%s",
job.ID,
job.Type,
job.Priority,
job.Status)
}
c.Ui.Output(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", formatList(out)))
return 0
}
// Prefix lookup matched a single job
job, _, err := client.Jobs().Info(jobs[0].ID, nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error deregistering job: %s", err))
return 1
}

// Invoke the stop
Expand Down
6 changes: 5 additions & 1 deletion commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,11 @@ func Commands(metaPtr *command.Meta) map[string]cli.CommandFactory {
Meta: meta,
}, nil
},

"inspect": func() (cli.Command, error) {
return &command.InspectCommand{
Meta: meta,
}, nil
},
"node-drain": func() (cli.Command, error) {
return &command.NodeDrainCommand{
Meta: meta,
Expand Down

0 comments on commit b0d10b1

Please sign in to comment.