github.com/hashicorp/nomad/api@v0.0.0-20240306165712-3193ac204f65/jobs.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package api 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "io" 11 "net/url" 12 "sort" 13 "strconv" 14 "time" 15 16 "github.com/hashicorp/cronexpr" 17 "golang.org/x/exp/maps" 18 ) 19 20 const ( 21 // JobTypeService indicates a long-running processes 22 JobTypeService = "service" 23 24 // JobTypeBatch indicates a short-lived process 25 JobTypeBatch = "batch" 26 27 // JobTypeSystem indicates a system process that should run on all clients 28 JobTypeSystem = "system" 29 30 // JobTypeSysbatch indicates a short-lived system process that should run 31 // on all clients. 32 JobTypeSysbatch = "sysbatch" 33 34 // JobDefaultPriority is the default priority if not specified. 35 JobDefaultPriority = 50 36 37 // PeriodicSpecCron is used for a cron spec. 38 PeriodicSpecCron = "cron" 39 40 // DefaultNamespace is the default namespace. 41 DefaultNamespace = "default" 42 43 // For Job configuration, GlobalRegion is a sentinel region value 44 // that users may specify to indicate the job should be run on 45 // the region of the node that the job was submitted to. 46 // For Client configuration, if no region information is given, 47 // the client node will default to be part of the GlobalRegion. 48 GlobalRegion = "global" 49 ) 50 51 const ( 52 // RegisterEnforceIndexErrPrefix is the prefix to use in errors caused by 53 // enforcing the job modify index during registers. 54 RegisterEnforceIndexErrPrefix = "Enforcing job modify index" 55 ) 56 57 const ( 58 // JobPeriodicLaunchSuffix is the string appended to the periodic jobs ID 59 // when launching derived instances of it. 60 JobPeriodicLaunchSuffix = "/periodic-" 61 62 // JobDispatchLaunchSuffix is the string appended to the parameterized job's ID 63 // when dispatching instances of it. 64 JobDispatchLaunchSuffix = "/dispatch-" 65 ) 66 67 // Jobs is used to access the job-specific endpoints. 68 type Jobs struct { 69 client *Client 70 } 71 72 // JobsParseRequest is used for arguments of the /v1/jobs/parse endpoint 73 type JobsParseRequest struct { 74 // JobHCL is an hcl jobspec 75 JobHCL string 76 77 // HCLv1 indicates whether the JobHCL should be parsed with the hcl v1 parser 78 HCLv1 bool `json:"hclv1,omitempty"` 79 80 // Variables are HCL2 variables associated with the job. Only works with hcl2. 81 // 82 // Interpreted as if it were the content of a variables file. 83 Variables string 84 85 // Canonicalize is a flag as to if the server should return default values 86 // for unset fields 87 Canonicalize bool 88 } 89 90 // Jobs returns a handle on the jobs endpoints. 91 func (c *Client) Jobs() *Jobs { 92 return &Jobs{client: c} 93 } 94 95 // ParseHCL is used to convert the HCL representation of a Job to JSON server side. 96 // To parse the HCL client side see package github.com/hashicorp/nomad/jobspec 97 // Use ParseHCLOpts if you need to customize JobsParseRequest. 98 func (j *Jobs) ParseHCL(jobHCL string, canonicalize bool) (*Job, error) { 99 req := &JobsParseRequest{ 100 JobHCL: jobHCL, 101 Canonicalize: canonicalize, 102 } 103 return j.ParseHCLOpts(req) 104 } 105 106 // ParseHCLOpts is used to request the server convert the HCL representation of a 107 // Job to JSON on our behalf. Accepts HCL1 or HCL2 jobs as input. 108 func (j *Jobs) ParseHCLOpts(req *JobsParseRequest) (*Job, error) { 109 var job Job 110 _, err := j.client.put("/v1/jobs/parse", req, &job, nil) 111 return &job, err 112 } 113 114 func (j *Jobs) Validate(job *Job, q *WriteOptions) (*JobValidateResponse, *WriteMeta, error) { 115 var resp JobValidateResponse 116 req := &JobValidateRequest{Job: job} 117 if q != nil { 118 req.WriteRequest = WriteRequest{Region: q.Region} 119 } 120 wm, err := j.client.put("/v1/validate/job", req, &resp, q) 121 return &resp, wm, err 122 } 123 124 // RegisterOptions is used to pass through job registration parameters 125 type RegisterOptions struct { 126 EnforceIndex bool 127 ModifyIndex uint64 128 PolicyOverride bool 129 PreserveCounts bool 130 EvalPriority int 131 Submission *JobSubmission 132 } 133 134 // Register is used to register a new job. It returns the ID 135 // of the evaluation, along with any errors encountered. 136 func (j *Jobs) Register(job *Job, q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) { 137 return j.RegisterOpts(job, nil, q) 138 } 139 140 // EnforceRegister is used to register a job enforcing its job modify index. 141 func (j *Jobs) EnforceRegister(job *Job, modifyIndex uint64, q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) { 142 opts := RegisterOptions{EnforceIndex: true, ModifyIndex: modifyIndex} 143 return j.RegisterOpts(job, &opts, q) 144 } 145 146 // RegisterOpts is used to register a new job with the passed RegisterOpts. It 147 // returns the ID of the evaluation, along with any errors encountered. 148 func (j *Jobs) RegisterOpts(job *Job, opts *RegisterOptions, q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) { 149 // Format the request 150 req := &JobRegisterRequest{Job: job} 151 if opts != nil { 152 if opts.EnforceIndex { 153 req.EnforceIndex = true 154 req.JobModifyIndex = opts.ModifyIndex 155 } 156 req.PolicyOverride = opts.PolicyOverride 157 req.PreserveCounts = opts.PreserveCounts 158 req.EvalPriority = opts.EvalPriority 159 req.Submission = opts.Submission 160 } 161 162 var resp JobRegisterResponse 163 wm, err := j.client.put("/v1/jobs", req, &resp, q) 164 if err != nil { 165 return nil, nil, err 166 } 167 return &resp, wm, nil 168 } 169 170 type JobListFields struct { 171 Meta bool 172 } 173 type JobListOptions struct { 174 Fields *JobListFields 175 } 176 177 // List is used to list all of the existing jobs. 178 func (j *Jobs) List(q *QueryOptions) ([]*JobListStub, *QueryMeta, error) { 179 return j.ListOptions(nil, q) 180 } 181 182 // List is used to list all of the existing jobs. 183 func (j *Jobs) ListOptions(opts *JobListOptions, q *QueryOptions) ([]*JobListStub, *QueryMeta, error) { 184 var resp []*JobListStub 185 186 destinationURL := "/v1/jobs" 187 188 if opts != nil && opts.Fields != nil { 189 qp := url.Values{} 190 qp.Add("meta", fmt.Sprint(opts.Fields.Meta)) 191 destinationURL = destinationURL + "?" + qp.Encode() 192 } 193 194 qm, err := j.client.query(destinationURL, &resp, q) 195 if err != nil { 196 return nil, qm, err 197 } 198 sort.Sort(JobIDSort(resp)) 199 return resp, qm, nil 200 } 201 202 // PrefixList is used to list all existing jobs that match the prefix. 203 func (j *Jobs) PrefixList(prefix string) ([]*JobListStub, *QueryMeta, error) { 204 return j.List(&QueryOptions{Prefix: prefix}) 205 } 206 207 // Info is used to retrieve information about a particular 208 // job given its unique ID. 209 func (j *Jobs) Info(jobID string, q *QueryOptions) (*Job, *QueryMeta, error) { 210 var resp Job 211 qm, err := j.client.query("/v1/job/"+url.PathEscape(jobID), &resp, q) 212 if err != nil { 213 return nil, nil, err 214 } 215 return &resp, qm, nil 216 } 217 218 // Scale is used to retrieve information about a particular 219 // job given its unique ID. 220 func (j *Jobs) Scale(jobID, group string, count *int, message string, error bool, meta map[string]interface{}, 221 q *WriteOptions) (*JobRegisterResponse, *WriteMeta, error) { 222 223 var count64 *int64 224 if count != nil { 225 count64 = pointerOf(int64(*count)) 226 } 227 req := &ScalingRequest{ 228 Count: count64, 229 Target: map[string]string{ 230 "Job": jobID, 231 "Group": group, 232 }, 233 Error: error, 234 Message: message, 235 Meta: meta, 236 } 237 var resp JobRegisterResponse 238 qm, err := j.client.put(fmt.Sprintf("/v1/job/%s/scale", url.PathEscape(jobID)), req, &resp, q) 239 if err != nil { 240 return nil, nil, err 241 } 242 return &resp, qm, nil 243 } 244 245 // ScaleStatus is used to retrieve information about a particular 246 // job given its unique ID. 247 func (j *Jobs) ScaleStatus(jobID string, q *QueryOptions) (*JobScaleStatusResponse, *QueryMeta, error) { 248 var resp JobScaleStatusResponse 249 qm, err := j.client.query(fmt.Sprintf("/v1/job/%s/scale", url.PathEscape(jobID)), &resp, q) 250 if err != nil { 251 return nil, nil, err 252 } 253 return &resp, qm, nil 254 } 255 256 // Versions is used to retrieve all versions of a particular job given its 257 // unique ID. 258 func (j *Jobs) Versions(jobID string, diffs bool, q *QueryOptions) ([]*Job, []*JobDiff, *QueryMeta, error) { 259 var resp JobVersionsResponse 260 qm, err := j.client.query(fmt.Sprintf("/v1/job/%s/versions?diffs=%v", url.PathEscape(jobID), diffs), &resp, q) 261 if err != nil { 262 return nil, nil, nil, err 263 } 264 return resp.Versions, resp.Diffs, qm, nil 265 } 266 267 // Submission is used to retrieve the original submitted source of a job given its 268 // namespace, jobID, and version number. The original source might not be available, 269 // which case nil is returned with no error. 270 func (j *Jobs) Submission(jobID string, version int, q *QueryOptions) (*JobSubmission, *QueryMeta, error) { 271 var sub JobSubmission 272 s := fmt.Sprintf("/v1/job/%s/submission?version=%d", url.PathEscape(jobID), version) 273 qm, err := j.client.query(s, &sub, q) 274 if err != nil { 275 return nil, nil, err 276 } 277 return &sub, qm, nil 278 } 279 280 // Allocations is used to return the allocs for a given job ID. 281 func (j *Jobs) Allocations(jobID string, allAllocs bool, q *QueryOptions) ([]*AllocationListStub, *QueryMeta, error) { 282 var resp []*AllocationListStub 283 u, err := url.Parse("/v1/job/" + url.PathEscape(jobID) + "/allocations") 284 if err != nil { 285 return nil, nil, err 286 } 287 288 v := u.Query() 289 v.Add("all", strconv.FormatBool(allAllocs)) 290 u.RawQuery = v.Encode() 291 292 qm, err := j.client.query(u.String(), &resp, q) 293 if err != nil { 294 return nil, nil, err 295 } 296 sort.Sort(AllocIndexSort(resp)) 297 return resp, qm, nil 298 } 299 300 // Deployments is used to query the deployments associated with the given job 301 // ID. 302 func (j *Jobs) Deployments(jobID string, all bool, q *QueryOptions) ([]*Deployment, *QueryMeta, error) { 303 var resp []*Deployment 304 u, err := url.Parse("/v1/job/" + url.PathEscape(jobID) + "/deployments") 305 if err != nil { 306 return nil, nil, err 307 } 308 309 v := u.Query() 310 v.Add("all", strconv.FormatBool(all)) 311 u.RawQuery = v.Encode() 312 qm, err := j.client.query(u.String(), &resp, q) 313 if err != nil { 314 return nil, nil, err 315 } 316 sort.Sort(DeploymentIndexSort(resp)) 317 return resp, qm, nil 318 } 319 320 // LatestDeployment is used to query for the latest deployment associated with 321 // the given job ID. 322 func (j *Jobs) LatestDeployment(jobID string, q *QueryOptions) (*Deployment, *QueryMeta, error) { 323 var resp *Deployment 324 qm, err := j.client.query("/v1/job/"+url.PathEscape(jobID)+"/deployment", &resp, q) 325 if err != nil { 326 return nil, nil, err 327 } 328 return resp, qm, nil 329 } 330 331 // Evaluations is used to query the evaluations associated with the given job 332 // ID. 333 func (j *Jobs) Evaluations(jobID string, q *QueryOptions) ([]*Evaluation, *QueryMeta, error) { 334 var resp []*Evaluation 335 qm, err := j.client.query("/v1/job/"+url.PathEscape(jobID)+"/evaluations", &resp, q) 336 if err != nil { 337 return nil, nil, err 338 } 339 sort.Sort(EvalIndexSort(resp)) 340 return resp, qm, nil 341 } 342 343 // Deregister is used to remove an existing job. If purge is set to true, the job 344 // is deregistered and purged from the system versus still being queryable and 345 // eventually GC'ed from the system. Most callers should not specify purge. 346 func (j *Jobs) Deregister(jobID string, purge bool, q *WriteOptions) (string, *WriteMeta, error) { 347 var resp JobDeregisterResponse 348 wm, err := j.client.delete(fmt.Sprintf("/v1/job/%v?purge=%t", url.PathEscape(jobID), purge), nil, &resp, q) 349 if err != nil { 350 return "", nil, err 351 } 352 return resp.EvalID, wm, nil 353 } 354 355 // DeregisterOptions is used to pass through job deregistration parameters 356 type DeregisterOptions struct { 357 // If Purge is set to true, the job is deregistered and purged from the 358 // system versus still being queryable and eventually GC'ed from the 359 // system. Most callers should not specify purge. 360 Purge bool 361 362 // If Global is set to true, all regions of a multiregion job will be 363 // stopped. 364 Global bool 365 366 // EvalPriority is an optional priority to use on any evaluation created as 367 // a result on this job deregistration. This value must be between 1-100 368 // inclusively, where a larger value corresponds to a higher priority. This 369 // is useful when an operator wishes to push through a job deregistration 370 // in busy clusters with a large evaluation backlog. 371 EvalPriority int 372 373 // NoShutdownDelay, if set to true, will override the group and 374 // task shutdown_delay configuration and ignore the delay for any 375 // allocations stopped as a result of this Deregister call. 376 NoShutdownDelay bool 377 } 378 379 // DeregisterOpts is used to remove an existing job. See DeregisterOptions 380 // for parameters. 381 func (j *Jobs) DeregisterOpts(jobID string, opts *DeregisterOptions, q *WriteOptions) (string, *WriteMeta, error) { 382 var resp JobDeregisterResponse 383 384 // The base endpoint to add query params to. 385 endpoint := "/v1/job/" + url.PathEscape(jobID) 386 387 // Protect against nil opts. url.Values expects a string, and so using 388 // fmt.Sprintf is the best way to do this. 389 if opts != nil { 390 endpoint += fmt.Sprintf("?purge=%t&global=%t&eval_priority=%v&no_shutdown_delay=%t", 391 opts.Purge, opts.Global, opts.EvalPriority, opts.NoShutdownDelay) 392 } 393 394 wm, err := j.client.delete(endpoint, nil, &resp, q) 395 if err != nil { 396 return "", nil, err 397 } 398 return resp.EvalID, wm, nil 399 } 400 401 // ForceEvaluate is used to force-evaluate an existing job. 402 func (j *Jobs) ForceEvaluate(jobID string, q *WriteOptions) (string, *WriteMeta, error) { 403 var resp JobRegisterResponse 404 wm, err := j.client.put("/v1/job/"+url.PathEscape(jobID)+"/evaluate", nil, &resp, q) 405 if err != nil { 406 return "", nil, err 407 } 408 return resp.EvalID, wm, nil 409 } 410 411 // EvaluateWithOpts is used to force-evaluate an existing job and takes additional options 412 // for whether to force reschedule failed allocations 413 func (j *Jobs) EvaluateWithOpts(jobID string, opts EvalOptions, q *WriteOptions) (string, *WriteMeta, error) { 414 req := &JobEvaluateRequest{ 415 JobID: jobID, 416 EvalOptions: opts, 417 } 418 419 var resp JobRegisterResponse 420 wm, err := j.client.put("/v1/job/"+url.PathEscape(jobID)+"/evaluate", req, &resp, q) 421 if err != nil { 422 return "", nil, err 423 } 424 return resp.EvalID, wm, nil 425 } 426 427 // PeriodicForce spawns a new instance of the periodic job and returns the eval ID 428 func (j *Jobs) PeriodicForce(jobID string, q *WriteOptions) (string, *WriteMeta, error) { 429 var resp periodicForceResponse 430 wm, err := j.client.put("/v1/job/"+url.PathEscape(jobID)+"/periodic/force", nil, &resp, q) 431 if err != nil { 432 return "", nil, err 433 } 434 return resp.EvalID, wm, nil 435 } 436 437 // PlanOptions is used to pass through job planning parameters 438 type PlanOptions struct { 439 Diff bool 440 PolicyOverride bool 441 } 442 443 func (j *Jobs) Plan(job *Job, diff bool, q *WriteOptions) (*JobPlanResponse, *WriteMeta, error) { 444 opts := PlanOptions{Diff: diff} 445 return j.PlanOpts(job, &opts, q) 446 } 447 448 func (j *Jobs) PlanOpts(job *Job, opts *PlanOptions, q *WriteOptions) (*JobPlanResponse, *WriteMeta, error) { 449 if job == nil { 450 return nil, nil, errors.New("must pass non-nil job") 451 } 452 if job.ID == nil { 453 return nil, nil, errors.New("job is missing ID") 454 } 455 456 // Setup the request 457 req := &JobPlanRequest{ 458 Job: job, 459 } 460 if opts != nil { 461 req.Diff = opts.Diff 462 req.PolicyOverride = opts.PolicyOverride 463 } 464 465 var resp JobPlanResponse 466 wm, err := j.client.put("/v1/job/"+url.PathEscape(*job.ID)+"/plan", req, &resp, q) 467 if err != nil { 468 return nil, nil, err 469 } 470 return &resp, wm, nil 471 } 472 473 func (j *Jobs) Summary(jobID string, q *QueryOptions) (*JobSummary, *QueryMeta, error) { 474 var resp JobSummary 475 qm, err := j.client.query("/v1/job/"+url.PathEscape(jobID)+"/summary", &resp, q) 476 if err != nil { 477 return nil, nil, err 478 } 479 return &resp, qm, nil 480 } 481 482 func (j *Jobs) Dispatch(jobID string, meta map[string]string, 483 payload []byte, idPrefixTemplate string, q *WriteOptions) (*JobDispatchResponse, *WriteMeta, error) { 484 var resp JobDispatchResponse 485 req := &JobDispatchRequest{ 486 JobID: jobID, 487 Meta: meta, 488 Payload: payload, 489 IdPrefixTemplate: idPrefixTemplate, 490 } 491 wm, err := j.client.put("/v1/job/"+url.PathEscape(jobID)+"/dispatch", req, &resp, q) 492 if err != nil { 493 return nil, nil, err 494 } 495 return &resp, wm, nil 496 } 497 498 // Revert is used to revert the given job to the passed version. If 499 // enforceVersion is set, the job is only reverted if the current version is at 500 // the passed version. 501 func (j *Jobs) Revert(jobID string, version uint64, enforcePriorVersion *uint64, 502 q *WriteOptions, consulToken, vaultToken string) (*JobRegisterResponse, *WriteMeta, error) { 503 504 var resp JobRegisterResponse 505 req := &JobRevertRequest{ 506 JobID: jobID, 507 JobVersion: version, 508 EnforcePriorVersion: enforcePriorVersion, 509 ConsulToken: consulToken, 510 VaultToken: vaultToken, 511 } 512 wm, err := j.client.put("/v1/job/"+url.PathEscape(jobID)+"/revert", req, &resp, q) 513 if err != nil { 514 return nil, nil, err 515 } 516 return &resp, wm, nil 517 } 518 519 // Stable is used to mark a job version's stability. 520 func (j *Jobs) Stable(jobID string, version uint64, stable bool, 521 q *WriteOptions) (*JobStabilityResponse, *WriteMeta, error) { 522 523 var resp JobStabilityResponse 524 req := &JobStabilityRequest{ 525 JobID: jobID, 526 JobVersion: version, 527 Stable: stable, 528 } 529 wm, err := j.client.put("/v1/job/"+url.PathEscape(jobID)+"/stable", req, &resp, q) 530 if err != nil { 531 return nil, nil, err 532 } 533 return &resp, wm, nil 534 } 535 536 // Services is used to return a list of service registrations associated to the 537 // specified jobID. 538 func (j *Jobs) Services(jobID string, q *QueryOptions) ([]*ServiceRegistration, *QueryMeta, error) { 539 var resp []*ServiceRegistration 540 qm, err := j.client.query("/v1/job/"+jobID+"/services", &resp, q) 541 return resp, qm, err 542 } 543 544 // periodicForceResponse is used to deserialize a force response 545 type periodicForceResponse struct { 546 EvalID string 547 } 548 549 // UpdateStrategy defines a task groups update strategy. 550 type UpdateStrategy struct { 551 Stagger *time.Duration `mapstructure:"stagger" hcl:"stagger,optional"` 552 MaxParallel *int `mapstructure:"max_parallel" hcl:"max_parallel,optional"` 553 HealthCheck *string `mapstructure:"health_check" hcl:"health_check,optional"` 554 MinHealthyTime *time.Duration `mapstructure:"min_healthy_time" hcl:"min_healthy_time,optional"` 555 HealthyDeadline *time.Duration `mapstructure:"healthy_deadline" hcl:"healthy_deadline,optional"` 556 ProgressDeadline *time.Duration `mapstructure:"progress_deadline" hcl:"progress_deadline,optional"` 557 Canary *int `mapstructure:"canary" hcl:"canary,optional"` 558 AutoRevert *bool `mapstructure:"auto_revert" hcl:"auto_revert,optional"` 559 AutoPromote *bool `mapstructure:"auto_promote" hcl:"auto_promote,optional"` 560 } 561 562 // DefaultUpdateStrategy provides a baseline that can be used to upgrade 563 // jobs with the old policy or for populating field defaults. 564 func DefaultUpdateStrategy() *UpdateStrategy { 565 return &UpdateStrategy{ 566 Stagger: pointerOf(30 * time.Second), 567 MaxParallel: pointerOf(1), 568 HealthCheck: pointerOf("checks"), 569 MinHealthyTime: pointerOf(10 * time.Second), 570 HealthyDeadline: pointerOf(5 * time.Minute), 571 ProgressDeadline: pointerOf(10 * time.Minute), 572 AutoRevert: pointerOf(false), 573 Canary: pointerOf(0), 574 AutoPromote: pointerOf(false), 575 } 576 } 577 578 func (u *UpdateStrategy) Copy() *UpdateStrategy { 579 if u == nil { 580 return nil 581 } 582 583 copy := new(UpdateStrategy) 584 585 if u.Stagger != nil { 586 copy.Stagger = pointerOf(*u.Stagger) 587 } 588 589 if u.MaxParallel != nil { 590 copy.MaxParallel = pointerOf(*u.MaxParallel) 591 } 592 593 if u.HealthCheck != nil { 594 copy.HealthCheck = pointerOf(*u.HealthCheck) 595 } 596 597 if u.MinHealthyTime != nil { 598 copy.MinHealthyTime = pointerOf(*u.MinHealthyTime) 599 } 600 601 if u.HealthyDeadline != nil { 602 copy.HealthyDeadline = pointerOf(*u.HealthyDeadline) 603 } 604 605 if u.ProgressDeadline != nil { 606 copy.ProgressDeadline = pointerOf(*u.ProgressDeadline) 607 } 608 609 if u.AutoRevert != nil { 610 copy.AutoRevert = pointerOf(*u.AutoRevert) 611 } 612 613 if u.Canary != nil { 614 copy.Canary = pointerOf(*u.Canary) 615 } 616 617 if u.AutoPromote != nil { 618 copy.AutoPromote = pointerOf(*u.AutoPromote) 619 } 620 621 return copy 622 } 623 624 func (u *UpdateStrategy) Merge(o *UpdateStrategy) { 625 if o == nil { 626 return 627 } 628 629 if o.Stagger != nil { 630 u.Stagger = pointerOf(*o.Stagger) 631 } 632 633 if o.MaxParallel != nil { 634 u.MaxParallel = pointerOf(*o.MaxParallel) 635 } 636 637 if o.HealthCheck != nil { 638 u.HealthCheck = pointerOf(*o.HealthCheck) 639 } 640 641 if o.MinHealthyTime != nil { 642 u.MinHealthyTime = pointerOf(*o.MinHealthyTime) 643 } 644 645 if o.HealthyDeadline != nil { 646 u.HealthyDeadline = pointerOf(*o.HealthyDeadline) 647 } 648 649 if o.ProgressDeadline != nil { 650 u.ProgressDeadline = pointerOf(*o.ProgressDeadline) 651 } 652 653 if o.AutoRevert != nil { 654 u.AutoRevert = pointerOf(*o.AutoRevert) 655 } 656 657 if o.Canary != nil { 658 u.Canary = pointerOf(*o.Canary) 659 } 660 661 if o.AutoPromote != nil { 662 u.AutoPromote = pointerOf(*o.AutoPromote) 663 } 664 } 665 666 func (u *UpdateStrategy) Canonicalize() { 667 d := DefaultUpdateStrategy() 668 669 if u.MaxParallel == nil { 670 u.MaxParallel = d.MaxParallel 671 } 672 673 if u.Stagger == nil { 674 u.Stagger = d.Stagger 675 } 676 677 if u.HealthCheck == nil { 678 u.HealthCheck = d.HealthCheck 679 } 680 681 if u.HealthyDeadline == nil { 682 u.HealthyDeadline = d.HealthyDeadline 683 } 684 685 if u.ProgressDeadline == nil { 686 u.ProgressDeadline = d.ProgressDeadline 687 } 688 689 if u.MinHealthyTime == nil { 690 u.MinHealthyTime = d.MinHealthyTime 691 } 692 693 if u.AutoRevert == nil { 694 u.AutoRevert = d.AutoRevert 695 } 696 697 if u.Canary == nil { 698 u.Canary = d.Canary 699 } 700 701 if u.AutoPromote == nil { 702 u.AutoPromote = d.AutoPromote 703 } 704 } 705 706 // Empty returns whether the UpdateStrategy is empty or has user defined values. 707 func (u *UpdateStrategy) Empty() bool { 708 if u == nil { 709 return true 710 } 711 712 if u.Stagger != nil && *u.Stagger != 0 { 713 return false 714 } 715 716 if u.MaxParallel != nil && *u.MaxParallel != 0 { 717 return false 718 } 719 720 if u.HealthCheck != nil && *u.HealthCheck != "" { 721 return false 722 } 723 724 if u.MinHealthyTime != nil && *u.MinHealthyTime != 0 { 725 return false 726 } 727 728 if u.HealthyDeadline != nil && *u.HealthyDeadline != 0 { 729 return false 730 } 731 732 if u.ProgressDeadline != nil && *u.ProgressDeadline != 0 { 733 return false 734 } 735 736 if u.AutoRevert != nil && *u.AutoRevert { 737 return false 738 } 739 740 if u.AutoPromote != nil && *u.AutoPromote { 741 return false 742 } 743 744 if u.Canary != nil && *u.Canary != 0 { 745 return false 746 } 747 748 return true 749 } 750 751 type Multiregion struct { 752 Strategy *MultiregionStrategy `hcl:"strategy,block"` 753 Regions []*MultiregionRegion `hcl:"region,block"` 754 } 755 756 func (m *Multiregion) Canonicalize() { 757 if m.Strategy == nil { 758 m.Strategy = &MultiregionStrategy{ 759 MaxParallel: pointerOf(0), 760 OnFailure: pointerOf(""), 761 } 762 } else { 763 if m.Strategy.MaxParallel == nil { 764 m.Strategy.MaxParallel = pointerOf(0) 765 } 766 if m.Strategy.OnFailure == nil { 767 m.Strategy.OnFailure = pointerOf("") 768 } 769 } 770 if m.Regions == nil { 771 m.Regions = []*MultiregionRegion{} 772 } 773 for _, region := range m.Regions { 774 if region.Count == nil { 775 region.Count = pointerOf(1) 776 } 777 if region.Datacenters == nil { 778 region.Datacenters = []string{} 779 } 780 if region.Meta == nil { 781 region.Meta = map[string]string{} 782 } 783 } 784 } 785 786 func (m *Multiregion) Copy() *Multiregion { 787 if m == nil { 788 return nil 789 } 790 copy := new(Multiregion) 791 if m.Strategy != nil { 792 copy.Strategy = new(MultiregionStrategy) 793 copy.Strategy.MaxParallel = pointerOf(*m.Strategy.MaxParallel) 794 copy.Strategy.OnFailure = pointerOf(*m.Strategy.OnFailure) 795 } 796 for _, region := range m.Regions { 797 copyRegion := new(MultiregionRegion) 798 copyRegion.Name = region.Name 799 copyRegion.Count = pointerOf(*region.Count) 800 copyRegion.Datacenters = append(copyRegion.Datacenters, region.Datacenters...) 801 copyRegion.NodePool = region.NodePool 802 for k, v := range region.Meta { 803 copyRegion.Meta[k] = v 804 } 805 806 copy.Regions = append(copy.Regions, copyRegion) 807 } 808 return copy 809 } 810 811 type MultiregionStrategy struct { 812 MaxParallel *int `mapstructure:"max_parallel" hcl:"max_parallel,optional"` 813 OnFailure *string `mapstructure:"on_failure" hcl:"on_failure,optional"` 814 } 815 816 type MultiregionRegion struct { 817 Name string `hcl:",label"` 818 Count *int `hcl:"count,optional"` 819 Datacenters []string `hcl:"datacenters,optional"` 820 NodePool string `hcl:"node_pool,optional"` 821 Meta map[string]string `hcl:"meta,block"` 822 } 823 824 // PeriodicConfig is for serializing periodic config for a job. 825 type PeriodicConfig struct { 826 Enabled *bool `hcl:"enabled,optional"` 827 Spec *string `hcl:"cron,optional"` 828 Specs []string `hcl:"crons,optional"` 829 SpecType *string 830 ProhibitOverlap *bool `mapstructure:"prohibit_overlap" hcl:"prohibit_overlap,optional"` 831 TimeZone *string `mapstructure:"time_zone" hcl:"time_zone,optional"` 832 } 833 834 func (p *PeriodicConfig) Canonicalize() { 835 if p.Enabled == nil { 836 p.Enabled = pointerOf(true) 837 } 838 if p.Spec == nil { 839 p.Spec = pointerOf("") 840 } 841 if p.Specs == nil { 842 p.Specs = []string{} 843 } 844 if p.SpecType == nil { 845 p.SpecType = pointerOf(PeriodicSpecCron) 846 } 847 if p.ProhibitOverlap == nil { 848 p.ProhibitOverlap = pointerOf(false) 849 } 850 if p.TimeZone == nil || *p.TimeZone == "" { 851 p.TimeZone = pointerOf("UTC") 852 } 853 } 854 855 // Next returns the closest time instant matching the spec that is after the 856 // passed time. If no matching instance exists, the zero value of time.Time is 857 // returned. The `time.Location` of the returned value matches that of the 858 // passed time. 859 func (p *PeriodicConfig) Next(fromTime time.Time) (time.Time, error) { 860 // Single spec parsing 861 if p != nil && *p.SpecType == PeriodicSpecCron { 862 if p.Spec != nil && *p.Spec != "" { 863 return cronParseNext(fromTime, *p.Spec) 864 } 865 } 866 867 // multiple specs parsing 868 var nextTime time.Time 869 for _, spec := range p.Specs { 870 t, err := cronParseNext(fromTime, spec) 871 if err != nil { 872 return time.Time{}, fmt.Errorf("failed parsing cron expression %s: %v", spec, err) 873 } 874 if nextTime.IsZero() || t.Before(nextTime) { 875 nextTime = t 876 } 877 } 878 return nextTime, nil 879 } 880 881 // cronParseNext is a helper that parses the next time for the given expression 882 // but captures any panic that may occur in the underlying library. 883 // --- THIS FUNCTION IS REPLICATED IN nomad/structs/structs.go 884 // and should be kept in sync. 885 func cronParseNext(fromTime time.Time, spec string) (t time.Time, err error) { 886 defer func() { 887 if recover() != nil { 888 t = time.Time{} 889 err = fmt.Errorf("failed parsing cron expression: %q", spec) 890 } 891 }() 892 exp, err := cronexpr.Parse(spec) 893 if err != nil { 894 return time.Time{}, fmt.Errorf("failed parsing cron expression: %s: %v", spec, err) 895 } 896 return exp.Next(fromTime), nil 897 } 898 899 func (p *PeriodicConfig) GetLocation() (*time.Location, error) { 900 if p.TimeZone == nil || *p.TimeZone == "" { 901 return time.UTC, nil 902 } 903 904 return time.LoadLocation(*p.TimeZone) 905 } 906 907 // ParameterizedJobConfig is used to configure the parameterized job. 908 type ParameterizedJobConfig struct { 909 Payload string `hcl:"payload,optional"` 910 MetaRequired []string `mapstructure:"meta_required" hcl:"meta_required,optional"` 911 MetaOptional []string `mapstructure:"meta_optional" hcl:"meta_optional,optional"` 912 } 913 914 // JobSubmission is used to hold information about the original content of a job 915 // specification being submitted to Nomad. 916 // 917 // At any time a JobSubmission may be nil, indicating no information is known about 918 // the job submission. 919 type JobSubmission struct { 920 // Source contains the original job definition (may be in the format of 921 // hcl1, hcl2, or json). 922 Source string 923 924 // Format indicates what the Source content was (hcl1, hcl2, or json). 925 Format string 926 927 // VariableFlags contains the CLI "-var" flag arguments as submitted with the 928 // job (hcl2 only). 929 VariableFlags map[string]string 930 931 // Variables contains the opaque variables configuration as coming from 932 // a var-file or the WebUI variables input (hcl2 only). 933 Variables string 934 } 935 936 func (js *JobSubmission) Canonicalize() { 937 if js == nil { 938 return 939 } 940 941 if len(js.VariableFlags) == 0 { 942 js.VariableFlags = nil 943 } 944 } 945 946 func (js *JobSubmission) Copy() *JobSubmission { 947 if js == nil { 948 return nil 949 } 950 951 return &JobSubmission{ 952 Source: js.Source, 953 Format: js.Format, 954 VariableFlags: maps.Clone(js.VariableFlags), 955 Variables: js.Variables, 956 } 957 } 958 959 // Job is used to serialize a job. 960 type Job struct { 961 /* Fields parsed from HCL config */ 962 963 Region *string `hcl:"region,optional"` 964 Namespace *string `hcl:"namespace,optional"` 965 ID *string `hcl:"id,optional"` 966 Name *string `hcl:"name,optional"` 967 Type *string `hcl:"type,optional"` 968 Priority *int `hcl:"priority,optional"` 969 AllAtOnce *bool `mapstructure:"all_at_once" hcl:"all_at_once,optional"` 970 Datacenters []string `hcl:"datacenters,optional"` 971 NodePool *string `mapstructure:"node_pool" hcl:"node_pool,optional"` 972 Constraints []*Constraint `hcl:"constraint,block"` 973 Affinities []*Affinity `hcl:"affinity,block"` 974 TaskGroups []*TaskGroup `hcl:"group,block"` 975 Update *UpdateStrategy `hcl:"update,block"` 976 Multiregion *Multiregion `hcl:"multiregion,block"` 977 Spreads []*Spread `hcl:"spread,block"` 978 Periodic *PeriodicConfig `hcl:"periodic,block"` 979 ParameterizedJob *ParameterizedJobConfig `hcl:"parameterized,block"` 980 Reschedule *ReschedulePolicy `hcl:"reschedule,block"` 981 Migrate *MigrateStrategy `hcl:"migrate,block"` 982 Meta map[string]string `hcl:"meta,block"` 983 ConsulToken *string `mapstructure:"consul_token" hcl:"consul_token,optional"` 984 VaultToken *string `mapstructure:"vault_token" hcl:"vault_token,optional"` 985 986 /* Fields set by server, not sourced from job config file */ 987 988 Stop *bool 989 ParentID *string 990 Dispatched bool 991 DispatchIdempotencyToken *string 992 Payload []byte 993 ConsulNamespace *string `mapstructure:"consul_namespace"` 994 VaultNamespace *string `mapstructure:"vault_namespace"` 995 NomadTokenID *string `mapstructure:"nomad_token_id"` 996 Status *string 997 StatusDescription *string 998 Stable *bool 999 Version *uint64 1000 SubmitTime *int64 1001 CreateIndex *uint64 1002 ModifyIndex *uint64 1003 JobModifyIndex *uint64 1004 } 1005 1006 // IsPeriodic returns whether a job is periodic. 1007 func (j *Job) IsPeriodic() bool { 1008 return j.Periodic != nil 1009 } 1010 1011 // IsParameterized returns whether a job is parameterized job. 1012 func (j *Job) IsParameterized() bool { 1013 return j.ParameterizedJob != nil && !j.Dispatched 1014 } 1015 1016 // IsMultiregion returns whether a job is a multiregion job 1017 func (j *Job) IsMultiregion() bool { 1018 return j.Multiregion != nil && j.Multiregion.Regions != nil && len(j.Multiregion.Regions) > 0 1019 } 1020 1021 func (j *Job) Canonicalize() { 1022 if j.ID == nil { 1023 j.ID = pointerOf("") 1024 } 1025 if j.Name == nil { 1026 j.Name = pointerOf(*j.ID) 1027 } 1028 if j.ParentID == nil { 1029 j.ParentID = pointerOf("") 1030 } 1031 if j.Namespace == nil { 1032 j.Namespace = pointerOf(DefaultNamespace) 1033 } 1034 if j.Priority == nil { 1035 j.Priority = pointerOf(JobDefaultPriority) 1036 } 1037 if j.Stop == nil { 1038 j.Stop = pointerOf(false) 1039 } 1040 if j.Region == nil { 1041 j.Region = pointerOf(GlobalRegion) 1042 } 1043 if j.NodePool == nil { 1044 j.NodePool = pointerOf("") 1045 } 1046 if j.Type == nil { 1047 j.Type = pointerOf("service") 1048 } 1049 if j.AllAtOnce == nil { 1050 j.AllAtOnce = pointerOf(false) 1051 } 1052 if j.ConsulToken == nil { 1053 j.ConsulToken = pointerOf("") 1054 } 1055 if j.ConsulNamespace == nil { 1056 j.ConsulNamespace = pointerOf("") 1057 } 1058 if j.VaultToken == nil { 1059 j.VaultToken = pointerOf("") 1060 } 1061 if j.VaultNamespace == nil { 1062 j.VaultNamespace = pointerOf("") 1063 } 1064 if j.NomadTokenID == nil { 1065 j.NomadTokenID = pointerOf("") 1066 } 1067 if j.Status == nil { 1068 j.Status = pointerOf("") 1069 } 1070 if j.StatusDescription == nil { 1071 j.StatusDescription = pointerOf("") 1072 } 1073 if j.Stable == nil { 1074 j.Stable = pointerOf(false) 1075 } 1076 if j.Version == nil { 1077 j.Version = pointerOf(uint64(0)) 1078 } 1079 if j.CreateIndex == nil { 1080 j.CreateIndex = pointerOf(uint64(0)) 1081 } 1082 if j.ModifyIndex == nil { 1083 j.ModifyIndex = pointerOf(uint64(0)) 1084 } 1085 if j.JobModifyIndex == nil { 1086 j.JobModifyIndex = pointerOf(uint64(0)) 1087 } 1088 if j.Periodic != nil { 1089 j.Periodic.Canonicalize() 1090 } 1091 if j.Update != nil { 1092 j.Update.Canonicalize() 1093 } else if *j.Type == JobTypeService { 1094 j.Update = DefaultUpdateStrategy() 1095 } 1096 if j.Multiregion != nil { 1097 j.Multiregion.Canonicalize() 1098 } 1099 1100 for _, tg := range j.TaskGroups { 1101 tg.Canonicalize(j) 1102 } 1103 1104 for _, spread := range j.Spreads { 1105 spread.Canonicalize() 1106 } 1107 for _, a := range j.Affinities { 1108 a.Canonicalize() 1109 } 1110 } 1111 1112 // LookupTaskGroup finds a task group by name 1113 func (j *Job) LookupTaskGroup(name string) *TaskGroup { 1114 for _, tg := range j.TaskGroups { 1115 if *tg.Name == name { 1116 return tg 1117 } 1118 } 1119 return nil 1120 } 1121 1122 // JobSummary summarizes the state of the allocations of a job 1123 type JobSummary struct { 1124 JobID string 1125 Namespace string 1126 Summary map[string]TaskGroupSummary 1127 Children *JobChildrenSummary 1128 1129 // Raft Indexes 1130 CreateIndex uint64 1131 ModifyIndex uint64 1132 } 1133 1134 // JobChildrenSummary contains the summary of children job status 1135 type JobChildrenSummary struct { 1136 Pending int64 1137 Running int64 1138 Dead int64 1139 } 1140 1141 func (jc *JobChildrenSummary) Sum() int { 1142 if jc == nil { 1143 return 0 1144 } 1145 1146 return int(jc.Pending + jc.Running + jc.Dead) 1147 } 1148 1149 // TaskGroup summarizes the state of all the allocations of a particular 1150 // TaskGroup 1151 type TaskGroupSummary struct { 1152 Queued int 1153 Complete int 1154 Failed int 1155 Running int 1156 Starting int 1157 Lost int 1158 Unknown int 1159 } 1160 1161 // JobListStub is used to return a subset of information about 1162 // jobs during list operations. 1163 type JobListStub struct { 1164 ID string 1165 ParentID string 1166 Name string 1167 Namespace string `json:",omitempty"` 1168 Datacenters []string 1169 Type string 1170 Priority int 1171 Periodic bool 1172 ParameterizedJob bool 1173 Stop bool 1174 Status string 1175 StatusDescription string 1176 JobSummary *JobSummary 1177 CreateIndex uint64 1178 ModifyIndex uint64 1179 JobModifyIndex uint64 1180 SubmitTime int64 1181 Meta map[string]string `json:",omitempty"` 1182 } 1183 1184 // JobIDSort is used to sort jobs by their job ID's. 1185 type JobIDSort []*JobListStub 1186 1187 func (j JobIDSort) Len() int { 1188 return len(j) 1189 } 1190 1191 func (j JobIDSort) Less(a, b int) bool { 1192 return j[a].ID < j[b].ID 1193 } 1194 1195 func (j JobIDSort) Swap(a, b int) { 1196 j[a], j[b] = j[b], j[a] 1197 } 1198 1199 // NewServiceJob creates and returns a new service-style job 1200 // for long-lived processes using the provided name, ID, and 1201 // relative job priority. 1202 func NewServiceJob(id, name, region string, pri int) *Job { 1203 return newJob(id, name, region, JobTypeService, pri) 1204 } 1205 1206 // NewBatchJob creates and returns a new batch-style job for 1207 // short-lived processes using the provided name and ID along 1208 // with the relative job priority. 1209 func NewBatchJob(id, name, region string, pri int) *Job { 1210 return newJob(id, name, region, JobTypeBatch, pri) 1211 } 1212 1213 // NewSystemJob creates and returns a new system-style job for processes 1214 // designed to run on all clients, using the provided name and ID along with 1215 // the relative job priority. 1216 func NewSystemJob(id, name, region string, pri int) *Job { 1217 return newJob(id, name, region, JobTypeSystem, pri) 1218 } 1219 1220 // NewSysbatchJob creates and returns a new sysbatch-style job for short-lived 1221 // processes designed to run on all clients, using the provided name and ID 1222 // along with the relative job priority. 1223 func NewSysbatchJob(id, name, region string, pri int) *Job { 1224 return newJob(id, name, region, JobTypeSysbatch, pri) 1225 } 1226 1227 // newJob is used to create a new Job struct. 1228 func newJob(id, name, region, typ string, pri int) *Job { 1229 return &Job{ 1230 Region: ®ion, 1231 ID: &id, 1232 Name: &name, 1233 Type: &typ, 1234 Priority: &pri, 1235 } 1236 } 1237 1238 // SetMeta is used to set arbitrary k/v pairs of metadata on a job. 1239 func (j *Job) SetMeta(key, val string) *Job { 1240 if j.Meta == nil { 1241 j.Meta = make(map[string]string) 1242 } 1243 j.Meta[key] = val 1244 return j 1245 } 1246 1247 // AddDatacenter is used to add a datacenter to a job. 1248 func (j *Job) AddDatacenter(dc string) *Job { 1249 j.Datacenters = append(j.Datacenters, dc) 1250 return j 1251 } 1252 1253 // Constrain is used to add a constraint to a job. 1254 func (j *Job) Constrain(c *Constraint) *Job { 1255 j.Constraints = append(j.Constraints, c) 1256 return j 1257 } 1258 1259 // AddAffinity is used to add an affinity to a job. 1260 func (j *Job) AddAffinity(a *Affinity) *Job { 1261 j.Affinities = append(j.Affinities, a) 1262 return j 1263 } 1264 1265 // AddTaskGroup adds a task group to an existing job. 1266 func (j *Job) AddTaskGroup(grp *TaskGroup) *Job { 1267 j.TaskGroups = append(j.TaskGroups, grp) 1268 return j 1269 } 1270 1271 // AddPeriodicConfig adds a periodic config to an existing job. 1272 func (j *Job) AddPeriodicConfig(cfg *PeriodicConfig) *Job { 1273 j.Periodic = cfg 1274 return j 1275 } 1276 1277 func (j *Job) AddSpread(s *Spread) *Job { 1278 j.Spreads = append(j.Spreads, s) 1279 return j 1280 } 1281 1282 type WriteRequest struct { 1283 // The target region for this write 1284 Region string 1285 1286 // Namespace is the target namespace for this write 1287 Namespace string 1288 1289 // SecretID is the secret ID of an ACL token 1290 SecretID string 1291 } 1292 1293 // JobValidateRequest is used to validate a job 1294 type JobValidateRequest struct { 1295 Job *Job 1296 WriteRequest 1297 } 1298 1299 // JobValidateResponse is the response from validate request 1300 type JobValidateResponse struct { 1301 // DriverConfigValidated indicates whether the agent validated the driver 1302 // config 1303 DriverConfigValidated bool 1304 1305 // ValidationErrors is a list of validation errors 1306 ValidationErrors []string 1307 1308 // Error is a string version of any error that may have occurred 1309 Error string 1310 1311 // Warnings contains any warnings about the given job. These may include 1312 // deprecation warnings. 1313 Warnings string 1314 } 1315 1316 // JobRevertRequest is used to revert a job to a prior version. 1317 type JobRevertRequest struct { 1318 // JobID is the ID of the job being reverted 1319 JobID string 1320 1321 // JobVersion the version to revert to. 1322 JobVersion uint64 1323 1324 // EnforcePriorVersion if set will enforce that the job is at the given 1325 // version before reverting. 1326 EnforcePriorVersion *uint64 1327 1328 // ConsulToken is the Consul token that proves the submitter of the job revert 1329 // has access to the Service Identity policies associated with the job's 1330 // Consul Connect enabled services. This field is only used to transfer the 1331 // token and is not stored after the Job revert. 1332 ConsulToken string `json:",omitempty"` 1333 1334 // VaultToken is the Vault token that proves the submitter of the job revert 1335 // has access to any Vault policies specified in the targeted job version. This 1336 // field is only used to authorize the revert and is not stored after the Job 1337 // revert. 1338 VaultToken string `json:",omitempty"` 1339 1340 WriteRequest 1341 } 1342 1343 // JobRegisterRequest is used to update a job 1344 type JobRegisterRequest struct { 1345 Submission *JobSubmission 1346 Job *Job 1347 1348 // If EnforceIndex is set then the job will only be registered if the passed 1349 // JobModifyIndex matches the current Jobs index. If the index is zero, the 1350 // register only occurs if the job is new. 1351 EnforceIndex bool `json:",omitempty"` 1352 JobModifyIndex uint64 `json:",omitempty"` 1353 PolicyOverride bool `json:",omitempty"` 1354 PreserveCounts bool `json:",omitempty"` 1355 1356 // EvalPriority is an optional priority to use on any evaluation created as 1357 // a result on this job registration. This value must be between 1-100 1358 // inclusively, where a larger value corresponds to a higher priority. This 1359 // is useful when an operator wishes to push through a job registration in 1360 // busy clusters with a large evaluation backlog. This avoids needing to 1361 // change the job priority which also impacts preemption. 1362 EvalPriority int `json:",omitempty"` 1363 1364 WriteRequest 1365 } 1366 1367 // JobRegisterResponse is used to respond to a job registration 1368 type JobRegisterResponse struct { 1369 EvalID string 1370 EvalCreateIndex uint64 1371 JobModifyIndex uint64 1372 1373 // Warnings contains any warnings about the given job. These may include 1374 // deprecation warnings. 1375 Warnings string 1376 1377 QueryMeta 1378 } 1379 1380 // JobDeregisterResponse is used to respond to a job deregistration 1381 type JobDeregisterResponse struct { 1382 EvalID string 1383 EvalCreateIndex uint64 1384 JobModifyIndex uint64 1385 QueryMeta 1386 } 1387 1388 type JobPlanRequest struct { 1389 Job *Job 1390 Diff bool 1391 PolicyOverride bool 1392 WriteRequest 1393 } 1394 1395 type JobPlanResponse struct { 1396 JobModifyIndex uint64 1397 CreatedEvals []*Evaluation 1398 Diff *JobDiff 1399 Annotations *PlanAnnotations 1400 FailedTGAllocs map[string]*AllocationMetric 1401 NextPeriodicLaunch time.Time 1402 1403 // Warnings contains any warnings about the given job. These may include 1404 // deprecation warnings. 1405 Warnings string 1406 } 1407 1408 type JobDiff struct { 1409 Type string 1410 ID string 1411 Fields []*FieldDiff 1412 Objects []*ObjectDiff 1413 TaskGroups []*TaskGroupDiff 1414 } 1415 1416 type TaskGroupDiff struct { 1417 Type string 1418 Name string 1419 Fields []*FieldDiff 1420 Objects []*ObjectDiff 1421 Tasks []*TaskDiff 1422 Updates map[string]uint64 1423 } 1424 1425 type TaskDiff struct { 1426 Type string 1427 Name string 1428 Fields []*FieldDiff 1429 Objects []*ObjectDiff 1430 Annotations []string 1431 } 1432 1433 type FieldDiff struct { 1434 Type string 1435 Name string 1436 Old, New string 1437 Annotations []string 1438 } 1439 1440 type ObjectDiff struct { 1441 Type string 1442 Name string 1443 Fields []*FieldDiff 1444 Objects []*ObjectDiff 1445 } 1446 1447 type PlanAnnotations struct { 1448 DesiredTGUpdates map[string]*DesiredUpdates 1449 PreemptedAllocs []*AllocationListStub 1450 } 1451 1452 type DesiredUpdates struct { 1453 Ignore uint64 1454 Place uint64 1455 Migrate uint64 1456 Stop uint64 1457 InPlaceUpdate uint64 1458 DestructiveUpdate uint64 1459 Canary uint64 1460 Preemptions uint64 1461 } 1462 1463 type JobDispatchRequest struct { 1464 JobID string 1465 Payload []byte 1466 Meta map[string]string 1467 IdPrefixTemplate string 1468 } 1469 1470 type JobDispatchResponse struct { 1471 DispatchedJobID string 1472 EvalID string 1473 EvalCreateIndex uint64 1474 JobCreateIndex uint64 1475 WriteMeta 1476 } 1477 1478 // JobVersionsResponse is used for a job get versions request 1479 type JobVersionsResponse struct { 1480 Versions []*Job 1481 Diffs []*JobDiff 1482 QueryMeta 1483 } 1484 1485 // JobSubmissionResponse is used for a job get submission request 1486 type JobSubmissionResponse struct { 1487 Submission *JobSubmission 1488 QueryMeta 1489 } 1490 1491 // JobStabilityRequest is used to marked a job as stable. 1492 type JobStabilityRequest struct { 1493 // Job to set the stability on 1494 JobID string 1495 JobVersion uint64 1496 1497 // Set the stability 1498 Stable bool 1499 WriteRequest 1500 } 1501 1502 // JobStabilityResponse is the response when marking a job as stable. 1503 type JobStabilityResponse struct { 1504 JobModifyIndex uint64 1505 WriteMeta 1506 } 1507 1508 // JobEvaluateRequest is used when we just need to re-evaluate a target job 1509 type JobEvaluateRequest struct { 1510 JobID string 1511 EvalOptions EvalOptions 1512 WriteRequest 1513 } 1514 1515 // EvalOptions is used to encapsulate options when forcing a job evaluation 1516 type EvalOptions struct { 1517 ForceReschedule bool 1518 } 1519 1520 // ActionExec is used to run a pre-defined command inside a running task. 1521 // The call blocks until command terminates (or an error occurs), and returns the exit code. 1522 func (j *Jobs) ActionExec(ctx context.Context, 1523 alloc *Allocation, job string, task string, tty bool, command []string, 1524 action string, 1525 stdin io.Reader, stdout, stderr io.Writer, 1526 terminalSizeCh <-chan TerminalSize, q *QueryOptions) (exitCode int, err error) { 1527 1528 s := &execSession{ 1529 client: j.client, 1530 alloc: alloc, 1531 job: job, 1532 task: task, 1533 tty: tty, 1534 command: command, 1535 action: action, 1536 1537 stdin: stdin, 1538 stdout: stdout, 1539 stderr: stderr, 1540 1541 terminalSizeCh: terminalSizeCh, 1542 q: q, 1543 } 1544 1545 return s.run(ctx) 1546 }