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