github.com/zhizhiboom/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/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 } 609 610 if l := len(job.Constraints); l != 0 { 611 j.Constraints = make([]*structs.Constraint, l) 612 for i, c := range job.Constraints { 613 con := &structs.Constraint{} 614 ApiConstraintToStructs(c, con) 615 j.Constraints[i] = con 616 } 617 } 618 619 if l := len(job.Affinities); l != 0 { 620 j.Affinities = make([]*structs.Affinity, l) 621 for i, a := range job.Affinities { 622 j.Affinities[i] = ApiAffinityToStructs(a) 623 } 624 } 625 626 // COMPAT: Remove in 0.7.0. Update has been pushed into the task groups 627 if job.Update != nil { 628 j.Update = structs.UpdateStrategy{} 629 630 if job.Update.Stagger != nil { 631 j.Update.Stagger = *job.Update.Stagger 632 } 633 if job.Update.MaxParallel != nil { 634 j.Update.MaxParallel = *job.Update.MaxParallel 635 } 636 } 637 638 if l := len(job.Spreads); l != 0 { 639 j.Spreads = make([]*structs.Spread, l) 640 for i, apiSpread := range job.Spreads { 641 j.Spreads[i] = ApiSpreadToStructs(apiSpread) 642 } 643 } 644 645 if job.Periodic != nil { 646 j.Periodic = &structs.PeriodicConfig{ 647 Enabled: *job.Periodic.Enabled, 648 SpecType: *job.Periodic.SpecType, 649 ProhibitOverlap: *job.Periodic.ProhibitOverlap, 650 TimeZone: *job.Periodic.TimeZone, 651 } 652 653 if job.Periodic.Spec != nil { 654 j.Periodic.Spec = *job.Periodic.Spec 655 } 656 } 657 658 if job.ParameterizedJob != nil { 659 j.ParameterizedJob = &structs.ParameterizedJobConfig{ 660 Payload: job.ParameterizedJob.Payload, 661 MetaRequired: job.ParameterizedJob.MetaRequired, 662 MetaOptional: job.ParameterizedJob.MetaOptional, 663 } 664 } 665 666 if l := len(job.TaskGroups); l != 0 { 667 j.TaskGroups = make([]*structs.TaskGroup, l) 668 for i, taskGroup := range job.TaskGroups { 669 tg := &structs.TaskGroup{} 670 ApiTgToStructsTG(taskGroup, tg) 671 j.TaskGroups[i] = tg 672 } 673 } 674 675 return j 676 } 677 678 func ApiTgToStructsTG(taskGroup *api.TaskGroup, tg *structs.TaskGroup) { 679 tg.Name = *taskGroup.Name 680 tg.Count = *taskGroup.Count 681 tg.Meta = taskGroup.Meta 682 683 if l := len(taskGroup.Constraints); l != 0 { 684 tg.Constraints = make([]*structs.Constraint, l) 685 for k, constraint := range taskGroup.Constraints { 686 c := &structs.Constraint{} 687 ApiConstraintToStructs(constraint, c) 688 tg.Constraints[k] = c 689 } 690 } 691 692 if l := len(taskGroup.Affinities); l != 0 { 693 tg.Affinities = make([]*structs.Affinity, l) 694 for k, affinity := range taskGroup.Affinities { 695 tg.Affinities[k] = ApiAffinityToStructs(affinity) 696 } 697 } 698 699 tg.RestartPolicy = &structs.RestartPolicy{ 700 Attempts: *taskGroup.RestartPolicy.Attempts, 701 Interval: *taskGroup.RestartPolicy.Interval, 702 Delay: *taskGroup.RestartPolicy.Delay, 703 Mode: *taskGroup.RestartPolicy.Mode, 704 } 705 706 if taskGroup.ReschedulePolicy != nil { 707 tg.ReschedulePolicy = &structs.ReschedulePolicy{ 708 Attempts: *taskGroup.ReschedulePolicy.Attempts, 709 Interval: *taskGroup.ReschedulePolicy.Interval, 710 Delay: *taskGroup.ReschedulePolicy.Delay, 711 DelayFunction: *taskGroup.ReschedulePolicy.DelayFunction, 712 MaxDelay: *taskGroup.ReschedulePolicy.MaxDelay, 713 Unlimited: *taskGroup.ReschedulePolicy.Unlimited, 714 } 715 } 716 717 if taskGroup.Migrate != nil { 718 tg.Migrate = &structs.MigrateStrategy{ 719 MaxParallel: *taskGroup.Migrate.MaxParallel, 720 HealthCheck: *taskGroup.Migrate.HealthCheck, 721 MinHealthyTime: *taskGroup.Migrate.MinHealthyTime, 722 HealthyDeadline: *taskGroup.Migrate.HealthyDeadline, 723 } 724 } 725 726 tg.EphemeralDisk = &structs.EphemeralDisk{ 727 Sticky: *taskGroup.EphemeralDisk.Sticky, 728 SizeMB: *taskGroup.EphemeralDisk.SizeMB, 729 Migrate: *taskGroup.EphemeralDisk.Migrate, 730 } 731 732 if l := len(taskGroup.Spreads); l != 0 { 733 tg.Spreads = make([]*structs.Spread, l) 734 for k, spread := range taskGroup.Spreads { 735 tg.Spreads[k] = ApiSpreadToStructs(spread) 736 } 737 } 738 739 if taskGroup.Update != nil { 740 tg.Update = &structs.UpdateStrategy{ 741 Stagger: *taskGroup.Update.Stagger, 742 MaxParallel: *taskGroup.Update.MaxParallel, 743 HealthCheck: *taskGroup.Update.HealthCheck, 744 MinHealthyTime: *taskGroup.Update.MinHealthyTime, 745 HealthyDeadline: *taskGroup.Update.HealthyDeadline, 746 ProgressDeadline: *taskGroup.Update.ProgressDeadline, 747 AutoRevert: *taskGroup.Update.AutoRevert, 748 Canary: *taskGroup.Update.Canary, 749 } 750 } 751 752 if l := len(taskGroup.Tasks); l != 0 { 753 tg.Tasks = make([]*structs.Task, l) 754 for l, task := range taskGroup.Tasks { 755 t := &structs.Task{} 756 ApiTaskToStructsTask(task, t) 757 tg.Tasks[l] = t 758 } 759 } 760 } 761 762 // ApiTaskToStructsTask is a copy and type conversion between the API 763 // representation of a task from a struct representation of a task. 764 func ApiTaskToStructsTask(apiTask *api.Task, structsTask *structs.Task) { 765 structsTask.Name = apiTask.Name 766 structsTask.Driver = apiTask.Driver 767 structsTask.User = apiTask.User 768 structsTask.Leader = apiTask.Leader 769 structsTask.Config = apiTask.Config 770 structsTask.Env = apiTask.Env 771 structsTask.Meta = apiTask.Meta 772 structsTask.KillTimeout = *apiTask.KillTimeout 773 structsTask.ShutdownDelay = apiTask.ShutdownDelay 774 structsTask.KillSignal = apiTask.KillSignal 775 776 if l := len(apiTask.Constraints); l != 0 { 777 structsTask.Constraints = make([]*structs.Constraint, l) 778 for i, constraint := range apiTask.Constraints { 779 c := &structs.Constraint{} 780 ApiConstraintToStructs(constraint, c) 781 structsTask.Constraints[i] = c 782 } 783 } 784 785 if l := len(apiTask.Affinities); l != 0 { 786 structsTask.Affinities = make([]*structs.Affinity, l) 787 for i, a := range apiTask.Affinities { 788 structsTask.Affinities[i] = ApiAffinityToStructs(a) 789 } 790 } 791 792 if l := len(apiTask.Services); l != 0 { 793 structsTask.Services = make([]*structs.Service, l) 794 for i, service := range apiTask.Services { 795 structsTask.Services[i] = &structs.Service{ 796 Name: service.Name, 797 PortLabel: service.PortLabel, 798 Tags: service.Tags, 799 CanaryTags: service.CanaryTags, 800 AddressMode: service.AddressMode, 801 } 802 803 if l := len(service.Checks); l != 0 { 804 structsTask.Services[i].Checks = make([]*structs.ServiceCheck, l) 805 for j, check := range service.Checks { 806 structsTask.Services[i].Checks[j] = &structs.ServiceCheck{ 807 Name: check.Name, 808 Type: check.Type, 809 Command: check.Command, 810 Args: check.Args, 811 Path: check.Path, 812 Protocol: check.Protocol, 813 PortLabel: check.PortLabel, 814 AddressMode: check.AddressMode, 815 Interval: check.Interval, 816 Timeout: check.Timeout, 817 InitialStatus: check.InitialStatus, 818 TLSSkipVerify: check.TLSSkipVerify, 819 Header: check.Header, 820 Method: check.Method, 821 GRPCService: check.GRPCService, 822 GRPCUseTLS: check.GRPCUseTLS, 823 } 824 if check.CheckRestart != nil { 825 structsTask.Services[i].Checks[j].CheckRestart = &structs.CheckRestart{ 826 Limit: check.CheckRestart.Limit, 827 Grace: *check.CheckRestart.Grace, 828 IgnoreWarnings: check.CheckRestart.IgnoreWarnings, 829 } 830 } 831 } 832 } 833 } 834 } 835 836 structsTask.Resources = &structs.Resources{ 837 CPU: *apiTask.Resources.CPU, 838 MemoryMB: *apiTask.Resources.MemoryMB, 839 IOPS: *apiTask.Resources.IOPS, 840 } 841 842 if l := len(apiTask.Resources.Networks); l != 0 { 843 structsTask.Resources.Networks = make([]*structs.NetworkResource, l) 844 for i, nw := range apiTask.Resources.Networks { 845 structsTask.Resources.Networks[i] = &structs.NetworkResource{ 846 CIDR: nw.CIDR, 847 IP: nw.IP, 848 MBits: *nw.MBits, 849 } 850 851 if l := len(nw.DynamicPorts); l != 0 { 852 structsTask.Resources.Networks[i].DynamicPorts = make([]structs.Port, l) 853 for j, dp := range nw.DynamicPorts { 854 structsTask.Resources.Networks[i].DynamicPorts[j] = structs.Port{ 855 Label: dp.Label, 856 Value: dp.Value, 857 } 858 } 859 } 860 861 if l := len(nw.ReservedPorts); l != 0 { 862 structsTask.Resources.Networks[i].ReservedPorts = make([]structs.Port, l) 863 for j, rp := range nw.ReservedPorts { 864 structsTask.Resources.Networks[i].ReservedPorts[j] = structs.Port{ 865 Label: rp.Label, 866 Value: rp.Value, 867 } 868 } 869 } 870 } 871 } 872 873 structsTask.LogConfig = &structs.LogConfig{ 874 MaxFiles: *apiTask.LogConfig.MaxFiles, 875 MaxFileSizeMB: *apiTask.LogConfig.MaxFileSizeMB, 876 } 877 878 if l := len(apiTask.Artifacts); l != 0 { 879 structsTask.Artifacts = make([]*structs.TaskArtifact, l) 880 for k, ta := range apiTask.Artifacts { 881 structsTask.Artifacts[k] = &structs.TaskArtifact{ 882 GetterSource: *ta.GetterSource, 883 GetterOptions: ta.GetterOptions, 884 GetterMode: *ta.GetterMode, 885 RelativeDest: *ta.RelativeDest, 886 } 887 } 888 } 889 890 if apiTask.Vault != nil { 891 structsTask.Vault = &structs.Vault{ 892 Policies: apiTask.Vault.Policies, 893 Env: *apiTask.Vault.Env, 894 ChangeMode: *apiTask.Vault.ChangeMode, 895 ChangeSignal: *apiTask.Vault.ChangeSignal, 896 } 897 } 898 899 if l := len(apiTask.Templates); l != 0 { 900 structsTask.Templates = make([]*structs.Template, l) 901 for i, template := range apiTask.Templates { 902 structsTask.Templates[i] = &structs.Template{ 903 SourcePath: *template.SourcePath, 904 DestPath: *template.DestPath, 905 EmbeddedTmpl: *template.EmbeddedTmpl, 906 ChangeMode: *template.ChangeMode, 907 ChangeSignal: *template.ChangeSignal, 908 Splay: *template.Splay, 909 Perms: *template.Perms, 910 LeftDelim: *template.LeftDelim, 911 RightDelim: *template.RightDelim, 912 Envvars: *template.Envvars, 913 VaultGrace: *template.VaultGrace, 914 } 915 } 916 } 917 918 if apiTask.DispatchPayload != nil { 919 structsTask.DispatchPayload = &structs.DispatchPayloadConfig{ 920 File: apiTask.DispatchPayload.File, 921 } 922 } 923 } 924 925 func ApiConstraintToStructs(c1 *api.Constraint, c2 *structs.Constraint) { 926 c2.LTarget = c1.LTarget 927 c2.RTarget = c1.RTarget 928 c2.Operand = c1.Operand 929 } 930 931 func ApiAffinityToStructs(a1 *api.Affinity) *structs.Affinity { 932 return &structs.Affinity{ 933 LTarget: a1.LTarget, 934 Operand: a1.Operand, 935 RTarget: a1.RTarget, 936 Weight: a1.Weight, 937 } 938 } 939 940 func ApiSpreadToStructs(a1 *api.Spread) *structs.Spread { 941 ret := &structs.Spread{} 942 ret.Attribute = a1.Attribute 943 ret.Weight = a1.Weight 944 if a1.SpreadTarget != nil { 945 ret.SpreadTarget = make([]*structs.SpreadTarget, len(a1.SpreadTarget)) 946 for i, st := range a1.SpreadTarget { 947 ret.SpreadTarget[i] = &structs.SpreadTarget{ 948 Value: st.Value, 949 Percent: st.Percent, 950 } 951 } 952 } 953 return ret 954 }