github.com/uchennaokeke444/nomad@v0.11.8/jobspec/parse_group.go (about)

     1  package jobspec
     2  
     3  import (
     4  	"fmt"
     5  
     6  	multierror "github.com/hashicorp/go-multierror"
     7  	"github.com/hashicorp/hcl"
     8  	"github.com/hashicorp/hcl/hcl/ast"
     9  	"github.com/hashicorp/nomad/api"
    10  	"github.com/hashicorp/nomad/helper"
    11  	"github.com/mitchellh/mapstructure"
    12  )
    13  
    14  func parseGroups(result *api.Job, list *ast.ObjectList) error {
    15  	list = list.Children()
    16  	if len(list.Items) == 0 {
    17  		return nil
    18  	}
    19  
    20  	// Go through each object and turn it into an actual result.
    21  	collection := make([]*api.TaskGroup, 0, len(list.Items))
    22  	seen := make(map[string]struct{})
    23  	for _, item := range list.Items {
    24  		n := item.Keys[0].Token.Value().(string)
    25  
    26  		// Make sure we haven't already found this
    27  		if _, ok := seen[n]; ok {
    28  			return fmt.Errorf("group '%s' defined more than once", n)
    29  		}
    30  		seen[n] = struct{}{}
    31  
    32  		// We need this later
    33  		var listVal *ast.ObjectList
    34  		if ot, ok := item.Val.(*ast.ObjectType); ok {
    35  			listVal = ot.List
    36  		} else {
    37  			return fmt.Errorf("group '%s': should be an object", n)
    38  		}
    39  
    40  		// Check for invalid keys
    41  		valid := []string{
    42  			"count",
    43  			"constraint",
    44  			"affinity",
    45  			"restart",
    46  			"meta",
    47  			"task",
    48  			"ephemeral_disk",
    49  			"update",
    50  			"reschedule",
    51  			"vault",
    52  			"migrate",
    53  			"spread",
    54  			"shutdown_delay",
    55  			"network",
    56  			"service",
    57  			"volume",
    58  			"scaling",
    59  			"stop_after_client_disconnect",
    60  		}
    61  		if err := helper.CheckHCLKeys(listVal, valid); err != nil {
    62  			return multierror.Prefix(err, fmt.Sprintf("'%s' ->", n))
    63  		}
    64  
    65  		var m map[string]interface{}
    66  		if err := hcl.DecodeObject(&m, item.Val); err != nil {
    67  			return err
    68  		}
    69  
    70  		delete(m, "constraint")
    71  		delete(m, "affinity")
    72  		delete(m, "meta")
    73  		delete(m, "task")
    74  		delete(m, "restart")
    75  		delete(m, "ephemeral_disk")
    76  		delete(m, "update")
    77  		delete(m, "vault")
    78  		delete(m, "migrate")
    79  		delete(m, "spread")
    80  		delete(m, "network")
    81  		delete(m, "service")
    82  		delete(m, "volume")
    83  		delete(m, "scaling")
    84  
    85  		// Build the group with the basic decode
    86  		var g api.TaskGroup
    87  		g.Name = helper.StringToPtr(n)
    88  		dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
    89  			DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
    90  			WeaklyTypedInput: true,
    91  			Result:           &g,
    92  		})
    93  
    94  		if err != nil {
    95  			return err
    96  		}
    97  		if err := dec.Decode(m); err != nil {
    98  			return err
    99  		}
   100  
   101  		// Parse constraints
   102  		if o := listVal.Filter("constraint"); len(o.Items) > 0 {
   103  			if err := parseConstraints(&g.Constraints, o); err != nil {
   104  				return multierror.Prefix(err, fmt.Sprintf("'%s', constraint ->", n))
   105  			}
   106  		}
   107  
   108  		// Parse affinities
   109  		if o := listVal.Filter("affinity"); len(o.Items) > 0 {
   110  			if err := parseAffinities(&g.Affinities, o); err != nil {
   111  				return multierror.Prefix(err, fmt.Sprintf("'%s', affinity ->", n))
   112  			}
   113  		}
   114  
   115  		// Parse restart policy
   116  		if o := listVal.Filter("restart"); len(o.Items) > 0 {
   117  			if err := parseRestartPolicy(&g.RestartPolicy, o); err != nil {
   118  				return multierror.Prefix(err, fmt.Sprintf("'%s', restart ->", n))
   119  			}
   120  		}
   121  
   122  		// Parse spread
   123  		if o := listVal.Filter("spread"); len(o.Items) > 0 {
   124  			if err := parseSpread(&g.Spreads, o); err != nil {
   125  				return multierror.Prefix(err, "spread ->")
   126  			}
   127  		}
   128  
   129  		// Parse network
   130  		if o := listVal.Filter("network"); len(o.Items) > 0 {
   131  			networks, err := ParseNetwork(o)
   132  			if err != nil {
   133  				return err
   134  			}
   135  			g.Networks = []*api.NetworkResource{networks}
   136  		}
   137  
   138  		// Parse reschedule policy
   139  		if o := listVal.Filter("reschedule"); len(o.Items) > 0 {
   140  			if err := parseReschedulePolicy(&g.ReschedulePolicy, o); err != nil {
   141  				return multierror.Prefix(err, fmt.Sprintf("'%s', reschedule ->", n))
   142  			}
   143  		}
   144  		// Parse ephemeral disk
   145  		if o := listVal.Filter("ephemeral_disk"); len(o.Items) > 0 {
   146  			g.EphemeralDisk = &api.EphemeralDisk{}
   147  			if err := parseEphemeralDisk(&g.EphemeralDisk, o); err != nil {
   148  				return multierror.Prefix(err, fmt.Sprintf("'%s', ephemeral_disk ->", n))
   149  			}
   150  		}
   151  
   152  		// If we have an update strategy, then parse that
   153  		if o := listVal.Filter("update"); len(o.Items) > 0 {
   154  			if err := parseUpdate(&g.Update, o); err != nil {
   155  				return multierror.Prefix(err, "update ->")
   156  			}
   157  		}
   158  
   159  		// If we have a migration strategy, then parse that
   160  		if o := listVal.Filter("migrate"); len(o.Items) > 0 {
   161  			if err := parseMigrate(&g.Migrate, o); err != nil {
   162  				return multierror.Prefix(err, "migrate ->")
   163  			}
   164  		}
   165  
   166  		// Parse out meta fields. These are in HCL as a list so we need
   167  		// to iterate over them and merge them.
   168  		if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 {
   169  			for _, o := range metaO.Elem().Items {
   170  				var m map[string]interface{}
   171  				if err := hcl.DecodeObject(&m, o.Val); err != nil {
   172  					return err
   173  				}
   174  				if err := mapstructure.WeakDecode(m, &g.Meta); err != nil {
   175  					return err
   176  				}
   177  			}
   178  		}
   179  
   180  		// Parse any volume declarations
   181  		if o := listVal.Filter("volume"); len(o.Items) > 0 {
   182  			if err := parseVolumes(&g.Volumes, o); err != nil {
   183  				return multierror.Prefix(err, "volume ->")
   184  			}
   185  		}
   186  
   187  		// Parse scaling policy
   188  		if o := listVal.Filter("scaling"); len(o.Items) > 0 {
   189  			if err := parseScalingPolicy(&g.Scaling, o); err != nil {
   190  				return multierror.Prefix(err, "scaling ->")
   191  			}
   192  		}
   193  
   194  		// Parse tasks
   195  		if o := listVal.Filter("task"); len(o.Items) > 0 {
   196  			if err := parseTasks(&g.Tasks, o); err != nil {
   197  				return multierror.Prefix(err, fmt.Sprintf("'%s', task:", n))
   198  			}
   199  		}
   200  
   201  		// If we have a vault block, then parse that
   202  		if o := listVal.Filter("vault"); len(o.Items) > 0 {
   203  			tgVault := &api.Vault{
   204  				Env:        helper.BoolToPtr(true),
   205  				ChangeMode: helper.StringToPtr("restart"),
   206  			}
   207  
   208  			if err := parseVault(tgVault, o); err != nil {
   209  				return multierror.Prefix(err, fmt.Sprintf("'%s', vault ->", n))
   210  			}
   211  
   212  			// Go through the tasks and if they don't have a Vault block, set it
   213  			for _, task := range g.Tasks {
   214  				if task.Vault == nil {
   215  					task.Vault = tgVault
   216  				}
   217  			}
   218  		}
   219  
   220  		if o := listVal.Filter("service"); len(o.Items) > 0 {
   221  			if err := parseGroupServices(&g, o); err != nil {
   222  				return multierror.Prefix(err, fmt.Sprintf("'%s',", n))
   223  			}
   224  		}
   225  		collection = append(collection, &g)
   226  	}
   227  
   228  	result.TaskGroups = append(result.TaskGroups, collection...)
   229  	return nil
   230  }
   231  
   232  func parseEphemeralDisk(result **api.EphemeralDisk, list *ast.ObjectList) error {
   233  	list = list.Elem()
   234  	if len(list.Items) > 1 {
   235  		return fmt.Errorf("only one 'ephemeral_disk' block allowed")
   236  	}
   237  
   238  	// Get our ephemeral_disk object
   239  	obj := list.Items[0]
   240  
   241  	// Check for invalid keys
   242  	valid := []string{
   243  		"sticky",
   244  		"size",
   245  		"migrate",
   246  	}
   247  	if err := helper.CheckHCLKeys(obj.Val, valid); err != nil {
   248  		return err
   249  	}
   250  
   251  	var m map[string]interface{}
   252  	if err := hcl.DecodeObject(&m, obj.Val); err != nil {
   253  		return err
   254  	}
   255  
   256  	var ephemeralDisk api.EphemeralDisk
   257  	if err := mapstructure.WeakDecode(m, &ephemeralDisk); err != nil {
   258  		return err
   259  	}
   260  	*result = &ephemeralDisk
   261  
   262  	return nil
   263  }
   264  
   265  func parseRestartPolicy(final **api.RestartPolicy, list *ast.ObjectList) error {
   266  	list = list.Elem()
   267  	if len(list.Items) > 1 {
   268  		return fmt.Errorf("only one 'restart' block allowed")
   269  	}
   270  
   271  	// Get our job object
   272  	obj := list.Items[0]
   273  
   274  	// Check for invalid keys
   275  	valid := []string{
   276  		"attempts",
   277  		"interval",
   278  		"delay",
   279  		"mode",
   280  	}
   281  	if err := helper.CheckHCLKeys(obj.Val, valid); err != nil {
   282  		return err
   283  	}
   284  
   285  	var m map[string]interface{}
   286  	if err := hcl.DecodeObject(&m, obj.Val); err != nil {
   287  		return err
   288  	}
   289  
   290  	var result api.RestartPolicy
   291  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   292  		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
   293  		WeaklyTypedInput: true,
   294  		Result:           &result,
   295  	})
   296  	if err != nil {
   297  		return err
   298  	}
   299  	if err := dec.Decode(m); err != nil {
   300  		return err
   301  	}
   302  
   303  	*final = &result
   304  	return nil
   305  }
   306  
   307  func parseVolumes(out *map[string]*api.VolumeRequest, list *ast.ObjectList) error {
   308  	hcl.DecodeObject(out, list)
   309  
   310  	for k, v := range *out {
   311  		err := helper.UnusedKeys(v)
   312  		if err != nil {
   313  			return err
   314  		}
   315  		// This is supported by `hcl:",key"`, but that only works if we start at the
   316  		// parent ast.ObjectItem
   317  		v.Name = k
   318  	}
   319  
   320  	return nil
   321  }
   322  
   323  func parseScalingPolicy(out **api.ScalingPolicy, list *ast.ObjectList) error {
   324  	list = list.Elem()
   325  	if len(list.Items) > 1 {
   326  		return fmt.Errorf("only one 'scaling' block allowed")
   327  	}
   328  
   329  	// Get our resource object
   330  	o := list.Items[0]
   331  
   332  	// We need this later
   333  	var listVal *ast.ObjectList
   334  	if ot, ok := o.Val.(*ast.ObjectType); ok {
   335  		listVal = ot.List
   336  	} else {
   337  		return fmt.Errorf("should be an object")
   338  	}
   339  
   340  	valid := []string{
   341  		"min",
   342  		"max",
   343  		"policy",
   344  		"enabled",
   345  	}
   346  	if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
   347  		return err
   348  	}
   349  
   350  	var m map[string]interface{}
   351  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
   352  		return err
   353  	}
   354  	delete(m, "policy")
   355  
   356  	var result api.ScalingPolicy
   357  	dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
   358  		WeaklyTypedInput: true,
   359  		Result:           &result,
   360  	})
   361  	if err != nil {
   362  		return err
   363  	}
   364  	if err := dec.Decode(m); err != nil {
   365  		return err
   366  	}
   367  
   368  	// If we have policy, then parse that
   369  	if o := listVal.Filter("policy"); len(o.Items) > 0 {
   370  		if len(o.Elem().Items) > 1 {
   371  			return fmt.Errorf("only one 'policy' block allowed per 'scaling' block")
   372  		}
   373  		p := o.Elem().Items[0]
   374  		var m map[string]interface{}
   375  		if err := hcl.DecodeObject(&m, p.Val); err != nil {
   376  			return err
   377  		}
   378  		if err := mapstructure.WeakDecode(m, &result.Policy); err != nil {
   379  			return err
   380  		}
   381  	}
   382  
   383  	*out = &result
   384  	return nil
   385  }