github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/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  
    12  	multierror "github.com/hashicorp/go-multierror"
    13  	"github.com/hashicorp/hcl"
    14  	"github.com/hashicorp/hcl/hcl/ast"
    15  	"github.com/hashicorp/nomad/api"
    16  	"github.com/mitchellh/mapstructure"
    17  )
    18  
    19  var reDynamicPorts = regexp.MustCompile("^[a-zA-Z0-9_]+$")
    20  var errPortLabel = fmt.Errorf("Port label does not conform to naming requirements %s", reDynamicPorts.String())
    21  
    22  // Parse parses the job spec from the given io.Reader.
    23  //
    24  // Due to current internal limitations, the entire contents of the
    25  // io.Reader will be copied into memory first before parsing.
    26  func Parse(r io.Reader) (*api.Job, error) {
    27  	// Copy the reader into an in-memory buffer first since HCL requires it.
    28  	var buf bytes.Buffer
    29  	if _, err := io.Copy(&buf, r); err != nil {
    30  		return nil, err
    31  	}
    32  
    33  	// Parse the buffer
    34  	root, err := hcl.Parse(buf.String())
    35  	if err != nil {
    36  		return nil, fmt.Errorf("error parsing: %s", err)
    37  	}
    38  	buf.Reset()
    39  
    40  	// Top-level item should be a list
    41  	list, ok := root.Node.(*ast.ObjectList)
    42  	if !ok {
    43  		return nil, fmt.Errorf("error parsing: root should be an object")
    44  	}
    45  
    46  	// Check for invalid keys
    47  	valid := []string{
    48  		"job",
    49  	}
    50  	if err := checkHCLKeys(list, valid); err != nil {
    51  		return nil, err
    52  	}
    53  
    54  	var job api.Job
    55  
    56  	// Parse the job out
    57  	matches := list.Filter("job")
    58  	if len(matches.Items) == 0 {
    59  		return nil, fmt.Errorf("'job' stanza not found")
    60  	}
    61  	if err := parseJob(&job, matches); err != nil {
    62  		return nil, fmt.Errorf("error parsing 'job': %s", err)
    63  	}
    64  
    65  	return &job, nil
    66  }
    67  
    68  // ParseFile parses the given path as a job spec.
    69  func ParseFile(path string) (*api.Job, error) {
    70  	path, err := filepath.Abs(path)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	f, err := os.Open(path)
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  	defer f.Close()
    80  
    81  	return Parse(f)
    82  }
    83  
    84  func parseReschedulePolicy(final **api.ReschedulePolicy, list *ast.ObjectList) error {
    85  	list = list.Elem()
    86  	if len(list.Items) > 1 {
    87  		return fmt.Errorf("only one 'reschedule' block allowed")
    88  	}
    89  
    90  	// Get our job object
    91  	obj := list.Items[0]
    92  
    93  	// Check for invalid keys
    94  	valid := []string{
    95  		"attempts",
    96  		"interval",
    97  		"unlimited",
    98  		"delay",
    99  		"max_delay",
   100  		"delay_function",
   101  	}
   102  	if err := checkHCLKeys(obj.Val, valid); err != nil {
   103  		return err
   104  	}
   105  
   106  	var m map[string]interface{}
   107  	if err := hcl.DecodeObject(&m, obj.Val); err != nil {
   108  		return err
   109  	}
   110  
   111  	var result api.ReschedulePolicy
   112  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   113  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   114  		WeaklyTypedInput: true,
   115  		Result:           &result,
   116  	})
   117  	if err != nil {
   118  		return err
   119  	}
   120  	if err := dec.Decode(m); err != nil {
   121  		return err
   122  	}
   123  
   124  	*final = &result
   125  	return nil
   126  }
   127  
   128  func parseConstraints(result *[]*api.Constraint, list *ast.ObjectList) error {
   129  	for _, o := range list.Elem().Items {
   130  		// Check for invalid keys
   131  		valid := []string{
   132  			"attribute",
   133  			"distinct_hosts",
   134  			"distinct_property",
   135  			"operator",
   136  			"regexp",
   137  			"set_contains",
   138  			"value",
   139  			"version",
   140  			"semver",
   141  		}
   142  		if err := checkHCLKeys(o.Val, valid); err != nil {
   143  			return err
   144  		}
   145  
   146  		var m map[string]interface{}
   147  		if err := hcl.DecodeObject(&m, o.Val); err != nil {
   148  			return err
   149  		}
   150  
   151  		m["LTarget"] = m["attribute"]
   152  		m["RTarget"] = m["value"]
   153  		m["Operand"] = m["operator"]
   154  
   155  		// If "version" is provided, set the operand
   156  		// to "version" and the value to the "RTarget"
   157  		if constraint, ok := m[api.ConstraintVersion]; ok {
   158  			m["Operand"] = api.ConstraintVersion
   159  			m["RTarget"] = constraint
   160  		}
   161  
   162  		// If "semver" is provided, set the operand
   163  		// to "semver" and the value to the "RTarget"
   164  		if constraint, ok := m[api.ConstraintSemver]; ok {
   165  			m["Operand"] = api.ConstraintSemver
   166  			m["RTarget"] = constraint
   167  		}
   168  
   169  		// If "regexp" is provided, set the operand
   170  		// to "regexp" and the value to the "RTarget"
   171  		if constraint, ok := m[api.ConstraintRegex]; ok {
   172  			m["Operand"] = api.ConstraintRegex
   173  			m["RTarget"] = constraint
   174  		}
   175  
   176  		// If "set_contains" is provided, set the operand
   177  		// to "set_contains" and the value to the "RTarget"
   178  		if constraint, ok := m[api.ConstraintSetContains]; ok {
   179  			m["Operand"] = api.ConstraintSetContains
   180  			m["RTarget"] = constraint
   181  		}
   182  
   183  		if value, ok := m[api.ConstraintDistinctHosts]; ok {
   184  			enabled, err := parseBool(value)
   185  			if err != nil {
   186  				return fmt.Errorf("distinct_hosts should be set to true or false; %v", err)
   187  			}
   188  
   189  			// If it is not enabled, skip the constraint.
   190  			if !enabled {
   191  				continue
   192  			}
   193  
   194  			m["Operand"] = api.ConstraintDistinctHosts
   195  		}
   196  
   197  		if property, ok := m[api.ConstraintDistinctProperty]; ok {
   198  			m["Operand"] = api.ConstraintDistinctProperty
   199  			m["LTarget"] = property
   200  		}
   201  
   202  		// Build the constraint
   203  		var c api.Constraint
   204  		if err := mapstructure.WeakDecode(m, &c); err != nil {
   205  			return err
   206  		}
   207  		if c.Operand == "" {
   208  			c.Operand = "="
   209  		}
   210  
   211  		*result = append(*result, &c)
   212  	}
   213  
   214  	return nil
   215  }
   216  
   217  func parseAffinities(result *[]*api.Affinity, list *ast.ObjectList) error {
   218  	for _, o := range list.Elem().Items {
   219  		// Check for invalid keys
   220  		valid := []string{
   221  			"attribute",
   222  			"operator",
   223  			"regexp",
   224  			"set_contains",
   225  			"set_contains_any",
   226  			"set_contains_all",
   227  			"value",
   228  			"version",
   229  			"semver",
   230  			"weight",
   231  		}
   232  		if err := checkHCLKeys(o.Val, valid); err != nil {
   233  			return err
   234  		}
   235  
   236  		var m map[string]interface{}
   237  		if err := hcl.DecodeObject(&m, o.Val); err != nil {
   238  			return err
   239  		}
   240  
   241  		m["LTarget"] = m["attribute"]
   242  		m["RTarget"] = m["value"]
   243  		m["Operand"] = m["operator"]
   244  
   245  		// If "version" is provided, set the operand
   246  		// to "version" and the value to the "RTarget"
   247  		if affinity, ok := m[api.ConstraintVersion]; ok {
   248  			m["Operand"] = api.ConstraintVersion
   249  			m["RTarget"] = affinity
   250  		}
   251  
   252  		// If "semver" is provided, set the operand
   253  		// to "semver" and the value to the "RTarget"
   254  		if affinity, ok := m[api.ConstraintSemver]; ok {
   255  			m["Operand"] = api.ConstraintSemver
   256  			m["RTarget"] = affinity
   257  		}
   258  
   259  		// If "regexp" is provided, set the operand
   260  		// to "regexp" and the value to the "RTarget"
   261  		if affinity, ok := m[api.ConstraintRegex]; ok {
   262  			m["Operand"] = api.ConstraintRegex
   263  			m["RTarget"] = affinity
   264  		}
   265  
   266  		// If "set_contains_any" is provided, set the operand
   267  		// to "set_contains_any" and the value to the "RTarget"
   268  		if affinity, ok := m[api.ConstraintSetContainsAny]; ok {
   269  			m["Operand"] = api.ConstraintSetContainsAny
   270  			m["RTarget"] = affinity
   271  		}
   272  
   273  		// If "set_contains_all" is provided, set the operand
   274  		// to "set_contains_all" and the value to the "RTarget"
   275  		if affinity, ok := m[api.ConstraintSetContainsAll]; ok {
   276  			m["Operand"] = api.ConstraintSetContainsAll
   277  			m["RTarget"] = affinity
   278  		}
   279  
   280  		// set_contains is a synonym of set_contains_all
   281  		if affinity, ok := m[api.ConstraintSetContains]; ok {
   282  			m["Operand"] = api.ConstraintSetContains
   283  			m["RTarget"] = affinity
   284  		}
   285  
   286  		// Build the affinity
   287  		var a api.Affinity
   288  		if err := mapstructure.WeakDecode(m, &a); err != nil {
   289  			return err
   290  		}
   291  		if a.Operand == "" {
   292  			a.Operand = "="
   293  		}
   294  
   295  		*result = append(*result, &a)
   296  	}
   297  
   298  	return nil
   299  }
   300  
   301  func parseSpread(result *[]*api.Spread, list *ast.ObjectList) error {
   302  	for _, o := range list.Elem().Items {
   303  		// Check for invalid keys
   304  		valid := []string{
   305  			"attribute",
   306  			"weight",
   307  			"target",
   308  		}
   309  		if err := checkHCLKeys(o.Val, valid); err != nil {
   310  			return err
   311  		}
   312  
   313  		// We need this later
   314  		var listVal *ast.ObjectList
   315  		if ot, ok := o.Val.(*ast.ObjectType); ok {
   316  			listVal = ot.List
   317  		} else {
   318  			return fmt.Errorf("spread should be an object")
   319  		}
   320  
   321  		var m map[string]interface{}
   322  		if err := hcl.DecodeObject(&m, o.Val); err != nil {
   323  			return err
   324  		}
   325  		delete(m, "target")
   326  		// Build spread
   327  		var s api.Spread
   328  		if err := mapstructure.WeakDecode(m, &s); err != nil {
   329  			return err
   330  		}
   331  
   332  		// Parse spread target
   333  		if o := listVal.Filter("target"); len(o.Items) > 0 {
   334  			if err := parseSpreadTarget(&s.SpreadTarget, o); err != nil {
   335  				return multierror.Prefix(err, "target ->")
   336  			}
   337  		}
   338  
   339  		*result = append(*result, &s)
   340  	}
   341  
   342  	return nil
   343  }
   344  
   345  func parseSpreadTarget(result *[]*api.SpreadTarget, list *ast.ObjectList) error {
   346  	seen := make(map[string]struct{})
   347  	for _, item := range list.Items {
   348  		if len(item.Keys) != 1 {
   349  			return fmt.Errorf("missing spread target")
   350  		}
   351  		n := item.Keys[0].Token.Value().(string)
   352  
   353  		// Make sure we haven't already found this
   354  		if _, ok := seen[n]; ok {
   355  			return fmt.Errorf("target '%s' defined more than once", n)
   356  		}
   357  		seen[n] = struct{}{}
   358  
   359  		// We need this later
   360  		var listVal *ast.ObjectList
   361  		if ot, ok := item.Val.(*ast.ObjectType); ok {
   362  			listVal = ot.List
   363  		} else {
   364  			return fmt.Errorf("target should be an object")
   365  		}
   366  
   367  		// Check for invalid keys
   368  		valid := []string{
   369  			"percent",
   370  			"value",
   371  		}
   372  		if err := checkHCLKeys(listVal, valid); err != nil {
   373  			return multierror.Prefix(err, fmt.Sprintf("'%s' ->", n))
   374  		}
   375  
   376  		var m map[string]interface{}
   377  		if err := hcl.DecodeObject(&m, item.Val); err != nil {
   378  			return err
   379  		}
   380  
   381  		// Decode spread target
   382  		var g api.SpreadTarget
   383  		g.Value = n
   384  		if err := mapstructure.WeakDecode(m, &g); err != nil {
   385  			return err
   386  		}
   387  		*result = append(*result, &g)
   388  	}
   389  	return nil
   390  }
   391  
   392  // parseBool takes an interface value and tries to convert it to a boolean and
   393  // returns an error if the type can't be converted.
   394  func parseBool(value interface{}) (bool, error) {
   395  	var enabled bool
   396  	var err error
   397  	switch data := value.(type) {
   398  	case string:
   399  		enabled, err = strconv.ParseBool(data)
   400  	case bool:
   401  		enabled = data
   402  	default:
   403  		err = fmt.Errorf("%v couldn't be converted to boolean value", value)
   404  	}
   405  
   406  	return enabled, err
   407  }
   408  
   409  func parseUpdate(result **api.UpdateStrategy, list *ast.ObjectList) error {
   410  	list = list.Elem()
   411  	if len(list.Items) > 1 {
   412  		return fmt.Errorf("only one 'update' block allowed")
   413  	}
   414  
   415  	// Get our resource object
   416  	o := list.Items[0]
   417  
   418  	var m map[string]interface{}
   419  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
   420  		return err
   421  	}
   422  
   423  	// Check for invalid keys
   424  	valid := []string{
   425  		"stagger",
   426  		"max_parallel",
   427  		"health_check",
   428  		"min_healthy_time",
   429  		"healthy_deadline",
   430  		"progress_deadline",
   431  		"auto_revert",
   432  		"auto_promote",
   433  		"canary",
   434  	}
   435  	if err := checkHCLKeys(o.Val, valid); err != nil {
   436  		return err
   437  	}
   438  
   439  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   440  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   441  		WeaklyTypedInput: true,
   442  		Result:           result,
   443  	})
   444  	if err != nil {
   445  		return err
   446  	}
   447  	return dec.Decode(m)
   448  }
   449  
   450  func parseMigrate(result **api.MigrateStrategy, list *ast.ObjectList) error {
   451  	list = list.Elem()
   452  	if len(list.Items) > 1 {
   453  		return fmt.Errorf("only one 'migrate' block allowed")
   454  	}
   455  
   456  	// Get our resource object
   457  	o := list.Items[0]
   458  
   459  	var m map[string]interface{}
   460  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
   461  		return err
   462  	}
   463  
   464  	// Check for invalid keys
   465  	valid := []string{
   466  		"max_parallel",
   467  		"health_check",
   468  		"min_healthy_time",
   469  		"healthy_deadline",
   470  	}
   471  	if err := checkHCLKeys(o.Val, valid); err != nil {
   472  		return err
   473  	}
   474  
   475  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   476  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   477  		WeaklyTypedInput: true,
   478  		Result:           result,
   479  	})
   480  	if err != nil {
   481  		return err
   482  	}
   483  	return dec.Decode(m)
   484  }
   485  
   486  func parseVault(result *api.Vault, list *ast.ObjectList) error {
   487  	list = list.Elem()
   488  	if len(list.Items) == 0 {
   489  		return nil
   490  	}
   491  	if len(list.Items) > 1 {
   492  		return fmt.Errorf("only one 'vault' block allowed per task")
   493  	}
   494  
   495  	// Get our resource object
   496  	o := list.Items[0]
   497  
   498  	// We need this later
   499  	var listVal *ast.ObjectList
   500  	if ot, ok := o.Val.(*ast.ObjectType); ok {
   501  		listVal = ot.List
   502  	} else {
   503  		return fmt.Errorf("vault: should be an object")
   504  	}
   505  
   506  	// Check for invalid keys
   507  	valid := []string{
   508  		"namespace",
   509  		"policies",
   510  		"env",
   511  		"change_mode",
   512  		"change_signal",
   513  	}
   514  	if err := checkHCLKeys(listVal, valid); err != nil {
   515  		return multierror.Prefix(err, "vault ->")
   516  	}
   517  
   518  	var m map[string]interface{}
   519  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
   520  		return err
   521  	}
   522  
   523  	if err := mapstructure.WeakDecode(m, result); err != nil {
   524  		return err
   525  	}
   526  
   527  	return nil
   528  }