open-nomad/jobspec/parse_job.go

308 lines
7.3 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package jobspec
import (
"fmt"
multierror "github.com/hashicorp/go-multierror"
"github.com/hashicorp/hcl"
"github.com/hashicorp/hcl/hcl/ast"
"github.com/hashicorp/nomad/api"
"github.com/mitchellh/mapstructure"
)
func parseJob(result *api.Job, list *ast.ObjectList) error {
if len(list.Items) != 1 {
return fmt.Errorf("only one 'job' block allowed")
}
list = list.Children()
if len(list.Items) != 1 {
return fmt.Errorf("'job' block missing name")
}
// Get our job object
obj := list.Items[0]
// Decode the full thing into a map[string]interface for ease
var m map[string]interface{}
if err := hcl.DecodeObject(&m, obj.Val); err != nil {
return err
}
delete(m, "constraint")
delete(m, "affinity")
delete(m, "meta")
delete(m, "migrate")
delete(m, "parameterized")
delete(m, "periodic")
delete(m, "reschedule")
delete(m, "update")
delete(m, "vault")
delete(m, "spread")
delete(m, "multiregion")
// Set the ID and name to the object key
result.ID = stringToPtr(obj.Keys[0].Token.Value().(string))
result.Name = stringToPtr(*result.ID)
// Decode the rest
if err := mapstructure.WeakDecode(m, result); err != nil {
return err
}
// Value should be an object
var listVal *ast.ObjectList
if ot, ok := obj.Val.(*ast.ObjectType); ok {
listVal = ot.List
} else {
return fmt.Errorf("job '%s' value: should be an object", *result.ID)
}
// Check for invalid keys
valid := []string{
"all_at_once",
"constraint",
"affinity",
"spread",
"datacenters",
"node_pool",
"group",
"id",
"meta",
"migrate",
"name",
"namespace",
"parameterized",
"periodic",
"priority",
"region",
"reschedule",
"task",
"type",
"update",
"vault",
"vault_token",
"consul_token",
"multiregion",
}
if err := checkHCLKeys(listVal, valid); err != nil {
return multierror.Prefix(err, "job:")
}
// Parse constraints
if o := listVal.Filter("constraint"); len(o.Items) > 0 {
if err := parseConstraints(&result.Constraints, o); err != nil {
return multierror.Prefix(err, "constraint ->")
}
}
// Parse affinities
if o := listVal.Filter("affinity"); len(o.Items) > 0 {
if err := parseAffinities(&result.Affinities, o); err != nil {
return multierror.Prefix(err, "affinity ->")
}
}
// If we have an update strategy, then parse that
if o := listVal.Filter("update"); len(o.Items) > 0 {
if err := parseUpdate(&result.Update, o); err != nil {
return multierror.Prefix(err, "update ->")
}
}
// If we have a periodic definition, then parse that
if o := listVal.Filter("periodic"); len(o.Items) > 0 {
if err := parsePeriodic(&result.Periodic, o); err != nil {
return multierror.Prefix(err, "periodic ->")
}
}
// Parse spread
if o := listVal.Filter("spread"); len(o.Items) > 0 {
if err := parseSpread(&result.Spreads, o); err != nil {
return multierror.Prefix(err, "spread ->")
}
}
// If we have a parameterized definition, then parse that
if o := listVal.Filter("parameterized"); len(o.Items) > 0 {
if err := parseParameterizedJob(&result.ParameterizedJob, o); err != nil {
return multierror.Prefix(err, "parameterized ->")
}
}
// If we have a reschedule block, then parse that
if o := listVal.Filter("reschedule"); len(o.Items) > 0 {
if err := parseReschedulePolicy(&result.Reschedule, o); err != nil {
return multierror.Prefix(err, "reschedule ->")
}
}
// If we have a migration strategy, then parse that
if o := listVal.Filter("migrate"); len(o.Items) > 0 {
if err := parseMigrate(&result.Migrate, o); err != nil {
return multierror.Prefix(err, "migrate ->")
}
}
// If we have a multiregion block, then parse that
if o := listVal.Filter("multiregion"); len(o.Items) > 0 {
var mr api.Multiregion
if err := parseMultiregion(&mr, o); err != nil {
return multierror.Prefix(err, "multiregion ->")
}
result.Multiregion = &mr
}
// Parse out meta fields. These are in HCL as a list so we need
// to iterate over them and merge them.
if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 {
for _, o := range metaO.Elem().Items {
var m map[string]interface{}
if err := hcl.DecodeObject(&m, o.Val); err != nil {
return err
}
if err := mapstructure.WeakDecode(m, &result.Meta); err != nil {
return err
}
}
}
// If we have tasks outside, create TaskGroups for them
if o := listVal.Filter("task"); len(o.Items) > 0 {
var tasks []*api.Task
if err := parseTasks(&tasks, o); err != nil {
return multierror.Prefix(err, "task:")
}
result.TaskGroups = make([]*api.TaskGroup, len(tasks), len(tasks)*2)
for i, t := range tasks {
result.TaskGroups[i] = &api.TaskGroup{
Name: stringToPtr(t.Name),
Tasks: []*api.Task{t},
}
}
}
// Parse the task groups
if o := listVal.Filter("group"); len(o.Items) > 0 {
if err := parseGroups(result, o); err != nil {
return multierror.Prefix(err, "group:")
}
}
// If we have a vault block, then parse that
if o := listVal.Filter("vault"); len(o.Items) > 0 {
jobVault := &api.Vault{
Env: boolToPtr(true),
DisableFile: boolToPtr(false),
ChangeMode: stringToPtr("restart"),
}
if err := parseVault(jobVault, o); err != nil {
return multierror.Prefix(err, "vault ->")
}
// Go through the task groups/tasks and if they don't have a Vault block, set it
for _, tg := range result.TaskGroups {
for _, task := range tg.Tasks {
if task.Vault == nil {
task.Vault = jobVault
}
}
}
}
return nil
}
func parsePeriodic(result **api.PeriodicConfig, list *ast.ObjectList) error {
list = list.Elem()
if len(list.Items) > 1 {
return fmt.Errorf("only one 'periodic' block allowed per job")
}
// Get our resource object
o := list.Items[0]
var m map[string]interface{}
if err := hcl.DecodeObject(&m, o.Val); err != nil {
return err
}
// Check for invalid keys
valid := []string{
"enabled",
"cron",
"crons",
"prohibit_overlap",
"time_zone",
}
if err := checkHCLKeys(o.Val, valid); err != nil {
return err
}
if value, ok := m["enabled"]; ok {
enabled, err := parseBool(value)
if err != nil {
return fmt.Errorf("periodic.enabled should be set to true or false; %v", err)
}
m["Enabled"] = enabled
}
// If "cron" is provided, set the type to "cron" and store the spec.
if cron, ok := m["cron"]; ok {
m["SpecType"] = api.PeriodicSpecCron
m["Spec"] = cron
}
// If "crons" is provided, set the type to "cron" and store the spec.
if cron, ok := m["crons"]; ok {
m["SpecType"] = api.PeriodicSpecCron
m["Specs"] = cron
}
// Build the constraint
var p api.PeriodicConfig
if err := mapstructure.WeakDecode(m, &p); err != nil {
return err
}
*result = &p
return nil
}
func parseParameterizedJob(result **api.ParameterizedJobConfig, list *ast.ObjectList) error {
list = list.Elem()
if len(list.Items) > 1 {
return fmt.Errorf("only one 'parameterized' block allowed per job")
}
// Get our resource object
o := list.Items[0]
var m map[string]interface{}
if err := hcl.DecodeObject(&m, o.Val); err != nil {
return err
}
// Check for invalid keys
valid := []string{
"payload",
"meta_required",
"meta_optional",
}
if err := checkHCLKeys(o.Val, valid); err != nil {
return err
}
// Build the parameterized job block
var d api.ParameterizedJobConfig
if err := mapstructure.WeakDecode(m, &d); err != nil {
return err
}
*result = &d
return nil
}