open-nomad/jobspec/parse_multiregion.go
Tim Gross 6851024925 Multiregion structs
Initial struct definitions, jobspec parsing, validation, and conversion
between Nomad structs and API structs for multi-region deployments.
2020-06-17 11:00:14 -04:00

164 lines
3.8 KiB
Go

package jobspec
import (
"fmt"
multierror "github.com/hashicorp/go-multierror"
"github.com/hashicorp/hcl"
"github.com/hashicorp/hcl/hcl/ast"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/helper"
"github.com/mitchellh/mapstructure"
)
func parseMultiregion(result *api.Multiregion, list *ast.ObjectList) error {
list = list.Elem()
if len(list.Items) > 1 {
return fmt.Errorf("only one 'multiregion' block allowed")
}
if len(list.Items) == 0 {
return nil
}
// Get our multiregion object and decode it
obj := list.Items[0]
var m map[string]interface{}
if err := hcl.DecodeObject(&m, obj.Val); err != nil {
return err
}
// Value should be an object
var listVal *ast.ObjectList
if ot, ok := obj.Val.(*ast.ObjectType); ok {
listVal = ot.List
} else {
return fmt.Errorf("multiregion should be an object")
}
// Check for invalid keys
valid := []string{
"strategy",
"region",
}
if err := helper.CheckHCLKeys(obj.Val, valid); err != nil {
return err
}
// If we have a strategy, then parse that
if o := listVal.Filter("strategy"); len(o.Items) > 0 {
if err := parseMultiregionStrategy(&result.Strategy, o); err != nil {
return multierror.Prefix(err, "strategy ->")
}
}
// If we have regions, then parse those
if o := listVal.Filter("region"); len(o.Items) > 0 {
if err := parseMultiregionRegions(result, o); err != nil {
return multierror.Prefix(err, "regions ->")
}
} else {
return fmt.Errorf("'multiregion' requires one or more 'region' blocks")
}
return nil
}
func parseMultiregionStrategy(final **api.MultiregionStrategy, list *ast.ObjectList) error {
list = list.Elem()
if len(list.Items) > 1 {
return fmt.Errorf("only one 'strategy' block allowed")
}
// Get our job object
obj := list.Items[0]
// Check for invalid keys
valid := []string{
"max_parallel",
"auto_revert",
}
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.MultiregionStrategy
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
WeaklyTypedInput: true,
Result: &result,
})
if err != nil {
return err
}
if err := dec.Decode(m); err != nil {
return err
}
*final = &result
return nil
}
func parseMultiregionRegions(result *api.Multiregion, list *ast.ObjectList) error {
list = list.Children()
if len(list.Items) == 0 {
return nil
}
// Go through each object and turn it into an actual result.
collection := make([]*api.MultiregionRegion, 0, len(list.Items))
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("region '%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("region '%s': should be an object", n)
}
// Check for invalid keys
valid := []string{
"count",
"datacenters",
"meta",
}
if err := helper.CheckHCLKeys(listVal, valid); err != nil {
return multierror.Prefix(err, fmt.Sprintf("'%s' ->", n))
}
var m map[string]interface{}
if err := hcl.DecodeObject(&m, item.Val); err != nil {
return err
}
// Build the region with the basic decode
var r api.MultiregionRegion
r.Name = helper.StringToPtr(n)
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
WeaklyTypedInput: true,
Result: &r,
})
if err != nil {
return err
}
if err := dec.Decode(m); err != nil {
return err
}
collection = append(collection, &r)
}
result.Regions = append(result.Regions, collection...)
return nil
}