github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/command/agent/config_parse.go (about) 1 package agent 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "os" 8 "path/filepath" 9 "time" 10 11 "github.com/hashicorp/go-multierror" 12 "github.com/hashicorp/hcl" 13 "github.com/hashicorp/hcl/hcl/ast" 14 "github.com/hashicorp/nomad/nomad/structs/config" 15 "github.com/mitchellh/mapstructure" 16 ) 17 18 // ParseConfigFile parses the given path as a config file. 19 func ParseConfigFile(path string) (*Config, error) { 20 path, err := filepath.Abs(path) 21 if err != nil { 22 return nil, err 23 } 24 25 f, err := os.Open(path) 26 if err != nil { 27 return nil, err 28 } 29 defer f.Close() 30 31 config, err := ParseConfig(f) 32 if err != nil { 33 return nil, err 34 } 35 36 return config, nil 37 } 38 39 // ParseConfig parses the config from the given io.Reader. 40 // 41 // Due to current internal limitations, the entire contents of the 42 // io.Reader will be copied into memory first before parsing. 43 func ParseConfig(r io.Reader) (*Config, error) { 44 // Copy the reader into an in-memory buffer first since HCL requires it. 45 var buf bytes.Buffer 46 if _, err := io.Copy(&buf, r); err != nil { 47 return nil, err 48 } 49 50 // Parse the buffer 51 root, err := hcl.Parse(buf.String()) 52 if err != nil { 53 return nil, fmt.Errorf("error parsing: %s", err) 54 } 55 buf.Reset() 56 57 // Top-level item should be a list 58 list, ok := root.Node.(*ast.ObjectList) 59 if !ok { 60 return nil, fmt.Errorf("error parsing: root should be an object") 61 } 62 63 var config Config 64 if err := parseConfig(&config, list); err != nil { 65 return nil, fmt.Errorf("error parsing 'config': %v", err) 66 } 67 68 return &config, nil 69 } 70 71 func parseConfig(result *Config, list *ast.ObjectList) error { 72 // Check for invalid keys 73 valid := []string{ 74 "region", 75 "datacenter", 76 "name", 77 "data_dir", 78 "log_level", 79 "bind_addr", 80 "enable_debug", 81 "ports", 82 "addresses", 83 "interfaces", 84 "advertise", 85 "client", 86 "server", 87 "telemetry", 88 "leave_on_interrupt", 89 "leave_on_terminate", 90 "enable_syslog", 91 "syslog_facility", 92 "disable_update_check", 93 "disable_anonymous_signature", 94 "atlas", 95 "consul", 96 "vault", 97 "tls", 98 "http_api_response_headers", 99 } 100 if err := checkHCLKeys(list, valid); err != nil { 101 return multierror.Prefix(err, "config:") 102 } 103 104 // Decode the full thing into a map[string]interface for ease 105 var m map[string]interface{} 106 if err := hcl.DecodeObject(&m, list); err != nil { 107 return err 108 } 109 delete(m, "ports") 110 delete(m, "addresses") 111 delete(m, "interfaces") 112 delete(m, "advertise") 113 delete(m, "client") 114 delete(m, "server") 115 delete(m, "telemetry") 116 delete(m, "atlas") 117 delete(m, "consul") 118 delete(m, "vault") 119 delete(m, "tls") 120 delete(m, "http_api_response_headers") 121 122 // Decode the rest 123 if err := mapstructure.WeakDecode(m, result); err != nil { 124 return err 125 } 126 127 // Parse ports 128 if o := list.Filter("ports"); len(o.Items) > 0 { 129 if err := parsePorts(&result.Ports, o); err != nil { 130 return multierror.Prefix(err, "ports ->") 131 } 132 } 133 134 // Parse addresses 135 if o := list.Filter("addresses"); len(o.Items) > 0 { 136 if err := parseAddresses(&result.Addresses, o); err != nil { 137 return multierror.Prefix(err, "addresses ->") 138 } 139 } 140 141 // Parse advertise 142 if o := list.Filter("advertise"); len(o.Items) > 0 { 143 if err := parseAdvertise(&result.AdvertiseAddrs, o); err != nil { 144 return multierror.Prefix(err, "advertise ->") 145 } 146 } 147 148 // Parse client config 149 if o := list.Filter("client"); len(o.Items) > 0 { 150 if err := parseClient(&result.Client, o); err != nil { 151 return multierror.Prefix(err, "client ->") 152 } 153 } 154 155 // Parse server config 156 if o := list.Filter("server"); len(o.Items) > 0 { 157 if err := parseServer(&result.Server, o); err != nil { 158 return multierror.Prefix(err, "server ->") 159 } 160 } 161 162 // Parse telemetry config 163 if o := list.Filter("telemetry"); len(o.Items) > 0 { 164 if err := parseTelemetry(&result.Telemetry, o); err != nil { 165 return multierror.Prefix(err, "telemetry ->") 166 } 167 } 168 169 // Parse atlas config 170 if o := list.Filter("atlas"); len(o.Items) > 0 { 171 if err := parseAtlas(&result.Atlas, o); err != nil { 172 return multierror.Prefix(err, "atlas ->") 173 } 174 } 175 176 // Parse the consul config 177 if o := list.Filter("consul"); len(o.Items) > 0 { 178 if err := parseConsulConfig(&result.Consul, o); err != nil { 179 return multierror.Prefix(err, "consul ->") 180 } 181 } 182 183 // Parse the vault config 184 if o := list.Filter("vault"); len(o.Items) > 0 { 185 if err := parseVaultConfig(&result.Vault, o); err != nil { 186 return multierror.Prefix(err, "vault ->") 187 } 188 } 189 190 // Parse the TLS config 191 if o := list.Filter("tls"); len(o.Items) > 0 { 192 if err := parseTLSConfig(&result.TLSConfig, o); err != nil { 193 return multierror.Prefix(err, "tls ->") 194 } 195 } 196 197 // Parse out http_api_response_headers fields. These are in HCL as a list so 198 // we need to iterate over them and merge them. 199 if headersO := list.Filter("http_api_response_headers"); len(headersO.Items) > 0 { 200 for _, o := range headersO.Elem().Items { 201 var m map[string]interface{} 202 if err := hcl.DecodeObject(&m, o.Val); err != nil { 203 return err 204 } 205 if err := mapstructure.WeakDecode(m, &result.HTTPAPIResponseHeaders); err != nil { 206 return err 207 } 208 } 209 } 210 211 return nil 212 } 213 214 func parsePorts(result **Ports, list *ast.ObjectList) error { 215 list = list.Elem() 216 if len(list.Items) > 1 { 217 return fmt.Errorf("only one 'ports' block allowed") 218 } 219 220 // Get our ports object 221 listVal := list.Items[0].Val 222 223 // Check for invalid keys 224 valid := []string{ 225 "http", 226 "rpc", 227 "serf", 228 } 229 if err := checkHCLKeys(listVal, valid); err != nil { 230 return err 231 } 232 233 var m map[string]interface{} 234 if err := hcl.DecodeObject(&m, listVal); err != nil { 235 return err 236 } 237 238 var ports Ports 239 if err := mapstructure.WeakDecode(m, &ports); err != nil { 240 return err 241 } 242 *result = &ports 243 return nil 244 } 245 246 func parseAddresses(result **Addresses, list *ast.ObjectList) error { 247 list = list.Elem() 248 if len(list.Items) > 1 { 249 return fmt.Errorf("only one 'addresses' block allowed") 250 } 251 252 // Get our addresses object 253 listVal := list.Items[0].Val 254 255 // Check for invalid keys 256 valid := []string{ 257 "http", 258 "rpc", 259 "serf", 260 } 261 if err := checkHCLKeys(listVal, valid); err != nil { 262 return err 263 } 264 265 var m map[string]interface{} 266 if err := hcl.DecodeObject(&m, listVal); err != nil { 267 return err 268 } 269 270 var addresses Addresses 271 if err := mapstructure.WeakDecode(m, &addresses); err != nil { 272 return err 273 } 274 *result = &addresses 275 return nil 276 } 277 278 func parseAdvertise(result **AdvertiseAddrs, list *ast.ObjectList) error { 279 list = list.Elem() 280 if len(list.Items) > 1 { 281 return fmt.Errorf("only one 'advertise' block allowed") 282 } 283 284 // Get our advertise object 285 listVal := list.Items[0].Val 286 287 // Check for invalid keys 288 valid := []string{ 289 "http", 290 "rpc", 291 "serf", 292 } 293 if err := checkHCLKeys(listVal, valid); err != nil { 294 return err 295 } 296 297 var m map[string]interface{} 298 if err := hcl.DecodeObject(&m, listVal); err != nil { 299 return err 300 } 301 302 var advertise AdvertiseAddrs 303 if err := mapstructure.WeakDecode(m, &advertise); err != nil { 304 return err 305 } 306 *result = &advertise 307 return nil 308 } 309 310 func parseClient(result **ClientConfig, list *ast.ObjectList) error { 311 list = list.Elem() 312 if len(list.Items) > 1 { 313 return fmt.Errorf("only one 'client' block allowed") 314 } 315 316 // Get our client object 317 obj := list.Items[0] 318 319 // Value should be an object 320 var listVal *ast.ObjectList 321 if ot, ok := obj.Val.(*ast.ObjectType); ok { 322 listVal = ot.List 323 } else { 324 return fmt.Errorf("client value: should be an object") 325 } 326 327 // Check for invalid keys 328 valid := []string{ 329 "enabled", 330 "state_dir", 331 "alloc_dir", 332 "servers", 333 "node_class", 334 "options", 335 "meta", 336 "chroot_env", 337 "network_interface", 338 "network_speed", 339 "max_kill_timeout", 340 "client_max_port", 341 "client_min_port", 342 "reserved", 343 "stats", 344 } 345 if err := checkHCLKeys(listVal, valid); err != nil { 346 return err 347 } 348 349 var m map[string]interface{} 350 if err := hcl.DecodeObject(&m, listVal); err != nil { 351 return err 352 } 353 354 delete(m, "options") 355 delete(m, "meta") 356 delete(m, "chroot_env") 357 delete(m, "reserved") 358 delete(m, "stats") 359 360 var config ClientConfig 361 if err := mapstructure.WeakDecode(m, &config); err != nil { 362 return err 363 } 364 365 // Parse out options fields. These are in HCL as a list so we need to 366 // iterate over them and merge them. 367 if optionsO := listVal.Filter("options"); len(optionsO.Items) > 0 { 368 for _, o := range optionsO.Elem().Items { 369 var m map[string]interface{} 370 if err := hcl.DecodeObject(&m, o.Val); err != nil { 371 return err 372 } 373 if err := mapstructure.WeakDecode(m, &config.Options); err != nil { 374 return err 375 } 376 } 377 } 378 379 // Parse out options meta. These are in HCL as a list so we need to 380 // iterate over them and merge them. 381 if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 { 382 for _, o := range metaO.Elem().Items { 383 var m map[string]interface{} 384 if err := hcl.DecodeObject(&m, o.Val); err != nil { 385 return err 386 } 387 if err := mapstructure.WeakDecode(m, &config.Meta); err != nil { 388 return err 389 } 390 } 391 } 392 393 // Parse out chroot_env fields. These are in HCL as a list so we need to 394 // iterate over them and merge them. 395 if chrootEnvO := listVal.Filter("chroot_env"); len(chrootEnvO.Items) > 0 { 396 for _, o := range chrootEnvO.Elem().Items { 397 var m map[string]interface{} 398 if err := hcl.DecodeObject(&m, o.Val); err != nil { 399 return err 400 } 401 if err := mapstructure.WeakDecode(m, &config.ChrootEnv); err != nil { 402 return err 403 } 404 } 405 } 406 407 // Parse reserved config 408 if o := listVal.Filter("reserved"); len(o.Items) > 0 { 409 if err := parseReserved(&config.Reserved, o); err != nil { 410 return multierror.Prefix(err, "reserved ->") 411 } 412 } 413 414 *result = &config 415 return nil 416 } 417 418 func parseReserved(result **Resources, list *ast.ObjectList) error { 419 list = list.Elem() 420 if len(list.Items) > 1 { 421 return fmt.Errorf("only one 'reserved' block allowed") 422 } 423 424 // Get our reserved object 425 obj := list.Items[0] 426 427 // Value should be an object 428 var listVal *ast.ObjectList 429 if ot, ok := obj.Val.(*ast.ObjectType); ok { 430 listVal = ot.List 431 } else { 432 return fmt.Errorf("client value: should be an object") 433 } 434 435 // Check for invalid keys 436 valid := []string{ 437 "cpu", 438 "memory", 439 "disk", 440 "iops", 441 "reserved_ports", 442 } 443 if err := checkHCLKeys(listVal, valid); err != nil { 444 return err 445 } 446 447 var m map[string]interface{} 448 if err := hcl.DecodeObject(&m, listVal); err != nil { 449 return err 450 } 451 452 var reserved Resources 453 if err := mapstructure.WeakDecode(m, &reserved); err != nil { 454 return err 455 } 456 if err := reserved.ParseReserved(); err != nil { 457 return err 458 } 459 460 *result = &reserved 461 return nil 462 } 463 464 func parseServer(result **ServerConfig, list *ast.ObjectList) error { 465 list = list.Elem() 466 if len(list.Items) > 1 { 467 return fmt.Errorf("only one 'server' block allowed") 468 } 469 470 // Get our server object 471 obj := list.Items[0] 472 473 // Value should be an object 474 var listVal *ast.ObjectList 475 if ot, ok := obj.Val.(*ast.ObjectType); ok { 476 listVal = ot.List 477 } else { 478 return fmt.Errorf("client value: should be an object") 479 } 480 481 // Check for invalid keys 482 valid := []string{ 483 "enabled", 484 "bootstrap_expect", 485 "data_dir", 486 "protocol_version", 487 "num_schedulers", 488 "enabled_schedulers", 489 "node_gc_threshold", 490 "heartbeat_grace", 491 "start_join", 492 "retry_join", 493 "retry_max", 494 "retry_interval", 495 "rejoin_after_leave", 496 "encrypt", 497 } 498 if err := checkHCLKeys(listVal, valid); err != nil { 499 return err 500 } 501 502 var m map[string]interface{} 503 if err := hcl.DecodeObject(&m, listVal); err != nil { 504 return err 505 } 506 507 var config ServerConfig 508 if err := mapstructure.WeakDecode(m, &config); err != nil { 509 return err 510 } 511 512 *result = &config 513 return nil 514 } 515 516 func parseTelemetry(result **Telemetry, list *ast.ObjectList) error { 517 list = list.Elem() 518 if len(list.Items) > 1 { 519 return fmt.Errorf("only one 'telemetry' block allowed") 520 } 521 522 // Get our telemetry object 523 listVal := list.Items[0].Val 524 525 // Check for invalid keys 526 valid := []string{ 527 "statsite_address", 528 "statsd_address", 529 "disable_hostname", 530 "collection_interval", 531 "publish_allocation_metrics", 532 "publish_node_metrics", 533 "datadog_address", 534 "circonus_api_token", 535 "circonus_api_app", 536 "circonus_api_url", 537 "circonus_submission_interval", 538 "circonus_submission_url", 539 "circonus_check_id", 540 "circonus_check_force_metric_activation", 541 "circonus_check_instance_id", 542 "circonus_check_search_tag", 543 "circonus_check_display_name", 544 "circonus_check_tags", 545 "circonus_broker_id", 546 "circonus_broker_select_tag", 547 } 548 if err := checkHCLKeys(listVal, valid); err != nil { 549 return err 550 } 551 552 var m map[string]interface{} 553 if err := hcl.DecodeObject(&m, listVal); err != nil { 554 return err 555 } 556 557 var telemetry Telemetry 558 if err := mapstructure.WeakDecode(m, &telemetry); err != nil { 559 return err 560 } 561 if telemetry.CollectionInterval != "" { 562 if dur, err := time.ParseDuration(telemetry.CollectionInterval); err != nil { 563 return fmt.Errorf("error parsing value of %q: %v", "collection_interval", err) 564 } else { 565 telemetry.collectionInterval = dur 566 } 567 } 568 *result = &telemetry 569 return nil 570 } 571 572 func parseAtlas(result **AtlasConfig, list *ast.ObjectList) error { 573 list = list.Elem() 574 if len(list.Items) > 1 { 575 return fmt.Errorf("only one 'atlas' block allowed") 576 } 577 578 // Get our atlas object 579 listVal := list.Items[0].Val 580 581 // Check for invalid keys 582 valid := []string{ 583 "infrastructure", 584 "token", 585 "join", 586 "endpoint", 587 } 588 if err := checkHCLKeys(listVal, valid); err != nil { 589 return err 590 } 591 592 var m map[string]interface{} 593 if err := hcl.DecodeObject(&m, listVal); err != nil { 594 return err 595 } 596 597 var atlas AtlasConfig 598 if err := mapstructure.WeakDecode(m, &atlas); err != nil { 599 return err 600 } 601 *result = &atlas 602 return nil 603 } 604 605 func parseConsulConfig(result **config.ConsulConfig, list *ast.ObjectList) error { 606 list = list.Elem() 607 if len(list.Items) > 1 { 608 return fmt.Errorf("only one 'consul' block allowed") 609 } 610 611 // Get our Consul object 612 listVal := list.Items[0].Val 613 614 // Check for invalid keys 615 valid := []string{ 616 "address", 617 "auth", 618 "auto_advertise", 619 "ca_file", 620 "cert_file", 621 "checks_use_advertise", 622 "client_auto_join", 623 "client_service_name", 624 "key_file", 625 "server_auto_join", 626 "server_service_name", 627 "ssl", 628 "timeout", 629 "token", 630 "verify_ssl", 631 } 632 633 if err := checkHCLKeys(listVal, valid); err != nil { 634 return err 635 } 636 637 var m map[string]interface{} 638 if err := hcl.DecodeObject(&m, listVal); err != nil { 639 return err 640 } 641 642 consulConfig := config.DefaultConsulConfig() 643 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 644 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 645 WeaklyTypedInput: true, 646 Result: &consulConfig, 647 }) 648 if err != nil { 649 return err 650 } 651 if err := dec.Decode(m); err != nil { 652 return err 653 } 654 655 *result = consulConfig 656 return nil 657 } 658 659 func parseTLSConfig(result **config.TLSConfig, list *ast.ObjectList) error { 660 list = list.Elem() 661 if len(list.Items) > 1 { 662 return fmt.Errorf("only one 'tls' block allowed") 663 } 664 665 // Get the TLS object 666 listVal := list.Items[0].Val 667 668 valid := []string{ 669 "http", 670 "rpc", 671 "verify_server_hostname", 672 "ca_file", 673 "cert_file", 674 "key_file", 675 } 676 677 if err := checkHCLKeys(listVal, valid); err != nil { 678 return err 679 } 680 681 var m map[string]interface{} 682 if err := hcl.DecodeObject(&m, listVal); err != nil { 683 return err 684 } 685 686 var tlsConfig config.TLSConfig 687 if err := mapstructure.WeakDecode(m, &tlsConfig); err != nil { 688 return err 689 } 690 *result = &tlsConfig 691 return nil 692 } 693 694 func parseVaultConfig(result **config.VaultConfig, list *ast.ObjectList) error { 695 list = list.Elem() 696 if len(list.Items) > 1 { 697 return fmt.Errorf("only one 'vault' block allowed") 698 } 699 700 // Get our Vault object 701 listVal := list.Items[0].Val 702 703 // Check for invalid keys 704 valid := []string{ 705 "address", 706 "allow_unauthenticated", 707 "enabled", 708 "task_token_ttl", 709 "ca_file", 710 "ca_path", 711 "cert_file", 712 "key_file", 713 "tls_server_name", 714 "tls_skip_verify", 715 "token", 716 } 717 718 if err := checkHCLKeys(listVal, valid); err != nil { 719 return err 720 } 721 722 var m map[string]interface{} 723 if err := hcl.DecodeObject(&m, listVal); err != nil { 724 return err 725 } 726 727 vaultConfig := config.DefaultVaultConfig() 728 dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{ 729 DecodeHook: mapstructure.StringToTimeDurationHookFunc(), 730 WeaklyTypedInput: true, 731 Result: &vaultConfig, 732 }) 733 if err != nil { 734 return err 735 } 736 if err := dec.Decode(m); err != nil { 737 return err 738 } 739 740 *result = vaultConfig 741 return nil 742 } 743 744 func checkHCLKeys(node ast.Node, valid []string) error { 745 var list *ast.ObjectList 746 switch n := node.(type) { 747 case *ast.ObjectList: 748 list = n 749 case *ast.ObjectType: 750 list = n.List 751 default: 752 return fmt.Errorf("cannot check HCL keys of type %T", n) 753 } 754 755 validMap := make(map[string]struct{}, len(valid)) 756 for _, v := range valid { 757 validMap[v] = struct{}{} 758 } 759 760 var result error 761 for _, item := range list.Items { 762 key := item.Keys[0].Token.Value().(string) 763 if _, ok := validMap[key]; !ok { 764 result = multierror.Append(result, fmt.Errorf( 765 "invalid key: %s", key)) 766 } 767 } 768 769 return result 770 }