open-nomad/jobspec/parse_service.go

972 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)
}
bind.Name = listenerName
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
}