github.com/mattyr/nomad@v0.3.3-0.20160919021406-3485a065154a/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 13 "github.com/hashicorp/go-multierror" 14 "github.com/hashicorp/hcl" 15 "github.com/hashicorp/hcl/hcl/ast" 16 "github.com/hashicorp/nomad/client/driver" 17 "github.com/hashicorp/nomad/nomad/structs" 18 "github.com/mitchellh/mapstructure" 19 ) 20 21 var reDynamicPorts = regexp.MustCompile("^[a-zA-Z0-9_]+$") 22 var errPortLabel = fmt.Errorf("Port label does not conform to naming requirements %s", reDynamicPorts.String()) 23 24 // Parse parses the job spec from the given io.Reader. 25 // 26 // Due to current internal limitations, the entire contents of the 27 // io.Reader will be copied into memory first before parsing. 28 func Parse(r io.Reader) (*structs.Job, error) { 29 // Copy the reader into an in-memory buffer first since HCL requires it. 30 var buf bytes.Buffer 31 if _, err := io.Copy(&buf, r); err != nil { 32 return nil, err 33 } 34 35 // Parse the buffer 36 root, err := hcl.Parse(buf.String()) 37 if err != nil { 38 return nil, fmt.Errorf("error parsing: %s", err) 39 } 40 buf.Reset() 41 42 // Top-level item should be a list 43 list, ok := root.Node.(*ast.ObjectList) 44 if !ok { 45 return nil, fmt.Errorf("error parsing: root should be an object") 46 } 47 48 // Check for invalid keys 49 valid := []string{ 50 "job", 51 } 52 if err := checkHCLKeys(list, valid); err != nil { 53 return nil, err 54 } 55 56 var job structs.Job 57 58 // Parse the job out 59 matches := list.Filter("job") 60 if len(matches.Items) == 0 { 61 return nil, fmt.Errorf("'job' stanza not found") 62 } 63 if err := parseJob(&job, matches); err != nil { 64 return nil, fmt.Errorf("error parsing 'job': %s", err) 65 } 66 67 return &job, nil 68 } 69 70 // ParseFile parses the given path as a job spec. 71 func ParseFile(path string) (*structs.Job, error) { 72 path, err := filepath.Abs(path) 73 if err != nil { 74 return nil, err 75 } 76 77 f, err := os.Open(path) 78 if err != nil { 79 return nil, err 80 } 81 defer f.Close() 82 83 return Parse(f) 84 } 85 86 func parseJob(result *structs.Job, list *ast.ObjectList) error { 87 list = list.Children() 88 if len(list.Items) != 1 { 89 return fmt.Errorf("only one 'job' block allowed") 90 } 91 92 // Get our job object 93 obj := list.Items[0] 94 95 // Decode the full thing into a map[string]interface for ease 96 var m map[string]interface{} 97 if err := hcl.DecodeObject(&m, obj.Val); err != nil { 98 return err 99 } 100 delete(m, "constraint") 101 delete(m, "meta") 102 delete(m, "update") 103 delete(m, "periodic") 104 105 // Set the ID and name to the object key 106 result.ID = obj.Keys[0].Token.Value().(string) 107 result.Name = result.ID 108 109 // Defaults 110 result.Priority = 50 111 result.Region = "global" 112 result.Type = "service" 113 114 // Decode the rest 115 if err := mapstructure.WeakDecode(m, result); err != nil { 116 return err 117 } 118 119 // Value should be an object 120 var listVal *ast.ObjectList 121 if ot, ok := obj.Val.(*ast.ObjectType); ok { 122 listVal = ot.List 123 } else { 124 return fmt.Errorf("job '%s' value: should be an object", result.ID) 125 } 126 127 // Check for invalid keys 128 valid := []string{ 129 "id", 130 "name", 131 "region", 132 "all_at_once", 133 "type", 134 "priority", 135 "datacenters", 136 "constraint", 137 "update", 138 "periodic", 139 "meta", 140 "task", 141 "group", 142 "vault_token", 143 } 144 if err := checkHCLKeys(listVal, valid); err != nil { 145 return multierror.Prefix(err, "job:") 146 } 147 148 // Parse constraints 149 if o := listVal.Filter("constraint"); len(o.Items) > 0 { 150 if err := parseConstraints(&result.Constraints, o); err != nil { 151 return multierror.Prefix(err, "constraint ->") 152 } 153 } 154 155 // If we have an update strategy, then parse that 156 if o := listVal.Filter("update"); len(o.Items) > 0 { 157 if err := parseUpdate(&result.Update, o); err != nil { 158 return multierror.Prefix(err, "update ->") 159 } 160 } 161 162 // If we have a periodic definition, then parse that 163 if o := listVal.Filter("periodic"); len(o.Items) > 0 { 164 if err := parsePeriodic(&result.Periodic, o); err != nil { 165 return multierror.Prefix(err, "periodic ->") 166 } 167 } 168 169 // Parse out meta fields. These are in HCL as a list so we need 170 // to iterate over them and merge them. 171 if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 { 172 for _, o := range metaO.Elem().Items { 173 var m map[string]interface{} 174 if err := hcl.DecodeObject(&m, o.Val); err != nil { 175 return err 176 } 177 if err := mapstructure.WeakDecode(m, &result.Meta); err != nil { 178 return err 179 } 180 } 181 } 182 183 // If we have tasks outside, create TaskGroups for them 184 if o := listVal.Filter("task"); len(o.Items) > 0 { 185 var tasks []*structs.Task 186 if err := parseTasks(result.Name, "", &tasks, o); err != nil { 187 return multierror.Prefix(err, "task:") 188 } 189 190 result.TaskGroups = make([]*structs.TaskGroup, len(tasks), len(tasks)*2) 191 for i, t := range tasks { 192 result.TaskGroups[i] = &structs.TaskGroup{ 193 Name: t.Name, 194 Count: 1, 195 EphemeralDisk: structs.DefaultEphemeralDisk(), 196 Tasks: []*structs.Task{t}, 197 } 198 } 199 } 200 201 // Parse the task groups 202 if o := listVal.Filter("group"); len(o.Items) > 0 { 203 if err := parseGroups(result, o); err != nil { 204 return multierror.Prefix(err, "group:") 205 } 206 } 207 208 return nil 209 } 210 211 func parseGroups(result *structs.Job, list *ast.ObjectList) error { 212 list = list.Children() 213 if len(list.Items) == 0 { 214 return nil 215 } 216 217 // Go through each object and turn it into an actual result. 218 collection := make([]*structs.TaskGroup, 0, len(list.Items)) 219 seen := make(map[string]struct{}) 220 for _, item := range list.Items { 221 n := item.Keys[0].Token.Value().(string) 222 223 // Make sure we haven't already found this 224 if _, ok := seen[n]; ok { 225 return fmt.Errorf("group '%s' defined more than once", n) 226 } 227 seen[n] = struct{}{} 228 229 // We need this later 230 var listVal *ast.ObjectList 231 if ot, ok := item.Val.(*ast.ObjectType); ok { 232 listVal = ot.List 233 } else { 234 return fmt.Errorf("group '%s': should be an object", n) 235 } 236 237 // Check for invalid keys 238 valid := []string{ 239 "count", 240 "constraint", 241 "restart", 242 "meta", 243 "task", 244 "ephemeral_disk", 245 } 246 if err := checkHCLKeys(listVal, valid); err != nil { 247 return multierror.Prefix(err, fmt.Sprintf("'%s' ->", n)) 248 } 249 250 var m map[string]interface{} 251 if err := hcl.DecodeObject(&m, item.Val); err != nil { 252 return err 253 } 254 delete(m, "constraint") 255 delete(m, "meta") 256 delete(m, "task") 257 delete(m, "restart") 258 delete(m, "ephemeral_disk") 259 260 // Default count to 1 if not specified 261 if _, ok := m["count"]; !ok { 262 m["count"] = 1 263 } 264 265 // Build the group with the basic decode 266 var g structs.TaskGroup 267 g.Name = n 268 if err := mapstructure.WeakDecode(m, &g); err != nil { 269 return err 270 } 271 272 // Parse constraints 273 if o := listVal.Filter("constraint"); len(o.Items) > 0 { 274 if err := parseConstraints(&g.Constraints, o); err != nil { 275 return multierror.Prefix(err, fmt.Sprintf("'%s', constraint ->", n)) 276 } 277 } 278 279 // Parse restart policy 280 if o := listVal.Filter("restart"); len(o.Items) > 0 { 281 if err := parseRestartPolicy(&g.RestartPolicy, o); err != nil { 282 return multierror.Prefix(err, fmt.Sprintf("'%s', restart ->", n)) 283 } 284 } 285 286 // Parse ephemeral disk 287 g.EphemeralDisk = structs.DefaultEphemeralDisk() 288 if o := listVal.Filter("ephemeral_disk"); len(o.Items) > 0 { 289 if err := parseEphemeralDisk(&g.EphemeralDisk, o); err != nil { 290 return multierror.Prefix(err, fmt.Sprintf("'%s', ephemeral_disk ->", n)) 291 } 292 } 293 294 // Parse out meta fields. These are in HCL as a list so we need 295 // to iterate over them and merge them. 296 if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 { 297 for _, o := range metaO.Elem().Items { 298 var m map[string]interface{} 299 if err := hcl.DecodeObject(&m, o.Val); err != nil { 300 return err 301 } 302 if err := mapstructure.WeakDecode(m, &g.Meta); err != nil { 303 return err 304 } 305 } 306 } 307 308 // Parse tasks 309 if o := listVal.Filter("task"); len(o.Items) > 0 { 310 if err := parseTasks(result.Name, g.Name, &g.Tasks, o); err != nil { 311 return multierror.Prefix(err, fmt.Sprintf("'%s', task:", n)) 312 } 313 } 314 315 collection = append(collection, &g) 316 } 317 318 result.TaskGroups = append(result.TaskGroups, collection...) 319 return nil 320 } 321 322 func parseRestartPolicy(final **structs.RestartPolicy, list *ast.ObjectList) error { 323 list = list.Elem() 324 if len(list.Items) > 1 { 325 return fmt.Errorf("only one 'restart' block allowed") 326 } 327 328 // Get our job object 329 obj := list.Items[0] 330 331 // Check for invalid keys 332 valid := []string{ 333 "attempts", 334 "interval", 335 "delay", 336 "mode", 337 } 338 if err := checkHCLKeys(obj.Val, valid); err != nil { 339 return err 340 } 341 342 var m map[string]interface{} 343 if err := hcl.DecodeObject(&m, obj.Val); err != nil { 344 return err 345 } 346 347 var result structs.RestartPolicy 348 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 349 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 350 WeaklyTypedInput: true, 351 Result: &result, 352 }) 353 if err != nil { 354 return err 355 } 356 if err := dec.Decode(m); err != nil { 357 return err 358 } 359 360 *final = &result 361 return nil 362 } 363 364 func parseConstraints(result *[]*structs.Constraint, list *ast.ObjectList) error { 365 for _, o := range list.Elem().Items { 366 // Check for invalid keys 367 valid := []string{ 368 "attribute", 369 "operator", 370 "value", 371 "version", 372 "regexp", 373 "distinct_hosts", 374 } 375 if err := checkHCLKeys(o.Val, valid); err != nil { 376 return err 377 } 378 379 var m map[string]interface{} 380 if err := hcl.DecodeObject(&m, o.Val); err != nil { 381 return err 382 } 383 384 m["LTarget"] = m["attribute"] 385 m["RTarget"] = m["value"] 386 m["Operand"] = m["operator"] 387 388 // If "version" is provided, set the operand 389 // to "version" and the value to the "RTarget" 390 if constraint, ok := m[structs.ConstraintVersion]; ok { 391 m["Operand"] = structs.ConstraintVersion 392 m["RTarget"] = constraint 393 } 394 395 // If "regexp" is provided, set the operand 396 // to "regexp" and the value to the "RTarget" 397 if constraint, ok := m[structs.ConstraintRegex]; ok { 398 m["Operand"] = structs.ConstraintRegex 399 m["RTarget"] = constraint 400 } 401 402 if value, ok := m[structs.ConstraintDistinctHosts]; ok { 403 enabled, err := parseBool(value) 404 if err != nil { 405 return fmt.Errorf("distinct_hosts should be set to true or false; %v", err) 406 } 407 408 // If it is not enabled, skip the constraint. 409 if !enabled { 410 continue 411 } 412 413 m["Operand"] = structs.ConstraintDistinctHosts 414 } 415 416 // Build the constraint 417 var c structs.Constraint 418 if err := mapstructure.WeakDecode(m, &c); err != nil { 419 return err 420 } 421 if c.Operand == "" { 422 c.Operand = "=" 423 } 424 425 *result = append(*result, &c) 426 } 427 428 return nil 429 } 430 431 func parseEphemeralDisk(result **structs.EphemeralDisk, list *ast.ObjectList) error { 432 list = list.Elem() 433 if len(list.Items) > 1 { 434 return fmt.Errorf("only one 'ephemeral_disk' block allowed") 435 } 436 437 // Get our ephemeral_disk object 438 obj := list.Items[0] 439 440 // Check for invalid keys 441 valid := []string{ 442 "sticky", 443 "size", 444 } 445 if err := checkHCLKeys(obj.Val, valid); err != nil { 446 return err 447 } 448 449 var m map[string]interface{} 450 if err := hcl.DecodeObject(&m, obj.Val); err != nil { 451 return err 452 } 453 454 var ephemeralDisk structs.EphemeralDisk 455 if err := mapstructure.WeakDecode(m, &ephemeralDisk); err != nil { 456 return err 457 } 458 *result = &ephemeralDisk 459 460 return nil 461 } 462 463 // parseBool takes an interface value and tries to convert it to a boolean and 464 // returns an error if the type can't be converted. 465 func parseBool(value interface{}) (bool, error) { 466 var enabled bool 467 var err error 468 switch value.(type) { 469 case string: 470 enabled, err = strconv.ParseBool(value.(string)) 471 case bool: 472 enabled = value.(bool) 473 default: 474 err = fmt.Errorf("%v couldn't be converted to boolean value", value) 475 } 476 477 return enabled, err 478 } 479 480 func parseTasks(jobName string, taskGroupName string, result *[]*structs.Task, list *ast.ObjectList) error { 481 list = list.Children() 482 if len(list.Items) == 0 { 483 return nil 484 } 485 486 // Go through each object and turn it into an actual result. 487 seen := make(map[string]struct{}) 488 for _, item := range list.Items { 489 n := item.Keys[0].Token.Value().(string) 490 491 // Make sure we haven't already found this 492 if _, ok := seen[n]; ok { 493 return fmt.Errorf("task '%s' defined more than once", n) 494 } 495 seen[n] = struct{}{} 496 497 // We need this later 498 var listVal *ast.ObjectList 499 if ot, ok := item.Val.(*ast.ObjectType); ok { 500 listVal = ot.List 501 } else { 502 return fmt.Errorf("group '%s': should be an object", n) 503 } 504 505 // Check for invalid keys 506 valid := []string{ 507 "artifact", 508 "config", 509 "constraint", 510 "driver", 511 "env", 512 "exclude_nomad_env", 513 "kill_timeout", 514 "logs", 515 "meta", 516 "resources", 517 "service", 518 "user", 519 "vault", 520 } 521 if err := checkHCLKeys(listVal, valid); err != nil { 522 return multierror.Prefix(err, fmt.Sprintf("'%s' ->", n)) 523 } 524 525 var m map[string]interface{} 526 if err := hcl.DecodeObject(&m, item.Val); err != nil { 527 return err 528 } 529 delete(m, "artifact") 530 delete(m, "config") 531 delete(m, "constraint") 532 delete(m, "env") 533 delete(m, "exclude_nomad_env") 534 delete(m, "logs") 535 delete(m, "meta") 536 delete(m, "resources") 537 delete(m, "service") 538 delete(m, "vault") 539 540 // Build the task 541 var t structs.Task 542 t.Name = n 543 if taskGroupName == "" { 544 taskGroupName = n 545 } 546 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 547 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 548 WeaklyTypedInput: true, 549 Result: &t, 550 }) 551 if err != nil { 552 return err 553 } 554 if err := dec.Decode(m); err != nil { 555 return err 556 } 557 558 // If we have env, then parse them 559 if o := listVal.Filter("env"); len(o.Items) > 0 { 560 for _, o := range o.Elem().Items { 561 var m map[string]interface{} 562 if err := hcl.DecodeObject(&m, o.Val); err != nil { 563 return err 564 } 565 if err := mapstructure.WeakDecode(m, &t.Env); err != nil { 566 return err 567 } 568 } 569 } 570 571 if o := listVal.Filter("service"); len(o.Items) > 0 { 572 if err := parseServices(jobName, taskGroupName, &t, o); err != nil { 573 return multierror.Prefix(err, fmt.Sprintf("'%s',", n)) 574 } 575 } 576 577 // If we have config, then parse that 578 if o := listVal.Filter("config"); len(o.Items) > 0 { 579 for _, o := range o.Elem().Items { 580 var m map[string]interface{} 581 if err := hcl.DecodeObject(&m, o.Val); err != nil { 582 return err 583 } 584 585 if err := mapstructure.WeakDecode(m, &t.Config); err != nil { 586 return err 587 } 588 } 589 590 // Instantiate a driver to validate the configuration 591 d, err := driver.NewDriver( 592 t.Driver, 593 driver.NewEmptyDriverContext(), 594 ) 595 596 if err != nil { 597 return multierror.Prefix(err, 598 fmt.Sprintf("'%s', config ->", n)) 599 } 600 601 if err := d.Validate(t.Config); err != nil { 602 return multierror.Prefix(err, 603 fmt.Sprintf("'%s', config ->", n)) 604 } 605 } 606 607 // Parse constraints 608 if o := listVal.Filter("constraint"); len(o.Items) > 0 { 609 if err := parseConstraints(&t.Constraints, o); err != nil { 610 return multierror.Prefix(err, fmt.Sprintf( 611 "'%s', constraint ->", n)) 612 } 613 } 614 615 // Parse out meta fields. These are in HCL as a list so we need 616 // to iterate over them and merge them. 617 if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 { 618 for _, o := range metaO.Elem().Items { 619 var m map[string]interface{} 620 if err := hcl.DecodeObject(&m, o.Val); err != nil { 621 return err 622 } 623 if err := mapstructure.WeakDecode(m, &t.Meta); err != nil { 624 return err 625 } 626 } 627 } 628 629 // If we have resources, then parse that 630 if o := listVal.Filter("resources"); len(o.Items) > 0 { 631 var r structs.Resources 632 if err := parseResources(&r, o); err != nil { 633 return multierror.Prefix(err, fmt.Sprintf("'%s',", n)) 634 } 635 636 t.Resources = &r 637 } 638 639 // If we have logs then parse that 640 logConfig := structs.DefaultLogConfig() 641 if o := listVal.Filter("logs"); len(o.Items) > 0 { 642 if len(o.Items) > 1 { 643 return fmt.Errorf("only one logs block is allowed in a Task. Number of logs block found: %d", len(o.Items)) 644 } 645 var m map[string]interface{} 646 logsBlock := o.Items[0] 647 648 // Check for invalid keys 649 valid := []string{ 650 "max_files", 651 "max_file_size", 652 "log_shuttle_config", 653 } 654 if err := checkHCLKeys(logsBlock.Val, valid); err != nil { 655 return multierror.Prefix(err, fmt.Sprintf("'%s', logs ->", n)) 656 } 657 658 if err := hcl.DecodeObject(&m, logsBlock.Val); err != nil { 659 return err 660 } 661 662 if err := mapstructure.WeakDecode(m, &logConfig); err != nil { 663 return err 664 } 665 } 666 t.LogConfig = logConfig 667 668 // Parse artifacts 669 if o := listVal.Filter("artifact"); len(o.Items) > 0 { 670 if err := parseArtifacts(&t.Artifacts, o); err != nil { 671 return multierror.Prefix(err, fmt.Sprintf("'%s', artifact ->", n)) 672 } 673 } 674 675 // If we have a vault block, then parse that 676 if o := listVal.Filter("vault"); len(o.Items) > 0 { 677 var v structs.Vault 678 if err := parseVault(&v, o); err != nil { 679 return multierror.Prefix(err, fmt.Sprintf("'%s', vault ->", n)) 680 } 681 682 t.Vault = &v 683 } 684 685 *result = append(*result, &t) 686 } 687 688 return nil 689 } 690 691 func parseArtifacts(result *[]*structs.TaskArtifact, list *ast.ObjectList) error { 692 for _, o := range list.Elem().Items { 693 // Check for invalid keys 694 valid := []string{ 695 "source", 696 "options", 697 "destination", 698 } 699 if err := checkHCLKeys(o.Val, valid); err != nil { 700 return err 701 } 702 703 var m map[string]interface{} 704 if err := hcl.DecodeObject(&m, o.Val); err != nil { 705 return err 706 } 707 708 delete(m, "options") 709 710 // Default to downloading to the local directory. 711 if _, ok := m["destination"]; !ok { 712 m["destination"] = "local/" 713 } 714 715 var ta structs.TaskArtifact 716 if err := mapstructure.WeakDecode(m, &ta); err != nil { 717 return err 718 } 719 720 var optionList *ast.ObjectList 721 if ot, ok := o.Val.(*ast.ObjectType); ok { 722 optionList = ot.List 723 } else { 724 return fmt.Errorf("artifact should be an object") 725 } 726 727 if oo := optionList.Filter("options"); len(oo.Items) > 0 { 728 options := make(map[string]string) 729 if err := parseArtifactOption(options, oo); err != nil { 730 return multierror.Prefix(err, "options: ") 731 } 732 ta.GetterOptions = options 733 } 734 735 *result = append(*result, &ta) 736 } 737 738 return nil 739 } 740 741 func parseArtifactOption(result map[string]string, list *ast.ObjectList) error { 742 list = list.Elem() 743 if len(list.Items) > 1 { 744 return fmt.Errorf("only one 'options' block allowed per artifact") 745 } 746 747 // Get our resource object 748 o := list.Items[0] 749 750 var m map[string]interface{} 751 if err := hcl.DecodeObject(&m, o.Val); err != nil { 752 return err 753 } 754 755 if err := mapstructure.WeakDecode(m, &result); err != nil { 756 return err 757 } 758 759 return nil 760 } 761 762 func parseServices(jobName string, taskGroupName string, task *structs.Task, serviceObjs *ast.ObjectList) error { 763 task.Services = make([]*structs.Service, len(serviceObjs.Items)) 764 var defaultServiceName bool 765 for idx, o := range serviceObjs.Items { 766 // Check for invalid keys 767 valid := []string{ 768 "name", 769 "tags", 770 "port", 771 "check", 772 } 773 if err := checkHCLKeys(o.Val, valid); err != nil { 774 return multierror.Prefix(err, fmt.Sprintf("service (%d) ->", idx)) 775 } 776 777 var service structs.Service 778 var m map[string]interface{} 779 if err := hcl.DecodeObject(&m, o.Val); err != nil { 780 return err 781 } 782 783 delete(m, "check") 784 785 if err := mapstructure.WeakDecode(m, &service); err != nil { 786 return err 787 } 788 789 if defaultServiceName && service.Name == "" { 790 return fmt.Errorf("Only one service block may omit the Name field") 791 } 792 793 if service.Name == "" { 794 defaultServiceName = true 795 service.Name = fmt.Sprintf("%s-%s-%s", jobName, taskGroupName, task.Name) 796 } 797 798 // Filter checks 799 var checkList *ast.ObjectList 800 if ot, ok := o.Val.(*ast.ObjectType); ok { 801 checkList = ot.List 802 } else { 803 return fmt.Errorf("service '%s': should be an object", service.Name) 804 } 805 806 if co := checkList.Filter("check"); len(co.Items) > 0 { 807 if err := parseChecks(&service, co); err != nil { 808 return multierror.Prefix(err, fmt.Sprintf("service: '%s',", service.Name)) 809 } 810 } 811 812 task.Services[idx] = &service 813 } 814 815 return nil 816 } 817 818 func parseChecks(service *structs.Service, checkObjs *ast.ObjectList) error { 819 service.Checks = make([]*structs.ServiceCheck, len(checkObjs.Items)) 820 for idx, co := range checkObjs.Items { 821 // Check for invalid keys 822 valid := []string{ 823 "name", 824 "type", 825 "interval", 826 "timeout", 827 "path", 828 "protocol", 829 "port", 830 "command", 831 "args", 832 "initial_status", 833 } 834 if err := checkHCLKeys(co.Val, valid); err != nil { 835 return multierror.Prefix(err, "check ->") 836 } 837 838 var check structs.ServiceCheck 839 var cm map[string]interface{} 840 if err := hcl.DecodeObject(&cm, co.Val); err != nil { 841 return err 842 } 843 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 844 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 845 WeaklyTypedInput: true, 846 Result: &check, 847 }) 848 if err != nil { 849 return err 850 } 851 if err := dec.Decode(cm); err != nil { 852 return err 853 } 854 855 service.Checks[idx] = &check 856 } 857 858 return nil 859 } 860 861 func parseResources(result *structs.Resources, list *ast.ObjectList) error { 862 list = list.Elem() 863 if len(list.Items) == 0 { 864 return nil 865 } 866 if len(list.Items) > 1 { 867 return fmt.Errorf("only one 'resource' block allowed per task") 868 } 869 870 // Get our resource object 871 o := list.Items[0] 872 873 // We need this later 874 var listVal *ast.ObjectList 875 if ot, ok := o.Val.(*ast.ObjectType); ok { 876 listVal = ot.List 877 } else { 878 return fmt.Errorf("resource: should be an object") 879 } 880 881 // Check for invalid keys 882 valid := []string{ 883 "cpu", 884 "iops", 885 "memory", 886 "network", 887 } 888 if err := checkHCLKeys(listVal, valid); err != nil { 889 return multierror.Prefix(err, "resources ->") 890 } 891 892 var m map[string]interface{} 893 if err := hcl.DecodeObject(&m, o.Val); err != nil { 894 return err 895 } 896 delete(m, "network") 897 898 if err := mapstructure.WeakDecode(m, result); err != nil { 899 return err 900 } 901 902 // Parse the network resources 903 if o := listVal.Filter("network"); len(o.Items) > 0 { 904 if len(o.Items) > 1 { 905 return fmt.Errorf("only one 'network' resource allowed") 906 } 907 908 // Check for invalid keys 909 valid := []string{ 910 "mbits", 911 "port", 912 } 913 if err := checkHCLKeys(o.Items[0].Val, valid); err != nil { 914 return multierror.Prefix(err, "resources, network ->") 915 } 916 917 var r structs.NetworkResource 918 var m map[string]interface{} 919 if err := hcl.DecodeObject(&m, o.Items[0].Val); err != nil { 920 return err 921 } 922 if err := mapstructure.WeakDecode(m, &r); err != nil { 923 return err 924 } 925 926 var networkObj *ast.ObjectList 927 if ot, ok := o.Items[0].Val.(*ast.ObjectType); ok { 928 networkObj = ot.List 929 } else { 930 return fmt.Errorf("resource: should be an object") 931 } 932 if err := parsePorts(networkObj, &r); err != nil { 933 return multierror.Prefix(err, "resources, network, ports ->") 934 } 935 936 result.Networks = []*structs.NetworkResource{&r} 937 } 938 939 // Combine the parsed resources with a default resource block. 940 min := structs.DefaultResources() 941 min.Merge(result) 942 *result = *min 943 return nil 944 } 945 946 func parsePorts(networkObj *ast.ObjectList, nw *structs.NetworkResource) error { 947 // Check for invalid keys 948 valid := []string{ 949 "mbits", 950 "port", 951 } 952 if err := checkHCLKeys(networkObj, valid); err != nil { 953 return err 954 } 955 956 portsObjList := networkObj.Filter("port") 957 knownPortLabels := make(map[string]bool) 958 for _, port := range portsObjList.Items { 959 if len(port.Keys) == 0 { 960 return fmt.Errorf("ports must be named") 961 } 962 label := port.Keys[0].Token.Value().(string) 963 if !reDynamicPorts.MatchString(label) { 964 return errPortLabel 965 } 966 l := strings.ToLower(label) 967 if knownPortLabels[l] { 968 return fmt.Errorf("found a port label collision: %s", label) 969 } 970 var p map[string]interface{} 971 var res structs.Port 972 if err := hcl.DecodeObject(&p, port.Val); err != nil { 973 return err 974 } 975 if err := mapstructure.WeakDecode(p, &res); err != nil { 976 return err 977 } 978 res.Label = label 979 if res.Value > 0 { 980 nw.ReservedPorts = append(nw.ReservedPorts, res) 981 } else { 982 nw.DynamicPorts = append(nw.DynamicPorts, res) 983 } 984 knownPortLabels[l] = true 985 } 986 return nil 987 } 988 989 func parseUpdate(result *structs.UpdateStrategy, list *ast.ObjectList) error { 990 list = list.Elem() 991 if len(list.Items) > 1 { 992 return fmt.Errorf("only one 'update' block allowed per job") 993 } 994 995 // Get our resource object 996 o := list.Items[0] 997 998 var m map[string]interface{} 999 if err := hcl.DecodeObject(&m, o.Val); err != nil { 1000 return err 1001 } 1002 1003 // Check for invalid keys 1004 valid := []string{ 1005 "stagger", 1006 "max_parallel", 1007 } 1008 if err := checkHCLKeys(o.Val, valid); err != nil { 1009 return err 1010 } 1011 1012 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 1013 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 1014 WeaklyTypedInput: true, 1015 Result: result, 1016 }) 1017 if err != nil { 1018 return err 1019 } 1020 return dec.Decode(m) 1021 } 1022 1023 func parsePeriodic(result **structs.PeriodicConfig, list *ast.ObjectList) error { 1024 list = list.Elem() 1025 if len(list.Items) > 1 { 1026 return fmt.Errorf("only one 'periodic' block allowed per job") 1027 } 1028 1029 // Get our resource object 1030 o := list.Items[0] 1031 1032 var m map[string]interface{} 1033 if err := hcl.DecodeObject(&m, o.Val); err != nil { 1034 return err 1035 } 1036 1037 // Check for invalid keys 1038 valid := []string{ 1039 "enabled", 1040 "cron", 1041 "prohibit_overlap", 1042 } 1043 if err := checkHCLKeys(o.Val, valid); err != nil { 1044 return err 1045 } 1046 1047 // Enabled by default if the periodic block exists. 1048 if value, ok := m["enabled"]; !ok { 1049 m["Enabled"] = true 1050 } else { 1051 enabled, err := parseBool(value) 1052 if err != nil { 1053 return fmt.Errorf("periodic.enabled should be set to true or false; %v", err) 1054 } 1055 m["Enabled"] = enabled 1056 } 1057 1058 // If "cron" is provided, set the type to "cron" and store the spec. 1059 if cron, ok := m["cron"]; ok { 1060 m["SpecType"] = structs.PeriodicSpecCron 1061 m["Spec"] = cron 1062 } 1063 1064 // Build the constraint 1065 var p structs.PeriodicConfig 1066 if err := mapstructure.WeakDecode(m, &p); err != nil { 1067 return err 1068 } 1069 *result = &p 1070 return nil 1071 } 1072 1073 func parseVault(result *structs.Vault, list *ast.ObjectList) error { 1074 list = list.Elem() 1075 if len(list.Items) == 0 { 1076 return nil 1077 } 1078 if len(list.Items) > 1 { 1079 return fmt.Errorf("only one 'vault' block allowed per task") 1080 } 1081 1082 // Get our resource object 1083 o := list.Items[0] 1084 1085 // We need this later 1086 var listVal *ast.ObjectList 1087 if ot, ok := o.Val.(*ast.ObjectType); ok { 1088 listVal = ot.List 1089 } else { 1090 return fmt.Errorf("vault: should be an object") 1091 } 1092 1093 // Check for invalid keys 1094 valid := []string{ 1095 "policies", 1096 } 1097 if err := checkHCLKeys(listVal, valid); err != nil { 1098 return multierror.Prefix(err, "vault ->") 1099 } 1100 1101 var m map[string]interface{} 1102 if err := hcl.DecodeObject(&m, o.Val); err != nil { 1103 return err 1104 } 1105 1106 if err := mapstructure.WeakDecode(m, result); err != nil { 1107 return err 1108 } 1109 1110 return nil 1111 } 1112 1113 func checkHCLKeys(node ast.Node, valid []string) error { 1114 var list *ast.ObjectList 1115 switch n := node.(type) { 1116 case *ast.ObjectList: 1117 list = n 1118 case *ast.ObjectType: 1119 list = n.List 1120 default: 1121 return fmt.Errorf("cannot check HCL keys of type %T", n) 1122 } 1123 1124 validMap := make(map[string]struct{}, len(valid)) 1125 for _, v := range valid { 1126 validMap[v] = struct{}{} 1127 } 1128 1129 var result error 1130 for _, item := range list.Items { 1131 key := item.Keys[0].Token.Value().(string) 1132 if _, ok := validMap[key]; !ok { 1133 result = multierror.Append(result, fmt.Errorf( 1134 "invalid key: %s", key)) 1135 } 1136 } 1137 1138 return result 1139 }