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