github.com/smithx10/nomad@v0.9.1-rc1/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 AllAllocs: 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 args := structs.JobSpecificRequest{ 274 JobID: jobName, 275 } 276 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 277 return nil, nil 278 } 279 280 var out structs.DeploymentListResponse 281 if err := s.agent.RPC("Job.Deployments", &args, &out); err != nil { 282 return nil, err 283 } 284 285 setMeta(resp, &out.QueryMeta) 286 if out.Deployments == nil { 287 out.Deployments = make([]*structs.Deployment, 0) 288 } 289 return out.Deployments, nil 290 } 291 292 func (s *HTTPServer) jobLatestDeployment(resp http.ResponseWriter, req *http.Request, 293 jobName string) (interface{}, error) { 294 if req.Method != "GET" { 295 return nil, CodedError(405, ErrInvalidMethod) 296 } 297 args := structs.JobSpecificRequest{ 298 JobID: jobName, 299 } 300 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 301 return nil, nil 302 } 303 304 var out structs.SingleDeploymentResponse 305 if err := s.agent.RPC("Job.LatestDeployment", &args, &out); err != nil { 306 return nil, err 307 } 308 309 setMeta(resp, &out.QueryMeta) 310 return out.Deployment, nil 311 } 312 313 func (s *HTTPServer) jobCRUD(resp http.ResponseWriter, req *http.Request, 314 jobName string) (interface{}, error) { 315 switch req.Method { 316 case "GET": 317 return s.jobQuery(resp, req, jobName) 318 case "PUT", "POST": 319 return s.jobUpdate(resp, req, jobName) 320 case "DELETE": 321 return s.jobDelete(resp, req, jobName) 322 default: 323 return nil, CodedError(405, ErrInvalidMethod) 324 } 325 } 326 327 func (s *HTTPServer) jobQuery(resp http.ResponseWriter, req *http.Request, 328 jobName string) (interface{}, error) { 329 args := structs.JobSpecificRequest{ 330 JobID: jobName, 331 } 332 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 333 return nil, nil 334 } 335 336 var out structs.SingleJobResponse 337 if err := s.agent.RPC("Job.GetJob", &args, &out); err != nil { 338 return nil, err 339 } 340 341 setMeta(resp, &out.QueryMeta) 342 if out.Job == nil { 343 return nil, CodedError(404, "job not found") 344 } 345 346 // Decode the payload if there is any 347 job := out.Job 348 if len(job.Payload) != 0 { 349 decoded, err := snappy.Decode(nil, out.Job.Payload) 350 if err != nil { 351 return nil, err 352 } 353 job = job.Copy() 354 job.Payload = decoded 355 } 356 357 return job, nil 358 } 359 360 func (s *HTTPServer) jobUpdate(resp http.ResponseWriter, req *http.Request, 361 jobName string) (interface{}, error) { 362 var args api.JobRegisterRequest 363 if err := decodeBody(req, &args); err != nil { 364 return nil, CodedError(400, err.Error()) 365 } 366 if args.Job == nil { 367 return nil, CodedError(400, "Job must be specified") 368 } 369 370 if args.Job.ID == nil { 371 return nil, CodedError(400, "Job ID hasn't been provided") 372 } 373 if jobName != "" && *args.Job.ID != jobName { 374 return nil, CodedError(400, "Job ID does not match name") 375 } 376 377 sJob := ApiJobToStructJob(args.Job) 378 379 regReq := structs.JobRegisterRequest{ 380 Job: sJob, 381 EnforceIndex: args.EnforceIndex, 382 JobModifyIndex: args.JobModifyIndex, 383 PolicyOverride: args.PolicyOverride, 384 WriteRequest: structs.WriteRequest{ 385 Region: args.WriteRequest.Region, 386 AuthToken: args.WriteRequest.SecretID, 387 }, 388 } 389 s.parseWriteRequest(req, ®Req.WriteRequest) 390 regReq.Namespace = sJob.Namespace 391 392 var out structs.JobRegisterResponse 393 if err := s.agent.RPC("Job.Register", ®Req, &out); err != nil { 394 return nil, err 395 } 396 setIndex(resp, out.Index) 397 return out, nil 398 } 399 400 func (s *HTTPServer) jobDelete(resp http.ResponseWriter, req *http.Request, 401 jobName string) (interface{}, error) { 402 403 purgeStr := req.URL.Query().Get("purge") 404 var purgeBool bool 405 if purgeStr != "" { 406 var err error 407 purgeBool, err = strconv.ParseBool(purgeStr) 408 if err != nil { 409 return nil, fmt.Errorf("Failed to parse value of %q (%v) as a bool: %v", "purge", purgeStr, err) 410 } 411 } 412 413 args := structs.JobDeregisterRequest{ 414 JobID: jobName, 415 Purge: purgeBool, 416 } 417 s.parseWriteRequest(req, &args.WriteRequest) 418 419 var out structs.JobDeregisterResponse 420 if err := s.agent.RPC("Job.Deregister", &args, &out); err != nil { 421 return nil, err 422 } 423 setIndex(resp, out.Index) 424 return out, nil 425 } 426 427 func (s *HTTPServer) jobVersions(resp http.ResponseWriter, req *http.Request, 428 jobName string) (interface{}, error) { 429 430 diffsStr := req.URL.Query().Get("diffs") 431 var diffsBool bool 432 if diffsStr != "" { 433 var err error 434 diffsBool, err = strconv.ParseBool(diffsStr) 435 if err != nil { 436 return nil, fmt.Errorf("Failed to parse value of %q (%v) as a bool: %v", "diffs", diffsStr, err) 437 } 438 } 439 440 args := structs.JobVersionsRequest{ 441 JobID: jobName, 442 Diffs: diffsBool, 443 } 444 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 445 return nil, nil 446 } 447 448 var out structs.JobVersionsResponse 449 if err := s.agent.RPC("Job.GetJobVersions", &args, &out); err != nil { 450 return nil, err 451 } 452 453 setMeta(resp, &out.QueryMeta) 454 if len(out.Versions) == 0 { 455 return nil, CodedError(404, "job versions not found") 456 } 457 458 return out, nil 459 } 460 461 func (s *HTTPServer) jobRevert(resp http.ResponseWriter, req *http.Request, 462 jobName string) (interface{}, error) { 463 464 if req.Method != "PUT" && req.Method != "POST" { 465 return nil, CodedError(405, ErrInvalidMethod) 466 } 467 468 var revertRequest structs.JobRevertRequest 469 if err := decodeBody(req, &revertRequest); err != nil { 470 return nil, CodedError(400, err.Error()) 471 } 472 if revertRequest.JobID == "" { 473 return nil, CodedError(400, "JobID must be specified") 474 } 475 if revertRequest.JobID != jobName { 476 return nil, CodedError(400, "Job ID does not match") 477 } 478 479 s.parseWriteRequest(req, &revertRequest.WriteRequest) 480 481 var out structs.JobRegisterResponse 482 if err := s.agent.RPC("Job.Revert", &revertRequest, &out); err != nil { 483 return nil, err 484 } 485 486 setMeta(resp, &out.QueryMeta) 487 return out, nil 488 } 489 490 func (s *HTTPServer) jobStable(resp http.ResponseWriter, req *http.Request, 491 jobName string) (interface{}, error) { 492 493 if req.Method != "PUT" && req.Method != "POST" { 494 return nil, CodedError(405, ErrInvalidMethod) 495 } 496 497 var stableRequest structs.JobStabilityRequest 498 if err := decodeBody(req, &stableRequest); err != nil { 499 return nil, CodedError(400, err.Error()) 500 } 501 if stableRequest.JobID == "" { 502 return nil, CodedError(400, "JobID must be specified") 503 } 504 if stableRequest.JobID != jobName { 505 return nil, CodedError(400, "Job ID does not match") 506 } 507 508 s.parseWriteRequest(req, &stableRequest.WriteRequest) 509 510 var out structs.JobStabilityResponse 511 if err := s.agent.RPC("Job.Stable", &stableRequest, &out); err != nil { 512 return nil, err 513 } 514 515 setIndex(resp, out.Index) 516 return out, nil 517 } 518 519 func (s *HTTPServer) jobSummaryRequest(resp http.ResponseWriter, req *http.Request, name string) (interface{}, error) { 520 args := structs.JobSummaryRequest{ 521 JobID: name, 522 } 523 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 524 return nil, nil 525 } 526 527 var out structs.JobSummaryResponse 528 if err := s.agent.RPC("Job.Summary", &args, &out); err != nil { 529 return nil, err 530 } 531 532 setMeta(resp, &out.QueryMeta) 533 if out.JobSummary == nil { 534 return nil, CodedError(404, "job not found") 535 } 536 setIndex(resp, out.Index) 537 return out.JobSummary, nil 538 } 539 540 func (s *HTTPServer) jobDispatchRequest(resp http.ResponseWriter, req *http.Request, name string) (interface{}, error) { 541 if req.Method != "PUT" && req.Method != "POST" { 542 return nil, CodedError(405, ErrInvalidMethod) 543 } 544 args := structs.JobDispatchRequest{} 545 if err := decodeBody(req, &args); err != nil { 546 return nil, CodedError(400, err.Error()) 547 } 548 if args.JobID != "" && args.JobID != name { 549 return nil, CodedError(400, "Job ID does not match") 550 } 551 if args.JobID == "" { 552 args.JobID = name 553 } 554 555 s.parseWriteRequest(req, &args.WriteRequest) 556 557 var out structs.JobDispatchResponse 558 if err := s.agent.RPC("Job.Dispatch", &args, &out); err != nil { 559 return nil, err 560 } 561 setIndex(resp, out.Index) 562 return out, nil 563 } 564 565 // JobsParseRequest parses a hcl jobspec and returns a api.Job 566 func (s *HTTPServer) JobsParseRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 567 if req.Method != http.MethodPut && req.Method != http.MethodPost { 568 return nil, CodedError(405, ErrInvalidMethod) 569 } 570 571 args := &api.JobsParseRequest{} 572 if err := decodeBody(req, &args); err != nil { 573 return nil, CodedError(400, err.Error()) 574 } 575 if args.JobHCL == "" { 576 return nil, CodedError(400, "Job spec is empty") 577 } 578 579 jobfile := strings.NewReader(args.JobHCL) 580 jobStruct, err := jobspec.Parse(jobfile) 581 if err != nil { 582 return nil, CodedError(400, err.Error()) 583 } 584 585 if args.Canonicalize { 586 jobStruct.Canonicalize() 587 } 588 return jobStruct, nil 589 } 590 591 func ApiJobToStructJob(job *api.Job) *structs.Job { 592 job.Canonicalize() 593 594 j := &structs.Job{ 595 Stop: *job.Stop, 596 Region: *job.Region, 597 Namespace: *job.Namespace, 598 ID: *job.ID, 599 ParentID: *job.ParentID, 600 Name: *job.Name, 601 Type: *job.Type, 602 Priority: *job.Priority, 603 AllAtOnce: *job.AllAtOnce, 604 Datacenters: job.Datacenters, 605 Payload: job.Payload, 606 Meta: job.Meta, 607 VaultToken: *job.VaultToken, 608 Constraints: ApiConstraintsToStructs(job.Constraints), 609 Affinities: ApiAffinitiesToStructs(job.Affinities), 610 } 611 612 // COMPAT: Remove in 0.7.0. Update has been pushed into the task groups 613 if job.Update != nil { 614 j.Update = structs.UpdateStrategy{} 615 616 if job.Update.Stagger != nil { 617 j.Update.Stagger = *job.Update.Stagger 618 } 619 if job.Update.MaxParallel != nil { 620 j.Update.MaxParallel = *job.Update.MaxParallel 621 } 622 } 623 624 if l := len(job.Spreads); l != 0 { 625 j.Spreads = make([]*structs.Spread, l) 626 for i, apiSpread := range job.Spreads { 627 j.Spreads[i] = ApiSpreadToStructs(apiSpread) 628 } 629 } 630 631 if job.Periodic != nil { 632 j.Periodic = &structs.PeriodicConfig{ 633 Enabled: *job.Periodic.Enabled, 634 SpecType: *job.Periodic.SpecType, 635 ProhibitOverlap: *job.Periodic.ProhibitOverlap, 636 TimeZone: *job.Periodic.TimeZone, 637 } 638 639 if job.Periodic.Spec != nil { 640 j.Periodic.Spec = *job.Periodic.Spec 641 } 642 } 643 644 if job.ParameterizedJob != nil { 645 j.ParameterizedJob = &structs.ParameterizedJobConfig{ 646 Payload: job.ParameterizedJob.Payload, 647 MetaRequired: job.ParameterizedJob.MetaRequired, 648 MetaOptional: job.ParameterizedJob.MetaOptional, 649 } 650 } 651 652 if l := len(job.TaskGroups); l != 0 { 653 j.TaskGroups = make([]*structs.TaskGroup, l) 654 for i, taskGroup := range job.TaskGroups { 655 tg := &structs.TaskGroup{} 656 ApiTgToStructsTG(taskGroup, tg) 657 j.TaskGroups[i] = tg 658 } 659 } 660 661 return j 662 } 663 664 func ApiTgToStructsTG(taskGroup *api.TaskGroup, tg *structs.TaskGroup) { 665 tg.Name = *taskGroup.Name 666 tg.Count = *taskGroup.Count 667 tg.Meta = taskGroup.Meta 668 tg.Constraints = ApiConstraintsToStructs(taskGroup.Constraints) 669 tg.Affinities = ApiAffinitiesToStructs(taskGroup.Affinities) 670 671 tg.RestartPolicy = &structs.RestartPolicy{ 672 Attempts: *taskGroup.RestartPolicy.Attempts, 673 Interval: *taskGroup.RestartPolicy.Interval, 674 Delay: *taskGroup.RestartPolicy.Delay, 675 Mode: *taskGroup.RestartPolicy.Mode, 676 } 677 678 if taskGroup.ReschedulePolicy != nil { 679 tg.ReschedulePolicy = &structs.ReschedulePolicy{ 680 Attempts: *taskGroup.ReschedulePolicy.Attempts, 681 Interval: *taskGroup.ReschedulePolicy.Interval, 682 Delay: *taskGroup.ReschedulePolicy.Delay, 683 DelayFunction: *taskGroup.ReschedulePolicy.DelayFunction, 684 MaxDelay: *taskGroup.ReschedulePolicy.MaxDelay, 685 Unlimited: *taskGroup.ReschedulePolicy.Unlimited, 686 } 687 } 688 689 if taskGroup.Migrate != nil { 690 tg.Migrate = &structs.MigrateStrategy{ 691 MaxParallel: *taskGroup.Migrate.MaxParallel, 692 HealthCheck: *taskGroup.Migrate.HealthCheck, 693 MinHealthyTime: *taskGroup.Migrate.MinHealthyTime, 694 HealthyDeadline: *taskGroup.Migrate.HealthyDeadline, 695 } 696 } 697 698 tg.EphemeralDisk = &structs.EphemeralDisk{ 699 Sticky: *taskGroup.EphemeralDisk.Sticky, 700 SizeMB: *taskGroup.EphemeralDisk.SizeMB, 701 Migrate: *taskGroup.EphemeralDisk.Migrate, 702 } 703 704 if l := len(taskGroup.Spreads); l != 0 { 705 tg.Spreads = make([]*structs.Spread, l) 706 for k, spread := range taskGroup.Spreads { 707 tg.Spreads[k] = ApiSpreadToStructs(spread) 708 } 709 } 710 711 if taskGroup.Update != nil { 712 tg.Update = &structs.UpdateStrategy{ 713 Stagger: *taskGroup.Update.Stagger, 714 MaxParallel: *taskGroup.Update.MaxParallel, 715 HealthCheck: *taskGroup.Update.HealthCheck, 716 MinHealthyTime: *taskGroup.Update.MinHealthyTime, 717 HealthyDeadline: *taskGroup.Update.HealthyDeadline, 718 ProgressDeadline: *taskGroup.Update.ProgressDeadline, 719 AutoRevert: *taskGroup.Update.AutoRevert, 720 Canary: *taskGroup.Update.Canary, 721 } 722 } 723 724 if l := len(taskGroup.Tasks); l != 0 { 725 tg.Tasks = make([]*structs.Task, l) 726 for l, task := range taskGroup.Tasks { 727 t := &structs.Task{} 728 ApiTaskToStructsTask(task, t) 729 tg.Tasks[l] = t 730 } 731 } 732 } 733 734 // ApiTaskToStructsTask is a copy and type conversion between the API 735 // representation of a task from a struct representation of a task. 736 func ApiTaskToStructsTask(apiTask *api.Task, structsTask *structs.Task) { 737 structsTask.Name = apiTask.Name 738 structsTask.Driver = apiTask.Driver 739 structsTask.User = apiTask.User 740 structsTask.Leader = apiTask.Leader 741 structsTask.Config = apiTask.Config 742 structsTask.Env = apiTask.Env 743 structsTask.Meta = apiTask.Meta 744 structsTask.KillTimeout = *apiTask.KillTimeout 745 structsTask.ShutdownDelay = apiTask.ShutdownDelay 746 structsTask.KillSignal = apiTask.KillSignal 747 structsTask.Constraints = ApiConstraintsToStructs(apiTask.Constraints) 748 structsTask.Affinities = ApiAffinitiesToStructs(apiTask.Affinities) 749 750 if l := len(apiTask.Services); l != 0 { 751 structsTask.Services = make([]*structs.Service, l) 752 for i, service := range apiTask.Services { 753 structsTask.Services[i] = &structs.Service{ 754 Name: service.Name, 755 PortLabel: service.PortLabel, 756 Tags: service.Tags, 757 CanaryTags: service.CanaryTags, 758 AddressMode: service.AddressMode, 759 } 760 761 if l := len(service.Checks); l != 0 { 762 structsTask.Services[i].Checks = make([]*structs.ServiceCheck, l) 763 for j, check := range service.Checks { 764 structsTask.Services[i].Checks[j] = &structs.ServiceCheck{ 765 Name: check.Name, 766 Type: check.Type, 767 Command: check.Command, 768 Args: check.Args, 769 Path: check.Path, 770 Protocol: check.Protocol, 771 PortLabel: check.PortLabel, 772 AddressMode: check.AddressMode, 773 Interval: check.Interval, 774 Timeout: check.Timeout, 775 InitialStatus: check.InitialStatus, 776 TLSSkipVerify: check.TLSSkipVerify, 777 Header: check.Header, 778 Method: check.Method, 779 GRPCService: check.GRPCService, 780 GRPCUseTLS: check.GRPCUseTLS, 781 } 782 if check.CheckRestart != nil { 783 structsTask.Services[i].Checks[j].CheckRestart = &structs.CheckRestart{ 784 Limit: check.CheckRestart.Limit, 785 Grace: *check.CheckRestart.Grace, 786 IgnoreWarnings: check.CheckRestart.IgnoreWarnings, 787 } 788 } 789 } 790 } 791 } 792 } 793 794 structsTask.Resources = ApiResourcesToStructs(apiTask.Resources) 795 796 structsTask.LogConfig = &structs.LogConfig{ 797 MaxFiles: *apiTask.LogConfig.MaxFiles, 798 MaxFileSizeMB: *apiTask.LogConfig.MaxFileSizeMB, 799 } 800 801 if l := len(apiTask.Artifacts); l != 0 { 802 structsTask.Artifacts = make([]*structs.TaskArtifact, l) 803 for k, ta := range apiTask.Artifacts { 804 structsTask.Artifacts[k] = &structs.TaskArtifact{ 805 GetterSource: *ta.GetterSource, 806 GetterOptions: ta.GetterOptions, 807 GetterMode: *ta.GetterMode, 808 RelativeDest: *ta.RelativeDest, 809 } 810 } 811 } 812 813 if apiTask.Vault != nil { 814 structsTask.Vault = &structs.Vault{ 815 Policies: apiTask.Vault.Policies, 816 Env: *apiTask.Vault.Env, 817 ChangeMode: *apiTask.Vault.ChangeMode, 818 ChangeSignal: *apiTask.Vault.ChangeSignal, 819 } 820 } 821 822 if l := len(apiTask.Templates); l != 0 { 823 structsTask.Templates = make([]*structs.Template, l) 824 for i, template := range apiTask.Templates { 825 structsTask.Templates[i] = &structs.Template{ 826 SourcePath: *template.SourcePath, 827 DestPath: *template.DestPath, 828 EmbeddedTmpl: *template.EmbeddedTmpl, 829 ChangeMode: *template.ChangeMode, 830 ChangeSignal: *template.ChangeSignal, 831 Splay: *template.Splay, 832 Perms: *template.Perms, 833 LeftDelim: *template.LeftDelim, 834 RightDelim: *template.RightDelim, 835 Envvars: *template.Envvars, 836 VaultGrace: *template.VaultGrace, 837 } 838 } 839 } 840 841 if apiTask.DispatchPayload != nil { 842 structsTask.DispatchPayload = &structs.DispatchPayloadConfig{ 843 File: apiTask.DispatchPayload.File, 844 } 845 } 846 } 847 848 func ApiResourcesToStructs(in *api.Resources) *structs.Resources { 849 if in == nil { 850 return nil 851 } 852 853 out := &structs.Resources{ 854 CPU: *in.CPU, 855 MemoryMB: *in.MemoryMB, 856 } 857 858 // COMPAT(0.10): Only being used to issue warnings 859 if in.IOPS != nil { 860 out.IOPS = *in.IOPS 861 } 862 863 if l := len(in.Networks); l != 0 { 864 out.Networks = make([]*structs.NetworkResource, l) 865 for i, nw := range in.Networks { 866 out.Networks[i] = &structs.NetworkResource{ 867 CIDR: nw.CIDR, 868 IP: nw.IP, 869 MBits: *nw.MBits, 870 } 871 872 if l := len(nw.DynamicPorts); l != 0 { 873 out.Networks[i].DynamicPorts = make([]structs.Port, l) 874 for j, dp := range nw.DynamicPorts { 875 out.Networks[i].DynamicPorts[j] = structs.Port{ 876 Label: dp.Label, 877 Value: dp.Value, 878 } 879 } 880 } 881 882 if l := len(nw.ReservedPorts); l != 0 { 883 out.Networks[i].ReservedPorts = make([]structs.Port, l) 884 for j, rp := range nw.ReservedPorts { 885 out.Networks[i].ReservedPorts[j] = structs.Port{ 886 Label: rp.Label, 887 Value: rp.Value, 888 } 889 } 890 } 891 } 892 } 893 894 if l := len(in.Devices); l != 0 { 895 out.Devices = make([]*structs.RequestedDevice, l) 896 for i, d := range in.Devices { 897 out.Devices[i] = &structs.RequestedDevice{ 898 Name: d.Name, 899 Count: *d.Count, 900 Constraints: ApiConstraintsToStructs(d.Constraints), 901 Affinities: ApiAffinitiesToStructs(d.Affinities), 902 } 903 } 904 } 905 906 return out 907 } 908 909 func ApiConstraintsToStructs(in []*api.Constraint) []*structs.Constraint { 910 if in == nil { 911 return nil 912 } 913 914 out := make([]*structs.Constraint, len(in)) 915 for i, ac := range in { 916 out[i] = ApiConstraintToStructs(ac) 917 } 918 919 return out 920 } 921 922 func ApiConstraintToStructs(in *api.Constraint) *structs.Constraint { 923 if in == nil { 924 return nil 925 } 926 927 return &structs.Constraint{ 928 LTarget: in.LTarget, 929 RTarget: in.RTarget, 930 Operand: in.Operand, 931 } 932 } 933 934 func ApiAffinitiesToStructs(in []*api.Affinity) []*structs.Affinity { 935 if in == nil { 936 return nil 937 } 938 939 out := make([]*structs.Affinity, len(in)) 940 for i, ac := range in { 941 out[i] = ApiAffinityToStructs(ac) 942 } 943 944 return out 945 } 946 947 func ApiAffinityToStructs(a1 *api.Affinity) *structs.Affinity { 948 return &structs.Affinity{ 949 LTarget: a1.LTarget, 950 Operand: a1.Operand, 951 RTarget: a1.RTarget, 952 Weight: *a1.Weight, 953 } 954 } 955 956 func ApiSpreadToStructs(a1 *api.Spread) *structs.Spread { 957 ret := &structs.Spread{} 958 ret.Attribute = a1.Attribute 959 ret.Weight = *a1.Weight 960 if a1.SpreadTarget != nil { 961 ret.SpreadTarget = make([]*structs.SpreadTarget, len(a1.SpreadTarget)) 962 for i, st := range a1.SpreadTarget { 963 ret.SpreadTarget[i] = &structs.SpreadTarget{ 964 Value: st.Value, 965 Percent: st.Percent, 966 } 967 } 968 } 969 return ret 970 }