cli: jobs allow querying jobs in all namespaces

This commit is contained in:
Mahmood Ali 2020-06-17 15:39:50 -04:00
parent e784fe331a
commit 7a33a75449
11 changed files with 97 additions and 39 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

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

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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)
}

View File

@ -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

View File

@ -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 {

View File

@ -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