open-nomad/jobspec/parse.go

1290 lines
30 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
2016-03-10 18:16:35 +00:00
"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/hashicorp/nomad/nomad/structs"
"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",
}
if err := checkHCLKeys(list, valid); err != nil {
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")
2015-09-15 00:46:52 +00:00
delete(m, "meta")
delete(m, "update")
2015-12-01 00:51:56 +00:00
delete(m, "periodic")
2016-09-21 18:18:44 +00:00
delete(m, "vault")
2017-01-20 20:46:04 +00:00
delete(m, "parameterized")
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",
"datacenters",
2017-01-20 20:46:04 +00:00
"parameterized",
2016-11-23 23:48:36 +00:00
"group",
2016-03-10 18:16:35 +00:00
"id",
2016-11-23 23:48:36 +00:00
"meta",
2016-03-10 18:16:35 +00:00
"name",
2016-11-23 23:48:36 +00:00
"periodic",
"priority",
2016-03-10 18:16:35 +00:00
"region",
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
}
if err := checkHCLKeys(listVal, valid); err != nil {
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
}
}
// 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
}
}
// 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
}
}
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",
"restart",
"meta",
"task",
"ephemeral_disk",
2016-09-21 18:18:44 +00:00
"vault",
2016-03-10 18:16:35 +00:00
}
if err := checkHCLKeys(listVal, valid); err != nil {
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")
delete(m, "meta")
delete(m, "task")
delete(m, "restart")
delete(m, "ephemeral_disk")
2016-09-21 18:18:44 +00:00
delete(m, "vault")
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
}
}
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 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))
}
}
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",
}
if err := checkHCLKeys(obj.Val, valid); err != nil {
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
}
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
}
if err := checkHCLKeys(o.Val, valid); err != nil {
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[structs.ConstraintVersion]; ok {
m["Operand"] = structs.ConstraintVersion
m["RTarget"] = constraint
}
// If "regexp" is provided, set the operand
// to "regexp" and the value to the "RTarget"
if constraint, ok := m[structs.ConstraintRegex]; ok {
m["Operand"] = structs.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[structs.ConstraintSetContains]; ok {
m["Operand"] = structs.ConstraintSetContains
m["RTarget"] = constraint
}
if value, ok := m[structs.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"] = structs.ConstraintDistinctHosts
}
2017-03-07 22:20:02 +00:00
if property, ok := m[structs.ConstraintDistinctProperty]; ok {
m["Operand"] = structs.ConstraintDistinctProperty
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
}
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",
}
if err := 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
}
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",
"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",
2016-09-26 22:23:26 +00:00
"template",
2016-08-09 23:07:45 +00:00
"user",
"vault",
2016-03-10 18:16:35 +00:00
}
if err := checkHCLKeys(listVal, valid); err != nil {
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")
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
}
}
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",
}
if err := checkHCLKeys(logsBlock.Val, valid); err != nil {
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",
}
if err := 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",
"destination",
2016-03-14 05:29:07 +00:00
}
if err := checkHCLKeys(o.Val, valid); err != nil {
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",
}
if err := checkHCLKeys(o.Val, valid); err != nil {
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",
"port",
"check",
}
if err := checkHCLKeys(o.Val, valid); err != nil {
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")
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))
}
}
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",
2016-03-10 18:16:35 +00:00
}
if err := checkHCLKeys(co.Val, valid); err != nil {
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
}
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
}
2017-02-06 19:48:28 +00:00
service.Checks[idx] = check
2015-11-17 22:21:14 +00:00
}
return 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",
"disk",
2016-03-10 18:16:35 +00:00
"memory",
"network",
}
if err := checkHCLKeys(listVal, valid); err != nil {
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")
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",
}
if err := checkHCLKeys(o.Items[0].Val, valid); err != nil {
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
}
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",
}
if err := checkHCLKeys(networkObj, valid); err != nil {
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 per job")
}
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",
}
if err := checkHCLKeys(o.Val, valid); err != nil {
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
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
}
if err := checkHCLKeys(o.Val, valid); err != nil {
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 {
2015-12-01 00:51:56 +00:00
m["SpecType"] = structs.PeriodicSpecCron
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
}
if err := checkHCLKeys(listVal, valid); err != nil {
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
}
if err := checkHCLKeys(o.Val, valid); err != nil {
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
}
2016-03-10 18:16:35 +00:00
func checkHCLKeys(node ast.Node, valid []string) error {
var list *ast.ObjectList
switch n := node.(type) {
case *ast.ObjectList:
list = n
case *ast.ObjectType:
list = n.List
default:
return fmt.Errorf("cannot check HCL keys of type %T", n)
}
validMap := make(map[string]struct{}, len(valid))
for _, v := range valid {
validMap[v] = struct{}{}
}
var result error
for _, item := range list.Items {
key := item.Keys[0].Token.Value().(string)
if _, ok := validMap[key]; !ok {
result = multierror.Append(result, fmt.Errorf(
"invalid key: %s", key))
}
}
return result
}