From 85db7187fbff10f0a987ca5e532ec8f11298b0b5 Mon Sep 17 00:00:00 2001 From: Mahmood Ali Date: Wed, 17 Jun 2020 15:39:50 -0400 Subject: [PATCH] cli: jobs allow querying jobs in all namespaces --- api/api.go | 6 +++ command/job_deployments.go | 8 +-- command/job_history.go | 6 ++- command/job_inspect.go | 14 +++-- command/job_periodic_force.go | 5 +- command/job_promote.go | 10 ++-- command/job_revert.go | 6 ++- command/job_status.go | 54 ++++++++++++++------ command/job_stop.go | 9 ++-- command/meta.go | 12 ++++- vendor/github.com/hashicorp/nomad/api/api.go | 6 +++ 11 files changed, 97 insertions(+), 39 deletions(-) diff --git a/api/api.go b/api/api.go index 9633efb0e..54ede4f89 100644 --- a/api/api.go +++ b/api/api.go @@ -28,6 +28,12 @@ var ( ClientConnTimeout = 1 * time.Second ) +const ( + // AllNamespacesNamespace is a sentinel Namespace value to indicate that api should search for + // jobs and allocations in all the namespaces the requester can access. + AllNamespacesNamespace = "*" +) + // QueryOptions are used to parametrize a query type QueryOptions struct { // Providing a datacenter overwrites the region provided diff --git a/command/job_deployments.go b/command/job_deployments.go index 39de2f94d..0e5944f08 100644 --- a/command/job_deployments.go +++ b/command/job_deployments.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/hashicorp/nomad/api" "github.com/hashicorp/nomad/api/contexts" "github.com/posener/complete" ) @@ -119,10 +120,11 @@ func (c *JobDeploymentsCommand) Run(args []string) int { return 1 } if len(jobs) > 1 && strings.TrimSpace(jobID) != jobs[0].ID { - c.Ui.Error(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", createStatusListOutput(jobs))) + 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} // Truncate the id unless full length is requested length := shortId @@ -131,7 +133,7 @@ func (c *JobDeploymentsCommand) Run(args []string) int { } if latest { - deploy, _, err := client.Jobs().LatestDeployment(jobID, nil) + deploy, _, err := client.Jobs().LatestDeployment(jobID, q) if err != nil { c.Ui.Error(fmt.Sprintf("Error retrieving deployments: %s", err)) return 1 @@ -152,7 +154,7 @@ func (c *JobDeploymentsCommand) Run(args []string) int { return 0 } - deploys, _, err := client.Jobs().Deployments(jobID, all, nil) + deploys, _, err := client.Jobs().Deployments(jobID, all, q) if err != nil { c.Ui.Error(fmt.Sprintf("Error retrieving deployments: %s", err)) return 1 diff --git a/command/job_history.go b/command/job_history.go index 73508ac22..be751b6db 100644 --- a/command/job_history.go +++ b/command/job_history.go @@ -131,12 +131,14 @@ func (c *JobHistoryCommand) Run(args []string) int { return 1 } if len(jobs) > 1 && strings.TrimSpace(jobID) != jobs[0].ID { - c.Ui.Error(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", createStatusListOutput(jobs))) + 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} + // Prefix lookup matched a single job - versions, diffs, _, err := client.Jobs().Versions(jobs[0].ID, diff, nil) + versions, diffs, _, err := client.Jobs().Versions(jobs[0].ID, diff, q) if err != nil { c.Ui.Error(fmt.Sprintf("Error retrieving job versions: %s", err)) return 1 diff --git a/command/job_inspect.go b/command/job_inspect.go index b3de0f4bb..9c002b1e2 100644 --- a/command/job_inspect.go +++ b/command/job_inspect.go @@ -127,7 +127,7 @@ func (c *JobInspectCommand) Run(args []string) int { return 1 } if len(jobs) > 1 && strings.TrimSpace(jobID) != jobs[0].ID { - c.Ui.Error(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", createStatusListOutput(jobs))) + c.Ui.Error(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", createStatusListOutput(jobs, c.allNamespaces()))) return 1 } @@ -143,7 +143,7 @@ func (c *JobInspectCommand) Run(args []string) int { } // Prefix lookup matched a single job - job, err := getJob(client, jobs[0].ID, version) + job, err := getJob(client, jobs[0].JobSummary.Namespace, jobs[0].ID, version) if err != nil { c.Ui.Error(fmt.Sprintf("Error inspecting job: %s", err)) return 1 @@ -183,13 +183,17 @@ func (c *JobInspectCommand) Run(args []string) int { } // getJob retrieves the job optionally at a particular version. -func getJob(client *api.Client, jobID string, version *uint64) (*api.Job, error) { +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, nil) + job, _, err := client.Jobs().Info(jobID, q) return job, err } - versions, _, _, err := client.Jobs().Versions(jobID, false, nil) + versions, _, _, err := client.Jobs().Versions(jobID, false, q) if err != nil { return nil, err } diff --git a/command/job_periodic_force.go b/command/job_periodic_force.go index f11d35031..b98ea2355 100644 --- a/command/job_periodic_force.go +++ b/command/job_periodic_force.go @@ -127,13 +127,14 @@ func (c *JobPeriodicForceCommand) Run(args []string) int { return 1 } if len(periodicJobs) > 1 { - c.Ui.Error(fmt.Sprintf("Prefix matched multiple periodic jobs\n\n%s", createStatusListOutput(periodicJobs))) + c.Ui.Error(fmt.Sprintf("Prefix matched multiple periodic jobs\n\n%s", createStatusListOutput(periodicJobs, c.allNamespaces()))) return 1 } jobID = periodicJobs[0].ID + q := &api.WriteOptions{Namespace: periodicJobs[0].JobSummary.Namespace} // force the evaluation - evalID, _, err := client.Jobs().PeriodicForce(jobID, nil) + evalID, _, err := client.Jobs().PeriodicForce(jobID, q) if err != nil { c.Ui.Error(fmt.Sprintf("Error forcing periodic job %q: %s", jobID, err)) return 1 diff --git a/command/job_promote.go b/command/job_promote.go index fe5426733..91492877c 100644 --- a/command/job_promote.go +++ b/command/job_promote.go @@ -125,13 +125,14 @@ func (c *JobPromoteCommand) Run(args []string) int { return 1 } if len(jobs) > 1 && strings.TrimSpace(jobID) != jobs[0].ID { - c.Ui.Error(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", createStatusListOutput(jobs))) + 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} // Do a prefix lookup - deploy, _, err := client.Jobs().LatestDeployment(jobID, nil) + deploy, _, err := client.Jobs().LatestDeployment(jobID, q) if err != nil { c.Ui.Error(fmt.Sprintf("Error retrieving deployment: %s", err)) return 1 @@ -142,11 +143,12 @@ func (c *JobPromoteCommand) Run(args []string) int { return 1 } + wq := &api.WriteOptions{Namespace: jobs[0].JobSummary.Namespace} var u *api.DeploymentUpdateResponse if len(groups) == 0 { - u, _, err = client.Deployments().PromoteAll(deploy.ID, nil) + u, _, err = client.Deployments().PromoteAll(deploy.ID, wq) } else { - u, _, err = client.Deployments().PromoteGroups(deploy.ID, groups, nil) + u, _, err = client.Deployments().PromoteGroups(deploy.ID, groups, wq) } if err != nil { diff --git a/command/job_revert.go b/command/job_revert.go index ca3b0a329..648106fa2 100644 --- a/command/job_revert.go +++ b/command/job_revert.go @@ -5,6 +5,7 @@ import ( "os" "strings" + "github.com/hashicorp/nomad/api" "github.com/hashicorp/nomad/api/contexts" "github.com/posener/complete" ) @@ -144,12 +145,13 @@ func (c *JobRevertCommand) Run(args []string) int { return 1 } if len(jobs) > 1 && strings.TrimSpace(jobID) != jobs[0].ID { - c.Ui.Error(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", createStatusListOutput(jobs))) + c.Ui.Error(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", createStatusListOutput(jobs, c.allNamespaces()))) return 1 } // Prefix lookup matched a single job - resp, _, err := client.Jobs().Revert(jobs[0].ID, revertVersion, nil, nil, consulToken, vaultToken) + q := &api.WriteOptions{Namespace: jobs[0].JobSummary.Namespace} + resp, _, err := client.Jobs().Revert(jobs[0].ID, revertVersion, nil, q, consulToken, vaultToken) if err != nil { c.Ui.Error(fmt.Sprintf("Error retrieving job versions: %s", err)) return 1 diff --git a/command/job_status.go b/command/job_status.go index 3dd505830..4babd8088 100644 --- a/command/job_status.go +++ b/command/job_status.go @@ -122,9 +122,12 @@ func (c *JobStatusCommand) Run(args []string) int { return 1 } + allNamespaces := c.allNamespaces() + // Invoke list mode if no job ID. if len(args) == 0 { jobs, _, err := client.Jobs().List(nil) + if err != nil { c.Ui.Error(fmt.Sprintf("Error querying jobs: %s", err)) return 1 @@ -134,7 +137,7 @@ func (c *JobStatusCommand) Run(args []string) int { // No output if we have no jobs c.Ui.Output("No running jobs") } else { - c.Ui.Output(createStatusListOutput(jobs)) + c.Ui.Output(createStatusListOutput(jobs, allNamespaces)) } return 0 } @@ -152,11 +155,12 @@ func (c *JobStatusCommand) Run(args []string) int { return 1 } if len(jobs) > 1 && strings.TrimSpace(jobID) != jobs[0].ID { - c.Ui.Error(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", createStatusListOutput(jobs))) + c.Ui.Error(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", createStatusListOutput(jobs, allNamespaces))) return 1 } // Prefix lookup matched a single job - job, _, err := client.Jobs().Info(jobs[0].ID, nil) + q := &api.QueryOptions{Namespace: jobs[0].JobSummary.Namespace} + job, _, err := client.Jobs().Info(jobs[0].ID, q) if err != nil { c.Ui.Error(fmt.Sprintf("Error querying job: %s", err)) return 1 @@ -313,20 +317,24 @@ func (c *JobStatusCommand) outputParameterizedInfo(client *api.Client, job *api. // outputJobInfo prints information about the passed non-periodic job. If a // request fails, an error is returned. func (c *JobStatusCommand) outputJobInfo(client *api.Client, job *api.Job) error { + var q *api.QueryOptions + if job.Namespace != nil { + q = &api.QueryOptions{Namespace: *job.Namespace} + } // Query the allocations - jobAllocs, _, err := client.Jobs().Allocations(*job.ID, c.allAllocs, nil) + jobAllocs, _, err := client.Jobs().Allocations(*job.ID, c.allAllocs, q) if err != nil { return fmt.Errorf("Error querying job allocations: %s", err) } // Query the evaluations - jobEvals, _, err := client.Jobs().Evaluations(*job.ID, nil) + jobEvals, _, err := client.Jobs().Evaluations(*job.ID, q) if err != nil { return fmt.Errorf("Error querying job evaluations: %s", err) } - latestDeployment, _, err := client.Jobs().LatestDeployment(*job.ID, nil) + latestDeployment, _, err := client.Jobs().LatestDeployment(*job.ID, q) if err != nil { return fmt.Errorf("Error querying latest job deployment: %s", err) } @@ -505,7 +513,8 @@ func formatAllocList(allocations []*api.Allocation, verbose bool, uuidLength int // where appropriate func (c *JobStatusCommand) outputJobSummary(client *api.Client, job *api.Job) error { // Query the summary - summary, _, err := client.Jobs().Summary(*job.ID, nil) + q := &api.QueryOptions{Namespace: *job.Namespace} + summary, _, err := client.Jobs().Summary(*job.ID, q) if err != nil { return fmt.Errorf("Error querying job summary: %s", err) } @@ -650,16 +659,29 @@ func (c *JobStatusCommand) outputFailedPlacements(failedEval *api.Evaluation) { } // list general information about a list of jobs -func createStatusListOutput(jobs []*api.JobListStub) string { +func createStatusListOutput(jobs []*api.JobListStub, displayNS bool) string { out := make([]string, len(jobs)+1) - out[0] = "ID|Type|Priority|Status|Submit Date" - for i, job := range jobs { - out[i+1] = fmt.Sprintf("%s|%s|%d|%s|%s", - job.ID, - getTypeString(job), - job.Priority, - getStatusString(job.Status, &job.Stop), - formatTime(time.Unix(0, job.SubmitTime))) + if displayNS { + out[0] = "ID|Namespace|Type|Priority|Status|Submit Date" + for i, job := range jobs { + out[i+1] = fmt.Sprintf("%s|%s|%s|%d|%s|%s", + job.ID, + job.JobSummary.Namespace, + getTypeString(job), + job.Priority, + getStatusString(job.Status, &job.Stop), + formatTime(time.Unix(0, job.SubmitTime))) + } + } else { + out[0] = "ID|Type|Priority|Status|Submit Date" + for i, job := range jobs { + out[i+1] = fmt.Sprintf("%s|%s|%d|%s|%s", + job.ID, + getTypeString(job), + job.Priority, + getStatusString(job.Status, &job.Stop), + formatTime(time.Unix(0, job.SubmitTime))) + } } return formatList(out) } diff --git a/command/job_stop.go b/command/job_stop.go index c6d088592..691b06bc9 100644 --- a/command/job_stop.go +++ b/command/job_stop.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/hashicorp/nomad/api" "github.com/hashicorp/nomad/api/contexts" "github.com/posener/complete" ) @@ -126,11 +127,12 @@ func (c *JobStopCommand) Run(args []string) int { return 1 } if len(jobs) > 1 && strings.TrimSpace(jobID) != jobs[0].ID { - c.Ui.Error(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", createStatusListOutput(jobs))) + c.Ui.Error(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", createStatusListOutput(jobs, c.allNamespaces()))) return 1 } // Prefix lookup matched a single job - job, _, err := client.Jobs().Info(jobs[0].ID, nil) + q := &api.QueryOptions{Namespace: jobs[0].JobSummary.Namespace} + job, _, err := client.Jobs().Info(jobs[0].ID, q) if err != nil { c.Ui.Error(fmt.Sprintf("Error deregistering job: %s", err)) return 1 @@ -160,7 +162,8 @@ func (c *JobStopCommand) Run(args []string) int { } // Invoke the stop - evalID, _, err := client.Jobs().Deregister(*job.ID, purge, nil) + wq := &api.WriteOptions{Namespace: jobs[0].JobSummary.Namespace} + evalID, _, err := client.Jobs().Deregister(*job.ID, purge, wq) if err != nil { c.Ui.Error(fmt.Sprintf("Error deregistering job: %s", err)) return 1 diff --git a/command/meta.go b/command/meta.go index 363e666ce..1403f413c 100644 --- a/command/meta.go +++ b/command/meta.go @@ -113,7 +113,7 @@ type ApiClientFactory func() (*api.Client, error) // Client is used to initialize and return a new API client using // the default command line arguments and env vars. -func (m *Meta) Client() (*api.Client, error) { +func (m *Meta) clientConfig() *api.Config { config := api.DefaultConfig() if m.flagAddress != "" { config.Address = m.flagAddress @@ -142,7 +142,15 @@ func (m *Meta) Client() (*api.Client, error) { config.SecretID = m.token } - return api.NewClient(config) + return config +} + +func (m *Meta) Client() (*api.Client, error) { + return api.NewClient(m.clientConfig()) +} + +func (m *Meta) allNamespaces() bool { + return m.clientConfig().Namespace == api.AllNamespacesNamespace } func (m *Meta) Colorize() *colorstring.Colorize { diff --git a/vendor/github.com/hashicorp/nomad/api/api.go b/vendor/github.com/hashicorp/nomad/api/api.go index 9633efb0e..54ede4f89 100644 --- a/vendor/github.com/hashicorp/nomad/api/api.go +++ b/vendor/github.com/hashicorp/nomad/api/api.go @@ -28,6 +28,12 @@ var ( ClientConnTimeout = 1 * time.Second ) +const ( + // AllNamespacesNamespace is a sentinel Namespace value to indicate that api should search for + // jobs and allocations in all the namespaces the requester can access. + AllNamespacesNamespace = "*" +) + // QueryOptions are used to parametrize a query type QueryOptions struct { // Providing a datacenter overwrites the region provided