github.com/smintz/nomad@v0.8.3/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 args := structs.JobEvaluateRequest{ 94 JobID: jobName, 95 } 96 s.parseWriteRequest(req, &args.WriteRequest) 97 98 var out structs.JobRegisterResponse 99 if err := s.agent.RPC("Job.Evaluate", &args, &out); err != nil { 100 return nil, err 101 } 102 setIndex(resp, out.Index) 103 return out, nil 104 } 105 106 func (s *HTTPServer) jobPlan(resp http.ResponseWriter, req *http.Request, 107 jobName string) (interface{}, error) { 108 if req.Method != "PUT" && req.Method != "POST" { 109 return nil, CodedError(405, ErrInvalidMethod) 110 } 111 112 var args api.JobPlanRequest 113 if err := decodeBody(req, &args); err != nil { 114 return nil, CodedError(400, err.Error()) 115 } 116 if args.Job == nil { 117 return nil, CodedError(400, "Job must be specified") 118 } 119 if args.Job.ID == nil { 120 return nil, CodedError(400, "Job must have a valid ID") 121 } 122 if jobName != "" && *args.Job.ID != jobName { 123 return nil, CodedError(400, "Job ID does not match") 124 } 125 126 sJob := ApiJobToStructJob(args.Job) 127 planReq := structs.JobPlanRequest{ 128 Job: sJob, 129 Diff: args.Diff, 130 PolicyOverride: args.PolicyOverride, 131 WriteRequest: structs.WriteRequest{ 132 Region: args.WriteRequest.Region, 133 }, 134 } 135 s.parseWriteRequest(req, &planReq.WriteRequest) 136 planReq.Namespace = sJob.Namespace 137 138 var out structs.JobPlanResponse 139 if err := s.agent.RPC("Job.Plan", &planReq, &out); err != nil { 140 return nil, err 141 } 142 setIndex(resp, out.Index) 143 return out, nil 144 } 145 146 func (s *HTTPServer) ValidateJobRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 147 // Ensure request method is POST or PUT 148 if !(req.Method == "POST" || req.Method == "PUT") { 149 return nil, CodedError(405, ErrInvalidMethod) 150 } 151 152 var validateRequest api.JobValidateRequest 153 if err := decodeBody(req, &validateRequest); err != nil { 154 return nil, CodedError(400, err.Error()) 155 } 156 if validateRequest.Job == nil { 157 return nil, CodedError(400, "Job must be specified") 158 } 159 160 job := ApiJobToStructJob(validateRequest.Job) 161 args := structs.JobValidateRequest{ 162 Job: job, 163 WriteRequest: structs.WriteRequest{ 164 Region: validateRequest.Region, 165 }, 166 } 167 s.parseWriteRequest(req, &args.WriteRequest) 168 args.Namespace = job.Namespace 169 170 var out structs.JobValidateResponse 171 if err := s.agent.RPC("Job.Validate", &args, &out); err != nil { 172 return nil, err 173 } 174 175 return out, nil 176 } 177 178 func (s *HTTPServer) periodicForceRequest(resp http.ResponseWriter, req *http.Request, 179 jobName string) (interface{}, error) { 180 if req.Method != "PUT" && req.Method != "POST" { 181 return nil, CodedError(405, ErrInvalidMethod) 182 } 183 184 args := structs.PeriodicForceRequest{ 185 JobID: jobName, 186 } 187 s.parseWriteRequest(req, &args.WriteRequest) 188 189 var out structs.PeriodicForceResponse 190 if err := s.agent.RPC("Periodic.Force", &args, &out); err != nil { 191 return nil, err 192 } 193 setIndex(resp, out.Index) 194 return out, nil 195 } 196 197 func (s *HTTPServer) jobAllocations(resp http.ResponseWriter, req *http.Request, 198 jobName string) (interface{}, error) { 199 if req.Method != "GET" { 200 return nil, CodedError(405, ErrInvalidMethod) 201 } 202 allAllocs, _ := strconv.ParseBool(req.URL.Query().Get("all")) 203 204 args := structs.JobSpecificRequest{ 205 JobID: jobName, 206 AllAllocs: allAllocs, 207 } 208 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 209 return nil, nil 210 } 211 212 var out structs.JobAllocationsResponse 213 if err := s.agent.RPC("Job.Allocations", &args, &out); err != nil { 214 return nil, err 215 } 216 217 setMeta(resp, &out.QueryMeta) 218 if out.Allocations == nil { 219 out.Allocations = make([]*structs.AllocListStub, 0) 220 } 221 for _, alloc := range out.Allocations { 222 alloc.SetEventDisplayMessages() 223 } 224 return out.Allocations, nil 225 } 226 227 func (s *HTTPServer) jobEvaluations(resp http.ResponseWriter, req *http.Request, 228 jobName string) (interface{}, error) { 229 if req.Method != "GET" { 230 return nil, CodedError(405, ErrInvalidMethod) 231 } 232 args := structs.JobSpecificRequest{ 233 JobID: jobName, 234 } 235 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 236 return nil, nil 237 } 238 239 var out structs.JobEvaluationsResponse 240 if err := s.agent.RPC("Job.Evaluations", &args, &out); err != nil { 241 return nil, err 242 } 243 244 setMeta(resp, &out.QueryMeta) 245 if out.Evaluations == nil { 246 out.Evaluations = make([]*structs.Evaluation, 0) 247 } 248 return out.Evaluations, nil 249 } 250 251 func (s *HTTPServer) jobDeployments(resp http.ResponseWriter, req *http.Request, 252 jobName string) (interface{}, error) { 253 if req.Method != "GET" { 254 return nil, CodedError(405, ErrInvalidMethod) 255 } 256 args := structs.JobSpecificRequest{ 257 JobID: jobName, 258 } 259 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 260 return nil, nil 261 } 262 263 var out structs.DeploymentListResponse 264 if err := s.agent.RPC("Job.Deployments", &args, &out); err != nil { 265 return nil, err 266 } 267 268 setMeta(resp, &out.QueryMeta) 269 if out.Deployments == nil { 270 out.Deployments = make([]*structs.Deployment, 0) 271 } 272 return out.Deployments, nil 273 } 274 275 func (s *HTTPServer) jobLatestDeployment(resp http.ResponseWriter, req *http.Request, 276 jobName string) (interface{}, error) { 277 if req.Method != "GET" { 278 return nil, CodedError(405, ErrInvalidMethod) 279 } 280 args := structs.JobSpecificRequest{ 281 JobID: jobName, 282 } 283 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 284 return nil, nil 285 } 286 287 var out structs.SingleDeploymentResponse 288 if err := s.agent.RPC("Job.LatestDeployment", &args, &out); err != nil { 289 return nil, err 290 } 291 292 setMeta(resp, &out.QueryMeta) 293 return out.Deployment, nil 294 } 295 296 func (s *HTTPServer) jobCRUD(resp http.ResponseWriter, req *http.Request, 297 jobName string) (interface{}, error) { 298 switch req.Method { 299 case "GET": 300 return s.jobQuery(resp, req, jobName) 301 case "PUT", "POST": 302 return s.jobUpdate(resp, req, jobName) 303 case "DELETE": 304 return s.jobDelete(resp, req, jobName) 305 default: 306 return nil, CodedError(405, ErrInvalidMethod) 307 } 308 } 309 310 func (s *HTTPServer) jobQuery(resp http.ResponseWriter, req *http.Request, 311 jobName string) (interface{}, error) { 312 args := structs.JobSpecificRequest{ 313 JobID: jobName, 314 } 315 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 316 return nil, nil 317 } 318 319 var out structs.SingleJobResponse 320 if err := s.agent.RPC("Job.GetJob", &args, &out); err != nil { 321 return nil, err 322 } 323 324 setMeta(resp, &out.QueryMeta) 325 if out.Job == nil { 326 return nil, CodedError(404, "job not found") 327 } 328 329 // Decode the payload if there is any 330 job := out.Job 331 if len(job.Payload) != 0 { 332 decoded, err := snappy.Decode(nil, out.Job.Payload) 333 if err != nil { 334 return nil, err 335 } 336 job = job.Copy() 337 job.Payload = decoded 338 } 339 340 return job, nil 341 } 342 343 func (s *HTTPServer) jobUpdate(resp http.ResponseWriter, req *http.Request, 344 jobName string) (interface{}, error) { 345 var args api.JobRegisterRequest 346 if err := decodeBody(req, &args); err != nil { 347 return nil, CodedError(400, err.Error()) 348 } 349 if args.Job == nil { 350 return nil, CodedError(400, "Job must be specified") 351 } 352 353 if args.Job.ID == nil { 354 return nil, CodedError(400, "Job ID hasn't been provided") 355 } 356 if jobName != "" && *args.Job.ID != jobName { 357 return nil, CodedError(400, "Job ID does not match name") 358 } 359 360 sJob := ApiJobToStructJob(args.Job) 361 362 regReq := structs.JobRegisterRequest{ 363 Job: sJob, 364 EnforceIndex: args.EnforceIndex, 365 JobModifyIndex: args.JobModifyIndex, 366 PolicyOverride: args.PolicyOverride, 367 WriteRequest: structs.WriteRequest{ 368 Region: args.WriteRequest.Region, 369 AuthToken: args.WriteRequest.SecretID, 370 }, 371 } 372 s.parseWriteRequest(req, ®Req.WriteRequest) 373 regReq.Namespace = sJob.Namespace 374 375 var out structs.JobRegisterResponse 376 if err := s.agent.RPC("Job.Register", ®Req, &out); err != nil { 377 return nil, err 378 } 379 setIndex(resp, out.Index) 380 return out, nil 381 } 382 383 func (s *HTTPServer) jobDelete(resp http.ResponseWriter, req *http.Request, 384 jobName string) (interface{}, error) { 385 386 purgeStr := req.URL.Query().Get("purge") 387 var purgeBool bool 388 if purgeStr != "" { 389 var err error 390 purgeBool, err = strconv.ParseBool(purgeStr) 391 if err != nil { 392 return nil, fmt.Errorf("Failed to parse value of %q (%v) as a bool: %v", "purge", purgeStr, err) 393 } 394 } 395 396 args := structs.JobDeregisterRequest{ 397 JobID: jobName, 398 Purge: purgeBool, 399 } 400 s.parseWriteRequest(req, &args.WriteRequest) 401 402 var out structs.JobDeregisterResponse 403 if err := s.agent.RPC("Job.Deregister", &args, &out); err != nil { 404 return nil, err 405 } 406 setIndex(resp, out.Index) 407 return out, nil 408 } 409 410 func (s *HTTPServer) jobVersions(resp http.ResponseWriter, req *http.Request, 411 jobName string) (interface{}, error) { 412 413 diffsStr := req.URL.Query().Get("diffs") 414 var diffsBool bool 415 if diffsStr != "" { 416 var err error 417 diffsBool, err = strconv.ParseBool(diffsStr) 418 if err != nil { 419 return nil, fmt.Errorf("Failed to parse value of %q (%v) as a bool: %v", "diffs", diffsStr, err) 420 } 421 } 422 423 args := structs.JobVersionsRequest{ 424 JobID: jobName, 425 Diffs: diffsBool, 426 } 427 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 428 return nil, nil 429 } 430 431 var out structs.JobVersionsResponse 432 if err := s.agent.RPC("Job.GetJobVersions", &args, &out); err != nil { 433 return nil, err 434 } 435 436 setMeta(resp, &out.QueryMeta) 437 if len(out.Versions) == 0 { 438 return nil, CodedError(404, "job versions not found") 439 } 440 441 return out, nil 442 } 443 444 func (s *HTTPServer) jobRevert(resp http.ResponseWriter, req *http.Request, 445 jobName string) (interface{}, error) { 446 447 if req.Method != "PUT" && req.Method != "POST" { 448 return nil, CodedError(405, ErrInvalidMethod) 449 } 450 451 var revertRequest structs.JobRevertRequest 452 if err := decodeBody(req, &revertRequest); err != nil { 453 return nil, CodedError(400, err.Error()) 454 } 455 if revertRequest.JobID == "" { 456 return nil, CodedError(400, "JobID must be specified") 457 } 458 if revertRequest.JobID != jobName { 459 return nil, CodedError(400, "Job ID does not match") 460 } 461 462 s.parseWriteRequest(req, &revertRequest.WriteRequest) 463 464 var out structs.JobRegisterResponse 465 if err := s.agent.RPC("Job.Revert", &revertRequest, &out); err != nil { 466 return nil, err 467 } 468 469 setMeta(resp, &out.QueryMeta) 470 return out, nil 471 } 472 473 func (s *HTTPServer) jobStable(resp http.ResponseWriter, req *http.Request, 474 jobName string) (interface{}, error) { 475 476 if req.Method != "PUT" && req.Method != "POST" { 477 return nil, CodedError(405, ErrInvalidMethod) 478 } 479 480 var stableRequest structs.JobStabilityRequest 481 if err := decodeBody(req, &stableRequest); err != nil { 482 return nil, CodedError(400, err.Error()) 483 } 484 if stableRequest.JobID == "" { 485 return nil, CodedError(400, "JobID must be specified") 486 } 487 if stableRequest.JobID != jobName { 488 return nil, CodedError(400, "Job ID does not match") 489 } 490 491 s.parseWriteRequest(req, &stableRequest.WriteRequest) 492 493 var out structs.JobStabilityResponse 494 if err := s.agent.RPC("Job.Stable", &stableRequest, &out); err != nil { 495 return nil, err 496 } 497 498 setIndex(resp, out.Index) 499 return out, nil 500 } 501 502 func (s *HTTPServer) jobSummaryRequest(resp http.ResponseWriter, req *http.Request, name string) (interface{}, error) { 503 args := structs.JobSummaryRequest{ 504 JobID: name, 505 } 506 if s.parse(resp, req, &args.Region, &args.QueryOptions) { 507 return nil, nil 508 } 509 510 var out structs.JobSummaryResponse 511 if err := s.agent.RPC("Job.Summary", &args, &out); err != nil { 512 return nil, err 513 } 514 515 setMeta(resp, &out.QueryMeta) 516 if out.JobSummary == nil { 517 return nil, CodedError(404, "job not found") 518 } 519 setIndex(resp, out.Index) 520 return out.JobSummary, nil 521 } 522 523 func (s *HTTPServer) jobDispatchRequest(resp http.ResponseWriter, req *http.Request, name string) (interface{}, error) { 524 if req.Method != "PUT" && req.Method != "POST" { 525 return nil, CodedError(405, ErrInvalidMethod) 526 } 527 args := structs.JobDispatchRequest{} 528 if err := decodeBody(req, &args); err != nil { 529 return nil, CodedError(400, err.Error()) 530 } 531 if args.JobID != "" && args.JobID != name { 532 return nil, CodedError(400, "Job ID does not match") 533 } 534 if args.JobID == "" { 535 args.JobID = name 536 } 537 538 s.parseWriteRequest(req, &args.WriteRequest) 539 540 var out structs.JobDispatchResponse 541 if err := s.agent.RPC("Job.Dispatch", &args, &out); err != nil { 542 return nil, err 543 } 544 setIndex(resp, out.Index) 545 return out, nil 546 } 547 548 // JobsParseRequest parses a hcl jobspec and returns a api.Job 549 func (s *HTTPServer) JobsParseRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 550 if req.Method != http.MethodPut && req.Method != http.MethodPost { 551 return nil, CodedError(405, ErrInvalidMethod) 552 } 553 554 args := &api.JobsParseRequest{} 555 if err := decodeBody(req, &args); err != nil { 556 return nil, CodedError(400, err.Error()) 557 } 558 if args.JobHCL == "" { 559 return nil, CodedError(400, "Job spec is empty") 560 } 561 562 jobfile := strings.NewReader(args.JobHCL) 563 jobStruct, err := jobspec.Parse(jobfile) 564 if err != nil { 565 return nil, CodedError(400, err.Error()) 566 } 567 568 if args.Canonicalize { 569 jobStruct.Canonicalize() 570 } 571 return jobStruct, nil 572 } 573 574 func ApiJobToStructJob(job *api.Job) *structs.Job { 575 job.Canonicalize() 576 577 j := &structs.Job{ 578 Stop: *job.Stop, 579 Region: *job.Region, 580 Namespace: *job.Namespace, 581 ID: *job.ID, 582 ParentID: *job.ParentID, 583 Name: *job.Name, 584 Type: *job.Type, 585 Priority: *job.Priority, 586 AllAtOnce: *job.AllAtOnce, 587 Datacenters: job.Datacenters, 588 Payload: job.Payload, 589 Meta: job.Meta, 590 VaultToken: *job.VaultToken, 591 } 592 593 if l := len(job.Constraints); l != 0 { 594 j.Constraints = make([]*structs.Constraint, l) 595 for i, c := range job.Constraints { 596 con := &structs.Constraint{} 597 ApiConstraintToStructs(c, con) 598 j.Constraints[i] = con 599 } 600 } 601 602 // COMPAT: Remove in 0.7.0. Update has been pushed into the task groups 603 if job.Update != nil { 604 j.Update = structs.UpdateStrategy{} 605 606 if job.Update.Stagger != nil { 607 j.Update.Stagger = *job.Update.Stagger 608 } 609 if job.Update.MaxParallel != nil { 610 j.Update.MaxParallel = *job.Update.MaxParallel 611 } 612 } 613 614 if job.Periodic != nil { 615 j.Periodic = &structs.PeriodicConfig{ 616 Enabled: *job.Periodic.Enabled, 617 SpecType: *job.Periodic.SpecType, 618 ProhibitOverlap: *job.Periodic.ProhibitOverlap, 619 TimeZone: *job.Periodic.TimeZone, 620 } 621 622 if job.Periodic.Spec != nil { 623 j.Periodic.Spec = *job.Periodic.Spec 624 } 625 } 626 627 if job.ParameterizedJob != nil { 628 j.ParameterizedJob = &structs.ParameterizedJobConfig{ 629 Payload: job.ParameterizedJob.Payload, 630 MetaRequired: job.ParameterizedJob.MetaRequired, 631 MetaOptional: job.ParameterizedJob.MetaOptional, 632 } 633 } 634 635 if l := len(job.TaskGroups); l != 0 { 636 j.TaskGroups = make([]*structs.TaskGroup, l) 637 for i, taskGroup := range job.TaskGroups { 638 tg := &structs.TaskGroup{} 639 ApiTgToStructsTG(taskGroup, tg) 640 j.TaskGroups[i] = tg 641 } 642 } 643 644 return j 645 } 646 647 func ApiTgToStructsTG(taskGroup *api.TaskGroup, tg *structs.TaskGroup) { 648 tg.Name = *taskGroup.Name 649 tg.Count = *taskGroup.Count 650 tg.Meta = taskGroup.Meta 651 652 if l := len(taskGroup.Constraints); l != 0 { 653 tg.Constraints = make([]*structs.Constraint, l) 654 for k, constraint := range taskGroup.Constraints { 655 c := &structs.Constraint{} 656 ApiConstraintToStructs(constraint, c) 657 tg.Constraints[k] = c 658 } 659 } 660 661 tg.RestartPolicy = &structs.RestartPolicy{ 662 Attempts: *taskGroup.RestartPolicy.Attempts, 663 Interval: *taskGroup.RestartPolicy.Interval, 664 Delay: *taskGroup.RestartPolicy.Delay, 665 Mode: *taskGroup.RestartPolicy.Mode, 666 } 667 668 if taskGroup.ReschedulePolicy != nil { 669 tg.ReschedulePolicy = &structs.ReschedulePolicy{ 670 Attempts: *taskGroup.ReschedulePolicy.Attempts, 671 Interval: *taskGroup.ReschedulePolicy.Interval, 672 Delay: *taskGroup.ReschedulePolicy.Delay, 673 DelayFunction: *taskGroup.ReschedulePolicy.DelayFunction, 674 MaxDelay: *taskGroup.ReschedulePolicy.MaxDelay, 675 Unlimited: *taskGroup.ReschedulePolicy.Unlimited, 676 } 677 } 678 679 if taskGroup.Migrate != nil { 680 tg.Migrate = &structs.MigrateStrategy{ 681 MaxParallel: *taskGroup.Migrate.MaxParallel, 682 HealthCheck: *taskGroup.Migrate.HealthCheck, 683 MinHealthyTime: *taskGroup.Migrate.MinHealthyTime, 684 HealthyDeadline: *taskGroup.Migrate.HealthyDeadline, 685 } 686 } 687 688 tg.EphemeralDisk = &structs.EphemeralDisk{ 689 Sticky: *taskGroup.EphemeralDisk.Sticky, 690 SizeMB: *taskGroup.EphemeralDisk.SizeMB, 691 Migrate: *taskGroup.EphemeralDisk.Migrate, 692 } 693 694 if taskGroup.Update != nil { 695 tg.Update = &structs.UpdateStrategy{ 696 Stagger: *taskGroup.Update.Stagger, 697 MaxParallel: *taskGroup.Update.MaxParallel, 698 HealthCheck: *taskGroup.Update.HealthCheck, 699 MinHealthyTime: *taskGroup.Update.MinHealthyTime, 700 HealthyDeadline: *taskGroup.Update.HealthyDeadline, 701 AutoRevert: *taskGroup.Update.AutoRevert, 702 Canary: *taskGroup.Update.Canary, 703 } 704 } 705 706 if l := len(taskGroup.Tasks); l != 0 { 707 tg.Tasks = make([]*structs.Task, l) 708 for l, task := range taskGroup.Tasks { 709 t := &structs.Task{} 710 ApiTaskToStructsTask(task, t) 711 tg.Tasks[l] = t 712 } 713 } 714 } 715 716 // ApiTaskToStructsTask is a copy and type conversion between the API 717 // representation of a task from a struct representation of a task. 718 func ApiTaskToStructsTask(apiTask *api.Task, structsTask *structs.Task) { 719 structsTask.Name = apiTask.Name 720 structsTask.Driver = apiTask.Driver 721 structsTask.User = apiTask.User 722 structsTask.Leader = apiTask.Leader 723 structsTask.Config = apiTask.Config 724 structsTask.Env = apiTask.Env 725 structsTask.Meta = apiTask.Meta 726 structsTask.KillTimeout = *apiTask.KillTimeout 727 structsTask.ShutdownDelay = apiTask.ShutdownDelay 728 structsTask.KillSignal = apiTask.KillSignal 729 730 if l := len(apiTask.Constraints); l != 0 { 731 structsTask.Constraints = make([]*structs.Constraint, l) 732 for i, constraint := range apiTask.Constraints { 733 c := &structs.Constraint{} 734 ApiConstraintToStructs(constraint, c) 735 structsTask.Constraints[i] = c 736 } 737 } 738 739 if l := len(apiTask.Services); l != 0 { 740 structsTask.Services = make([]*structs.Service, l) 741 for i, service := range apiTask.Services { 742 structsTask.Services[i] = &structs.Service{ 743 Name: service.Name, 744 PortLabel: service.PortLabel, 745 Tags: service.Tags, 746 AddressMode: service.AddressMode, 747 } 748 749 if l := len(service.Checks); l != 0 { 750 structsTask.Services[i].Checks = make([]*structs.ServiceCheck, l) 751 for j, check := range service.Checks { 752 structsTask.Services[i].Checks[j] = &structs.ServiceCheck{ 753 Name: check.Name, 754 Type: check.Type, 755 Command: check.Command, 756 Args: check.Args, 757 Path: check.Path, 758 Protocol: check.Protocol, 759 PortLabel: check.PortLabel, 760 AddressMode: check.AddressMode, 761 Interval: check.Interval, 762 Timeout: check.Timeout, 763 InitialStatus: check.InitialStatus, 764 TLSSkipVerify: check.TLSSkipVerify, 765 Header: check.Header, 766 Method: check.Method, 767 } 768 if check.CheckRestart != nil { 769 structsTask.Services[i].Checks[j].CheckRestart = &structs.CheckRestart{ 770 Limit: check.CheckRestart.Limit, 771 Grace: *check.CheckRestart.Grace, 772 IgnoreWarnings: check.CheckRestart.IgnoreWarnings, 773 } 774 } 775 } 776 } 777 } 778 } 779 780 structsTask.Resources = &structs.Resources{ 781 CPU: *apiTask.Resources.CPU, 782 MemoryMB: *apiTask.Resources.MemoryMB, 783 IOPS: *apiTask.Resources.IOPS, 784 } 785 786 if l := len(apiTask.Resources.Networks); l != 0 { 787 structsTask.Resources.Networks = make([]*structs.NetworkResource, l) 788 for i, nw := range apiTask.Resources.Networks { 789 structsTask.Resources.Networks[i] = &structs.NetworkResource{ 790 CIDR: nw.CIDR, 791 IP: nw.IP, 792 MBits: *nw.MBits, 793 } 794 795 if l := len(nw.DynamicPorts); l != 0 { 796 structsTask.Resources.Networks[i].DynamicPorts = make([]structs.Port, l) 797 for j, dp := range nw.DynamicPorts { 798 structsTask.Resources.Networks[i].DynamicPorts[j] = structs.Port{ 799 Label: dp.Label, 800 Value: dp.Value, 801 } 802 } 803 } 804 805 if l := len(nw.ReservedPorts); l != 0 { 806 structsTask.Resources.Networks[i].ReservedPorts = make([]structs.Port, l) 807 for j, rp := range nw.ReservedPorts { 808 structsTask.Resources.Networks[i].ReservedPorts[j] = structs.Port{ 809 Label: rp.Label, 810 Value: rp.Value, 811 } 812 } 813 } 814 } 815 } 816 817 structsTask.LogConfig = &structs.LogConfig{ 818 MaxFiles: *apiTask.LogConfig.MaxFiles, 819 MaxFileSizeMB: *apiTask.LogConfig.MaxFileSizeMB, 820 } 821 822 if l := len(apiTask.Artifacts); l != 0 { 823 structsTask.Artifacts = make([]*structs.TaskArtifact, l) 824 for k, ta := range apiTask.Artifacts { 825 structsTask.Artifacts[k] = &structs.TaskArtifact{ 826 GetterSource: *ta.GetterSource, 827 GetterOptions: ta.GetterOptions, 828 GetterMode: *ta.GetterMode, 829 RelativeDest: *ta.RelativeDest, 830 } 831 } 832 } 833 834 if apiTask.Vault != nil { 835 structsTask.Vault = &structs.Vault{ 836 Policies: apiTask.Vault.Policies, 837 Env: *apiTask.Vault.Env, 838 ChangeMode: *apiTask.Vault.ChangeMode, 839 ChangeSignal: *apiTask.Vault.ChangeSignal, 840 } 841 } 842 843 if l := len(apiTask.Templates); l != 0 { 844 structsTask.Templates = make([]*structs.Template, l) 845 for i, template := range apiTask.Templates { 846 structsTask.Templates[i] = &structs.Template{ 847 SourcePath: *template.SourcePath, 848 DestPath: *template.DestPath, 849 EmbeddedTmpl: *template.EmbeddedTmpl, 850 ChangeMode: *template.ChangeMode, 851 ChangeSignal: *template.ChangeSignal, 852 Splay: *template.Splay, 853 Perms: *template.Perms, 854 LeftDelim: *template.LeftDelim, 855 RightDelim: *template.RightDelim, 856 Envvars: *template.Envvars, 857 VaultGrace: *template.VaultGrace, 858 } 859 } 860 } 861 862 if apiTask.DispatchPayload != nil { 863 structsTask.DispatchPayload = &structs.DispatchPayloadConfig{ 864 File: apiTask.DispatchPayload.File, 865 } 866 } 867 } 868 869 func ApiConstraintToStructs(c1 *api.Constraint, c2 *structs.Constraint) { 870 c2.LTarget = c1.LTarget 871 c2.RTarget = c1.RTarget 872 c2.Operand = c1.Operand 873 }