github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/jobspec/parse_job.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 parseJob(result *api.Job, list *ast.ObjectList) error {
    14  	if len(list.Items) != 1 {
    15  		return fmt.Errorf("only one 'job' block allowed")
    16  	}
    17  	list = list.Children()
    18  	if len(list.Items) != 1 {
    19  		return fmt.Errorf("'job' block missing name")
    20  	}
    21  
    22  	// Get our job object
    23  	obj := list.Items[0]
    24  
    25  	// Decode the full thing into a map[string]interface for ease
    26  	var m map[string]interface{}
    27  	if err := hcl.DecodeObject(&m, obj.Val); err != nil {
    28  		return err
    29  	}
    30  	delete(m, "constraint")
    31  	delete(m, "affinity")
    32  	delete(m, "meta")
    33  	delete(m, "migrate")
    34  	delete(m, "parameterized")
    35  	delete(m, "periodic")
    36  	delete(m, "reschedule")
    37  	delete(m, "update")
    38  	delete(m, "vault")
    39  	delete(m, "spread")
    40  	delete(m, "multiregion")
    41  
    42  	// Set the ID and name to the object key
    43  	result.ID = stringToPtr(obj.Keys[0].Token.Value().(string))
    44  	result.Name = stringToPtr(*result.ID)
    45  
    46  	// Decode the rest
    47  	if err := mapstructure.WeakDecode(m, result); err != nil {
    48  		return err
    49  	}
    50  
    51  	// Value should be an object
    52  	var listVal *ast.ObjectList
    53  	if ot, ok := obj.Val.(*ast.ObjectType); ok {
    54  		listVal = ot.List
    55  	} else {
    56  		return fmt.Errorf("job '%s' value: should be an object", *result.ID)
    57  	}
    58  
    59  	// Check for invalid keys
    60  	valid := []string{
    61  		"all_at_once",
    62  		"constraint",
    63  		"affinity",
    64  		"spread",
    65  		"datacenters",
    66  		"group",
    67  		"id",
    68  		"meta",
    69  		"migrate",
    70  		"name",
    71  		"namespace",
    72  		"parameterized",
    73  		"periodic",
    74  		"priority",
    75  		"region",
    76  		"reschedule",
    77  		"task",
    78  		"type",
    79  		"update",
    80  		"vault",
    81  		"vault_token",
    82  		"consul_token",
    83  		"multiregion",
    84  	}
    85  	if err := checkHCLKeys(listVal, valid); err != nil {
    86  		return multierror.Prefix(err, "job:")
    87  	}
    88  
    89  	// Parse constraints
    90  	if o := listVal.Filter("constraint"); len(o.Items) > 0 {
    91  		if err := parseConstraints(&result.Constraints, o); err != nil {
    92  			return multierror.Prefix(err, "constraint ->")
    93  		}
    94  	}
    95  
    96  	// Parse affinities
    97  	if o := listVal.Filter("affinity"); len(o.Items) > 0 {
    98  		if err := parseAffinities(&result.Affinities, o); err != nil {
    99  			return multierror.Prefix(err, "affinity ->")
   100  		}
   101  	}
   102  
   103  	// If we have an update strategy, then parse that
   104  	if o := listVal.Filter("update"); len(o.Items) > 0 {
   105  		if err := parseUpdate(&result.Update, o); err != nil {
   106  			return multierror.Prefix(err, "update ->")
   107  		}
   108  	}
   109  
   110  	// If we have a periodic definition, then parse that
   111  	if o := listVal.Filter("periodic"); len(o.Items) > 0 {
   112  		if err := parsePeriodic(&result.Periodic, o); err != nil {
   113  			return multierror.Prefix(err, "periodic ->")
   114  		}
   115  	}
   116  
   117  	// Parse spread
   118  	if o := listVal.Filter("spread"); len(o.Items) > 0 {
   119  		if err := parseSpread(&result.Spreads, o); err != nil {
   120  			return multierror.Prefix(err, "spread ->")
   121  		}
   122  	}
   123  
   124  	// If we have a parameterized definition, then parse that
   125  	if o := listVal.Filter("parameterized"); len(o.Items) > 0 {
   126  		if err := parseParameterizedJob(&result.ParameterizedJob, o); err != nil {
   127  			return multierror.Prefix(err, "parameterized ->")
   128  		}
   129  	}
   130  
   131  	// If we have a reschedule stanza, then parse that
   132  	if o := listVal.Filter("reschedule"); len(o.Items) > 0 {
   133  		if err := parseReschedulePolicy(&result.Reschedule, o); err != nil {
   134  			return multierror.Prefix(err, "reschedule ->")
   135  		}
   136  	}
   137  
   138  	// If we have a migration strategy, then parse that
   139  	if o := listVal.Filter("migrate"); len(o.Items) > 0 {
   140  		if err := parseMigrate(&result.Migrate, o); err != nil {
   141  			return multierror.Prefix(err, "migrate ->")
   142  		}
   143  	}
   144  
   145  	// If we have a multiregion block, then parse that
   146  	if o := listVal.Filter("multiregion"); len(o.Items) > 0 {
   147  		var mr api.Multiregion
   148  		if err := parseMultiregion(&mr, o); err != nil {
   149  			return multierror.Prefix(err, "multiregion ->")
   150  		}
   151  		result.Multiregion = &mr
   152  	}
   153  
   154  	// Parse out meta fields. These are in HCL as a list so we need
   155  	// to iterate over them and merge them.
   156  	if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 {
   157  		for _, o := range metaO.Elem().Items {
   158  			var m map[string]interface{}
   159  			if err := hcl.DecodeObject(&m, o.Val); err != nil {
   160  				return err
   161  			}
   162  			if err := mapstructure.WeakDecode(m, &result.Meta); err != nil {
   163  				return err
   164  			}
   165  		}
   166  	}
   167  
   168  	// If we have tasks outside, create TaskGroups for them
   169  	if o := listVal.Filter("task"); len(o.Items) > 0 {
   170  		var tasks []*api.Task
   171  		if err := parseTasks(&tasks, o); err != nil {
   172  			return multierror.Prefix(err, "task:")
   173  		}
   174  
   175  		result.TaskGroups = make([]*api.TaskGroup, len(tasks), len(tasks)*2)
   176  		for i, t := range tasks {
   177  			result.TaskGroups[i] = &api.TaskGroup{
   178  				Name:  stringToPtr(t.Name),
   179  				Tasks: []*api.Task{t},
   180  			}
   181  		}
   182  	}
   183  
   184  	// Parse the task groups
   185  	if o := listVal.Filter("group"); len(o.Items) > 0 {
   186  		if err := parseGroups(result, o); err != nil {
   187  			return multierror.Prefix(err, "group:")
   188  		}
   189  	}
   190  
   191  	// If we have a vault block, then parse that
   192  	if o := listVal.Filter("vault"); len(o.Items) > 0 {
   193  		jobVault := &api.Vault{
   194  			Env:        boolToPtr(true),
   195  			ChangeMode: stringToPtr("restart"),
   196  		}
   197  
   198  		if err := parseVault(jobVault, o); err != nil {
   199  			return multierror.Prefix(err, "vault ->")
   200  		}
   201  
   202  		// Go through the task groups/tasks and if they don't have a Vault block, set it
   203  		for _, tg := range result.TaskGroups {
   204  			for _, task := range tg.Tasks {
   205  				if task.Vault == nil {
   206  					task.Vault = jobVault
   207  				}
   208  			}
   209  		}
   210  	}
   211  
   212  	return nil
   213  }
   214  
   215  func parsePeriodic(result **api.PeriodicConfig, list *ast.ObjectList) error {
   216  	list = list.Elem()
   217  	if len(list.Items) > 1 {
   218  		return fmt.Errorf("only one 'periodic' block allowed per job")
   219  	}
   220  
   221  	// Get our resource object
   222  	o := list.Items[0]
   223  
   224  	var m map[string]interface{}
   225  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
   226  		return err
   227  	}
   228  
   229  	// Check for invalid keys
   230  	valid := []string{
   231  		"enabled",
   232  		"cron",
   233  		"prohibit_overlap",
   234  		"time_zone",
   235  	}
   236  	if err := checkHCLKeys(o.Val, valid); err != nil {
   237  		return err
   238  	}
   239  
   240  	if value, ok := m["enabled"]; ok {
   241  		enabled, err := parseBool(value)
   242  		if err != nil {
   243  			return fmt.Errorf("periodic.enabled should be set to true or false; %v", err)
   244  		}
   245  		m["Enabled"] = enabled
   246  	}
   247  
   248  	// If "cron" is provided, set the type to "cron" and store the spec.
   249  	if cron, ok := m["cron"]; ok {
   250  		m["SpecType"] = api.PeriodicSpecCron
   251  		m["Spec"] = cron
   252  	}
   253  
   254  	// Build the constraint
   255  	var p api.PeriodicConfig
   256  	if err := mapstructure.WeakDecode(m, &p); err != nil {
   257  		return err
   258  	}
   259  	*result = &p
   260  	return nil
   261  }
   262  
   263  func parseParameterizedJob(result **api.ParameterizedJobConfig, list *ast.ObjectList) error {
   264  	list = list.Elem()
   265  	if len(list.Items) > 1 {
   266  		return fmt.Errorf("only one 'parameterized' block allowed per job")
   267  	}
   268  
   269  	// Get our resource object
   270  	o := list.Items[0]
   271  
   272  	var m map[string]interface{}
   273  	if err := hcl.DecodeObject(&m, o.Val); err != nil {
   274  		return err
   275  	}
   276  
   277  	// Check for invalid keys
   278  	valid := []string{
   279  		"payload",
   280  		"meta_required",
   281  		"meta_optional",
   282  	}
   283  	if err := checkHCLKeys(o.Val, valid); err != nil {
   284  		return err
   285  	}
   286  
   287  	// Build the parameterized job block
   288  	var d api.ParameterizedJobConfig
   289  	if err := mapstructure.WeakDecode(m, &d); err != nil {
   290  		return err
   291  	}
   292  
   293  	*result = &d
   294  	return nil
   295  }