2015-09-15 00:43:42 +00:00
|
|
|
package jobspec
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2015-09-23 20:44:08 +00:00
|
|
|
"regexp"
|
2015-10-22 23:37:20 +00:00
|
|
|
"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",
|
|
|
|
}
|
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")
|
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")
|
2018-01-18 20:49:01 +00:00
|
|
|
delete(m, "reschedule")
|
2018-03-01 19:21:32 +00:00
|
|
|
delete(m, "update")
|
|
|
|
delete(m, "vault")
|
2015-09-15 00:43:42 +00:00
|
|
|
|
2015-09-17 05:06:55 +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))
|
2017-02-28 00:00:19 +00:00
|
|
|
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 {
|
2017-02-28 00:00:19 +00:00
|
|
|
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",
|
|
|
|
"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",
|
2016-08-11 22:37:53 +00:00
|
|
|
"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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-20 21:18:10 +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 {
|
2015-09-20 21:18:10 +00:00
|
|
|
if err := parseUpdate(&result.Update, o); err != nil {
|
2016-03-10 18:16:35 +00:00
|
|
|
return multierror.Prefix(err, "update ->")
|
2015-09-20 21:18:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-20 18:33:52 +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 {
|
2017-01-20 18:33:52 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-18 20:49:01 +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-01-18 20:49:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-30 22:51:39 +00:00
|
|
|
// 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",
|
2016-09-14 22:43:42 +00:00
|
|
|
"ephemeral_disk",
|
2017-05-09 00:44:26 +00:00
|
|
|
"update",
|
2018-01-18 20:49:01 +00:00
|
|
|
"reschedule",
|
2016-09-21 18:18:44 +00:00
|
|
|
"vault",
|
2018-01-24 00:47:00 +00:00
|
|
|
"migrate",
|
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")
|
|
|
|
delete(m, "meta")
|
|
|
|
delete(m, "task")
|
2015-10-30 22:51:39 +00:00
|
|
|
delete(m, "restart")
|
2016-09-14 22:43:42 +00:00
|
|
|
delete(m, "ephemeral_disk")
|
2017-05-09 00:44:26 +00:00
|
|
|
delete(m, "update")
|
2016-09-21 18:18:44 +00:00
|
|
|
delete(m, "vault")
|
2018-01-24 00:47:00 +00:00
|
|
|
delete(m, "migrate")
|
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
|
|
|
}
|
2015-10-30 22:51:39 +00:00
|
|
|
}
|
|
|
|
|
2018-01-18 20:49:01 +00:00
|
|
|
// 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))
|
|
|
|
}
|
|
|
|
}
|
2016-09-14 22:43:42 +00:00
|
|
|
// Parse ephemeral disk
|
|
|
|
if o := listVal.Filter("ephemeral_disk"); len(o.Items) > 0 {
|
2017-02-22 20:30:05 +00:00
|
|
|
g.EphemeralDisk = &api.EphemeralDisk{}
|
2016-09-14 22:43:42 +00:00
|
|
|
if err := parseEphemeralDisk(&g.EphemeralDisk, o); err != nil {
|
|
|
|
return multierror.Prefix(err, fmt.Sprintf("'%s', ephemeral_disk ->", n))
|
2016-08-24 18:51:15 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-09 00:44:26 +00:00
|
|
|
// 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 ->")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-24 00:47:00 +00:00
|
|
|
// 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-10-30 22:51:39 +00:00
|
|
|
}
|
|
|
|
|
2015-11-09 06:57:39 +00:00
|
|
|
// Get our job object
|
|
|
|
obj := list.Items[0]
|
2015-10-30 22:51:39 +00:00
|
|
|
|
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
|
2015-10-30 22:51:39 +00:00
|
|
|
}
|
|
|
|
|
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-10-30 22:51:39 +00:00
|
|
|
}
|
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
|
2015-10-30 22:51:39 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-01-18 20:49:01 +00:00
|
|
|
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",
|
2018-03-02 00:16:01 +00:00
|
|
|
"unlimited",
|
|
|
|
"delay",
|
2018-03-13 15:06:26 +00:00
|
|
|
"max_delay",
|
2018-03-02 00:16:01 +00:00
|
|
|
"delay_function",
|
2018-01-18 20:49:01 +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
|
|
|
|
}
|
|
|
|
|
|
|
|
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"]
|
|
|
|
|
2015-10-11 19:20:58 +00:00
|
|
|
// If "version" is provided, set the operand
|
|
|
|
// to "version" and the value to the "RTarget"
|
2015-10-26 20:47:56 +00:00
|
|
|
if constraint, ok := m[structs.ConstraintVersion]; ok {
|
|
|
|
m["Operand"] = structs.ConstraintVersion
|
2015-10-11 19:20:58 +00:00
|
|
|
m["RTarget"] = constraint
|
2015-10-11 19:37:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// If "regexp" is provided, set the operand
|
|
|
|
// to "regexp" and the value to the "RTarget"
|
2015-10-26 20:47:56 +00:00
|
|
|
if constraint, ok := m[structs.ConstraintRegex]; ok {
|
|
|
|
m["Operand"] = structs.ConstraintRegex
|
2015-10-11 19:37:50 +00:00
|
|
|
m["RTarget"] = constraint
|
2015-10-11 19:20:58 +00:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2015-10-26 20:47:56 +00:00
|
|
|
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)
|
2015-10-22 23:37:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// If it is not enabled, skip the constraint.
|
|
|
|
if !enabled {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2015-10-26 20:47:56 +00:00
|
|
|
m["Operand"] = structs.ConstraintDistinctHosts
|
2015-10-22 23:37:20 +00:00
|
|
|
}
|
|
|
|
|
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 {
|
2016-08-24 18:51:15 +00:00
|
|
|
list = list.Elem()
|
|
|
|
if len(list.Items) > 1 {
|
2016-09-14 22:43:42 +00:00
|
|
|
return fmt.Errorf("only one 'ephemeral_disk' block allowed")
|
2016-08-24 18:51:15 +00:00
|
|
|
}
|
|
|
|
|
2016-09-14 22:43:42 +00:00
|
|
|
// Get our ephemeral_disk object
|
2016-08-24 18:51:15 +00:00
|
|
|
obj := list.Items[0]
|
|
|
|
|
|
|
|
// Check for invalid keys
|
|
|
|
valid := []string{
|
|
|
|
"sticky",
|
2016-09-14 22:43:42 +00:00
|
|
|
"size",
|
2016-10-03 16:59:57 +00:00
|
|
|
"migrate",
|
2016-08-24 18:51:15 +00:00
|
|
|
}
|
2017-10-13 21:36:02 +00:00
|
|
|
if err := helper.CheckHCLKeys(obj.Val, valid); err != nil {
|
2016-08-24 18:51:15 +00:00
|
|
|
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
|
2016-09-14 22:43:42 +00:00
|
|
|
if err := mapstructure.WeakDecode(m, &ephemeralDisk); err != nil {
|
2016-08-24 18:51:15 +00:00
|
|
|
return err
|
|
|
|
}
|
2016-09-14 22:43:42 +00:00
|
|
|
*result = &ephemeralDisk
|
2016-08-24 18:51:15 +00:00
|
|
|
|
|
|
|
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",
|
2017-01-26 05:06:16 +00:00
|
|
|
"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",
|
2017-08-17 00:54:11 +00:00
|
|
|
"shutdown_delay",
|
2016-09-26 22:23:26 +00:00
|
|
|
"template",
|
2016-08-09 23:07:45 +00:00
|
|
|
"user",
|
|
|
|
"vault",
|
2017-11-30 21:53:35 +00:00
|
|
|
"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")
|
2017-01-26 05:06:16 +00:00
|
|
|
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
|
|
|
|
}
|
2015-12-23 00:10:30 +00:00
|
|
|
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
|
|
|
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
|
|
|
|
WeaklyTypedInput: true,
|
|
|
|
Result: &t,
|
|
|
|
})
|
2017-11-30 21:53:35 +00:00
|
|
|
|
2015-12-23 00:10:30 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := dec.Decode(m); err != nil {
|
2015-09-15 00:43:42 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-09-30 16:18:43 +00:00
|
|
|
// 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 {
|
2015-09-30 16:18:43 +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-30 16:18:43 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := mapstructure.WeakDecode(m, &t.Env); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-17 06:37:09 +00:00
|
|
|
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-11-17 06:37:09 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-11-14 02:09:42 +00:00
|
|
|
|
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",
|
|
|
|
}
|
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
|
|
|
|
}
|
|
|
|
|
2017-02-22 20:30:05 +00:00
|
|
|
var log api.LogConfig
|
|
|
|
if err := mapstructure.WeakDecode(m, &log); err != nil {
|
2016-02-05 07:28:01 +00:00
|
|
|
return err
|
|
|
|
}
|
2017-02-22 20:30:05 +00:00
|
|
|
|
|
|
|
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"),
|
|
|
|
}
|
|
|
|
|
2016-10-17 18:41:22 +00:00
|
|
|
if err := parseVault(v, o); err != nil {
|
2016-08-09 23:07:45 +00:00
|
|
|
return multierror.Prefix(err, fmt.Sprintf("'%s', vault ->", n))
|
|
|
|
}
|
|
|
|
|
2016-10-17 18:41:22 +00:00
|
|
|
t.Vault = v
|
2016-08-09 23:07:45 +00:00
|
|
|
}
|
|
|
|
|
2017-01-26 05:06:16 +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 {
|
2017-01-26 05:06:16 +00:00
|
|
|
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 {
|
2017-01-26 05:06:16 +00:00
|
|
|
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{}
|
2017-01-26 05:06:16 +00:00
|
|
|
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",
|
2017-07-06 03:44:49 +00:00
|
|
|
"mode",
|
2016-03-18 19:01:46 +00:00
|
|
|
"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)
|
2016-03-14 22:46:06 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2016-03-14 22:46:06 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2016-03-14 22:46:06 +00:00
|
|
|
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",
|
2017-02-01 04:00:33 +00:00
|
|
|
"data",
|
|
|
|
"destination",
|
2017-02-21 00:43:28 +00:00
|
|
|
"left_delimiter",
|
2017-02-01 04:00:33 +00:00
|
|
|
"perms",
|
2017-02-21 00:43:28 +00:00
|
|
|
"right_delimiter",
|
2017-02-01 04:00:33 +00:00
|
|
|
"source",
|
2016-09-26 22:23:26 +00:00
|
|
|
"splay",
|
2017-05-13 00:07:54 +00:00
|
|
|
"env",
|
2017-08-01 21:14:08 +00:00
|
|
|
"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))
|
2015-11-17 20:38:34 +00:00
|
|
|
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",
|
2017-06-09 17:29:41 +00:00
|
|
|
"address_mode",
|
2018-01-04 23:04:45 +00:00
|
|
|
"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
|
2015-11-17 20:38:34 +00:00
|
|
|
var m map[string]interface{}
|
|
|
|
if err := hcl.DecodeObject(&m, o.Val); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
delete(m, "check")
|
2017-08-26 05:40:18 +00:00
|
|
|
delete(m, "check_restart")
|
2015-11-17 20:38:34 +00:00
|
|
|
|
|
|
|
if err := mapstructure.WeakDecode(m, &service); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-03-10 18:16:35 +00:00
|
|
|
// Filter checks
|
2015-11-17 20:38:34 +00:00
|
|
|
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)
|
2015-11-17 20:38:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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))
|
2015-11-17 20:38:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-26 05:40:18 +00:00
|
|
|
// 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
|
2015-11-17 20:38:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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",
|
2016-07-08 21:09:27 +00:00
|
|
|
"port",
|
2016-03-28 21:05:12 +00:00
|
|
|
"command",
|
2016-03-25 02:00:24 +00:00
|
|
|
"args",
|
2016-08-16 19:05:15 +00:00
|
|
|
"initial_status",
|
2017-04-19 18:46:30 +00:00
|
|
|
"tls_skip_verify",
|
2017-08-15 23:13:05 +00:00
|
|
|
"header",
|
|
|
|
"method",
|
2017-08-26 05:40:18 +00:00
|
|
|
"check_restart",
|
2017-12-05 19:39:42 +00:00
|
|
|
"address_mode",
|
2018-05-03 22:18:12 +00:00
|
|
|
"grpc_service",
|
2018-05-02 23:49:47 +00:00
|
|
|
"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
|
|
|
|
}
|
2017-08-15 23:13:05 +00:00
|
|
|
|
|
|
|
// 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 {
|
2017-08-17 22:21:28 +00:00
|
|
|
return fmt.Errorf("check -> header -> expected a []map[string][]string but found %T", headerI)
|
2017-08-15 23:13:05 +00:00
|
|
|
}
|
|
|
|
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")
|
|
|
|
}
|
|
|
|
|
2017-08-26 05:40:18 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2017-08-26 05:40:18 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2017-08-26 05:40:18 +00:00
|
|
|
func parseCheckRestart(cro *ast.ObjectItem) (*api.CheckRestart, error) {
|
|
|
|
valid := []string{
|
|
|
|
"limit",
|
2017-09-26 17:21:35 +00:00
|
|
|
"grace",
|
2017-09-11 00:00:25 +00:00
|
|
|
"ignore_warnings",
|
2017-08-26 05:40:18 +00:00
|
|
|
}
|
|
|
|
|
2017-10-13 21:36:02 +00:00
|
|
|
if err := helper.CheckHCLKeys(cro.Val, valid); err != nil {
|
2017-08-26 05:40:18 +00:00
|
|
|
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",
|
2016-11-09 01:00:59 +00:00
|
|
|
"disk",
|
2016-03-10 18:16:35 +00:00
|
|
|
"memory",
|
|
|
|
"network",
|
|
|
|
}
|
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")
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2015-11-14 02:09:42 +00:00
|
|
|
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
|
2015-11-14 02:09:42 +00:00
|
|
|
} 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
|
|
|
|
}
|
2015-09-20 21:18:10 +00:00
|
|
|
|
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")
|
2015-11-14 02:09:42 +00:00
|
|
|
knownPortLabels := make(map[string]bool)
|
|
|
|
for _, port := range portsObjList.Items {
|
2015-12-18 18:56:45 +00:00
|
|
|
if len(port.Keys) == 0 {
|
2015-12-18 20:19:47 +00:00
|
|
|
return fmt.Errorf("ports must be named")
|
2015-12-18 18:56:45 +00:00
|
|
|
}
|
2015-11-14 02:09:42 +00:00
|
|
|
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)
|
2015-11-14 02:09:42 +00:00
|
|
|
}
|
|
|
|
var p map[string]interface{}
|
2017-02-06 19:48:28 +00:00
|
|
|
var res api.Port
|
2015-11-14 02:09:42 +00:00
|
|
|
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
|
2015-11-14 02:09:42 +00:00
|
|
|
}
|
|
|
|
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 {
|
2017-05-09 00:44:26 +00:00
|
|
|
return fmt.Errorf("only one 'update' block allowed")
|
2015-09-20 21:18:10 +00:00
|
|
|
}
|
|
|
|
|
2015-11-09 06:57:39 +00:00
|
|
|
// Get our resource object
|
|
|
|
o := list.Items[0]
|
2015-09-20 21:18:10 +00:00
|
|
|
|
2015-11-09 06:57:39 +00:00
|
|
|
var m map[string]interface{}
|
|
|
|
if err := hcl.DecodeObject(&m, o.Val); err != nil {
|
|
|
|
return err
|
2015-09-20 21:18:10 +00:00
|
|
|
}
|
2015-10-30 22:51:39 +00:00
|
|
|
|
2016-03-10 18:16:35 +00:00
|
|
|
// Check for invalid keys
|
|
|
|
valid := []string{
|
2017-05-09 00:44:26 +00:00
|
|
|
// COMPAT: Remove in 0.7.0. Stagger is deprecated in 0.6.0.
|
2016-03-10 18:16:35 +00:00
|
|
|
"stagger",
|
|
|
|
"max_parallel",
|
2017-05-09 00:44:26 +00:00
|
|
|
"health_check",
|
|
|
|
"min_healthy_time",
|
|
|
|
"healthy_deadline",
|
2018-03-23 17:56:00 +00:00
|
|
|
"progress_deadline",
|
2017-05-09 00:44:26 +00:00
|
|
|
"auto_revert",
|
|
|
|
"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-10-30 22:51:39 +00:00
|
|
|
}
|
2015-11-09 06:57:39 +00:00
|
|
|
return dec.Decode(m)
|
2015-10-30 22:51:39 +00:00
|
|
|
}
|
2015-12-01 00:51:56 +00:00
|
|
|
|
2018-01-24 00:47:00 +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
|
|
|
|
}
|
|
|
|
|
2017-02-22 20:30:05 +00:00
|
|
|
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
|
|
|
}
|
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
|
|
|
|
}
|
|
|
|
|
2017-01-20 18:33:52 +00:00
|
|
|
// 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
|
|
|
|
}
|