af8cab3d74
This eases adoption of the jobspec package by other projects (e.g. terraform nomad provider, Lavant). Either by consuming directy as a library (hopefully without having go mod import rest of nomad) or by copying the package without modification. Ideally, this package will be published as an independent module. We aren't ready for that considering we'll be switching to HCLv2 "soon", but eitherway, this seems like a reasonable intermediate step if we choose to.
971 lines
22 KiB
Go
971 lines
22 KiB
Go
package jobspec
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
multierror "github.com/hashicorp/go-multierror"
|
|
"github.com/hashicorp/hcl"
|
|
"github.com/hashicorp/hcl/hcl/ast"
|
|
"github.com/hashicorp/nomad/api"
|
|
"github.com/mitchellh/mapstructure"
|
|
)
|
|
|
|
func parseGroupServices(g *api.TaskGroup, serviceObjs *ast.ObjectList) error {
|
|
g.Services = make([]*api.Service, len(serviceObjs.Items))
|
|
for idx, o := range serviceObjs.Items {
|
|
service, err := parseService(o)
|
|
if err != nil {
|
|
return multierror.Prefix(err, fmt.Sprintf("service (%d):", idx))
|
|
}
|
|
g.Services[idx] = service
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func parseServices(serviceObjs *ast.ObjectList) ([]*api.Service, error) {
|
|
services := make([]*api.Service, len(serviceObjs.Items))
|
|
for idx, o := range serviceObjs.Items {
|
|
service, err := parseService(o)
|
|
if err != nil {
|
|
return nil, multierror.Prefix(err, fmt.Sprintf("service (%d):", idx))
|
|
}
|
|
services[idx] = service
|
|
}
|
|
return services, nil
|
|
}
|
|
func parseService(o *ast.ObjectItem) (*api.Service, error) {
|
|
// Check for invalid keys
|
|
valid := []string{
|
|
"name",
|
|
"tags",
|
|
"canary_tags",
|
|
"enable_tag_override",
|
|
"port",
|
|
"check",
|
|
"address_mode",
|
|
"check_restart",
|
|
"connect",
|
|
"task",
|
|
"meta",
|
|
"canary_meta",
|
|
}
|
|
if err := checkHCLKeys(o.Val, valid); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var service api.Service
|
|
var m map[string]interface{}
|
|
if err := hcl.DecodeObject(&m, o.Val); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
delete(m, "check")
|
|
delete(m, "check_restart")
|
|
delete(m, "connect")
|
|
delete(m, "meta")
|
|
delete(m, "canary_meta")
|
|
|
|
if err := mapstructure.WeakDecode(m, &service); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Filter list
|
|
var listVal *ast.ObjectList
|
|
if ot, ok := o.Val.(*ast.ObjectType); ok {
|
|
listVal = ot.List
|
|
} else {
|
|
return nil, fmt.Errorf("'%s': should be an object", service.Name)
|
|
}
|
|
|
|
if co := listVal.Filter("check"); len(co.Items) > 0 {
|
|
if err := parseChecks(&service, co); err != nil {
|
|
return nil, multierror.Prefix(err, fmt.Sprintf("'%s',", service.Name))
|
|
}
|
|
}
|
|
|
|
// Filter check_restart
|
|
if cro := listVal.Filter("check_restart"); len(cro.Items) > 0 {
|
|
if len(cro.Items) > 1 {
|
|
return nil, fmt.Errorf("check_restart '%s': cannot have more than 1 check_restart", service.Name)
|
|
}
|
|
cr, err := parseCheckRestart(cro.Items[0])
|
|
if err != nil {
|
|
return nil, multierror.Prefix(err, fmt.Sprintf("'%s',", service.Name))
|
|
}
|
|
service.CheckRestart = cr
|
|
|
|
}
|
|
|
|
// Filter connect
|
|
if co := listVal.Filter("connect"); len(co.Items) > 0 {
|
|
if len(co.Items) > 1 {
|
|
return nil, fmt.Errorf("connect '%s': cannot have more than 1 connect stanza", service.Name)
|
|
}
|
|
|
|
c, err := parseConnect(co.Items[0])
|
|
if err != nil {
|
|
return nil, multierror.Prefix(err, fmt.Sprintf("'%s',", service.Name))
|
|
}
|
|
|
|
service.Connect = c
|
|
}
|
|
|
|
// Parse out meta fields. These are in HCL as a list so we need
|
|
// to iterate over them and merge them.
|
|
if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 {
|
|
for _, o := range metaO.Elem().Items {
|
|
var m map[string]interface{}
|
|
if err := hcl.DecodeObject(&m, o.Val); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := mapstructure.WeakDecode(m, &service.Meta); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse out canary_meta fields. These are in HCL as a list so we need
|
|
// to iterate over them and merge them.
|
|
if metaO := listVal.Filter("canary_meta"); len(metaO.Items) > 0 {
|
|
for _, o := range metaO.Elem().Items {
|
|
var m map[string]interface{}
|
|
if err := hcl.DecodeObject(&m, o.Val); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := mapstructure.WeakDecode(m, &service.CanaryMeta); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
return &service, nil
|
|
}
|
|
|
|
func parseConnect(co *ast.ObjectItem) (*api.ConsulConnect, error) {
|
|
valid := []string{
|
|
"native",
|
|
"gateway",
|
|
"sidecar_service",
|
|
"sidecar_task",
|
|
}
|
|
|
|
if err := checkHCLKeys(co.Val, valid); err != nil {
|
|
return nil, multierror.Prefix(err, "connect ->")
|
|
}
|
|
|
|
var connect api.ConsulConnect
|
|
var m map[string]interface{}
|
|
if err := hcl.DecodeObject(&m, co.Val); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
delete(m, "gateway")
|
|
delete(m, "sidecar_service")
|
|
delete(m, "sidecar_task")
|
|
|
|
if err := mapstructure.WeakDecode(m, &connect); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var connectList *ast.ObjectList
|
|
if ot, ok := co.Val.(*ast.ObjectType); ok {
|
|
connectList = ot.List
|
|
} else {
|
|
return nil, fmt.Errorf("connect should be an object")
|
|
}
|
|
|
|
// Parse the gateway
|
|
o := connectList.Filter("gateway")
|
|
if len(o.Items) > 1 {
|
|
return nil, fmt.Errorf("only one 'gateway' block allowed per task")
|
|
} else if len(o.Items) == 1 {
|
|
g, err := parseGateway(o.Items[0])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("gateway, %v", err)
|
|
}
|
|
connect.Gateway = g
|
|
}
|
|
|
|
// Parse the sidecar_service
|
|
o = connectList.Filter("sidecar_service")
|
|
if len(o.Items) == 0 {
|
|
return &connect, nil
|
|
}
|
|
if len(o.Items) > 1 {
|
|
return nil, fmt.Errorf("only one 'sidecar_service' block allowed per task")
|
|
}
|
|
|
|
r, err := parseSidecarService(o.Items[0])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("sidecar_service, %v", err)
|
|
}
|
|
connect.SidecarService = r
|
|
|
|
// Parse the sidecar_task
|
|
o = connectList.Filter("sidecar_task")
|
|
if len(o.Items) == 0 {
|
|
return &connect, nil
|
|
}
|
|
if len(o.Items) > 1 {
|
|
return nil, fmt.Errorf("only one 'sidecar_task' block allowed per task")
|
|
}
|
|
|
|
t, err := parseSidecarTask(o.Items[0])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("sidecar_task, %v", err)
|
|
}
|
|
connect.SidecarTask = t
|
|
|
|
return &connect, nil
|
|
}
|
|
|
|
func parseGateway(o *ast.ObjectItem) (*api.ConsulGateway, error) {
|
|
valid := []string{
|
|
"proxy",
|
|
"ingress",
|
|
}
|
|
|
|
if err := checkHCLKeys(o.Val, valid); err != nil {
|
|
return nil, multierror.Prefix(err, "gateway ->")
|
|
}
|
|
|
|
var gateway api.ConsulGateway
|
|
var m map[string]interface{}
|
|
if err := hcl.DecodeObject(&m, o.Val); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
delete(m, "proxy")
|
|
delete(m, "ingress")
|
|
|
|
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
|
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
|
|
WeaklyTypedInput: true,
|
|
Result: &gateway,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := dec.Decode(m); err != nil {
|
|
return nil, fmt.Errorf("gateway: %v", err)
|
|
}
|
|
|
|
// list of parameters
|
|
var listVal *ast.ObjectList
|
|
if ot, ok := o.Val.(*ast.ObjectType); ok {
|
|
listVal = ot.List
|
|
} else {
|
|
return nil, fmt.Errorf("proxy: should be an object")
|
|
}
|
|
|
|
// extract and parse the proxy block
|
|
po := listVal.Filter("proxy")
|
|
if len(po.Items) != 1 {
|
|
return nil, fmt.Errorf("must have one 'proxy' block")
|
|
}
|
|
proxy, err := parseGatewayProxy(po.Items[0])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("proxy, %v", err)
|
|
}
|
|
gateway.Proxy = proxy
|
|
|
|
// extract and parse the ingress block
|
|
io := listVal.Filter("ingress")
|
|
if len(io.Items) != 1 {
|
|
// in the future, may be terminating or mesh block instead
|
|
return nil, fmt.Errorf("must have one 'ingress' block")
|
|
}
|
|
ingress, err := parseIngressConfigEntry(io.Items[0])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("ingress, %v", err)
|
|
}
|
|
gateway.Ingress = ingress
|
|
|
|
return &gateway, nil
|
|
}
|
|
|
|
// parseGatewayProxy parses envoy gateway proxy options supported by Consul.
|
|
//
|
|
// consul.io/docs/connect/proxies/envoy#gateway-options
|
|
func parseGatewayProxy(o *ast.ObjectItem) (*api.ConsulGatewayProxy, error) {
|
|
valid := []string{
|
|
"connect_timeout",
|
|
"envoy_gateway_bind_tagged_addresses",
|
|
"envoy_gateway_bind_addresses",
|
|
"envoy_gateway_no_default_bind",
|
|
"envoy_dns_discovery_type",
|
|
"config",
|
|
}
|
|
|
|
if err := checkHCLKeys(o.Val, valid); err != nil {
|
|
return nil, multierror.Prefix(err, "proxy ->")
|
|
}
|
|
|
|
var proxy api.ConsulGatewayProxy
|
|
var m map[string]interface{}
|
|
if err := hcl.DecodeObject(&m, o.Val); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
delete(m, "config")
|
|
delete(m, "envoy_gateway_bind_addresses")
|
|
|
|
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
|
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
|
|
WeaklyTypedInput: true,
|
|
Result: &proxy,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := dec.Decode(m); err != nil {
|
|
return nil, fmt.Errorf("proxy: %v", err)
|
|
}
|
|
|
|
var listVal *ast.ObjectList
|
|
if ot, ok := o.Val.(*ast.ObjectType); ok {
|
|
listVal = ot.List
|
|
} else {
|
|
return nil, fmt.Errorf("proxy: should be an object")
|
|
}
|
|
|
|
// need to parse envoy_gateway_bind_addresses if present
|
|
|
|
if ebo := listVal.Filter("envoy_gateway_bind_addresses"); len(ebo.Items) > 0 {
|
|
proxy.EnvoyGatewayBindAddresses = make(map[string]*api.ConsulGatewayBindAddress)
|
|
for _, listenerM := range ebo.Items { // object item, each listener object
|
|
listenerName := listenerM.Keys[0].Token.Value().(string)
|
|
|
|
var listenerListVal *ast.ObjectList
|
|
if ot, ok := listenerM.Val.(*ast.ObjectType); ok {
|
|
listenerListVal = ot.List
|
|
} else {
|
|
return nil, fmt.Errorf("listener: should be an object")
|
|
}
|
|
|
|
var bind api.ConsulGatewayBindAddress
|
|
if err := hcl.DecodeObject(&bind, listenerListVal); err != nil {
|
|
panic(err)
|
|
}
|
|
proxy.EnvoyGatewayBindAddresses[listenerName] = &bind
|
|
}
|
|
}
|
|
|
|
// need to parse the opaque config if present
|
|
|
|
if co := listVal.Filter("config"); len(co.Items) > 1 {
|
|
return nil, fmt.Errorf("only 1 meta object supported")
|
|
} else if len(co.Items) == 1 {
|
|
var mSlice []map[string]interface{}
|
|
if err := hcl.DecodeObject(&mSlice, co.Items[0].Val); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(mSlice) > 1 {
|
|
return nil, fmt.Errorf("only 1 meta object supported")
|
|
}
|
|
|
|
m := mSlice[0]
|
|
|
|
if err := mapstructure.WeakDecode(m, &proxy.Config); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
proxy.Config = flattenMapSlice(proxy.Config)
|
|
}
|
|
|
|
return &proxy, nil
|
|
}
|
|
|
|
func parseConsulIngressService(o *ast.ObjectItem) (*api.ConsulIngressService, error) {
|
|
valid := []string{
|
|
"name",
|
|
"hosts",
|
|
}
|
|
|
|
if err := checkHCLKeys(o.Val, valid); err != nil {
|
|
return nil, multierror.Prefix(err, "service ->")
|
|
}
|
|
|
|
var service api.ConsulIngressService
|
|
var m map[string]interface{}
|
|
if err := hcl.DecodeObject(&m, o.Val); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
|
Result: &service,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := dec.Decode(m); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &service, nil
|
|
}
|
|
|
|
func parseConsulIngressListener(o *ast.ObjectItem) (*api.ConsulIngressListener, error) {
|
|
valid := []string{
|
|
"port",
|
|
"protocol",
|
|
"service",
|
|
}
|
|
|
|
if err := checkHCLKeys(o.Val, valid); err != nil {
|
|
return nil, multierror.Prefix(err, "listener ->")
|
|
}
|
|
|
|
var listener api.ConsulIngressListener
|
|
var m map[string]interface{}
|
|
if err := hcl.DecodeObject(&m, o.Val); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
delete(m, "service")
|
|
|
|
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
|
Result: &listener,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := dec.Decode(m); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Parse services
|
|
|
|
var listVal *ast.ObjectList
|
|
if ot, ok := o.Val.(*ast.ObjectType); ok {
|
|
listVal = ot.List
|
|
} else {
|
|
return nil, fmt.Errorf("listener: should be an object")
|
|
}
|
|
|
|
so := listVal.Filter("service")
|
|
if len(so.Items) > 0 {
|
|
listener.Services = make([]*api.ConsulIngressService, len(so.Items))
|
|
for i := range so.Items {
|
|
is, err := parseConsulIngressService(so.Items[i])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
listener.Services[i] = is
|
|
}
|
|
}
|
|
return &listener, nil
|
|
}
|
|
|
|
func parseConsulGatewayTLS(o *ast.ObjectItem) (*api.ConsulGatewayTLSConfig, error) {
|
|
valid := []string{
|
|
"enabled",
|
|
}
|
|
|
|
if err := checkHCLKeys(o.Val, valid); err != nil {
|
|
return nil, multierror.Prefix(err, "tls ->")
|
|
}
|
|
|
|
var tls api.ConsulGatewayTLSConfig
|
|
var m map[string]interface{}
|
|
if err := hcl.DecodeObject(&m, o.Val); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
|
Result: &tls,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := dec.Decode(m); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &tls, nil
|
|
}
|
|
|
|
func parseIngressConfigEntry(o *ast.ObjectItem) (*api.ConsulIngressConfigEntry, error) {
|
|
valid := []string{
|
|
"tls",
|
|
"listener",
|
|
}
|
|
|
|
if err := checkHCLKeys(o.Val, valid); err != nil {
|
|
return nil, multierror.Prefix(err, "ingress ->")
|
|
}
|
|
|
|
var ingress api.ConsulIngressConfigEntry
|
|
var m map[string]interface{}
|
|
if err := hcl.DecodeObject(&m, o.Val); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
delete(m, "tls")
|
|
delete(m, "listener")
|
|
|
|
// Parse tls and listener(s)
|
|
|
|
var listVal *ast.ObjectList
|
|
if ot, ok := o.Val.(*ast.ObjectType); ok {
|
|
listVal = ot.List
|
|
} else {
|
|
return nil, fmt.Errorf("ingress: should be an object")
|
|
}
|
|
|
|
if to := listVal.Filter("tls"); len(to.Items) > 1 {
|
|
return nil, fmt.Errorf("only 1 tls object supported")
|
|
} else if len(to.Items) == 1 {
|
|
if tls, err := parseConsulGatewayTLS(to.Items[0]); err != nil {
|
|
return nil, err
|
|
} else {
|
|
ingress.TLS = tls
|
|
}
|
|
}
|
|
|
|
lo := listVal.Filter("listener")
|
|
if len(lo.Items) > 0 {
|
|
ingress.Listeners = make([]*api.ConsulIngressListener, len(lo.Items))
|
|
for i := range lo.Items {
|
|
listener, err := parseConsulIngressListener(lo.Items[i])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ingress.Listeners[i] = listener
|
|
}
|
|
}
|
|
|
|
return &ingress, nil
|
|
}
|
|
|
|
func parseSidecarService(o *ast.ObjectItem) (*api.ConsulSidecarService, error) {
|
|
valid := []string{
|
|
"port",
|
|
"proxy",
|
|
"tags",
|
|
}
|
|
|
|
if err := checkHCLKeys(o.Val, valid); err != nil {
|
|
return nil, multierror.Prefix(err, "sidecar_service ->")
|
|
}
|
|
|
|
var sidecar api.ConsulSidecarService
|
|
var m map[string]interface{}
|
|
if err := hcl.DecodeObject(&m, o.Val); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
delete(m, "proxy")
|
|
|
|
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
|
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
|
|
WeaklyTypedInput: true,
|
|
Result: &sidecar,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := dec.Decode(m); err != nil {
|
|
return nil, fmt.Errorf("sidecar_service: %v", err)
|
|
}
|
|
|
|
var proxyList *ast.ObjectList
|
|
if ot, ok := o.Val.(*ast.ObjectType); ok {
|
|
proxyList = ot.List
|
|
} else {
|
|
return nil, fmt.Errorf("sidecar_service: should be an object")
|
|
}
|
|
|
|
// Parse the proxy
|
|
po := proxyList.Filter("proxy")
|
|
if len(po.Items) == 0 {
|
|
return &sidecar, nil
|
|
}
|
|
if len(po.Items) > 1 {
|
|
return nil, fmt.Errorf("only one 'proxy' block allowed per task")
|
|
}
|
|
|
|
r, err := parseProxy(po.Items[0])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("proxy, %v", err)
|
|
}
|
|
sidecar.Proxy = r
|
|
|
|
return &sidecar, nil
|
|
}
|
|
|
|
func parseSidecarTask(item *ast.ObjectItem) (*api.SidecarTask, error) {
|
|
task, err := parseTask(item, sidecarTaskKeys)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sidecarTask := &api.SidecarTask{
|
|
Name: task.Name,
|
|
Driver: task.Driver,
|
|
User: task.User,
|
|
Config: task.Config,
|
|
Env: task.Env,
|
|
Resources: task.Resources,
|
|
Meta: task.Meta,
|
|
KillTimeout: task.KillTimeout,
|
|
LogConfig: task.LogConfig,
|
|
KillSignal: task.KillSignal,
|
|
}
|
|
|
|
// Parse ShutdownDelay separatly to get pointer
|
|
var m map[string]interface{}
|
|
if err := hcl.DecodeObject(&m, item.Val); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
m = map[string]interface{}{
|
|
"shutdown_delay": m["shutdown_delay"],
|
|
}
|
|
|
|
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
|
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
|
|
WeaklyTypedInput: true,
|
|
Result: sidecarTask,
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := dec.Decode(m); err != nil {
|
|
return nil, err
|
|
}
|
|
return sidecarTask, nil
|
|
}
|
|
|
|
func parseProxy(o *ast.ObjectItem) (*api.ConsulProxy, error) {
|
|
valid := []string{
|
|
"local_service_address",
|
|
"local_service_port",
|
|
"upstreams",
|
|
"expose",
|
|
"config",
|
|
}
|
|
|
|
if err := checkHCLKeys(o.Val, valid); err != nil {
|
|
return nil, multierror.Prefix(err, "proxy ->")
|
|
}
|
|
|
|
var proxy api.ConsulProxy
|
|
var m map[string]interface{}
|
|
if err := hcl.DecodeObject(&m, o.Val); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
delete(m, "upstreams")
|
|
delete(m, "expose")
|
|
delete(m, "config")
|
|
|
|
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
|
Result: &proxy,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := dec.Decode(m); err != nil {
|
|
return nil, fmt.Errorf("proxy: %v", err)
|
|
}
|
|
|
|
// Parse upstreams, expose, and config
|
|
|
|
var listVal *ast.ObjectList
|
|
if ot, ok := o.Val.(*ast.ObjectType); ok {
|
|
listVal = ot.List
|
|
} else {
|
|
return nil, fmt.Errorf("proxy: should be an object")
|
|
}
|
|
|
|
uo := listVal.Filter("upstreams")
|
|
if len(uo.Items) > 0 {
|
|
proxy.Upstreams = make([]*api.ConsulUpstream, len(uo.Items))
|
|
for i := range uo.Items {
|
|
u, err := parseUpstream(uo.Items[i])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
proxy.Upstreams[i] = u
|
|
}
|
|
}
|
|
|
|
if eo := listVal.Filter("expose"); len(eo.Items) > 1 {
|
|
return nil, fmt.Errorf("only 1 expose object supported")
|
|
} else if len(eo.Items) == 1 {
|
|
if e, err := parseExpose(eo.Items[0]); err != nil {
|
|
return nil, err
|
|
} else {
|
|
proxy.ExposeConfig = e
|
|
}
|
|
}
|
|
|
|
// If we have config, then parse that
|
|
if o := listVal.Filter("config"); len(o.Items) > 1 {
|
|
return nil, fmt.Errorf("only 1 meta object supported")
|
|
} else if len(o.Items) == 1 {
|
|
var mSlice []map[string]interface{}
|
|
if err := hcl.DecodeObject(&mSlice, o.Items[0].Val); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(mSlice) > 1 {
|
|
return nil, fmt.Errorf("only 1 meta object supported")
|
|
}
|
|
|
|
m := mSlice[0]
|
|
|
|
if err := mapstructure.WeakDecode(m, &proxy.Config); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
proxy.Config = flattenMapSlice(proxy.Config)
|
|
}
|
|
|
|
return &proxy, nil
|
|
}
|
|
|
|
func parseExpose(eo *ast.ObjectItem) (*api.ConsulExposeConfig, error) {
|
|
valid := []string{
|
|
"path", // an array of path blocks
|
|
}
|
|
|
|
if err := checkHCLKeys(eo.Val, valid); err != nil {
|
|
return nil, multierror.Prefix(err, "expose ->")
|
|
}
|
|
|
|
var expose api.ConsulExposeConfig
|
|
|
|
var listVal *ast.ObjectList
|
|
if eoType, ok := eo.Val.(*ast.ObjectType); ok {
|
|
listVal = eoType.List
|
|
} else {
|
|
return nil, fmt.Errorf("expose: should be an object")
|
|
}
|
|
|
|
// Parse the expose block
|
|
|
|
po := listVal.Filter("path") // array
|
|
if len(po.Items) > 0 {
|
|
expose.Path = make([]*api.ConsulExposePath, len(po.Items))
|
|
for i := range po.Items {
|
|
p, err := parseExposePath(po.Items[i])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
expose.Path[i] = p
|
|
}
|
|
}
|
|
|
|
return &expose, nil
|
|
}
|
|
|
|
func parseExposePath(epo *ast.ObjectItem) (*api.ConsulExposePath, error) {
|
|
valid := []string{
|
|
"path",
|
|
"protocol",
|
|
"local_path_port",
|
|
"listener_port",
|
|
}
|
|
|
|
if err := checkHCLKeys(epo.Val, valid); err != nil {
|
|
return nil, multierror.Prefix(err, "path ->")
|
|
}
|
|
|
|
var path api.ConsulExposePath
|
|
var m map[string]interface{}
|
|
if err := hcl.DecodeObject(&m, epo.Val); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
|
Result: &path,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := dec.Decode(m); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &path, nil
|
|
}
|
|
|
|
func parseUpstream(uo *ast.ObjectItem) (*api.ConsulUpstream, error) {
|
|
valid := []string{
|
|
"destination_name",
|
|
"local_bind_port",
|
|
}
|
|
|
|
if err := checkHCLKeys(uo.Val, valid); err != nil {
|
|
return nil, multierror.Prefix(err, "upstream ->")
|
|
}
|
|
|
|
var upstream api.ConsulUpstream
|
|
var m map[string]interface{}
|
|
if err := hcl.DecodeObject(&m, uo.Val); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
|
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
|
|
WeaklyTypedInput: true,
|
|
Result: &upstream,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := dec.Decode(m); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &upstream, nil
|
|
}
|
|
|
|
func parseChecks(service *api.Service, checkObjs *ast.ObjectList) error {
|
|
service.Checks = make([]api.ServiceCheck, len(checkObjs.Items))
|
|
for idx, co := range checkObjs.Items {
|
|
// Check for invalid keys
|
|
valid := []string{
|
|
"name",
|
|
"type",
|
|
"interval",
|
|
"timeout",
|
|
"path",
|
|
"protocol",
|
|
"port",
|
|
"expose",
|
|
"command",
|
|
"args",
|
|
"initial_status",
|
|
"tls_skip_verify",
|
|
"header",
|
|
"method",
|
|
"check_restart",
|
|
"address_mode",
|
|
"grpc_service",
|
|
"grpc_use_tls",
|
|
"task",
|
|
"success_before_passing",
|
|
"failures_before_critical",
|
|
}
|
|
if err := checkHCLKeys(co.Val, valid); err != nil {
|
|
return multierror.Prefix(err, "check ->")
|
|
}
|
|
|
|
var check api.ServiceCheck
|
|
var cm map[string]interface{}
|
|
if err := hcl.DecodeObject(&cm, co.Val); err != nil {
|
|
return err
|
|
}
|
|
|
|
// HCL allows repeating stanzas so merge 'header' into a single
|
|
// map[string][]string.
|
|
if headerI, ok := cm["header"]; ok {
|
|
headerRaw, ok := headerI.([]map[string]interface{})
|
|
if !ok {
|
|
return fmt.Errorf("check -> header -> expected a []map[string][]string but found %T", headerI)
|
|
}
|
|
m := map[string][]string{}
|
|
for _, rawm := range headerRaw {
|
|
for k, vI := range rawm {
|
|
vs, ok := vI.([]interface{})
|
|
if !ok {
|
|
return fmt.Errorf("check -> header -> %q expected a []string but found %T", k, vI)
|
|
}
|
|
for _, vI := range vs {
|
|
v, ok := vI.(string)
|
|
if !ok {
|
|
return fmt.Errorf("check -> header -> %q expected a string but found %T", k, vI)
|
|
}
|
|
m[k] = append(m[k], v)
|
|
}
|
|
}
|
|
}
|
|
|
|
check.Header = m
|
|
|
|
// Remove "header" as it has been parsed
|
|
delete(cm, "header")
|
|
}
|
|
|
|
delete(cm, "check_restart")
|
|
|
|
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
|
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
|
|
WeaklyTypedInput: true,
|
|
Result: &check,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := dec.Decode(cm); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Filter check_restart
|
|
var checkRestartList *ast.ObjectList
|
|
if ot, ok := co.Val.(*ast.ObjectType); ok {
|
|
checkRestartList = ot.List
|
|
} else {
|
|
return fmt.Errorf("check_restart '%s': should be an object", check.Name)
|
|
}
|
|
|
|
if cro := checkRestartList.Filter("check_restart"); len(cro.Items) > 0 {
|
|
if len(cro.Items) > 1 {
|
|
return fmt.Errorf("check_restart '%s': cannot have more than 1 check_restart", check.Name)
|
|
}
|
|
cr, err := parseCheckRestart(cro.Items[0])
|
|
if err != nil {
|
|
return multierror.Prefix(err, fmt.Sprintf("check: '%s',", check.Name))
|
|
}
|
|
check.CheckRestart = cr
|
|
}
|
|
|
|
service.Checks[idx] = check
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func parseCheckRestart(cro *ast.ObjectItem) (*api.CheckRestart, error) {
|
|
valid := []string{
|
|
"limit",
|
|
"grace",
|
|
"ignore_warnings",
|
|
}
|
|
|
|
if err := checkHCLKeys(cro.Val, valid); err != nil {
|
|
return nil, multierror.Prefix(err, "check_restart ->")
|
|
}
|
|
|
|
var checkRestart api.CheckRestart
|
|
var crm map[string]interface{}
|
|
if err := hcl.DecodeObject(&crm, cro.Val); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
|
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
|
|
WeaklyTypedInput: true,
|
|
Result: &checkRestart,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := dec.Decode(crm); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &checkRestart, nil
|
|
}
|