1aceff7806
Most job subcommands allow for job ID prefix match as a convenience functionality so users don't have to type the full job ID. But this introduces a hard ACL requirement that the token used to run these commands have the `list-jobs` permission, even if the token has enough permission to execute the basic command action and the user passed an exact job ID. This change softens this requirement by not failing the prefix match in case the request results in a permission denied error and instead using the information passed by the user directly.
206 lines
4.6 KiB
Go
206 lines
4.6 KiB
Go
package command
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/nomad/api"
|
|
"github.com/hashicorp/nomad/api/contexts"
|
|
"github.com/posener/complete"
|
|
)
|
|
|
|
type JobInspectCommand struct {
|
|
Meta
|
|
}
|
|
|
|
func (c *JobInspectCommand) Help() string {
|
|
helpText := `
|
|
Usage: nomad job inspect [options] <job>
|
|
Alias: nomad inspect
|
|
|
|
Inspect is used to see the specification of a submitted job.
|
|
|
|
When ACLs are enabled, this command requires a token with the 'read-job'
|
|
capability for the job's namespace. The 'list-jobs' capability is required to
|
|
run the command with a job prefix instead of the exact job ID.
|
|
|
|
General Options:
|
|
|
|
` + generalOptionsUsage(usageOptsDefault) + `
|
|
|
|
Inspect Options:
|
|
|
|
-version <job version>
|
|
Display the job at the given job version.
|
|
|
|
-json
|
|
Output the job in its JSON format.
|
|
|
|
-t
|
|
Format and display job using a Go template.
|
|
`
|
|
return strings.TrimSpace(helpText)
|
|
}
|
|
|
|
func (c *JobInspectCommand) Synopsis() string {
|
|
return "Inspect a submitted job"
|
|
}
|
|
|
|
func (c *JobInspectCommand) AutocompleteFlags() complete.Flags {
|
|
return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
|
|
complete.Flags{
|
|
"-version": complete.PredictAnything,
|
|
"-json": complete.PredictNothing,
|
|
"-t": complete.PredictAnything,
|
|
})
|
|
}
|
|
|
|
func (c *JobInspectCommand) AutocompleteArgs() complete.Predictor {
|
|
return complete.PredictFunc(func(a complete.Args) []string {
|
|
client, err := c.Meta.Client()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
resp, _, err := client.Search().PrefixSearch(a.Last, contexts.Jobs, nil)
|
|
if err != nil {
|
|
return []string{}
|
|
}
|
|
return resp.Matches[contexts.Jobs]
|
|
})
|
|
}
|
|
|
|
func (c *JobInspectCommand) Name() string { return "job inspect" }
|
|
|
|
func (c *JobInspectCommand) Run(args []string) int {
|
|
var json bool
|
|
var tmpl, versionStr string
|
|
|
|
flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
|
|
flags.Usage = func() { c.Ui.Output(c.Help()) }
|
|
flags.BoolVar(&json, "json", false, "")
|
|
flags.StringVar(&tmpl, "t", "", "")
|
|
flags.StringVar(&versionStr, "version", "", "")
|
|
|
|
if err := flags.Parse(args); err != nil {
|
|
return 1
|
|
}
|
|
args = flags.Args()
|
|
|
|
// Get the HTTP client
|
|
client, err := c.Meta.Client()
|
|
if err != nil {
|
|
c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
|
|
return 1
|
|
}
|
|
|
|
// If args not specified but output format is specified, format and output the jobs data list
|
|
if len(args) == 0 && (json || len(tmpl) > 0) {
|
|
jobs, _, err := client.Jobs().List(nil)
|
|
if err != nil {
|
|
c.Ui.Error(fmt.Sprintf("Error querying jobs: %v", err))
|
|
return 1
|
|
}
|
|
|
|
out, err := Format(json, tmpl, jobs)
|
|
if err != nil {
|
|
c.Ui.Error(err.Error())
|
|
return 1
|
|
}
|
|
|
|
c.Ui.Output(out)
|
|
return 0
|
|
}
|
|
|
|
// Check that we got exactly one job
|
|
if len(args) != 1 {
|
|
c.Ui.Error("This command takes one argument: <job>")
|
|
c.Ui.Error(commandErrorText(c))
|
|
return 1
|
|
}
|
|
|
|
// Check if the job exists
|
|
jobIDPrefix := strings.TrimSpace(args[0])
|
|
jobID, namespace, err := c.JobIDByPrefix(client, jobIDPrefix, nil)
|
|
if err != nil {
|
|
c.Ui.Error(err.Error())
|
|
return 1
|
|
}
|
|
|
|
var version *uint64
|
|
if versionStr != "" {
|
|
v, _, err := parseVersion(versionStr)
|
|
if err != nil {
|
|
c.Ui.Error(fmt.Sprintf("Error parsing version value %q: %v", versionStr, err))
|
|
return 1
|
|
}
|
|
|
|
version = &v
|
|
}
|
|
|
|
// Prefix lookup matched a single job
|
|
job, err := getJob(client, namespace, jobID, version)
|
|
if err != nil {
|
|
c.Ui.Error(fmt.Sprintf("Error inspecting job: %s", err))
|
|
return 1
|
|
}
|
|
|
|
// If output format is specified, format and output the data
|
|
if json || len(tmpl) > 0 {
|
|
out, err := Format(json, tmpl, job)
|
|
if err != nil {
|
|
c.Ui.Error(err.Error())
|
|
return 1
|
|
}
|
|
|
|
c.Ui.Output(out)
|
|
return 0
|
|
}
|
|
|
|
// Print the contents of the job
|
|
req := struct {
|
|
Job *api.Job
|
|
}{
|
|
Job: job,
|
|
}
|
|
f, err := DataFormat("json", "")
|
|
if err != nil {
|
|
c.Ui.Error(fmt.Sprintf("Error getting formatter: %s", err))
|
|
return 1
|
|
}
|
|
|
|
out, err := f.TransformData(req)
|
|
if err != nil {
|
|
c.Ui.Error(fmt.Sprintf("Error formatting the data: %s", err))
|
|
return 1
|
|
}
|
|
c.Ui.Output(out)
|
|
return 0
|
|
}
|
|
|
|
// getJob retrieves the job optionally at a particular version.
|
|
func getJob(client *api.Client, namespace, jobID string, version *uint64) (*api.Job, error) {
|
|
var q *api.QueryOptions
|
|
if namespace != "" {
|
|
q = &api.QueryOptions{Namespace: namespace}
|
|
}
|
|
if version == nil {
|
|
job, _, err := client.Jobs().Info(jobID, q)
|
|
return job, err
|
|
}
|
|
|
|
versions, _, _, err := client.Jobs().Versions(jobID, false, q)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, j := range versions {
|
|
if *j.Version != *version {
|
|
continue
|
|
}
|
|
return j, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("job %q with version %d couldn't be found", jobID, *version)
|
|
}
|