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

     1  package jobspec
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"time"
     7  
     8  	multierror "github.com/hashicorp/go-multierror"
     9  	"github.com/hashicorp/hcl"
    10  	"github.com/hashicorp/hcl/hcl/ast"
    11  	"github.com/hashicorp/nomad/api"
    12  	"github.com/mitchellh/mapstructure"
    13  )
    14  
    15  var (
    16  	commonTaskKeys = []string{
    17  		"driver",
    18  		"user",
    19  		"config",
    20  		"env",
    21  		"resources",
    22  		"meta",
    23  		"logs",
    24  		"kill_timeout",
    25  		"shutdown_delay",
    26  		"kill_signal",
    27  		"scaling",
    28  	}
    29  
    30  	normalTaskKeys = append(commonTaskKeys,
    31  		"artifact",
    32  		"constraint",
    33  		"affinity",
    34  		"dispatch_payload",
    35  		"lifecycle",
    36  		"leader",
    37  		"restart",
    38  		"service",
    39  		"template",
    40  		"vault",
    41  		"kind",
    42  		"volume_mount",
    43  		"csi_plugin",
    44  	)
    45  
    46  	sidecarTaskKeys = append(commonTaskKeys,
    47  		"name",
    48  	)
    49  )
    50  
    51  func parseTasks(result *[]*api.Task, list *ast.ObjectList) error {
    52  	list = list.Children()
    53  	if len(list.Items) == 0 {
    54  		return nil
    55  	}
    56  
    57  	// Go through each object and turn it into an actual result.
    58  	seen := make(map[string]struct{})
    59  	for _, item := range list.Items {
    60  		n := item.Keys[0].Token.Value().(string)
    61  
    62  		// Make sure we haven't already found this
    63  		if _, ok := seen[n]; ok {
    64  			return fmt.Errorf("task '%s' defined more than once", n)
    65  		}
    66  		seen[n] = struct{}{}
    67  
    68  		t, err := parseTask(item, normalTaskKeys)
    69  		if err != nil {
    70  			return multierror.Prefix(err, fmt.Sprintf("'%s',", n))
    71  		}
    72  
    73  		t.Name = n
    74  
    75  		*result = append(*result, t)
    76  	}
    77  
    78  	return nil
    79  }
    80  
    81  func parseTask(item *ast.ObjectItem, keys []string) (*api.Task, error) {
    82  	// We need this later
    83  	var listVal *ast.ObjectList
    84  	if ot, ok := item.Val.(*ast.ObjectType); ok {
    85  		listVal = ot.List
    86  	} else {
    87  		return nil, fmt.Errorf("should be an object")
    88  	}
    89  
    90  	// Check for invalid keys
    91  	if err := checkHCLKeys(listVal, keys); err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	var m map[string]interface{}
    96  	if err := hcl.DecodeObject(&m, item.Val); err != nil {
    97  		return nil, err
    98  	}
    99  	delete(m, "artifact")
   100  	delete(m, "config")
   101  	delete(m, "constraint")
   102  	delete(m, "affinity")
   103  	delete(m, "dispatch_payload")
   104  	delete(m, "lifecycle")
   105  	delete(m, "env")
   106  	delete(m, "logs")
   107  	delete(m, "meta")
   108  	delete(m, "resources")
   109  	delete(m, "restart")
   110  	delete(m, "service")
   111  	delete(m, "template")
   112  	delete(m, "vault")
   113  	delete(m, "volume_mount")
   114  	delete(m, "csi_plugin")
   115  	delete(m, "scaling")
   116  
   117  	// Build the task
   118  	var t api.Task
   119  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   120  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   121  		WeaklyTypedInput: true,
   122  		Result:           &t,
   123  	})
   124  
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  	if err := dec.Decode(m); err != nil {
   129  		return nil, err
   130  	}
   131  
   132  	// If we have env, then parse them
   133  	if o := listVal.Filter("env"); len(o.Items) > 0 {
   134  		for _, o := range o.Elem().Items {
   135  			var m map[string]interface{}
   136  			if err := hcl.DecodeObject(&m, o.Val); err != nil {
   137  				return nil, err
   138  			}
   139  			if err := mapstructure.WeakDecode(m, &t.Env); err != nil {
   140  				return nil, err
   141  			}
   142  		}
   143  	}
   144  
   145  	if o := listVal.Filter("service"); len(o.Items) > 0 {
   146  		services, err := parseServices(o)
   147  		if err != nil {
   148  			return nil, err
   149  		}
   150  
   151  		t.Services = services
   152  	}
   153  
   154  	if o := listVal.Filter("csi_plugin"); len(o.Items) > 0 {
   155  		if len(o.Items) != 1 {
   156  			return nil, fmt.Errorf("csi_plugin -> Expected single stanza, got %d", len(o.Items))
   157  		}
   158  		i := o.Elem().Items[0]
   159  
   160  		var m map[string]interface{}
   161  		if err := hcl.DecodeObject(&m, i.Val); err != nil {
   162  			return nil, err
   163  		}
   164  
   165  		var cfg api.TaskCSIPluginConfig
   166  		if err := mapstructure.WeakDecode(m, &cfg); err != nil {
   167  			return nil, err
   168  		}
   169  
   170  		t.CSIPluginConfig = &cfg
   171  	}
   172  
   173  	// If we have config, then parse that
   174  	if o := listVal.Filter("config"); len(o.Items) > 0 {
   175  		for _, o := range o.Elem().Items {
   176  			var m map[string]interface{}
   177  			if err := hcl.DecodeObject(&m, o.Val); err != nil {
   178  				return nil, err
   179  			}
   180  
   181  			if err := mapstructure.WeakDecode(m, &t.Config); err != nil {
   182  				return nil, err
   183  			}
   184  		}
   185  	}
   186  
   187  	// Parse constraints
   188  	if o := listVal.Filter("constraint"); len(o.Items) > 0 {
   189  		if err := parseConstraints(&t.Constraints, o); err != nil {
   190  			return nil, multierror.Prefix(err, "constraint ->")
   191  		}
   192  	}
   193  
   194  	// Parse affinities
   195  	if o := listVal.Filter("affinity"); len(o.Items) > 0 {
   196  		if err := parseAffinities(&t.Affinities, o); err != nil {
   197  			return nil, multierror.Prefix(err, "affinity ->")
   198  		}
   199  	}
   200  
   201  	// Parse out meta fields. These are in HCL as a list so we need
   202  	// to iterate over them and merge them.
   203  	if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 {
   204  		for _, o := range metaO.Elem().Items {
   205  			var m map[string]interface{}
   206  			if err := hcl.DecodeObject(&m, o.Val); err != nil {
   207  				return nil, err
   208  			}
   209  			if err := mapstructure.WeakDecode(m, &t.Meta); err != nil {
   210  				return nil, err
   211  			}
   212  		}
   213  	}
   214  
   215  	// Parse volume mounts
   216  	if o := listVal.Filter("volume_mount"); len(o.Items) > 0 {
   217  		if err := parseVolumeMounts(&t.VolumeMounts, o); err != nil {
   218  			return nil, multierror.Prefix(err, "volume_mount ->")
   219  		}
   220  	}
   221  
   222  	// If we have resources, then parse that
   223  	if o := listVal.Filter("resources"); len(o.Items) > 0 {
   224  		var r api.Resources
   225  		if err := parseResources(&r, o); err != nil {
   226  			return nil, multierror.Prefix(err, "resources ->")
   227  		}
   228  
   229  		t.Resources = &r
   230  	}
   231  
   232  	// Parse restart policy
   233  	if o := listVal.Filter("restart"); len(o.Items) > 0 {
   234  		if err := parseRestartPolicy(&t.RestartPolicy, o); err != nil {
   235  			return nil, multierror.Prefix(err, "restart ->")
   236  		}
   237  	}
   238  
   239  	// If we have logs then parse that
   240  	if o := listVal.Filter("logs"); len(o.Items) > 0 {
   241  		if len(o.Items) > 1 {
   242  			return nil, fmt.Errorf("only one logs block is allowed in a Task. Number of logs block found: %d", len(o.Items))
   243  		}
   244  		var m map[string]interface{}
   245  		logsBlock := o.Items[0]
   246  
   247  		// Check for invalid keys
   248  		valid := []string{
   249  			"max_files",
   250  			"max_file_size",
   251  		}
   252  		if err := checkHCLKeys(logsBlock.Val, valid); err != nil {
   253  			return nil, multierror.Prefix(err, "logs ->")
   254  		}
   255  
   256  		if err := hcl.DecodeObject(&m, logsBlock.Val); err != nil {
   257  			return nil, err
   258  		}
   259  
   260  		var log api.LogConfig
   261  		if err := mapstructure.WeakDecode(m, &log); err != nil {
   262  			return nil, err
   263  		}
   264  
   265  		t.LogConfig = &log
   266  	}
   267  
   268  	// Parse artifacts
   269  	if o := listVal.Filter("artifact"); len(o.Items) > 0 {
   270  		if err := parseArtifacts(&t.Artifacts, o); err != nil {
   271  			return nil, multierror.Prefix(err, "artifact ->")
   272  		}
   273  	}
   274  
   275  	// Parse templates
   276  	if o := listVal.Filter("template"); len(o.Items) > 0 {
   277  		if err := parseTemplates(&t.Templates, o); err != nil {
   278  			return nil, multierror.Prefix(err, "template ->")
   279  		}
   280  	}
   281  
   282  	// Parse scaling policies
   283  	if o := listVal.Filter("scaling"); len(o.Items) > 0 {
   284  		if err := parseTaskScalingPolicies(&t.ScalingPolicies, o); err != nil {
   285  			return nil, err
   286  		}
   287  	}
   288  
   289  	// If we have a vault block, then parse that
   290  	if o := listVal.Filter("vault"); len(o.Items) > 0 {
   291  		v := &api.Vault{
   292  			Env:        boolToPtr(true),
   293  			ChangeMode: stringToPtr("restart"),
   294  		}
   295  
   296  		if err := parseVault(v, o); err != nil {
   297  			return nil, multierror.Prefix(err, "vault ->")
   298  		}
   299  
   300  		t.Vault = v
   301  	}
   302  
   303  	// If we have a dispatch_payload block parse that
   304  	if o := listVal.Filter("dispatch_payload"); len(o.Items) > 0 {
   305  		if len(o.Items) > 1 {
   306  			return nil, fmt.Errorf("only one dispatch_payload block is allowed in a task. Number of dispatch_payload blocks found: %d", len(o.Items))
   307  		}
   308  		var m map[string]interface{}
   309  		dispatchBlock := o.Items[0]
   310  
   311  		// Check for invalid keys
   312  		valid := []string{
   313  			"file",
   314  		}
   315  		if err := checkHCLKeys(dispatchBlock.Val, valid); err != nil {
   316  			return nil, multierror.Prefix(err, "dispatch_payload ->")
   317  		}
   318  
   319  		if err := hcl.DecodeObject(&m, dispatchBlock.Val); err != nil {
   320  			return nil, err
   321  		}
   322  
   323  		t.DispatchPayload = &api.DispatchPayloadConfig{}
   324  		if err := mapstructure.WeakDecode(m, t.DispatchPayload); err != nil {
   325  			return nil, err
   326  		}
   327  	}
   328  
   329  	// If we have a lifecycle block parse that
   330  	if o := listVal.Filter("lifecycle"); len(o.Items) > 0 {
   331  		if len(o.Items) > 1 {
   332  			return nil, fmt.Errorf("only one lifecycle block is allowed in a task. Number of lifecycle blocks found: %d", len(o.Items))
   333  		}
   334  
   335  		var m map[string]interface{}
   336  		lifecycleBlock := o.Items[0]
   337  
   338  		// Check for invalid keys
   339  		valid := []string{
   340  			"hook",
   341  			"sidecar",
   342  		}
   343  		if err := checkHCLKeys(lifecycleBlock.Val, valid); err != nil {
   344  			return nil, multierror.Prefix(err, "lifecycle ->")
   345  		}
   346  
   347  		if err := hcl.DecodeObject(&m, lifecycleBlock.Val); err != nil {
   348  			return nil, err
   349  		}
   350  
   351  		t.Lifecycle = &api.TaskLifecycle{}
   352  		if err := mapstructure.WeakDecode(m, t.Lifecycle); err != nil {
   353  			return nil, err
   354  		}
   355  	}
   356  	return &t, nil
   357  }
   358  
   359  func parseArtifacts(result *[]*api.TaskArtifact, list *ast.ObjectList) error {
   360  	for _, o := range list.Elem().Items {
   361  		// Check for invalid keys
   362  		valid := []string{
   363  			"source",
   364  			"options",
   365  			"mode",
   366  			"destination",
   367  		}
   368  		if err := checkHCLKeys(o.Val, valid); err != nil {
   369  			return err
   370  		}
   371  
   372  		var m map[string]interface{}
   373  		if err := hcl.DecodeObject(&m, o.Val); err != nil {
   374  			return err
   375  		}
   376  
   377  		delete(m, "options")
   378  
   379  		var ta api.TaskArtifact
   380  		if err := mapstructure.WeakDecode(m, &ta); err != nil {
   381  			return err
   382  		}
   383  
   384  		var optionList *ast.ObjectList
   385  		if ot, ok := o.Val.(*ast.ObjectType); ok {
   386  			optionList = ot.List
   387  		} else {
   388  			return fmt.Errorf("artifact should be an object")
   389  		}
   390  
   391  		if oo := optionList.Filter("options"); len(oo.Items) > 0 {
   392  			options := make(map[string]string)
   393  			if err := parseArtifactOption(options, oo); err != nil {
   394  				return multierror.Prefix(err, "options: ")
   395  			}
   396  			ta.GetterOptions = options
   397  		}
   398  
   399  		*result = append(*result, &ta)
   400  	}
   401  
   402  	return nil
   403  }
   404  
   405  func parseArtifactOption(result map[string]string, list *ast.ObjectList) error {
   406  	list = list.Elem()
   407  	if len(list.Items) > 1 {
   408  		return fmt.Errorf("only one 'options' block allowed per artifact")
   409  	}
   410  
   411  	// Get our resource object
   412  	o := list.Items[0]
   413  
   414  	var m map[string]interface{}
   415  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
   416  		return err
   417  	}
   418  
   419  	if err := mapstructure.WeakDecode(m, &result); err != nil {
   420  		return err
   421  	}
   422  
   423  	return nil
   424  }
   425  
   426  func parseTemplates(result *[]*api.Template, list *ast.ObjectList) error {
   427  	for _, o := range list.Elem().Items {
   428  		// Check for invalid keys
   429  		valid := []string{
   430  			"change_mode",
   431  			"change_signal",
   432  			"data",
   433  			"destination",
   434  			"left_delimiter",
   435  			"perms",
   436  			"right_delimiter",
   437  			"source",
   438  			"splay",
   439  			"env",
   440  			"vault_grace", //COMPAT(0.12) not used; emits warning in 0.11.
   441  		}
   442  		if err := checkHCLKeys(o.Val, valid); err != nil {
   443  			return err
   444  		}
   445  
   446  		var m map[string]interface{}
   447  		if err := hcl.DecodeObject(&m, o.Val); err != nil {
   448  			return err
   449  		}
   450  
   451  		templ := &api.Template{
   452  			ChangeMode: stringToPtr("restart"),
   453  			Splay:      timeToPtr(5 * time.Second),
   454  			Perms:      stringToPtr("0644"),
   455  		}
   456  
   457  		dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   458  			DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   459  			WeaklyTypedInput: true,
   460  			Result:           templ,
   461  		})
   462  		if err != nil {
   463  			return err
   464  		}
   465  		if err := dec.Decode(m); err != nil {
   466  			return err
   467  		}
   468  
   469  		*result = append(*result, templ)
   470  	}
   471  
   472  	return nil
   473  }
   474  
   475  func parseTaskScalingPolicies(result *[]*api.ScalingPolicy, list *ast.ObjectList) error {
   476  	if len(list.Items) == 0 {
   477  		return nil
   478  	}
   479  
   480  	errPrefix := "scaling ->"
   481  	// Go through each object and turn it into an actual result.
   482  	seen := make(map[string]bool)
   483  	for _, item := range list.Items {
   484  		if l := len(item.Keys); l == 0 {
   485  			return multierror.Prefix(fmt.Errorf("task scaling policy missing name"), errPrefix)
   486  		} else if l > 1 {
   487  			return multierror.Prefix(fmt.Errorf("task scaling policy should only have one name"), errPrefix)
   488  		}
   489  		n := item.Keys[0].Token.Value().(string)
   490  		errPrefix = fmt.Sprintf("scaling[%v] ->", n)
   491  
   492  		var policyType string
   493  		switch strings.ToLower(n) {
   494  		case "cpu":
   495  			policyType = "vertical_cpu"
   496  		case "mem":
   497  			policyType = "vertical_mem"
   498  		default:
   499  			return multierror.Prefix(fmt.Errorf(`scaling policy name must be "cpu" or "mem"`), errPrefix)
   500  		}
   501  
   502  		// Make sure we haven't already found this
   503  		if seen[n] {
   504  			return multierror.Prefix(fmt.Errorf("scaling policy cannot be defined more than once"), errPrefix)
   505  		}
   506  		seen[n] = true
   507  
   508  		p, err := parseScalingPolicy(item)
   509  		if err != nil {
   510  			return multierror.Prefix(err, errPrefix)
   511  		}
   512  
   513  		if p.Type == "" {
   514  			p.Type = policyType
   515  		} else if p.Type != policyType {
   516  			return multierror.Prefix(fmt.Errorf("policy had invalid 'type': %q", p.Type), errPrefix)
   517  		}
   518  
   519  		*result = append(*result, p)
   520  	}
   521  
   522  	return nil
   523  }
   524  
   525  func parseResources(result *api.Resources, list *ast.ObjectList) error {
   526  	list = list.Elem()
   527  	if len(list.Items) == 0 {
   528  		return nil
   529  	}
   530  	if len(list.Items) > 1 {
   531  		return fmt.Errorf("only one 'resource' block allowed per task")
   532  	}
   533  
   534  	// Get our resource object
   535  	o := list.Items[0]
   536  
   537  	// We need this later
   538  	var listVal *ast.ObjectList
   539  	if ot, ok := o.Val.(*ast.ObjectType); ok {
   540  		listVal = ot.List
   541  	} else {
   542  		return fmt.Errorf("resource: should be an object")
   543  	}
   544  
   545  	// Check for invalid keys
   546  	valid := []string{
   547  		"cpu",
   548  		"iops", // COMPAT(0.10): Remove after one release to allow it to be removed from jobspecs
   549  		"disk",
   550  		"memory",
   551  		"network",
   552  		"device",
   553  	}
   554  	if err := checkHCLKeys(listVal, valid); err != nil {
   555  		return multierror.Prefix(err, "resources ->")
   556  	}
   557  
   558  	var m map[string]interface{}
   559  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
   560  		return err
   561  	}
   562  	delete(m, "network")
   563  	delete(m, "device")
   564  
   565  	if err := mapstructure.WeakDecode(m, result); err != nil {
   566  		return err
   567  	}
   568  
   569  	// Parse the network resources
   570  	if o := listVal.Filter("network"); len(o.Items) > 0 {
   571  		r, err := ParseNetwork(o)
   572  		if err != nil {
   573  			return fmt.Errorf("resource, %v", err)
   574  		}
   575  		result.Networks = []*api.NetworkResource{r}
   576  	}
   577  
   578  	// Parse the device resources
   579  	if o := listVal.Filter("device"); len(o.Items) > 0 {
   580  		result.Devices = make([]*api.RequestedDevice, len(o.Items))
   581  		for idx, do := range o.Items {
   582  			if l := len(do.Keys); l == 0 {
   583  				return multierror.Prefix(fmt.Errorf("missing device name"), fmt.Sprintf("resources, device[%d]->", idx))
   584  			} else if l > 1 {
   585  				return multierror.Prefix(fmt.Errorf("only one name may be specified"), fmt.Sprintf("resources, device[%d]->", idx))
   586  			}
   587  			name := do.Keys[0].Token.Value().(string)
   588  
   589  			// Value should be an object
   590  			var listVal *ast.ObjectList
   591  			if ot, ok := do.Val.(*ast.ObjectType); ok {
   592  				listVal = ot.List
   593  			} else {
   594  				return fmt.Errorf("device should be an object")
   595  			}
   596  
   597  			// Check for invalid keys
   598  			valid := []string{
   599  				"name",
   600  				"count",
   601  				"affinity",
   602  				"constraint",
   603  			}
   604  			if err := checkHCLKeys(do.Val, valid); err != nil {
   605  				return multierror.Prefix(err, fmt.Sprintf("resources, device[%d]->", idx))
   606  			}
   607  
   608  			// Set the name
   609  			var r api.RequestedDevice
   610  			r.Name = name
   611  
   612  			var m map[string]interface{}
   613  			if err := hcl.DecodeObject(&m, do.Val); err != nil {
   614  				return err
   615  			}
   616  
   617  			delete(m, "constraint")
   618  			delete(m, "affinity")
   619  
   620  			if err := mapstructure.WeakDecode(m, &r); err != nil {
   621  				return err
   622  			}
   623  
   624  			// Parse constraints
   625  			if o := listVal.Filter("constraint"); len(o.Items) > 0 {
   626  				if err := parseConstraints(&r.Constraints, o); err != nil {
   627  					return multierror.Prefix(err, "constraint ->")
   628  				}
   629  			}
   630  
   631  			// Parse affinities
   632  			if o := listVal.Filter("affinity"); len(o.Items) > 0 {
   633  				if err := parseAffinities(&r.Affinities, o); err != nil {
   634  					return multierror.Prefix(err, "affinity ->")
   635  				}
   636  			}
   637  
   638  			result.Devices[idx] = &r
   639  		}
   640  	}
   641  
   642  	return nil
   643  }
   644  
   645  func parseVolumeMounts(out *[]*api.VolumeMount, list *ast.ObjectList) error {
   646  	mounts := make([]*api.VolumeMount, len(list.Items))
   647  
   648  	for i, item := range list.Items {
   649  		valid := []string{
   650  			"volume",
   651  			"read_only",
   652  			"destination",
   653  			"propagation_mode",
   654  		}
   655  		if err := checkHCLKeys(item.Val, valid); err != nil {
   656  			return err
   657  		}
   658  
   659  		var m map[string]interface{}
   660  		if err := hcl.DecodeObject(&m, item.Val); err != nil {
   661  			return err
   662  		}
   663  
   664  		var result api.VolumeMount
   665  		dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   666  			WeaklyTypedInput: true,
   667  			Result:           &result,
   668  		})
   669  		if err != nil {
   670  			return err
   671  		}
   672  		if err := dec.Decode(m); err != nil {
   673  			return err
   674  		}
   675  
   676  		mounts[i] = &result
   677  	}
   678  
   679  	*out = mounts
   680  	return nil
   681  }