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