open-nomad/jobspec/parse.go

1782 lines
42 KiB
Go
Raw Normal View History

2015-09-15 00:43:42 +00:00
package jobspec
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"strconv"
2015-11-15 08:10:48 +00:00
"strings"
2017-02-06 19:48:28 +00:00
"time"
2015-09-15 00:43:42 +00:00
2019-01-15 19:46:12 +00:00
multierror "github.com/hashicorp/go-multierror"
2015-09-15 00:43:42 +00:00
"github.com/hashicorp/hcl"
2015-11-09 06:57:39 +00:00
"github.com/hashicorp/hcl/hcl/ast"
2017-02-06 19:48:28 +00:00
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/helper"
2015-09-15 00:43:42 +00:00
"github.com/mitchellh/mapstructure"
)
2016-03-10 18:16:35 +00:00
var reDynamicPorts = regexp.MustCompile("^[a-zA-Z0-9_]+$")
2015-11-15 08:10:48 +00:00
var errPortLabel = fmt.Errorf("Port label does not conform to naming requirements %s", reDynamicPorts.String())
2015-11-09 06:57:39 +00:00
2015-09-15 00:43:42 +00:00
// Parse parses the job spec from the given io.Reader.
//
// Due to current internal limitations, the entire contents of the
// io.Reader will be copied into memory first before parsing.
2017-02-06 19:48:28 +00:00
func Parse(r io.Reader) (*api.Job, error) {
2015-09-15 00:43:42 +00:00
// Copy the reader into an in-memory buffer first since HCL requires it.
var buf bytes.Buffer
if _, err := io.Copy(&buf, r); err != nil {
return nil, err
}
// Parse the buffer
2015-11-09 06:57:39 +00:00
root, err := hcl.Parse(buf.String())
2015-09-15 00:43:42 +00:00
if err != nil {
return nil, fmt.Errorf("error parsing: %s", err)
}
buf.Reset()
2015-11-09 06:57:39 +00:00
// Top-level item should be a list
list, ok := root.Node.(*ast.ObjectList)
if !ok {
return nil, fmt.Errorf("error parsing: root should be an object")
}
2016-03-10 18:16:35 +00:00
// Check for invalid keys
valid := []string{
"job",
}
2017-10-13 21:36:02 +00:00
if err := helper.CheckHCLKeys(list, valid); err != nil {
2016-03-10 18:16:35 +00:00
return nil, err
}
2017-02-06 19:48:28 +00:00
var job api.Job
2015-09-15 00:43:42 +00:00
// Parse the job out
2015-11-09 06:57:39 +00:00
matches := list.Filter("job")
if len(matches.Items) == 0 {
2015-09-15 00:43:42 +00:00
return nil, fmt.Errorf("'job' stanza not found")
}
2015-11-09 06:57:39 +00:00
if err := parseJob(&job, matches); err != nil {
2015-09-15 00:43:42 +00:00
return nil, fmt.Errorf("error parsing 'job': %s", err)
}
return &job, nil
}
// ParseFile parses the given path as a job spec.
2017-02-06 19:48:28 +00:00
func ParseFile(path string) (*api.Job, error) {
2015-09-15 00:43:42 +00:00
path, err := filepath.Abs(path)
if err != nil {
return nil, err
}
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
return Parse(f)
}
2017-02-06 19:48:28 +00:00
func parseJob(result *api.Job, list *ast.ObjectList) error {
2015-11-09 06:57:39 +00:00
if len(list.Items) != 1 {
2015-09-15 00:43:42 +00:00
return fmt.Errorf("only one 'job' block allowed")
}
2016-11-24 01:18:50 +00:00
list = list.Children()
if len(list.Items) != 1 {
return fmt.Errorf("'job' block missing name")
}
2015-09-15 00:43:42 +00:00
// Get our job object
2015-11-09 06:57:39 +00:00
obj := list.Items[0]
2015-09-15 00:43:42 +00:00
// Decode the full thing into a map[string]interface for ease
var m map[string]interface{}
2015-11-09 06:57:39 +00:00
if err := hcl.DecodeObject(&m, obj.Val); err != nil {
2015-09-15 00:43:42 +00:00
return err
}
2015-09-15 00:48:11 +00:00
delete(m, "constraint")
2018-07-16 13:30:58 +00:00
delete(m, "affinity")
2015-09-15 00:46:52 +00:00
delete(m, "meta")
2018-03-01 19:21:32 +00:00
delete(m, "migrate")
2017-01-20 20:46:04 +00:00
delete(m, "parameterized")
2018-03-01 19:21:32 +00:00
delete(m, "periodic")
delete(m, "reschedule")
2018-03-01 19:21:32 +00:00
delete(m, "update")
delete(m, "vault")
delete(m, "spread")
2015-09-15 00:43:42 +00:00
// Set the ID and name to the object key
2017-02-06 19:48:28 +00:00
result.ID = helper.StringToPtr(obj.Keys[0].Token.Value().(string))
result.Name = helper.StringToPtr(*result.ID)
2015-09-15 00:43:42 +00:00
// Decode the rest
if err := mapstructure.WeakDecode(m, result); err != nil {
return err
}
2015-11-09 06:57:39 +00:00
// 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)
2015-11-09 06:57:39 +00:00
}
2016-03-10 18:16:35 +00:00
// Check for invalid keys
valid := []string{
2016-11-23 23:48:36 +00:00
"all_at_once",
"constraint",
2018-07-16 13:30:58 +00:00
"affinity",
"spread",
2016-11-23 23:48:36 +00:00
"datacenters",
"group",
2016-03-10 18:16:35 +00:00
"id",
2016-11-23 23:48:36 +00:00
"meta",
2018-03-01 19:21:32 +00:00
"migrate",
2016-03-10 18:16:35 +00:00
"name",
2017-09-07 23:56:15 +00:00
"namespace",
2018-03-01 19:21:32 +00:00
"parameterized",
2016-11-23 23:48:36 +00:00
"periodic",
"priority",
2016-03-10 18:16:35 +00:00
"region",
2018-03-01 19:21:32 +00:00
"reschedule",
2016-11-23 23:48:36 +00:00
"task",
2016-03-10 18:16:35 +00:00
"type",
"update",
2016-09-21 18:18:44 +00:00
"vault",
"vault_token",
2016-03-10 18:16:35 +00:00
}
2017-10-13 21:36:02 +00:00
if err := helper.CheckHCLKeys(listVal, valid); err != nil {
2016-03-10 18:16:35 +00:00
return multierror.Prefix(err, "job:")
}
2015-09-15 00:48:11 +00:00
// Parse constraints
2015-11-09 06:57:39 +00:00
if o := listVal.Filter("constraint"); len(o.Items) > 0 {
2015-09-15 00:48:11 +00:00
if err := parseConstraints(&result.Constraints, o); err != nil {
2016-03-10 18:16:35 +00:00
return multierror.Prefix(err, "constraint ->")
2015-09-15 00:48:11 +00:00
}
}
2018-07-16 13:30:58 +00:00
// 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
2015-11-09 06:57:39 +00:00
if o := listVal.Filter("update"); len(o.Items) > 0 {
if err := parseUpdate(&result.Update, o); err != nil {
2016-03-10 18:16:35 +00:00
return multierror.Prefix(err, "update ->")
}
}
2015-12-01 00:51:56 +00:00
// 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 {
2016-03-10 18:16:35 +00:00
return multierror.Prefix(err, "periodic ->")
2015-12-01 00:51:56 +00:00
}
}
// 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
2017-01-20 20:46:04 +00:00
if o := listVal.Filter("parameterized"); len(o.Items) > 0 {
if err := parseParameterizedJob(&result.ParameterizedJob, o); err != nil {
2017-01-20 20:46:04 +00:00
return multierror.Prefix(err, "parameterized ->")
2016-11-23 23:48:36 +00:00
}
}
// 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 {
2018-01-22 22:40:47 +00:00
return multierror.Prefix(err, "reschedule ->")
}
}
2018-03-01 19:21:32 +00:00
// 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 ->")
}
}
2015-09-15 00:46:52 +00:00
// Parse out meta fields. These are in HCL as a list so we need
// to iterate over them and merge them.
2015-11-09 06:57:39 +00:00
if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 {
for _, o := range metaO.Elem().Items {
2015-09-15 00:46:52 +00:00
var m map[string]interface{}
2015-11-09 06:57:39 +00:00
if err := hcl.DecodeObject(&m, o.Val); err != nil {
2015-09-15 00:46:52 +00:00
return err
}
if err := mapstructure.WeakDecode(m, &result.Meta); err != nil {
return err
}
}
}
// If we have tasks outside, create TaskGroups for them
2015-11-09 06:57:39 +00:00
if o := listVal.Filter("task"); len(o.Items) > 0 {
2017-02-06 19:48:28 +00:00
var tasks []*api.Task
if err := parseTasks(*result.Name, "", &tasks, o); err != nil {
2016-03-10 18:16:35 +00:00
return multierror.Prefix(err, "task:")
2015-09-15 00:43:42 +00:00
}
2017-02-06 19:48:28 +00:00
result.TaskGroups = make([]*api.TaskGroup, len(tasks), len(tasks)*2)
2015-09-15 00:43:42 +00:00
for i, t := range tasks {
2017-02-06 19:48:28 +00:00
result.TaskGroups[i] = &api.TaskGroup{
Name: helper.StringToPtr(t.Name),
Tasks: []*api.Task{t},
2015-09-15 00:43:42 +00:00
}
}
}
// Parse the task groups
2015-11-09 06:57:39 +00:00
if o := listVal.Filter("group"); len(o.Items) > 0 {
2015-09-15 00:43:42 +00:00
if err := parseGroups(result, o); err != nil {
2016-03-10 18:16:35 +00:00
return multierror.Prefix(err, "group:")
2015-09-15 00:43:42 +00:00
}
}
2016-09-21 18:18:44 +00:00
// If we have a vault block, then parse that
if o := listVal.Filter("vault"); len(o.Items) > 0 {
2017-02-06 19:48:28 +00:00
jobVault := &api.Vault{
Env: helper.BoolToPtr(true),
ChangeMode: helper.StringToPtr("restart"),
}
2016-10-18 21:54:14 +00:00
if err := parseVault(jobVault, o); err != nil {
2016-09-21 18:18:44 +00:00
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 {
2016-10-18 21:54:14 +00:00
task.Vault = jobVault
2016-09-21 18:18:44 +00:00
}
}
}
}
2015-09-15 00:43:42 +00:00
return nil
}
2017-02-06 19:48:28 +00:00
func parseGroups(result *api.Job, list *ast.ObjectList) error {
2015-11-09 06:57:39 +00:00
list = list.Children()
if len(list.Items) == 0 {
2015-09-15 00:43:42 +00:00
return nil
}
// Go through each object and turn it into an actual result.
2017-02-06 19:48:28 +00:00
collection := make([]*api.TaskGroup, 0, len(list.Items))
2015-11-09 06:57:39 +00:00
seen := make(map[string]struct{})
for _, item := range list.Items {
n := item.Keys[0].Token.Value().(string)
// Make sure we haven't already found this
if _, ok := seen[n]; ok {
return fmt.Errorf("group '%s' defined more than once", n)
}
seen[n] = struct{}{}
// We need this later
var listVal *ast.ObjectList
if ot, ok := item.Val.(*ast.ObjectType); ok {
listVal = ot.List
} else {
return fmt.Errorf("group '%s': should be an object", n)
}
2016-03-10 18:16:35 +00:00
// Check for invalid keys
valid := []string{
"count",
"constraint",
2018-07-16 13:30:58 +00:00
"affinity",
2016-03-10 18:16:35 +00:00
"restart",
"meta",
"task",
"ephemeral_disk",
"update",
"reschedule",
2016-09-21 18:18:44 +00:00
"vault",
"migrate",
"spread",
2016-03-10 18:16:35 +00:00
}
2017-10-13 21:36:02 +00:00
if err := helper.CheckHCLKeys(listVal, valid); err != nil {
2016-03-10 18:16:35 +00:00
return multierror.Prefix(err, fmt.Sprintf("'%s' ->", n))
}
2015-09-15 00:43:42 +00:00
var m map[string]interface{}
2015-11-09 06:57:39 +00:00
if err := hcl.DecodeObject(&m, item.Val); err != nil {
2015-09-15 00:43:42 +00:00
return err
}
delete(m, "constraint")
2018-07-16 13:30:58 +00:00
delete(m, "affinity")
2015-09-15 00:43:42 +00:00
delete(m, "meta")
delete(m, "task")
delete(m, "restart")
delete(m, "ephemeral_disk")
delete(m, "update")
2016-09-21 18:18:44 +00:00
delete(m, "vault")
delete(m, "migrate")
delete(m, "spread")
2015-09-15 00:43:42 +00:00
// Build the group with the basic decode
2017-02-06 19:48:28 +00:00
var g api.TaskGroup
g.Name = helper.StringToPtr(n)
2015-09-15 00:43:42 +00:00
if err := mapstructure.WeakDecode(m, &g); err != nil {
return err
}
// Parse constraints
2015-11-09 06:57:39 +00:00
if o := listVal.Filter("constraint"); len(o.Items) > 0 {
2015-09-15 00:43:42 +00:00
if err := parseConstraints(&g.Constraints, o); err != nil {
2016-03-10 18:16:35 +00:00
return multierror.Prefix(err, fmt.Sprintf("'%s', constraint ->", n))
2015-09-15 00:43:42 +00:00
}
}
2018-07-16 13:30:58 +00:00
// Parse affinities
if o := listVal.Filter("affinity"); len(o.Items) > 0 {
if err := parseAffinities(&g.Affinities, o); err != nil {
return multierror.Prefix(err, fmt.Sprintf("'%s', affinity ->", n))
}
}
2015-11-09 06:57:39 +00:00
// Parse restart policy
if o := listVal.Filter("restart"); len(o.Items) > 0 {
2015-12-18 20:17:13 +00:00
if err := parseRestartPolicy(&g.RestartPolicy, o); err != nil {
2016-03-10 18:16:35 +00:00
return multierror.Prefix(err, fmt.Sprintf("'%s', restart ->", n))
2015-11-09 06:57:39 +00:00
}
}
// Parse spread
if o := listVal.Filter("spread"); len(o.Items) > 0 {
if err := parseSpread(&g.Spreads, o); err != nil {
return multierror.Prefix(err, "spread ->")
}
}
// Parse reschedule policy
if o := listVal.Filter("reschedule"); len(o.Items) > 0 {
if err := parseReschedulePolicy(&g.ReschedulePolicy, o); err != nil {
return multierror.Prefix(err, fmt.Sprintf("'%s', reschedule ->", n))
}
}
// Parse ephemeral disk
if o := listVal.Filter("ephemeral_disk"); len(o.Items) > 0 {
g.EphemeralDisk = &api.EphemeralDisk{}
if err := parseEphemeralDisk(&g.EphemeralDisk, o); err != nil {
return multierror.Prefix(err, fmt.Sprintf("'%s', ephemeral_disk ->", n))
}
}
// If we have an update strategy, then parse that
if o := listVal.Filter("update"); len(o.Items) > 0 {
if err := parseUpdate(&g.Update, o); err != nil {
return multierror.Prefix(err, "update ->")
}
}
// If we have a migration strategy, then parse that
if o := listVal.Filter("migrate"); len(o.Items) > 0 {
if err := parseMigrate(&g.Migrate, o); err != nil {
return multierror.Prefix(err, "migrate ->")
}
}
2015-09-15 00:43:42 +00:00
// Parse out meta fields. These are in HCL as a list so we need
// to iterate over them and merge them.
2015-11-09 06:57:39 +00:00
if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 {
for _, o := range metaO.Elem().Items {
2015-09-15 00:43:42 +00:00
var m map[string]interface{}
2015-11-09 06:57:39 +00:00
if err := hcl.DecodeObject(&m, o.Val); err != nil {
2015-09-15 00:43:42 +00:00
return err
}
if err := mapstructure.WeakDecode(m, &g.Meta); err != nil {
return err
}
}
}
// Parse tasks
2015-11-09 06:57:39 +00:00
if o := listVal.Filter("task"); len(o.Items) > 0 {
2017-02-06 19:48:28 +00:00
if err := parseTasks(*result.Name, *g.Name, &g.Tasks, o); err != nil {
2016-03-10 18:16:35 +00:00
return multierror.Prefix(err, fmt.Sprintf("'%s', task:", n))
2015-09-15 00:43:42 +00:00
}
}
2016-09-21 18:18:44 +00:00
// If we have a vault block, then parse that
if o := listVal.Filter("vault"); len(o.Items) > 0 {
2017-02-06 19:48:28 +00:00
tgVault := &api.Vault{
Env: helper.BoolToPtr(true),
ChangeMode: helper.StringToPtr("restart"),
}
2016-10-18 21:54:14 +00:00
if err := parseVault(tgVault, o); err != nil {
2016-09-21 18:18:44 +00:00
return multierror.Prefix(err, fmt.Sprintf("'%s', vault ->", n))
}
// Go through the tasks and if they don't have a Vault block, set it
for _, task := range g.Tasks {
if task.Vault == nil {
2016-10-18 21:54:14 +00:00
task.Vault = tgVault
2016-09-21 18:18:44 +00:00
}
}
}
2015-09-15 00:43:42 +00:00
collection = append(collection, &g)
}
result.TaskGroups = append(result.TaskGroups, collection...)
return nil
}
2017-02-06 19:48:28 +00:00
func parseRestartPolicy(final **api.RestartPolicy, list *ast.ObjectList) error {
2015-11-09 06:57:39 +00:00
list = list.Elem()
2015-12-18 20:17:13 +00:00
if len(list.Items) > 1 {
2015-11-09 06:57:39 +00:00
return fmt.Errorf("only one 'restart' block allowed")
}
2015-11-09 06:57:39 +00:00
// Get our job object
obj := list.Items[0]
2016-03-10 18:16:35 +00:00
// Check for invalid keys
valid := []string{
"attempts",
"interval",
"delay",
"mode",
}
2017-10-13 21:36:02 +00:00
if err := helper.CheckHCLKeys(obj.Val, valid); err != nil {
2016-03-10 18:16:35 +00:00
return err
}
2015-11-09 06:57:39 +00:00
var m map[string]interface{}
if err := hcl.DecodeObject(&m, obj.Val); err != nil {
return err
}
2017-02-06 19:48:28 +00:00
var result api.RestartPolicy
2015-11-09 06:57:39 +00:00
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
WeaklyTypedInput: true,
Result: &result,
})
if err != nil {
return err
}
2015-11-09 06:57:39 +00:00
if err := dec.Decode(m); err != nil {
return err
}
2015-12-18 20:17:13 +00:00
*final = &result
return nil
}
func parseReschedulePolicy(final **api.ReschedulePolicy, list *ast.ObjectList) error {
list = list.Elem()
if len(list.Items) > 1 {
return fmt.Errorf("only one 'reschedule' block allowed")
}
// Get our job object
obj := list.Items[0]
// Check for invalid keys
valid := []string{
"attempts",
"interval",
"unlimited",
"delay",
2018-03-13 15:06:26 +00:00
"max_delay",
"delay_function",
}
if err := helper.CheckHCLKeys(obj.Val, valid); err != nil {
return err
}
var m map[string]interface{}
if err := hcl.DecodeObject(&m, obj.Val); err != nil {
return err
}
var result api.ReschedulePolicy
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
WeaklyTypedInput: true,
Result: &result,
})
if err != nil {
return err
}
if err := dec.Decode(m); err != nil {
return err
}
*final = &result
return nil
}
2017-02-06 19:48:28 +00:00
func parseConstraints(result *[]*api.Constraint, list *ast.ObjectList) error {
2015-11-09 06:57:39 +00:00
for _, o := range list.Elem().Items {
2016-03-10 18:16:35 +00:00
// Check for invalid keys
valid := []string{
"attribute",
2017-03-07 22:20:02 +00:00
"distinct_hosts",
"distinct_property",
2016-03-10 18:16:35 +00:00
"operator",
"regexp",
2016-10-19 20:06:28 +00:00
"set_contains",
2017-03-07 22:20:02 +00:00
"value",
"version",
2016-03-10 18:16:35 +00:00
}
2017-10-13 21:36:02 +00:00
if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
2016-03-10 18:16:35 +00:00
return err
}
2015-09-15 00:43:42 +00:00
var m map[string]interface{}
2015-11-09 06:57:39 +00:00
if err := hcl.DecodeObject(&m, o.Val); err != nil {
2015-09-15 00:43:42 +00:00
return err
}
2016-03-10 18:16:35 +00:00
2015-09-15 00:43:42 +00:00
m["LTarget"] = m["attribute"]
m["RTarget"] = m["value"]
m["Operand"] = m["operator"]
// If "version" is provided, set the operand
// to "version" and the value to the "RTarget"
if constraint, ok := m[api.ConstraintVersion]; ok {
m["Operand"] = api.ConstraintVersion
m["RTarget"] = constraint
}
// If "regexp" is provided, set the operand
// to "regexp" and the value to the "RTarget"
if constraint, ok := m[api.ConstraintRegex]; ok {
m["Operand"] = api.ConstraintRegex
m["RTarget"] = constraint
}
2016-10-19 20:06:28 +00:00
// If "set_contains" is provided, set the operand
// to "set_contains" and the value to the "RTarget"
if constraint, ok := m[api.ConstraintSetContains]; ok {
m["Operand"] = api.ConstraintSetContains
2016-10-19 20:06:28 +00:00
m["RTarget"] = constraint
}
if value, ok := m[api.ConstraintDistinctHosts]; ok {
2015-11-25 20:33:56 +00:00
enabled, err := parseBool(value)
if err != nil {
return fmt.Errorf("distinct_hosts should be set to true or false; %v", err)
}
// If it is not enabled, skip the constraint.
if !enabled {
continue
}
m["Operand"] = api.ConstraintDistinctHosts
}
if property, ok := m[api.ConstraintDistinctProperty]; ok {
m["Operand"] = api.ConstraintDistinctProperty
2017-03-07 22:20:02 +00:00
m["LTarget"] = property
}
2015-09-15 00:43:42 +00:00
// Build the constraint
2017-02-06 19:48:28 +00:00
var c api.Constraint
2015-09-15 00:43:42 +00:00
if err := mapstructure.WeakDecode(m, &c); err != nil {
return err
}
if c.Operand == "" {
c.Operand = "="
}
*result = append(*result, &c)
}
return nil
}
2018-07-16 13:30:58 +00:00
func parseAffinities(result *[]*api.Affinity, list *ast.ObjectList) error {
for _, o := range list.Elem().Items {
// Check for invalid keys
valid := []string{
"attribute",
"operator",
"regexp",
"set_contains",
2018-07-16 13:30:58 +00:00
"set_contains_any",
"set_contains_all",
"value",
"version",
"weight",
}
if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
return err
}
var m map[string]interface{}
if err := hcl.DecodeObject(&m, o.Val); err != nil {
return err
}
m["LTarget"] = m["attribute"]
m["RTarget"] = m["value"]
m["Operand"] = m["operator"]
// If "version" is provided, set the operand
// to "version" and the value to the "RTarget"
if affinity, ok := m[api.ConstraintVersion]; ok {
m["Operand"] = api.ConstraintVersion
2018-07-16 13:30:58 +00:00
m["RTarget"] = affinity
}
// If "regexp" is provided, set the operand
// to "regexp" and the value to the "RTarget"
if affinity, ok := m[api.ConstraintRegex]; ok {
m["Operand"] = api.ConstraintRegex
2018-07-16 13:30:58 +00:00
m["RTarget"] = affinity
}
// If "set_contains_any" is provided, set the operand
// to "set_contains_any" and the value to the "RTarget"
if affinity, ok := m[api.ConstraintSetContainsAny]; ok {
m["Operand"] = api.ConstraintSetContainsAny
2018-07-16 13:30:58 +00:00
m["RTarget"] = affinity
}
// If "set_contains_all" is provided, set the operand
// to "set_contains_all" and the value to the "RTarget"
if affinity, ok := m[api.ConstraintSetContainsAll]; ok {
m["Operand"] = api.ConstraintSetContainsAll
2018-07-16 13:30:58 +00:00
m["RTarget"] = affinity
}
// set_contains is a synonym of set_contains_all
if affinity, ok := m[api.ConstraintSetContains]; ok {
m["Operand"] = api.ConstraintSetContains
m["RTarget"] = affinity
}
2018-07-16 13:30:58 +00:00
// Build the affinity
var a api.Affinity
if err := mapstructure.WeakDecode(m, &a); err != nil {
return err
}
if a.Operand == "" {
a.Operand = "="
}
*result = append(*result, &a)
}
return nil
}
2017-02-06 19:48:28 +00:00
func parseEphemeralDisk(result **api.EphemeralDisk, list *ast.ObjectList) error {
list = list.Elem()
if len(list.Items) > 1 {
return fmt.Errorf("only one 'ephemeral_disk' block allowed")
}
// Get our ephemeral_disk object
obj := list.Items[0]
// Check for invalid keys
valid := []string{
"sticky",
"size",
"migrate",
}
2017-10-13 21:36:02 +00:00
if err := helper.CheckHCLKeys(obj.Val, valid); err != nil {
return err
}
var m map[string]interface{}
if err := hcl.DecodeObject(&m, obj.Val); err != nil {
return err
}
2017-02-06 19:48:28 +00:00
var ephemeralDisk api.EphemeralDisk
if err := mapstructure.WeakDecode(m, &ephemeralDisk); err != nil {
return err
}
*result = &ephemeralDisk
return nil
}
func parseSpread(result *[]*api.Spread, list *ast.ObjectList) error {
for _, o := range list.Elem().Items {
// Check for invalid keys
valid := []string{
"attribute",
"weight",
"target",
}
if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
return err
}
// We need this later
var listVal *ast.ObjectList
if ot, ok := o.Val.(*ast.ObjectType); ok {
listVal = ot.List
} else {
return fmt.Errorf("spread should be an object")
}
var m map[string]interface{}
if err := hcl.DecodeObject(&m, o.Val); err != nil {
return err
}
delete(m, "target")
// Build spread
var s api.Spread
if err := mapstructure.WeakDecode(m, &s); err != nil {
return err
}
// Parse spread target
if o := listVal.Filter("target"); len(o.Items) > 0 {
if err := parseSpreadTarget(&s.SpreadTarget, o); err != nil {
2018-10-08 23:09:41 +00:00
return multierror.Prefix(err, fmt.Sprintf("target ->"))
}
}
*result = append(*result, &s)
}
return nil
}
func parseSpreadTarget(result *[]*api.SpreadTarget, list *ast.ObjectList) error {
seen := make(map[string]struct{})
for _, item := range list.Items {
2018-10-08 23:09:41 +00:00
if len(item.Keys) != 1 {
return fmt.Errorf("missing spread target")
}
n := item.Keys[0].Token.Value().(string)
// Make sure we haven't already found this
if _, ok := seen[n]; ok {
return fmt.Errorf("target '%s' defined more than once", n)
}
seen[n] = struct{}{}
// We need this later
var listVal *ast.ObjectList
if ot, ok := item.Val.(*ast.ObjectType); ok {
listVal = ot.List
} else {
return fmt.Errorf("target should be an object")
}
// Check for invalid keys
valid := []string{
"percent",
"value",
}
if err := helper.CheckHCLKeys(listVal, valid); err != nil {
return multierror.Prefix(err, fmt.Sprintf("'%s' ->", n))
}
var m map[string]interface{}
if err := hcl.DecodeObject(&m, item.Val); err != nil {
return err
}
// Decode spread target
var g api.SpreadTarget
g.Value = n
if err := mapstructure.WeakDecode(m, &g); err != nil {
return err
}
*result = append(*result, &g)
}
return nil
}
2015-11-25 20:33:56 +00:00
// parseBool takes an interface value and tries to convert it to a boolean and
// returns an error if the type can't be converted.
func parseBool(value interface{}) (bool, error) {
var enabled bool
var err error
switch value.(type) {
case string:
enabled, err = strconv.ParseBool(value.(string))
case bool:
enabled = value.(bool)
default:
err = fmt.Errorf("%v couldn't be converted to boolean value", value)
}
return enabled, err
}
2017-02-06 19:48:28 +00:00
func parseTasks(jobName string, taskGroupName string, result *[]*api.Task, list *ast.ObjectList) error {
2015-11-09 06:57:39 +00:00
list = list.Children()
if len(list.Items) == 0 {
return nil
}
2015-09-15 00:43:42 +00:00
2015-11-09 06:57:39 +00:00
// Go through each object and turn it into an actual result.
seen := make(map[string]struct{})
for _, item := range list.Items {
n := item.Keys[0].Token.Value().(string)
// Make sure we haven't already found this
if _, ok := seen[n]; ok {
return fmt.Errorf("task '%s' defined more than once", n)
2015-09-15 00:43:42 +00:00
}
2015-11-09 06:57:39 +00:00
seen[n] = struct{}{}
2015-09-15 00:43:42 +00:00
2015-11-09 06:57:39 +00:00
// We need this later
var listVal *ast.ObjectList
if ot, ok := item.Val.(*ast.ObjectType); ok {
listVal = ot.List
} else {
return fmt.Errorf("group '%s': should be an object", n)
}
2015-09-15 00:43:42 +00:00
2016-03-10 18:16:35 +00:00
// Check for invalid keys
valid := []string{
2016-08-09 23:07:45 +00:00
"artifact",
2016-03-10 18:16:35 +00:00
"config",
"constraint",
2018-07-16 13:30:58 +00:00
"affinity",
"dispatch_payload",
2016-08-09 23:07:45 +00:00
"driver",
"env",
"kill_timeout",
2017-02-11 00:57:47 +00:00
"leader",
2016-08-09 23:07:45 +00:00
"logs",
2016-03-10 18:16:35 +00:00
"meta",
"resources",
2016-08-09 23:07:45 +00:00
"service",
"shutdown_delay",
2016-09-26 22:23:26 +00:00
"template",
2016-08-09 23:07:45 +00:00
"user",
"vault",
"kill_signal",
2016-03-10 18:16:35 +00:00
}
2017-10-13 21:36:02 +00:00
if err := helper.CheckHCLKeys(listVal, valid); err != nil {
2016-03-10 18:16:35 +00:00
return multierror.Prefix(err, fmt.Sprintf("'%s' ->", n))
}
2015-09-15 00:43:42 +00:00
var m map[string]interface{}
2015-11-09 06:57:39 +00:00
if err := hcl.DecodeObject(&m, item.Val); err != nil {
2015-09-15 00:43:42 +00:00
return err
}
2016-08-09 23:07:45 +00:00
delete(m, "artifact")
2015-09-15 00:43:42 +00:00
delete(m, "config")
2015-09-15 00:50:34 +00:00
delete(m, "constraint")
2018-07-16 13:30:58 +00:00
delete(m, "affinity")
delete(m, "dispatch_payload")
2016-08-09 23:07:45 +00:00
delete(m, "env")
delete(m, "logs")
2015-09-15 00:43:42 +00:00
delete(m, "meta")
delete(m, "resources")
2016-08-09 23:07:45 +00:00
delete(m, "service")
2016-09-26 22:23:26 +00:00
delete(m, "template")
2016-08-09 23:07:45 +00:00
delete(m, "vault")
2015-09-15 00:43:42 +00:00
// Build the task
2017-02-06 19:48:28 +00:00
var t api.Task
2015-11-09 06:57:39 +00:00
t.Name = n
2015-11-18 00:05:03 +00:00
if taskGroupName == "" {
taskGroupName = n
}
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
WeaklyTypedInput: true,
Result: &t,
})
if err != nil {
return err
}
if err := dec.Decode(m); err != nil {
2015-09-15 00:43:42 +00:00
return err
}
// If we have env, then parse them
2015-11-09 06:57:39 +00:00
if o := listVal.Filter("env"); len(o.Items) > 0 {
for _, o := range o.Elem().Items {
var m map[string]interface{}
2015-11-09 06:57:39 +00:00
if err := hcl.DecodeObject(&m, o.Val); err != nil {
return err
}
if err := mapstructure.WeakDecode(m, &t.Env); err != nil {
return err
}
}
}
if o := listVal.Filter("service"); len(o.Items) > 0 {
2015-11-18 00:05:03 +00:00
if err := parseServices(jobName, taskGroupName, &t, o); err != nil {
2016-03-10 18:16:35 +00:00
return multierror.Prefix(err, fmt.Sprintf("'%s',", n))
}
}
2015-09-15 00:43:42 +00:00
// If we have config, then parse that
2015-11-09 06:57:39 +00:00
if o := listVal.Filter("config"); len(o.Items) > 0 {
for _, o := range o.Elem().Items {
2015-09-15 00:43:42 +00:00
var m map[string]interface{}
2015-11-09 06:57:39 +00:00
if err := hcl.DecodeObject(&m, o.Val); err != nil {
2015-09-15 00:43:42 +00:00
return err
}
2015-09-15 00:43:42 +00:00
if err := mapstructure.WeakDecode(m, &t.Config); err != nil {
return err
}
}
}
2015-09-15 00:50:34 +00:00
// Parse constraints
2015-11-09 06:57:39 +00:00
if o := listVal.Filter("constraint"); len(o.Items) > 0 {
2015-09-15 00:50:34 +00:00
if err := parseConstraints(&t.Constraints, o); err != nil {
2016-03-10 18:16:35 +00:00
return multierror.Prefix(err, fmt.Sprintf(
"'%s', constraint ->", n))
2015-09-15 00:50:34 +00:00
}
}
2018-07-16 13:30:58 +00:00
// Parse affinities
if o := listVal.Filter("affinity"); len(o.Items) > 0 {
if err := parseAffinities(&t.Affinities, o); err != nil {
return multierror.Prefix(err, "affinity ->")
}
}
2015-09-15 00:43:42 +00:00
// Parse out meta fields. These are in HCL as a list so we need
// to iterate over them and merge them.
2015-11-09 06:57:39 +00:00
if metaO := listVal.Filter("meta"); len(metaO.Items) > 0 {
for _, o := range metaO.Elem().Items {
2015-09-15 00:43:42 +00:00
var m map[string]interface{}
2015-11-09 06:57:39 +00:00
if err := hcl.DecodeObject(&m, o.Val); err != nil {
2015-09-15 00:43:42 +00:00
return err
}
if err := mapstructure.WeakDecode(m, &t.Meta); err != nil {
return err
}
}
}
// If we have resources, then parse that
2015-11-09 06:57:39 +00:00
if o := listVal.Filter("resources"); len(o.Items) > 0 {
2017-02-06 19:48:28 +00:00
var r api.Resources
2015-09-15 01:27:37 +00:00
if err := parseResources(&r, o); err != nil {
2016-03-10 18:16:35 +00:00
return multierror.Prefix(err, fmt.Sprintf("'%s',", n))
2015-09-15 00:43:42 +00:00
}
t.Resources = &r
}
2016-02-05 07:28:01 +00:00
// If we have logs then parse that
if o := listVal.Filter("logs"); len(o.Items) > 0 {
if len(o.Items) > 1 {
return fmt.Errorf("only one logs block is allowed in a Task. Number of logs block found: %d", len(o.Items))
}
var m map[string]interface{}
logsBlock := o.Items[0]
2016-03-10 18:16:35 +00:00
// Check for invalid keys
valid := []string{
"max_files",
"max_file_size",
}
2017-10-13 21:36:02 +00:00
if err := helper.CheckHCLKeys(logsBlock.Val, valid); err != nil {
2016-03-10 18:16:35 +00:00
return multierror.Prefix(err, fmt.Sprintf("'%s', logs ->", n))
}
2016-02-05 07:28:01 +00:00
if err := hcl.DecodeObject(&m, logsBlock.Val); err != nil {
return err
}
var log api.LogConfig
if err := mapstructure.WeakDecode(m, &log); err != nil {
2016-02-05 07:28:01 +00:00
return err
}
t.LogConfig = &log
2016-02-05 07:28:01 +00:00
}
2016-03-14 05:29:07 +00:00
// Parse artifacts
if o := listVal.Filter("artifact"); len(o.Items) > 0 {
if err := parseArtifacts(&t.Artifacts, o); err != nil {
return multierror.Prefix(err, fmt.Sprintf("'%s', artifact ->", n))
}
}
2016-09-26 22:23:26 +00:00
// Parse templates
if o := listVal.Filter("template"); len(o.Items) > 0 {
if err := parseTemplates(&t.Templates, o); err != nil {
return multierror.Prefix(err, fmt.Sprintf("'%s', template ->", n))
}
}
2016-08-09 23:07:45 +00:00
// If we have a vault block, then parse that
if o := listVal.Filter("vault"); len(o.Items) > 0 {
2017-02-06 19:48:28 +00:00
v := &api.Vault{
Env: helper.BoolToPtr(true),
ChangeMode: helper.StringToPtr("restart"),
}
if err := parseVault(v, o); err != nil {
2016-08-09 23:07:45 +00:00
return multierror.Prefix(err, fmt.Sprintf("'%s', vault ->", n))
}
t.Vault = v
2016-08-09 23:07:45 +00:00
}
// If we have a dispatch_payload block parse that
if o := listVal.Filter("dispatch_payload"); len(o.Items) > 0 {
2016-11-23 23:48:36 +00:00
if len(o.Items) > 1 {
return fmt.Errorf("only one dispatch_payload block is allowed in a task. Number of dispatch_payload blocks found: %d", len(o.Items))
2016-11-23 23:48:36 +00:00
}
var m map[string]interface{}
dispatchBlock := o.Items[0]
// Check for invalid keys
valid := []string{
"file",
}
2017-10-13 21:36:02 +00:00
if err := helper.CheckHCLKeys(dispatchBlock.Val, valid); err != nil {
return multierror.Prefix(err, fmt.Sprintf("'%s', dispatch_payload ->", n))
2016-11-23 23:48:36 +00:00
}
if err := hcl.DecodeObject(&m, dispatchBlock.Val); err != nil {
return err
}
2017-02-06 19:48:28 +00:00
t.DispatchPayload = &api.DispatchPayloadConfig{}
if err := mapstructure.WeakDecode(m, t.DispatchPayload); err != nil {
2016-11-23 23:48:36 +00:00
return err
}
}
2015-09-15 00:43:42 +00:00
*result = append(*result, &t)
}
return nil
}
2015-09-15 01:27:37 +00:00
2017-02-06 19:48:28 +00:00
func parseArtifacts(result *[]*api.TaskArtifact, list *ast.ObjectList) error {
2016-03-14 05:29:07 +00:00
for _, o := range list.Elem().Items {
// Check for invalid keys
valid := []string{
"source",
"options",
"mode",
"destination",
2016-03-14 05:29:07 +00:00
}
2017-10-13 21:36:02 +00:00
if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
2016-03-14 05:29:07 +00:00
return err
}
var m map[string]interface{}
if err := hcl.DecodeObject(&m, o.Val); err != nil {
return err
}
2016-03-14 18:13:43 +00:00
delete(m, "options")
2016-03-14 05:29:07 +00:00
2017-02-06 19:48:28 +00:00
var ta api.TaskArtifact
2016-03-14 18:13:43 +00:00
if err := mapstructure.WeakDecode(m, &ta); err != nil {
return err
2016-03-14 05:29:07 +00:00
}
var optionList *ast.ObjectList
if ot, ok := o.Val.(*ast.ObjectType); ok {
optionList = ot.List
} else {
return fmt.Errorf("artifact should be an object")
}
if oo := optionList.Filter("options"); len(oo.Items) > 0 {
2016-06-10 18:50:18 +00:00
options := make(map[string]string)
if err := parseArtifactOption(options, oo); err != nil {
2016-03-14 05:29:07 +00:00
return multierror.Prefix(err, "options: ")
}
2016-06-10 18:50:18 +00:00
ta.GetterOptions = options
2016-03-14 05:29:07 +00:00
}
2016-03-14 18:13:43 +00:00
*result = append(*result, &ta)
2016-03-14 05:29:07 +00:00
}
return nil
}
func parseArtifactOption(result map[string]string, list *ast.ObjectList) error {
2016-03-14 05:29:07 +00:00
list = list.Elem()
if len(list.Items) > 1 {
return fmt.Errorf("only one 'options' block allowed per artifact")
}
// Get our resource object
o := list.Items[0]
var m map[string]interface{}
if err := hcl.DecodeObject(&m, o.Val); err != nil {
return err
}
if err := mapstructure.WeakDecode(m, &result); err != nil {
2016-03-14 05:29:07 +00:00
return err
}
return nil
}
2017-02-06 19:48:28 +00:00
func parseTemplates(result *[]*api.Template, list *ast.ObjectList) error {
2016-09-26 22:23:26 +00:00
for _, o := range list.Elem().Items {
// Check for invalid keys
valid := []string{
"change_mode",
2016-10-03 19:42:18 +00:00
"change_signal",
"data",
"destination",
2017-02-21 00:43:28 +00:00
"left_delimiter",
"perms",
2017-02-21 00:43:28 +00:00
"right_delimiter",
"source",
2016-09-26 22:23:26 +00:00
"splay",
"env",
"vault_grace",
2016-09-26 22:23:26 +00:00
}
2017-10-13 21:36:02 +00:00
if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
2016-09-26 22:23:26 +00:00
return err
}
var m map[string]interface{}
if err := hcl.DecodeObject(&m, o.Val); err != nil {
return err
}
2017-02-06 19:48:28 +00:00
templ := &api.Template{
ChangeMode: helper.StringToPtr("restart"),
Splay: helper.TimeToPtr(5 * time.Second),
Perms: helper.StringToPtr("0644"),
}
2016-09-26 22:23:26 +00:00
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
WeaklyTypedInput: true,
Result: templ,
})
if err != nil {
return err
}
if err := dec.Decode(m); err != nil {
return err
}
*result = append(*result, templ)
}
return nil
}
2017-02-06 19:48:28 +00:00
func parseServices(jobName string, taskGroupName string, task *api.Task, serviceObjs *ast.ObjectList) error {
2017-03-01 23:30:01 +00:00
task.Services = make([]*api.Service, len(serviceObjs.Items))
for idx, o := range serviceObjs.Items {
2016-03-10 18:16:35 +00:00
// Check for invalid keys
valid := []string{
"name",
"tags",
2018-04-19 22:12:23 +00:00
"canary_tags",
2016-03-10 18:16:35 +00:00
"port",
"check",
"address_mode",
"check_restart",
2016-03-10 18:16:35 +00:00
}
2017-10-13 21:36:02 +00:00
if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
2016-03-10 18:16:35 +00:00
return multierror.Prefix(err, fmt.Sprintf("service (%d) ->", idx))
}
2017-02-06 19:48:28 +00:00
var service api.Service
var m map[string]interface{}
if err := hcl.DecodeObject(&m, o.Val); err != nil {
return err
}
delete(m, "check")
delete(m, "check_restart")
if err := mapstructure.WeakDecode(m, &service); err != nil {
return err
}
2016-03-10 18:16:35 +00:00
// Filter checks
var checkList *ast.ObjectList
if ot, ok := o.Val.(*ast.ObjectType); ok {
checkList = ot.List
} else {
2015-11-18 00:05:03 +00:00
return fmt.Errorf("service '%s': should be an object", service.Name)
}
if co := checkList.Filter("check"); len(co.Items) > 0 {
2015-11-17 22:21:14 +00:00
if err := parseChecks(&service, co); err != nil {
2016-03-10 18:16:35 +00:00
return multierror.Prefix(err, fmt.Sprintf("service: '%s',", service.Name))
}
}
// Filter check_restart
if cro := checkList.Filter("check_restart"); len(cro.Items) > 0 {
if len(cro.Items) > 1 {
return fmt.Errorf("check_restart '%s': cannot have more than 1 check_restart", service.Name)
}
if cr, err := parseCheckRestart(cro.Items[0]); err != nil {
return multierror.Prefix(err, fmt.Sprintf("service: '%s',", service.Name))
} else {
service.CheckRestart = cr
}
}
2017-03-01 23:30:01 +00:00
task.Services[idx] = &service
}
return nil
}
2017-02-06 19:48:28 +00:00
func parseChecks(service *api.Service, checkObjs *ast.ObjectList) error {
service.Checks = make([]api.ServiceCheck, len(checkObjs.Items))
2015-11-17 22:21:14 +00:00
for idx, co := range checkObjs.Items {
2016-03-10 18:16:35 +00:00
// Check for invalid keys
valid := []string{
"name",
"type",
"interval",
"timeout",
"path",
"protocol",
"port",
"command",
2016-03-25 02:00:24 +00:00
"args",
2016-08-16 19:05:15 +00:00
"initial_status",
"tls_skip_verify",
"header",
"method",
"check_restart",
"address_mode",
"grpc_service",
"grpc_use_tls",
2016-03-10 18:16:35 +00:00
}
2017-10-13 21:36:02 +00:00
if err := helper.CheckHCLKeys(co.Val, valid); err != nil {
2016-03-10 18:16:35 +00:00
return multierror.Prefix(err, "check ->")
}
2017-02-06 19:48:28 +00:00
var check api.ServiceCheck
2015-11-17 22:21:14 +00:00
var cm map[string]interface{}
if err := hcl.DecodeObject(&cm, co.Val); err != nil {
return err
}
// HCL allows repeating stanzas so merge 'header' into a single
// map[string][]string.
if headerI, ok := cm["header"]; ok {
headerRaw, ok := headerI.([]map[string]interface{})
if !ok {
return fmt.Errorf("check -> header -> expected a []map[string][]string but found %T", headerI)
}
m := map[string][]string{}
for _, rawm := range headerRaw {
for k, vI := range rawm {
vs, ok := vI.([]interface{})
if !ok {
return fmt.Errorf("check -> header -> %q expected a []string but found %T", k, vI)
}
for _, vI := range vs {
v, ok := vI.(string)
if !ok {
return fmt.Errorf("check -> header -> %q expected a string but found %T", k, vI)
}
m[k] = append(m[k], v)
}
}
}
check.Header = m
// Remove "header" as it has been parsed
delete(cm, "header")
}
delete(cm, "check_restart")
2015-11-17 22:21:14 +00:00
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
WeaklyTypedInput: true,
Result: &check,
})
if err != nil {
return err
}
if err := dec.Decode(cm); err != nil {
return err
}
// Filter check_restart
var checkRestartList *ast.ObjectList
if ot, ok := co.Val.(*ast.ObjectType); ok {
checkRestartList = ot.List
} else {
return fmt.Errorf("check_restart '%s': should be an object", check.Name)
}
if cro := checkRestartList.Filter("check_restart"); len(cro.Items) > 0 {
if len(cro.Items) > 1 {
return fmt.Errorf("check_restart '%s': cannot have more than 1 check_restart", check.Name)
}
if cr, err := parseCheckRestart(cro.Items[0]); err != nil {
return multierror.Prefix(err, fmt.Sprintf("check: '%s',", check.Name))
} else {
check.CheckRestart = cr
}
}
2017-02-06 19:48:28 +00:00
service.Checks[idx] = check
2015-11-17 22:21:14 +00:00
}
return nil
}
func parseCheckRestart(cro *ast.ObjectItem) (*api.CheckRestart, error) {
valid := []string{
"limit",
2017-09-26 17:21:35 +00:00
"grace",
"ignore_warnings",
}
2017-10-13 21:36:02 +00:00
if err := helper.CheckHCLKeys(cro.Val, valid); err != nil {
return nil, multierror.Prefix(err, "check_restart ->")
}
var checkRestart api.CheckRestart
var crm map[string]interface{}
if err := hcl.DecodeObject(&crm, cro.Val); err != nil {
return nil, err
}
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
WeaklyTypedInput: true,
Result: &checkRestart,
})
if err != nil {
return nil, err
}
if err := dec.Decode(crm); err != nil {
return nil, err
}
return &checkRestart, nil
}
2017-02-06 19:48:28 +00:00
func parseResources(result *api.Resources, list *ast.ObjectList) error {
2015-11-09 06:57:39 +00:00
list = list.Elem()
if len(list.Items) == 0 {
return nil
}
if len(list.Items) > 1 {
2015-09-15 01:30:26 +00:00
return fmt.Errorf("only one 'resource' block allowed per task")
}
2015-11-09 06:57:39 +00:00
// Get our resource object
o := list.Items[0]
// We need this later
var listVal *ast.ObjectList
if ot, ok := o.Val.(*ast.ObjectType); ok {
listVal = ot.List
} else {
return fmt.Errorf("resource: should be an object")
}
2016-03-10 18:16:35 +00:00
// Check for invalid keys
valid := []string{
"cpu",
"iops", // COMPAT(0.10): Remove after one release to allow it to be removed from jobspecs
"disk",
2016-03-10 18:16:35 +00:00
"memory",
"network",
2018-10-08 23:09:41 +00:00
"device",
2016-03-10 18:16:35 +00:00
}
2017-10-13 21:36:02 +00:00
if err := helper.CheckHCLKeys(listVal, valid); err != nil {
2016-03-10 18:16:35 +00:00
return multierror.Prefix(err, "resources ->")
}
2015-11-09 06:57:39 +00:00
var m map[string]interface{}
if err := hcl.DecodeObject(&m, o.Val); err != nil {
return err
}
delete(m, "network")
2018-10-08 23:09:41 +00:00
delete(m, "device")
2015-11-09 06:57:39 +00:00
if err := mapstructure.WeakDecode(m, result); err != nil {
return err
}
// Parse the network resources
if o := listVal.Filter("network"); len(o.Items) > 0 {
if len(o.Items) > 1 {
return fmt.Errorf("only one 'network' resource allowed")
}
2016-03-10 18:16:35 +00:00
// Check for invalid keys
valid := []string{
"mbits",
"port",
}
2017-10-13 21:36:02 +00:00
if err := helper.CheckHCLKeys(o.Items[0].Val, valid); err != nil {
2016-03-10 18:16:35 +00:00
return multierror.Prefix(err, "resources, network ->")
}
2017-02-06 19:48:28 +00:00
var r api.NetworkResource
2015-09-15 01:27:37 +00:00
var m map[string]interface{}
2015-11-09 06:57:39 +00:00
if err := hcl.DecodeObject(&m, o.Items[0].Val); err != nil {
2015-09-15 01:27:37 +00:00
return err
}
2015-11-09 06:57:39 +00:00
if err := mapstructure.WeakDecode(m, &r); err != nil {
2015-09-15 01:27:37 +00:00
return err
}
var networkObj *ast.ObjectList
if ot, ok := o.Items[0].Val.(*ast.ObjectType); ok {
2015-11-14 04:51:30 +00:00
networkObj = ot.List
} else {
return fmt.Errorf("resource: should be an object")
}
if err := parsePorts(networkObj, &r); err != nil {
2016-03-10 18:16:35 +00:00
return multierror.Prefix(err, "resources, network, ports ->")
2015-09-15 01:27:37 +00:00
}
2017-02-06 19:48:28 +00:00
result.Networks = []*api.NetworkResource{&r}
2015-09-15 01:27:37 +00:00
}
2018-10-08 23:09:41 +00:00
// Parse the device resources
if o := listVal.Filter("device"); len(o.Items) > 0 {
result.Devices = make([]*api.RequestedDevice, len(o.Items))
for idx, do := range o.Items {
if l := len(do.Keys); l == 0 {
return multierror.Prefix(fmt.Errorf("missing device name"), fmt.Sprintf("resources, device[%d]->", idx))
} else if l > 1 {
return multierror.Prefix(fmt.Errorf("only one name may be specified"), fmt.Sprintf("resources, device[%d]->", idx))
}
name := do.Keys[0].Token.Value().(string)
// Value should be an object
var listVal *ast.ObjectList
if ot, ok := do.Val.(*ast.ObjectType); ok {
listVal = ot.List
} else {
return fmt.Errorf("device should be an object")
}
2018-10-08 23:09:41 +00:00
// Check for invalid keys
valid := []string{
"name",
"count",
"affinity",
"constraint",
2018-10-08 23:09:41 +00:00
}
if err := helper.CheckHCLKeys(do.Val, valid); err != nil {
return multierror.Prefix(err, fmt.Sprintf("resources, device[%d]->", idx))
}
// Set the name
var r api.RequestedDevice
r.Name = name
var m map[string]interface{}
if err := hcl.DecodeObject(&m, do.Val); err != nil {
return err
}
delete(m, "constraint")
delete(m, "affinity")
2018-10-08 23:09:41 +00:00
if err := mapstructure.WeakDecode(m, &r); err != nil {
return err
}
// Parse constraints
if o := listVal.Filter("constraint"); len(o.Items) > 0 {
if err := parseConstraints(&r.Constraints, o); err != nil {
return multierror.Prefix(err, "constraint ->")
}
}
// Parse affinities
if o := listVal.Filter("affinity"); len(o.Items) > 0 {
if err := parseAffinities(&r.Affinities, o); err != nil {
return multierror.Prefix(err, "affinity ->")
}
}
2018-10-08 23:09:41 +00:00
result.Devices[idx] = &r
}
}
2015-09-15 01:27:37 +00:00
return nil
}
2017-02-06 19:48:28 +00:00
func parsePorts(networkObj *ast.ObjectList, nw *api.NetworkResource) error {
2016-03-10 18:16:35 +00:00
// Check for invalid keys
valid := []string{
"mbits",
"port",
}
2017-10-13 21:36:02 +00:00
if err := helper.CheckHCLKeys(networkObj, valid); err != nil {
2016-03-10 18:16:35 +00:00
return err
}
2015-11-15 08:10:48 +00:00
portsObjList := networkObj.Filter("port")
knownPortLabels := make(map[string]bool)
for _, port := range portsObjList.Items {
if len(port.Keys) == 0 {
2015-12-18 20:19:47 +00:00
return fmt.Errorf("ports must be named")
}
label := port.Keys[0].Token.Value().(string)
2015-11-15 08:10:48 +00:00
if !reDynamicPorts.MatchString(label) {
return errPortLabel
}
l := strings.ToLower(label)
if knownPortLabels[l] {
2015-12-18 20:19:47 +00:00
return fmt.Errorf("found a port label collision: %s", label)
}
var p map[string]interface{}
2017-02-06 19:48:28 +00:00
var res api.Port
if err := hcl.DecodeObject(&p, port.Val); err != nil {
return err
}
if err := mapstructure.WeakDecode(p, &res); err != nil {
return err
}
res.Label = label
if res.Value > 0 {
nw.ReservedPorts = append(nw.ReservedPorts, res)
} else {
nw.DynamicPorts = append(nw.DynamicPorts, res)
}
2015-11-15 08:10:48 +00:00
knownPortLabels[l] = true
}
return nil
}
2017-02-06 19:48:28 +00:00
func parseUpdate(result **api.UpdateStrategy, list *ast.ObjectList) error {
2015-11-09 06:57:39 +00:00
list = list.Elem()
if len(list.Items) > 1 {
return fmt.Errorf("only one 'update' block allowed")
}
2015-11-09 06:57:39 +00:00
// Get our resource object
o := list.Items[0]
2015-11-09 06:57:39 +00:00
var m map[string]interface{}
if err := hcl.DecodeObject(&m, o.Val); err != nil {
return err
}
2016-03-10 18:16:35 +00:00
// Check for invalid keys
valid := []string{
"stagger",
"max_parallel",
"health_check",
"min_healthy_time",
"healthy_deadline",
2018-03-23 17:56:00 +00:00
"progress_deadline",
"auto_revert",
2019-05-09 13:42:18 +00:00
"auto_promote",
"canary",
2016-03-10 18:16:35 +00:00
}
2017-10-13 21:36:02 +00:00
if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
2016-03-10 18:16:35 +00:00
return err
}
2015-11-09 06:57:39 +00:00
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
WeaklyTypedInput: true,
Result: result,
})
if err != nil {
return err
}
2015-11-09 06:57:39 +00:00
return dec.Decode(m)
}
2015-12-01 00:51:56 +00:00
func parseMigrate(result **api.MigrateStrategy, list *ast.ObjectList) error {
list = list.Elem()
if len(list.Items) > 1 {
return fmt.Errorf("only one 'migrate' block allowed")
}
// 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{
"max_parallel",
"health_check",
"min_healthy_time",
"healthy_deadline",
}
if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
return err
}
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
WeaklyTypedInput: true,
Result: result,
})
if err != nil {
return err
}
return dec.Decode(m)
}
2017-02-06 19:48:28 +00:00
func parsePeriodic(result **api.PeriodicConfig, list *ast.ObjectList) error {
2015-12-01 00:51:56 +00:00
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
}
2016-03-10 18:16:35 +00:00
// Check for invalid keys
valid := []string{
"enabled",
"cron",
"prohibit_overlap",
2017-02-15 22:37:06 +00:00
"time_zone",
2016-03-10 18:16:35 +00:00
}
2017-10-13 21:36:02 +00:00
if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
2016-03-10 18:16:35 +00:00
return err
}
if value, ok := m["enabled"]; ok {
2015-12-01 00:51:56 +00:00
enabled, err := parseBool(value)
if err != nil {
return fmt.Errorf("periodic.enabled should be set to true or false; %v", err)
}
m["Enabled"] = enabled
}
2015-12-19 01:51:30 +00:00
// If "cron" is provided, set the type to "cron" and store the spec.
if cron, ok := m["cron"]; ok {
m["SpecType"] = api.PeriodicSpecCron
2015-12-01 00:51:56 +00:00
m["Spec"] = cron
}
// Build the constraint
2017-02-06 19:48:28 +00:00
var p api.PeriodicConfig
2015-12-01 00:51:56 +00:00
if err := mapstructure.WeakDecode(m, &p); err != nil {
return err
}
2015-12-01 16:58:36 +00:00
*result = &p
2015-12-01 00:51:56 +00:00
return nil
}
2016-03-10 18:16:35 +00:00
2017-02-06 19:48:28 +00:00
func parseVault(result *api.Vault, list *ast.ObjectList) error {
2016-08-09 23:07:45 +00:00
list = list.Elem()
if len(list.Items) == 0 {
return nil
}
if len(list.Items) > 1 {
return fmt.Errorf("only one 'vault' block allowed per task")
}
// Get our resource object
o := list.Items[0]
// We need this later
var listVal *ast.ObjectList
if ot, ok := o.Val.(*ast.ObjectType); ok {
listVal = ot.List
} else {
return fmt.Errorf("vault: should be an object")
}
// Check for invalid keys
valid := []string{
"policies",
2016-09-20 20:22:29 +00:00
"env",
2016-10-11 22:25:49 +00:00
"change_mode",
"change_signal",
2016-08-09 23:07:45 +00:00
}
2017-10-13 21:36:02 +00:00
if err := helper.CheckHCLKeys(listVal, valid); err != nil {
2016-08-09 23:07:45 +00:00
return multierror.Prefix(err, "vault ->")
}
var m map[string]interface{}
if err := hcl.DecodeObject(&m, o.Val); err != nil {
return err
}
if err := mapstructure.WeakDecode(m, result); err != nil {
return err
}
return nil
}
2017-02-06 19:48:28 +00:00
func parseParameterizedJob(result **api.ParameterizedJobConfig, list *ast.ObjectList) error {
2016-11-23 23:48:36 +00:00
list = list.Elem()
if len(list.Items) > 1 {
2017-01-20 20:46:04 +00:00
return fmt.Errorf("only one 'parameterized' block allowed per job")
2016-11-23 23:48:36 +00:00
}
// 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{
2016-12-14 20:50:08 +00:00
"payload",
2017-01-26 05:13:18 +00:00
"meta_required",
"meta_optional",
2016-11-23 23:48:36 +00:00
}
2017-10-13 21:36:02 +00:00
if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
2016-11-23 23:48:36 +00:00
return err
}
// Build the parameterized job block
2017-02-06 19:48:28 +00:00
var d api.ParameterizedJobConfig
2016-11-23 23:48:36 +00:00
if err := mapstructure.WeakDecode(m, &d); err != nil {
return err
}
*result = &d
return nil
}