github.com/smithx10/nomad@v0.9.1-rc1/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  	version "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/helper/tlsutil"
    17  	"github.com/hashicorp/nomad/nomad/structs/config"
    18  	"github.com/mitchellh/mapstructure"
    19  )
    20  
    21  // ParseConfigFile parses the given path as a config file.
    22  func ParseConfigFile(path string) (*Config, error) {
    23  	path, err := filepath.Abs(path)
    24  	if err != nil {
    25  		return nil, err
    26  	}
    27  
    28  	f, err := os.Open(path)
    29  	if err != nil {
    30  		return nil, err
    31  	}
    32  	defer f.Close()
    33  
    34  	config, err := ParseConfig(f)
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	return config, nil
    40  }
    41  
    42  // ParseConfig parses the config from the given io.Reader.
    43  //
    44  // Due to current internal limitations, the entire contents of the
    45  // io.Reader will be copied into memory first before parsing.
    46  func ParseConfig(r io.Reader) (*Config, error) {
    47  	// Copy the reader into an in-memory buffer first since HCL requires it.
    48  	var buf bytes.Buffer
    49  	if _, err := io.Copy(&buf, r); err != nil {
    50  		return nil, err
    51  	}
    52  
    53  	// Parse the buffer
    54  	root, err := hcl.Parse(buf.String())
    55  	if err != nil {
    56  		return nil, fmt.Errorf("error parsing: %s", err)
    57  	}
    58  	buf.Reset()
    59  
    60  	// Top-level item should be a list
    61  	list, ok := root.Node.(*ast.ObjectList)
    62  	if !ok {
    63  		return nil, fmt.Errorf("error parsing: root should be an object")
    64  	}
    65  
    66  	var config Config
    67  	if err := parseConfig(&config, list); err != nil {
    68  		return nil, fmt.Errorf("error parsing 'config': %v", err)
    69  	}
    70  
    71  	return &config, nil
    72  }
    73  
    74  func parseConfig(result *Config, list *ast.ObjectList) error {
    75  	// Check for invalid keys
    76  	valid := []string{
    77  		"region",
    78  		"datacenter",
    79  		"name",
    80  		"data_dir",
    81  		"plugin_dir",
    82  		"log_level",
    83  		"log_json",
    84  		"bind_addr",
    85  		"enable_debug",
    86  		"ports",
    87  		"addresses",
    88  		"interfaces",
    89  		"advertise",
    90  		"client",
    91  		"server",
    92  		"telemetry",
    93  		"leave_on_interrupt",
    94  		"leave_on_terminate",
    95  		"enable_syslog",
    96  		"syslog_facility",
    97  		"disable_update_check",
    98  		"disable_anonymous_signature",
    99  		"consul",
   100  		"vault",
   101  		"tls",
   102  		"http_api_response_headers",
   103  		"acl",
   104  		"sentinel",
   105  		"autopilot",
   106  		"plugin",
   107  	}
   108  	if err := helper.CheckHCLKeys(list, valid); err != nil {
   109  		return multierror.Prefix(err, "config:")
   110  	}
   111  
   112  	// Decode the full thing into a map[string]interface for ease
   113  	var m map[string]interface{}
   114  	if err := hcl.DecodeObject(&m, list); err != nil {
   115  		return err
   116  	}
   117  	delete(m, "ports")
   118  	delete(m, "addresses")
   119  	delete(m, "interfaces")
   120  	delete(m, "advertise")
   121  	delete(m, "client")
   122  	delete(m, "server")
   123  	delete(m, "telemetry")
   124  	delete(m, "consul")
   125  	delete(m, "vault")
   126  	delete(m, "tls")
   127  	delete(m, "http_api_response_headers")
   128  	delete(m, "acl")
   129  	delete(m, "sentinel")
   130  	delete(m, "autopilot")
   131  	delete(m, "plugin")
   132  
   133  	// Decode the rest
   134  	if err := mapstructure.WeakDecode(m, result); err != nil {
   135  		return err
   136  	}
   137  
   138  	// Parse ports
   139  	if o := list.Filter("ports"); len(o.Items) > 0 {
   140  		if err := parsePorts(&result.Ports, o); err != nil {
   141  			return multierror.Prefix(err, "ports ->")
   142  		}
   143  	}
   144  
   145  	// Parse addresses
   146  	if o := list.Filter("addresses"); len(o.Items) > 0 {
   147  		if err := parseAddresses(&result.Addresses, o); err != nil {
   148  			return multierror.Prefix(err, "addresses ->")
   149  		}
   150  	}
   151  
   152  	// Parse advertise
   153  	if o := list.Filter("advertise"); len(o.Items) > 0 {
   154  		if err := parseAdvertise(&result.AdvertiseAddrs, o); err != nil {
   155  			return multierror.Prefix(err, "advertise ->")
   156  		}
   157  	}
   158  
   159  	// Parse client config
   160  	if o := list.Filter("client"); len(o.Items) > 0 {
   161  		if err := parseClient(&result.Client, o); err != nil {
   162  			return multierror.Prefix(err, "client ->")
   163  		}
   164  	}
   165  
   166  	// Parse server config
   167  	if o := list.Filter("server"); len(o.Items) > 0 {
   168  		if err := parseServer(&result.Server, o); err != nil {
   169  			return multierror.Prefix(err, "server ->")
   170  		}
   171  	}
   172  
   173  	// Parse ACL config
   174  	if o := list.Filter("acl"); len(o.Items) > 0 {
   175  		if err := parseACL(&result.ACL, o); err != nil {
   176  			return multierror.Prefix(err, "acl ->")
   177  		}
   178  	}
   179  
   180  	// Parse telemetry config
   181  	if o := list.Filter("telemetry"); len(o.Items) > 0 {
   182  		if err := parseTelemetry(&result.Telemetry, o); err != nil {
   183  			return multierror.Prefix(err, "telemetry ->")
   184  		}
   185  	}
   186  
   187  	// Parse the consul config
   188  	if o := list.Filter("consul"); len(o.Items) > 0 {
   189  		if err := parseConsulConfig(&result.Consul, o); err != nil {
   190  			return multierror.Prefix(err, "consul ->")
   191  		}
   192  	}
   193  
   194  	// Parse the vault config
   195  	if o := list.Filter("vault"); len(o.Items) > 0 {
   196  		if err := parseVaultConfig(&result.Vault, o); err != nil {
   197  			return multierror.Prefix(err, "vault ->")
   198  		}
   199  	}
   200  
   201  	// Parse the TLS config
   202  	if o := list.Filter("tls"); len(o.Items) > 0 {
   203  		if err := parseTLSConfig(&result.TLSConfig, o); err != nil {
   204  			return multierror.Prefix(err, "tls ->")
   205  		}
   206  	}
   207  
   208  	// Parse Sentinel config
   209  	if o := list.Filter("sentinel"); len(o.Items) > 0 {
   210  		if err := parseSentinel(&result.Sentinel, o); err != nil {
   211  			return multierror.Prefix(err, "sentinel->")
   212  		}
   213  	}
   214  
   215  	// Parse Autopilot config
   216  	if o := list.Filter("autopilot"); len(o.Items) > 0 {
   217  		if err := parseAutopilot(&result.Autopilot, o); err != nil {
   218  			return multierror.Prefix(err, "autopilot->")
   219  		}
   220  	}
   221  
   222  	// Parse Plugin configs
   223  	if o := list.Filter("plugin"); len(o.Items) > 0 {
   224  		if err := parsePlugins(&result.Plugins, o); err != nil {
   225  			return multierror.Prefix(err, "plugin->")
   226  		}
   227  	}
   228  
   229  	// Parse out http_api_response_headers fields. These are in HCL as a list so
   230  	// we need to iterate over them and merge them.
   231  	if headersO := list.Filter("http_api_response_headers"); len(headersO.Items) > 0 {
   232  		for _, o := range headersO.Elem().Items {
   233  			var m map[string]interface{}
   234  			if err := hcl.DecodeObject(&m, o.Val); err != nil {
   235  				return err
   236  			}
   237  			if err := mapstructure.WeakDecode(m, &result.HTTPAPIResponseHeaders); err != nil {
   238  				return err
   239  			}
   240  		}
   241  	}
   242  
   243  	return nil
   244  }
   245  
   246  func parsePorts(result **Ports, list *ast.ObjectList) error {
   247  	list = list.Elem()
   248  	if len(list.Items) > 1 {
   249  		return fmt.Errorf("only one 'ports' block allowed")
   250  	}
   251  
   252  	// Get our ports 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 := helper.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 ports Ports
   271  	if err := mapstructure.WeakDecode(m, &ports); err != nil {
   272  		return err
   273  	}
   274  	*result = &ports
   275  	return nil
   276  }
   277  
   278  func parseAddresses(result **Addresses, list *ast.ObjectList) error {
   279  	list = list.Elem()
   280  	if len(list.Items) > 1 {
   281  		return fmt.Errorf("only one 'addresses' block allowed")
   282  	}
   283  
   284  	// Get our addresses 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 := helper.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 addresses Addresses
   303  	if err := mapstructure.WeakDecode(m, &addresses); err != nil {
   304  		return err
   305  	}
   306  	*result = &addresses
   307  	return nil
   308  }
   309  
   310  func parseAdvertise(result **AdvertiseAddrs, list *ast.ObjectList) error {
   311  	list = list.Elem()
   312  	if len(list.Items) > 1 {
   313  		return fmt.Errorf("only one 'advertise' block allowed")
   314  	}
   315  
   316  	// Get our advertise object
   317  	listVal := list.Items[0].Val
   318  
   319  	// Check for invalid keys
   320  	valid := []string{
   321  		"http",
   322  		"rpc",
   323  		"serf",
   324  	}
   325  	if err := helper.CheckHCLKeys(listVal, valid); err != nil {
   326  		return err
   327  	}
   328  
   329  	var m map[string]interface{}
   330  	if err := hcl.DecodeObject(&m, listVal); err != nil {
   331  		return err
   332  	}
   333  
   334  	var advertise AdvertiseAddrs
   335  	if err := mapstructure.WeakDecode(m, &advertise); err != nil {
   336  		return err
   337  	}
   338  	*result = &advertise
   339  	return nil
   340  }
   341  
   342  func parseClient(result **ClientConfig, list *ast.ObjectList) error {
   343  	list = list.Elem()
   344  	if len(list.Items) > 1 {
   345  		return fmt.Errorf("only one 'client' block allowed")
   346  	}
   347  
   348  	// Get our client object
   349  	obj := list.Items[0]
   350  
   351  	// Value should be an object
   352  	var listVal *ast.ObjectList
   353  	if ot, ok := obj.Val.(*ast.ObjectType); ok {
   354  		listVal = ot.List
   355  	} else {
   356  		return fmt.Errorf("client value: should be an object")
   357  	}
   358  
   359  	// Check for invalid keys
   360  	valid := []string{
   361  		"enabled",
   362  		"state_dir",
   363  		"alloc_dir",
   364  		"servers",
   365  		"node_class",
   366  		"options",
   367  		"meta",
   368  		"chroot_env",
   369  		"network_interface",
   370  		"network_speed",
   371  		"memory_total_mb",
   372  		"cpu_total_compute",
   373  		"max_kill_timeout",
   374  		"client_max_port",
   375  		"client_min_port",
   376  		"reserved",
   377  		"stats",
   378  		"gc_interval",
   379  		"gc_disk_usage_threshold",
   380  		"gc_inode_usage_threshold",
   381  		"gc_parallel_destroys",
   382  		"gc_max_allocs",
   383  		"no_host_uuid",
   384  		"server_join",
   385  	}
   386  	if err := helper.CheckHCLKeys(listVal, valid); err != nil {
   387  		return err
   388  	}
   389  
   390  	var m map[string]interface{}
   391  	if err := hcl.DecodeObject(&m, listVal); err != nil {
   392  		return err
   393  	}
   394  
   395  	delete(m, "options")
   396  	delete(m, "meta")
   397  	delete(m, "chroot_env")
   398  	delete(m, "reserved")
   399  	delete(m, "stats")
   400  	delete(m, "server_join")
   401  
   402  	var config ClientConfig
   403  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   404  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   405  		WeaklyTypedInput: true,
   406  		Result:           &config,
   407  	})
   408  	if err != nil {
   409  		return err
   410  	}
   411  	if err := dec.Decode(m); err != nil {
   412  		return err
   413  	}
   414  
   415  	// Parse out options fields. These are in HCL as a list so we need to
   416  	// iterate over them and merge them.
   417  	if optionsO := listVal.Filter("options"); len(optionsO.Items) > 0 {
   418  		for _, o := range optionsO.Elem().Items {
   419  			var m map[string]interface{}
   420  			if err := hcl.DecodeObject(&m, o.Val); err != nil {
   421  				return err
   422  			}
   423  			if err := mapstructure.WeakDecode(m, &config.Options); err != nil {
   424  				return err
   425  			}
   426  		}
   427  	}
   428  
   429  	// Parse out options meta. These are in HCL as a list so we need to
   430  	// iterate over them and merge them.
   431  	if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 {
   432  		for _, o := range metaO.Elem().Items {
   433  			var m map[string]interface{}
   434  			if err := hcl.DecodeObject(&m, o.Val); err != nil {
   435  				return err
   436  			}
   437  			if err := mapstructure.WeakDecode(m, &config.Meta); err != nil {
   438  				return err
   439  			}
   440  		}
   441  	}
   442  
   443  	// Parse out chroot_env fields. These are in HCL as a list so we need to
   444  	// iterate over them and merge them.
   445  	if chrootEnvO := listVal.Filter("chroot_env"); len(chrootEnvO.Items) > 0 {
   446  		for _, o := range chrootEnvO.Elem().Items {
   447  			var m map[string]interface{}
   448  			if err := hcl.DecodeObject(&m, o.Val); err != nil {
   449  				return err
   450  			}
   451  			if err := mapstructure.WeakDecode(m, &config.ChrootEnv); err != nil {
   452  				return err
   453  			}
   454  		}
   455  	}
   456  
   457  	// Parse reserved config
   458  	if o := listVal.Filter("reserved"); len(o.Items) > 0 {
   459  		if err := parseReserved(&config.Reserved, o); err != nil {
   460  			return multierror.Prefix(err, "reserved ->")
   461  		}
   462  	}
   463  
   464  	// Parse ServerJoin config
   465  	if o := listVal.Filter("server_join"); len(o.Items) > 0 {
   466  		if err := parseServerJoin(&config.ServerJoin, o); err != nil {
   467  			return multierror.Prefix(err, "server_join->")
   468  		}
   469  	}
   470  
   471  	*result = &config
   472  	return nil
   473  }
   474  
   475  func parseReserved(result **Resources, list *ast.ObjectList) error {
   476  	list = list.Elem()
   477  	if len(list.Items) > 1 {
   478  		return fmt.Errorf("only one 'reserved' block allowed")
   479  	}
   480  
   481  	// Get our reserved object
   482  	obj := list.Items[0]
   483  
   484  	// Value should be an object
   485  	var listVal *ast.ObjectList
   486  	if ot, ok := obj.Val.(*ast.ObjectType); ok {
   487  		listVal = ot.List
   488  	} else {
   489  		return fmt.Errorf("client value: should be an object")
   490  	}
   491  
   492  	// Check for invalid keys
   493  	valid := []string{
   494  		"cpu",
   495  		"memory",
   496  		"disk",
   497  		"reserved_ports",
   498  	}
   499  	if err := helper.CheckHCLKeys(listVal, valid); err != nil {
   500  		return err
   501  	}
   502  
   503  	var m map[string]interface{}
   504  	if err := hcl.DecodeObject(&m, listVal); err != nil {
   505  		return err
   506  	}
   507  
   508  	var reserved Resources
   509  	if err := mapstructure.WeakDecode(m, &reserved); err != nil {
   510  		return err
   511  	}
   512  	if err := reserved.CanParseReserved(); err != nil {
   513  		return err
   514  	}
   515  
   516  	*result = &reserved
   517  	return nil
   518  }
   519  
   520  func parseServer(result **ServerConfig, list *ast.ObjectList) error {
   521  	list = list.Elem()
   522  	if len(list.Items) > 1 {
   523  		return fmt.Errorf("only one 'server' block allowed")
   524  	}
   525  
   526  	// Get our server object
   527  	obj := list.Items[0]
   528  
   529  	// Value should be an object
   530  	var listVal *ast.ObjectList
   531  	if ot, ok := obj.Val.(*ast.ObjectType); ok {
   532  		listVal = ot.List
   533  	} else {
   534  		return fmt.Errorf("client value: should be an object")
   535  	}
   536  
   537  	// Check for invalid keys
   538  	valid := []string{
   539  		"enabled",
   540  		"bootstrap_expect",
   541  		"data_dir",
   542  		"protocol_version",
   543  		"raft_protocol",
   544  		"num_schedulers",
   545  		"enabled_schedulers",
   546  		"node_gc_threshold",
   547  		"eval_gc_threshold",
   548  		"job_gc_threshold",
   549  		"deployment_gc_threshold",
   550  		"heartbeat_grace",
   551  		"min_heartbeat_ttl",
   552  		"max_heartbeats_per_second",
   553  		"rejoin_after_leave",
   554  		"encrypt",
   555  		"authoritative_region",
   556  		"non_voting_server",
   557  		"redundancy_zone",
   558  		"upgrade_version",
   559  
   560  		"server_join",
   561  
   562  		// For backwards compatibility
   563  		"start_join",
   564  		"retry_join",
   565  		"retry_max",
   566  		"retry_interval",
   567  	}
   568  	if err := helper.CheckHCLKeys(listVal, valid); err != nil {
   569  		return err
   570  	}
   571  
   572  	var m map[string]interface{}
   573  	if err := hcl.DecodeObject(&m, listVal); err != nil {
   574  		return err
   575  	}
   576  
   577  	delete(m, "server_join")
   578  
   579  	var config ServerConfig
   580  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   581  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   582  		WeaklyTypedInput: true,
   583  		Result:           &config,
   584  	})
   585  	if err != nil {
   586  		return err
   587  	}
   588  	if err := dec.Decode(m); err != nil {
   589  		return err
   590  	}
   591  
   592  	if config.UpgradeVersion != "" {
   593  		if _, err := version.NewVersion(config.UpgradeVersion); err != nil {
   594  			return fmt.Errorf("error parsing upgrade_version: %v", err)
   595  		}
   596  	}
   597  
   598  	// Parse ServerJoin config
   599  	if o := listVal.Filter("server_join"); len(o.Items) > 0 {
   600  		if err := parseServerJoin(&config.ServerJoin, o); err != nil {
   601  			return multierror.Prefix(err, "server_join->")
   602  		}
   603  	}
   604  
   605  	*result = &config
   606  	return nil
   607  }
   608  
   609  func parseServerJoin(result **ServerJoin, list *ast.ObjectList) error {
   610  	list = list.Elem()
   611  	if len(list.Items) > 1 {
   612  		return fmt.Errorf("only one 'server_join' block allowed")
   613  	}
   614  
   615  	// Get our object
   616  	listVal := list.Items[0].Val
   617  
   618  	// Check for invalid keys
   619  	valid := []string{
   620  		"start_join",
   621  		"retry_join",
   622  		"retry_max",
   623  		"retry_interval",
   624  	}
   625  	if err := helper.CheckHCLKeys(listVal, valid); err != nil {
   626  		return err
   627  	}
   628  
   629  	var m map[string]interface{}
   630  	if err := hcl.DecodeObject(&m, listVal); err != nil {
   631  		return err
   632  	}
   633  
   634  	var serverJoinInfo ServerJoin
   635  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   636  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   637  		WeaklyTypedInput: true,
   638  		Result:           &serverJoinInfo,
   639  	})
   640  	if err != nil {
   641  		return err
   642  	}
   643  	if err := dec.Decode(m); err != nil {
   644  		return err
   645  	}
   646  
   647  	*result = &serverJoinInfo
   648  	return nil
   649  }
   650  
   651  func parseACL(result **ACLConfig, list *ast.ObjectList) error {
   652  	list = list.Elem()
   653  	if len(list.Items) > 1 {
   654  		return fmt.Errorf("only one 'acl' block allowed")
   655  	}
   656  
   657  	// Get our server object
   658  	obj := list.Items[0]
   659  
   660  	// Value should be an object
   661  	var listVal *ast.ObjectList
   662  	if ot, ok := obj.Val.(*ast.ObjectType); ok {
   663  		listVal = ot.List
   664  	} else {
   665  		return fmt.Errorf("acl value: should be an object")
   666  	}
   667  
   668  	// Check for invalid keys
   669  	valid := []string{
   670  		"enabled",
   671  		"token_ttl",
   672  		"policy_ttl",
   673  		"replication_token",
   674  	}
   675  	if err := helper.CheckHCLKeys(listVal, valid); err != nil {
   676  		return err
   677  	}
   678  
   679  	var m map[string]interface{}
   680  	if err := hcl.DecodeObject(&m, listVal); err != nil {
   681  		return err
   682  	}
   683  
   684  	var config ACLConfig
   685  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   686  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   687  		WeaklyTypedInput: true,
   688  		Result:           &config,
   689  	})
   690  	if err != nil {
   691  		return err
   692  	}
   693  	if err := dec.Decode(m); err != nil {
   694  		return err
   695  	}
   696  
   697  	*result = &config
   698  	return nil
   699  }
   700  
   701  func parseTelemetry(result **Telemetry, list *ast.ObjectList) error {
   702  	list = list.Elem()
   703  	if len(list.Items) > 1 {
   704  		return fmt.Errorf("only one 'telemetry' block allowed")
   705  	}
   706  
   707  	// Get our telemetry object
   708  	listVal := list.Items[0].Val
   709  
   710  	// Check for invalid keys
   711  	valid := []string{
   712  		"statsite_address",
   713  		"statsd_address",
   714  		"disable_hostname",
   715  		"use_node_name",
   716  		"collection_interval",
   717  		"publish_allocation_metrics",
   718  		"publish_node_metrics",
   719  		"datadog_address",
   720  		"datadog_tags",
   721  		"prometheus_metrics",
   722  		"circonus_api_token",
   723  		"circonus_api_app",
   724  		"circonus_api_url",
   725  		"circonus_submission_interval",
   726  		"circonus_submission_url",
   727  		"circonus_check_id",
   728  		"circonus_check_force_metric_activation",
   729  		"circonus_check_instance_id",
   730  		"circonus_check_search_tag",
   731  		"circonus_check_display_name",
   732  		"circonus_check_tags",
   733  		"circonus_broker_id",
   734  		"circonus_broker_select_tag",
   735  		"disable_tagged_metrics",
   736  		"backwards_compatible_metrics",
   737  		"prefix_filter",
   738  		"filter_default",
   739  		"disable_dispatched_job_summary_metrics",
   740  	}
   741  	if err := helper.CheckHCLKeys(listVal, valid); err != nil {
   742  		return err
   743  	}
   744  
   745  	var m map[string]interface{}
   746  	if err := hcl.DecodeObject(&m, listVal); err != nil {
   747  		return err
   748  	}
   749  
   750  	var telemetry Telemetry
   751  	if err := mapstructure.WeakDecode(m, &telemetry); err != nil {
   752  		return err
   753  	}
   754  	if telemetry.CollectionInterval != "" {
   755  		if dur, err := time.ParseDuration(telemetry.CollectionInterval); err != nil {
   756  			return fmt.Errorf("error parsing value of %q: %v", "collection_interval", err)
   757  		} else {
   758  			telemetry.collectionInterval = dur
   759  		}
   760  	}
   761  	*result = &telemetry
   762  	return nil
   763  }
   764  
   765  func parseConsulConfig(result **config.ConsulConfig, list *ast.ObjectList) error {
   766  	list = list.Elem()
   767  	if len(list.Items) > 1 {
   768  		return fmt.Errorf("only one 'consul' block allowed")
   769  	}
   770  
   771  	// Get our Consul object
   772  	listVal := list.Items[0].Val
   773  
   774  	// Check for invalid keys
   775  	valid := []string{
   776  		"address",
   777  		"auth",
   778  		"auto_advertise",
   779  		"ca_file",
   780  		"cert_file",
   781  		"checks_use_advertise",
   782  		"client_auto_join",
   783  		"client_service_name",
   784  		"client_http_check_name",
   785  		"key_file",
   786  		"server_auto_join",
   787  		"server_service_name",
   788  		"server_http_check_name",
   789  		"server_serf_check_name",
   790  		"server_rpc_check_name",
   791  		"ssl",
   792  		"timeout",
   793  		"token",
   794  		"verify_ssl",
   795  	}
   796  
   797  	if err := helper.CheckHCLKeys(listVal, valid); err != nil {
   798  		return err
   799  	}
   800  
   801  	var m map[string]interface{}
   802  	if err := hcl.DecodeObject(&m, listVal); err != nil {
   803  		return err
   804  	}
   805  
   806  	consulConfig := config.DefaultConsulConfig()
   807  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   808  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   809  		WeaklyTypedInput: true,
   810  		Result:           &consulConfig,
   811  	})
   812  	if err != nil {
   813  		return err
   814  	}
   815  	if err := dec.Decode(m); err != nil {
   816  		return err
   817  	}
   818  
   819  	*result = consulConfig
   820  	return nil
   821  }
   822  
   823  func parseTLSConfig(result **config.TLSConfig, list *ast.ObjectList) error {
   824  	list = list.Elem()
   825  	if len(list.Items) > 1 {
   826  		return fmt.Errorf("only one 'tls' block allowed")
   827  	}
   828  
   829  	// Get the TLS object
   830  	listVal := list.Items[0].Val
   831  
   832  	valid := []string{
   833  		"http",
   834  		"rpc",
   835  		"verify_server_hostname",
   836  		"rpc_upgrade_mode",
   837  		"ca_file",
   838  		"cert_file",
   839  		"key_file",
   840  		"verify_https_client",
   841  		"tls_cipher_suites",
   842  		"tls_min_version",
   843  		"tls_prefer_server_cipher_suites",
   844  	}
   845  
   846  	if err := helper.CheckHCLKeys(listVal, valid); err != nil {
   847  		return err
   848  	}
   849  
   850  	var m map[string]interface{}
   851  	if err := hcl.DecodeObject(&m, listVal); err != nil {
   852  		return err
   853  	}
   854  
   855  	var tlsConfig config.TLSConfig
   856  	if err := mapstructure.WeakDecode(m, &tlsConfig); err != nil {
   857  		return err
   858  	}
   859  
   860  	if _, err := tlsutil.ParseCiphers(&tlsConfig); err != nil {
   861  		return err
   862  	}
   863  
   864  	if _, err := tlsutil.ParseMinVersion(tlsConfig.TLSMinVersion); err != nil {
   865  		return err
   866  	}
   867  
   868  	*result = &tlsConfig
   869  	return nil
   870  }
   871  
   872  func parseVaultConfig(result **config.VaultConfig, list *ast.ObjectList) error {
   873  	list = list.Elem()
   874  	if len(list.Items) > 1 {
   875  		return fmt.Errorf("only one 'vault' block allowed")
   876  	}
   877  
   878  	// Get our Vault object
   879  	listVal := list.Items[0].Val
   880  
   881  	// Check for invalid keys
   882  	valid := []string{
   883  		"address",
   884  		"allow_unauthenticated",
   885  		"enabled",
   886  		"task_token_ttl",
   887  		"ca_file",
   888  		"ca_path",
   889  		"cert_file",
   890  		"create_from_role",
   891  		"key_file",
   892  		"tls_server_name",
   893  		"tls_skip_verify",
   894  		"token",
   895  	}
   896  
   897  	if err := helper.CheckHCLKeys(listVal, valid); err != nil {
   898  		return err
   899  	}
   900  
   901  	var m map[string]interface{}
   902  	if err := hcl.DecodeObject(&m, listVal); err != nil {
   903  		return err
   904  	}
   905  
   906  	vaultConfig := config.DefaultVaultConfig()
   907  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   908  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   909  		WeaklyTypedInput: true,
   910  		Result:           &vaultConfig,
   911  	})
   912  	if err != nil {
   913  		return err
   914  	}
   915  	if err := dec.Decode(m); err != nil {
   916  		return err
   917  	}
   918  
   919  	*result = vaultConfig
   920  	return nil
   921  }
   922  
   923  func parseSentinel(result **config.SentinelConfig, list *ast.ObjectList) error {
   924  	list = list.Elem()
   925  	if len(list.Items) > 1 {
   926  		return fmt.Errorf("only one 'sentinel' block allowed")
   927  	}
   928  
   929  	// Get our sentinel object
   930  	obj := list.Items[0]
   931  
   932  	// Value should be an object
   933  	var listVal *ast.ObjectList
   934  	if ot, ok := obj.Val.(*ast.ObjectType); ok {
   935  		listVal = ot.List
   936  	} else {
   937  		return fmt.Errorf("sentinel value: should be an object")
   938  	}
   939  
   940  	// Check for invalid keys
   941  	valid := []string{
   942  		"import",
   943  	}
   944  	if err := helper.CheckHCLKeys(listVal, valid); err != nil {
   945  		return err
   946  	}
   947  
   948  	var config config.SentinelConfig
   949  	if err := hcl.DecodeObject(&config, listVal); err != nil {
   950  		return err
   951  	}
   952  
   953  	*result = &config
   954  	return nil
   955  }
   956  
   957  func parseAutopilot(result **config.AutopilotConfig, list *ast.ObjectList) error {
   958  	list = list.Elem()
   959  	if len(list.Items) > 1 {
   960  		return fmt.Errorf("only one 'autopilot' block allowed")
   961  	}
   962  
   963  	// Get our Autopilot object
   964  	listVal := list.Items[0].Val
   965  
   966  	// Check for invalid keys
   967  	valid := []string{
   968  		"cleanup_dead_servers",
   969  		"server_stabilization_time",
   970  		"last_contact_threshold",
   971  		"max_trailing_logs",
   972  		"enable_redundancy_zones",
   973  		"disable_upgrade_migration",
   974  		"enable_custom_upgrades",
   975  	}
   976  
   977  	if err := helper.CheckHCLKeys(listVal, valid); err != nil {
   978  		return err
   979  	}
   980  
   981  	var m map[string]interface{}
   982  	if err := hcl.DecodeObject(&m, listVal); err != nil {
   983  		return err
   984  	}
   985  
   986  	autopilotConfig := config.DefaultAutopilotConfig()
   987  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   988  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   989  		WeaklyTypedInput: true,
   990  		Result:           &autopilotConfig,
   991  	})
   992  	if err != nil {
   993  		return err
   994  	}
   995  	if err := dec.Decode(m); err != nil {
   996  		return err
   997  	}
   998  
   999  	*result = autopilotConfig
  1000  	return nil
  1001  }
  1002  
  1003  func parsePlugins(result *[]*config.PluginConfig, list *ast.ObjectList) error {
  1004  	listLen := len(list.Items)
  1005  	plugins := make([]*config.PluginConfig, listLen)
  1006  
  1007  	// Check for invalid keys
  1008  	valid := []string{
  1009  		"args",
  1010  		"config",
  1011  	}
  1012  
  1013  	for i := 0; i < listLen; i++ {
  1014  		// Get the current plugin object
  1015  		listVal := list.Items[i]
  1016  
  1017  		// Deal with json->hcl AST parsing incorrectness when directly nested
  1018  		// items show up as additional keys.
  1019  		// TODO(preetha): Add additional tests and fix other places that have the same issue
  1020  		unwrapLegacyHCLObjectKeysFromJSON(listVal, 1)
  1021  		if err := helper.CheckHCLKeys(listVal.Val, valid); err != nil {
  1022  			return fmt.Errorf("invalid keys in plugin config %d: %v", i+1, err)
  1023  		}
  1024  
  1025  		// Ensure there is a key
  1026  		if len(listVal.Keys) != 1 {
  1027  			return fmt.Errorf("plugin config %d doesn't incude a name key", i+1)
  1028  		}
  1029  
  1030  		var plugin config.PluginConfig
  1031  		if err := hcl.DecodeObject(&plugin, listVal); err != nil {
  1032  			return fmt.Errorf("error decoding plugin config %d: %v", i+1, err)
  1033  		}
  1034  
  1035  		plugins[i] = &plugin
  1036  	}
  1037  
  1038  	*result = plugins
  1039  	return nil
  1040  }
  1041  
  1042  // unwrapLegacyHCLObjectKeysFromJSON cleans up an edge case that can occur when
  1043  // parsing JSON as input: if we're parsing JSON then directly nested
  1044  // items will show up as additional "keys".
  1045  //
  1046  // For objects that expect a fixed number of keys, this breaks the
  1047  // decoding process. This function unwraps the object into what it would've
  1048  // looked like if it came directly from HCL by specifying the number of keys
  1049  // you expect.
  1050  //
  1051  // Example:
  1052  //
  1053  // { "foo": { "baz": {} } }
  1054  //
  1055  // Will show up with Keys being: []string{"foo", "baz"}
  1056  // when we really just want the first two. This function will fix this.
  1057  func unwrapLegacyHCLObjectKeysFromJSON(item *ast.ObjectItem, depth int) {
  1058  	if len(item.Keys) > depth && item.Keys[0].Token.JSON {
  1059  		for len(item.Keys) > depth {
  1060  			// Pop off the last key
  1061  			n := len(item.Keys)
  1062  			key := item.Keys[n-1]
  1063  			item.Keys[n-1] = nil
  1064  			item.Keys = item.Keys[:n-1]
  1065  
  1066  			// Wrap our value in a list
  1067  			item.Val = &ast.ObjectType{
  1068  				List: &ast.ObjectList{
  1069  					Items: []*ast.ObjectItem{
  1070  						{
  1071  							Keys: []*ast.ObjectKey{key},
  1072  							Val:  item.Val,
  1073  						},
  1074  					},
  1075  				},
  1076  			}
  1077  		}
  1078  	}
  1079  }