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