github.com/karlem/nomad@v0.10.2-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/helper" 12 "github.com/hashicorp/nomad/jobspec" 13 "github.com/hashicorp/nomad/nomad/structs" 14 ) 15 16 func (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 27 func (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 45 func (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 89 func (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 124 func (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 177 func (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 210 func (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 229 func (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 259 func (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 283 func (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 309 func (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 330 func (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 344 func (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 377 func (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 429 func (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 456 func (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 490 func (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 519 func (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 548 func (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 569 func (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 595 func (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 620 func 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 695 func 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.ReschedulePolicy != nil { 712 tg.ReschedulePolicy = &structs.ReschedulePolicy{ 713 Attempts: *taskGroup.ReschedulePolicy.Attempts, 714 Interval: *taskGroup.ReschedulePolicy.Interval, 715 Delay: *taskGroup.ReschedulePolicy.Delay, 716 DelayFunction: *taskGroup.ReschedulePolicy.DelayFunction, 717 MaxDelay: *taskGroup.ReschedulePolicy.MaxDelay, 718 Unlimited: *taskGroup.ReschedulePolicy.Unlimited, 719 } 720 } 721 722 if taskGroup.Migrate != nil { 723 tg.Migrate = &structs.MigrateStrategy{ 724 MaxParallel: *taskGroup.Migrate.MaxParallel, 725 HealthCheck: *taskGroup.Migrate.HealthCheck, 726 MinHealthyTime: *taskGroup.Migrate.MinHealthyTime, 727 HealthyDeadline: *taskGroup.Migrate.HealthyDeadline, 728 } 729 } 730 731 tg.EphemeralDisk = &structs.EphemeralDisk{ 732 Sticky: *taskGroup.EphemeralDisk.Sticky, 733 SizeMB: *taskGroup.EphemeralDisk.SizeMB, 734 Migrate: *taskGroup.EphemeralDisk.Migrate, 735 } 736 737 if l := len(taskGroup.Spreads); l != 0 { 738 tg.Spreads = make([]*structs.Spread, l) 739 for k, spread := range taskGroup.Spreads { 740 tg.Spreads[k] = ApiSpreadToStructs(spread) 741 } 742 } 743 744 if l := len(taskGroup.Volumes); l != 0 { 745 tg.Volumes = make(map[string]*structs.VolumeRequest, l) 746 for k, v := range taskGroup.Volumes { 747 if v.Type != structs.VolumeTypeHost { 748 // Ignore non-host volumes in this iteration currently. 749 continue 750 } 751 752 vol := &structs.VolumeRequest{ 753 Name: v.Name, 754 Type: v.Type, 755 ReadOnly: v.ReadOnly, 756 Source: v.Source, 757 } 758 759 tg.Volumes[k] = vol 760 } 761 } 762 763 if taskGroup.Update != nil { 764 tg.Update = &structs.UpdateStrategy{ 765 Stagger: *taskGroup.Update.Stagger, 766 MaxParallel: *taskGroup.Update.MaxParallel, 767 HealthCheck: *taskGroup.Update.HealthCheck, 768 MinHealthyTime: *taskGroup.Update.MinHealthyTime, 769 HealthyDeadline: *taskGroup.Update.HealthyDeadline, 770 ProgressDeadline: *taskGroup.Update.ProgressDeadline, 771 Canary: *taskGroup.Update.Canary, 772 } 773 774 // boolPtr fields may be nil, others will have pointers to default values via Canonicalize 775 if taskGroup.Update.AutoRevert != nil { 776 tg.Update.AutoRevert = *taskGroup.Update.AutoRevert 777 } 778 779 if taskGroup.Update.AutoPromote != nil { 780 tg.Update.AutoPromote = *taskGroup.Update.AutoPromote 781 } 782 } 783 784 if l := len(taskGroup.Tasks); l != 0 { 785 tg.Tasks = make([]*structs.Task, l) 786 for l, task := range taskGroup.Tasks { 787 t := &structs.Task{} 788 ApiTaskToStructsTask(task, t) 789 tg.Tasks[l] = t 790 } 791 } 792 } 793 794 // ApiTaskToStructsTask is a copy and type conversion between the API 795 // representation of a task from a struct representation of a task. 796 func ApiTaskToStructsTask(apiTask *api.Task, structsTask *structs.Task) { 797 structsTask.Name = apiTask.Name 798 structsTask.Driver = apiTask.Driver 799 structsTask.User = apiTask.User 800 structsTask.Leader = apiTask.Leader 801 structsTask.Config = apiTask.Config 802 structsTask.Env = apiTask.Env 803 structsTask.Meta = apiTask.Meta 804 structsTask.KillTimeout = *apiTask.KillTimeout 805 structsTask.ShutdownDelay = apiTask.ShutdownDelay 806 structsTask.KillSignal = apiTask.KillSignal 807 structsTask.Kind = structs.TaskKind(apiTask.Kind) 808 structsTask.Constraints = ApiConstraintsToStructs(apiTask.Constraints) 809 structsTask.Affinities = ApiAffinitiesToStructs(apiTask.Affinities) 810 811 if l := len(apiTask.VolumeMounts); l != 0 { 812 structsTask.VolumeMounts = make([]*structs.VolumeMount, l) 813 for i, mount := range apiTask.VolumeMounts { 814 structsTask.VolumeMounts[i] = &structs.VolumeMount{ 815 Volume: *mount.Volume, 816 Destination: *mount.Destination, 817 ReadOnly: *mount.ReadOnly, 818 PropagationMode: *mount.PropagationMode, 819 } 820 } 821 } 822 823 if l := len(apiTask.Services); l != 0 { 824 structsTask.Services = make([]*structs.Service, l) 825 for i, service := range apiTask.Services { 826 structsTask.Services[i] = &structs.Service{ 827 Name: service.Name, 828 PortLabel: service.PortLabel, 829 Tags: service.Tags, 830 CanaryTags: service.CanaryTags, 831 AddressMode: service.AddressMode, 832 Meta: helper.CopyMapStringString(service.Meta), 833 } 834 835 if l := len(service.Checks); l != 0 { 836 structsTask.Services[i].Checks = make([]*structs.ServiceCheck, l) 837 for j, check := range service.Checks { 838 structsTask.Services[i].Checks[j] = &structs.ServiceCheck{ 839 Name: check.Name, 840 Type: check.Type, 841 Command: check.Command, 842 Args: check.Args, 843 Path: check.Path, 844 Protocol: check.Protocol, 845 PortLabel: check.PortLabel, 846 AddressMode: check.AddressMode, 847 Interval: check.Interval, 848 Timeout: check.Timeout, 849 InitialStatus: check.InitialStatus, 850 TLSSkipVerify: check.TLSSkipVerify, 851 Header: check.Header, 852 Method: check.Method, 853 GRPCService: check.GRPCService, 854 GRPCUseTLS: check.GRPCUseTLS, 855 } 856 if check.CheckRestart != nil { 857 structsTask.Services[i].Checks[j].CheckRestart = &structs.CheckRestart{ 858 Limit: check.CheckRestart.Limit, 859 Grace: *check.CheckRestart.Grace, 860 IgnoreWarnings: check.CheckRestart.IgnoreWarnings, 861 } 862 } 863 } 864 } 865 } 866 } 867 868 structsTask.Resources = ApiResourcesToStructs(apiTask.Resources) 869 870 structsTask.LogConfig = &structs.LogConfig{ 871 MaxFiles: *apiTask.LogConfig.MaxFiles, 872 MaxFileSizeMB: *apiTask.LogConfig.MaxFileSizeMB, 873 } 874 875 if l := len(apiTask.Artifacts); l != 0 { 876 structsTask.Artifacts = make([]*structs.TaskArtifact, l) 877 for k, ta := range apiTask.Artifacts { 878 structsTask.Artifacts[k] = &structs.TaskArtifact{ 879 GetterSource: *ta.GetterSource, 880 GetterOptions: ta.GetterOptions, 881 GetterMode: *ta.GetterMode, 882 RelativeDest: *ta.RelativeDest, 883 } 884 } 885 } 886 887 if apiTask.Vault != nil { 888 structsTask.Vault = &structs.Vault{ 889 Policies: apiTask.Vault.Policies, 890 Env: *apiTask.Vault.Env, 891 ChangeMode: *apiTask.Vault.ChangeMode, 892 ChangeSignal: *apiTask.Vault.ChangeSignal, 893 } 894 } 895 896 if l := len(apiTask.Templates); l != 0 { 897 structsTask.Templates = make([]*structs.Template, l) 898 for i, template := range apiTask.Templates { 899 structsTask.Templates[i] = &structs.Template{ 900 SourcePath: *template.SourcePath, 901 DestPath: *template.DestPath, 902 EmbeddedTmpl: *template.EmbeddedTmpl, 903 ChangeMode: *template.ChangeMode, 904 ChangeSignal: *template.ChangeSignal, 905 Splay: *template.Splay, 906 Perms: *template.Perms, 907 LeftDelim: *template.LeftDelim, 908 RightDelim: *template.RightDelim, 909 Envvars: *template.Envvars, 910 VaultGrace: *template.VaultGrace, 911 } 912 } 913 } 914 915 if apiTask.DispatchPayload != nil { 916 structsTask.DispatchPayload = &structs.DispatchPayloadConfig{ 917 File: apiTask.DispatchPayload.File, 918 } 919 } 920 } 921 922 func ApiResourcesToStructs(in *api.Resources) *structs.Resources { 923 if in == nil { 924 return nil 925 } 926 927 out := &structs.Resources{ 928 CPU: *in.CPU, 929 MemoryMB: *in.MemoryMB, 930 } 931 932 // COMPAT(0.10): Only being used to issue warnings 933 if in.IOPS != nil { 934 out.IOPS = *in.IOPS 935 } 936 937 if len(in.Networks) != 0 { 938 out.Networks = ApiNetworkResourceToStructs(in.Networks) 939 } 940 941 if l := len(in.Devices); l != 0 { 942 out.Devices = make([]*structs.RequestedDevice, l) 943 for i, d := range in.Devices { 944 out.Devices[i] = &structs.RequestedDevice{ 945 Name: d.Name, 946 Count: *d.Count, 947 Constraints: ApiConstraintsToStructs(d.Constraints), 948 Affinities: ApiAffinitiesToStructs(d.Affinities), 949 } 950 } 951 } 952 953 return out 954 } 955 956 func ApiNetworkResourceToStructs(in []*api.NetworkResource) []*structs.NetworkResource { 957 var out []*structs.NetworkResource 958 if len(in) == 0 { 959 return out 960 } 961 out = make([]*structs.NetworkResource, len(in)) 962 for i, nw := range in { 963 out[i] = &structs.NetworkResource{ 964 Mode: nw.Mode, 965 CIDR: nw.CIDR, 966 IP: nw.IP, 967 MBits: *nw.MBits, 968 } 969 970 if l := len(nw.DynamicPorts); l != 0 { 971 out[i].DynamicPorts = make([]structs.Port, l) 972 for j, dp := range nw.DynamicPorts { 973 out[i].DynamicPorts[j] = structs.Port{ 974 Label: dp.Label, 975 Value: dp.Value, 976 To: dp.To, 977 } 978 } 979 } 980 981 if l := len(nw.ReservedPorts); l != 0 { 982 out[i].ReservedPorts = make([]structs.Port, l) 983 for j, rp := range nw.ReservedPorts { 984 out[i].ReservedPorts[j] = structs.Port{ 985 Label: rp.Label, 986 Value: rp.Value, 987 To: rp.To, 988 } 989 } 990 } 991 } 992 993 return out 994 } 995 996 //TODO(schmichael) refactor and reuse in service parsing above 997 func ApiServicesToStructs(in []*api.Service) []*structs.Service { 998 if len(in) == 0 { 999 return nil 1000 } 1001 1002 out := make([]*structs.Service, len(in)) 1003 for i, s := range in { 1004 out[i] = &structs.Service{ 1005 Name: s.Name, 1006 PortLabel: s.PortLabel, 1007 Tags: s.Tags, 1008 CanaryTags: s.CanaryTags, 1009 AddressMode: s.AddressMode, 1010 Meta: helper.CopyMapStringString(s.Meta), 1011 } 1012 1013 if l := len(s.Checks); l != 0 { 1014 out[i].Checks = make([]*structs.ServiceCheck, l) 1015 for j, check := range s.Checks { 1016 out[i].Checks[j] = &structs.ServiceCheck{ 1017 Name: check.Name, 1018 Type: check.Type, 1019 Command: check.Command, 1020 Args: check.Args, 1021 Path: check.Path, 1022 Protocol: check.Protocol, 1023 PortLabel: check.PortLabel, 1024 AddressMode: check.AddressMode, 1025 Interval: check.Interval, 1026 Timeout: check.Timeout, 1027 InitialStatus: check.InitialStatus, 1028 TLSSkipVerify: check.TLSSkipVerify, 1029 Header: check.Header, 1030 Method: check.Method, 1031 GRPCService: check.GRPCService, 1032 GRPCUseTLS: check.GRPCUseTLS, 1033 TaskName: check.TaskName, 1034 } 1035 if check.CheckRestart != nil { 1036 out[i].Checks[j].CheckRestart = &structs.CheckRestart{ 1037 Limit: check.CheckRestart.Limit, 1038 Grace: *check.CheckRestart.Grace, 1039 IgnoreWarnings: check.CheckRestart.IgnoreWarnings, 1040 } 1041 } 1042 } 1043 } 1044 1045 if s.Connect != nil { 1046 out[i].Connect = ApiConsulConnectToStructs(s.Connect) 1047 } 1048 1049 } 1050 1051 return out 1052 } 1053 1054 func ApiConsulConnectToStructs(in *api.ConsulConnect) *structs.ConsulConnect { 1055 if in == nil { 1056 return nil 1057 } 1058 1059 out := &structs.ConsulConnect{ 1060 Native: in.Native, 1061 } 1062 1063 if in.SidecarService != nil { 1064 1065 out.SidecarService = &structs.ConsulSidecarService{ 1066 Tags: helper.CopySliceString(in.SidecarService.Tags), 1067 Port: in.SidecarService.Port, 1068 } 1069 1070 if in.SidecarService.Proxy != nil { 1071 1072 out.SidecarService.Proxy = &structs.ConsulProxy{ 1073 LocalServiceAddress: in.SidecarService.Proxy.LocalServiceAddress, 1074 LocalServicePort: in.SidecarService.Proxy.LocalServicePort, 1075 Config: in.SidecarService.Proxy.Config, 1076 } 1077 1078 upstreams := make([]structs.ConsulUpstream, len(in.SidecarService.Proxy.Upstreams)) 1079 for i, p := range in.SidecarService.Proxy.Upstreams { 1080 upstreams[i] = structs.ConsulUpstream{ 1081 DestinationName: p.DestinationName, 1082 LocalBindPort: p.LocalBindPort, 1083 } 1084 } 1085 1086 out.SidecarService.Proxy.Upstreams = upstreams 1087 } 1088 } 1089 1090 if in.SidecarTask != nil { 1091 out.SidecarTask = &structs.SidecarTask{ 1092 Name: in.SidecarTask.Name, 1093 Driver: in.SidecarTask.Driver, 1094 Config: in.SidecarTask.Config, 1095 User: in.SidecarTask.User, 1096 Env: in.SidecarTask.Env, 1097 Resources: ApiResourcesToStructs(in.SidecarTask.Resources), 1098 Meta: in.SidecarTask.Meta, 1099 LogConfig: &structs.LogConfig{}, 1100 ShutdownDelay: in.SidecarTask.ShutdownDelay, 1101 KillSignal: in.SidecarTask.KillSignal, 1102 } 1103 1104 if in.SidecarTask.KillTimeout != nil { 1105 out.SidecarTask.KillTimeout = in.SidecarTask.KillTimeout 1106 } 1107 if in.SidecarTask.LogConfig != nil { 1108 out.SidecarTask.LogConfig = &structs.LogConfig{} 1109 if in.SidecarTask.LogConfig.MaxFiles != nil { 1110 out.SidecarTask.LogConfig.MaxFiles = *in.SidecarTask.LogConfig.MaxFiles 1111 } 1112 if in.SidecarTask.LogConfig.MaxFileSizeMB != nil { 1113 out.SidecarTask.LogConfig.MaxFileSizeMB = *in.SidecarTask.LogConfig.MaxFileSizeMB 1114 } 1115 } 1116 } 1117 1118 return out 1119 } 1120 1121 func ApiConstraintsToStructs(in []*api.Constraint) []*structs.Constraint { 1122 if in == nil { 1123 return nil 1124 } 1125 1126 out := make([]*structs.Constraint, len(in)) 1127 for i, ac := range in { 1128 out[i] = ApiConstraintToStructs(ac) 1129 } 1130 1131 return out 1132 } 1133 1134 func ApiConstraintToStructs(in *api.Constraint) *structs.Constraint { 1135 if in == nil { 1136 return nil 1137 } 1138 1139 return &structs.Constraint{ 1140 LTarget: in.LTarget, 1141 RTarget: in.RTarget, 1142 Operand: in.Operand, 1143 } 1144 } 1145 1146 func ApiAffinitiesToStructs(in []*api.Affinity) []*structs.Affinity { 1147 if in == nil { 1148 return nil 1149 } 1150 1151 out := make([]*structs.Affinity, len(in)) 1152 for i, ac := range in { 1153 out[i] = ApiAffinityToStructs(ac) 1154 } 1155 1156 return out 1157 } 1158 1159 func ApiAffinityToStructs(a1 *api.Affinity) *structs.Affinity { 1160 return &structs.Affinity{ 1161 LTarget: a1.LTarget, 1162 Operand: a1.Operand, 1163 RTarget: a1.RTarget, 1164 Weight: *a1.Weight, 1165 } 1166 } 1167 1168 func ApiSpreadToStructs(a1 *api.Spread) *structs.Spread { 1169 ret := &structs.Spread{} 1170 ret.Attribute = a1.Attribute 1171 ret.Weight = *a1.Weight 1172 if a1.SpreadTarget != nil { 1173 ret.SpreadTarget = make([]*structs.SpreadTarget, len(a1.SpreadTarget)) 1174 for i, st := range a1.SpreadTarget { 1175 ret.SpreadTarget[i] = &structs.SpreadTarget{ 1176 Value: st.Value, 1177 Percent: st.Percent, 1178 } 1179 } 1180 } 1181 return ret 1182 }