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