af8cab3d74
This eases adoption of the jobspec package by other projects (e.g. terraform nomad provider, Lavant). Either by consuming directy as a library (hopefully without having go mod import rest of nomad) or by copying the package without modification. Ideally, this package will be published as an independent module. We aren't ready for that considering we'll be switching to HCLv2 "soon", but eitherway, this seems like a reasonable intermediate step if we choose to.
296 lines
7 KiB
Go
296 lines
7 KiB
Go
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",
|
|
"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 stanza, 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),
|
|
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",
|
|
"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
|
|
}
|
|
|
|
// 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
|
|
}
|