github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/command/job_status.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 "time" 8 9 "github.com/hashicorp/nomad/api" 10 "github.com/hashicorp/nomad/api/contexts" 11 "github.com/hashicorp/nomad/nomad/structs" 12 "github.com/posener/complete" 13 ) 14 15 const ( 16 // maxFailedTGs is the maximum number of task groups we show failure reasons 17 // for before deferring to eval-status 18 maxFailedTGs = 5 19 ) 20 21 type JobStatusCommand struct { 22 Meta 23 length int 24 evals bool 25 allAllocs bool 26 verbose bool 27 } 28 29 func (c *JobStatusCommand) Help() string { 30 helpText := ` 31 Usage: nomad status [options] <job> 32 33 Display status information about a job. If no job ID is given, a list of all 34 known jobs will be displayed. 35 36 When ACLs are enabled, this command requires a token with the 'read-job' and 37 'list-jobs' capabilities for the job's namespace. 38 39 General Options: 40 41 ` + generalOptionsUsage(usageOptsDefault) + ` 42 43 Status Options: 44 45 -short 46 Display short output. Used only when a single job is being 47 queried, and drops verbose information about allocations. 48 49 -evals 50 Display the evaluations associated with the job. 51 52 -all-allocs 53 Display all allocations matching the job ID, including those from an older 54 instance of the job. 55 56 -verbose 57 Display full information. 58 ` 59 return strings.TrimSpace(helpText) 60 } 61 62 func (c *JobStatusCommand) Synopsis() string { 63 return "Display status information about a job" 64 } 65 66 func (c *JobStatusCommand) AutocompleteFlags() complete.Flags { 67 return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient), 68 complete.Flags{ 69 "-all-allocs": complete.PredictNothing, 70 "-evals": complete.PredictNothing, 71 "-short": complete.PredictNothing, 72 "-verbose": complete.PredictNothing, 73 }) 74 } 75 76 func (c *JobStatusCommand) AutocompleteArgs() complete.Predictor { 77 return complete.PredictFunc(func(a complete.Args) []string { 78 client, err := c.Meta.Client() 79 if err != nil { 80 return nil 81 } 82 83 resp, _, err := client.Search().PrefixSearch(a.Last, contexts.Jobs, nil) 84 if err != nil { 85 return []string{} 86 } 87 return resp.Matches[contexts.Jobs] 88 }) 89 } 90 91 func (c *JobStatusCommand) Name() string { return "status" } 92 93 func (c *JobStatusCommand) Run(args []string) int { 94 var short bool 95 96 flags := c.Meta.FlagSet(c.Name(), FlagSetClient) 97 flags.Usage = func() { c.Ui.Output(c.Help()) } 98 flags.BoolVar(&short, "short", false, "") 99 flags.BoolVar(&c.evals, "evals", false, "") 100 flags.BoolVar(&c.allAllocs, "all-allocs", false, "") 101 flags.BoolVar(&c.verbose, "verbose", false, "") 102 103 if err := flags.Parse(args); err != nil { 104 return 1 105 } 106 107 // Check that we either got no jobs or exactly one. 108 args = flags.Args() 109 if len(args) > 1 { 110 c.Ui.Error("This command takes either no arguments or one: <job>") 111 c.Ui.Error(commandErrorText(c)) 112 return 1 113 } 114 115 // Truncate the id unless full length is requested 116 c.length = shortId 117 if c.verbose { 118 c.length = fullId 119 } 120 121 // Get the HTTP client 122 client, err := c.Meta.Client() 123 if err != nil { 124 c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) 125 return 1 126 } 127 128 allNamespaces := c.allNamespaces() 129 130 // Invoke list mode if no job ID. 131 if len(args) == 0 { 132 jobs, _, err := client.Jobs().List(nil) 133 134 if err != nil { 135 c.Ui.Error(fmt.Sprintf("Error querying jobs: %s", err)) 136 return 1 137 } 138 139 if len(jobs) == 0 { 140 // No output if we have no jobs 141 c.Ui.Output("No running jobs") 142 } else { 143 c.Ui.Output(createStatusListOutput(jobs, allNamespaces)) 144 } 145 return 0 146 } 147 148 // Try querying the job 149 jobID := args[0] 150 151 jobs, _, err := client.Jobs().PrefixList(jobID) 152 if err != nil { 153 c.Ui.Error(fmt.Sprintf("Error querying job: %s", err)) 154 return 1 155 } 156 if len(jobs) == 0 { 157 c.Ui.Error(fmt.Sprintf("No job(s) with prefix or id %q found", jobID)) 158 return 1 159 } 160 if len(jobs) > 1 && (allNamespaces || strings.TrimSpace(jobID) != jobs[0].ID) { 161 c.Ui.Error(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", createStatusListOutput(jobs, allNamespaces))) 162 return 1 163 } 164 // Prefix lookup matched a single job 165 q := &api.QueryOptions{Namespace: jobs[0].JobSummary.Namespace} 166 job, _, err := client.Jobs().Info(jobs[0].ID, q) 167 if err != nil { 168 c.Ui.Error(fmt.Sprintf("Error querying job: %s", err)) 169 return 1 170 } 171 172 periodic := job.IsPeriodic() 173 parameterized := job.IsParameterized() 174 175 // Format the job info 176 basic := []string{ 177 fmt.Sprintf("ID|%s", *job.ID), 178 fmt.Sprintf("Name|%s", *job.Name), 179 fmt.Sprintf("Submit Date|%s", formatTime(time.Unix(0, *job.SubmitTime))), 180 fmt.Sprintf("Type|%s", *job.Type), 181 fmt.Sprintf("Priority|%d", *job.Priority), 182 fmt.Sprintf("Datacenters|%s", strings.Join(job.Datacenters, ",")), 183 fmt.Sprintf("Namespace|%s", *job.Namespace), 184 fmt.Sprintf("Status|%s", getStatusString(*job.Status, job.Stop)), 185 fmt.Sprintf("Periodic|%v", periodic), 186 fmt.Sprintf("Parameterized|%v", parameterized), 187 } 188 189 if periodic && !parameterized { 190 if *job.Stop { 191 basic = append(basic, "Next Periodic Launch|none (job stopped)") 192 } else { 193 location, err := job.Periodic.GetLocation() 194 if err == nil { 195 now := time.Now().In(location) 196 next, err := job.Periodic.Next(now) 197 if err == nil { 198 basic = append(basic, fmt.Sprintf("Next Periodic Launch|%s", 199 fmt.Sprintf("%s (%s from now)", 200 formatTime(next), formatTimeDifference(now, next, time.Second)))) 201 } 202 } 203 } 204 } 205 206 c.Ui.Output(formatKV(basic)) 207 208 // Exit early 209 if short { 210 return 0 211 } 212 213 // Print periodic job information 214 if periodic && !parameterized { 215 if err := c.outputPeriodicInfo(client, job); err != nil { 216 c.Ui.Error(err.Error()) 217 return 1 218 } 219 } else if parameterized { 220 if err := c.outputParameterizedInfo(client, job); err != nil { 221 c.Ui.Error(err.Error()) 222 return 1 223 } 224 } else { 225 if err := c.outputJobInfo(client, job); err != nil { 226 c.Ui.Error(err.Error()) 227 return 1 228 } 229 } 230 231 return 0 232 } 233 234 // outputPeriodicInfo prints information about the passed periodic job. If a 235 // request fails, an error is returned. 236 func (c *JobStatusCommand) outputPeriodicInfo(client *api.Client, job *api.Job) error { 237 // Output the summary 238 if err := c.outputJobSummary(client, job); err != nil { 239 return err 240 } 241 242 // Generate the prefix that matches launched jobs from the periodic job. 243 prefix := fmt.Sprintf("%s%s", *job.ID, structs.PeriodicLaunchSuffix) 244 children, _, err := client.Jobs().PrefixList(prefix) 245 if err != nil { 246 return fmt.Errorf("Error querying job: %s", err) 247 } 248 249 if len(children) == 0 { 250 c.Ui.Output("\nNo instances of periodic job found") 251 return nil 252 } 253 254 out := make([]string, 1) 255 out[0] = "ID|Status" 256 for _, child := range children { 257 // Ensure that we are only showing jobs whose parent is the requested 258 // job. 259 if child.ParentID != *job.ID { 260 continue 261 } 262 263 out = append(out, fmt.Sprintf("%s|%s", 264 child.ID, 265 child.Status)) 266 } 267 268 c.Ui.Output(c.Colorize().Color("\n[bold]Previously Launched Jobs[reset]")) 269 c.Ui.Output(formatList(out)) 270 return nil 271 } 272 273 // outputParameterizedInfo prints information about a parameterized job. If a 274 // request fails, an error is returned. 275 func (c *JobStatusCommand) outputParameterizedInfo(client *api.Client, job *api.Job) error { 276 // Output parameterized job details 277 c.Ui.Output(c.Colorize().Color("\n[bold]Parameterized Job[reset]")) 278 parameterizedJob := make([]string, 3) 279 parameterizedJob[0] = fmt.Sprintf("Payload|%s", job.ParameterizedJob.Payload) 280 parameterizedJob[1] = fmt.Sprintf("Required Metadata|%v", strings.Join(job.ParameterizedJob.MetaRequired, ", ")) 281 parameterizedJob[2] = fmt.Sprintf("Optional Metadata|%v", strings.Join(job.ParameterizedJob.MetaOptional, ", ")) 282 c.Ui.Output(formatKV(parameterizedJob)) 283 284 // Output the summary 285 if err := c.outputJobSummary(client, job); err != nil { 286 return err 287 } 288 289 // Generate the prefix that matches launched jobs from the parameterized job. 290 prefix := fmt.Sprintf("%s%s", *job.ID, structs.DispatchLaunchSuffix) 291 children, _, err := client.Jobs().PrefixList(prefix) 292 if err != nil { 293 return fmt.Errorf("Error querying job: %s", err) 294 } 295 296 if len(children) == 0 { 297 c.Ui.Output("\nNo dispatched instances of parameterized job found") 298 return nil 299 } 300 301 out := make([]string, 1) 302 out[0] = "ID|Status" 303 for _, child := range children { 304 // Ensure that we are only showing jobs whose parent is the requested 305 // job. 306 if child.ParentID != *job.ID { 307 continue 308 } 309 310 out = append(out, fmt.Sprintf("%s|%s", 311 child.ID, 312 child.Status)) 313 } 314 315 c.Ui.Output(c.Colorize().Color("\n[bold]Dispatched Jobs[reset]")) 316 c.Ui.Output(formatList(out)) 317 return nil 318 } 319 320 // outputJobInfo prints information about the passed non-periodic job. If a 321 // request fails, an error is returned. 322 func (c *JobStatusCommand) outputJobInfo(client *api.Client, job *api.Job) error { 323 var q *api.QueryOptions 324 if job.Namespace != nil { 325 q = &api.QueryOptions{Namespace: *job.Namespace} 326 } 327 328 // Query the allocations 329 jobAllocs, _, err := client.Jobs().Allocations(*job.ID, c.allAllocs, q) 330 if err != nil { 331 return fmt.Errorf("Error querying job allocations: %s", err) 332 } 333 334 // Query the evaluations 335 jobEvals, _, err := client.Jobs().Evaluations(*job.ID, q) 336 if err != nil { 337 return fmt.Errorf("Error querying job evaluations: %s", err) 338 } 339 340 latestDeployment, _, err := client.Jobs().LatestDeployment(*job.ID, q) 341 if err != nil { 342 return fmt.Errorf("Error querying latest job deployment: %s", err) 343 } 344 345 // Output the summary 346 if err := c.outputJobSummary(client, job); err != nil { 347 return err 348 } 349 350 // Determine latest evaluation with failures whose follow up hasn't 351 // completed, this is done while formatting 352 var latestFailedPlacement *api.Evaluation 353 blockedEval := false 354 355 // Format the evals 356 evals := make([]string, len(jobEvals)+1) 357 evals[0] = "ID|Priority|Triggered By|Status|Placement Failures" 358 for i, eval := range jobEvals { 359 failures, _ := evalFailureStatus(eval) 360 evals[i+1] = fmt.Sprintf("%s|%d|%s|%s|%s", 361 limit(eval.ID, c.length), 362 eval.Priority, 363 eval.TriggeredBy, 364 eval.Status, 365 failures, 366 ) 367 368 if eval.Status == "blocked" { 369 blockedEval = true 370 } 371 372 if len(eval.FailedTGAllocs) == 0 { 373 // Skip evals without failures 374 continue 375 } 376 377 if latestFailedPlacement == nil || latestFailedPlacement.CreateIndex < eval.CreateIndex { 378 latestFailedPlacement = eval 379 } 380 } 381 382 if c.verbose || c.evals { 383 c.Ui.Output(c.Colorize().Color("\n[bold]Evaluations[reset]")) 384 c.Ui.Output(formatList(evals)) 385 } 386 387 if blockedEval && latestFailedPlacement != nil { 388 c.outputFailedPlacements(latestFailedPlacement) 389 } 390 391 c.outputReschedulingEvals(client, job, jobAllocs, c.length) 392 393 if latestDeployment != nil { 394 c.Ui.Output(c.Colorize().Color("\n[bold]Latest Deployment[reset]")) 395 c.Ui.Output(c.Colorize().Color(c.formatDeployment(client, latestDeployment))) 396 } 397 398 // Format the allocs 399 c.Ui.Output(c.Colorize().Color("\n[bold]Allocations[reset]")) 400 c.Ui.Output(formatAllocListStubs(jobAllocs, c.verbose, c.length)) 401 return nil 402 } 403 404 func (c *JobStatusCommand) formatDeployment(client *api.Client, d *api.Deployment) string { 405 // Format the high-level elements 406 high := []string{ 407 fmt.Sprintf("ID|%s", limit(d.ID, c.length)), 408 fmt.Sprintf("Status|%s", d.Status), 409 fmt.Sprintf("Description|%s", d.StatusDescription), 410 } 411 412 base := formatKV(high) 413 414 if d.IsMultiregion { 415 regions, err := fetchMultiRegionDeployments(client, d) 416 if err != nil { 417 base += "\n\nError fetching Multiregion deployments\n\n" 418 } else if len(regions) > 0 { 419 base += "\n\n[bold]Multiregion Deployment[reset]\n" 420 base += formatMultiregionDeployment(regions, 8) 421 } 422 } 423 424 if len(d.TaskGroups) == 0 { 425 return base 426 } 427 base += "\n\n[bold]Deployed[reset]\n" 428 base += formatDeploymentGroups(d, c.length) 429 return base 430 } 431 432 func formatAllocListStubs(stubs []*api.AllocationListStub, verbose bool, uuidLength int) string { 433 if len(stubs) == 0 { 434 return "No allocations placed" 435 } 436 437 allocs := make([]string, len(stubs)+1) 438 if verbose { 439 allocs[0] = "ID|Eval ID|Node ID|Node Name|Task Group|Version|Desired|Status|Created|Modified" 440 for i, alloc := range stubs { 441 allocs[i+1] = fmt.Sprintf("%s|%s|%s|%s|%s|%d|%s|%s|%s|%s", 442 limit(alloc.ID, uuidLength), 443 limit(alloc.EvalID, uuidLength), 444 limit(alloc.NodeID, uuidLength), 445 alloc.NodeName, 446 alloc.TaskGroup, 447 alloc.JobVersion, 448 alloc.DesiredStatus, 449 alloc.ClientStatus, 450 formatUnixNanoTime(alloc.CreateTime), 451 formatUnixNanoTime(alloc.ModifyTime)) 452 } 453 } else { 454 allocs[0] = "ID|Node ID|Task Group|Version|Desired|Status|Created|Modified" 455 for i, alloc := range stubs { 456 now := time.Now() 457 createTimePretty := prettyTimeDiff(time.Unix(0, alloc.CreateTime), now) 458 modTimePretty := prettyTimeDiff(time.Unix(0, alloc.ModifyTime), now) 459 allocs[i+1] = fmt.Sprintf("%s|%s|%s|%d|%s|%s|%s|%s", 460 limit(alloc.ID, uuidLength), 461 limit(alloc.NodeID, uuidLength), 462 alloc.TaskGroup, 463 alloc.JobVersion, 464 alloc.DesiredStatus, 465 alloc.ClientStatus, 466 createTimePretty, 467 modTimePretty) 468 } 469 } 470 471 return formatList(allocs) 472 } 473 474 func formatAllocList(allocations []*api.Allocation, verbose bool, uuidLength int) string { 475 if len(allocations) == 0 { 476 return "No allocations placed" 477 } 478 479 allocs := make([]string, len(allocations)+1) 480 if verbose { 481 allocs[0] = "ID|Eval ID|Node ID|Task Group|Version|Desired|Status|Created|Modified" 482 for i, alloc := range allocations { 483 allocs[i+1] = fmt.Sprintf("%s|%s|%s|%s|%d|%s|%s|%s|%s", 484 limit(alloc.ID, uuidLength), 485 limit(alloc.EvalID, uuidLength), 486 limit(alloc.NodeID, uuidLength), 487 alloc.TaskGroup, 488 *alloc.Job.Version, 489 alloc.DesiredStatus, 490 alloc.ClientStatus, 491 formatUnixNanoTime(alloc.CreateTime), 492 formatUnixNanoTime(alloc.ModifyTime)) 493 } 494 } else { 495 allocs[0] = "ID|Node ID|Task Group|Version|Desired|Status|Created|Modified" 496 for i, alloc := range allocations { 497 now := time.Now() 498 createTimePretty := prettyTimeDiff(time.Unix(0, alloc.CreateTime), now) 499 modTimePretty := prettyTimeDiff(time.Unix(0, alloc.ModifyTime), now) 500 allocs[i+1] = fmt.Sprintf("%s|%s|%s|%d|%s|%s|%s|%s", 501 limit(alloc.ID, uuidLength), 502 limit(alloc.NodeID, uuidLength), 503 alloc.TaskGroup, 504 *alloc.Job.Version, 505 alloc.DesiredStatus, 506 alloc.ClientStatus, 507 createTimePretty, 508 modTimePretty) 509 } 510 } 511 512 return formatList(allocs) 513 } 514 515 // outputJobSummary displays the given jobs summary and children job summary 516 // where appropriate 517 func (c *JobStatusCommand) outputJobSummary(client *api.Client, job *api.Job) error { 518 // Query the summary 519 q := &api.QueryOptions{Namespace: *job.Namespace} 520 summary, _, err := client.Jobs().Summary(*job.ID, q) 521 if err != nil { 522 return fmt.Errorf("Error querying job summary: %s", err) 523 } 524 525 if summary == nil { 526 return nil 527 } 528 529 periodic := job.IsPeriodic() 530 parameterizedJob := job.IsParameterized() 531 532 // Print the summary 533 if !periodic && !parameterizedJob { 534 c.Ui.Output(c.Colorize().Color("\n[bold]Summary[reset]")) 535 summaries := make([]string, len(summary.Summary)+1) 536 summaries[0] = "Task Group|Queued|Starting|Running|Failed|Complete|Lost" 537 taskGroups := make([]string, 0, len(summary.Summary)) 538 for taskGroup := range summary.Summary { 539 taskGroups = append(taskGroups, taskGroup) 540 } 541 sort.Strings(taskGroups) 542 for idx, taskGroup := range taskGroups { 543 tgs := summary.Summary[taskGroup] 544 summaries[idx+1] = fmt.Sprintf("%s|%d|%d|%d|%d|%d|%d", 545 taskGroup, tgs.Queued, tgs.Starting, 546 tgs.Running, tgs.Failed, 547 tgs.Complete, tgs.Lost, 548 ) 549 } 550 c.Ui.Output(formatList(summaries)) 551 } 552 553 // Always display the summary if we are periodic or parameterized, but 554 // only display if the summary is non-zero on normal jobs 555 if summary.Children != nil && (parameterizedJob || periodic || summary.Children.Sum() > 0) { 556 if parameterizedJob { 557 c.Ui.Output(c.Colorize().Color("\n[bold]Parameterized Job Summary[reset]")) 558 } else { 559 c.Ui.Output(c.Colorize().Color("\n[bold]Children Job Summary[reset]")) 560 } 561 summaries := make([]string, 2) 562 summaries[0] = "Pending|Running|Dead" 563 summaries[1] = fmt.Sprintf("%d|%d|%d", 564 summary.Children.Pending, summary.Children.Running, summary.Children.Dead) 565 c.Ui.Output(formatList(summaries)) 566 } 567 568 return nil 569 } 570 571 // outputReschedulingEvals displays eval IDs and time for any 572 // delayed evaluations by task group 573 func (c *JobStatusCommand) outputReschedulingEvals(client *api.Client, job *api.Job, allocListStubs []*api.AllocationListStub, uuidLength int) error { 574 // Get the most recent alloc ID by task group 575 576 mostRecentAllocs := make(map[string]*api.AllocationListStub) 577 for _, alloc := range allocListStubs { 578 a, ok := mostRecentAllocs[alloc.TaskGroup] 579 if !ok || alloc.ModifyTime > a.ModifyTime { 580 mostRecentAllocs[alloc.TaskGroup] = alloc 581 } 582 } 583 584 followUpEvalIds := make(map[string]string) 585 for tg, alloc := range mostRecentAllocs { 586 if alloc.FollowupEvalID != "" { 587 followUpEvalIds[tg] = alloc.FollowupEvalID 588 } 589 } 590 591 if len(followUpEvalIds) == 0 { 592 return nil 593 } 594 // Print the reschedule info section 595 var delayedEvalInfos []string 596 597 taskGroups := make([]string, 0, len(followUpEvalIds)) 598 for taskGroup := range followUpEvalIds { 599 taskGroups = append(taskGroups, taskGroup) 600 } 601 sort.Strings(taskGroups) 602 var evalDetails []string 603 first := true 604 for _, taskGroup := range taskGroups { 605 evalID := followUpEvalIds[taskGroup] 606 evaluation, _, err := client.Evaluations().Info(evalID, nil) 607 // Eval time is not critical output, 608 // so don't return it on errors, if its not set, or its already in the past 609 if err != nil || evaluation.WaitUntil.IsZero() || time.Now().After(evaluation.WaitUntil) { 610 continue 611 } 612 evalTime := prettyTimeDiff(evaluation.WaitUntil, time.Now()) 613 if c.verbose { 614 if first { 615 delayedEvalInfos = append(delayedEvalInfos, "Task Group|Reschedule Policy|Eval ID|Eval Time") 616 } 617 rp := job.LookupTaskGroup(taskGroup).ReschedulePolicy 618 evalDetails = append(evalDetails, fmt.Sprintf("%s|%s|%s|%s", taskGroup, rp.String(), limit(evalID, uuidLength), evalTime)) 619 } else { 620 if first { 621 delayedEvalInfos = append(delayedEvalInfos, "Task Group|Eval ID|Eval Time") 622 } 623 evalDetails = append(evalDetails, fmt.Sprintf("%s|%s|%s", taskGroup, limit(evalID, uuidLength), evalTime)) 624 } 625 first = false 626 } 627 if len(evalDetails) == 0 { 628 return nil 629 } 630 // Only show this section if there is pending evals 631 delayedEvalInfos = append(delayedEvalInfos, evalDetails...) 632 c.Ui.Output(c.Colorize().Color("\n[bold]Future Rescheduling Attempts[reset]")) 633 c.Ui.Output(formatList(delayedEvalInfos)) 634 return nil 635 } 636 637 func (c *JobStatusCommand) outputFailedPlacements(failedEval *api.Evaluation) { 638 if failedEval == nil || len(failedEval.FailedTGAllocs) == 0 { 639 return 640 } 641 642 c.Ui.Output(c.Colorize().Color("\n[bold]Placement Failure[reset]")) 643 644 sorted := sortedTaskGroupFromMetrics(failedEval.FailedTGAllocs) 645 for i, tg := range sorted { 646 if i >= maxFailedTGs { 647 break 648 } 649 650 c.Ui.Output(fmt.Sprintf("Task Group %q:", tg)) 651 metrics := failedEval.FailedTGAllocs[tg] 652 c.Ui.Output(formatAllocMetrics(metrics, false, " ")) 653 if i != len(sorted)-1 { 654 c.Ui.Output("") 655 } 656 } 657 658 if len(sorted) > maxFailedTGs { 659 trunc := fmt.Sprintf("\nPlacement failures truncated. To see remainder run:\nnomad eval-status %s", failedEval.ID) 660 c.Ui.Output(trunc) 661 } 662 } 663 664 // list general information about a list of jobs 665 func createStatusListOutput(jobs []*api.JobListStub, displayNS bool) string { 666 out := make([]string, len(jobs)+1) 667 if displayNS { 668 out[0] = "ID|Namespace|Type|Priority|Status|Submit Date" 669 for i, job := range jobs { 670 out[i+1] = fmt.Sprintf("%s|%s|%s|%d|%s|%s", 671 job.ID, 672 job.JobSummary.Namespace, 673 getTypeString(job), 674 job.Priority, 675 getStatusString(job.Status, &job.Stop), 676 formatTime(time.Unix(0, job.SubmitTime))) 677 } 678 } else { 679 out[0] = "ID|Type|Priority|Status|Submit Date" 680 for i, job := range jobs { 681 out[i+1] = fmt.Sprintf("%s|%s|%d|%s|%s", 682 job.ID, 683 getTypeString(job), 684 job.Priority, 685 getStatusString(job.Status, &job.Stop), 686 formatTime(time.Unix(0, job.SubmitTime))) 687 } 688 } 689 return formatList(out) 690 } 691 692 func getTypeString(job *api.JobListStub) string { 693 t := job.Type 694 695 if job.Periodic { 696 t += "/periodic" 697 } 698 699 if job.ParameterizedJob { 700 t += "/parameterized" 701 } 702 703 return t 704 } 705 706 func getStatusString(status string, stop *bool) string { 707 if stop != nil && *stop { 708 return fmt.Sprintf("%s (stopped)", status) 709 } 710 return status 711 }