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: Add nomad inspect command #952

Merged
merged 2 commits into from
Mar 21, 2016
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
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