github.com/superfly/nomad@v0.10.5-fly/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/hashicorp/nomad/helper"
    11  	"github.com/mitchellh/mapstructure"
    12  )
    13  
    14  func parseGroupServices(g *api.TaskGroup, serviceObjs *ast.ObjectList) error {
    15  	g.Services = make([]*api.Service, len(serviceObjs.Items))
    16  	for idx, o := range serviceObjs.Items {
    17  		service, err := parseService(o)
    18  		if err != nil {
    19  			return multierror.Prefix(err, fmt.Sprintf("service (%d):", idx))
    20  		}
    21  		g.Services[idx] = service
    22  	}
    23  
    24  	return nil
    25  }
    26  
    27  func parseServices(serviceObjs *ast.ObjectList) ([]*api.Service, error) {
    28  	services := make([]*api.Service, len(serviceObjs.Items))
    29  	for idx, o := range serviceObjs.Items {
    30  		service, err := parseService(o)
    31  		if err != nil {
    32  			return nil, multierror.Prefix(err, fmt.Sprintf("service (%d):", idx))
    33  		}
    34  		services[idx] = service
    35  	}
    36  	return services, nil
    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  		"port",
    45  		"check",
    46  		"address_mode",
    47  		"check_restart",
    48  		"connect",
    49  		"meta",
    50  		"canary_meta",
    51  	}
    52  	if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	var service api.Service
    57  	var m map[string]interface{}
    58  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	delete(m, "check")
    63  	delete(m, "check_restart")
    64  	delete(m, "connect")
    65  	delete(m, "meta")
    66  	delete(m, "canary_meta")
    67  
    68  	if err := mapstructure.WeakDecode(m, &service); err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	// Filter list
    73  	var listVal *ast.ObjectList
    74  	if ot, ok := o.Val.(*ast.ObjectType); ok {
    75  		listVal = ot.List
    76  	} else {
    77  		return nil, fmt.Errorf("'%s': should be an object", service.Name)
    78  	}
    79  
    80  	if co := listVal.Filter("check"); len(co.Items) > 0 {
    81  		if err := parseChecks(&service, co); err != nil {
    82  			return nil, multierror.Prefix(err, fmt.Sprintf("'%s',", service.Name))
    83  		}
    84  	}
    85  
    86  	// Filter check_restart
    87  	if cro := listVal.Filter("check_restart"); len(cro.Items) > 0 {
    88  		if len(cro.Items) > 1 {
    89  			return nil, fmt.Errorf("check_restart '%s': cannot have more than 1 check_restart", service.Name)
    90  		}
    91  		cr, err := parseCheckRestart(cro.Items[0])
    92  		if err != nil {
    93  			return nil, multierror.Prefix(err, fmt.Sprintf("'%s',", service.Name))
    94  		}
    95  		service.CheckRestart = cr
    96  
    97  	}
    98  
    99  	// Filter connect
   100  	if co := listVal.Filter("connect"); len(co.Items) > 0 {
   101  		if len(co.Items) > 1 {
   102  			return nil, fmt.Errorf("connect '%s': cannot have more than 1 connect stanza", service.Name)
   103  		}
   104  
   105  		c, err := parseConnect(co.Items[0])
   106  		if err != nil {
   107  			return nil, multierror.Prefix(err, fmt.Sprintf("'%s',", service.Name))
   108  		}
   109  
   110  		service.Connect = c
   111  	}
   112  
   113  	// Parse out meta fields. These are in HCL as a list so we need
   114  	// to iterate over them and merge them.
   115  	if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 {
   116  		for _, o := range metaO.Elem().Items {
   117  			var m map[string]interface{}
   118  			if err := hcl.DecodeObject(&m, o.Val); err != nil {
   119  				return nil, err
   120  			}
   121  			if err := mapstructure.WeakDecode(m, &service.Meta); err != nil {
   122  				return nil, err
   123  			}
   124  		}
   125  	}
   126  
   127  	// Parse out canary_meta fields. These are in HCL as a list so we need
   128  	// to iterate over them and merge them.
   129  	if metaO := listVal.Filter("canary_meta"); len(metaO.Items) > 0 {
   130  		for _, o := range metaO.Elem().Items {
   131  			var m map[string]interface{}
   132  			if err := hcl.DecodeObject(&m, o.Val); err != nil {
   133  				return nil, err
   134  			}
   135  			if err := mapstructure.WeakDecode(m, &service.CanaryMeta); err != nil {
   136  				return nil, err
   137  			}
   138  		}
   139  	}
   140  
   141  	return &service, nil
   142  }
   143  
   144  func parseConnect(co *ast.ObjectItem) (*api.ConsulConnect, error) {
   145  	valid := []string{
   146  		"native",
   147  		"sidecar_service",
   148  		"sidecar_task",
   149  	}
   150  
   151  	if err := helper.CheckHCLKeys(co.Val, valid); err != nil {
   152  		return nil, multierror.Prefix(err, "connect ->")
   153  	}
   154  
   155  	var connect api.ConsulConnect
   156  	var m map[string]interface{}
   157  	if err := hcl.DecodeObject(&m, co.Val); err != nil {
   158  		return nil, err
   159  	}
   160  
   161  	delete(m, "sidecar_service")
   162  	delete(m, "sidecar_task")
   163  
   164  	if err := mapstructure.WeakDecode(m, &connect); err != nil {
   165  		return nil, err
   166  	}
   167  
   168  	var connectList *ast.ObjectList
   169  	if ot, ok := co.Val.(*ast.ObjectType); ok {
   170  		connectList = ot.List
   171  	} else {
   172  		return nil, fmt.Errorf("connect should be an object")
   173  	}
   174  
   175  	// Parse the sidecar_service
   176  	o := connectList.Filter("sidecar_service")
   177  	if len(o.Items) == 0 {
   178  		return &connect, nil
   179  	}
   180  	if len(o.Items) > 1 {
   181  		return nil, fmt.Errorf("only one 'sidecar_service' block allowed per task")
   182  	}
   183  
   184  	r, err := parseSidecarService(o.Items[0])
   185  	if err != nil {
   186  		return nil, fmt.Errorf("sidecar_service, %v", err)
   187  	}
   188  	connect.SidecarService = r
   189  
   190  	// Parse the sidecar_task
   191  	o = connectList.Filter("sidecar_task")
   192  	if len(o.Items) == 0 {
   193  		return &connect, nil
   194  	}
   195  	if len(o.Items) > 1 {
   196  		return nil, fmt.Errorf("only one 'sidecar_task' block allowed per task")
   197  	}
   198  
   199  	t, err := parseSidecarTask(o.Items[0])
   200  	if err != nil {
   201  		return nil, fmt.Errorf("sidecar_task, %v", err)
   202  	}
   203  	connect.SidecarTask = t
   204  
   205  	return &connect, nil
   206  }
   207  
   208  func parseSidecarService(o *ast.ObjectItem) (*api.ConsulSidecarService, error) {
   209  	valid := []string{
   210  		"port",
   211  		"proxy",
   212  		"tags",
   213  	}
   214  
   215  	if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
   216  		return nil, multierror.Prefix(err, "sidecar_service ->")
   217  	}
   218  
   219  	var sidecar api.ConsulSidecarService
   220  	var m map[string]interface{}
   221  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
   222  		return nil, err
   223  	}
   224  
   225  	delete(m, "proxy")
   226  
   227  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   228  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   229  		WeaklyTypedInput: true,
   230  		Result:           &sidecar,
   231  	})
   232  	if err != nil {
   233  		return nil, err
   234  	}
   235  	if err := dec.Decode(m); err != nil {
   236  		return nil, fmt.Errorf("sidecar_service: %v", err)
   237  	}
   238  
   239  	var proxyList *ast.ObjectList
   240  	if ot, ok := o.Val.(*ast.ObjectType); ok {
   241  		proxyList = ot.List
   242  	} else {
   243  		return nil, fmt.Errorf("sidecar_service: should be an object")
   244  	}
   245  
   246  	// Parse the proxy
   247  	po := proxyList.Filter("proxy")
   248  	if len(po.Items) == 0 {
   249  		return &sidecar, nil
   250  	}
   251  	if len(po.Items) > 1 {
   252  		return nil, fmt.Errorf("only one 'proxy' block allowed per task")
   253  	}
   254  
   255  	r, err := parseProxy(po.Items[0])
   256  	if err != nil {
   257  		return nil, fmt.Errorf("proxy, %v", err)
   258  	}
   259  	sidecar.Proxy = r
   260  
   261  	return &sidecar, nil
   262  }
   263  
   264  func parseSidecarTask(item *ast.ObjectItem) (*api.SidecarTask, error) {
   265  	// We need this later
   266  	var listVal *ast.ObjectList
   267  	if ot, ok := item.Val.(*ast.ObjectType); ok {
   268  		listVal = ot.List
   269  	} else {
   270  		return nil, fmt.Errorf("should be an object")
   271  	}
   272  
   273  	// Check for invalid keys
   274  	valid := []string{
   275  		"config",
   276  		"driver",
   277  		"env",
   278  		"kill_timeout",
   279  		"logs",
   280  		"meta",
   281  		"resources",
   282  		"shutdown_delay",
   283  		"user",
   284  		"kill_signal",
   285  	}
   286  	if err := helper.CheckHCLKeys(listVal, valid); err != nil {
   287  		return nil, err
   288  	}
   289  
   290  	task, err := parseTask(item)
   291  	if err != nil {
   292  		return nil, err
   293  	}
   294  
   295  	sidecarTask := &api.SidecarTask{
   296  		Name:        task.Name,
   297  		Driver:      task.Driver,
   298  		User:        task.User,
   299  		Config:      task.Config,
   300  		Env:         task.Env,
   301  		Resources:   task.Resources,
   302  		Meta:        task.Meta,
   303  		KillTimeout: task.KillTimeout,
   304  		LogConfig:   task.LogConfig,
   305  		KillSignal:  task.KillSignal,
   306  	}
   307  
   308  	// Parse ShutdownDelay seperatly to get pointer
   309  	var m map[string]interface{}
   310  	if err := hcl.DecodeObject(&m, item.Val); err != nil {
   311  		return nil, err
   312  	}
   313  
   314  	m = map[string]interface{}{
   315  		"shutdown_delay": m["shutdown_delay"],
   316  	}
   317  
   318  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   319  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   320  		WeaklyTypedInput: true,
   321  		Result:           sidecarTask,
   322  	})
   323  
   324  	if err != nil {
   325  		return nil, err
   326  	}
   327  	if err := dec.Decode(m); err != nil {
   328  		return nil, err
   329  	}
   330  	return sidecarTask, nil
   331  }
   332  
   333  func parseProxy(o *ast.ObjectItem) (*api.ConsulProxy, error) {
   334  	valid := []string{
   335  		"local_service_address",
   336  		"local_service_port",
   337  		"upstreams",
   338  		"config",
   339  	}
   340  
   341  	if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
   342  		return nil, multierror.Prefix(err, "proxy ->")
   343  	}
   344  
   345  	var proxy api.ConsulProxy
   346  
   347  	var listVal *ast.ObjectList
   348  	if ot, ok := o.Val.(*ast.ObjectType); ok {
   349  		listVal = ot.List
   350  	} else {
   351  		return nil, fmt.Errorf("proxy: should be an object")
   352  	}
   353  
   354  	// Parse the proxy
   355  	uo := listVal.Filter("upstreams")
   356  	proxy.Upstreams = make([]*api.ConsulUpstream, len(uo.Items))
   357  	for i := range uo.Items {
   358  		u, err := parseUpstream(uo.Items[i])
   359  		if err != nil {
   360  			return nil, err
   361  		}
   362  
   363  		proxy.Upstreams[i] = u
   364  	}
   365  
   366  	// If we have config, then parse that
   367  	if o := listVal.Filter("config"); len(o.Items) > 1 {
   368  		return nil, fmt.Errorf("only 1 meta object supported")
   369  	} else if len(o.Items) == 1 {
   370  		var mSlice []map[string]interface{}
   371  		if err := hcl.DecodeObject(&mSlice, o.Items[0].Val); err != nil {
   372  			return nil, err
   373  		}
   374  
   375  		if len(mSlice) > 1 {
   376  			return nil, fmt.Errorf("only 1 meta object supported")
   377  		}
   378  
   379  		m := mSlice[0]
   380  
   381  		if err := mapstructure.WeakDecode(m, &proxy.Config); err != nil {
   382  			return nil, err
   383  		}
   384  
   385  		proxy.Config = flattenMapSlice(proxy.Config)
   386  	}
   387  
   388  	return &proxy, nil
   389  }
   390  
   391  func parseUpstream(uo *ast.ObjectItem) (*api.ConsulUpstream, error) {
   392  	valid := []string{
   393  		"destination_name",
   394  		"local_bind_port",
   395  	}
   396  
   397  	if err := helper.CheckHCLKeys(uo.Val, valid); err != nil {
   398  		return nil, multierror.Prefix(err, "upstream ->")
   399  	}
   400  
   401  	var upstream api.ConsulUpstream
   402  	var m map[string]interface{}
   403  	if err := hcl.DecodeObject(&m, uo.Val); err != nil {
   404  		return nil, err
   405  	}
   406  
   407  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   408  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   409  		WeaklyTypedInput: true,
   410  		Result:           &upstream,
   411  	})
   412  	if err != nil {
   413  		return nil, err
   414  	}
   415  
   416  	if err := dec.Decode(m); err != nil {
   417  		return nil, err
   418  	}
   419  
   420  	return &upstream, nil
   421  }
   422  func parseChecks(service *api.Service, checkObjs *ast.ObjectList) error {
   423  	service.Checks = make([]api.ServiceCheck, len(checkObjs.Items))
   424  	for idx, co := range checkObjs.Items {
   425  		// Check for invalid keys
   426  		valid := []string{
   427  			"name",
   428  			"type",
   429  			"interval",
   430  			"timeout",
   431  			"path",
   432  			"protocol",
   433  			"port",
   434  			"command",
   435  			"args",
   436  			"initial_status",
   437  			"tls_skip_verify",
   438  			"header",
   439  			"method",
   440  			"check_restart",
   441  			"address_mode",
   442  			"grpc_service",
   443  			"grpc_use_tls",
   444  			"task",
   445  		}
   446  		if err := helper.CheckHCLKeys(co.Val, valid); err != nil {
   447  			return multierror.Prefix(err, "check ->")
   448  		}
   449  
   450  		var check api.ServiceCheck
   451  		var cm map[string]interface{}
   452  		if err := hcl.DecodeObject(&cm, co.Val); err != nil {
   453  			return err
   454  		}
   455  
   456  		// HCL allows repeating stanzas so merge 'header' into a single
   457  		// map[string][]string.
   458  		if headerI, ok := cm["header"]; ok {
   459  			headerRaw, ok := headerI.([]map[string]interface{})
   460  			if !ok {
   461  				return fmt.Errorf("check -> header -> expected a []map[string][]string but found %T", headerI)
   462  			}
   463  			m := map[string][]string{}
   464  			for _, rawm := range headerRaw {
   465  				for k, vI := range rawm {
   466  					vs, ok := vI.([]interface{})
   467  					if !ok {
   468  						return fmt.Errorf("check -> header -> %q expected a []string but found %T", k, vI)
   469  					}
   470  					for _, vI := range vs {
   471  						v, ok := vI.(string)
   472  						if !ok {
   473  							return fmt.Errorf("check -> header -> %q expected a string but found %T", k, vI)
   474  						}
   475  						m[k] = append(m[k], v)
   476  					}
   477  				}
   478  			}
   479  
   480  			check.Header = m
   481  
   482  			// Remove "header" as it has been parsed
   483  			delete(cm, "header")
   484  		}
   485  
   486  		delete(cm, "check_restart")
   487  
   488  		dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   489  			DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   490  			WeaklyTypedInput: true,
   491  			Result:           &check,
   492  		})
   493  		if err != nil {
   494  			return err
   495  		}
   496  		if err := dec.Decode(cm); err != nil {
   497  			return err
   498  		}
   499  
   500  		// Filter check_restart
   501  		var checkRestartList *ast.ObjectList
   502  		if ot, ok := co.Val.(*ast.ObjectType); ok {
   503  			checkRestartList = ot.List
   504  		} else {
   505  			return fmt.Errorf("check_restart '%s': should be an object", check.Name)
   506  		}
   507  
   508  		if cro := checkRestartList.Filter("check_restart"); len(cro.Items) > 0 {
   509  			if len(cro.Items) > 1 {
   510  				return fmt.Errorf("check_restart '%s': cannot have more than 1 check_restart", check.Name)
   511  			}
   512  			cr, err := parseCheckRestart(cro.Items[0])
   513  			if err != nil {
   514  				return multierror.Prefix(err, fmt.Sprintf("check: '%s',", check.Name))
   515  			}
   516  			check.CheckRestart = cr
   517  		}
   518  
   519  		service.Checks[idx] = check
   520  	}
   521  
   522  	return nil
   523  }
   524  
   525  func parseCheckRestart(cro *ast.ObjectItem) (*api.CheckRestart, error) {
   526  	valid := []string{
   527  		"limit",
   528  		"grace",
   529  		"ignore_warnings",
   530  	}
   531  
   532  	if err := helper.CheckHCLKeys(cro.Val, valid); err != nil {
   533  		return nil, multierror.Prefix(err, "check_restart ->")
   534  	}
   535  
   536  	var checkRestart api.CheckRestart
   537  	var crm map[string]interface{}
   538  	if err := hcl.DecodeObject(&crm, cro.Val); err != nil {
   539  		return nil, err
   540  	}
   541  
   542  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   543  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   544  		WeaklyTypedInput: true,
   545  		Result:           &checkRestart,
   546  	})
   547  	if err != nil {
   548  		return nil, err
   549  	}
   550  	if err := dec.Decode(crm); err != nil {
   551  		return nil, err
   552  	}
   553  
   554  	return &checkRestart, nil
   555  }