github.com/hspak/nomad@v0.7.2-0.20180309000617-bc4ae22a39a5/jobspec/parse.go (about)

     1  package jobspec
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"regexp"
    10  	"strconv"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/hashicorp/go-multierror"
    15  	"github.com/hashicorp/hcl"
    16  	"github.com/hashicorp/hcl/hcl/ast"
    17  	"github.com/hashicorp/nomad/api"
    18  	"github.com/hashicorp/nomad/helper"
    19  	"github.com/hashicorp/nomad/nomad/structs"
    20  	"github.com/mitchellh/mapstructure"
    21  )
    22  
    23  var reDynamicPorts = regexp.MustCompile("^[a-zA-Z0-9_]+$")
    24  var errPortLabel = fmt.Errorf("Port label does not conform to naming requirements %s", reDynamicPorts.String())
    25  
    26  // Parse parses the job spec from the given io.Reader.
    27  //
    28  // Due to current internal limitations, the entire contents of the
    29  // io.Reader will be copied into memory first before parsing.
    30  func Parse(r io.Reader) (*api.Job, error) {
    31  	// Copy the reader into an in-memory buffer first since HCL requires it.
    32  	var buf bytes.Buffer
    33  	if _, err := io.Copy(&buf, r); err != nil {
    34  		return nil, err
    35  	}
    36  
    37  	// Parse the buffer
    38  	root, err := hcl.Parse(buf.String())
    39  	if err != nil {
    40  		return nil, fmt.Errorf("error parsing: %s", err)
    41  	}
    42  	buf.Reset()
    43  
    44  	// Top-level item should be a list
    45  	list, ok := root.Node.(*ast.ObjectList)
    46  	if !ok {
    47  		return nil, fmt.Errorf("error parsing: root should be an object")
    48  	}
    49  
    50  	// Check for invalid keys
    51  	valid := []string{
    52  		"job",
    53  	}
    54  	if err := helper.CheckHCLKeys(list, valid); err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	var job api.Job
    59  
    60  	// Parse the job out
    61  	matches := list.Filter("job")
    62  	if len(matches.Items) == 0 {
    63  		return nil, fmt.Errorf("'job' stanza not found")
    64  	}
    65  	if err := parseJob(&job, matches); err != nil {
    66  		return nil, fmt.Errorf("error parsing 'job': %s", err)
    67  	}
    68  
    69  	return &job, nil
    70  }
    71  
    72  // ParseFile parses the given path as a job spec.
    73  func ParseFile(path string) (*api.Job, error) {
    74  	path, err := filepath.Abs(path)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  
    79  	f, err := os.Open(path)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  	defer f.Close()
    84  
    85  	return Parse(f)
    86  }
    87  
    88  func parseJob(result *api.Job, list *ast.ObjectList) error {
    89  	if len(list.Items) != 1 {
    90  		return fmt.Errorf("only one 'job' block allowed")
    91  	}
    92  	list = list.Children()
    93  	if len(list.Items) != 1 {
    94  		return fmt.Errorf("'job' block missing name")
    95  	}
    96  
    97  	// Get our job object
    98  	obj := list.Items[0]
    99  
   100  	// Decode the full thing into a map[string]interface for ease
   101  	var m map[string]interface{}
   102  	if err := hcl.DecodeObject(&m, obj.Val); err != nil {
   103  		return err
   104  	}
   105  	delete(m, "constraint")
   106  	delete(m, "meta")
   107  	delete(m, "update")
   108  	delete(m, "periodic")
   109  	delete(m, "vault")
   110  	delete(m, "parameterized")
   111  	delete(m, "reschedule")
   112  
   113  	// Set the ID and name to the object key
   114  	result.ID = helper.StringToPtr(obj.Keys[0].Token.Value().(string))
   115  	result.Name = helper.StringToPtr(*result.ID)
   116  
   117  	// Decode the rest
   118  	if err := mapstructure.WeakDecode(m, result); err != nil {
   119  		return err
   120  	}
   121  
   122  	// Value should be an object
   123  	var listVal *ast.ObjectList
   124  	if ot, ok := obj.Val.(*ast.ObjectType); ok {
   125  		listVal = ot.List
   126  	} else {
   127  		return fmt.Errorf("job '%s' value: should be an object", *result.ID)
   128  	}
   129  
   130  	// Check for invalid keys
   131  	valid := []string{
   132  		"all_at_once",
   133  		"constraint",
   134  		"datacenters",
   135  		"parameterized",
   136  		"group",
   137  		"id",
   138  		"meta",
   139  		"name",
   140  		"namespace",
   141  		"periodic",
   142  		"priority",
   143  		"region",
   144  		"task",
   145  		"type",
   146  		"update",
   147  		"reschedule",
   148  		"vault",
   149  		"vault_token",
   150  	}
   151  	if err := helper.CheckHCLKeys(listVal, valid); err != nil {
   152  		return multierror.Prefix(err, "job:")
   153  	}
   154  
   155  	// Parse constraints
   156  	if o := listVal.Filter("constraint"); len(o.Items) > 0 {
   157  		if err := parseConstraints(&result.Constraints, o); err != nil {
   158  			return multierror.Prefix(err, "constraint ->")
   159  		}
   160  	}
   161  
   162  	// If we have an update strategy, then parse that
   163  	if o := listVal.Filter("update"); len(o.Items) > 0 {
   164  		if err := parseUpdate(&result.Update, o); err != nil {
   165  			return multierror.Prefix(err, "update ->")
   166  		}
   167  	}
   168  
   169  	// If we have a periodic definition, then parse that
   170  	if o := listVal.Filter("periodic"); len(o.Items) > 0 {
   171  		if err := parsePeriodic(&result.Periodic, o); err != nil {
   172  			return multierror.Prefix(err, "periodic ->")
   173  		}
   174  	}
   175  
   176  	// If we have a parameterized definition, then parse that
   177  	if o := listVal.Filter("parameterized"); len(o.Items) > 0 {
   178  		if err := parseParameterizedJob(&result.ParameterizedJob, o); err != nil {
   179  			return multierror.Prefix(err, "parameterized ->")
   180  		}
   181  	}
   182  
   183  	// If we have a reschedule stanza, then parse that
   184  	if o := listVal.Filter("reschedule"); len(o.Items) > 0 {
   185  		if err := parseReschedulePolicy(&result.Reschedule, o); err != nil {
   186  			return multierror.Prefix(err, "reschedule ->")
   187  		}
   188  	}
   189  
   190  	// Parse out meta fields. These are in HCL as a list so we need
   191  	// to iterate over them and merge them.
   192  	if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 {
   193  		for _, o := range metaO.Elem().Items {
   194  			var m map[string]interface{}
   195  			if err := hcl.DecodeObject(&m, o.Val); err != nil {
   196  				return err
   197  			}
   198  			if err := mapstructure.WeakDecode(m, &result.Meta); err != nil {
   199  				return err
   200  			}
   201  		}
   202  	}
   203  
   204  	// If we have tasks outside, create TaskGroups for them
   205  	if o := listVal.Filter("task"); len(o.Items) > 0 {
   206  		var tasks []*api.Task
   207  		if err := parseTasks(*result.Name, "", &tasks, o); err != nil {
   208  			return multierror.Prefix(err, "task:")
   209  		}
   210  
   211  		result.TaskGroups = make([]*api.TaskGroup, len(tasks), len(tasks)*2)
   212  		for i, t := range tasks {
   213  			result.TaskGroups[i] = &api.TaskGroup{
   214  				Name:  helper.StringToPtr(t.Name),
   215  				Tasks: []*api.Task{t},
   216  			}
   217  		}
   218  	}
   219  
   220  	// Parse the task groups
   221  	if o := listVal.Filter("group"); len(o.Items) > 0 {
   222  		if err := parseGroups(result, o); err != nil {
   223  			return multierror.Prefix(err, "group:")
   224  		}
   225  	}
   226  
   227  	// If we have a vault block, then parse that
   228  	if o := listVal.Filter("vault"); len(o.Items) > 0 {
   229  		jobVault := &api.Vault{
   230  			Env:        helper.BoolToPtr(true),
   231  			ChangeMode: helper.StringToPtr("restart"),
   232  		}
   233  
   234  		if err := parseVault(jobVault, o); err != nil {
   235  			return multierror.Prefix(err, "vault ->")
   236  		}
   237  
   238  		// Go through the task groups/tasks and if they don't have a Vault block, set it
   239  		for _, tg := range result.TaskGroups {
   240  			for _, task := range tg.Tasks {
   241  				if task.Vault == nil {
   242  					task.Vault = jobVault
   243  				}
   244  			}
   245  		}
   246  	}
   247  
   248  	return nil
   249  }
   250  
   251  func parseGroups(result *api.Job, list *ast.ObjectList) error {
   252  	list = list.Children()
   253  	if len(list.Items) == 0 {
   254  		return nil
   255  	}
   256  
   257  	// Go through each object and turn it into an actual result.
   258  	collection := make([]*api.TaskGroup, 0, len(list.Items))
   259  	seen := make(map[string]struct{})
   260  	for _, item := range list.Items {
   261  		n := item.Keys[0].Token.Value().(string)
   262  
   263  		// Make sure we haven't already found this
   264  		if _, ok := seen[n]; ok {
   265  			return fmt.Errorf("group '%s' defined more than once", n)
   266  		}
   267  		seen[n] = struct{}{}
   268  
   269  		// We need this later
   270  		var listVal *ast.ObjectList
   271  		if ot, ok := item.Val.(*ast.ObjectType); ok {
   272  			listVal = ot.List
   273  		} else {
   274  			return fmt.Errorf("group '%s': should be an object", n)
   275  		}
   276  
   277  		// Check for invalid keys
   278  		valid := []string{
   279  			"count",
   280  			"constraint",
   281  			"restart",
   282  			"meta",
   283  			"task",
   284  			"ephemeral_disk",
   285  			"update",
   286  			"reschedule",
   287  			"vault",
   288  		}
   289  		if err := helper.CheckHCLKeys(listVal, valid); err != nil {
   290  			return multierror.Prefix(err, fmt.Sprintf("'%s' ->", n))
   291  		}
   292  
   293  		var m map[string]interface{}
   294  		if err := hcl.DecodeObject(&m, item.Val); err != nil {
   295  			return err
   296  		}
   297  		delete(m, "constraint")
   298  		delete(m, "meta")
   299  		delete(m, "task")
   300  		delete(m, "restart")
   301  		delete(m, "ephemeral_disk")
   302  		delete(m, "update")
   303  		delete(m, "vault")
   304  
   305  		// Build the group with the basic decode
   306  		var g api.TaskGroup
   307  		g.Name = helper.StringToPtr(n)
   308  		if err := mapstructure.WeakDecode(m, &g); err != nil {
   309  			return err
   310  		}
   311  
   312  		// Parse constraints
   313  		if o := listVal.Filter("constraint"); len(o.Items) > 0 {
   314  			if err := parseConstraints(&g.Constraints, o); err != nil {
   315  				return multierror.Prefix(err, fmt.Sprintf("'%s', constraint ->", n))
   316  			}
   317  		}
   318  
   319  		// Parse restart policy
   320  		if o := listVal.Filter("restart"); len(o.Items) > 0 {
   321  			if err := parseRestartPolicy(&g.RestartPolicy, o); err != nil {
   322  				return multierror.Prefix(err, fmt.Sprintf("'%s', restart ->", n))
   323  			}
   324  		}
   325  
   326  		// Parse reschedule policy
   327  		if o := listVal.Filter("reschedule"); len(o.Items) > 0 {
   328  			if err := parseReschedulePolicy(&g.ReschedulePolicy, o); err != nil {
   329  				return multierror.Prefix(err, fmt.Sprintf("'%s', reschedule ->", n))
   330  			}
   331  		}
   332  		// Parse ephemeral disk
   333  		if o := listVal.Filter("ephemeral_disk"); len(o.Items) > 0 {
   334  			g.EphemeralDisk = &api.EphemeralDisk{}
   335  			if err := parseEphemeralDisk(&g.EphemeralDisk, o); err != nil {
   336  				return multierror.Prefix(err, fmt.Sprintf("'%s', ephemeral_disk ->", n))
   337  			}
   338  		}
   339  
   340  		// If we have an update strategy, then parse that
   341  		if o := listVal.Filter("update"); len(o.Items) > 0 {
   342  			if err := parseUpdate(&g.Update, o); err != nil {
   343  				return multierror.Prefix(err, "update ->")
   344  			}
   345  		}
   346  
   347  		// Parse out meta fields. These are in HCL as a list so we need
   348  		// to iterate over them and merge them.
   349  		if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 {
   350  			for _, o := range metaO.Elem().Items {
   351  				var m map[string]interface{}
   352  				if err := hcl.DecodeObject(&m, o.Val); err != nil {
   353  					return err
   354  				}
   355  				if err := mapstructure.WeakDecode(m, &g.Meta); err != nil {
   356  					return err
   357  				}
   358  			}
   359  		}
   360  
   361  		// Parse tasks
   362  		if o := listVal.Filter("task"); len(o.Items) > 0 {
   363  			if err := parseTasks(*result.Name, *g.Name, &g.Tasks, o); err != nil {
   364  				return multierror.Prefix(err, fmt.Sprintf("'%s', task:", n))
   365  			}
   366  		}
   367  
   368  		// If we have a vault block, then parse that
   369  		if o := listVal.Filter("vault"); len(o.Items) > 0 {
   370  			tgVault := &api.Vault{
   371  				Env:        helper.BoolToPtr(true),
   372  				ChangeMode: helper.StringToPtr("restart"),
   373  			}
   374  
   375  			if err := parseVault(tgVault, o); err != nil {
   376  				return multierror.Prefix(err, fmt.Sprintf("'%s', vault ->", n))
   377  			}
   378  
   379  			// Go through the tasks and if they don't have a Vault block, set it
   380  			for _, task := range g.Tasks {
   381  				if task.Vault == nil {
   382  					task.Vault = tgVault
   383  				}
   384  			}
   385  		}
   386  
   387  		collection = append(collection, &g)
   388  	}
   389  
   390  	result.TaskGroups = append(result.TaskGroups, collection...)
   391  	return nil
   392  }
   393  
   394  func parseRestartPolicy(final **api.RestartPolicy, list *ast.ObjectList) error {
   395  	list = list.Elem()
   396  	if len(list.Items) > 1 {
   397  		return fmt.Errorf("only one 'restart' block allowed")
   398  	}
   399  
   400  	// Get our job object
   401  	obj := list.Items[0]
   402  
   403  	// Check for invalid keys
   404  	valid := []string{
   405  		"attempts",
   406  		"interval",
   407  		"delay",
   408  		"mode",
   409  	}
   410  	if err := helper.CheckHCLKeys(obj.Val, valid); err != nil {
   411  		return err
   412  	}
   413  
   414  	var m map[string]interface{}
   415  	if err := hcl.DecodeObject(&m, obj.Val); err != nil {
   416  		return err
   417  	}
   418  
   419  	var result api.RestartPolicy
   420  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   421  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   422  		WeaklyTypedInput: true,
   423  		Result:           &result,
   424  	})
   425  	if err != nil {
   426  		return err
   427  	}
   428  	if err := dec.Decode(m); err != nil {
   429  		return err
   430  	}
   431  
   432  	*final = &result
   433  	return nil
   434  }
   435  
   436  func parseReschedulePolicy(final **api.ReschedulePolicy, list *ast.ObjectList) error {
   437  	list = list.Elem()
   438  	if len(list.Items) > 1 {
   439  		return fmt.Errorf("only one 'reschedule' block allowed")
   440  	}
   441  
   442  	// Get our job object
   443  	obj := list.Items[0]
   444  
   445  	// Check for invalid keys
   446  	valid := []string{
   447  		"attempts",
   448  		"interval",
   449  	}
   450  	if err := helper.CheckHCLKeys(obj.Val, valid); err != nil {
   451  		return err
   452  	}
   453  
   454  	var m map[string]interface{}
   455  	if err := hcl.DecodeObject(&m, obj.Val); err != nil {
   456  		return err
   457  	}
   458  
   459  	var result api.ReschedulePolicy
   460  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   461  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   462  		WeaklyTypedInput: true,
   463  		Result:           &result,
   464  	})
   465  	if err != nil {
   466  		return err
   467  	}
   468  	if err := dec.Decode(m); err != nil {
   469  		return err
   470  	}
   471  
   472  	*final = &result
   473  	return nil
   474  }
   475  
   476  func parseConstraints(result *[]*api.Constraint, list *ast.ObjectList) error {
   477  	for _, o := range list.Elem().Items {
   478  		// Check for invalid keys
   479  		valid := []string{
   480  			"attribute",
   481  			"distinct_hosts",
   482  			"distinct_property",
   483  			"operator",
   484  			"regexp",
   485  			"set_contains",
   486  			"value",
   487  			"version",
   488  		}
   489  		if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
   490  			return err
   491  		}
   492  
   493  		var m map[string]interface{}
   494  		if err := hcl.DecodeObject(&m, o.Val); err != nil {
   495  			return err
   496  		}
   497  
   498  		m["LTarget"] = m["attribute"]
   499  		m["RTarget"] = m["value"]
   500  		m["Operand"] = m["operator"]
   501  
   502  		// If "version" is provided, set the operand
   503  		// to "version" and the value to the "RTarget"
   504  		if constraint, ok := m[structs.ConstraintVersion]; ok {
   505  			m["Operand"] = structs.ConstraintVersion
   506  			m["RTarget"] = constraint
   507  		}
   508  
   509  		// If "regexp" is provided, set the operand
   510  		// to "regexp" and the value to the "RTarget"
   511  		if constraint, ok := m[structs.ConstraintRegex]; ok {
   512  			m["Operand"] = structs.ConstraintRegex
   513  			m["RTarget"] = constraint
   514  		}
   515  
   516  		// If "set_contains" is provided, set the operand
   517  		// to "set_contains" and the value to the "RTarget"
   518  		if constraint, ok := m[structs.ConstraintSetContains]; ok {
   519  			m["Operand"] = structs.ConstraintSetContains
   520  			m["RTarget"] = constraint
   521  		}
   522  
   523  		if value, ok := m[structs.ConstraintDistinctHosts]; ok {
   524  			enabled, err := parseBool(value)
   525  			if err != nil {
   526  				return fmt.Errorf("distinct_hosts should be set to true or false; %v", err)
   527  			}
   528  
   529  			// If it is not enabled, skip the constraint.
   530  			if !enabled {
   531  				continue
   532  			}
   533  
   534  			m["Operand"] = structs.ConstraintDistinctHosts
   535  		}
   536  
   537  		if property, ok := m[structs.ConstraintDistinctProperty]; ok {
   538  			m["Operand"] = structs.ConstraintDistinctProperty
   539  			m["LTarget"] = property
   540  		}
   541  
   542  		// Build the constraint
   543  		var c api.Constraint
   544  		if err := mapstructure.WeakDecode(m, &c); err != nil {
   545  			return err
   546  		}
   547  		if c.Operand == "" {
   548  			c.Operand = "="
   549  		}
   550  
   551  		*result = append(*result, &c)
   552  	}
   553  
   554  	return nil
   555  }
   556  
   557  func parseEphemeralDisk(result **api.EphemeralDisk, list *ast.ObjectList) error {
   558  	list = list.Elem()
   559  	if len(list.Items) > 1 {
   560  		return fmt.Errorf("only one 'ephemeral_disk' block allowed")
   561  	}
   562  
   563  	// Get our ephemeral_disk object
   564  	obj := list.Items[0]
   565  
   566  	// Check for invalid keys
   567  	valid := []string{
   568  		"sticky",
   569  		"size",
   570  		"migrate",
   571  	}
   572  	if err := helper.CheckHCLKeys(obj.Val, valid); err != nil {
   573  		return err
   574  	}
   575  
   576  	var m map[string]interface{}
   577  	if err := hcl.DecodeObject(&m, obj.Val); err != nil {
   578  		return err
   579  	}
   580  
   581  	var ephemeralDisk api.EphemeralDisk
   582  	if err := mapstructure.WeakDecode(m, &ephemeralDisk); err != nil {
   583  		return err
   584  	}
   585  	*result = &ephemeralDisk
   586  
   587  	return nil
   588  }
   589  
   590  // parseBool takes an interface value and tries to convert it to a boolean and
   591  // returns an error if the type can't be converted.
   592  func parseBool(value interface{}) (bool, error) {
   593  	var enabled bool
   594  	var err error
   595  	switch value.(type) {
   596  	case string:
   597  		enabled, err = strconv.ParseBool(value.(string))
   598  	case bool:
   599  		enabled = value.(bool)
   600  	default:
   601  		err = fmt.Errorf("%v couldn't be converted to boolean value", value)
   602  	}
   603  
   604  	return enabled, err
   605  }
   606  
   607  func parseTasks(jobName string, taskGroupName string, result *[]*api.Task, list *ast.ObjectList) error {
   608  	list = list.Children()
   609  	if len(list.Items) == 0 {
   610  		return nil
   611  	}
   612  
   613  	// Go through each object and turn it into an actual result.
   614  	seen := make(map[string]struct{})
   615  	for _, item := range list.Items {
   616  		n := item.Keys[0].Token.Value().(string)
   617  
   618  		// Make sure we haven't already found this
   619  		if _, ok := seen[n]; ok {
   620  			return fmt.Errorf("task '%s' defined more than once", n)
   621  		}
   622  		seen[n] = struct{}{}
   623  
   624  		// We need this later
   625  		var listVal *ast.ObjectList
   626  		if ot, ok := item.Val.(*ast.ObjectType); ok {
   627  			listVal = ot.List
   628  		} else {
   629  			return fmt.Errorf("group '%s': should be an object", n)
   630  		}
   631  
   632  		// Check for invalid keys
   633  		valid := []string{
   634  			"artifact",
   635  			"config",
   636  			"constraint",
   637  			"dispatch_payload",
   638  			"driver",
   639  			"env",
   640  			"kill_timeout",
   641  			"leader",
   642  			"logs",
   643  			"meta",
   644  			"resources",
   645  			"service",
   646  			"shutdown_delay",
   647  			"template",
   648  			"user",
   649  			"vault",
   650  			"kill_signal",
   651  		}
   652  		if err := helper.CheckHCLKeys(listVal, valid); err != nil {
   653  			return multierror.Prefix(err, fmt.Sprintf("'%s' ->", n))
   654  		}
   655  
   656  		var m map[string]interface{}
   657  		if err := hcl.DecodeObject(&m, item.Val); err != nil {
   658  			return err
   659  		}
   660  		delete(m, "artifact")
   661  		delete(m, "config")
   662  		delete(m, "constraint")
   663  		delete(m, "dispatch_payload")
   664  		delete(m, "env")
   665  		delete(m, "logs")
   666  		delete(m, "meta")
   667  		delete(m, "resources")
   668  		delete(m, "service")
   669  		delete(m, "template")
   670  		delete(m, "vault")
   671  
   672  		// Build the task
   673  		var t api.Task
   674  		t.Name = n
   675  		if taskGroupName == "" {
   676  			taskGroupName = n
   677  		}
   678  		dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   679  			DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   680  			WeaklyTypedInput: true,
   681  			Result:           &t,
   682  		})
   683  
   684  		if err != nil {
   685  			return err
   686  		}
   687  		if err := dec.Decode(m); err != nil {
   688  			return err
   689  		}
   690  
   691  		// If we have env, then parse them
   692  		if o := listVal.Filter("env"); len(o.Items) > 0 {
   693  			for _, o := range o.Elem().Items {
   694  				var m map[string]interface{}
   695  				if err := hcl.DecodeObject(&m, o.Val); err != nil {
   696  					return err
   697  				}
   698  				if err := mapstructure.WeakDecode(m, &t.Env); err != nil {
   699  					return err
   700  				}
   701  			}
   702  		}
   703  
   704  		if o := listVal.Filter("service"); len(o.Items) > 0 {
   705  			if err := parseServices(jobName, taskGroupName, &t, o); err != nil {
   706  				return multierror.Prefix(err, fmt.Sprintf("'%s',", n))
   707  			}
   708  		}
   709  
   710  		// If we have config, then parse that
   711  		if o := listVal.Filter("config"); len(o.Items) > 0 {
   712  			for _, o := range o.Elem().Items {
   713  				var m map[string]interface{}
   714  				if err := hcl.DecodeObject(&m, o.Val); err != nil {
   715  					return err
   716  				}
   717  
   718  				if err := mapstructure.WeakDecode(m, &t.Config); err != nil {
   719  					return err
   720  				}
   721  			}
   722  		}
   723  
   724  		// Parse constraints
   725  		if o := listVal.Filter("constraint"); len(o.Items) > 0 {
   726  			if err := parseConstraints(&t.Constraints, o); err != nil {
   727  				return multierror.Prefix(err, fmt.Sprintf(
   728  					"'%s', constraint ->", n))
   729  			}
   730  		}
   731  
   732  		// Parse out meta fields. These are in HCL as a list so we need
   733  		// to iterate over them and merge them.
   734  		if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 {
   735  			for _, o := range metaO.Elem().Items {
   736  				var m map[string]interface{}
   737  				if err := hcl.DecodeObject(&m, o.Val); err != nil {
   738  					return err
   739  				}
   740  				if err := mapstructure.WeakDecode(m, &t.Meta); err != nil {
   741  					return err
   742  				}
   743  			}
   744  		}
   745  
   746  		// If we have resources, then parse that
   747  		if o := listVal.Filter("resources"); len(o.Items) > 0 {
   748  			var r api.Resources
   749  			if err := parseResources(&r, o); err != nil {
   750  				return multierror.Prefix(err, fmt.Sprintf("'%s',", n))
   751  			}
   752  
   753  			t.Resources = &r
   754  		}
   755  
   756  		// If we have logs then parse that
   757  		if o := listVal.Filter("logs"); len(o.Items) > 0 {
   758  			if len(o.Items) > 1 {
   759  				return fmt.Errorf("only one logs block is allowed in a Task. Number of logs block found: %d", len(o.Items))
   760  			}
   761  			var m map[string]interface{}
   762  			logsBlock := o.Items[0]
   763  
   764  			// Check for invalid keys
   765  			valid := []string{
   766  				"max_files",
   767  				"max_file_size",
   768  			}
   769  			if err := helper.CheckHCLKeys(logsBlock.Val, valid); err != nil {
   770  				return multierror.Prefix(err, fmt.Sprintf("'%s', logs ->", n))
   771  			}
   772  
   773  			if err := hcl.DecodeObject(&m, logsBlock.Val); err != nil {
   774  				return err
   775  			}
   776  
   777  			var log api.LogConfig
   778  			if err := mapstructure.WeakDecode(m, &log); err != nil {
   779  				return err
   780  			}
   781  
   782  			t.LogConfig = &log
   783  		}
   784  
   785  		// Parse artifacts
   786  		if o := listVal.Filter("artifact"); len(o.Items) > 0 {
   787  			if err := parseArtifacts(&t.Artifacts, o); err != nil {
   788  				return multierror.Prefix(err, fmt.Sprintf("'%s', artifact ->", n))
   789  			}
   790  		}
   791  
   792  		// Parse templates
   793  		if o := listVal.Filter("template"); len(o.Items) > 0 {
   794  			if err := parseTemplates(&t.Templates, o); err != nil {
   795  				return multierror.Prefix(err, fmt.Sprintf("'%s', template ->", n))
   796  			}
   797  		}
   798  
   799  		// If we have a vault block, then parse that
   800  		if o := listVal.Filter("vault"); len(o.Items) > 0 {
   801  			v := &api.Vault{
   802  				Env:        helper.BoolToPtr(true),
   803  				ChangeMode: helper.StringToPtr("restart"),
   804  			}
   805  
   806  			if err := parseVault(v, o); err != nil {
   807  				return multierror.Prefix(err, fmt.Sprintf("'%s', vault ->", n))
   808  			}
   809  
   810  			t.Vault = v
   811  		}
   812  
   813  		// If we have a dispatch_payload block parse that
   814  		if o := listVal.Filter("dispatch_payload"); len(o.Items) > 0 {
   815  			if len(o.Items) > 1 {
   816  				return fmt.Errorf("only one dispatch_payload block is allowed in a task. Number of dispatch_payload blocks found: %d", len(o.Items))
   817  			}
   818  			var m map[string]interface{}
   819  			dispatchBlock := o.Items[0]
   820  
   821  			// Check for invalid keys
   822  			valid := []string{
   823  				"file",
   824  			}
   825  			if err := helper.CheckHCLKeys(dispatchBlock.Val, valid); err != nil {
   826  				return multierror.Prefix(err, fmt.Sprintf("'%s', dispatch_payload ->", n))
   827  			}
   828  
   829  			if err := hcl.DecodeObject(&m, dispatchBlock.Val); err != nil {
   830  				return err
   831  			}
   832  
   833  			t.DispatchPayload = &api.DispatchPayloadConfig{}
   834  			if err := mapstructure.WeakDecode(m, t.DispatchPayload); err != nil {
   835  				return err
   836  			}
   837  		}
   838  
   839  		*result = append(*result, &t)
   840  	}
   841  
   842  	return nil
   843  }
   844  
   845  func parseArtifacts(result *[]*api.TaskArtifact, list *ast.ObjectList) error {
   846  	for _, o := range list.Elem().Items {
   847  		// Check for invalid keys
   848  		valid := []string{
   849  			"source",
   850  			"options",
   851  			"mode",
   852  			"destination",
   853  		}
   854  		if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
   855  			return err
   856  		}
   857  
   858  		var m map[string]interface{}
   859  		if err := hcl.DecodeObject(&m, o.Val); err != nil {
   860  			return err
   861  		}
   862  
   863  		delete(m, "options")
   864  
   865  		var ta api.TaskArtifact
   866  		if err := mapstructure.WeakDecode(m, &ta); err != nil {
   867  			return err
   868  		}
   869  
   870  		var optionList *ast.ObjectList
   871  		if ot, ok := o.Val.(*ast.ObjectType); ok {
   872  			optionList = ot.List
   873  		} else {
   874  			return fmt.Errorf("artifact should be an object")
   875  		}
   876  
   877  		if oo := optionList.Filter("options"); len(oo.Items) > 0 {
   878  			options := make(map[string]string)
   879  			if err := parseArtifactOption(options, oo); err != nil {
   880  				return multierror.Prefix(err, "options: ")
   881  			}
   882  			ta.GetterOptions = options
   883  		}
   884  
   885  		*result = append(*result, &ta)
   886  	}
   887  
   888  	return nil
   889  }
   890  
   891  func parseArtifactOption(result map[string]string, list *ast.ObjectList) error {
   892  	list = list.Elem()
   893  	if len(list.Items) > 1 {
   894  		return fmt.Errorf("only one 'options' block allowed per artifact")
   895  	}
   896  
   897  	// Get our resource object
   898  	o := list.Items[0]
   899  
   900  	var m map[string]interface{}
   901  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
   902  		return err
   903  	}
   904  
   905  	if err := mapstructure.WeakDecode(m, &result); err != nil {
   906  		return err
   907  	}
   908  
   909  	return nil
   910  }
   911  
   912  func parseTemplates(result *[]*api.Template, list *ast.ObjectList) error {
   913  	for _, o := range list.Elem().Items {
   914  		// Check for invalid keys
   915  		valid := []string{
   916  			"change_mode",
   917  			"change_signal",
   918  			"data",
   919  			"destination",
   920  			"left_delimiter",
   921  			"perms",
   922  			"right_delimiter",
   923  			"source",
   924  			"splay",
   925  			"env",
   926  			"vault_grace",
   927  		}
   928  		if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
   929  			return err
   930  		}
   931  
   932  		var m map[string]interface{}
   933  		if err := hcl.DecodeObject(&m, o.Val); err != nil {
   934  			return err
   935  		}
   936  
   937  		templ := &api.Template{
   938  			ChangeMode: helper.StringToPtr("restart"),
   939  			Splay:      helper.TimeToPtr(5 * time.Second),
   940  			Perms:      helper.StringToPtr("0644"),
   941  		}
   942  
   943  		dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   944  			DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   945  			WeaklyTypedInput: true,
   946  			Result:           templ,
   947  		})
   948  		if err != nil {
   949  			return err
   950  		}
   951  		if err := dec.Decode(m); err != nil {
   952  			return err
   953  		}
   954  
   955  		*result = append(*result, templ)
   956  	}
   957  
   958  	return nil
   959  }
   960  
   961  func parseServices(jobName string, taskGroupName string, task *api.Task, serviceObjs *ast.ObjectList) error {
   962  	task.Services = make([]*api.Service, len(serviceObjs.Items))
   963  	for idx, o := range serviceObjs.Items {
   964  		// Check for invalid keys
   965  		valid := []string{
   966  			"name",
   967  			"tags",
   968  			"port",
   969  			"check",
   970  			"address_mode",
   971  			"check_restart",
   972  		}
   973  		if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
   974  			return multierror.Prefix(err, fmt.Sprintf("service (%d) ->", idx))
   975  		}
   976  
   977  		var service api.Service
   978  		var m map[string]interface{}
   979  		if err := hcl.DecodeObject(&m, o.Val); err != nil {
   980  			return err
   981  		}
   982  
   983  		delete(m, "check")
   984  		delete(m, "check_restart")
   985  
   986  		if err := mapstructure.WeakDecode(m, &service); err != nil {
   987  			return err
   988  		}
   989  
   990  		// Filter checks
   991  		var checkList *ast.ObjectList
   992  		if ot, ok := o.Val.(*ast.ObjectType); ok {
   993  			checkList = ot.List
   994  		} else {
   995  			return fmt.Errorf("service '%s': should be an object", service.Name)
   996  		}
   997  
   998  		if co := checkList.Filter("check"); len(co.Items) > 0 {
   999  			if err := parseChecks(&service, co); err != nil {
  1000  				return multierror.Prefix(err, fmt.Sprintf("service: '%s',", service.Name))
  1001  			}
  1002  		}
  1003  
  1004  		// Filter check_restart
  1005  		if cro := checkList.Filter("check_restart"); len(cro.Items) > 0 {
  1006  			if len(cro.Items) > 1 {
  1007  				return fmt.Errorf("check_restart '%s': cannot have more than 1 check_restart", service.Name)
  1008  			}
  1009  			if cr, err := parseCheckRestart(cro.Items[0]); err != nil {
  1010  				return multierror.Prefix(err, fmt.Sprintf("service: '%s',", service.Name))
  1011  			} else {
  1012  				service.CheckRestart = cr
  1013  			}
  1014  		}
  1015  
  1016  		task.Services[idx] = &service
  1017  	}
  1018  
  1019  	return nil
  1020  }
  1021  
  1022  func parseChecks(service *api.Service, checkObjs *ast.ObjectList) error {
  1023  	service.Checks = make([]api.ServiceCheck, len(checkObjs.Items))
  1024  	for idx, co := range checkObjs.Items {
  1025  		// Check for invalid keys
  1026  		valid := []string{
  1027  			"name",
  1028  			"type",
  1029  			"interval",
  1030  			"timeout",
  1031  			"path",
  1032  			"protocol",
  1033  			"port",
  1034  			"command",
  1035  			"args",
  1036  			"initial_status",
  1037  			"tls_skip_verify",
  1038  			"header",
  1039  			"method",
  1040  			"check_restart",
  1041  			"address_mode",
  1042  		}
  1043  		if err := helper.CheckHCLKeys(co.Val, valid); err != nil {
  1044  			return multierror.Prefix(err, "check ->")
  1045  		}
  1046  
  1047  		var check api.ServiceCheck
  1048  		var cm map[string]interface{}
  1049  		if err := hcl.DecodeObject(&cm, co.Val); err != nil {
  1050  			return err
  1051  		}
  1052  
  1053  		// HCL allows repeating stanzas so merge 'header' into a single
  1054  		// map[string][]string.
  1055  		if headerI, ok := cm["header"]; ok {
  1056  			headerRaw, ok := headerI.([]map[string]interface{})
  1057  			if !ok {
  1058  				return fmt.Errorf("check -> header -> expected a []map[string][]string but found %T", headerI)
  1059  			}
  1060  			m := map[string][]string{}
  1061  			for _, rawm := range headerRaw {
  1062  				for k, vI := range rawm {
  1063  					vs, ok := vI.([]interface{})
  1064  					if !ok {
  1065  						return fmt.Errorf("check -> header -> %q expected a []string but found %T", k, vI)
  1066  					}
  1067  					for _, vI := range vs {
  1068  						v, ok := vI.(string)
  1069  						if !ok {
  1070  							return fmt.Errorf("check -> header -> %q expected a string but found %T", k, vI)
  1071  						}
  1072  						m[k] = append(m[k], v)
  1073  					}
  1074  				}
  1075  			}
  1076  
  1077  			check.Header = m
  1078  
  1079  			// Remove "header" as it has been parsed
  1080  			delete(cm, "header")
  1081  		}
  1082  
  1083  		delete(cm, "check_restart")
  1084  
  1085  		dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
  1086  			DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
  1087  			WeaklyTypedInput: true,
  1088  			Result:           &check,
  1089  		})
  1090  		if err != nil {
  1091  			return err
  1092  		}
  1093  		if err := dec.Decode(cm); err != nil {
  1094  			return err
  1095  		}
  1096  
  1097  		// Filter check_restart
  1098  		var checkRestartList *ast.ObjectList
  1099  		if ot, ok := co.Val.(*ast.ObjectType); ok {
  1100  			checkRestartList = ot.List
  1101  		} else {
  1102  			return fmt.Errorf("check_restart '%s': should be an object", check.Name)
  1103  		}
  1104  
  1105  		if cro := checkRestartList.Filter("check_restart"); len(cro.Items) > 0 {
  1106  			if len(cro.Items) > 1 {
  1107  				return fmt.Errorf("check_restart '%s': cannot have more than 1 check_restart", check.Name)
  1108  			}
  1109  			if cr, err := parseCheckRestart(cro.Items[0]); err != nil {
  1110  				return multierror.Prefix(err, fmt.Sprintf("check: '%s',", check.Name))
  1111  			} else {
  1112  				check.CheckRestart = cr
  1113  			}
  1114  		}
  1115  
  1116  		service.Checks[idx] = check
  1117  	}
  1118  
  1119  	return nil
  1120  }
  1121  
  1122  func parseCheckRestart(cro *ast.ObjectItem) (*api.CheckRestart, error) {
  1123  	valid := []string{
  1124  		"limit",
  1125  		"grace",
  1126  		"ignore_warnings",
  1127  	}
  1128  
  1129  	if err := helper.CheckHCLKeys(cro.Val, valid); err != nil {
  1130  		return nil, multierror.Prefix(err, "check_restart ->")
  1131  	}
  1132  
  1133  	var checkRestart api.CheckRestart
  1134  	var crm map[string]interface{}
  1135  	if err := hcl.DecodeObject(&crm, cro.Val); err != nil {
  1136  		return nil, err
  1137  	}
  1138  
  1139  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
  1140  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
  1141  		WeaklyTypedInput: true,
  1142  		Result:           &checkRestart,
  1143  	})
  1144  	if err != nil {
  1145  		return nil, err
  1146  	}
  1147  	if err := dec.Decode(crm); err != nil {
  1148  		return nil, err
  1149  	}
  1150  
  1151  	return &checkRestart, nil
  1152  }
  1153  
  1154  func parseResources(result *api.Resources, list *ast.ObjectList) error {
  1155  	list = list.Elem()
  1156  	if len(list.Items) == 0 {
  1157  		return nil
  1158  	}
  1159  	if len(list.Items) > 1 {
  1160  		return fmt.Errorf("only one 'resource' block allowed per task")
  1161  	}
  1162  
  1163  	// Get our resource object
  1164  	o := list.Items[0]
  1165  
  1166  	// We need this later
  1167  	var listVal *ast.ObjectList
  1168  	if ot, ok := o.Val.(*ast.ObjectType); ok {
  1169  		listVal = ot.List
  1170  	} else {
  1171  		return fmt.Errorf("resource: should be an object")
  1172  	}
  1173  
  1174  	// Check for invalid keys
  1175  	valid := []string{
  1176  		"cpu",
  1177  		"iops",
  1178  		"disk",
  1179  		"memory",
  1180  		"network",
  1181  	}
  1182  	if err := helper.CheckHCLKeys(listVal, valid); err != nil {
  1183  		return multierror.Prefix(err, "resources ->")
  1184  	}
  1185  
  1186  	var m map[string]interface{}
  1187  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
  1188  		return err
  1189  	}
  1190  	delete(m, "network")
  1191  
  1192  	if err := mapstructure.WeakDecode(m, result); err != nil {
  1193  		return err
  1194  	}
  1195  
  1196  	// Parse the network resources
  1197  	if o := listVal.Filter("network"); len(o.Items) > 0 {
  1198  		if len(o.Items) > 1 {
  1199  			return fmt.Errorf("only one 'network' resource allowed")
  1200  		}
  1201  
  1202  		// Check for invalid keys
  1203  		valid := []string{
  1204  			"mbits",
  1205  			"port",
  1206  		}
  1207  		if err := helper.CheckHCLKeys(o.Items[0].Val, valid); err != nil {
  1208  			return multierror.Prefix(err, "resources, network ->")
  1209  		}
  1210  
  1211  		var r api.NetworkResource
  1212  		var m map[string]interface{}
  1213  		if err := hcl.DecodeObject(&m, o.Items[0].Val); err != nil {
  1214  			return err
  1215  		}
  1216  		if err := mapstructure.WeakDecode(m, &r); err != nil {
  1217  			return err
  1218  		}
  1219  
  1220  		var networkObj *ast.ObjectList
  1221  		if ot, ok := o.Items[0].Val.(*ast.ObjectType); ok {
  1222  			networkObj = ot.List
  1223  		} else {
  1224  			return fmt.Errorf("resource: should be an object")
  1225  		}
  1226  		if err := parsePorts(networkObj, &r); err != nil {
  1227  			return multierror.Prefix(err, "resources, network, ports ->")
  1228  		}
  1229  
  1230  		result.Networks = []*api.NetworkResource{&r}
  1231  	}
  1232  
  1233  	return nil
  1234  }
  1235  
  1236  func parsePorts(networkObj *ast.ObjectList, nw *api.NetworkResource) error {
  1237  	// Check for invalid keys
  1238  	valid := []string{
  1239  		"mbits",
  1240  		"port",
  1241  	}
  1242  	if err := helper.CheckHCLKeys(networkObj, valid); err != nil {
  1243  		return err
  1244  	}
  1245  
  1246  	portsObjList := networkObj.Filter("port")
  1247  	knownPortLabels := make(map[string]bool)
  1248  	for _, port := range portsObjList.Items {
  1249  		if len(port.Keys) == 0 {
  1250  			return fmt.Errorf("ports must be named")
  1251  		}
  1252  		label := port.Keys[0].Token.Value().(string)
  1253  		if !reDynamicPorts.MatchString(label) {
  1254  			return errPortLabel
  1255  		}
  1256  		l := strings.ToLower(label)
  1257  		if knownPortLabels[l] {
  1258  			return fmt.Errorf("found a port label collision: %s", label)
  1259  		}
  1260  		var p map[string]interface{}
  1261  		var res api.Port
  1262  		if err := hcl.DecodeObject(&p, port.Val); err != nil {
  1263  			return err
  1264  		}
  1265  		if err := mapstructure.WeakDecode(p, &res); err != nil {
  1266  			return err
  1267  		}
  1268  		res.Label = label
  1269  		if res.Value > 0 {
  1270  			nw.ReservedPorts = append(nw.ReservedPorts, res)
  1271  		} else {
  1272  			nw.DynamicPorts = append(nw.DynamicPorts, res)
  1273  		}
  1274  		knownPortLabels[l] = true
  1275  	}
  1276  	return nil
  1277  }
  1278  
  1279  func parseUpdate(result **api.UpdateStrategy, list *ast.ObjectList) error {
  1280  	list = list.Elem()
  1281  	if len(list.Items) > 1 {
  1282  		return fmt.Errorf("only one 'update' block allowed")
  1283  	}
  1284  
  1285  	// Get our resource object
  1286  	o := list.Items[0]
  1287  
  1288  	var m map[string]interface{}
  1289  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
  1290  		return err
  1291  	}
  1292  
  1293  	// Check for invalid keys
  1294  	valid := []string{
  1295  		// COMPAT: Remove in 0.7.0. Stagger is deprecated in 0.6.0.
  1296  		"stagger",
  1297  		"max_parallel",
  1298  		"health_check",
  1299  		"min_healthy_time",
  1300  		"healthy_deadline",
  1301  		"auto_revert",
  1302  		"canary",
  1303  	}
  1304  	if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
  1305  		return err
  1306  	}
  1307  
  1308  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
  1309  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
  1310  		WeaklyTypedInput: true,
  1311  		Result:           result,
  1312  	})
  1313  	if err != nil {
  1314  		return err
  1315  	}
  1316  	return dec.Decode(m)
  1317  }
  1318  
  1319  func parsePeriodic(result **api.PeriodicConfig, list *ast.ObjectList) error {
  1320  	list = list.Elem()
  1321  	if len(list.Items) > 1 {
  1322  		return fmt.Errorf("only one 'periodic' block allowed per job")
  1323  	}
  1324  
  1325  	// Get our resource object
  1326  	o := list.Items[0]
  1327  
  1328  	var m map[string]interface{}
  1329  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
  1330  		return err
  1331  	}
  1332  
  1333  	// Check for invalid keys
  1334  	valid := []string{
  1335  		"enabled",
  1336  		"cron",
  1337  		"prohibit_overlap",
  1338  		"time_zone",
  1339  	}
  1340  	if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
  1341  		return err
  1342  	}
  1343  
  1344  	if value, ok := m["enabled"]; ok {
  1345  		enabled, err := parseBool(value)
  1346  		if err != nil {
  1347  			return fmt.Errorf("periodic.enabled should be set to true or false; %v", err)
  1348  		}
  1349  		m["Enabled"] = enabled
  1350  	}
  1351  
  1352  	// If "cron" is provided, set the type to "cron" and store the spec.
  1353  	if cron, ok := m["cron"]; ok {
  1354  		m["SpecType"] = structs.PeriodicSpecCron
  1355  		m["Spec"] = cron
  1356  	}
  1357  
  1358  	// Build the constraint
  1359  	var p api.PeriodicConfig
  1360  	if err := mapstructure.WeakDecode(m, &p); err != nil {
  1361  		return err
  1362  	}
  1363  	*result = &p
  1364  	return nil
  1365  }
  1366  
  1367  func parseVault(result *api.Vault, list *ast.ObjectList) error {
  1368  	list = list.Elem()
  1369  	if len(list.Items) == 0 {
  1370  		return nil
  1371  	}
  1372  	if len(list.Items) > 1 {
  1373  		return fmt.Errorf("only one 'vault' block allowed per task")
  1374  	}
  1375  
  1376  	// Get our resource object
  1377  	o := list.Items[0]
  1378  
  1379  	// We need this later
  1380  	var listVal *ast.ObjectList
  1381  	if ot, ok := o.Val.(*ast.ObjectType); ok {
  1382  		listVal = ot.List
  1383  	} else {
  1384  		return fmt.Errorf("vault: should be an object")
  1385  	}
  1386  
  1387  	// Check for invalid keys
  1388  	valid := []string{
  1389  		"policies",
  1390  		"env",
  1391  		"change_mode",
  1392  		"change_signal",
  1393  	}
  1394  	if err := helper.CheckHCLKeys(listVal, valid); err != nil {
  1395  		return multierror.Prefix(err, "vault ->")
  1396  	}
  1397  
  1398  	var m map[string]interface{}
  1399  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
  1400  		return err
  1401  	}
  1402  
  1403  	if err := mapstructure.WeakDecode(m, result); err != nil {
  1404  		return err
  1405  	}
  1406  
  1407  	return nil
  1408  }
  1409  
  1410  func parseParameterizedJob(result **api.ParameterizedJobConfig, list *ast.ObjectList) error {
  1411  	list = list.Elem()
  1412  	if len(list.Items) > 1 {
  1413  		return fmt.Errorf("only one 'parameterized' block allowed per job")
  1414  	}
  1415  
  1416  	// Get our resource object
  1417  	o := list.Items[0]
  1418  
  1419  	var m map[string]interface{}
  1420  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
  1421  		return err
  1422  	}
  1423  
  1424  	// Check for invalid keys
  1425  	valid := []string{
  1426  		"payload",
  1427  		"meta_required",
  1428  		"meta_optional",
  1429  	}
  1430  	if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
  1431  		return err
  1432  	}
  1433  
  1434  	// Build the parameterized job block
  1435  	var d api.ParameterizedJobConfig
  1436  	if err := mapstructure.WeakDecode(m, &d); err != nil {
  1437  		return err
  1438  	}
  1439  
  1440  	*result = &d
  1441  	return nil
  1442  }