Skip to content

Commit

Permalink
cli: use shared logic for resolving job prefix (#16306)
Browse files Browse the repository at this point in the history
Several `nomad job` subcommands had duplicate or slightly similar logic
for resolving a job ID from a CLI argument prefix, while others did not
have this functionality at all.

This commit pulls the shared logic to the command Meta and updates all
`nomad job` subcommands to use it.
  • Loading branch information
lgfa29 authored Mar 3, 2023
1 parent a4f7926 commit 0e824d3
Show file tree
Hide file tree
Showing 25 changed files with 280 additions and 179 deletions.
7 changes: 7 additions & 0 deletions .changelog/16306.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```release-note:improvement
cli: Add job prefix match to the `nomad job dispatch`, `nomad job eval`, `nomad job scale`, and `nomad job scaling-events` commands
```

```release-note:improvement
cli: Add support for the wildcard namespace `*` to the `nomad job dispatch`, `nomad job eval`, `nomad job scale`, and `nomad job scaling-events` commands
```
20 changes: 4 additions & 16 deletions command/job_allocs.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,27 +105,15 @@ func (c *JobAllocsCommand) Run(args []string) int {
return 1
}

jobID := strings.TrimSpace(args[0])

// Check if the job exists
jobs, _, err := client.Jobs().PrefixList(jobID)
jobIDPrefix := strings.TrimSpace(args[0])
jobID, namespace, err := c.JobIDByPrefix(client, jobIDPrefix, nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error listing jobs: %s", err))
return 1
}
if len(jobs) == 0 {
c.Ui.Error(fmt.Sprintf("No job(s) with prefix or id %q found", jobID))
c.Ui.Error(err.Error())
return 1
}
if len(jobs) > 1 {
if (jobID != jobs[0].ID) || (c.allNamespaces() && jobs[0].ID == jobs[1].ID) {
c.Ui.Error(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", createStatusListOutput(jobs, c.allNamespaces())))
return 1
}
}

jobID = jobs[0].ID
q := &api.QueryOptions{Namespace: jobs[0].JobSummary.Namespace}
q := &api.QueryOptions{Namespace: namespace}

allocs, _, err := client.Jobs().Allocations(jobID, all, q)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions command/job_allocs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ func TestJobAllocsCommand_Fails(t *testing.T) {
code = cmd.Run([]string{"-address=nope", "foo"})
outerr = ui.ErrorWriter.String()
require.Equalf(t, 1, code, "expected exit code 1, got: %d", code)
require.Containsf(t, outerr, "Error listing jobs", "expected failed query error, got: %s", outerr)
require.Containsf(t, outerr, "Error querying job prefix", "expected failed query error, got: %s", outerr)

ui.ErrorWriter.Reset()

// Bad job name
code = cmd.Run([]string{"-address=" + url, "foo"})
outerr = ui.ErrorWriter.String()
require.Equalf(t, 1, code, "expected exit 1, got: %d", code)
require.Containsf(t, outerr, "No job(s) with prefix or id \"foo\" found", "expected no job found, got: %s", outerr)
require.Containsf(t, outerr, "No job(s) with prefix or ID \"foo\" found", "expected no job found, got: %s", outerr)

ui.ErrorWriter.Reset()
}
Expand Down
20 changes: 4 additions & 16 deletions command/job_deployments.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,27 +110,15 @@ func (c *JobDeploymentsCommand) Run(args []string) int {
return 1
}

jobID := strings.TrimSpace(args[0])

// Check if the job exists
jobs, _, err := client.Jobs().PrefixList(jobID)
jobIDPrefix := strings.TrimSpace(args[0])
jobID, namespace, err := c.JobIDByPrefix(client, jobIDPrefix, nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error listing jobs: %s", err))
return 1
}
if len(jobs) == 0 {
c.Ui.Error(fmt.Sprintf("No job(s) with prefix or id %q found", jobID))
c.Ui.Error(err.Error())
return 1
}
if len(jobs) > 1 {
if (jobID != jobs[0].ID) || (c.allNamespaces() && jobs[0].ID == jobs[1].ID) {
c.Ui.Error(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", createStatusListOutput(jobs, c.allNamespaces())))
return 1
}
}

jobID = jobs[0].ID
q := &api.QueryOptions{Namespace: jobs[0].JobSummary.Namespace}
q := &api.QueryOptions{Namespace: namespace}

// Truncate the id unless full length is requested
length := shortId
Expand Down
2 changes: 1 addition & 1 deletion command/job_deployments_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func TestJobDeploymentsCommand_Fails(t *testing.T) {
if code := cmd.Run([]string{"-address=nope", "foo"}); code != 1 {
t.Fatalf("expected exit code 1, got: %d", code)
}
if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error listing jobs") {
if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error querying job prefix") {
t.Fatalf("expected failed query error, got: %s", out)
}
ui.ErrorWriter.Reset()
Expand Down
14 changes: 12 additions & 2 deletions command/job_dispatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ func (c *JobDispatchCommand) Run(args []string) int {
return 1
}

job := args[0]
var payload []byte
var readErr error

Expand Down Expand Up @@ -175,11 +174,22 @@ func (c *JobDispatchCommand) Run(args []string) int {
return 1
}

// Check if the job exists
jobIDPrefix := strings.TrimSpace(args[0])
jobID, namespace, err := c.JobIDByPrefix(client, jobIDPrefix, func(j *api.JobListStub) bool {
return j.ParameterizedJob
})
if err != nil {
c.Ui.Error(err.Error())
return 1
}

// Dispatch the job
w := &api.WriteOptions{
IdempotencyToken: idempotencyToken,
Namespace: namespace,
}
resp, _, err := client.Jobs().Dispatch(job, metaMap, payload, idPrefixTemplate, w)
resp, _, err := client.Jobs().Dispatch(jobID, metaMap, payload, idPrefixTemplate, w)
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to dispatch job: %s", err))
return 1
Expand Down
2 changes: 1 addition & 1 deletion command/job_dispatch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func TestJobDispatchCommand_Fails(t *testing.T) {
if code := cmd.Run([]string{"-address=nope", "foo"}); code != 1 {
t.Fatalf("expected exit code 1, got: %d", code)
}
if out := ui.ErrorWriter.String(); !strings.Contains(out, "Failed to dispatch") {
if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error querying job prefix") {
t.Fatalf("expected failed query error, got: %s", out)
}
ui.ErrorWriter.Reset()
Expand Down
16 changes: 13 additions & 3 deletions command/job_eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,23 @@ func (c *JobEvalCommand) Run(args []string) int {
if verbose {
length = fullId
}
// Call eval endpoint
jobID := args[0]

// 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
}

// Call eval endpoint
opts := api.EvalOptions{
ForceReschedule: c.forceRescheduling,
}
evalId, _, err := client.Jobs().EvaluateWithOpts(jobID, opts, nil)
w := &api.WriteOptions{
Namespace: namespace,
}
evalId, _, err := client.Jobs().EvaluateWithOpts(jobID, opts, w)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error evaluating job: %s", err))
return 1
Expand Down
21 changes: 5 additions & 16 deletions command/job_history.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,29 +121,18 @@ func (c *JobHistoryCommand) Run(args []string) int {
return 1
}

jobID := strings.TrimSpace(args[0])

// Check if the job exists
jobs, _, err := client.Jobs().PrefixList(jobID)
jobIDPrefix := strings.TrimSpace(args[0])
jobID, namespace, err := c.JobIDByPrefix(client, jobIDPrefix, nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error listing jobs: %s", err))
return 1
}
if len(jobs) == 0 {
c.Ui.Error(fmt.Sprintf("No job(s) with prefix or id %q found", jobID))
c.Ui.Error(err.Error())
return 1
}
if len(jobs) > 1 {
if (jobID != jobs[0].ID) || (c.allNamespaces() && jobs[0].ID == jobs[1].ID) {
c.Ui.Error(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", createStatusListOutput(jobs, c.allNamespaces())))
return 1
}
}

q := &api.QueryOptions{Namespace: jobs[0].JobSummary.Namespace}
q := &api.QueryOptions{Namespace: namespace}

// Prefix lookup matched a single job
versions, diffs, _, err := client.Jobs().Versions(jobs[0].ID, diff, q)
versions, diffs, _, err := client.Jobs().Versions(jobID, diff, q)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error retrieving job versions: %s", err))
return 1
Expand Down
2 changes: 1 addition & 1 deletion command/job_history_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func TestJobHistoryCommand_Fails(t *testing.T) {
if code := cmd.Run([]string{"-address=nope", "foo"}); code != 1 {
t.Fatalf("expected exit code 1, got: %d", code)
}
if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error listing jobs") {
if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error querying job prefix") {
t.Fatalf("expected failed query error, got: %s", out)
}
ui.ErrorWriter.Reset()
Expand Down
18 changes: 4 additions & 14 deletions command/job_inspect.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,24 +117,14 @@ func (c *JobInspectCommand) Run(args []string) int {
c.Ui.Error(commandErrorText(c))
return 1
}
jobID := strings.TrimSpace(args[0])

// Check if the job exists
jobs, _, err := client.Jobs().PrefixList(jobID)
jobIDPrefix := strings.TrimSpace(args[0])
jobID, namespace, err := c.JobIDByPrefix(client, jobIDPrefix, nil)
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))
c.Ui.Error(err.Error())
return 1
}
if len(jobs) > 1 {
if (jobID != jobs[0].ID) || (c.allNamespaces() && jobs[0].ID == jobs[1].ID) {
c.Ui.Error(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", createStatusListOutput(jobs, c.allNamespaces())))
return 1
}
}

var version *uint64
if versionStr != "" {
Expand All @@ -148,7 +138,7 @@ func (c *JobInspectCommand) Run(args []string) int {
}

// Prefix lookup matched a single job
job, err := getJob(client, jobs[0].JobSummary.Namespace, jobs[0].ID, version)
job, err := getJob(client, namespace, jobID, version)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error inspecting job: %s", err))
return 1
Expand Down
4 changes: 2 additions & 2 deletions command/job_inspect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func TestInspectCommand_Fails(t *testing.T) {
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") {
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()
Expand All @@ -47,7 +47,7 @@ func TestInspectCommand_Fails(t *testing.T) {
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") {
if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error querying job prefix") {
t.Fatalf("expected failed query error, got: %s", out)
}
ui.ErrorWriter.Reset()
Expand Down
31 changes: 10 additions & 21 deletions command/job_periodic_force.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package command

import (
"errors"
"fmt"
"strings"

Expand Down Expand Up @@ -112,31 +113,19 @@ func (c *JobPeriodicForceCommand) Run(args []string) int {
}

// Check if the job exists
jobID := args[0]
jobs, _, err := client.Jobs().PrefixList(jobID)
jobIDPrefix := strings.TrimSpace(args[0])
jobID, namespace, err := c.JobIDByPrefix(client, jobIDPrefix, func(j *api.JobListStub) bool {
return j.Periodic
})
if err != nil {
c.Ui.Error(fmt.Sprintf("Error forcing periodic job: %s", err))
return 1
}
// filter non-periodic jobs
periodicJobs := make([]*api.JobListStub, 0, len(jobs))
for _, j := range jobs {
if j.Periodic {
periodicJobs = append(periodicJobs, j)
var noPrefixErr *NoJobWithPrefixError
if errors.As(err, &noPrefixErr) {
err = fmt.Errorf("No periodic job(s) with prefix or ID %q found", jobIDPrefix)
}
}
if len(periodicJobs) == 0 {
c.Ui.Error(fmt.Sprintf("No periodic job(s) with prefix or id %q found", jobID))
return 1
}
// preriodicJobs is sorted by job ID
// so if there is a job whose ID is equal to jobID then it must be the first item
if len(periodicJobs) > 1 && periodicJobs[0].ID != jobID {
c.Ui.Error(fmt.Sprintf("Prefix matched multiple periodic jobs\n\n%s", createStatusListOutput(periodicJobs, c.allNamespaces())))
c.Ui.Error(err.Error())
return 1
}
jobID = periodicJobs[0].ID
q := &api.WriteOptions{Namespace: periodicJobs[0].JobSummary.Namespace}
q := &api.WriteOptions{Namespace: namespace}

// force the evaluation
evalID, _, err := client.Jobs().PeriodicForce(jobID, q)
Expand Down
2 changes: 1 addition & 1 deletion command/job_periodic_force_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func TestJobPeriodicForceCommand_Fails(t *testing.T) {
code = cmd.Run([]string{"-address=nope", "12"})
require.Equal(t, code, 1, "expected error")
out = ui.ErrorWriter.String()
require.Contains(t, out, "Error forcing periodic job", "expected force error")
require.Contains(t, out, "Error querying job prefix", "expected force error")
}

func TestJobPeriodicForceCommand_AutocompleteArgs(t *testing.T) {
Expand Down
21 changes: 5 additions & 16 deletions command/job_promote.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,24 +117,13 @@ func (c *JobPromoteCommand) Run(args []string) int {
}

// Check if the job exists
jobID := strings.TrimSpace(args[0])
jobs, _, err := client.Jobs().PrefixList(jobID)
jobIDPrefix := strings.TrimSpace(args[0])
jobID, namespace, err := c.JobIDByPrefix(client, jobIDPrefix, nil)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error promoting job: %s", err))
c.Ui.Error(err.Error())
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 {
if (jobID != jobs[0].ID) || (c.allNamespaces() && jobs[0].ID == jobs[1].ID) {
c.Ui.Error(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", createStatusListOutput(jobs, c.allNamespaces())))
return 1
}
}
jobID = jobs[0].ID
q := &api.QueryOptions{Namespace: jobs[0].JobSummary.Namespace}
q := &api.QueryOptions{Namespace: namespace}

// Do a prefix lookup
deploy, _, err := client.Jobs().LatestDeployment(jobID, q)
Expand All @@ -148,7 +137,7 @@ func (c *JobPromoteCommand) Run(args []string) int {
return 1
}

wq := &api.WriteOptions{Namespace: jobs[0].JobSummary.Namespace}
wq := &api.WriteOptions{Namespace: namespace}
var u *api.DeploymentUpdateResponse
if len(groups) == 0 {
u, _, err = client.Deployments().PromoteAll(deploy.ID, wq)
Expand Down
2 changes: 1 addition & 1 deletion command/job_promote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func TestJobPromoteCommand_Fails(t *testing.T) {
if code := cmd.Run([]string{"-address=nope", "12"}); code != 1 {
t.Fatalf("expected exit code 1, got: %d", code)
}
if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error promoting") {
if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error querying job prefix") {
t.Fatalf("expected failed to promote error, got: %s", out)
}
ui.ErrorWriter.Reset()
Expand Down
Loading

0 comments on commit 0e824d3

Please sign in to comment.