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