diff --git a/api/api.go b/api/api.go index 9633efb0ea3f..54ede4f89c64 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/api/jobs.go b/api/jobs.go index 7c1d66714f8f..735f2190cb64 100644 --- a/api/jobs.go +++ b/api/jobs.go @@ -142,13 +142,6 @@ func (j *Jobs) PrefixList(prefix string) ([]*JobListStub, *QueryMeta, error) { return j.List(&QueryOptions{Prefix: prefix}) } -// ListAll is used to list all of the existing jobs in all namespaces. -func (j *Jobs) ListAll() ([]*JobListStub, *QueryMeta, error) { - return j.List(&QueryOptions{ - Params: map[string]string{"all_namespaces": "true"}, - }) -} - // Info is used to retrieve information about a particular // job given its unique ID. func (j *Jobs) Info(jobID string, q *QueryOptions) (*Job, *QueryMeta, error) { diff --git a/command/agent/job_endpoint.go b/command/agent/job_endpoint.go index b2c6db3f9c61..f9ce348cf28a 100644 --- a/command/agent/job_endpoint.go +++ b/command/agent/job_endpoint.go @@ -31,7 +31,6 @@ func (s *HTTPServer) jobListRequest(resp http.ResponseWriter, req *http.Request) return nil, nil } - args.AllNamespaces, _ = strconv.ParseBool(req.URL.Query().Get("all_namespaces")) var out structs.JobListResponse if err := s.agent.RPC("Job.List", &args, &out); err != nil { return nil, err diff --git a/command/agent/job_endpoint_test.go b/command/agent/job_endpoint_test.go index 7d6f5f568be6..2d9b615d0185 100644 --- a/command/agent/job_endpoint_test.go +++ b/command/agent/job_endpoint_test.go @@ -146,7 +146,7 @@ func TestHTTP_JobsList_AllNamespaces_OSS(t *testing.T) { } // Make the HTTP request - req, err := http.NewRequest("GET", "/v1/jobs?all_namespaces=true", nil) + req, err := http.NewRequest("GET", "/v1/jobs?namespace=*", nil) require.NoError(t, err) respW := httptest.NewRecorder() diff --git a/command/alloc_exec.go b/command/alloc_exec.go index c19bca29a916..04b2dd45c307 100644 --- a/command/alloc_exec.go +++ b/command/alloc_exec.go @@ -183,7 +183,8 @@ func (l *AllocExecCommand) Run(args []string) int { return 1 } // Prefix lookup matched a single allocation - alloc, _, err := client.Allocations().Info(allocs[0].ID, nil) + q := &api.QueryOptions{Namespace: allocs[0].Namespace} + alloc, _, err := client.Allocations().Info(allocs[0].ID, q) if err != nil { l.Ui.Error(fmt.Sprintf("Error querying allocation: %s", err)) return 1 diff --git a/command/alloc_fs.go b/command/alloc_fs.go index debbbb5031a4..b7d5554d6f56 100644 --- a/command/alloc_fs.go +++ b/command/alloc_fs.go @@ -196,7 +196,8 @@ func (f *AllocFSCommand) Run(args []string) int { return 1 } // Prefix lookup matched a single allocation - alloc, _, err := client.Allocations().Info(allocs[0].ID, nil) + q := &api.QueryOptions{Namespace: allocs[0].Namespace} + alloc, _, err := client.Allocations().Info(allocs[0].ID, q) if err != nil { f.Ui.Error(fmt.Sprintf("Error querying allocation: %s", err)) return 1 diff --git a/command/alloc_logs.go b/command/alloc_logs.go index 5865f955bc53..6e916ca8e620 100644 --- a/command/alloc_logs.go +++ b/command/alloc_logs.go @@ -171,7 +171,8 @@ func (l *AllocLogsCommand) Run(args []string) int { return 1 } // Prefix lookup matched a single allocation - alloc, _, err := client.Allocations().Info(allocs[0].ID, nil) + q := &api.QueryOptions{Namespace: allocs[0].Namespace} + alloc, _, err := client.Allocations().Info(allocs[0].ID, q) if err != nil { l.Ui.Error(fmt.Sprintf("Error querying allocation: %s", err)) return 1 diff --git a/command/alloc_restart.go b/command/alloc_restart.go index b27b0f363090..bacd04efa6da 100644 --- a/command/alloc_restart.go +++ b/command/alloc_restart.go @@ -96,7 +96,8 @@ func (c *AllocRestartCommand) Run(args []string) int { } // Prefix lookup matched a single allocation - alloc, _, err := client.Allocations().Info(allocs[0].ID, nil) + q := &api.QueryOptions{Namespace: allocs[0].Namespace} + alloc, _, err := client.Allocations().Info(allocs[0].ID, q) if err != nil { c.Ui.Error(fmt.Sprintf("Error querying allocation: %s", err)) return 1 diff --git a/command/alloc_signal.go b/command/alloc_signal.go index bc33825a0dff..418108ef29f0 100644 --- a/command/alloc_signal.go +++ b/command/alloc_signal.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + "github.com/hashicorp/nomad/api" "github.com/hashicorp/nomad/api/contexts" "github.com/posener/complete" ) @@ -100,7 +101,8 @@ func (c *AllocSignalCommand) Run(args []string) int { } // Prefix lookup matched a single allocation - alloc, _, err := client.Allocations().Info(allocs[0].ID, nil) + q := &api.QueryOptions{Namespace: allocs[0].Namespace} + alloc, _, err := client.Allocations().Info(allocs[0].ID, q) if err != nil { c.Ui.Error(fmt.Sprintf("Error querying allocation: %s", err)) return 1 diff --git a/command/alloc_status.go b/command/alloc_status.go index 52e0faa41b1b..c22d23e53d7d 100644 --- a/command/alloc_status.go +++ b/command/alloc_status.go @@ -166,7 +166,8 @@ func (c *AllocStatusCommand) Run(args []string) int { return 0 } // Prefix lookup matched a single allocation - alloc, _, err := client.Allocations().Info(allocs[0].ID, nil) + q := &api.QueryOptions{Namespace: allocs[0].Namespace} + alloc, _, err := client.Allocations().Info(allocs[0].ID, q) if err != nil { c.Ui.Error(fmt.Sprintf("Error querying allocation: %s", err)) return 1 diff --git a/command/alloc_stop.go b/command/alloc_stop.go index a44842f69df4..37050b460405 100644 --- a/command/alloc_stop.go +++ b/command/alloc_stop.go @@ -3,6 +3,8 @@ package command import ( "fmt" "strings" + + "github.com/hashicorp/nomad/api" ) type AllocStopCommand struct { @@ -102,7 +104,8 @@ func (c *AllocStopCommand) Run(args []string) int { } // Prefix lookup matched a single allocation - alloc, _, err := client.Allocations().Info(allocs[0].ID, nil) + q := &api.QueryOptions{Namespace: allocs[0].Namespace} + alloc, _, err := client.Allocations().Info(allocs[0].ID, q) if err != nil { c.Ui.Error(fmt.Sprintf("Error querying allocation: %s", err)) return 1 diff --git a/command/job_deployments.go b/command/job_deployments.go index 39de2f94ddc0..6f7ffac2461c 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" ) @@ -118,11 +119,12 @@ func (c *JobDeploymentsCommand) Run(args []string) int { c.Ui.Error(fmt.Sprintf("No job(s) with prefix or id %q found", jobID)) 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))) + if len(jobs) > 1 && (c.allNamespaces() || strings.TrimSpace(jobID) != jobs[0].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} // 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 73508ac22dea..eeefaa6a68b3 100644 --- a/command/job_history.go +++ b/command/job_history.go @@ -130,13 +130,15 @@ func (c *JobHistoryCommand) Run(args []string) int { c.Ui.Error(fmt.Sprintf("No job(s) with prefix or id %q found", jobID)) 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))) + if len(jobs) > 1 && (c.allNamespaces() || strings.TrimSpace(jobID) != jobs[0].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} + // 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 b3de0f4bb5cc..b127e960b5e8 100644 --- a/command/job_inspect.go +++ b/command/job_inspect.go @@ -126,8 +126,8 @@ func (c *JobInspectCommand) Run(args []string) int { c.Ui.Error(fmt.Sprintf("No job(s) with prefix or id %q found", jobID)) 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))) + if len(jobs) > 1 && (c.allNamespaces() || strings.TrimSpace(jobID) != jobs[0].ID) { + 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 f11d3503192b..b98ea235516d 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 fe5426733bed..c05e701f1848 100644 --- a/command/job_promote.go +++ b/command/job_promote.go @@ -124,14 +124,15 @@ func (c *JobPromoteCommand) Run(args []string) int { c.Ui.Error(fmt.Sprintf("No job(s) with prefix or id %q found", jobID)) 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))) + if len(jobs) > 1 && (c.allNamespaces() || strings.TrimSpace(jobID) != jobs[0].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} // 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 ca3b0a3295b6..ece3f556aa7b 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" ) @@ -143,13 +144,14 @@ func (c *JobRevertCommand) Run(args []string) int { c.Ui.Error(fmt.Sprintf("No job(s) with prefix or id %q found", jobID)) 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))) + if len(jobs) > 1 && (c.allNamespaces() || strings.TrimSpace(jobID) != jobs[0].ID) { + 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 3dd505830990..994103f8a2fa 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 } @@ -151,12 +154,13 @@ func (c *JobStatusCommand) Run(args []string) int { c.Ui.Error(fmt.Sprintf("No job(s) with prefix or id %q found", jobID)) 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))) + if len(jobs) > 1 && (allNamespaces || strings.TrimSpace(jobID) != jobs[0].ID) { + 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 c6d088592a1a..911c614d007b 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" ) @@ -125,12 +126,13 @@ func (c *JobStopCommand) Run(args []string) int { c.Ui.Error(fmt.Sprintf("No job(s) with prefix or id %q found", jobID)) 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))) + if len(jobs) > 1 && (c.allNamespaces() || strings.TrimSpace(jobID) != jobs[0].ID) { + 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 363e666ceb98..1403f413cab3 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/nomad/alloc_endpoint.go b/nomad/alloc_endpoint.go index fb55c614cdec..0909c3907a63 100644 --- a/nomad/alloc_endpoint.go +++ b/nomad/alloc_endpoint.go @@ -30,7 +30,8 @@ func (a *Alloc) List(args *structs.AllocListRequest, reply *structs.AllocListRes defer metrics.MeasureSince([]string{"nomad", "alloc", "list"}, time.Now()) // Check namespace read-job permissions - if aclObj, err := a.srv.ResolveToken(args.AuthToken); err != nil { + aclObj, err := a.srv.ResolveToken(args.AuthToken) + if err != nil { return err } else if aclObj != nil && !aclObj.AllowNsOp(args.RequestNamespace(), acl.NamespaceCapabilityReadJob) { return structs.ErrPermissionDenied @@ -44,7 +45,18 @@ func (a *Alloc) List(args *structs.AllocListRequest, reply *structs.AllocListRes // Capture all the allocations var err error var iter memdb.ResultIterator - if prefix := args.QueryOptions.Prefix; prefix != "" { + + prefix := args.QueryOptions.Prefix + if args.RequestNamespace() == structs.AllNamespacesSentinel { + allowedNSes, err := allowedNSes(aclObj, state) + if err != nil { + return err + } + iter, err = state.AllocsByIDPrefixInNSes(ws, allowedNSes, prefix) + if err != nil { + return err + } + } else if prefix != "" { iter, err = state.AllocsByIDPrefix(ws, args.RequestNamespace(), prefix) } else { iter, err = state.AllocsByNamespace(ws, args.RequestNamespace()) diff --git a/nomad/job_endpoint.go b/nomad/job_endpoint.go index e706eb89ba9b..6cd89b540e6f 100644 --- a/nomad/job_endpoint.go +++ b/nomad/job_endpoint.go @@ -1183,7 +1183,7 @@ func (j *Job) GetJobVersions(args *structs.JobVersionsRequest, // allowedNSes returns a set (as map of ns->true) of the namespaces a token has access to. // Returns `nil` set if the token has access to all namespaces // and ErrPermissionDenied if the token has no capabilities on any namespace. -func (j *Job) allowedNSes(aclObj *acl.ACL, state *state.StateStore) (map[string]bool, error) { +func allowedNSes(aclObj *acl.ACL, state *state.StateStore) (map[string]bool, error) { if aclObj == nil || aclObj.IsManagement() { return nil, nil } @@ -1216,7 +1216,7 @@ func (j *Job) List(args *structs.JobListRequest, reply *structs.JobListResponse) } defer metrics.MeasureSince([]string{"nomad", "job", "list"}, time.Now()) - if args.AllNamespaces { + if args.RequestNamespace() == structs.AllNamespacesSentinel { return j.listAllNamespaces(args, reply) } @@ -1292,7 +1292,7 @@ func (j *Job) listAllNamespaces(args *structs.JobListRequest, reply *structs.Job queryMeta: &reply.QueryMeta, run: func(ws memdb.WatchSet, state *state.StateStore) error { // check if user has permission to all namespaces - allowedNSes, err := j.allowedNSes(aclObj, state) + allowedNSes, err := allowedNSes(aclObj, state) if err == structs.ErrPermissionDenied { // return empty jobs if token isn't authorized for any // namespace, matching other endpoints diff --git a/nomad/job_endpoint_test.go b/nomad/job_endpoint_test.go index 8f45afab1285..7df781ade34d 100644 --- a/nomad/job_endpoint_test.go +++ b/nomad/job_endpoint_test.go @@ -152,7 +152,7 @@ func TestJobEndpoint_Register_PreserveCounts(t *testing.T) { // Perform the update require.NoError(msgpackrpc.CallWithCodec(codec, "Job.Register", &structs.JobRegisterRequest{ - Job: job, + Job: job, PreserveCounts: true, WriteRequest: structs.WriteRequest{ Region: "global", @@ -165,10 +165,9 @@ func TestJobEndpoint_Register_PreserveCounts(t *testing.T) { require.NoError(err) require.NotNil(out) require.Equal(10, out.TaskGroups[0].Count) // should not change - require.Equal(2, out.TaskGroups[1].Count) // should be as in job spec + require.Equal(2, out.TaskGroups[1].Count) // should be as in job spec } - func TestJobEndpoint_Register_Connect(t *testing.T) { t.Parallel() require := require.New(t) @@ -3864,10 +3863,9 @@ func TestJobEndpoint_ListJobs_AllNamespaces_OSS(t *testing.T) { // Lookup the jobs get := &structs.JobListRequest{ - AllNamespaces: true, QueryOptions: structs.QueryOptions{ Region: "global", - Namespace: job.Namespace, + Namespace: "*", }, } var resp2 structs.JobListResponse @@ -3880,10 +3878,9 @@ func TestJobEndpoint_ListJobs_AllNamespaces_OSS(t *testing.T) { // Lookup the jobs by prefix get = &structs.JobListRequest{ - AllNamespaces: true, QueryOptions: structs.QueryOptions{ Region: "global", - Namespace: job.Namespace, + Namespace: "*", Prefix: resp2.Jobs[0].ID[:4], }, } @@ -3897,10 +3894,9 @@ func TestJobEndpoint_ListJobs_AllNamespaces_OSS(t *testing.T) { // Lookup the jobs by prefix get = &structs.JobListRequest{ - AllNamespaces: true, QueryOptions: structs.QueryOptions{ Region: "global", - Namespace: job.Namespace, + Namespace: "*", Prefix: "z" + resp2.Jobs[0].ID[:4], }, } diff --git a/nomad/state/state_store.go b/nomad/state/state_store.go index 757d84f3bbb7..9827fdffdc8b 100644 --- a/nomad/state/state_store.go +++ b/nomad/state/state_store.go @@ -3146,6 +3146,38 @@ func allocNamespaceFilter(namespace string) func(interface{}) bool { } } +// AllocsByIDPrefix is used to lookup allocs by prefix +func (s *StateStore) AllocsByIDPrefixInNSes(ws memdb.WatchSet, namespaces map[string]bool, prefix string) (memdb.ResultIterator, error) { + txn := s.db.Txn(false) + + var iter memdb.ResultIterator + var err error + if prefix != "" { + iter, err = txn.Get("allocs", "id_prefix", prefix) + } else { + iter, err = txn.Get("allocs", "id") + + } + if err != nil { + return nil, fmt.Errorf("alloc lookup failed: %v", err) + } + + ws.Add(iter.WatchCh()) + + // Wrap the iterator in a filter + nsesFilter := func(raw interface{}) bool { + alloc, ok := raw.(*structs.Allocation) + if !ok { + return true + } + + return namespaces[alloc.Namespace] + } + + wrap := memdb.NewFilterIterator(iter, nsesFilter) + return wrap, nil +} + // AllocsByNode returns all the allocations by node func (s *StateStore) AllocsByNode(ws memdb.WatchSet, node string) ([]*structs.Allocation, error) { txn := s.db.Txn(false) diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index 875ebb826548..c6b8f6ddd5db 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -137,6 +137,10 @@ const ( DefaultNamespace = "default" DefaultNamespaceDescription = "Default shared namespace" + // AllNamespacesSentinel is the value used as a namespace RPC value + // to indicate that endpoints must search in all namespaces + AllNamespacesSentinel = "*" + // JitterFraction is a the limit to the amount of jitter we apply // to a user specified MaxQueryTime. We divide the specified time by // the fraction. So 16 == 6.25% limit of jitter. This jitter is also @@ -614,7 +618,6 @@ type JobSpecificRequest struct { // JobListRequest is used to parameterize a list request type JobListRequest struct { - AllNamespaces bool QueryOptions } diff --git a/vendor/github.com/hashicorp/nomad/api/api.go b/vendor/github.com/hashicorp/nomad/api/api.go index 9633efb0ea3f..54ede4f89c64 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 diff --git a/vendor/github.com/hashicorp/nomad/api/jobs.go b/vendor/github.com/hashicorp/nomad/api/jobs.go index 7c1d66714f8f..735f2190cb64 100644 --- a/vendor/github.com/hashicorp/nomad/api/jobs.go +++ b/vendor/github.com/hashicorp/nomad/api/jobs.go @@ -142,13 +142,6 @@ func (j *Jobs) PrefixList(prefix string) ([]*JobListStub, *QueryMeta, error) { return j.List(&QueryOptions{Prefix: prefix}) } -// ListAll is used to list all of the existing jobs in all namespaces. -func (j *Jobs) ListAll() ([]*JobListStub, *QueryMeta, error) { - return j.List(&QueryOptions{ - Params: map[string]string{"all_namespaces": "true"}, - }) -} - // Info is used to retrieve information about a particular // job given its unique ID. func (j *Jobs) Info(jobID string, q *QueryOptions) (*Job, *QueryMeta, error) { diff --git a/website/pages/api-docs/jobs.mdx b/website/pages/api-docs/jobs.mdx index 2c4bc1f5ee84..8b5cccd82c3e 100644 --- a/website/pages/api-docs/jobs.mdx +++ b/website/pages/api-docs/jobs.mdx @@ -30,10 +30,6 @@ The table below shows this endpoint's support for - `prefix` `(string: "")` - Specifies a string to filter jobs on based on an index prefix. This is specified as a query string parameter. -- `all_namespaces` `(bool: false)` - Specifies whether to return the all - known jobs across all namespaces the token has `namespace:list-jobs` ACL - capability on. - ### Sample Request ```shell-session