github.com/ferranbt/nomad@v0.9.3-0.20190607002617-85c449b7667c/command/agent/job_endpoint.go (about) 1 package agent 2 3 import ( 4 "fmt" 5 "net/http" 6 "strconv" 7 "strings" 8 9 "github.com/golang/snappy" 10 "github.com/hashicorp/nomad/api" 11 "github.com/hashicorp/nomad/jobspec" 12 "github.com/hashicorp/nomad/nomad/structs" 13 ) 14 15 func (s *HTTPServer) JobsRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 16 switch req.Method { 17 case "GET": 18 return s.jobListRequest(resp, req) 19 case "PUT", "POST": 20 return s.jobUpdate(resp, req, "") 21 default: 22 return nil, CodedError(405, ErrInvalidMethod) 23 } 24 } 25 26 func (s *HTTPServer) jobListRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 27 args := structs.JobListRequest{} 28 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 29 return nil, nil 30 } 31 32 var out structs.JobListResponse 33 if err := s.agent.RPC("Job.List", &args, &out); err != nil { 34 return nil, err 35 } 36 37 setMeta(resp, &out.QueryMeta) 38 if out.Jobs == nil { 39 out.Jobs = make([]*structs.JobListStub, 0) 40 } 41 return out.Jobs, nil 42 } 43 44 func (s *HTTPServer) JobSpecificRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 45 path := strings.TrimPrefix(req.URL.Path, "/v1/job/") 46 switch { 47 case strings.HasSuffix(path, "/evaluate"): 48 jobName := strings.TrimSuffix(path, "/evaluate") 49 return s.jobForceEvaluate(resp, req, jobName) 50 case strings.HasSuffix(path, "/allocations"): 51 jobName := strings.TrimSuffix(path, "/allocations") 52 return s.jobAllocations(resp, req, jobName) 53 case strings.HasSuffix(path, "/evaluations"): 54 jobName := strings.TrimSuffix(path, "/evaluations") 55 return s.jobEvaluations(resp, req, jobName) 56 case strings.HasSuffix(path, "/periodic/force"): 57 jobName := strings.TrimSuffix(path, "/periodic/force") 58 return s.periodicForceRequest(resp, req, jobName) 59 case strings.HasSuffix(path, "/plan"): 60 jobName := strings.TrimSuffix(path, "/plan") 61 return s.jobPlan(resp, req, jobName) 62 case strings.HasSuffix(path, "/summary"): 63 jobName := strings.TrimSuffix(path, "/summary") 64 return s.jobSummaryRequest(resp, req, jobName) 65 case strings.HasSuffix(path, "/dispatch"): 66 jobName := strings.TrimSuffix(path, "/dispatch") 67 return s.jobDispatchRequest(resp, req, jobName) 68 case strings.HasSuffix(path, "/versions"): 69 jobName := strings.TrimSuffix(path, "/versions") 70 return s.jobVersions(resp, req, jobName) 71 case strings.HasSuffix(path, "/revert"): 72 jobName := strings.TrimSuffix(path, "/revert") 73 return s.jobRevert(resp, req, jobName) 74 case strings.HasSuffix(path, "/deployments"): 75 jobName := strings.TrimSuffix(path, "/deployments") 76 return s.jobDeployments(resp, req, jobName) 77 case strings.HasSuffix(path, "/deployment"): 78 jobName := strings.TrimSuffix(path, "/deployment") 79 return s.jobLatestDeployment(resp, req, jobName) 80 case strings.HasSuffix(path, "/stable"): 81 jobName := strings.TrimSuffix(path, "/stable") 82 return s.jobStable(resp, req, jobName) 83 default: 84 return s.jobCRUD(resp, req, path) 85 } 86 } 87 88 func (s *HTTPServer) jobForceEvaluate(resp http.ResponseWriter, req *http.Request, 89 jobName string) (interface{}, error) { 90 if req.Method != "PUT" && req.Method != "POST" { 91 return nil, CodedError(405, ErrInvalidMethod) 92 } 93 var args structs.JobEvaluateRequest 94 95 // TODO(preetha): remove in 0.9 96 // COMPAT: For backwards compatibility allow using this endpoint without a payload 97 if req.ContentLength == 0 { 98 args = structs.JobEvaluateRequest{ 99 JobID: jobName, 100 } 101 } else { 102 if err := decodeBody(req, &args); err != nil { 103 return nil, CodedError(400, err.Error()) 104 } 105 if args.JobID == "" { 106 return nil, CodedError(400, "Job ID must be specified") 107 } 108 109 if jobName != "" && args.JobID != jobName { 110 return nil, CodedError(400, "JobID not same as job name") 111 } 112 } 113 s.parseWriteRequest(req, &args.WriteRequest) 114 115 var out structs.JobRegisterResponse 116 if err := s.agent.RPC("Job.Evaluate", &args, &out); err != nil { 117 return nil, err 118 } 119 setIndex(resp, out.Index) 120 return out, nil 121 } 122 123 func (s *HTTPServer) jobPlan(resp http.ResponseWriter, req *http.Request, 124 jobName string) (interface{}, error) { 125 if req.Method != "PUT" && req.Method != "POST" { 126 return nil, CodedError(405, ErrInvalidMethod) 127 } 128 129 var args api.JobPlanRequest 130 if err := decodeBody(req, &args); err != nil { 131 return nil, CodedError(400, err.Error()) 132 } 133 if args.Job == nil { 134 return nil, CodedError(400, "Job must be specified") 135 } 136 if args.Job.ID == nil { 137 return nil, CodedError(400, "Job must have a valid ID") 138 } 139 if jobName != "" && *args.Job.ID != jobName { 140 return nil, CodedError(400, "Job ID does not match") 141 } 142 143 sJob := ApiJobToStructJob(args.Job) 144 planReq := structs.JobPlanRequest{ 145 Job: sJob, 146 Diff: args.Diff, 147 PolicyOverride: args.PolicyOverride, 148 WriteRequest: structs.WriteRequest{ 149 Region: args.WriteRequest.Region, 150 }, 151 } 152 s.parseWriteRequest(req, &planReq.WriteRequest) 153 planReq.Namespace = sJob.Namespace 154 155 var out structs.JobPlanResponse 156 if err := s.agent.RPC("Job.Plan", &planReq, &out); err != nil { 157 return nil, err 158 } 159 setIndex(resp, out.Index) 160 return out, nil 161 } 162 163 func (s *HTTPServer) ValidateJobRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 164 // Ensure request method is POST or PUT 165 if !(req.Method == "POST" || req.Method == "PUT") { 166 return nil, CodedError(405, ErrInvalidMethod) 167 } 168 169 var validateRequest api.JobValidateRequest 170 if err := decodeBody(req, &validateRequest); err != nil { 171 return nil, CodedError(400, err.Error()) 172 } 173 if validateRequest.Job == nil { 174 return nil, CodedError(400, "Job must be specified") 175 } 176 177 job := ApiJobToStructJob(validateRequest.Job) 178 args := structs.JobValidateRequest{ 179 Job: job, 180 WriteRequest: structs.WriteRequest{ 181 Region: validateRequest.Region, 182 }, 183 } 184 s.parseWriteRequest(req, &args.WriteRequest) 185 args.Namespace = job.Namespace 186 187 var out structs.JobValidateResponse 188 if err := s.agent.RPC("Job.Validate", &args, &out); err != nil { 189 return nil, err 190 } 191 192 return out, nil 193 } 194 195 func (s *HTTPServer) periodicForceRequest(resp http.ResponseWriter, req *http.Request, 196 jobName string) (interface{}, error) { 197 if req.Method != "PUT" && req.Method != "POST" { 198 return nil, CodedError(405, ErrInvalidMethod) 199 } 200 201 args := structs.PeriodicForceRequest{ 202 JobID: jobName, 203 } 204 s.parseWriteRequest(req, &args.WriteRequest) 205 206 var out structs.PeriodicForceResponse 207 if err := s.agent.RPC("Periodic.Force", &args, &out); err != nil { 208 return nil, err 209 } 210 setIndex(resp, out.Index) 211 return out, nil 212 } 213 214 func (s *HTTPServer) jobAllocations(resp http.ResponseWriter, req *http.Request, 215 jobName string) (interface{}, error) { 216 if req.Method != "GET" { 217 return nil, CodedError(405, ErrInvalidMethod) 218 } 219 allAllocs, _ := strconv.ParseBool(req.URL.Query().Get("all")) 220 221 args := structs.JobSpecificRequest{ 222 JobID: jobName, 223 All: allAllocs, 224 } 225 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 226 return nil, nil 227 } 228 229 var out structs.JobAllocationsResponse 230 if err := s.agent.RPC("Job.Allocations", &args, &out); err != nil { 231 return nil, err 232 } 233 234 setMeta(resp, &out.QueryMeta) 235 if out.Allocations == nil { 236 out.Allocations = make([]*structs.AllocListStub, 0) 237 } 238 for _, alloc := range out.Allocations { 239 alloc.SetEventDisplayMessages() 240 } 241 return out.Allocations, nil 242 } 243 244 func (s *HTTPServer) jobEvaluations(resp http.ResponseWriter, req *http.Request, 245 jobName string) (interface{}, error) { 246 if req.Method != "GET" { 247 return nil, CodedError(405, ErrInvalidMethod) 248 } 249 args := structs.JobSpecificRequest{ 250 JobID: jobName, 251 } 252 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 253 return nil, nil 254 } 255 256 var out structs.JobEvaluationsResponse 257 if err := s.agent.RPC("Job.Evaluations", &args, &out); err != nil { 258 return nil, err 259 } 260 261 setMeta(resp, &out.QueryMeta) 262 if out.Evaluations == nil { 263 out.Evaluations = make([]*structs.Evaluation, 0) 264 } 265 return out.Evaluations, nil 266 } 267 268 func (s *HTTPServer) jobDeployments(resp http.ResponseWriter, req *http.Request, 269 jobName string) (interface{}, error) { 270 if req.Method != "GET" { 271 return nil, CodedError(405, ErrInvalidMethod) 272 } 273 all, _ := strconv.ParseBool(req.URL.Query().Get("all")) 274 args := structs.JobSpecificRequest{ 275 JobID: jobName, 276 All: all, 277 } 278 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 279 return nil, nil 280 } 281 282 var out structs.DeploymentListResponse 283 if err := s.agent.RPC("Job.Deployments", &args, &out); err != nil { 284 return nil, err 285 } 286 287 setMeta(resp, &out.QueryMeta) 288 if out.Deployments == nil { 289 out.Deployments = make([]*structs.Deployment, 0) 290 } 291 return out.Deployments, nil 292 } 293 294 func (s *HTTPServer) jobLatestDeployment(resp http.ResponseWriter, req *http.Request, 295 jobName string) (interface{}, error) { 296 if req.Method != "GET" { 297 return nil, CodedError(405, ErrInvalidMethod) 298 } 299 args := structs.JobSpecificRequest{ 300 JobID: jobName, 301 } 302 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 303 return nil, nil 304 } 305 306 var out structs.SingleDeploymentResponse 307 if err := s.agent.RPC("Job.LatestDeployment", &args, &out); err != nil { 308 return nil, err 309 } 310 311 setMeta(resp, &out.QueryMeta) 312 return out.Deployment, nil 313 } 314 315 func (s *HTTPServer) jobCRUD(resp http.ResponseWriter, req *http.Request, 316 jobName string) (interface{}, error) { 317 switch req.Method { 318 case "GET": 319 return s.jobQuery(resp, req, jobName) 320 case "PUT", "POST": 321 return s.jobUpdate(resp, req, jobName) 322 case "DELETE": 323 return s.jobDelete(resp, req, jobName) 324 default: 325 return nil, CodedError(405, ErrInvalidMethod) 326 } 327 } 328 329 func (s *HTTPServer) jobQuery(resp http.ResponseWriter, req *http.Request, 330 jobName string) (interface{}, error) { 331 args := structs.JobSpecificRequest{ 332 JobID: jobName, 333 } 334 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 335 return nil, nil 336 } 337 338 var out structs.SingleJobResponse 339 if err := s.agent.RPC("Job.GetJob", &args, &out); err != nil { 340 return nil, err 341 } 342 343 setMeta(resp, &out.QueryMeta) 344 if out.Job == nil { 345 return nil, CodedError(404, "job not found") 346 } 347 348 // Decode the payload if there is any 349 job := out.Job 350 if len(job.Payload) != 0 { 351 decoded, err := snappy.Decode(nil, out.Job.Payload) 352 if err != nil { 353 return nil, err 354 } 355 job = job.Copy() 356 job.Payload = decoded 357 } 358 359 return job, nil 360 } 361 362 func (s *HTTPServer) jobUpdate(resp http.ResponseWriter, req *http.Request, 363 jobName string) (interface{}, error) { 364 var args api.JobRegisterRequest 365 if err := decodeBody(req, &args); err != nil { 366 return nil, CodedError(400, err.Error()) 367 } 368 if args.Job == nil { 369 return nil, CodedError(400, "Job must be specified") 370 } 371 372 if args.Job.ID == nil { 373 return nil, CodedError(400, "Job ID hasn't been provided") 374 } 375 if jobName != "" && *args.Job.ID != jobName { 376 return nil, CodedError(400, "Job ID does not match name") 377 } 378 379 sJob := ApiJobToStructJob(args.Job) 380 381 regReq := structs.JobRegisterRequest{ 382 Job: sJob, 383 EnforceIndex: args.EnforceIndex, 384 JobModifyIndex: args.JobModifyIndex, 385 PolicyOverride: args.PolicyOverride, 386 WriteRequest: structs.WriteRequest{ 387 Region: args.WriteRequest.Region, 388 AuthToken: args.WriteRequest.SecretID, 389 }, 390 } 391 s.parseWriteRequest(req, ®Req.WriteRequest) 392 regReq.Namespace = sJob.Namespace 393 394 var out structs.JobRegisterResponse 395 if err := s.agent.RPC("Job.Register", ®Req, &out); err != nil { 396 return nil, err 397 } 398 setIndex(resp, out.Index) 399 return out, nil 400 } 401 402 func (s *HTTPServer) jobDelete(resp http.ResponseWriter, req *http.Request, 403 jobName string) (interface{}, error) { 404 405 purgeStr := req.URL.Query().Get("purge") 406 var purgeBool bool 407 if purgeStr != "" { 408 var err error 409 purgeBool, err = strconv.ParseBool(purgeStr) 410 if err != nil { 411 return nil, fmt.Errorf("Failed to parse value of %q (%v) as a bool: %v", "purge", purgeStr, err) 412 } 413 } 414 415 args := structs.JobDeregisterRequest{ 416 JobID: jobName, 417 Purge: purgeBool, 418 } 419 s.parseWriteRequest(req, &args.WriteRequest) 420 421 var out structs.JobDeregisterResponse 422 if err := s.agent.RPC("Job.Deregister", &args, &out); err != nil { 423 return nil, err 424 } 425 setIndex(resp, out.Index) 426 return out, nil 427 } 428 429 func (s *HTTPServer) jobVersions(resp http.ResponseWriter, req *http.Request, 430 jobName string) (interface{}, error) { 431 432 diffsStr := req.URL.Query().Get("diffs") 433 var diffsBool bool 434 if diffsStr != "" { 435 var err error 436 diffsBool, err = strconv.ParseBool(diffsStr) 437 if err != nil { 438 return nil, fmt.Errorf("Failed to parse value of %q (%v) as a bool: %v", "diffs", diffsStr, err) 439 } 440 } 441 442 args := structs.JobVersionsRequest{ 443 JobID: jobName, 444 Diffs: diffsBool, 445 } 446 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 447 return nil, nil 448 } 449 450 var out structs.JobVersionsResponse 451 if err := s.agent.RPC("Job.GetJobVersions", &args, &out); err != nil { 452 return nil, err 453 } 454 455 setMeta(resp, &out.QueryMeta) 456 if len(out.Versions) == 0 { 457 return nil, CodedError(404, "job versions not found") 458 } 459 460 return out, nil 461 } 462 463 func (s *HTTPServer) jobRevert(resp http.ResponseWriter, req *http.Request, 464 jobName string) (interface{}, error) { 465 466 if req.Method != "PUT" && req.Method != "POST" { 467 return nil, CodedError(405, ErrInvalidMethod) 468 } 469 470 var revertRequest structs.JobRevertRequest 471 if err := decodeBody(req, &revertRequest); err != nil { 472 return nil, CodedError(400, err.Error()) 473 } 474 if revertRequest.JobID == "" { 475 return nil, CodedError(400, "JobID must be specified") 476 } 477 if revertRequest.JobID != jobName { 478 return nil, CodedError(400, "Job ID does not match") 479 } 480 481 s.parseWriteRequest(req, &revertRequest.WriteRequest) 482 483 var out structs.JobRegisterResponse 484 if err := s.agent.RPC("Job.Revert", &revertRequest, &out); err != nil { 485 return nil, err 486 } 487 488 setMeta(resp, &out.QueryMeta) 489 return out, nil 490 } 491 492 func (s *HTTPServer) jobStable(resp http.ResponseWriter, req *http.Request, 493 jobName string) (interface{}, error) { 494 495 if req.Method != "PUT" && req.Method != "POST" { 496 return nil, CodedError(405, ErrInvalidMethod) 497 } 498 499 var stableRequest structs.JobStabilityRequest 500 if err := decodeBody(req, &stableRequest); err != nil { 501 return nil, CodedError(400, err.Error()) 502 } 503 if stableRequest.JobID == "" { 504 return nil, CodedError(400, "JobID must be specified") 505 } 506 if stableRequest.JobID != jobName { 507 return nil, CodedError(400, "Job ID does not match") 508 } 509 510 s.parseWriteRequest(req, &stableRequest.WriteRequest) 511 512 var out structs.JobStabilityResponse 513 if err := s.agent.RPC("Job.Stable", &stableRequest, &out); err != nil { 514 return nil, err 515 } 516 517 setIndex(resp, out.Index) 518 return out, nil 519 } 520 521 func (s *HTTPServer) jobSummaryRequest(resp http.ResponseWriter, req *http.Request, name string) (interface{}, error) { 522 args := structs.JobSummaryRequest{ 523 JobID: name, 524 } 525 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 526 return nil, nil 527 } 528 529 var out structs.JobSummaryResponse 530 if err := s.agent.RPC("Job.Summary", &args, &out); err != nil { 531 return nil, err 532 } 533 534 setMeta(resp, &out.QueryMeta) 535 if out.JobSummary == nil { 536 return nil, CodedError(404, "job not found") 537 } 538 setIndex(resp, out.Index) 539 return out.JobSummary, nil 540 } 541 542 func (s *HTTPServer) jobDispatchRequest(resp http.ResponseWriter, req *http.Request, name string) (interface{}, error) { 543 if req.Method != "PUT" && req.Method != "POST" { 544 return nil, CodedError(405, ErrInvalidMethod) 545 } 546 args := structs.JobDispatchRequest{} 547 if err := decodeBody(req, &args); err != nil { 548 return nil, CodedError(400, err.Error()) 549 } 550 if args.JobID != "" && args.JobID != name { 551 return nil, CodedError(400, "Job ID does not match") 552 } 553 if args.JobID == "" { 554 args.JobID = name 555 } 556 557 s.parseWriteRequest(req, &args.WriteRequest) 558 559 var out structs.JobDispatchResponse 560 if err := s.agent.RPC("Job.Dispatch", &args, &out); err != nil { 561 return nil, err 562 } 563 setIndex(resp, out.Index) 564 return out, nil 565 } 566 567 // JobsParseRequest parses a hcl jobspec and returns a api.Job 568 func (s *HTTPServer) JobsParseRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 569 if req.Method != http.MethodPut && req.Method != http.MethodPost { 570 return nil, CodedError(405, ErrInvalidMethod) 571 } 572 573 args := &api.JobsParseRequest{} 574 if err := decodeBody(req, &args); err != nil { 575 return nil, CodedError(400, err.Error()) 576 } 577 if args.JobHCL == "" { 578 return nil, CodedError(400, "Job spec is empty") 579 } 580 581 jobfile := strings.NewReader(args.JobHCL) 582 jobStruct, err := jobspec.Parse(jobfile) 583 if err != nil { 584 return nil, CodedError(400, err.Error()) 585 } 586 587 if args.Canonicalize { 588 jobStruct.Canonicalize() 589 } 590 return jobStruct, nil 591 } 592 593 func ApiJobToStructJob(job *api.Job) *structs.Job { 594 job.Canonicalize() 595 596 j := &structs.Job{ 597 Stop: *job.Stop, 598 Region: *job.Region, 599 Namespace: *job.Namespace, 600 ID: *job.ID, 601 ParentID: *job.ParentID, 602 Name: *job.Name, 603 Type: *job.Type, 604 Priority: *job.Priority, 605 AllAtOnce: *job.AllAtOnce, 606 Datacenters: job.Datacenters, 607 Payload: job.Payload, 608 Meta: job.Meta, 609 VaultToken: *job.VaultToken, 610 Constraints: ApiConstraintsToStructs(job.Constraints), 611 Affinities: ApiAffinitiesToStructs(job.Affinities), 612 } 613 614 // Update has been pushed into the task groups. stagger and max_parallel are 615 // preserved at the job level, but all other values are discarded. The job.Update 616 // api value is merged into TaskGroups already in api.Canonicalize 617 if job.Update != nil { 618 j.Update = structs.UpdateStrategy{} 619 620 if job.Update.Stagger != nil { 621 j.Update.Stagger = *job.Update.Stagger 622 } 623 if job.Update.MaxParallel != nil { 624 j.Update.MaxParallel = *job.Update.MaxParallel 625 } 626 } 627 628 if l := len(job.Spreads); l != 0 { 629 j.Spreads = make([]*structs.Spread, l) 630 for i, apiSpread := range job.Spreads { 631 j.Spreads[i] = ApiSpreadToStructs(apiSpread) 632 } 633 } 634 635 if job.Periodic != nil { 636 j.Periodic = &structs.PeriodicConfig{ 637 Enabled: *job.Periodic.Enabled, 638 SpecType: *job.Periodic.SpecType, 639 ProhibitOverlap: *job.Periodic.ProhibitOverlap, 640 TimeZone: *job.Periodic.TimeZone, 641 } 642 643 if job.Periodic.Spec != nil { 644 j.Periodic.Spec = *job.Periodic.Spec 645 } 646 } 647 648 if job.ParameterizedJob != nil { 649 j.ParameterizedJob = &structs.ParameterizedJobConfig{ 650 Payload: job.ParameterizedJob.Payload, 651 MetaRequired: job.ParameterizedJob.MetaRequired, 652 MetaOptional: job.ParameterizedJob.MetaOptional, 653 } 654 } 655 656 if l := len(job.TaskGroups); l != 0 { 657 j.TaskGroups = make([]*structs.TaskGroup, l) 658 for i, taskGroup := range job.TaskGroups { 659 tg := &structs.TaskGroup{} 660 ApiTgToStructsTG(taskGroup, tg) 661 j.TaskGroups[i] = tg 662 } 663 } 664 665 return j 666 } 667 668 func ApiTgToStructsTG(taskGroup *api.TaskGroup, tg *structs.TaskGroup) { 669 tg.Name = *taskGroup.Name 670 tg.Count = *taskGroup.Count 671 tg.Meta = taskGroup.Meta 672 tg.Constraints = ApiConstraintsToStructs(taskGroup.Constraints) 673 tg.Affinities = ApiAffinitiesToStructs(taskGroup.Affinities) 674 675 tg.RestartPolicy = &structs.RestartPolicy{ 676 Attempts: *taskGroup.RestartPolicy.Attempts, 677 Interval: *taskGroup.RestartPolicy.Interval, 678 Delay: *taskGroup.RestartPolicy.Delay, 679 Mode: *taskGroup.RestartPolicy.Mode, 680 } 681 682 if taskGroup.ReschedulePolicy != nil { 683 tg.ReschedulePolicy = &structs.ReschedulePolicy{ 684 Attempts: *taskGroup.ReschedulePolicy.Attempts, 685 Interval: *taskGroup.ReschedulePolicy.Interval, 686 Delay: *taskGroup.ReschedulePolicy.Delay, 687 DelayFunction: *taskGroup.ReschedulePolicy.DelayFunction, 688 MaxDelay: *taskGroup.ReschedulePolicy.MaxDelay, 689 Unlimited: *taskGroup.ReschedulePolicy.Unlimited, 690 } 691 } 692 693 if taskGroup.Migrate != nil { 694 tg.Migrate = &structs.MigrateStrategy{ 695 MaxParallel: *taskGroup.Migrate.MaxParallel, 696 HealthCheck: *taskGroup.Migrate.HealthCheck, 697 MinHealthyTime: *taskGroup.Migrate.MinHealthyTime, 698 HealthyDeadline: *taskGroup.Migrate.HealthyDeadline, 699 } 700 } 701 702 tg.EphemeralDisk = &structs.EphemeralDisk{ 703 Sticky: *taskGroup.EphemeralDisk.Sticky, 704 SizeMB: *taskGroup.EphemeralDisk.SizeMB, 705 Migrate: *taskGroup.EphemeralDisk.Migrate, 706 } 707 708 if l := len(taskGroup.Spreads); l != 0 { 709 tg.Spreads = make([]*structs.Spread, l) 710 for k, spread := range taskGroup.Spreads { 711 tg.Spreads[k] = ApiSpreadToStructs(spread) 712 } 713 } 714 715 if taskGroup.Update != nil { 716 tg.Update = &structs.UpdateStrategy{ 717 Stagger: *taskGroup.Update.Stagger, 718 MaxParallel: *taskGroup.Update.MaxParallel, 719 HealthCheck: *taskGroup.Update.HealthCheck, 720 MinHealthyTime: *taskGroup.Update.MinHealthyTime, 721 HealthyDeadline: *taskGroup.Update.HealthyDeadline, 722 ProgressDeadline: *taskGroup.Update.ProgressDeadline, 723 Canary: *taskGroup.Update.Canary, 724 } 725 726 // boolPtr fields may be nil, others will have pointers to default values via Canonicalize 727 if taskGroup.Update.AutoRevert != nil { 728 tg.Update.AutoRevert = *taskGroup.Update.AutoRevert 729 } 730 731 if taskGroup.Update.AutoPromote != nil { 732 tg.Update.AutoPromote = *taskGroup.Update.AutoPromote 733 } 734 } 735 736 if l := len(taskGroup.Tasks); l != 0 { 737 tg.Tasks = make([]*structs.Task, l) 738 for l, task := range taskGroup.Tasks { 739 t := &structs.Task{} 740 ApiTaskToStructsTask(task, t) 741 tg.Tasks[l] = t 742 } 743 } 744 } 745 746 // ApiTaskToStructsTask is a copy and type conversion between the API 747 // representation of a task from a struct representation of a task. 748 func ApiTaskToStructsTask(apiTask *api.Task, structsTask *structs.Task) { 749 structsTask.Name = apiTask.Name 750 structsTask.Driver = apiTask.Driver 751 structsTask.User = apiTask.User 752 structsTask.Leader = apiTask.Leader 753 structsTask.Config = apiTask.Config 754 structsTask.Env = apiTask.Env 755 structsTask.Meta = apiTask.Meta 756 structsTask.KillTimeout = *apiTask.KillTimeout 757 structsTask.ShutdownDelay = apiTask.ShutdownDelay 758 structsTask.KillSignal = apiTask.KillSignal 759 structsTask.Constraints = ApiConstraintsToStructs(apiTask.Constraints) 760 structsTask.Affinities = ApiAffinitiesToStructs(apiTask.Affinities) 761 762 if l := len(apiTask.Services); l != 0 { 763 structsTask.Services = make([]*structs.Service, l) 764 for i, service := range apiTask.Services { 765 structsTask.Services[i] = &structs.Service{ 766 Name: service.Name, 767 PortLabel: service.PortLabel, 768 Tags: service.Tags, 769 CanaryTags: service.CanaryTags, 770 AddressMode: service.AddressMode, 771 } 772 773 if l := len(service.Checks); l != 0 { 774 structsTask.Services[i].Checks = make([]*structs.ServiceCheck, l) 775 for j, check := range service.Checks { 776 structsTask.Services[i].Checks[j] = &structs.ServiceCheck{ 777 Name: check.Name, 778 Type: check.Type, 779 Command: check.Command, 780 Args: check.Args, 781 Path: check.Path, 782 Protocol: check.Protocol, 783 PortLabel: check.PortLabel, 784 AddressMode: check.AddressMode, 785 Interval: check.Interval, 786 Timeout: check.Timeout, 787 InitialStatus: check.InitialStatus, 788 TLSSkipVerify: check.TLSSkipVerify, 789 Header: check.Header, 790 Method: check.Method, 791 GRPCService: check.GRPCService, 792 GRPCUseTLS: check.GRPCUseTLS, 793 } 794 if check.CheckRestart != nil { 795 structsTask.Services[i].Checks[j].CheckRestart = &structs.CheckRestart{ 796 Limit: check.CheckRestart.Limit, 797 Grace: *check.CheckRestart.Grace, 798 IgnoreWarnings: check.CheckRestart.IgnoreWarnings, 799 } 800 } 801 } 802 } 803 } 804 } 805 806 structsTask.Resources = ApiResourcesToStructs(apiTask.Resources) 807 808 structsTask.LogConfig = &structs.LogConfig{ 809 MaxFiles: *apiTask.LogConfig.MaxFiles, 810 MaxFileSizeMB: *apiTask.LogConfig.MaxFileSizeMB, 811 } 812 813 if l := len(apiTask.Artifacts); l != 0 { 814 structsTask.Artifacts = make([]*structs.TaskArtifact, l) 815 for k, ta := range apiTask.Artifacts { 816 structsTask.Artifacts[k] = &structs.TaskArtifact{ 817 GetterSource: *ta.GetterSource, 818 GetterOptions: ta.GetterOptions, 819 GetterMode: *ta.GetterMode, 820 RelativeDest: *ta.RelativeDest, 821 } 822 } 823 } 824 825 if apiTask.Vault != nil { 826 structsTask.Vault = &structs.Vault{ 827 Policies: apiTask.Vault.Policies, 828 Env: *apiTask.Vault.Env, 829 ChangeMode: *apiTask.Vault.ChangeMode, 830 ChangeSignal: *apiTask.Vault.ChangeSignal, 831 } 832 } 833 834 if l := len(apiTask.Templates); l != 0 { 835 structsTask.Templates = make([]*structs.Template, l) 836 for i, template := range apiTask.Templates { 837 structsTask.Templates[i] = &structs.Template{ 838 SourcePath: *template.SourcePath, 839 DestPath: *template.DestPath, 840 EmbeddedTmpl: *template.EmbeddedTmpl, 841 ChangeMode: *template.ChangeMode, 842 ChangeSignal: *template.ChangeSignal, 843 Splay: *template.Splay, 844 Perms: *template.Perms, 845 LeftDelim: *template.LeftDelim, 846 RightDelim: *template.RightDelim, 847 Envvars: *template.Envvars, 848 VaultGrace: *template.VaultGrace, 849 } 850 } 851 } 852 853 if apiTask.DispatchPayload != nil { 854 structsTask.DispatchPayload = &structs.DispatchPayloadConfig{ 855 File: apiTask.DispatchPayload.File, 856 } 857 } 858 } 859 860 func ApiResourcesToStructs(in *api.Resources) *structs.Resources { 861 if in == nil { 862 return nil 863 } 864 865 out := &structs.Resources{ 866 CPU: *in.CPU, 867 MemoryMB: *in.MemoryMB, 868 } 869 870 // COMPAT(0.10): Only being used to issue warnings 871 if in.IOPS != nil { 872 out.IOPS = *in.IOPS 873 } 874 875 if l := len(in.Networks); l != 0 { 876 out.Networks = make([]*structs.NetworkResource, l) 877 for i, nw := range in.Networks { 878 out.Networks[i] = &structs.NetworkResource{ 879 CIDR: nw.CIDR, 880 IP: nw.IP, 881 MBits: *nw.MBits, 882 } 883 884 if l := len(nw.DynamicPorts); l != 0 { 885 out.Networks[i].DynamicPorts = make([]structs.Port, l) 886 for j, dp := range nw.DynamicPorts { 887 out.Networks[i].DynamicPorts[j] = structs.Port{ 888 Label: dp.Label, 889 Value: dp.Value, 890 } 891 } 892 } 893 894 if l := len(nw.ReservedPorts); l != 0 { 895 out.Networks[i].ReservedPorts = make([]structs.Port, l) 896 for j, rp := range nw.ReservedPorts { 897 out.Networks[i].ReservedPorts[j] = structs.Port{ 898 Label: rp.Label, 899 Value: rp.Value, 900 } 901 } 902 } 903 } 904 } 905 906 if l := len(in.Devices); l != 0 { 907 out.Devices = make([]*structs.RequestedDevice, l) 908 for i, d := range in.Devices { 909 out.Devices[i] = &structs.RequestedDevice{ 910 Name: d.Name, 911 Count: *d.Count, 912 Constraints: ApiConstraintsToStructs(d.Constraints), 913 Affinities: ApiAffinitiesToStructs(d.Affinities), 914 } 915 } 916 } 917 918 return out 919 } 920 921 func ApiConstraintsToStructs(in []*api.Constraint) []*structs.Constraint { 922 if in == nil { 923 return nil 924 } 925 926 out := make([]*structs.Constraint, len(in)) 927 for i, ac := range in { 928 out[i] = ApiConstraintToStructs(ac) 929 } 930 931 return out 932 } 933 934 func ApiConstraintToStructs(in *api.Constraint) *structs.Constraint { 935 if in == nil { 936 return nil 937 } 938 939 return &structs.Constraint{ 940 LTarget: in.LTarget, 941 RTarget: in.RTarget, 942 Operand: in.Operand, 943 } 944 } 945 946 func ApiAffinitiesToStructs(in []*api.Affinity) []*structs.Affinity { 947 if in == nil { 948 return nil 949 } 950 951 out := make([]*structs.Affinity, len(in)) 952 for i, ac := range in { 953 out[i] = ApiAffinityToStructs(ac) 954 } 955 956 return out 957 } 958 959 func ApiAffinityToStructs(a1 *api.Affinity) *structs.Affinity { 960 return &structs.Affinity{ 961 LTarget: a1.LTarget, 962 Operand: a1.Operand, 963 RTarget: a1.RTarget, 964 Weight: *a1.Weight, 965 } 966 } 967 968 func ApiSpreadToStructs(a1 *api.Spread) *structs.Spread { 969 ret := &structs.Spread{} 970 ret.Attribute = a1.Attribute 971 ret.Weight = *a1.Weight 972 if a1.SpreadTarget != nil { 973 ret.SpreadTarget = make([]*structs.SpreadTarget, len(a1.SpreadTarget)) 974 for i, st := range a1.SpreadTarget { 975 ret.SpreadTarget[i] = &structs.SpreadTarget{ 976 Value: st.Value, 977 Percent: st.Percent, 978 } 979 } 980 } 981 return ret 982 }