github.com/mattyr/nomad@v0.3.3-0.20160919021406-3485a065154a/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  
    13  	"github.com/hashicorp/go-multierror"
    14  	"github.com/hashicorp/hcl"
    15  	"github.com/hashicorp/hcl/hcl/ast"
    16  	"github.com/hashicorp/nomad/client/driver"
    17  	"github.com/hashicorp/nomad/nomad/structs"
    18  	"github.com/mitchellh/mapstructure"
    19  )
    20  
    21  var reDynamicPorts = regexp.MustCompile("^[a-zA-Z0-9_]+$")
    22  var errPortLabel = fmt.Errorf("Port label does not conform to naming requirements %s", reDynamicPorts.String())
    23  
    24  // Parse parses the job spec from the given io.Reader.
    25  //
    26  // Due to current internal limitations, the entire contents of the
    27  // io.Reader will be copied into memory first before parsing.
    28  func Parse(r io.Reader) (*structs.Job, error) {
    29  	// Copy the reader into an in-memory buffer first since HCL requires it.
    30  	var buf bytes.Buffer
    31  	if _, err := io.Copy(&buf, r); err != nil {
    32  		return nil, err
    33  	}
    34  
    35  	// Parse the buffer
    36  	root, err := hcl.Parse(buf.String())
    37  	if err != nil {
    38  		return nil, fmt.Errorf("error parsing: %s", err)
    39  	}
    40  	buf.Reset()
    41  
    42  	// Top-level item should be a list
    43  	list, ok := root.Node.(*ast.ObjectList)
    44  	if !ok {
    45  		return nil, fmt.Errorf("error parsing: root should be an object")
    46  	}
    47  
    48  	// Check for invalid keys
    49  	valid := []string{
    50  		"job",
    51  	}
    52  	if err := checkHCLKeys(list, valid); err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	var job structs.Job
    57  
    58  	// Parse the job out
    59  	matches := list.Filter("job")
    60  	if len(matches.Items) == 0 {
    61  		return nil, fmt.Errorf("'job' stanza not found")
    62  	}
    63  	if err := parseJob(&job, matches); err != nil {
    64  		return nil, fmt.Errorf("error parsing 'job': %s", err)
    65  	}
    66  
    67  	return &job, nil
    68  }
    69  
    70  // ParseFile parses the given path as a job spec.
    71  func ParseFile(path string) (*structs.Job, error) {
    72  	path, err := filepath.Abs(path)
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	f, err := os.Open(path)
    78  	if err != nil {
    79  		return nil, err
    80  	}
    81  	defer f.Close()
    82  
    83  	return Parse(f)
    84  }
    85  
    86  func parseJob(result *structs.Job, list *ast.ObjectList) error {
    87  	list = list.Children()
    88  	if len(list.Items) != 1 {
    89  		return fmt.Errorf("only one 'job' block allowed")
    90  	}
    91  
    92  	// Get our job object
    93  	obj := list.Items[0]
    94  
    95  	// Decode the full thing into a map[string]interface for ease
    96  	var m map[string]interface{}
    97  	if err := hcl.DecodeObject(&m, obj.Val); err != nil {
    98  		return err
    99  	}
   100  	delete(m, "constraint")
   101  	delete(m, "meta")
   102  	delete(m, "update")
   103  	delete(m, "periodic")
   104  
   105  	// Set the ID and name to the object key
   106  	result.ID = obj.Keys[0].Token.Value().(string)
   107  	result.Name = result.ID
   108  
   109  	// Defaults
   110  	result.Priority = 50
   111  	result.Region = "global"
   112  	result.Type = "service"
   113  
   114  	// Decode the rest
   115  	if err := mapstructure.WeakDecode(m, result); err != nil {
   116  		return err
   117  	}
   118  
   119  	// Value should be an object
   120  	var listVal *ast.ObjectList
   121  	if ot, ok := obj.Val.(*ast.ObjectType); ok {
   122  		listVal = ot.List
   123  	} else {
   124  		return fmt.Errorf("job '%s' value: should be an object", result.ID)
   125  	}
   126  
   127  	// Check for invalid keys
   128  	valid := []string{
   129  		"id",
   130  		"name",
   131  		"region",
   132  		"all_at_once",
   133  		"type",
   134  		"priority",
   135  		"datacenters",
   136  		"constraint",
   137  		"update",
   138  		"periodic",
   139  		"meta",
   140  		"task",
   141  		"group",
   142  		"vault_token",
   143  	}
   144  	if err := checkHCLKeys(listVal, valid); err != nil {
   145  		return multierror.Prefix(err, "job:")
   146  	}
   147  
   148  	// Parse constraints
   149  	if o := listVal.Filter("constraint"); len(o.Items) > 0 {
   150  		if err := parseConstraints(&result.Constraints, o); err != nil {
   151  			return multierror.Prefix(err, "constraint ->")
   152  		}
   153  	}
   154  
   155  	// If we have an update strategy, then parse that
   156  	if o := listVal.Filter("update"); len(o.Items) > 0 {
   157  		if err := parseUpdate(&result.Update, o); err != nil {
   158  			return multierror.Prefix(err, "update ->")
   159  		}
   160  	}
   161  
   162  	// If we have a periodic definition, then parse that
   163  	if o := listVal.Filter("periodic"); len(o.Items) > 0 {
   164  		if err := parsePeriodic(&result.Periodic, o); err != nil {
   165  			return multierror.Prefix(err, "periodic ->")
   166  		}
   167  	}
   168  
   169  	// Parse out meta fields. These are in HCL as a list so we need
   170  	// to iterate over them and merge them.
   171  	if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 {
   172  		for _, o := range metaO.Elem().Items {
   173  			var m map[string]interface{}
   174  			if err := hcl.DecodeObject(&m, o.Val); err != nil {
   175  				return err
   176  			}
   177  			if err := mapstructure.WeakDecode(m, &result.Meta); err != nil {
   178  				return err
   179  			}
   180  		}
   181  	}
   182  
   183  	// If we have tasks outside, create TaskGroups for them
   184  	if o := listVal.Filter("task"); len(o.Items) > 0 {
   185  		var tasks []*structs.Task
   186  		if err := parseTasks(result.Name, "", &tasks, o); err != nil {
   187  			return multierror.Prefix(err, "task:")
   188  		}
   189  
   190  		result.TaskGroups = make([]*structs.TaskGroup, len(tasks), len(tasks)*2)
   191  		for i, t := range tasks {
   192  			result.TaskGroups[i] = &structs.TaskGroup{
   193  				Name:          t.Name,
   194  				Count:         1,
   195  				EphemeralDisk: structs.DefaultEphemeralDisk(),
   196  				Tasks:         []*structs.Task{t},
   197  			}
   198  		}
   199  	}
   200  
   201  	// Parse the task groups
   202  	if o := listVal.Filter("group"); len(o.Items) > 0 {
   203  		if err := parseGroups(result, o); err != nil {
   204  			return multierror.Prefix(err, "group:")
   205  		}
   206  	}
   207  
   208  	return nil
   209  }
   210  
   211  func parseGroups(result *structs.Job, list *ast.ObjectList) error {
   212  	list = list.Children()
   213  	if len(list.Items) == 0 {
   214  		return nil
   215  	}
   216  
   217  	// Go through each object and turn it into an actual result.
   218  	collection := make([]*structs.TaskGroup, 0, len(list.Items))
   219  	seen := make(map[string]struct{})
   220  	for _, item := range list.Items {
   221  		n := item.Keys[0].Token.Value().(string)
   222  
   223  		// Make sure we haven't already found this
   224  		if _, ok := seen[n]; ok {
   225  			return fmt.Errorf("group '%s' defined more than once", n)
   226  		}
   227  		seen[n] = struct{}{}
   228  
   229  		// We need this later
   230  		var listVal *ast.ObjectList
   231  		if ot, ok := item.Val.(*ast.ObjectType); ok {
   232  			listVal = ot.List
   233  		} else {
   234  			return fmt.Errorf("group '%s': should be an object", n)
   235  		}
   236  
   237  		// Check for invalid keys
   238  		valid := []string{
   239  			"count",
   240  			"constraint",
   241  			"restart",
   242  			"meta",
   243  			"task",
   244  			"ephemeral_disk",
   245  		}
   246  		if err := checkHCLKeys(listVal, valid); err != nil {
   247  			return multierror.Prefix(err, fmt.Sprintf("'%s' ->", n))
   248  		}
   249  
   250  		var m map[string]interface{}
   251  		if err := hcl.DecodeObject(&m, item.Val); err != nil {
   252  			return err
   253  		}
   254  		delete(m, "constraint")
   255  		delete(m, "meta")
   256  		delete(m, "task")
   257  		delete(m, "restart")
   258  		delete(m, "ephemeral_disk")
   259  
   260  		// Default count to 1 if not specified
   261  		if _, ok := m["count"]; !ok {
   262  			m["count"] = 1
   263  		}
   264  
   265  		// Build the group with the basic decode
   266  		var g structs.TaskGroup
   267  		g.Name = n
   268  		if err := mapstructure.WeakDecode(m, &g); err != nil {
   269  			return err
   270  		}
   271  
   272  		// Parse constraints
   273  		if o := listVal.Filter("constraint"); len(o.Items) > 0 {
   274  			if err := parseConstraints(&g.Constraints, o); err != nil {
   275  				return multierror.Prefix(err, fmt.Sprintf("'%s', constraint ->", n))
   276  			}
   277  		}
   278  
   279  		// Parse restart policy
   280  		if o := listVal.Filter("restart"); len(o.Items) > 0 {
   281  			if err := parseRestartPolicy(&g.RestartPolicy, o); err != nil {
   282  				return multierror.Prefix(err, fmt.Sprintf("'%s', restart ->", n))
   283  			}
   284  		}
   285  
   286  		// Parse ephemeral disk
   287  		g.EphemeralDisk = structs.DefaultEphemeralDisk()
   288  		if o := listVal.Filter("ephemeral_disk"); len(o.Items) > 0 {
   289  			if err := parseEphemeralDisk(&g.EphemeralDisk, o); err != nil {
   290  				return multierror.Prefix(err, fmt.Sprintf("'%s', ephemeral_disk ->", n))
   291  			}
   292  		}
   293  
   294  		// Parse out meta fields. These are in HCL as a list so we need
   295  		// to iterate over them and merge them.
   296  		if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 {
   297  			for _, o := range metaO.Elem().Items {
   298  				var m map[string]interface{}
   299  				if err := hcl.DecodeObject(&m, o.Val); err != nil {
   300  					return err
   301  				}
   302  				if err := mapstructure.WeakDecode(m, &g.Meta); err != nil {
   303  					return err
   304  				}
   305  			}
   306  		}
   307  
   308  		// Parse tasks
   309  		if o := listVal.Filter("task"); len(o.Items) > 0 {
   310  			if err := parseTasks(result.Name, g.Name, &g.Tasks, o); err != nil {
   311  				return multierror.Prefix(err, fmt.Sprintf("'%s', task:", n))
   312  			}
   313  		}
   314  
   315  		collection = append(collection, &g)
   316  	}
   317  
   318  	result.TaskGroups = append(result.TaskGroups, collection...)
   319  	return nil
   320  }
   321  
   322  func parseRestartPolicy(final **structs.RestartPolicy, list *ast.ObjectList) error {
   323  	list = list.Elem()
   324  	if len(list.Items) > 1 {
   325  		return fmt.Errorf("only one 'restart' block allowed")
   326  	}
   327  
   328  	// Get our job object
   329  	obj := list.Items[0]
   330  
   331  	// Check for invalid keys
   332  	valid := []string{
   333  		"attempts",
   334  		"interval",
   335  		"delay",
   336  		"mode",
   337  	}
   338  	if err := checkHCLKeys(obj.Val, valid); err != nil {
   339  		return err
   340  	}
   341  
   342  	var m map[string]interface{}
   343  	if err := hcl.DecodeObject(&m, obj.Val); err != nil {
   344  		return err
   345  	}
   346  
   347  	var result structs.RestartPolicy
   348  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   349  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   350  		WeaklyTypedInput: true,
   351  		Result:           &result,
   352  	})
   353  	if err != nil {
   354  		return err
   355  	}
   356  	if err := dec.Decode(m); err != nil {
   357  		return err
   358  	}
   359  
   360  	*final = &result
   361  	return nil
   362  }
   363  
   364  func parseConstraints(result *[]*structs.Constraint, list *ast.ObjectList) error {
   365  	for _, o := range list.Elem().Items {
   366  		// Check for invalid keys
   367  		valid := []string{
   368  			"attribute",
   369  			"operator",
   370  			"value",
   371  			"version",
   372  			"regexp",
   373  			"distinct_hosts",
   374  		}
   375  		if err := checkHCLKeys(o.Val, valid); err != nil {
   376  			return err
   377  		}
   378  
   379  		var m map[string]interface{}
   380  		if err := hcl.DecodeObject(&m, o.Val); err != nil {
   381  			return err
   382  		}
   383  
   384  		m["LTarget"] = m["attribute"]
   385  		m["RTarget"] = m["value"]
   386  		m["Operand"] = m["operator"]
   387  
   388  		// If "version" is provided, set the operand
   389  		// to "version" and the value to the "RTarget"
   390  		if constraint, ok := m[structs.ConstraintVersion]; ok {
   391  			m["Operand"] = structs.ConstraintVersion
   392  			m["RTarget"] = constraint
   393  		}
   394  
   395  		// If "regexp" is provided, set the operand
   396  		// to "regexp" and the value to the "RTarget"
   397  		if constraint, ok := m[structs.ConstraintRegex]; ok {
   398  			m["Operand"] = structs.ConstraintRegex
   399  			m["RTarget"] = constraint
   400  		}
   401  
   402  		if value, ok := m[structs.ConstraintDistinctHosts]; ok {
   403  			enabled, err := parseBool(value)
   404  			if err != nil {
   405  				return fmt.Errorf("distinct_hosts should be set to true or false; %v", err)
   406  			}
   407  
   408  			// If it is not enabled, skip the constraint.
   409  			if !enabled {
   410  				continue
   411  			}
   412  
   413  			m["Operand"] = structs.ConstraintDistinctHosts
   414  		}
   415  
   416  		// Build the constraint
   417  		var c structs.Constraint
   418  		if err := mapstructure.WeakDecode(m, &c); err != nil {
   419  			return err
   420  		}
   421  		if c.Operand == "" {
   422  			c.Operand = "="
   423  		}
   424  
   425  		*result = append(*result, &c)
   426  	}
   427  
   428  	return nil
   429  }
   430  
   431  func parseEphemeralDisk(result **structs.EphemeralDisk, list *ast.ObjectList) error {
   432  	list = list.Elem()
   433  	if len(list.Items) > 1 {
   434  		return fmt.Errorf("only one 'ephemeral_disk' block allowed")
   435  	}
   436  
   437  	// Get our ephemeral_disk object
   438  	obj := list.Items[0]
   439  
   440  	// Check for invalid keys
   441  	valid := []string{
   442  		"sticky",
   443  		"size",
   444  	}
   445  	if err := checkHCLKeys(obj.Val, valid); err != nil {
   446  		return err
   447  	}
   448  
   449  	var m map[string]interface{}
   450  	if err := hcl.DecodeObject(&m, obj.Val); err != nil {
   451  		return err
   452  	}
   453  
   454  	var ephemeralDisk structs.EphemeralDisk
   455  	if err := mapstructure.WeakDecode(m, &ephemeralDisk); err != nil {
   456  		return err
   457  	}
   458  	*result = &ephemeralDisk
   459  
   460  	return nil
   461  }
   462  
   463  // parseBool takes an interface value and tries to convert it to a boolean and
   464  // returns an error if the type can't be converted.
   465  func parseBool(value interface{}) (bool, error) {
   466  	var enabled bool
   467  	var err error
   468  	switch value.(type) {
   469  	case string:
   470  		enabled, err = strconv.ParseBool(value.(string))
   471  	case bool:
   472  		enabled = value.(bool)
   473  	default:
   474  		err = fmt.Errorf("%v couldn't be converted to boolean value", value)
   475  	}
   476  
   477  	return enabled, err
   478  }
   479  
   480  func parseTasks(jobName string, taskGroupName string, result *[]*structs.Task, list *ast.ObjectList) error {
   481  	list = list.Children()
   482  	if len(list.Items) == 0 {
   483  		return nil
   484  	}
   485  
   486  	// Go through each object and turn it into an actual result.
   487  	seen := make(map[string]struct{})
   488  	for _, item := range list.Items {
   489  		n := item.Keys[0].Token.Value().(string)
   490  
   491  		// Make sure we haven't already found this
   492  		if _, ok := seen[n]; ok {
   493  			return fmt.Errorf("task '%s' defined more than once", n)
   494  		}
   495  		seen[n] = struct{}{}
   496  
   497  		// We need this later
   498  		var listVal *ast.ObjectList
   499  		if ot, ok := item.Val.(*ast.ObjectType); ok {
   500  			listVal = ot.List
   501  		} else {
   502  			return fmt.Errorf("group '%s': should be an object", n)
   503  		}
   504  
   505  		// Check for invalid keys
   506  		valid := []string{
   507  			"artifact",
   508  			"config",
   509  			"constraint",
   510  			"driver",
   511  			"env",
   512  			"exclude_nomad_env",
   513  			"kill_timeout",
   514  			"logs",
   515  			"meta",
   516  			"resources",
   517  			"service",
   518  			"user",
   519  			"vault",
   520  		}
   521  		if err := checkHCLKeys(listVal, valid); err != nil {
   522  			return multierror.Prefix(err, fmt.Sprintf("'%s' ->", n))
   523  		}
   524  
   525  		var m map[string]interface{}
   526  		if err := hcl.DecodeObject(&m, item.Val); err != nil {
   527  			return err
   528  		}
   529  		delete(m, "artifact")
   530  		delete(m, "config")
   531  		delete(m, "constraint")
   532  		delete(m, "env")
   533  		delete(m, "exclude_nomad_env")
   534  		delete(m, "logs")
   535  		delete(m, "meta")
   536  		delete(m, "resources")
   537  		delete(m, "service")
   538  		delete(m, "vault")
   539  
   540  		// Build the task
   541  		var t structs.Task
   542  		t.Name = n
   543  		if taskGroupName == "" {
   544  			taskGroupName = n
   545  		}
   546  		dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   547  			DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   548  			WeaklyTypedInput: true,
   549  			Result:           &t,
   550  		})
   551  		if err != nil {
   552  			return err
   553  		}
   554  		if err := dec.Decode(m); err != nil {
   555  			return err
   556  		}
   557  
   558  		// If we have env, then parse them
   559  		if o := listVal.Filter("env"); len(o.Items) > 0 {
   560  			for _, o := range o.Elem().Items {
   561  				var m map[string]interface{}
   562  				if err := hcl.DecodeObject(&m, o.Val); err != nil {
   563  					return err
   564  				}
   565  				if err := mapstructure.WeakDecode(m, &t.Env); err != nil {
   566  					return err
   567  				}
   568  			}
   569  		}
   570  
   571  		if o := listVal.Filter("service"); len(o.Items) > 0 {
   572  			if err := parseServices(jobName, taskGroupName, &t, o); err != nil {
   573  				return multierror.Prefix(err, fmt.Sprintf("'%s',", n))
   574  			}
   575  		}
   576  
   577  		// If we have config, then parse that
   578  		if o := listVal.Filter("config"); len(o.Items) > 0 {
   579  			for _, o := range o.Elem().Items {
   580  				var m map[string]interface{}
   581  				if err := hcl.DecodeObject(&m, o.Val); err != nil {
   582  					return err
   583  				}
   584  
   585  				if err := mapstructure.WeakDecode(m, &t.Config); err != nil {
   586  					return err
   587  				}
   588  			}
   589  
   590  			// Instantiate a driver to validate the configuration
   591  			d, err := driver.NewDriver(
   592  				t.Driver,
   593  				driver.NewEmptyDriverContext(),
   594  			)
   595  
   596  			if err != nil {
   597  				return multierror.Prefix(err,
   598  					fmt.Sprintf("'%s', config ->", n))
   599  			}
   600  
   601  			if err := d.Validate(t.Config); err != nil {
   602  				return multierror.Prefix(err,
   603  					fmt.Sprintf("'%s', config ->", n))
   604  			}
   605  		}
   606  
   607  		// Parse constraints
   608  		if o := listVal.Filter("constraint"); len(o.Items) > 0 {
   609  			if err := parseConstraints(&t.Constraints, o); err != nil {
   610  				return multierror.Prefix(err, fmt.Sprintf(
   611  					"'%s', constraint ->", n))
   612  			}
   613  		}
   614  
   615  		// Parse out meta fields. These are in HCL as a list so we need
   616  		// to iterate over them and merge them.
   617  		if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 {
   618  			for _, o := range metaO.Elem().Items {
   619  				var m map[string]interface{}
   620  				if err := hcl.DecodeObject(&m, o.Val); err != nil {
   621  					return err
   622  				}
   623  				if err := mapstructure.WeakDecode(m, &t.Meta); err != nil {
   624  					return err
   625  				}
   626  			}
   627  		}
   628  
   629  		// If we have resources, then parse that
   630  		if o := listVal.Filter("resources"); len(o.Items) > 0 {
   631  			var r structs.Resources
   632  			if err := parseResources(&r, o); err != nil {
   633  				return multierror.Prefix(err, fmt.Sprintf("'%s',", n))
   634  			}
   635  
   636  			t.Resources = &r
   637  		}
   638  
   639  		// If we have logs then parse that
   640  		logConfig := structs.DefaultLogConfig()
   641  		if o := listVal.Filter("logs"); len(o.Items) > 0 {
   642  			if len(o.Items) > 1 {
   643  				return fmt.Errorf("only one logs block is allowed in a Task. Number of logs block found: %d", len(o.Items))
   644  			}
   645  			var m map[string]interface{}
   646  			logsBlock := o.Items[0]
   647  
   648  			// Check for invalid keys
   649  			valid := []string{
   650  				"max_files",
   651  				"max_file_size",
   652  				"log_shuttle_config",
   653  			}
   654  			if err := checkHCLKeys(logsBlock.Val, valid); err != nil {
   655  				return multierror.Prefix(err, fmt.Sprintf("'%s', logs ->", n))
   656  			}
   657  
   658  			if err := hcl.DecodeObject(&m, logsBlock.Val); err != nil {
   659  				return err
   660  			}
   661  
   662  			if err := mapstructure.WeakDecode(m, &logConfig); err != nil {
   663  				return err
   664  			}
   665  		}
   666  		t.LogConfig = logConfig
   667  
   668  		// Parse artifacts
   669  		if o := listVal.Filter("artifact"); len(o.Items) > 0 {
   670  			if err := parseArtifacts(&t.Artifacts, o); err != nil {
   671  				return multierror.Prefix(err, fmt.Sprintf("'%s', artifact ->", n))
   672  			}
   673  		}
   674  
   675  		// If we have a vault block, then parse that
   676  		if o := listVal.Filter("vault"); len(o.Items) > 0 {
   677  			var v structs.Vault
   678  			if err := parseVault(&v, o); err != nil {
   679  				return multierror.Prefix(err, fmt.Sprintf("'%s', vault ->", n))
   680  			}
   681  
   682  			t.Vault = &v
   683  		}
   684  
   685  		*result = append(*result, &t)
   686  	}
   687  
   688  	return nil
   689  }
   690  
   691  func parseArtifacts(result *[]*structs.TaskArtifact, list *ast.ObjectList) error {
   692  	for _, o := range list.Elem().Items {
   693  		// Check for invalid keys
   694  		valid := []string{
   695  			"source",
   696  			"options",
   697  			"destination",
   698  		}
   699  		if err := checkHCLKeys(o.Val, valid); err != nil {
   700  			return err
   701  		}
   702  
   703  		var m map[string]interface{}
   704  		if err := hcl.DecodeObject(&m, o.Val); err != nil {
   705  			return err
   706  		}
   707  
   708  		delete(m, "options")
   709  
   710  		// Default to downloading to the local directory.
   711  		if _, ok := m["destination"]; !ok {
   712  			m["destination"] = "local/"
   713  		}
   714  
   715  		var ta structs.TaskArtifact
   716  		if err := mapstructure.WeakDecode(m, &ta); err != nil {
   717  			return err
   718  		}
   719  
   720  		var optionList *ast.ObjectList
   721  		if ot, ok := o.Val.(*ast.ObjectType); ok {
   722  			optionList = ot.List
   723  		} else {
   724  			return fmt.Errorf("artifact should be an object")
   725  		}
   726  
   727  		if oo := optionList.Filter("options"); len(oo.Items) > 0 {
   728  			options := make(map[string]string)
   729  			if err := parseArtifactOption(options, oo); err != nil {
   730  				return multierror.Prefix(err, "options: ")
   731  			}
   732  			ta.GetterOptions = options
   733  		}
   734  
   735  		*result = append(*result, &ta)
   736  	}
   737  
   738  	return nil
   739  }
   740  
   741  func parseArtifactOption(result map[string]string, list *ast.ObjectList) error {
   742  	list = list.Elem()
   743  	if len(list.Items) > 1 {
   744  		return fmt.Errorf("only one 'options' block allowed per artifact")
   745  	}
   746  
   747  	// Get our resource object
   748  	o := list.Items[0]
   749  
   750  	var m map[string]interface{}
   751  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
   752  		return err
   753  	}
   754  
   755  	if err := mapstructure.WeakDecode(m, &result); err != nil {
   756  		return err
   757  	}
   758  
   759  	return nil
   760  }
   761  
   762  func parseServices(jobName string, taskGroupName string, task *structs.Task, serviceObjs *ast.ObjectList) error {
   763  	task.Services = make([]*structs.Service, len(serviceObjs.Items))
   764  	var defaultServiceName bool
   765  	for idx, o := range serviceObjs.Items {
   766  		// Check for invalid keys
   767  		valid := []string{
   768  			"name",
   769  			"tags",
   770  			"port",
   771  			"check",
   772  		}
   773  		if err := checkHCLKeys(o.Val, valid); err != nil {
   774  			return multierror.Prefix(err, fmt.Sprintf("service (%d) ->", idx))
   775  		}
   776  
   777  		var service structs.Service
   778  		var m map[string]interface{}
   779  		if err := hcl.DecodeObject(&m, o.Val); err != nil {
   780  			return err
   781  		}
   782  
   783  		delete(m, "check")
   784  
   785  		if err := mapstructure.WeakDecode(m, &service); err != nil {
   786  			return err
   787  		}
   788  
   789  		if defaultServiceName && service.Name == "" {
   790  			return fmt.Errorf("Only one service block may omit the Name field")
   791  		}
   792  
   793  		if service.Name == "" {
   794  			defaultServiceName = true
   795  			service.Name = fmt.Sprintf("%s-%s-%s", jobName, taskGroupName, task.Name)
   796  		}
   797  
   798  		// Filter checks
   799  		var checkList *ast.ObjectList
   800  		if ot, ok := o.Val.(*ast.ObjectType); ok {
   801  			checkList = ot.List
   802  		} else {
   803  			return fmt.Errorf("service '%s': should be an object", service.Name)
   804  		}
   805  
   806  		if co := checkList.Filter("check"); len(co.Items) > 0 {
   807  			if err := parseChecks(&service, co); err != nil {
   808  				return multierror.Prefix(err, fmt.Sprintf("service: '%s',", service.Name))
   809  			}
   810  		}
   811  
   812  		task.Services[idx] = &service
   813  	}
   814  
   815  	return nil
   816  }
   817  
   818  func parseChecks(service *structs.Service, checkObjs *ast.ObjectList) error {
   819  	service.Checks = make([]*structs.ServiceCheck, len(checkObjs.Items))
   820  	for idx, co := range checkObjs.Items {
   821  		// Check for invalid keys
   822  		valid := []string{
   823  			"name",
   824  			"type",
   825  			"interval",
   826  			"timeout",
   827  			"path",
   828  			"protocol",
   829  			"port",
   830  			"command",
   831  			"args",
   832  			"initial_status",
   833  		}
   834  		if err := checkHCLKeys(co.Val, valid); err != nil {
   835  			return multierror.Prefix(err, "check ->")
   836  		}
   837  
   838  		var check structs.ServiceCheck
   839  		var cm map[string]interface{}
   840  		if err := hcl.DecodeObject(&cm, co.Val); err != nil {
   841  			return err
   842  		}
   843  		dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   844  			DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   845  			WeaklyTypedInput: true,
   846  			Result:           &check,
   847  		})
   848  		if err != nil {
   849  			return err
   850  		}
   851  		if err := dec.Decode(cm); err != nil {
   852  			return err
   853  		}
   854  
   855  		service.Checks[idx] = &check
   856  	}
   857  
   858  	return nil
   859  }
   860  
   861  func parseResources(result *structs.Resources, list *ast.ObjectList) error {
   862  	list = list.Elem()
   863  	if len(list.Items) == 0 {
   864  		return nil
   865  	}
   866  	if len(list.Items) > 1 {
   867  		return fmt.Errorf("only one 'resource' block allowed per task")
   868  	}
   869  
   870  	// Get our resource object
   871  	o := list.Items[0]
   872  
   873  	// We need this later
   874  	var listVal *ast.ObjectList
   875  	if ot, ok := o.Val.(*ast.ObjectType); ok {
   876  		listVal = ot.List
   877  	} else {
   878  		return fmt.Errorf("resource: should be an object")
   879  	}
   880  
   881  	// Check for invalid keys
   882  	valid := []string{
   883  		"cpu",
   884  		"iops",
   885  		"memory",
   886  		"network",
   887  	}
   888  	if err := checkHCLKeys(listVal, valid); err != nil {
   889  		return multierror.Prefix(err, "resources ->")
   890  	}
   891  
   892  	var m map[string]interface{}
   893  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
   894  		return err
   895  	}
   896  	delete(m, "network")
   897  
   898  	if err := mapstructure.WeakDecode(m, result); err != nil {
   899  		return err
   900  	}
   901  
   902  	// Parse the network resources
   903  	if o := listVal.Filter("network"); len(o.Items) > 0 {
   904  		if len(o.Items) > 1 {
   905  			return fmt.Errorf("only one 'network' resource allowed")
   906  		}
   907  
   908  		// Check for invalid keys
   909  		valid := []string{
   910  			"mbits",
   911  			"port",
   912  		}
   913  		if err := checkHCLKeys(o.Items[0].Val, valid); err != nil {
   914  			return multierror.Prefix(err, "resources, network ->")
   915  		}
   916  
   917  		var r structs.NetworkResource
   918  		var m map[string]interface{}
   919  		if err := hcl.DecodeObject(&m, o.Items[0].Val); err != nil {
   920  			return err
   921  		}
   922  		if err := mapstructure.WeakDecode(m, &r); err != nil {
   923  			return err
   924  		}
   925  
   926  		var networkObj *ast.ObjectList
   927  		if ot, ok := o.Items[0].Val.(*ast.ObjectType); ok {
   928  			networkObj = ot.List
   929  		} else {
   930  			return fmt.Errorf("resource: should be an object")
   931  		}
   932  		if err := parsePorts(networkObj, &r); err != nil {
   933  			return multierror.Prefix(err, "resources, network, ports ->")
   934  		}
   935  
   936  		result.Networks = []*structs.NetworkResource{&r}
   937  	}
   938  
   939  	// Combine the parsed resources with a default resource block.
   940  	min := structs.DefaultResources()
   941  	min.Merge(result)
   942  	*result = *min
   943  	return nil
   944  }
   945  
   946  func parsePorts(networkObj *ast.ObjectList, nw *structs.NetworkResource) error {
   947  	// Check for invalid keys
   948  	valid := []string{
   949  		"mbits",
   950  		"port",
   951  	}
   952  	if err := checkHCLKeys(networkObj, valid); err != nil {
   953  		return err
   954  	}
   955  
   956  	portsObjList := networkObj.Filter("port")
   957  	knownPortLabels := make(map[string]bool)
   958  	for _, port := range portsObjList.Items {
   959  		if len(port.Keys) == 0 {
   960  			return fmt.Errorf("ports must be named")
   961  		}
   962  		label := port.Keys[0].Token.Value().(string)
   963  		if !reDynamicPorts.MatchString(label) {
   964  			return errPortLabel
   965  		}
   966  		l := strings.ToLower(label)
   967  		if knownPortLabels[l] {
   968  			return fmt.Errorf("found a port label collision: %s", label)
   969  		}
   970  		var p map[string]interface{}
   971  		var res structs.Port
   972  		if err := hcl.DecodeObject(&p, port.Val); err != nil {
   973  			return err
   974  		}
   975  		if err := mapstructure.WeakDecode(p, &res); err != nil {
   976  			return err
   977  		}
   978  		res.Label = label
   979  		if res.Value > 0 {
   980  			nw.ReservedPorts = append(nw.ReservedPorts, res)
   981  		} else {
   982  			nw.DynamicPorts = append(nw.DynamicPorts, res)
   983  		}
   984  		knownPortLabels[l] = true
   985  	}
   986  	return nil
   987  }
   988  
   989  func parseUpdate(result *structs.UpdateStrategy, list *ast.ObjectList) error {
   990  	list = list.Elem()
   991  	if len(list.Items) > 1 {
   992  		return fmt.Errorf("only one 'update' block allowed per job")
   993  	}
   994  
   995  	// Get our resource object
   996  	o := list.Items[0]
   997  
   998  	var m map[string]interface{}
   999  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
  1000  		return err
  1001  	}
  1002  
  1003  	// Check for invalid keys
  1004  	valid := []string{
  1005  		"stagger",
  1006  		"max_parallel",
  1007  	}
  1008  	if err := checkHCLKeys(o.Val, valid); err != nil {
  1009  		return err
  1010  	}
  1011  
  1012  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
  1013  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
  1014  		WeaklyTypedInput: true,
  1015  		Result:           result,
  1016  	})
  1017  	if err != nil {
  1018  		return err
  1019  	}
  1020  	return dec.Decode(m)
  1021  }
  1022  
  1023  func parsePeriodic(result **structs.PeriodicConfig, list *ast.ObjectList) error {
  1024  	list = list.Elem()
  1025  	if len(list.Items) > 1 {
  1026  		return fmt.Errorf("only one 'periodic' block allowed per job")
  1027  	}
  1028  
  1029  	// Get our resource object
  1030  	o := list.Items[0]
  1031  
  1032  	var m map[string]interface{}
  1033  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
  1034  		return err
  1035  	}
  1036  
  1037  	// Check for invalid keys
  1038  	valid := []string{
  1039  		"enabled",
  1040  		"cron",
  1041  		"prohibit_overlap",
  1042  	}
  1043  	if err := checkHCLKeys(o.Val, valid); err != nil {
  1044  		return err
  1045  	}
  1046  
  1047  	// Enabled by default if the periodic block exists.
  1048  	if value, ok := m["enabled"]; !ok {
  1049  		m["Enabled"] = true
  1050  	} else {
  1051  		enabled, err := parseBool(value)
  1052  		if err != nil {
  1053  			return fmt.Errorf("periodic.enabled should be set to true or false; %v", err)
  1054  		}
  1055  		m["Enabled"] = enabled
  1056  	}
  1057  
  1058  	// If "cron" is provided, set the type to "cron" and store the spec.
  1059  	if cron, ok := m["cron"]; ok {
  1060  		m["SpecType"] = structs.PeriodicSpecCron
  1061  		m["Spec"] = cron
  1062  	}
  1063  
  1064  	// Build the constraint
  1065  	var p structs.PeriodicConfig
  1066  	if err := mapstructure.WeakDecode(m, &p); err != nil {
  1067  		return err
  1068  	}
  1069  	*result = &p
  1070  	return nil
  1071  }
  1072  
  1073  func parseVault(result *structs.Vault, list *ast.ObjectList) error {
  1074  	list = list.Elem()
  1075  	if len(list.Items) == 0 {
  1076  		return nil
  1077  	}
  1078  	if len(list.Items) > 1 {
  1079  		return fmt.Errorf("only one 'vault' block allowed per task")
  1080  	}
  1081  
  1082  	// Get our resource object
  1083  	o := list.Items[0]
  1084  
  1085  	// We need this later
  1086  	var listVal *ast.ObjectList
  1087  	if ot, ok := o.Val.(*ast.ObjectType); ok {
  1088  		listVal = ot.List
  1089  	} else {
  1090  		return fmt.Errorf("vault: should be an object")
  1091  	}
  1092  
  1093  	// Check for invalid keys
  1094  	valid := []string{
  1095  		"policies",
  1096  	}
  1097  	if err := checkHCLKeys(listVal, valid); err != nil {
  1098  		return multierror.Prefix(err, "vault ->")
  1099  	}
  1100  
  1101  	var m map[string]interface{}
  1102  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
  1103  		return err
  1104  	}
  1105  
  1106  	if err := mapstructure.WeakDecode(m, result); err != nil {
  1107  		return err
  1108  	}
  1109  
  1110  	return nil
  1111  }
  1112  
  1113  func checkHCLKeys(node ast.Node, valid []string) error {
  1114  	var list *ast.ObjectList
  1115  	switch n := node.(type) {
  1116  	case *ast.ObjectList:
  1117  		list = n
  1118  	case *ast.ObjectType:
  1119  		list = n.List
  1120  	default:
  1121  		return fmt.Errorf("cannot check HCL keys of type %T", n)
  1122  	}
  1123  
  1124  	validMap := make(map[string]struct{}, len(valid))
  1125  	for _, v := range valid {
  1126  		validMap[v] = struct{}{}
  1127  	}
  1128  
  1129  	var result error
  1130  	for _, item := range list.Items {
  1131  		key := item.Keys[0].Token.Value().(string)
  1132  		if _, ok := validMap[key]; !ok {
  1133  			result = multierror.Append(result, fmt.Errorf(
  1134  				"invalid key: %s", key))
  1135  		}
  1136  	}
  1137  
  1138  	return result
  1139  }