From e684636aedeb7cde624a18ed03f58ae6b9eeb019 Mon Sep 17 00:00:00 2001 From: Tim Gross Date: Mon, 5 Aug 2024 15:35:18 -0400 Subject: [PATCH] cli: add option to return original HCL in `job inspect` command (#23699) In 1.6.0 we shipped the ability to review the original HCL in the web UI, but didn't follow-up with an equivalent in the command line. Add a `-hcl` flag to the `job inspect` command. Closes: https://github.com/hashicorp/nomad/issues/6778 --- .changelog/23699.txt | 3 + command/job_inspect.go | 100 ++++++++++++++++-- command/job_inspect_test.go | 34 ++++++ website/content/docs/commands/job/index.mdx | 2 + website/content/docs/commands/job/inspect.mdx | 6 +- 5 files changed, 138 insertions(+), 7 deletions(-) create mode 100644 .changelog/23699.txt diff --git a/.changelog/23699.txt b/.changelog/23699.txt new file mode 100644 index 000000000..894749e1b --- /dev/null +++ b/.changelog/23699.txt @@ -0,0 +1,3 @@ +```release-note:improvement +cli: Added option to return original HCL in `job inspect` command +``` diff --git a/command/job_inspect.go b/command/job_inspect.go index 86be13e9d..b35ac286c 100644 --- a/command/job_inspect.go +++ b/command/job_inspect.go @@ -34,10 +34,17 @@ General Options: Inspect Options: -version - Display the job at the given job version. + Display the job at the given job version. Defaults to current version. -json - Output the job in its JSON format. + Output the job in its JSON format. Cannot be used with -hcl. + + -hcl + Output the original HCL submitted with the job. Cannot be used with -json. + + -with-vars + Include the original HCL2 variables submitted with the job. Can only be used + with -hcl. -t Format and display job using a Go template. @@ -52,9 +59,11 @@ func (c *JobInspectCommand) Synopsis() string { func (c *JobInspectCommand) AutocompleteFlags() complete.Flags { return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient), complete.Flags{ - "-version": complete.PredictAnything, - "-json": complete.PredictNothing, - "-t": complete.PredictAnything, + "-version": complete.PredictAnything, + "-hcl": complete.PredictNothing, + "-with-vars": complete.PredictNothing, + "-json": complete.PredictNothing, + "-t": complete.PredictAnything, }) } @@ -76,12 +85,14 @@ func (c *JobInspectCommand) AutocompleteArgs() complete.Predictor { func (c *JobInspectCommand) Name() string { return "job inspect" } func (c *JobInspectCommand) Run(args []string) int { - var json bool + var json, hcl, withVars 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.BoolVar(&hcl, "hcl", false, "") + flags.BoolVar(&withVars, "with-vars", false, "") flags.StringVar(&tmpl, "t", "", "") flags.StringVar(&versionStr, "version", "", "") @@ -90,6 +101,15 @@ func (c *JobInspectCommand) Run(args []string) int { } args = flags.Args() + if hcl && json { + c.Ui.Error("can only use one of -hcl or -json") + return 1 + } + if withVars && !hcl { + c.Ui.Error("can only use -with-vars with -hcl") + return 1 + } + // Get the HTTP client client, err := c.Meta.Client() if err != nil { @@ -141,6 +161,21 @@ func (c *JobInspectCommand) Run(args []string) int { version = &v } + if hcl { + out, err := getJobHCL(client, namespace, jobID, version) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error inspecting job: %s", err)) + return 1 + } + c.Ui.Output(out.Source) + + if withVars { + c.Ui.Warn(getWithVarsOutput(namespace, jobID, out.Variables, out.VariableFlags)) + } + + return 0 + } + // Prefix lookup matched a single job job, err := getJob(client, namespace, jobID, version) if err != nil { @@ -206,3 +241,56 @@ func getJob(client *api.Client, namespace, jobID string, version *uint64) (*api. return nil, fmt.Errorf("job %q with version %d couldn't be found", jobID, *version) } + +// getJob retrieves the job optionally at a particular version. +func getJobHCL(client *api.Client, namespace, jobID string, version *uint64) (*api.JobSubmission, error) { + var q *api.QueryOptions + if namespace != "" { + q = &api.QueryOptions{Namespace: namespace} + } + v := uint64(0) + if version != nil { + v = *version + } else { + job, _, err := client.Jobs().Info(jobID, q) + if err != nil { + return nil, err + } + v = *job.Version + } + submission, _, err := client.Jobs().Submission(jobID, int(v), q) + + if err != nil { + return nil, fmt.Errorf("job %q with version %d couldn't be found", jobID, version) + } + return submission, err +} + +func getWithVarsOutput(namespace, jobID string, uiVars string, varsMap map[string]string) string { + runArgs := []string{} + if namespace != "" { + runArgs = append(runArgs, "-namespace") + runArgs = append(runArgs, namespace) + } + + for k, v := range varsMap { + runArgs = append(runArgs, "-var") + runArgs = append(runArgs, fmt.Sprintf("%s=%s", k, v)) + } + for _, uiVar := range strings.Split(uiVars, "\n") { + uiVar = strings.TrimSpace(uiVar) + if uiVar != "" { + runArgs = append(runArgs, "-var") + runArgs = append(runArgs, uiVar) + } + } + runArgs = append(runArgs, jobID) + + return fmt.Sprintf(` +To run this job as originally submitted: + +$ nomad job inspect -namespace %s -hcl %s | + nomad job run %s +`, namespace, jobID, strings.Join(runArgs, " ")) + +} diff --git a/command/job_inspect_test.go b/command/job_inspect_test.go index 9f33817a8..f09a0bee0 100644 --- a/command/job_inspect_test.go +++ b/command/job_inspect_test.go @@ -186,3 +186,37 @@ namespace "default" { }) } } + +func TestInspectCommand_HCLVars(t *testing.T) { + ci.Parallel(t) + + // no vars + out := getWithVarsOutput("default", "example", "", map[string]string{}) + must.Eq(t, ` +To run this job as originally submitted: + +$ nomad job inspect -namespace default -hcl example | + nomad job run -namespace default example +`, out) + + // vars from the UI, erratic extra spaces + out = getWithVarsOutput("default", "example", "\n http_port=foo \n \nbar=baz ", + map[string]string{}) + must.Eq(t, ` +To run this job as originally submitted: + +$ nomad job inspect -namespace default -hcl example | + nomad job run -namespace default -var http_port=foo -var bar=baz example +`, out) + + // same vars from the CLI + out = getWithVarsOutput("default", "example", "", + map[string]string{"http_port": "foo", "bar": "baz"}) + must.Eq(t, ` +To run this job as originally submitted: + +$ nomad job inspect -namespace default -hcl example | + nomad job run -namespace default -var http_port=foo -var bar=baz example +`, out) + +} diff --git a/website/content/docs/commands/job/index.mdx b/website/content/docs/commands/job/index.mdx index 47ff5c79d..6a592688e 100644 --- a/website/content/docs/commands/job/index.mdx +++ b/website/content/docs/commands/job/index.mdx @@ -20,6 +20,7 @@ subcommands are available: - [`job dispatch`][dispatch] - Dispatch an instance of a parameterized job - [`job eval`][eval] - Force an evaluation for a job - [`job history`][history] - Display all tracked versions of a job +- [`job inspect`][inspect] - Inspect the contents of a submitted job. - [`job promote`][promote] - Promote a job's canaries - [`job revert`][revert] - Revert to a prior version of the job - [`job status`][status] - Display status information about a job @@ -28,6 +29,7 @@ subcommands are available: [dispatch]: /nomad/docs/commands/job/dispatch 'Dispatch an instance of a parameterized job' [eval]: /nomad/docs/commands/job/eval 'Force an evaluation for a job' [history]: /nomad/docs/commands/job/history 'Display all tracked versions of a job' +[inspect]: /nomad/docs/commands/job/inspect [promote]: /nomad/docs/commands/job/promote "Promote a job's canaries" [revert]: /nomad/docs/commands/job/revert 'Revert to a prior version of the job' [status]: /nomad/docs/commands/job/status 'Display status information about a job' diff --git a/website/content/docs/commands/job/inspect.mdx b/website/content/docs/commands/job/inspect.mdx index a96593f77..90a8535b2 100644 --- a/website/content/docs/commands/job/inspect.mdx +++ b/website/content/docs/commands/job/inspect.mdx @@ -33,7 +33,11 @@ run the command with a job prefix instead of the exact job ID. ## Inspect Options - `-version`: Display only the job at the given job version. -- `-json` : Output the job in its JSON format. +- `-json` : Output the job in its JSON format. Cannot be used with `-hcl`. +- `-hcl`: Output the original HCL submitted with the job. Cannot be used with + `-json`. +- `-with-vars`: Include the original HCL2 variables submitted with the job. Can + only be used with `-hcl`. - `-t` : Format and display the job using a Go template. ## Examples