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