github.com/kardianos/nomad@v0.1.3-0.20151022182107-b13df73ee850/jobspec/parse.go (about)

     1  package jobspec
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"regexp"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/hashicorp/hcl"
    14  	hclobj "github.com/hashicorp/hcl/hcl"
    15  	"github.com/hashicorp/nomad/nomad/structs"
    16  	"github.com/mitchellh/mapstructure"
    17  )
    18  
    19  // Parse parses the job spec from the given io.Reader.
    20  //
    21  // Due to current internal limitations, the entire contents of the
    22  // io.Reader will be copied into memory first before parsing.
    23  func Parse(r io.Reader) (*structs.Job, error) {
    24  	// Copy the reader into an in-memory buffer first since HCL requires it.
    25  	var buf bytes.Buffer
    26  	if _, err := io.Copy(&buf, r); err != nil {
    27  		return nil, err
    28  	}
    29  
    30  	// Parse the buffer
    31  	obj, err := hcl.Parse(buf.String())
    32  	if err != nil {
    33  		return nil, fmt.Errorf("error parsing: %s", err)
    34  	}
    35  	buf.Reset()
    36  
    37  	var job structs.Job
    38  
    39  	// Parse the job out
    40  	jobO := obj.Get("job", false)
    41  	if jobO == nil {
    42  		return nil, fmt.Errorf("'job' stanza not found")
    43  	}
    44  	if err := parseJob(&job, jobO); err != nil {
    45  		return nil, fmt.Errorf("error parsing 'job': %s", err)
    46  	}
    47  
    48  	return &job, nil
    49  }
    50  
    51  // ParseFile parses the given path as a job spec.
    52  func ParseFile(path string) (*structs.Job, error) {
    53  	path, err := filepath.Abs(path)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	f, err := os.Open(path)
    59  	if err != nil {
    60  		return nil, err
    61  	}
    62  	defer f.Close()
    63  
    64  	return Parse(f)
    65  }
    66  
    67  func parseJob(result *structs.Job, obj *hclobj.Object) error {
    68  	if obj.Len() > 1 {
    69  		return fmt.Errorf("only one 'job' block allowed")
    70  	}
    71  
    72  	// Get our job object
    73  	obj = obj.Elem(true)[0]
    74  
    75  	// Decode the full thing into a map[string]interface for ease
    76  	var m map[string]interface{}
    77  	if err := hcl.DecodeObject(&m, obj); err != nil {
    78  		return err
    79  	}
    80  	delete(m, "constraint")
    81  	delete(m, "meta")
    82  	delete(m, "update")
    83  
    84  	// Set the ID and name to the object key
    85  	result.ID = obj.Key
    86  	result.Name = obj.Key
    87  
    88  	// Defaults
    89  	result.Priority = 50
    90  	result.Region = "global"
    91  	result.Type = "service"
    92  
    93  	// Decode the rest
    94  	if err := mapstructure.WeakDecode(m, result); err != nil {
    95  		return err
    96  	}
    97  
    98  	// Parse constraints
    99  	if o := obj.Get("constraint", false); o != nil {
   100  		if err := parseConstraints(&result.Constraints, o); err != nil {
   101  			return err
   102  		}
   103  	}
   104  
   105  	// If we have an update strategy, then parse that
   106  	if o := obj.Get("update", false); o != nil {
   107  		if err := parseUpdate(&result.Update, o); err != nil {
   108  			return err
   109  		}
   110  	}
   111  
   112  	// Parse out meta fields. These are in HCL as a list so we need
   113  	// to iterate over them and merge them.
   114  	if metaO := obj.Get("meta", false); metaO != nil {
   115  		for _, o := range metaO.Elem(false) {
   116  			var m map[string]interface{}
   117  			if err := hcl.DecodeObject(&m, o); err != nil {
   118  				return err
   119  			}
   120  			if err := mapstructure.WeakDecode(m, &result.Meta); err != nil {
   121  				return err
   122  			}
   123  		}
   124  	}
   125  
   126  	// If we have tasks outside, do those
   127  	if o := obj.Get("task", false); o != nil {
   128  		var tasks []*structs.Task
   129  		if err := parseTasks(&tasks, o); err != nil {
   130  			return err
   131  		}
   132  
   133  		result.TaskGroups = make([]*structs.TaskGroup, len(tasks), len(tasks)*2)
   134  		for i, t := range tasks {
   135  			result.TaskGroups[i] = &structs.TaskGroup{
   136  				Name:  t.Name,
   137  				Count: 1,
   138  				Tasks: []*structs.Task{t},
   139  			}
   140  		}
   141  	}
   142  
   143  	// Parse the task groups
   144  	if o := obj.Get("group", false); o != nil {
   145  		if err := parseGroups(result, o); err != nil {
   146  			return fmt.Errorf("error parsing 'group': %s", err)
   147  		}
   148  	}
   149  
   150  	return nil
   151  }
   152  
   153  func parseGroups(result *structs.Job, obj *hclobj.Object) error {
   154  	// Get all the maps of keys to the actual object
   155  	objects := make(map[string]*hclobj.Object)
   156  	for _, o1 := range obj.Elem(false) {
   157  		for _, o2 := range o1.Elem(true) {
   158  			if _, ok := objects[o2.Key]; ok {
   159  				return fmt.Errorf(
   160  					"group '%s' defined more than once",
   161  					o2.Key)
   162  			}
   163  
   164  			objects[o2.Key] = o2
   165  		}
   166  	}
   167  
   168  	if len(objects) == 0 {
   169  		return nil
   170  	}
   171  
   172  	// Go through each object and turn it into an actual result.
   173  	collection := make([]*structs.TaskGroup, 0, len(objects))
   174  	for n, o := range objects {
   175  		var m map[string]interface{}
   176  		if err := hcl.DecodeObject(&m, o); err != nil {
   177  			return err
   178  		}
   179  		delete(m, "constraint")
   180  		delete(m, "meta")
   181  		delete(m, "task")
   182  
   183  		// Default count to 1 if not specified
   184  		if _, ok := m["count"]; !ok {
   185  			m["count"] = 1
   186  		}
   187  
   188  		// Build the group with the basic decode
   189  		var g structs.TaskGroup
   190  		g.Name = n
   191  		if err := mapstructure.WeakDecode(m, &g); err != nil {
   192  			return err
   193  		}
   194  
   195  		// Parse constraints
   196  		if o := o.Get("constraint", false); o != nil {
   197  			if err := parseConstraints(&g.Constraints, o); err != nil {
   198  				return err
   199  			}
   200  		}
   201  
   202  		// Parse out meta fields. These are in HCL as a list so we need
   203  		// to iterate over them and merge them.
   204  		if metaO := o.Get("meta", false); metaO != nil {
   205  			for _, o := range metaO.Elem(false) {
   206  				var m map[string]interface{}
   207  				if err := hcl.DecodeObject(&m, o); err != nil {
   208  					return err
   209  				}
   210  				if err := mapstructure.WeakDecode(m, &g.Meta); err != nil {
   211  					return err
   212  				}
   213  			}
   214  		}
   215  
   216  		// Parse tasks
   217  		if o := o.Get("task", false); o != nil {
   218  			if err := parseTasks(&g.Tasks, o); err != nil {
   219  				return err
   220  			}
   221  		}
   222  
   223  		collection = append(collection, &g)
   224  	}
   225  
   226  	result.TaskGroups = append(result.TaskGroups, collection...)
   227  	return nil
   228  }
   229  
   230  func parseConstraints(result *[]*structs.Constraint, obj *hclobj.Object) error {
   231  	for _, o := range obj.Elem(false) {
   232  		var m map[string]interface{}
   233  		if err := hcl.DecodeObject(&m, o); err != nil {
   234  			return err
   235  		}
   236  		m["LTarget"] = m["attribute"]
   237  		m["RTarget"] = m["value"]
   238  		m["Operand"] = m["operator"]
   239  
   240  		// Default constraint to being hard
   241  		if _, ok := m["hard"]; !ok {
   242  			m["hard"] = true
   243  		}
   244  
   245  		// If "version" is provided, set the operand
   246  		// to "version" and the value to the "RTarget"
   247  		if constraint, ok := m["version"]; ok {
   248  			m["Operand"] = "version"
   249  			m["RTarget"] = constraint
   250  		}
   251  
   252  		// If "regexp" is provided, set the operand
   253  		// to "regexp" and the value to the "RTarget"
   254  		if constraint, ok := m["regexp"]; ok {
   255  			m["Operand"] = "regexp"
   256  			m["RTarget"] = constraint
   257  		}
   258  
   259  		// Build the constraint
   260  		var c structs.Constraint
   261  		if err := mapstructure.WeakDecode(m, &c); err != nil {
   262  			return err
   263  		}
   264  		if c.Operand == "" {
   265  			c.Operand = "="
   266  		}
   267  
   268  		*result = append(*result, &c)
   269  	}
   270  
   271  	return nil
   272  }
   273  
   274  func parseTasks(result *[]*structs.Task, obj *hclobj.Object) error {
   275  	// Get all the maps of keys to the actual object
   276  	objects := make([]*hclobj.Object, 0, 5)
   277  	set := make(map[string]struct{})
   278  	for _, o1 := range obj.Elem(false) {
   279  		for _, o2 := range o1.Elem(true) {
   280  			if _, ok := set[o2.Key]; ok {
   281  				return fmt.Errorf(
   282  					"group '%s' defined more than once",
   283  					o2.Key)
   284  			}
   285  
   286  			objects = append(objects, o2)
   287  			set[o2.Key] = struct{}{}
   288  		}
   289  	}
   290  
   291  	if len(objects) == 0 {
   292  		return nil
   293  	}
   294  
   295  	for _, o := range objects {
   296  		var m map[string]interface{}
   297  		if err := hcl.DecodeObject(&m, o); err != nil {
   298  			return err
   299  		}
   300  		delete(m, "config")
   301  		delete(m, "env")
   302  		delete(m, "constraint")
   303  		delete(m, "meta")
   304  		delete(m, "resources")
   305  
   306  		// Build the task
   307  		var t structs.Task
   308  		t.Name = o.Key
   309  		if err := mapstructure.WeakDecode(m, &t); err != nil {
   310  			return err
   311  		}
   312  
   313  		// If we have env, then parse them
   314  		if o := o.Get("env", false); o != nil {
   315  			for _, o := range o.Elem(false) {
   316  				var m map[string]interface{}
   317  				if err := hcl.DecodeObject(&m, o); err != nil {
   318  					return err
   319  				}
   320  				if err := mapstructure.WeakDecode(m, &t.Env); err != nil {
   321  					return err
   322  				}
   323  			}
   324  		}
   325  
   326  		// If we have config, then parse that
   327  		if o := o.Get("config", false); o != nil {
   328  			for _, o := range o.Elem(false) {
   329  				var m map[string]interface{}
   330  				if err := hcl.DecodeObject(&m, o); err != nil {
   331  					return err
   332  				}
   333  				if err := mapstructure.WeakDecode(m, &t.Config); err != nil {
   334  					return err
   335  				}
   336  			}
   337  		}
   338  
   339  		// Parse constraints
   340  		if o := o.Get("constraint", false); o != nil {
   341  			if err := parseConstraints(&t.Constraints, o); err != nil {
   342  				return err
   343  			}
   344  		}
   345  
   346  		// Parse out meta fields. These are in HCL as a list so we need
   347  		// to iterate over them and merge them.
   348  		if metaO := o.Get("meta", false); metaO != nil {
   349  			for _, o := range metaO.Elem(false) {
   350  				var m map[string]interface{}
   351  				if err := hcl.DecodeObject(&m, o); err != nil {
   352  					return err
   353  				}
   354  				if err := mapstructure.WeakDecode(m, &t.Meta); err != nil {
   355  					return err
   356  				}
   357  			}
   358  		}
   359  
   360  		// If we have resources, then parse that
   361  		if o := o.Get("resources", false); o != nil {
   362  			var r structs.Resources
   363  			if err := parseResources(&r, o); err != nil {
   364  				return fmt.Errorf("task '%s': %s", t.Name, err)
   365  			}
   366  
   367  			t.Resources = &r
   368  		}
   369  
   370  		*result = append(*result, &t)
   371  	}
   372  
   373  	return nil
   374  }
   375  
   376  var reDynamicPorts *regexp.Regexp = regexp.MustCompile("^[a-zA-Z0-9_]+$")
   377  var errDynamicPorts = fmt.Errorf("DynamicPort label does not conform to naming requirements %s", reDynamicPorts.String())
   378  
   379  func parseResources(result *structs.Resources, obj *hclobj.Object) error {
   380  	if obj.Len() > 1 {
   381  		return fmt.Errorf("only one 'resource' block allowed per task")
   382  	}
   383  
   384  	for _, o := range obj.Elem(false) {
   385  		var m map[string]interface{}
   386  		if err := hcl.DecodeObject(&m, o); err != nil {
   387  			return err
   388  		}
   389  		delete(m, "network")
   390  
   391  		if err := mapstructure.WeakDecode(m, result); err != nil {
   392  			return err
   393  		}
   394  
   395  		// Parse the network resources
   396  		if o := o.Get("network", false); o != nil {
   397  			if o.Len() > 1 {
   398  				return fmt.Errorf("only one 'network' resource allowed")
   399  			}
   400  
   401  			var r structs.NetworkResource
   402  			var m map[string]interface{}
   403  			if err := hcl.DecodeObject(&m, o); err != nil {
   404  				return err
   405  			}
   406  			if err := mapstructure.WeakDecode(m, &r); err != nil {
   407  				return err
   408  			}
   409  
   410  			// Keep track of labels we've already seen so we can ensure there
   411  			// are no collisions when we turn them into environment variables.
   412  			// lowercase:NomalCase so we can get the first for the error message
   413  			seenLabel := map[string]string{}
   414  
   415  			for _, label := range r.DynamicPorts {
   416  				if !reDynamicPorts.MatchString(label) {
   417  					return errDynamicPorts
   418  				}
   419  				first, seen := seenLabel[strings.ToLower(label)]
   420  				if seen {
   421  					return fmt.Errorf("Found a port label collision: `%s` overlaps with previous `%s`", label, first)
   422  				} else {
   423  					seenLabel[strings.ToLower(label)] = label
   424  				}
   425  
   426  			}
   427  
   428  			result.Networks = []*structs.NetworkResource{&r}
   429  		}
   430  
   431  	}
   432  
   433  	return nil
   434  }
   435  
   436  func parseUpdate(result *structs.UpdateStrategy, obj *hclobj.Object) error {
   437  	if obj.Len() > 1 {
   438  		return fmt.Errorf("only one 'update' block allowed per job")
   439  	}
   440  
   441  	for _, o := range obj.Elem(false) {
   442  		var m map[string]interface{}
   443  		if err := hcl.DecodeObject(&m, o); err != nil {
   444  			return err
   445  		}
   446  		for _, key := range []string{"stagger", "Stagger"} {
   447  			if raw, ok := m[key]; ok {
   448  				switch v := raw.(type) {
   449  				case string:
   450  					dur, err := time.ParseDuration(v)
   451  					if err != nil {
   452  						return fmt.Errorf("invalid stagger time '%s'", raw)
   453  					}
   454  					m[key] = dur
   455  				case int:
   456  					m[key] = time.Duration(v) * time.Second
   457  				default:
   458  					return fmt.Errorf("invalid type for stagger time '%s'",
   459  						raw)
   460  				}
   461  			}
   462  		}
   463  
   464  		if err := mapstructure.WeakDecode(m, result); err != nil {
   465  			return err
   466  		}
   467  	}
   468  	return nil
   469  }