github.com/smithx10/nomad@v0.9.1-rc1/jobspec/parse.go (about) 1 package jobspec 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "os" 8 "path/filepath" 9 "regexp" 10 "strconv" 11 "strings" 12 "time" 13 14 multierror "github.com/hashicorp/go-multierror" 15 "github.com/hashicorp/hcl" 16 "github.com/hashicorp/hcl/hcl/ast" 17 "github.com/hashicorp/nomad/api" 18 "github.com/hashicorp/nomad/helper" 19 "github.com/hashicorp/nomad/nomad/structs" 20 "github.com/mitchellh/mapstructure" 21 ) 22 23 var reDynamicPorts = regexp.MustCompile("^[a-zA-Z0-9_]+$") 24 var errPortLabel = fmt.Errorf("Port label does not conform to naming requirements %s", reDynamicPorts.String()) 25 26 // Parse parses the job spec from the given io.Reader. 27 // 28 // Due to current internal limitations, the entire contents of the 29 // io.Reader will be copied into memory first before parsing. 30 func Parse(r io.Reader) (*api.Job, error) { 31 // Copy the reader into an in-memory buffer first since HCL requires it. 32 var buf bytes.Buffer 33 if _, err := io.Copy(&buf, r); err != nil { 34 return nil, err 35 } 36 37 // Parse the buffer 38 root, err := hcl.Parse(buf.String()) 39 if err != nil { 40 return nil, fmt.Errorf("error parsing: %s", err) 41 } 42 buf.Reset() 43 44 // Top-level item should be a list 45 list, ok := root.Node.(*ast.ObjectList) 46 if !ok { 47 return nil, fmt.Errorf("error parsing: root should be an object") 48 } 49 50 // Check for invalid keys 51 valid := []string{ 52 "job", 53 } 54 if err := helper.CheckHCLKeys(list, valid); err != nil { 55 return nil, err 56 } 57 58 var job api.Job 59 60 // Parse the job out 61 matches := list.Filter("job") 62 if len(matches.Items) == 0 { 63 return nil, fmt.Errorf("'job' stanza not found") 64 } 65 if err := parseJob(&job, matches); err != nil { 66 return nil, fmt.Errorf("error parsing 'job': %s", err) 67 } 68 69 return &job, nil 70 } 71 72 // ParseFile parses the given path as a job spec. 73 func ParseFile(path string) (*api.Job, error) { 74 path, err := filepath.Abs(path) 75 if err != nil { 76 return nil, err 77 } 78 79 f, err := os.Open(path) 80 if err != nil { 81 return nil, err 82 } 83 defer f.Close() 84 85 return Parse(f) 86 } 87 88 func parseJob(result *api.Job, list *ast.ObjectList) error { 89 if len(list.Items) != 1 { 90 return fmt.Errorf("only one 'job' block allowed") 91 } 92 list = list.Children() 93 if len(list.Items) != 1 { 94 return fmt.Errorf("'job' block missing name") 95 } 96 97 // Get our job object 98 obj := list.Items[0] 99 100 // Decode the full thing into a map[string]interface for ease 101 var m map[string]interface{} 102 if err := hcl.DecodeObject(&m, obj.Val); err != nil { 103 return err 104 } 105 delete(m, "constraint") 106 delete(m, "affinity") 107 delete(m, "meta") 108 delete(m, "migrate") 109 delete(m, "parameterized") 110 delete(m, "periodic") 111 delete(m, "reschedule") 112 delete(m, "update") 113 delete(m, "vault") 114 delete(m, "spread") 115 116 // Set the ID and name to the object key 117 result.ID = helper.StringToPtr(obj.Keys[0].Token.Value().(string)) 118 result.Name = helper.StringToPtr(*result.ID) 119 120 // Decode the rest 121 if err := mapstructure.WeakDecode(m, result); err != nil { 122 return err 123 } 124 125 // Value should be an object 126 var listVal *ast.ObjectList 127 if ot, ok := obj.Val.(*ast.ObjectType); ok { 128 listVal = ot.List 129 } else { 130 return fmt.Errorf("job '%s' value: should be an object", *result.ID) 131 } 132 133 // Check for invalid keys 134 valid := []string{ 135 "all_at_once", 136 "constraint", 137 "affinity", 138 "spread", 139 "datacenters", 140 "group", 141 "id", 142 "meta", 143 "migrate", 144 "name", 145 "namespace", 146 "parameterized", 147 "periodic", 148 "priority", 149 "region", 150 "reschedule", 151 "task", 152 "type", 153 "update", 154 "vault", 155 "vault_token", 156 } 157 if err := helper.CheckHCLKeys(listVal, valid); err != nil { 158 return multierror.Prefix(err, "job:") 159 } 160 161 // Parse constraints 162 if o := listVal.Filter("constraint"); len(o.Items) > 0 { 163 if err := parseConstraints(&result.Constraints, o); err != nil { 164 return multierror.Prefix(err, "constraint ->") 165 } 166 } 167 168 // Parse affinities 169 if o := listVal.Filter("affinity"); len(o.Items) > 0 { 170 if err := parseAffinities(&result.Affinities, o); err != nil { 171 return multierror.Prefix(err, "affinity ->") 172 } 173 } 174 175 // If we have an update strategy, then parse that 176 if o := listVal.Filter("update"); len(o.Items) > 0 { 177 if err := parseUpdate(&result.Update, o); err != nil { 178 return multierror.Prefix(err, "update ->") 179 } 180 } 181 182 // If we have a periodic definition, then parse that 183 if o := listVal.Filter("periodic"); len(o.Items) > 0 { 184 if err := parsePeriodic(&result.Periodic, o); err != nil { 185 return multierror.Prefix(err, "periodic ->") 186 } 187 } 188 189 // Parse spread 190 if o := listVal.Filter("spread"); len(o.Items) > 0 { 191 if err := parseSpread(&result.Spreads, o); err != nil { 192 return multierror.Prefix(err, "spread ->") 193 } 194 } 195 196 // If we have a parameterized definition, then parse that 197 if o := listVal.Filter("parameterized"); len(o.Items) > 0 { 198 if err := parseParameterizedJob(&result.ParameterizedJob, o); err != nil { 199 return multierror.Prefix(err, "parameterized ->") 200 } 201 } 202 203 // If we have a reschedule stanza, then parse that 204 if o := listVal.Filter("reschedule"); len(o.Items) > 0 { 205 if err := parseReschedulePolicy(&result.Reschedule, o); err != nil { 206 return multierror.Prefix(err, "reschedule ->") 207 } 208 } 209 210 // If we have a migration strategy, then parse that 211 if o := listVal.Filter("migrate"); len(o.Items) > 0 { 212 if err := parseMigrate(&result.Migrate, o); err != nil { 213 return multierror.Prefix(err, "migrate ->") 214 } 215 } 216 217 // Parse out meta fields. These are in HCL as a list so we need 218 // to iterate over them and merge them. 219 if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 { 220 for _, o := range metaO.Elem().Items { 221 var m map[string]interface{} 222 if err := hcl.DecodeObject(&m, o.Val); err != nil { 223 return err 224 } 225 if err := mapstructure.WeakDecode(m, &result.Meta); err != nil { 226 return err 227 } 228 } 229 } 230 231 // If we have tasks outside, create TaskGroups for them 232 if o := listVal.Filter("task"); len(o.Items) > 0 { 233 var tasks []*api.Task 234 if err := parseTasks(*result.Name, "", &tasks, o); err != nil { 235 return multierror.Prefix(err, "task:") 236 } 237 238 result.TaskGroups = make([]*api.TaskGroup, len(tasks), len(tasks)*2) 239 for i, t := range tasks { 240 result.TaskGroups[i] = &api.TaskGroup{ 241 Name: helper.StringToPtr(t.Name), 242 Tasks: []*api.Task{t}, 243 } 244 } 245 } 246 247 // Parse the task groups 248 if o := listVal.Filter("group"); len(o.Items) > 0 { 249 if err := parseGroups(result, o); err != nil { 250 return multierror.Prefix(err, "group:") 251 } 252 } 253 254 // If we have a vault block, then parse that 255 if o := listVal.Filter("vault"); len(o.Items) > 0 { 256 jobVault := &api.Vault{ 257 Env: helper.BoolToPtr(true), 258 ChangeMode: helper.StringToPtr("restart"), 259 } 260 261 if err := parseVault(jobVault, o); err != nil { 262 return multierror.Prefix(err, "vault ->") 263 } 264 265 // Go through the task groups/tasks and if they don't have a Vault block, set it 266 for _, tg := range result.TaskGroups { 267 for _, task := range tg.Tasks { 268 if task.Vault == nil { 269 task.Vault = jobVault 270 } 271 } 272 } 273 } 274 275 return nil 276 } 277 278 func parseGroups(result *api.Job, list *ast.ObjectList) error { 279 list = list.Children() 280 if len(list.Items) == 0 { 281 return nil 282 } 283 284 // Go through each object and turn it into an actual result. 285 collection := make([]*api.TaskGroup, 0, len(list.Items)) 286 seen := make(map[string]struct{}) 287 for _, item := range list.Items { 288 n := item.Keys[0].Token.Value().(string) 289 290 // Make sure we haven't already found this 291 if _, ok := seen[n]; ok { 292 return fmt.Errorf("group '%s' defined more than once", n) 293 } 294 seen[n] = struct{}{} 295 296 // We need this later 297 var listVal *ast.ObjectList 298 if ot, ok := item.Val.(*ast.ObjectType); ok { 299 listVal = ot.List 300 } else { 301 return fmt.Errorf("group '%s': should be an object", n) 302 } 303 304 // Check for invalid keys 305 valid := []string{ 306 "count", 307 "constraint", 308 "affinity", 309 "restart", 310 "meta", 311 "task", 312 "ephemeral_disk", 313 "update", 314 "reschedule", 315 "vault", 316 "migrate", 317 "spread", 318 } 319 if err := helper.CheckHCLKeys(listVal, valid); err != nil { 320 return multierror.Prefix(err, fmt.Sprintf("'%s' ->", n)) 321 } 322 323 var m map[string]interface{} 324 if err := hcl.DecodeObject(&m, item.Val); err != nil { 325 return err 326 } 327 delete(m, "constraint") 328 delete(m, "affinity") 329 delete(m, "meta") 330 delete(m, "task") 331 delete(m, "restart") 332 delete(m, "ephemeral_disk") 333 delete(m, "update") 334 delete(m, "vault") 335 delete(m, "migrate") 336 delete(m, "spread") 337 338 // Build the group with the basic decode 339 var g api.TaskGroup 340 g.Name = helper.StringToPtr(n) 341 if err := mapstructure.WeakDecode(m, &g); err != nil { 342 return err 343 } 344 345 // Parse constraints 346 if o := listVal.Filter("constraint"); len(o.Items) > 0 { 347 if err := parseConstraints(&g.Constraints, o); err != nil { 348 return multierror.Prefix(err, fmt.Sprintf("'%s', constraint ->", n)) 349 } 350 } 351 352 // Parse affinities 353 if o := listVal.Filter("affinity"); len(o.Items) > 0 { 354 if err := parseAffinities(&g.Affinities, o); err != nil { 355 return multierror.Prefix(err, fmt.Sprintf("'%s', affinity ->", n)) 356 } 357 } 358 359 // Parse restart policy 360 if o := listVal.Filter("restart"); len(o.Items) > 0 { 361 if err := parseRestartPolicy(&g.RestartPolicy, o); err != nil { 362 return multierror.Prefix(err, fmt.Sprintf("'%s', restart ->", n)) 363 } 364 } 365 366 // Parse spread 367 if o := listVal.Filter("spread"); len(o.Items) > 0 { 368 if err := parseSpread(&g.Spreads, o); err != nil { 369 return multierror.Prefix(err, "spread ->") 370 } 371 } 372 373 // Parse reschedule policy 374 if o := listVal.Filter("reschedule"); len(o.Items) > 0 { 375 if err := parseReschedulePolicy(&g.ReschedulePolicy, o); err != nil { 376 return multierror.Prefix(err, fmt.Sprintf("'%s', reschedule ->", n)) 377 } 378 } 379 // Parse ephemeral disk 380 if o := listVal.Filter("ephemeral_disk"); len(o.Items) > 0 { 381 g.EphemeralDisk = &api.EphemeralDisk{} 382 if err := parseEphemeralDisk(&g.EphemeralDisk, o); err != nil { 383 return multierror.Prefix(err, fmt.Sprintf("'%s', ephemeral_disk ->", n)) 384 } 385 } 386 387 // If we have an update strategy, then parse that 388 if o := listVal.Filter("update"); len(o.Items) > 0 { 389 if err := parseUpdate(&g.Update, o); err != nil { 390 return multierror.Prefix(err, "update ->") 391 } 392 } 393 394 // If we have a migration strategy, then parse that 395 if o := listVal.Filter("migrate"); len(o.Items) > 0 { 396 if err := parseMigrate(&g.Migrate, o); err != nil { 397 return multierror.Prefix(err, "migrate ->") 398 } 399 } 400 401 // Parse out meta fields. These are in HCL as a list so we need 402 // to iterate over them and merge them. 403 if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 { 404 for _, o := range metaO.Elem().Items { 405 var m map[string]interface{} 406 if err := hcl.DecodeObject(&m, o.Val); err != nil { 407 return err 408 } 409 if err := mapstructure.WeakDecode(m, &g.Meta); err != nil { 410 return err 411 } 412 } 413 } 414 415 // Parse tasks 416 if o := listVal.Filter("task"); len(o.Items) > 0 { 417 if err := parseTasks(*result.Name, *g.Name, &g.Tasks, o); err != nil { 418 return multierror.Prefix(err, fmt.Sprintf("'%s', task:", n)) 419 } 420 } 421 422 // If we have a vault block, then parse that 423 if o := listVal.Filter("vault"); len(o.Items) > 0 { 424 tgVault := &api.Vault{ 425 Env: helper.BoolToPtr(true), 426 ChangeMode: helper.StringToPtr("restart"), 427 } 428 429 if err := parseVault(tgVault, o); err != nil { 430 return multierror.Prefix(err, fmt.Sprintf("'%s', vault ->", n)) 431 } 432 433 // Go through the tasks and if they don't have a Vault block, set it 434 for _, task := range g.Tasks { 435 if task.Vault == nil { 436 task.Vault = tgVault 437 } 438 } 439 } 440 441 collection = append(collection, &g) 442 } 443 444 result.TaskGroups = append(result.TaskGroups, collection...) 445 return nil 446 } 447 448 func parseRestartPolicy(final **api.RestartPolicy, list *ast.ObjectList) error { 449 list = list.Elem() 450 if len(list.Items) > 1 { 451 return fmt.Errorf("only one 'restart' block allowed") 452 } 453 454 // Get our job object 455 obj := list.Items[0] 456 457 // Check for invalid keys 458 valid := []string{ 459 "attempts", 460 "interval", 461 "delay", 462 "mode", 463 } 464 if err := helper.CheckHCLKeys(obj.Val, valid); err != nil { 465 return err 466 } 467 468 var m map[string]interface{} 469 if err := hcl.DecodeObject(&m, obj.Val); err != nil { 470 return err 471 } 472 473 var result api.RestartPolicy 474 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 475 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 476 WeaklyTypedInput: true, 477 Result: &result, 478 }) 479 if err != nil { 480 return err 481 } 482 if err := dec.Decode(m); err != nil { 483 return err 484 } 485 486 *final = &result 487 return nil 488 } 489 490 func parseReschedulePolicy(final **api.ReschedulePolicy, list *ast.ObjectList) error { 491 list = list.Elem() 492 if len(list.Items) > 1 { 493 return fmt.Errorf("only one 'reschedule' block allowed") 494 } 495 496 // Get our job object 497 obj := list.Items[0] 498 499 // Check for invalid keys 500 valid := []string{ 501 "attempts", 502 "interval", 503 "unlimited", 504 "delay", 505 "max_delay", 506 "delay_function", 507 } 508 if err := helper.CheckHCLKeys(obj.Val, valid); err != nil { 509 return err 510 } 511 512 var m map[string]interface{} 513 if err := hcl.DecodeObject(&m, obj.Val); err != nil { 514 return err 515 } 516 517 var result api.ReschedulePolicy 518 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 519 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 520 WeaklyTypedInput: true, 521 Result: &result, 522 }) 523 if err != nil { 524 return err 525 } 526 if err := dec.Decode(m); err != nil { 527 return err 528 } 529 530 *final = &result 531 return nil 532 } 533 534 func parseConstraints(result *[]*api.Constraint, list *ast.ObjectList) error { 535 for _, o := range list.Elem().Items { 536 // Check for invalid keys 537 valid := []string{ 538 "attribute", 539 "distinct_hosts", 540 "distinct_property", 541 "operator", 542 "regexp", 543 "set_contains", 544 "value", 545 "version", 546 } 547 if err := helper.CheckHCLKeys(o.Val, valid); err != nil { 548 return err 549 } 550 551 var m map[string]interface{} 552 if err := hcl.DecodeObject(&m, o.Val); err != nil { 553 return err 554 } 555 556 m["LTarget"] = m["attribute"] 557 m["RTarget"] = m["value"] 558 m["Operand"] = m["operator"] 559 560 // If "version" is provided, set the operand 561 // to "version" and the value to the "RTarget" 562 if constraint, ok := m[structs.ConstraintVersion]; ok { 563 m["Operand"] = structs.ConstraintVersion 564 m["RTarget"] = constraint 565 } 566 567 // If "regexp" is provided, set the operand 568 // to "regexp" and the value to the "RTarget" 569 if constraint, ok := m[structs.ConstraintRegex]; ok { 570 m["Operand"] = structs.ConstraintRegex 571 m["RTarget"] = constraint 572 } 573 574 // If "set_contains" is provided, set the operand 575 // to "set_contains" and the value to the "RTarget" 576 if constraint, ok := m[structs.ConstraintSetContains]; ok { 577 m["Operand"] = structs.ConstraintSetContains 578 m["RTarget"] = constraint 579 } 580 581 if value, ok := m[structs.ConstraintDistinctHosts]; ok { 582 enabled, err := parseBool(value) 583 if err != nil { 584 return fmt.Errorf("distinct_hosts should be set to true or false; %v", err) 585 } 586 587 // If it is not enabled, skip the constraint. 588 if !enabled { 589 continue 590 } 591 592 m["Operand"] = structs.ConstraintDistinctHosts 593 } 594 595 if property, ok := m[structs.ConstraintDistinctProperty]; ok { 596 m["Operand"] = structs.ConstraintDistinctProperty 597 m["LTarget"] = property 598 } 599 600 // Build the constraint 601 var c api.Constraint 602 if err := mapstructure.WeakDecode(m, &c); err != nil { 603 return err 604 } 605 if c.Operand == "" { 606 c.Operand = "=" 607 } 608 609 *result = append(*result, &c) 610 } 611 612 return nil 613 } 614 615 func parseAffinities(result *[]*api.Affinity, list *ast.ObjectList) error { 616 for _, o := range list.Elem().Items { 617 // Check for invalid keys 618 valid := []string{ 619 "attribute", 620 "operator", 621 "regexp", 622 "set_contains", 623 "set_contains_any", 624 "set_contains_all", 625 "value", 626 "version", 627 "weight", 628 } 629 if err := helper.CheckHCLKeys(o.Val, valid); err != nil { 630 return err 631 } 632 633 var m map[string]interface{} 634 if err := hcl.DecodeObject(&m, o.Val); err != nil { 635 return err 636 } 637 638 m["LTarget"] = m["attribute"] 639 m["RTarget"] = m["value"] 640 m["Operand"] = m["operator"] 641 642 // If "version" is provided, set the operand 643 // to "version" and the value to the "RTarget" 644 if affinity, ok := m[structs.ConstraintVersion]; ok { 645 m["Operand"] = structs.ConstraintVersion 646 m["RTarget"] = affinity 647 } 648 649 // If "regexp" is provided, set the operand 650 // to "regexp" and the value to the "RTarget" 651 if affinity, ok := m[structs.ConstraintRegex]; ok { 652 m["Operand"] = structs.ConstraintRegex 653 m["RTarget"] = affinity 654 } 655 656 // If "set_contains_any" is provided, set the operand 657 // to "set_contains_any" and the value to the "RTarget" 658 if affinity, ok := m[structs.ConstraintSetContainsAny]; ok { 659 m["Operand"] = structs.ConstraintSetContainsAny 660 m["RTarget"] = affinity 661 } 662 663 // If "set_contains_all" is provided, set the operand 664 // to "set_contains_all" and the value to the "RTarget" 665 if affinity, ok := m[structs.ConstraintSetContainsAll]; ok { 666 m["Operand"] = structs.ConstraintSetContainsAll 667 m["RTarget"] = affinity 668 } 669 670 // set_contains is a synonym of set_contains_all 671 if affinity, ok := m[structs.ConstraintSetContains]; ok { 672 m["Operand"] = structs.ConstraintSetContains 673 m["RTarget"] = affinity 674 } 675 676 // Build the affinity 677 var a api.Affinity 678 if err := mapstructure.WeakDecode(m, &a); err != nil { 679 return err 680 } 681 if a.Operand == "" { 682 a.Operand = "=" 683 } 684 685 *result = append(*result, &a) 686 } 687 688 return nil 689 } 690 691 func parseEphemeralDisk(result **api.EphemeralDisk, list *ast.ObjectList) error { 692 list = list.Elem() 693 if len(list.Items) > 1 { 694 return fmt.Errorf("only one 'ephemeral_disk' block allowed") 695 } 696 697 // Get our ephemeral_disk object 698 obj := list.Items[0] 699 700 // Check for invalid keys 701 valid := []string{ 702 "sticky", 703 "size", 704 "migrate", 705 } 706 if err := helper.CheckHCLKeys(obj.Val, valid); err != nil { 707 return err 708 } 709 710 var m map[string]interface{} 711 if err := hcl.DecodeObject(&m, obj.Val); err != nil { 712 return err 713 } 714 715 var ephemeralDisk api.EphemeralDisk 716 if err := mapstructure.WeakDecode(m, &ephemeralDisk); err != nil { 717 return err 718 } 719 *result = &ephemeralDisk 720 721 return nil 722 } 723 724 func parseSpread(result *[]*api.Spread, list *ast.ObjectList) error { 725 for _, o := range list.Elem().Items { 726 // Check for invalid keys 727 valid := []string{ 728 "attribute", 729 "weight", 730 "target", 731 } 732 if err := helper.CheckHCLKeys(o.Val, valid); err != nil { 733 return err 734 } 735 736 // We need this later 737 var listVal *ast.ObjectList 738 if ot, ok := o.Val.(*ast.ObjectType); ok { 739 listVal = ot.List 740 } else { 741 return fmt.Errorf("spread should be an object") 742 } 743 744 var m map[string]interface{} 745 if err := hcl.DecodeObject(&m, o.Val); err != nil { 746 return err 747 } 748 delete(m, "target") 749 // Build spread 750 var s api.Spread 751 if err := mapstructure.WeakDecode(m, &s); err != nil { 752 return err 753 } 754 755 // Parse spread target 756 if o := listVal.Filter("target"); len(o.Items) > 0 { 757 if err := parseSpreadTarget(&s.SpreadTarget, o); err != nil { 758 return multierror.Prefix(err, fmt.Sprintf("target ->")) 759 } 760 } 761 762 *result = append(*result, &s) 763 } 764 765 return nil 766 } 767 768 func parseSpreadTarget(result *[]*api.SpreadTarget, list *ast.ObjectList) error { 769 seen := make(map[string]struct{}) 770 for _, item := range list.Items { 771 if len(item.Keys) != 1 { 772 return fmt.Errorf("missing spread target") 773 } 774 n := item.Keys[0].Token.Value().(string) 775 776 // Make sure we haven't already found this 777 if _, ok := seen[n]; ok { 778 return fmt.Errorf("target '%s' defined more than once", n) 779 } 780 seen[n] = struct{}{} 781 782 // We need this later 783 var listVal *ast.ObjectList 784 if ot, ok := item.Val.(*ast.ObjectType); ok { 785 listVal = ot.List 786 } else { 787 return fmt.Errorf("target should be an object") 788 } 789 790 // Check for invalid keys 791 valid := []string{ 792 "percent", 793 "value", 794 } 795 if err := helper.CheckHCLKeys(listVal, valid); err != nil { 796 return multierror.Prefix(err, fmt.Sprintf("'%s' ->", n)) 797 } 798 799 var m map[string]interface{} 800 if err := hcl.DecodeObject(&m, item.Val); err != nil { 801 return err 802 } 803 804 // Decode spread target 805 var g api.SpreadTarget 806 g.Value = n 807 if err := mapstructure.WeakDecode(m, &g); err != nil { 808 return err 809 } 810 *result = append(*result, &g) 811 } 812 return nil 813 } 814 815 // parseBool takes an interface value and tries to convert it to a boolean and 816 // returns an error if the type can't be converted. 817 func parseBool(value interface{}) (bool, error) { 818 var enabled bool 819 var err error 820 switch value.(type) { 821 case string: 822 enabled, err = strconv.ParseBool(value.(string)) 823 case bool: 824 enabled = value.(bool) 825 default: 826 err = fmt.Errorf("%v couldn't be converted to boolean value", value) 827 } 828 829 return enabled, err 830 } 831 832 func parseTasks(jobName string, taskGroupName string, result *[]*api.Task, list *ast.ObjectList) error { 833 list = list.Children() 834 if len(list.Items) == 0 { 835 return nil 836 } 837 838 // Go through each object and turn it into an actual result. 839 seen := make(map[string]struct{}) 840 for _, item := range list.Items { 841 n := item.Keys[0].Token.Value().(string) 842 843 // Make sure we haven't already found this 844 if _, ok := seen[n]; ok { 845 return fmt.Errorf("task '%s' defined more than once", n) 846 } 847 seen[n] = struct{}{} 848 849 // We need this later 850 var listVal *ast.ObjectList 851 if ot, ok := item.Val.(*ast.ObjectType); ok { 852 listVal = ot.List 853 } else { 854 return fmt.Errorf("group '%s': should be an object", n) 855 } 856 857 // Check for invalid keys 858 valid := []string{ 859 "artifact", 860 "config", 861 "constraint", 862 "affinity", 863 "dispatch_payload", 864 "driver", 865 "env", 866 "kill_timeout", 867 "leader", 868 "logs", 869 "meta", 870 "resources", 871 "service", 872 "shutdown_delay", 873 "template", 874 "user", 875 "vault", 876 "kill_signal", 877 } 878 if err := helper.CheckHCLKeys(listVal, valid); err != nil { 879 return multierror.Prefix(err, fmt.Sprintf("'%s' ->", n)) 880 } 881 882 var m map[string]interface{} 883 if err := hcl.DecodeObject(&m, item.Val); err != nil { 884 return err 885 } 886 delete(m, "artifact") 887 delete(m, "config") 888 delete(m, "constraint") 889 delete(m, "affinity") 890 delete(m, "dispatch_payload") 891 delete(m, "env") 892 delete(m, "logs") 893 delete(m, "meta") 894 delete(m, "resources") 895 delete(m, "service") 896 delete(m, "template") 897 delete(m, "vault") 898 899 // Build the task 900 var t api.Task 901 t.Name = n 902 if taskGroupName == "" { 903 taskGroupName = n 904 } 905 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 906 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 907 WeaklyTypedInput: true, 908 Result: &t, 909 }) 910 911 if err != nil { 912 return err 913 } 914 if err := dec.Decode(m); err != nil { 915 return err 916 } 917 918 // If we have env, then parse them 919 if o := listVal.Filter("env"); len(o.Items) > 0 { 920 for _, o := range o.Elem().Items { 921 var m map[string]interface{} 922 if err := hcl.DecodeObject(&m, o.Val); err != nil { 923 return err 924 } 925 if err := mapstructure.WeakDecode(m, &t.Env); err != nil { 926 return err 927 } 928 } 929 } 930 931 if o := listVal.Filter("service"); len(o.Items) > 0 { 932 if err := parseServices(jobName, taskGroupName, &t, o); err != nil { 933 return multierror.Prefix(err, fmt.Sprintf("'%s',", n)) 934 } 935 } 936 937 // If we have config, then parse that 938 if o := listVal.Filter("config"); len(o.Items) > 0 { 939 for _, o := range o.Elem().Items { 940 var m map[string]interface{} 941 if err := hcl.DecodeObject(&m, o.Val); err != nil { 942 return err 943 } 944 945 if err := mapstructure.WeakDecode(m, &t.Config); err != nil { 946 return err 947 } 948 } 949 } 950 951 // Parse constraints 952 if o := listVal.Filter("constraint"); len(o.Items) > 0 { 953 if err := parseConstraints(&t.Constraints, o); err != nil { 954 return multierror.Prefix(err, fmt.Sprintf( 955 "'%s', constraint ->", n)) 956 } 957 } 958 959 // Parse affinities 960 if o := listVal.Filter("affinity"); len(o.Items) > 0 { 961 if err := parseAffinities(&t.Affinities, o); err != nil { 962 return multierror.Prefix(err, "affinity ->") 963 } 964 } 965 966 // Parse out meta fields. These are in HCL as a list so we need 967 // to iterate over them and merge them. 968 if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 { 969 for _, o := range metaO.Elem().Items { 970 var m map[string]interface{} 971 if err := hcl.DecodeObject(&m, o.Val); err != nil { 972 return err 973 } 974 if err := mapstructure.WeakDecode(m, &t.Meta); err != nil { 975 return err 976 } 977 } 978 } 979 980 // If we have resources, then parse that 981 if o := listVal.Filter("resources"); len(o.Items) > 0 { 982 var r api.Resources 983 if err := parseResources(&r, o); err != nil { 984 return multierror.Prefix(err, fmt.Sprintf("'%s',", n)) 985 } 986 987 t.Resources = &r 988 } 989 990 // If we have logs then parse that 991 if o := listVal.Filter("logs"); len(o.Items) > 0 { 992 if len(o.Items) > 1 { 993 return fmt.Errorf("only one logs block is allowed in a Task. Number of logs block found: %d", len(o.Items)) 994 } 995 var m map[string]interface{} 996 logsBlock := o.Items[0] 997 998 // Check for invalid keys 999 valid := []string{ 1000 "max_files", 1001 "max_file_size", 1002 } 1003 if err := helper.CheckHCLKeys(logsBlock.Val, valid); err != nil { 1004 return multierror.Prefix(err, fmt.Sprintf("'%s', logs ->", n)) 1005 } 1006 1007 if err := hcl.DecodeObject(&m, logsBlock.Val); err != nil { 1008 return err 1009 } 1010 1011 var log api.LogConfig 1012 if err := mapstructure.WeakDecode(m, &log); err != nil { 1013 return err 1014 } 1015 1016 t.LogConfig = &log 1017 } 1018 1019 // Parse artifacts 1020 if o := listVal.Filter("artifact"); len(o.Items) > 0 { 1021 if err := parseArtifacts(&t.Artifacts, o); err != nil { 1022 return multierror.Prefix(err, fmt.Sprintf("'%s', artifact ->", n)) 1023 } 1024 } 1025 1026 // Parse templates 1027 if o := listVal.Filter("template"); len(o.Items) > 0 { 1028 if err := parseTemplates(&t.Templates, o); err != nil { 1029 return multierror.Prefix(err, fmt.Sprintf("'%s', template ->", n)) 1030 } 1031 } 1032 1033 // If we have a vault block, then parse that 1034 if o := listVal.Filter("vault"); len(o.Items) > 0 { 1035 v := &api.Vault{ 1036 Env: helper.BoolToPtr(true), 1037 ChangeMode: helper.StringToPtr("restart"), 1038 } 1039 1040 if err := parseVault(v, o); err != nil { 1041 return multierror.Prefix(err, fmt.Sprintf("'%s', vault ->", n)) 1042 } 1043 1044 t.Vault = v 1045 } 1046 1047 // If we have a dispatch_payload block parse that 1048 if o := listVal.Filter("dispatch_payload"); len(o.Items) > 0 { 1049 if len(o.Items) > 1 { 1050 return fmt.Errorf("only one dispatch_payload block is allowed in a task. Number of dispatch_payload blocks found: %d", len(o.Items)) 1051 } 1052 var m map[string]interface{} 1053 dispatchBlock := o.Items[0] 1054 1055 // Check for invalid keys 1056 valid := []string{ 1057 "file", 1058 } 1059 if err := helper.CheckHCLKeys(dispatchBlock.Val, valid); err != nil { 1060 return multierror.Prefix(err, fmt.Sprintf("'%s', dispatch_payload ->", n)) 1061 } 1062 1063 if err := hcl.DecodeObject(&m, dispatchBlock.Val); err != nil { 1064 return err 1065 } 1066 1067 t.DispatchPayload = &api.DispatchPayloadConfig{} 1068 if err := mapstructure.WeakDecode(m, t.DispatchPayload); err != nil { 1069 return err 1070 } 1071 } 1072 1073 *result = append(*result, &t) 1074 } 1075 1076 return nil 1077 } 1078 1079 func parseArtifacts(result *[]*api.TaskArtifact, list *ast.ObjectList) error { 1080 for _, o := range list.Elem().Items { 1081 // Check for invalid keys 1082 valid := []string{ 1083 "source", 1084 "options", 1085 "mode", 1086 "destination", 1087 } 1088 if err := helper.CheckHCLKeys(o.Val, valid); err != nil { 1089 return err 1090 } 1091 1092 var m map[string]interface{} 1093 if err := hcl.DecodeObject(&m, o.Val); err != nil { 1094 return err 1095 } 1096 1097 delete(m, "options") 1098 1099 var ta api.TaskArtifact 1100 if err := mapstructure.WeakDecode(m, &ta); err != nil { 1101 return err 1102 } 1103 1104 var optionList *ast.ObjectList 1105 if ot, ok := o.Val.(*ast.ObjectType); ok { 1106 optionList = ot.List 1107 } else { 1108 return fmt.Errorf("artifact should be an object") 1109 } 1110 1111 if oo := optionList.Filter("options"); len(oo.Items) > 0 { 1112 options := make(map[string]string) 1113 if err := parseArtifactOption(options, oo); err != nil { 1114 return multierror.Prefix(err, "options: ") 1115 } 1116 ta.GetterOptions = options 1117 } 1118 1119 *result = append(*result, &ta) 1120 } 1121 1122 return nil 1123 } 1124 1125 func parseArtifactOption(result map[string]string, list *ast.ObjectList) error { 1126 list = list.Elem() 1127 if len(list.Items) > 1 { 1128 return fmt.Errorf("only one 'options' block allowed per artifact") 1129 } 1130 1131 // Get our resource object 1132 o := list.Items[0] 1133 1134 var m map[string]interface{} 1135 if err := hcl.DecodeObject(&m, o.Val); err != nil { 1136 return err 1137 } 1138 1139 if err := mapstructure.WeakDecode(m, &result); err != nil { 1140 return err 1141 } 1142 1143 return nil 1144 } 1145 1146 func parseTemplates(result *[]*api.Template, list *ast.ObjectList) error { 1147 for _, o := range list.Elem().Items { 1148 // Check for invalid keys 1149 valid := []string{ 1150 "change_mode", 1151 "change_signal", 1152 "data", 1153 "destination", 1154 "left_delimiter", 1155 "perms", 1156 "right_delimiter", 1157 "source", 1158 "splay", 1159 "env", 1160 "vault_grace", 1161 } 1162 if err := helper.CheckHCLKeys(o.Val, valid); err != nil { 1163 return err 1164 } 1165 1166 var m map[string]interface{} 1167 if err := hcl.DecodeObject(&m, o.Val); err != nil { 1168 return err 1169 } 1170 1171 templ := &api.Template{ 1172 ChangeMode: helper.StringToPtr("restart"), 1173 Splay: helper.TimeToPtr(5 * time.Second), 1174 Perms: helper.StringToPtr("0644"), 1175 } 1176 1177 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 1178 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 1179 WeaklyTypedInput: true, 1180 Result: templ, 1181 }) 1182 if err != nil { 1183 return err 1184 } 1185 if err := dec.Decode(m); err != nil { 1186 return err 1187 } 1188 1189 *result = append(*result, templ) 1190 } 1191 1192 return nil 1193 } 1194 1195 func parseServices(jobName string, taskGroupName string, task *api.Task, serviceObjs *ast.ObjectList) error { 1196 task.Services = make([]*api.Service, len(serviceObjs.Items)) 1197 for idx, o := range serviceObjs.Items { 1198 // Check for invalid keys 1199 valid := []string{ 1200 "name", 1201 "tags", 1202 "canary_tags", 1203 "port", 1204 "check", 1205 "address_mode", 1206 "check_restart", 1207 } 1208 if err := helper.CheckHCLKeys(o.Val, valid); err != nil { 1209 return multierror.Prefix(err, fmt.Sprintf("service (%d) ->", idx)) 1210 } 1211 1212 var service api.Service 1213 var m map[string]interface{} 1214 if err := hcl.DecodeObject(&m, o.Val); err != nil { 1215 return err 1216 } 1217 1218 delete(m, "check") 1219 delete(m, "check_restart") 1220 1221 if err := mapstructure.WeakDecode(m, &service); err != nil { 1222 return err 1223 } 1224 1225 // Filter checks 1226 var checkList *ast.ObjectList 1227 if ot, ok := o.Val.(*ast.ObjectType); ok { 1228 checkList = ot.List 1229 } else { 1230 return fmt.Errorf("service '%s': should be an object", service.Name) 1231 } 1232 1233 if co := checkList.Filter("check"); len(co.Items) > 0 { 1234 if err := parseChecks(&service, co); err != nil { 1235 return multierror.Prefix(err, fmt.Sprintf("service: '%s',", service.Name)) 1236 } 1237 } 1238 1239 // Filter check_restart 1240 if cro := checkList.Filter("check_restart"); len(cro.Items) > 0 { 1241 if len(cro.Items) > 1 { 1242 return fmt.Errorf("check_restart '%s': cannot have more than 1 check_restart", service.Name) 1243 } 1244 if cr, err := parseCheckRestart(cro.Items[0]); err != nil { 1245 return multierror.Prefix(err, fmt.Sprintf("service: '%s',", service.Name)) 1246 } else { 1247 service.CheckRestart = cr 1248 } 1249 } 1250 1251 task.Services[idx] = &service 1252 } 1253 1254 return nil 1255 } 1256 1257 func parseChecks(service *api.Service, checkObjs *ast.ObjectList) error { 1258 service.Checks = make([]api.ServiceCheck, len(checkObjs.Items)) 1259 for idx, co := range checkObjs.Items { 1260 // Check for invalid keys 1261 valid := []string{ 1262 "name", 1263 "type", 1264 "interval", 1265 "timeout", 1266 "path", 1267 "protocol", 1268 "port", 1269 "command", 1270 "args", 1271 "initial_status", 1272 "tls_skip_verify", 1273 "header", 1274 "method", 1275 "check_restart", 1276 "address_mode", 1277 "grpc_service", 1278 "grpc_use_tls", 1279 } 1280 if err := helper.CheckHCLKeys(co.Val, valid); err != nil { 1281 return multierror.Prefix(err, "check ->") 1282 } 1283 1284 var check api.ServiceCheck 1285 var cm map[string]interface{} 1286 if err := hcl.DecodeObject(&cm, co.Val); err != nil { 1287 return err 1288 } 1289 1290 // HCL allows repeating stanzas so merge 'header' into a single 1291 // map[string][]string. 1292 if headerI, ok := cm["header"]; ok { 1293 headerRaw, ok := headerI.([]map[string]interface{}) 1294 if !ok { 1295 return fmt.Errorf("check -> header -> expected a []map[string][]string but found %T", headerI) 1296 } 1297 m := map[string][]string{} 1298 for _, rawm := range headerRaw { 1299 for k, vI := range rawm { 1300 vs, ok := vI.([]interface{}) 1301 if !ok { 1302 return fmt.Errorf("check -> header -> %q expected a []string but found %T", k, vI) 1303 } 1304 for _, vI := range vs { 1305 v, ok := vI.(string) 1306 if !ok { 1307 return fmt.Errorf("check -> header -> %q expected a string but found %T", k, vI) 1308 } 1309 m[k] = append(m[k], v) 1310 } 1311 } 1312 } 1313 1314 check.Header = m 1315 1316 // Remove "header" as it has been parsed 1317 delete(cm, "header") 1318 } 1319 1320 delete(cm, "check_restart") 1321 1322 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 1323 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 1324 WeaklyTypedInput: true, 1325 Result: &check, 1326 }) 1327 if err != nil { 1328 return err 1329 } 1330 if err := dec.Decode(cm); err != nil { 1331 return err 1332 } 1333 1334 // Filter check_restart 1335 var checkRestartList *ast.ObjectList 1336 if ot, ok := co.Val.(*ast.ObjectType); ok { 1337 checkRestartList = ot.List 1338 } else { 1339 return fmt.Errorf("check_restart '%s': should be an object", check.Name) 1340 } 1341 1342 if cro := checkRestartList.Filter("check_restart"); len(cro.Items) > 0 { 1343 if len(cro.Items) > 1 { 1344 return fmt.Errorf("check_restart '%s': cannot have more than 1 check_restart", check.Name) 1345 } 1346 if cr, err := parseCheckRestart(cro.Items[0]); err != nil { 1347 return multierror.Prefix(err, fmt.Sprintf("check: '%s',", check.Name)) 1348 } else { 1349 check.CheckRestart = cr 1350 } 1351 } 1352 1353 service.Checks[idx] = check 1354 } 1355 1356 return nil 1357 } 1358 1359 func parseCheckRestart(cro *ast.ObjectItem) (*api.CheckRestart, error) { 1360 valid := []string{ 1361 "limit", 1362 "grace", 1363 "ignore_warnings", 1364 } 1365 1366 if err := helper.CheckHCLKeys(cro.Val, valid); err != nil { 1367 return nil, multierror.Prefix(err, "check_restart ->") 1368 } 1369 1370 var checkRestart api.CheckRestart 1371 var crm map[string]interface{} 1372 if err := hcl.DecodeObject(&crm, cro.Val); err != nil { 1373 return nil, err 1374 } 1375 1376 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 1377 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 1378 WeaklyTypedInput: true, 1379 Result: &checkRestart, 1380 }) 1381 if err != nil { 1382 return nil, err 1383 } 1384 if err := dec.Decode(crm); err != nil { 1385 return nil, err 1386 } 1387 1388 return &checkRestart, nil 1389 } 1390 1391 func parseResources(result *api.Resources, list *ast.ObjectList) error { 1392 list = list.Elem() 1393 if len(list.Items) == 0 { 1394 return nil 1395 } 1396 if len(list.Items) > 1 { 1397 return fmt.Errorf("only one 'resource' block allowed per task") 1398 } 1399 1400 // Get our resource object 1401 o := list.Items[0] 1402 1403 // We need this later 1404 var listVal *ast.ObjectList 1405 if ot, ok := o.Val.(*ast.ObjectType); ok { 1406 listVal = ot.List 1407 } else { 1408 return fmt.Errorf("resource: should be an object") 1409 } 1410 1411 // Check for invalid keys 1412 valid := []string{ 1413 "cpu", 1414 "iops", // COMPAT(0.10): Remove after one release to allow it to be removed from jobspecs 1415 "disk", 1416 "memory", 1417 "network", 1418 "device", 1419 } 1420 if err := helper.CheckHCLKeys(listVal, valid); err != nil { 1421 return multierror.Prefix(err, "resources ->") 1422 } 1423 1424 var m map[string]interface{} 1425 if err := hcl.DecodeObject(&m, o.Val); err != nil { 1426 return err 1427 } 1428 delete(m, "network") 1429 delete(m, "device") 1430 1431 if err := mapstructure.WeakDecode(m, result); err != nil { 1432 return err 1433 } 1434 1435 // Parse the network resources 1436 if o := listVal.Filter("network"); len(o.Items) > 0 { 1437 if len(o.Items) > 1 { 1438 return fmt.Errorf("only one 'network' resource allowed") 1439 } 1440 1441 // Check for invalid keys 1442 valid := []string{ 1443 "mbits", 1444 "port", 1445 } 1446 if err := helper.CheckHCLKeys(o.Items[0].Val, valid); err != nil { 1447 return multierror.Prefix(err, "resources, network ->") 1448 } 1449 1450 var r api.NetworkResource 1451 var m map[string]interface{} 1452 if err := hcl.DecodeObject(&m, o.Items[0].Val); err != nil { 1453 return err 1454 } 1455 if err := mapstructure.WeakDecode(m, &r); err != nil { 1456 return err 1457 } 1458 1459 var networkObj *ast.ObjectList 1460 if ot, ok := o.Items[0].Val.(*ast.ObjectType); ok { 1461 networkObj = ot.List 1462 } else { 1463 return fmt.Errorf("resource: should be an object") 1464 } 1465 if err := parsePorts(networkObj, &r); err != nil { 1466 return multierror.Prefix(err, "resources, network, ports ->") 1467 } 1468 1469 result.Networks = []*api.NetworkResource{&r} 1470 } 1471 1472 // Parse the device resources 1473 if o := listVal.Filter("device"); len(o.Items) > 0 { 1474 result.Devices = make([]*api.RequestedDevice, len(o.Items)) 1475 for idx, do := range o.Items { 1476 if l := len(do.Keys); l == 0 { 1477 return multierror.Prefix(fmt.Errorf("missing device name"), fmt.Sprintf("resources, device[%d]->", idx)) 1478 } else if l > 1 { 1479 return multierror.Prefix(fmt.Errorf("only one name may be specified"), fmt.Sprintf("resources, device[%d]->", idx)) 1480 } 1481 name := do.Keys[0].Token.Value().(string) 1482 1483 // Value should be an object 1484 var listVal *ast.ObjectList 1485 if ot, ok := do.Val.(*ast.ObjectType); ok { 1486 listVal = ot.List 1487 } else { 1488 return fmt.Errorf("device should be an object") 1489 } 1490 1491 // Check for invalid keys 1492 valid := []string{ 1493 "name", 1494 "count", 1495 "affinity", 1496 "constraint", 1497 } 1498 if err := helper.CheckHCLKeys(do.Val, valid); err != nil { 1499 return multierror.Prefix(err, fmt.Sprintf("resources, device[%d]->", idx)) 1500 } 1501 1502 // Set the name 1503 var r api.RequestedDevice 1504 r.Name = name 1505 1506 var m map[string]interface{} 1507 if err := hcl.DecodeObject(&m, do.Val); err != nil { 1508 return err 1509 } 1510 1511 delete(m, "constraint") 1512 delete(m, "affinity") 1513 1514 if err := mapstructure.WeakDecode(m, &r); err != nil { 1515 return err 1516 } 1517 1518 // Parse constraints 1519 if o := listVal.Filter("constraint"); len(o.Items) > 0 { 1520 if err := parseConstraints(&r.Constraints, o); err != nil { 1521 return multierror.Prefix(err, "constraint ->") 1522 } 1523 } 1524 1525 // Parse affinities 1526 if o := listVal.Filter("affinity"); len(o.Items) > 0 { 1527 if err := parseAffinities(&r.Affinities, o); err != nil { 1528 return multierror.Prefix(err, "affinity ->") 1529 } 1530 } 1531 1532 result.Devices[idx] = &r 1533 } 1534 } 1535 1536 return nil 1537 } 1538 1539 func parsePorts(networkObj *ast.ObjectList, nw *api.NetworkResource) error { 1540 // Check for invalid keys 1541 valid := []string{ 1542 "mbits", 1543 "port", 1544 } 1545 if err := helper.CheckHCLKeys(networkObj, valid); err != nil { 1546 return err 1547 } 1548 1549 portsObjList := networkObj.Filter("port") 1550 knownPortLabels := make(map[string]bool) 1551 for _, port := range portsObjList.Items { 1552 if len(port.Keys) == 0 { 1553 return fmt.Errorf("ports must be named") 1554 } 1555 label := port.Keys[0].Token.Value().(string) 1556 if !reDynamicPorts.MatchString(label) { 1557 return errPortLabel 1558 } 1559 l := strings.ToLower(label) 1560 if knownPortLabels[l] { 1561 return fmt.Errorf("found a port label collision: %s", label) 1562 } 1563 var p map[string]interface{} 1564 var res api.Port 1565 if err := hcl.DecodeObject(&p, port.Val); err != nil { 1566 return err 1567 } 1568 if err := mapstructure.WeakDecode(p, &res); err != nil { 1569 return err 1570 } 1571 res.Label = label 1572 if res.Value > 0 { 1573 nw.ReservedPorts = append(nw.ReservedPorts, res) 1574 } else { 1575 nw.DynamicPorts = append(nw.DynamicPorts, res) 1576 } 1577 knownPortLabels[l] = true 1578 } 1579 return nil 1580 } 1581 1582 func parseUpdate(result **api.UpdateStrategy, list *ast.ObjectList) error { 1583 list = list.Elem() 1584 if len(list.Items) > 1 { 1585 return fmt.Errorf("only one 'update' block allowed") 1586 } 1587 1588 // Get our resource object 1589 o := list.Items[0] 1590 1591 var m map[string]interface{} 1592 if err := hcl.DecodeObject(&m, o.Val); err != nil { 1593 return err 1594 } 1595 1596 // Check for invalid keys 1597 valid := []string{ 1598 // COMPAT: Remove in 0.7.0. Stagger is deprecated in 0.6.0. 1599 "stagger", 1600 "max_parallel", 1601 "health_check", 1602 "min_healthy_time", 1603 "healthy_deadline", 1604 "progress_deadline", 1605 "auto_revert", 1606 "canary", 1607 } 1608 if err := helper.CheckHCLKeys(o.Val, valid); err != nil { 1609 return err 1610 } 1611 1612 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 1613 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 1614 WeaklyTypedInput: true, 1615 Result: result, 1616 }) 1617 if err != nil { 1618 return err 1619 } 1620 return dec.Decode(m) 1621 } 1622 1623 func parseMigrate(result **api.MigrateStrategy, list *ast.ObjectList) error { 1624 list = list.Elem() 1625 if len(list.Items) > 1 { 1626 return fmt.Errorf("only one 'migrate' block allowed") 1627 } 1628 1629 // Get our resource object 1630 o := list.Items[0] 1631 1632 var m map[string]interface{} 1633 if err := hcl.DecodeObject(&m, o.Val); err != nil { 1634 return err 1635 } 1636 1637 // Check for invalid keys 1638 valid := []string{ 1639 "max_parallel", 1640 "health_check", 1641 "min_healthy_time", 1642 "healthy_deadline", 1643 } 1644 if err := helper.CheckHCLKeys(o.Val, valid); err != nil { 1645 return err 1646 } 1647 1648 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 1649 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 1650 WeaklyTypedInput: true, 1651 Result: result, 1652 }) 1653 if err != nil { 1654 return err 1655 } 1656 return dec.Decode(m) 1657 } 1658 1659 func parsePeriodic(result **api.PeriodicConfig, list *ast.ObjectList) error { 1660 list = list.Elem() 1661 if len(list.Items) > 1 { 1662 return fmt.Errorf("only one 'periodic' block allowed per job") 1663 } 1664 1665 // Get our resource object 1666 o := list.Items[0] 1667 1668 var m map[string]interface{} 1669 if err := hcl.DecodeObject(&m, o.Val); err != nil { 1670 return err 1671 } 1672 1673 // Check for invalid keys 1674 valid := []string{ 1675 "enabled", 1676 "cron", 1677 "prohibit_overlap", 1678 "time_zone", 1679 } 1680 if err := helper.CheckHCLKeys(o.Val, valid); err != nil { 1681 return err 1682 } 1683 1684 if value, ok := m["enabled"]; ok { 1685 enabled, err := parseBool(value) 1686 if err != nil { 1687 return fmt.Errorf("periodic.enabled should be set to true or false; %v", err) 1688 } 1689 m["Enabled"] = enabled 1690 } 1691 1692 // If "cron" is provided, set the type to "cron" and store the spec. 1693 if cron, ok := m["cron"]; ok { 1694 m["SpecType"] = structs.PeriodicSpecCron 1695 m["Spec"] = cron 1696 } 1697 1698 // Build the constraint 1699 var p api.PeriodicConfig 1700 if err := mapstructure.WeakDecode(m, &p); err != nil { 1701 return err 1702 } 1703 *result = &p 1704 return nil 1705 } 1706 1707 func parseVault(result *api.Vault, list *ast.ObjectList) error { 1708 list = list.Elem() 1709 if len(list.Items) == 0 { 1710 return nil 1711 } 1712 if len(list.Items) > 1 { 1713 return fmt.Errorf("only one 'vault' block allowed per task") 1714 } 1715 1716 // Get our resource object 1717 o := list.Items[0] 1718 1719 // We need this later 1720 var listVal *ast.ObjectList 1721 if ot, ok := o.Val.(*ast.ObjectType); ok { 1722 listVal = ot.List 1723 } else { 1724 return fmt.Errorf("vault: should be an object") 1725 } 1726 1727 // Check for invalid keys 1728 valid := []string{ 1729 "policies", 1730 "env", 1731 "change_mode", 1732 "change_signal", 1733 } 1734 if err := helper.CheckHCLKeys(listVal, valid); err != nil { 1735 return multierror.Prefix(err, "vault ->") 1736 } 1737 1738 var m map[string]interface{} 1739 if err := hcl.DecodeObject(&m, o.Val); err != nil { 1740 return err 1741 } 1742 1743 if err := mapstructure.WeakDecode(m, result); err != nil { 1744 return err 1745 } 1746 1747 return nil 1748 } 1749 1750 func parseParameterizedJob(result **api.ParameterizedJobConfig, list *ast.ObjectList) error { 1751 list = list.Elem() 1752 if len(list.Items) > 1 { 1753 return fmt.Errorf("only one 'parameterized' block allowed per job") 1754 } 1755 1756 // Get our resource object 1757 o := list.Items[0] 1758 1759 var m map[string]interface{} 1760 if err := hcl.DecodeObject(&m, o.Val); err != nil { 1761 return err 1762 } 1763 1764 // Check for invalid keys 1765 valid := []string{ 1766 "payload", 1767 "meta_required", 1768 "meta_optional", 1769 } 1770 if err := helper.CheckHCLKeys(o.Val, valid); err != nil { 1771 return err 1772 } 1773 1774 // Build the parameterized job block 1775 var d api.ParameterizedJobConfig 1776 if err := mapstructure.WeakDecode(m, &d); err != nil { 1777 return err 1778 } 1779 1780 *result = &d 1781 return nil 1782 }