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