github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/jobspec/parse_service.go (about)

     1  package jobspec
     2  
     3  import (
     4  	"fmt"
     5  
     6  	multierror "github.com/hashicorp/go-multierror"
     7  	"github.com/hashicorp/hcl"
     8  	"github.com/hashicorp/hcl/hcl/ast"
     9  	"github.com/hashicorp/nomad/api"
    10  	"github.com/mitchellh/mapstructure"
    11  )
    12  
    13  func parseGroupServices(g *api.TaskGroup, serviceObjs *ast.ObjectList) error {
    14  	g.Services = make([]*api.Service, len(serviceObjs.Items))
    15  	for idx, o := range serviceObjs.Items {
    16  		service, err := parseService(o)
    17  		if err != nil {
    18  			return multierror.Prefix(err, fmt.Sprintf("service (%d):", idx))
    19  		}
    20  		g.Services[idx] = service
    21  	}
    22  
    23  	return nil
    24  }
    25  
    26  func parseServices(serviceObjs *ast.ObjectList) ([]*api.Service, error) {
    27  	services := make([]*api.Service, len(serviceObjs.Items))
    28  	for idx, o := range serviceObjs.Items {
    29  		service, err := parseService(o)
    30  		if err != nil {
    31  			return nil, multierror.Prefix(err, fmt.Sprintf("service (%d):", idx))
    32  		}
    33  		services[idx] = service
    34  	}
    35  	return services, nil
    36  }
    37  
    38  func parseService(o *ast.ObjectItem) (*api.Service, error) {
    39  	// Check for invalid keys
    40  	valid := []string{
    41  		"name",
    42  		"tags",
    43  		"canary_tags",
    44  		"enable_tag_override",
    45  		"port",
    46  		"check",
    47  		"address_mode",
    48  		"check_restart",
    49  		"connect",
    50  		"task",
    51  		"meta",
    52  		"canary_meta",
    53  	}
    54  	if err := checkHCLKeys(o.Val, valid); err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	var service api.Service
    59  	var m map[string]interface{}
    60  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
    61  		return nil, err
    62  	}
    63  
    64  	delete(m, "check")
    65  	delete(m, "check_restart")
    66  	delete(m, "connect")
    67  	delete(m, "meta")
    68  	delete(m, "canary_meta")
    69  
    70  	if err := mapstructure.WeakDecode(m, &service); err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	// Filter list
    75  	var listVal *ast.ObjectList
    76  	if ot, ok := o.Val.(*ast.ObjectType); ok {
    77  		listVal = ot.List
    78  	} else {
    79  		return nil, fmt.Errorf("'%s': should be an object", service.Name)
    80  	}
    81  
    82  	if co := listVal.Filter("check"); len(co.Items) > 0 {
    83  		if err := parseChecks(&service, co); err != nil {
    84  			return nil, multierror.Prefix(err, fmt.Sprintf("'%s',", service.Name))
    85  		}
    86  	}
    87  
    88  	// Filter check_restart
    89  	if cro := listVal.Filter("check_restart"); len(cro.Items) > 0 {
    90  		if len(cro.Items) > 1 {
    91  			return nil, fmt.Errorf("check_restart '%s': cannot have more than 1 check_restart", service.Name)
    92  		}
    93  		cr, err := parseCheckRestart(cro.Items[0])
    94  		if err != nil {
    95  			return nil, multierror.Prefix(err, fmt.Sprintf("'%s',", service.Name))
    96  		}
    97  		service.CheckRestart = cr
    98  
    99  	}
   100  
   101  	// Filter connect
   102  	if co := listVal.Filter("connect"); len(co.Items) > 0 {
   103  		if len(co.Items) > 1 {
   104  			return nil, fmt.Errorf("connect '%s': cannot have more than 1 connect stanza", service.Name)
   105  		}
   106  
   107  		c, err := parseConnect(co.Items[0])
   108  		if err != nil {
   109  			return nil, multierror.Prefix(err, fmt.Sprintf("'%s',", service.Name))
   110  		}
   111  
   112  		service.Connect = c
   113  	}
   114  
   115  	// Parse out meta fields. These are in HCL as a list so we need
   116  	// to iterate over them and merge them.
   117  	if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 {
   118  		for _, o := range metaO.Elem().Items {
   119  			var m map[string]interface{}
   120  			if err := hcl.DecodeObject(&m, o.Val); err != nil {
   121  				return nil, err
   122  			}
   123  			if err := mapstructure.WeakDecode(m, &service.Meta); err != nil {
   124  				return nil, err
   125  			}
   126  		}
   127  	}
   128  
   129  	// Parse out canary_meta fields. These are in HCL as a list so we need
   130  	// to iterate over them and merge them.
   131  	if metaO := listVal.Filter("canary_meta"); len(metaO.Items) > 0 {
   132  		for _, o := range metaO.Elem().Items {
   133  			var m map[string]interface{}
   134  			if err := hcl.DecodeObject(&m, o.Val); err != nil {
   135  				return nil, err
   136  			}
   137  			if err := mapstructure.WeakDecode(m, &service.CanaryMeta); err != nil {
   138  				return nil, err
   139  			}
   140  		}
   141  	}
   142  
   143  	return &service, nil
   144  }
   145  
   146  func parseConnect(co *ast.ObjectItem) (*api.ConsulConnect, error) {
   147  	valid := []string{
   148  		"native",
   149  		"gateway",
   150  		"sidecar_service",
   151  		"sidecar_task",
   152  	}
   153  
   154  	if err := checkHCLKeys(co.Val, valid); err != nil {
   155  		return nil, multierror.Prefix(err, "connect ->")
   156  	}
   157  
   158  	var connect api.ConsulConnect
   159  	var m map[string]interface{}
   160  	if err := hcl.DecodeObject(&m, co.Val); err != nil {
   161  		return nil, err
   162  	}
   163  
   164  	delete(m, "gateway")
   165  	delete(m, "sidecar_service")
   166  	delete(m, "sidecar_task")
   167  
   168  	if err := mapstructure.WeakDecode(m, &connect); err != nil {
   169  		return nil, err
   170  	}
   171  
   172  	var connectList *ast.ObjectList
   173  	if ot, ok := co.Val.(*ast.ObjectType); ok {
   174  		connectList = ot.List
   175  	} else {
   176  		return nil, fmt.Errorf("connect should be an object")
   177  	}
   178  
   179  	// Parse the gateway
   180  	o := connectList.Filter("gateway")
   181  	if len(o.Items) > 1 {
   182  		return nil, fmt.Errorf("only one 'gateway' block allowed per task")
   183  	} else if len(o.Items) == 1 {
   184  		g, err := parseGateway(o.Items[0])
   185  		if err != nil {
   186  			return nil, fmt.Errorf("gateway, %v", err)
   187  		}
   188  		connect.Gateway = g
   189  	}
   190  
   191  	// Parse the sidecar_service
   192  	o = connectList.Filter("sidecar_service")
   193  	if len(o.Items) == 0 {
   194  		return &connect, nil
   195  	}
   196  	if len(o.Items) > 1 {
   197  		return nil, fmt.Errorf("only one 'sidecar_service' block allowed per task")
   198  	}
   199  
   200  	r, err := parseSidecarService(o.Items[0])
   201  	if err != nil {
   202  		return nil, fmt.Errorf("sidecar_service, %v", err)
   203  	}
   204  	connect.SidecarService = r
   205  
   206  	// Parse the sidecar_task
   207  	o = connectList.Filter("sidecar_task")
   208  	if len(o.Items) == 0 {
   209  		return &connect, nil
   210  	}
   211  	if len(o.Items) > 1 {
   212  		return nil, fmt.Errorf("only one 'sidecar_task' block allowed per task")
   213  	}
   214  
   215  	t, err := parseSidecarTask(o.Items[0])
   216  	if err != nil {
   217  		return nil, fmt.Errorf("sidecar_task, %v", err)
   218  	}
   219  	connect.SidecarTask = t
   220  
   221  	return &connect, nil
   222  }
   223  
   224  func parseGateway(o *ast.ObjectItem) (*api.ConsulGateway, error) {
   225  	valid := []string{
   226  		"proxy",
   227  		"ingress",
   228  	}
   229  
   230  	if err := checkHCLKeys(o.Val, valid); err != nil {
   231  		return nil, multierror.Prefix(err, "gateway ->")
   232  	}
   233  
   234  	var gateway api.ConsulGateway
   235  	var m map[string]interface{}
   236  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
   237  		return nil, err
   238  	}
   239  
   240  	delete(m, "proxy")
   241  	delete(m, "ingress")
   242  
   243  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   244  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   245  		WeaklyTypedInput: true,
   246  		Result:           &gateway,
   247  	})
   248  	if err != nil {
   249  		return nil, err
   250  	}
   251  	if err := dec.Decode(m); err != nil {
   252  		return nil, fmt.Errorf("gateway: %v", err)
   253  	}
   254  
   255  	// list of parameters
   256  	var listVal *ast.ObjectList
   257  	if ot, ok := o.Val.(*ast.ObjectType); ok {
   258  		listVal = ot.List
   259  	} else {
   260  		return nil, fmt.Errorf("proxy: should be an object")
   261  	}
   262  
   263  	// extract and parse the proxy block
   264  	po := listVal.Filter("proxy")
   265  	if len(po.Items) != 1 {
   266  		return nil, fmt.Errorf("must have one 'proxy' block")
   267  	}
   268  	proxy, err := parseGatewayProxy(po.Items[0])
   269  	if err != nil {
   270  		return nil, fmt.Errorf("proxy, %v", err)
   271  	}
   272  	gateway.Proxy = proxy
   273  
   274  	// extract and parse the ingress block
   275  	io := listVal.Filter("ingress")
   276  	if len(io.Items) != 1 {
   277  		// in the future, may be terminating or mesh block instead
   278  		return nil, fmt.Errorf("must have one 'ingress' block")
   279  	}
   280  	ingress, err := parseIngressConfigEntry(io.Items[0])
   281  	if err != nil {
   282  		return nil, fmt.Errorf("ingress, %v", err)
   283  	}
   284  	gateway.Ingress = ingress
   285  
   286  	return &gateway, nil
   287  }
   288  
   289  // parseGatewayProxy parses envoy gateway proxy options supported by Consul.
   290  //
   291  // consul.io/docs/connect/proxies/envoy#gateway-options
   292  func parseGatewayProxy(o *ast.ObjectItem) (*api.ConsulGatewayProxy, error) {
   293  	valid := []string{
   294  		"connect_timeout",
   295  		"envoy_gateway_bind_tagged_addresses",
   296  		"envoy_gateway_bind_addresses",
   297  		"envoy_gateway_no_default_bind",
   298  		"envoy_dns_discovery_type",
   299  		"config",
   300  	}
   301  
   302  	if err := checkHCLKeys(o.Val, valid); err != nil {
   303  		return nil, multierror.Prefix(err, "proxy ->")
   304  	}
   305  
   306  	var proxy api.ConsulGatewayProxy
   307  	var m map[string]interface{}
   308  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
   309  		return nil, err
   310  	}
   311  
   312  	delete(m, "config")
   313  	delete(m, "envoy_gateway_bind_addresses")
   314  
   315  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   316  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   317  		WeaklyTypedInput: true,
   318  		Result:           &proxy,
   319  	})
   320  	if err != nil {
   321  		return nil, err
   322  	}
   323  	if err := dec.Decode(m); err != nil {
   324  		return nil, fmt.Errorf("proxy: %v", err)
   325  	}
   326  
   327  	var listVal *ast.ObjectList
   328  	if ot, ok := o.Val.(*ast.ObjectType); ok {
   329  		listVal = ot.List
   330  	} else {
   331  		return nil, fmt.Errorf("proxy: should be an object")
   332  	}
   333  
   334  	// need to parse envoy_gateway_bind_addresses if present
   335  
   336  	if ebo := listVal.Filter("envoy_gateway_bind_addresses"); len(ebo.Items) > 0 {
   337  		proxy.EnvoyGatewayBindAddresses = make(map[string]*api.ConsulGatewayBindAddress)
   338  		for _, listenerM := range ebo.Items { // object item, each listener object
   339  			listenerName := listenerM.Keys[0].Token.Value().(string)
   340  
   341  			var listenerListVal *ast.ObjectList
   342  			if ot, ok := listenerM.Val.(*ast.ObjectType); ok {
   343  				listenerListVal = ot.List
   344  			} else {
   345  				return nil, fmt.Errorf("listener: should be an object")
   346  			}
   347  
   348  			var bind api.ConsulGatewayBindAddress
   349  			if err := hcl.DecodeObject(&bind, listenerListVal); err != nil {
   350  				return nil, fmt.Errorf("port: should be an int")
   351  			}
   352  			bind.Name = listenerName
   353  			proxy.EnvoyGatewayBindAddresses[listenerName] = &bind
   354  		}
   355  	}
   356  
   357  	// need to parse the opaque config if present
   358  
   359  	if co := listVal.Filter("config"); len(co.Items) > 1 {
   360  		return nil, fmt.Errorf("only 1 meta object supported")
   361  	} else if len(co.Items) == 1 {
   362  		var mSlice []map[string]interface{}
   363  		if err := hcl.DecodeObject(&mSlice, co.Items[0].Val); err != nil {
   364  			return nil, err
   365  		}
   366  
   367  		if len(mSlice) > 1 {
   368  			return nil, fmt.Errorf("only 1 meta object supported")
   369  		}
   370  
   371  		m := mSlice[0]
   372  
   373  		if err := mapstructure.WeakDecode(m, &proxy.Config); err != nil {
   374  			return nil, err
   375  		}
   376  
   377  		proxy.Config = flattenMapSlice(proxy.Config)
   378  	}
   379  
   380  	return &proxy, nil
   381  }
   382  
   383  func parseConsulIngressService(o *ast.ObjectItem) (*api.ConsulIngressService, error) {
   384  	valid := []string{
   385  		"name",
   386  		"hosts",
   387  	}
   388  
   389  	if err := checkHCLKeys(o.Val, valid); err != nil {
   390  		return nil, multierror.Prefix(err, "service ->")
   391  	}
   392  
   393  	var service api.ConsulIngressService
   394  	var m map[string]interface{}
   395  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
   396  		return nil, err
   397  	}
   398  
   399  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   400  		Result: &service,
   401  	})
   402  	if err != nil {
   403  		return nil, err
   404  	}
   405  
   406  	if err := dec.Decode(m); err != nil {
   407  		return nil, err
   408  	}
   409  
   410  	return &service, nil
   411  }
   412  
   413  func parseConsulIngressListener(o *ast.ObjectItem) (*api.ConsulIngressListener, error) {
   414  	valid := []string{
   415  		"port",
   416  		"protocol",
   417  		"service",
   418  	}
   419  
   420  	if err := checkHCLKeys(o.Val, valid); err != nil {
   421  		return nil, multierror.Prefix(err, "listener ->")
   422  	}
   423  
   424  	var listener api.ConsulIngressListener
   425  	var m map[string]interface{}
   426  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
   427  		return nil, err
   428  	}
   429  
   430  	delete(m, "service")
   431  
   432  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   433  		Result: &listener,
   434  	})
   435  	if err != nil {
   436  		return nil, err
   437  	}
   438  
   439  	if err := dec.Decode(m); err != nil {
   440  		return nil, err
   441  	}
   442  
   443  	// Parse services
   444  
   445  	var listVal *ast.ObjectList
   446  	if ot, ok := o.Val.(*ast.ObjectType); ok {
   447  		listVal = ot.List
   448  	} else {
   449  		return nil, fmt.Errorf("listener: should be an object")
   450  	}
   451  
   452  	so := listVal.Filter("service")
   453  	if len(so.Items) > 0 {
   454  		listener.Services = make([]*api.ConsulIngressService, len(so.Items))
   455  		for i := range so.Items {
   456  			is, err := parseConsulIngressService(so.Items[i])
   457  			if err != nil {
   458  				return nil, err
   459  			}
   460  			listener.Services[i] = is
   461  		}
   462  	}
   463  	return &listener, nil
   464  }
   465  
   466  func parseConsulGatewayTLS(o *ast.ObjectItem) (*api.ConsulGatewayTLSConfig, error) {
   467  	valid := []string{
   468  		"enabled",
   469  	}
   470  
   471  	if err := checkHCLKeys(o.Val, valid); err != nil {
   472  		return nil, multierror.Prefix(err, "tls ->")
   473  	}
   474  
   475  	var tls api.ConsulGatewayTLSConfig
   476  	var m map[string]interface{}
   477  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
   478  		return nil, err
   479  	}
   480  
   481  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   482  		Result: &tls,
   483  	})
   484  	if err != nil {
   485  		return nil, err
   486  	}
   487  
   488  	if err := dec.Decode(m); err != nil {
   489  		return nil, err
   490  	}
   491  
   492  	return &tls, nil
   493  }
   494  
   495  func parseIngressConfigEntry(o *ast.ObjectItem) (*api.ConsulIngressConfigEntry, error) {
   496  	valid := []string{
   497  		"tls",
   498  		"listener",
   499  	}
   500  
   501  	if err := checkHCLKeys(o.Val, valid); err != nil {
   502  		return nil, multierror.Prefix(err, "ingress ->")
   503  	}
   504  
   505  	var ingress api.ConsulIngressConfigEntry
   506  	var m map[string]interface{}
   507  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
   508  		return nil, err
   509  	}
   510  
   511  	delete(m, "tls")
   512  	delete(m, "listener")
   513  
   514  	// Parse tls and listener(s)
   515  
   516  	var listVal *ast.ObjectList
   517  	if ot, ok := o.Val.(*ast.ObjectType); ok {
   518  		listVal = ot.List
   519  	} else {
   520  		return nil, fmt.Errorf("ingress: should be an object")
   521  	}
   522  
   523  	if to := listVal.Filter("tls"); len(to.Items) > 1 {
   524  		return nil, fmt.Errorf("only 1 tls object supported")
   525  	} else if len(to.Items) == 1 {
   526  		if tls, err := parseConsulGatewayTLS(to.Items[0]); err != nil {
   527  			return nil, err
   528  		} else {
   529  			ingress.TLS = tls
   530  		}
   531  	}
   532  
   533  	lo := listVal.Filter("listener")
   534  	if len(lo.Items) > 0 {
   535  		ingress.Listeners = make([]*api.ConsulIngressListener, len(lo.Items))
   536  		for i := range lo.Items {
   537  			listener, err := parseConsulIngressListener(lo.Items[i])
   538  			if err != nil {
   539  				return nil, err
   540  			}
   541  			ingress.Listeners[i] = listener
   542  		}
   543  	}
   544  
   545  	return &ingress, nil
   546  }
   547  
   548  func parseSidecarService(o *ast.ObjectItem) (*api.ConsulSidecarService, error) {
   549  	valid := []string{
   550  		"port",
   551  		"proxy",
   552  		"tags",
   553  	}
   554  
   555  	if err := checkHCLKeys(o.Val, valid); err != nil {
   556  		return nil, multierror.Prefix(err, "sidecar_service ->")
   557  	}
   558  
   559  	var sidecar api.ConsulSidecarService
   560  	var m map[string]interface{}
   561  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
   562  		return nil, err
   563  	}
   564  
   565  	delete(m, "proxy")
   566  
   567  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   568  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   569  		WeaklyTypedInput: true,
   570  		Result:           &sidecar,
   571  	})
   572  	if err != nil {
   573  		return nil, err
   574  	}
   575  	if err := dec.Decode(m); err != nil {
   576  		return nil, fmt.Errorf("sidecar_service: %v", err)
   577  	}
   578  
   579  	var proxyList *ast.ObjectList
   580  	if ot, ok := o.Val.(*ast.ObjectType); ok {
   581  		proxyList = ot.List
   582  	} else {
   583  		return nil, fmt.Errorf("sidecar_service: should be an object")
   584  	}
   585  
   586  	// Parse the proxy
   587  	po := proxyList.Filter("proxy")
   588  	if len(po.Items) == 0 {
   589  		return &sidecar, nil
   590  	}
   591  	if len(po.Items) > 1 {
   592  		return nil, fmt.Errorf("only one 'proxy' block allowed per task")
   593  	}
   594  
   595  	r, err := parseProxy(po.Items[0])
   596  	if err != nil {
   597  		return nil, fmt.Errorf("proxy, %v", err)
   598  	}
   599  	sidecar.Proxy = r
   600  
   601  	return &sidecar, nil
   602  }
   603  
   604  func parseSidecarTask(item *ast.ObjectItem) (*api.SidecarTask, error) {
   605  	task, err := parseTask(item, sidecarTaskKeys)
   606  	if err != nil {
   607  		return nil, err
   608  	}
   609  
   610  	sidecarTask := &api.SidecarTask{
   611  		Name:        task.Name,
   612  		Driver:      task.Driver,
   613  		User:        task.User,
   614  		Config:      task.Config,
   615  		Env:         task.Env,
   616  		Resources:   task.Resources,
   617  		Meta:        task.Meta,
   618  		KillTimeout: task.KillTimeout,
   619  		LogConfig:   task.LogConfig,
   620  		KillSignal:  task.KillSignal,
   621  	}
   622  
   623  	// Parse ShutdownDelay separatly to get pointer
   624  	var m map[string]interface{}
   625  	if err := hcl.DecodeObject(&m, item.Val); err != nil {
   626  		return nil, err
   627  	}
   628  
   629  	m = map[string]interface{}{
   630  		"shutdown_delay": m["shutdown_delay"],
   631  	}
   632  
   633  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   634  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   635  		WeaklyTypedInput: true,
   636  		Result:           sidecarTask,
   637  	})
   638  
   639  	if err != nil {
   640  		return nil, err
   641  	}
   642  	if err := dec.Decode(m); err != nil {
   643  		return nil, err
   644  	}
   645  	return sidecarTask, nil
   646  }
   647  
   648  func parseProxy(o *ast.ObjectItem) (*api.ConsulProxy, error) {
   649  	valid := []string{
   650  		"local_service_address",
   651  		"local_service_port",
   652  		"upstreams",
   653  		"expose",
   654  		"config",
   655  	}
   656  
   657  	if err := checkHCLKeys(o.Val, valid); err != nil {
   658  		return nil, multierror.Prefix(err, "proxy ->")
   659  	}
   660  
   661  	var proxy api.ConsulProxy
   662  	var m map[string]interface{}
   663  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
   664  		return nil, err
   665  	}
   666  
   667  	delete(m, "upstreams")
   668  	delete(m, "expose")
   669  	delete(m, "config")
   670  
   671  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   672  		Result: &proxy,
   673  	})
   674  	if err != nil {
   675  		return nil, err
   676  	}
   677  	if err := dec.Decode(m); err != nil {
   678  		return nil, fmt.Errorf("proxy: %v", err)
   679  	}
   680  
   681  	// Parse upstreams, expose, and config
   682  
   683  	var listVal *ast.ObjectList
   684  	if ot, ok := o.Val.(*ast.ObjectType); ok {
   685  		listVal = ot.List
   686  	} else {
   687  		return nil, fmt.Errorf("proxy: should be an object")
   688  	}
   689  
   690  	uo := listVal.Filter("upstreams")
   691  	if len(uo.Items) > 0 {
   692  		proxy.Upstreams = make([]*api.ConsulUpstream, len(uo.Items))
   693  		for i := range uo.Items {
   694  			u, err := parseUpstream(uo.Items[i])
   695  			if err != nil {
   696  				return nil, err
   697  			}
   698  			proxy.Upstreams[i] = u
   699  		}
   700  	}
   701  
   702  	if eo := listVal.Filter("expose"); len(eo.Items) > 1 {
   703  		return nil, fmt.Errorf("only 1 expose object supported")
   704  	} else if len(eo.Items) == 1 {
   705  		if e, err := parseExpose(eo.Items[0]); err != nil {
   706  			return nil, err
   707  		} else {
   708  			proxy.ExposeConfig = e
   709  		}
   710  	}
   711  
   712  	// If we have config, then parse that
   713  	if o := listVal.Filter("config"); len(o.Items) > 1 {
   714  		return nil, fmt.Errorf("only 1 meta object supported")
   715  	} else if len(o.Items) == 1 {
   716  		var mSlice []map[string]interface{}
   717  		if err := hcl.DecodeObject(&mSlice, o.Items[0].Val); err != nil {
   718  			return nil, err
   719  		}
   720  
   721  		if len(mSlice) > 1 {
   722  			return nil, fmt.Errorf("only 1 meta object supported")
   723  		}
   724  
   725  		m := mSlice[0]
   726  
   727  		if err := mapstructure.WeakDecode(m, &proxy.Config); err != nil {
   728  			return nil, err
   729  		}
   730  
   731  		proxy.Config = flattenMapSlice(proxy.Config)
   732  	}
   733  
   734  	return &proxy, nil
   735  }
   736  
   737  func parseExpose(eo *ast.ObjectItem) (*api.ConsulExposeConfig, error) {
   738  	valid := []string{
   739  		"path", // an array of path blocks
   740  	}
   741  
   742  	if err := checkHCLKeys(eo.Val, valid); err != nil {
   743  		return nil, multierror.Prefix(err, "expose ->")
   744  	}
   745  
   746  	var expose api.ConsulExposeConfig
   747  
   748  	var listVal *ast.ObjectList
   749  	if eoType, ok := eo.Val.(*ast.ObjectType); ok {
   750  		listVal = eoType.List
   751  	} else {
   752  		return nil, fmt.Errorf("expose: should be an object")
   753  	}
   754  
   755  	// Parse the expose block
   756  
   757  	po := listVal.Filter("path") // array
   758  	if len(po.Items) > 0 {
   759  		expose.Path = make([]*api.ConsulExposePath, len(po.Items))
   760  		for i := range po.Items {
   761  			p, err := parseExposePath(po.Items[i])
   762  			if err != nil {
   763  				return nil, err
   764  			}
   765  			expose.Path[i] = p
   766  		}
   767  	}
   768  
   769  	return &expose, nil
   770  }
   771  
   772  func parseExposePath(epo *ast.ObjectItem) (*api.ConsulExposePath, error) {
   773  	valid := []string{
   774  		"path",
   775  		"protocol",
   776  		"local_path_port",
   777  		"listener_port",
   778  	}
   779  
   780  	if err := checkHCLKeys(epo.Val, valid); err != nil {
   781  		return nil, multierror.Prefix(err, "path ->")
   782  	}
   783  
   784  	var path api.ConsulExposePath
   785  	var m map[string]interface{}
   786  	if err := hcl.DecodeObject(&m, epo.Val); err != nil {
   787  		return nil, err
   788  	}
   789  
   790  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   791  		Result: &path,
   792  	})
   793  	if err != nil {
   794  		return nil, err
   795  	}
   796  
   797  	if err := dec.Decode(m); err != nil {
   798  		return nil, err
   799  	}
   800  
   801  	return &path, nil
   802  }
   803  
   804  func parseUpstream(uo *ast.ObjectItem) (*api.ConsulUpstream, error) {
   805  	valid := []string{
   806  		"destination_name",
   807  		"local_bind_port",
   808  		"local_bind_address",
   809  		"datacenter",
   810  	}
   811  
   812  	if err := checkHCLKeys(uo.Val, valid); err != nil {
   813  		return nil, multierror.Prefix(err, "upstream ->")
   814  	}
   815  
   816  	var upstream api.ConsulUpstream
   817  	var m map[string]interface{}
   818  	if err := hcl.DecodeObject(&m, uo.Val); err != nil {
   819  		return nil, err
   820  	}
   821  
   822  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   823  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   824  		WeaklyTypedInput: true,
   825  		Result:           &upstream,
   826  	})
   827  	if err != nil {
   828  		return nil, err
   829  	}
   830  
   831  	if err := dec.Decode(m); err != nil {
   832  		return nil, err
   833  	}
   834  
   835  	return &upstream, nil
   836  }
   837  
   838  func parseChecks(service *api.Service, checkObjs *ast.ObjectList) error {
   839  	service.Checks = make([]api.ServiceCheck, len(checkObjs.Items))
   840  	for idx, co := range checkObjs.Items {
   841  		// Check for invalid keys
   842  		valid := []string{
   843  			"name",
   844  			"type",
   845  			"interval",
   846  			"timeout",
   847  			"path",
   848  			"protocol",
   849  			"port",
   850  			"expose",
   851  			"command",
   852  			"args",
   853  			"initial_status",
   854  			"tls_skip_verify",
   855  			"header",
   856  			"method",
   857  			"check_restart",
   858  			"address_mode",
   859  			"grpc_service",
   860  			"grpc_use_tls",
   861  			"task",
   862  			"success_before_passing",
   863  			"failures_before_critical",
   864  		}
   865  		if err := checkHCLKeys(co.Val, valid); err != nil {
   866  			return multierror.Prefix(err, "check ->")
   867  		}
   868  
   869  		var check api.ServiceCheck
   870  		var cm map[string]interface{}
   871  		if err := hcl.DecodeObject(&cm, co.Val); err != nil {
   872  			return err
   873  		}
   874  
   875  		// HCL allows repeating stanzas so merge 'header' into a single
   876  		// map[string][]string.
   877  		if headerI, ok := cm["header"]; ok {
   878  			headerRaw, ok := headerI.([]map[string]interface{})
   879  			if !ok {
   880  				return fmt.Errorf("check -> header -> expected a []map[string][]string but found %T", headerI)
   881  			}
   882  			m := map[string][]string{}
   883  			for _, rawm := range headerRaw {
   884  				for k, vI := range rawm {
   885  					vs, ok := vI.([]interface{})
   886  					if !ok {
   887  						return fmt.Errorf("check -> header -> %q expected a []string but found %T", k, vI)
   888  					}
   889  					for _, vI := range vs {
   890  						v, ok := vI.(string)
   891  						if !ok {
   892  							return fmt.Errorf("check -> header -> %q expected a string but found %T", k, vI)
   893  						}
   894  						m[k] = append(m[k], v)
   895  					}
   896  				}
   897  			}
   898  
   899  			check.Header = m
   900  
   901  			// Remove "header" as it has been parsed
   902  			delete(cm, "header")
   903  		}
   904  
   905  		delete(cm, "check_restart")
   906  
   907  		dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   908  			DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   909  			WeaklyTypedInput: true,
   910  			Result:           &check,
   911  		})
   912  		if err != nil {
   913  			return err
   914  		}
   915  		if err := dec.Decode(cm); err != nil {
   916  			return err
   917  		}
   918  
   919  		// Filter check_restart
   920  		var checkRestartList *ast.ObjectList
   921  		if ot, ok := co.Val.(*ast.ObjectType); ok {
   922  			checkRestartList = ot.List
   923  		} else {
   924  			return fmt.Errorf("check_restart '%s': should be an object", check.Name)
   925  		}
   926  
   927  		if cro := checkRestartList.Filter("check_restart"); len(cro.Items) > 0 {
   928  			if len(cro.Items) > 1 {
   929  				return fmt.Errorf("check_restart '%s': cannot have more than 1 check_restart", check.Name)
   930  			}
   931  			cr, err := parseCheckRestart(cro.Items[0])
   932  			if err != nil {
   933  				return multierror.Prefix(err, fmt.Sprintf("check: '%s',", check.Name))
   934  			}
   935  			check.CheckRestart = cr
   936  		}
   937  
   938  		service.Checks[idx] = check
   939  	}
   940  
   941  	return nil
   942  }
   943  
   944  func parseCheckRestart(cro *ast.ObjectItem) (*api.CheckRestart, error) {
   945  	valid := []string{
   946  		"limit",
   947  		"grace",
   948  		"ignore_warnings",
   949  	}
   950  
   951  	if err := checkHCLKeys(cro.Val, valid); err != nil {
   952  		return nil, multierror.Prefix(err, "check_restart ->")
   953  	}
   954  
   955  	var checkRestart api.CheckRestart
   956  	var crm map[string]interface{}
   957  	if err := hcl.DecodeObject(&crm, cro.Val); err != nil {
   958  		return nil, err
   959  	}
   960  
   961  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   962  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   963  		WeaklyTypedInput: true,
   964  		Result:           &checkRestart,
   965  	})
   966  	if err != nil {
   967  		return nil, err
   968  	}
   969  	if err := dec.Decode(crm); err != nil {
   970  		return nil, err
   971  	}
   972  
   973  	return &checkRestart, nil
   974  }