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