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