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