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  }