Skip to content

Commit

Permalink
Isolate the jobspec package from the rest of Nomad (#8815)
Browse files Browse the repository at this point in the history
This eases adoption of the jobspec package by other projects (e.g. terraform nomad provider, Lavant). Either by consuming directy as a library (hopefully without having go mod import rest of nomad) or by copying the package without modification.

Ideally, this package will be published as an independent module. We aren't ready for that considering we'll be switching to HCLv2 "soon", but eitherway, this seems like a reasonable intermediate step if we choose to.
  • Loading branch information
Mahmood Ali authored Sep 3, 2020
1 parent 884fe5c commit 9fe74c9
Show file tree
Hide file tree
Showing 10 changed files with 470 additions and 330 deletions.
113 changes: 113 additions & 0 deletions jobspec/helper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package jobspec

// These functions are copied from helper/funcs.go
// added here to avoid jobspec depending on any other package

import (
"fmt"
"reflect"
"strings"
"time"

multierror "github.com/hashicorp/go-multierror"
"github.com/hashicorp/hcl/hcl/ast"
)

// stringToPtr returns the pointer to a string
func stringToPtr(str string) *string {
return &str
}

// timeToPtr returns the pointer to a time.Duration.
func timeToPtr(t time.Duration) *time.Duration {
return &t
}

// boolToPtr returns the pointer to a boolean
func boolToPtr(b bool) *bool {
return &b
}

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
}

// UnusedKeys returns a pretty-printed error if any `hcl:",unusedKeys"` is not empty
func unusedKeys(obj interface{}) error {
val := reflect.ValueOf(obj)
if val.Kind() == reflect.Ptr {
val = reflect.Indirect(val)
}
return unusedKeysImpl([]string{}, val)
}

func unusedKeysImpl(path []string, val reflect.Value) error {
stype := val.Type()
for i := 0; i < stype.NumField(); i++ {
ftype := stype.Field(i)
fval := val.Field(i)
tags := strings.Split(ftype.Tag.Get("hcl"), ",")
name := tags[0]
tags = tags[1:]

if fval.Kind() == reflect.Ptr {
fval = reflect.Indirect(fval)
}

// struct? recurse. Add the struct's key to the path
if fval.Kind() == reflect.Struct {
err := unusedKeysImpl(append([]string{name}, path...), fval)
if err != nil {
return err
}
continue
}

// Search the hcl tags for "unusedKeys"
unusedKeys := false
for _, p := range tags {
if p == "unusedKeys" {
unusedKeys = true
break
}
}

if unusedKeys {
ks, ok := fval.Interface().([]string)
if ok && len(ks) != 0 {
ps := ""
if len(path) > 0 {
ps = strings.Join(path, ".") + " "
}
return fmt.Errorf("%sunexpected keys %s",
ps,
strings.Join(ks, ", "))
}
}
}
return nil
}
24 changes: 24 additions & 0 deletions jobspec/helper_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package jobspec

// These functions are copied from helper/funcs.go
// added here to avoid jobspec depending on any other package

// intToPtr returns the pointer to an int
func intToPtr(i int) *int {
return &i
}

// int8ToPtr returns the pointer to an int8
func int8ToPtr(i int8) *int8 {
return &i
}

// int64ToPtr returns the pointer to an int
func int64ToPtr(i int64) *int64 {
return &i
}

// Uint64ToPtr returns the pointer to an uint64
func uint64ToPtr(u uint64) *uint64 {
return &u
}
19 changes: 9 additions & 10 deletions jobspec/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"github.com/hashicorp/hcl"
"github.com/hashicorp/hcl/hcl/ast"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/helper"
"github.com/mitchellh/mapstructure"
)

Expand Down Expand Up @@ -48,7 +47,7 @@ func Parse(r io.Reader) (*api.Job, error) {
valid := []string{
"job",
}
if err := helper.CheckHCLKeys(list, valid); err != nil {
if err := checkHCLKeys(list, valid); err != nil {
return nil, err
}

Expand Down Expand Up @@ -100,7 +99,7 @@ func parseReschedulePolicy(final **api.ReschedulePolicy, list *ast.ObjectList) e
"max_delay",
"delay_function",
}
if err := helper.CheckHCLKeys(obj.Val, valid); err != nil {
if err := checkHCLKeys(obj.Val, valid); err != nil {
return err
}

Expand Down Expand Up @@ -140,7 +139,7 @@ func parseConstraints(result *[]*api.Constraint, list *ast.ObjectList) error {
"version",
"semver",
}
if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
if err := checkHCLKeys(o.Val, valid); err != nil {
return err
}

Expand Down Expand Up @@ -230,7 +229,7 @@ func parseAffinities(result *[]*api.Affinity, list *ast.ObjectList) error {
"semver",
"weight",
}
if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
if err := checkHCLKeys(o.Val, valid); err != nil {
return err
}

Expand Down Expand Up @@ -307,7 +306,7 @@ func parseSpread(result *[]*api.Spread, list *ast.ObjectList) error {
"weight",
"target",
}
if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
if err := checkHCLKeys(o.Val, valid); err != nil {
return err
}

Expand Down Expand Up @@ -370,7 +369,7 @@ func parseSpreadTarget(result *[]*api.SpreadTarget, list *ast.ObjectList) error
"percent",
"value",
}
if err := helper.CheckHCLKeys(listVal, valid); err != nil {
if err := checkHCLKeys(listVal, valid); err != nil {
return multierror.Prefix(err, fmt.Sprintf("'%s' ->", n))
}

Expand Down Expand Up @@ -433,7 +432,7 @@ func parseUpdate(result **api.UpdateStrategy, list *ast.ObjectList) error {
"auto_promote",
"canary",
}
if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
if err := checkHCLKeys(o.Val, valid); err != nil {
return err
}

Expand Down Expand Up @@ -469,7 +468,7 @@ func parseMigrate(result **api.MigrateStrategy, list *ast.ObjectList) error {
"min_healthy_time",
"healthy_deadline",
}
if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
if err := checkHCLKeys(o.Val, valid); err != nil {
return err
}

Expand Down Expand Up @@ -512,7 +511,7 @@ func parseVault(result *api.Vault, list *ast.ObjectList) error {
"change_mode",
"change_signal",
}
if err := helper.CheckHCLKeys(listVal, valid); err != nil {
if err := checkHCLKeys(listVal, valid); err != nil {
return multierror.Prefix(err, "vault ->")
}

Expand Down
17 changes: 8 additions & 9 deletions jobspec/parse_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"github.com/hashicorp/hcl"
"github.com/hashicorp/hcl/hcl/ast"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/helper"
"github.com/mitchellh/mapstructure"
)

Expand Down Expand Up @@ -58,7 +57,7 @@ func parseGroups(result *api.Job, list *ast.ObjectList) error {
"scaling",
"stop_after_client_disconnect",
}
if err := helper.CheckHCLKeys(listVal, valid); err != nil {
if err := checkHCLKeys(listVal, valid); err != nil {
return multierror.Prefix(err, fmt.Sprintf("'%s' ->", n))
}

Expand All @@ -84,7 +83,7 @@ func parseGroups(result *api.Job, list *ast.ObjectList) error {

// Build the group with the basic decode
var g api.TaskGroup
g.Name = helper.StringToPtr(n)
g.Name = stringToPtr(n)
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
WeaklyTypedInput: true,
Expand Down Expand Up @@ -201,8 +200,8 @@ func parseGroups(result *api.Job, list *ast.ObjectList) error {
// If we have a vault block, then parse that
if o := listVal.Filter("vault"); len(o.Items) > 0 {
tgVault := &api.Vault{
Env: helper.BoolToPtr(true),
ChangeMode: helper.StringToPtr("restart"),
Env: boolToPtr(true),
ChangeMode: stringToPtr("restart"),
}

if err := parseVault(tgVault, o); err != nil {
Expand Down Expand Up @@ -244,7 +243,7 @@ func parseEphemeralDisk(result **api.EphemeralDisk, list *ast.ObjectList) error
"size",
"migrate",
}
if err := helper.CheckHCLKeys(obj.Val, valid); err != nil {
if err := checkHCLKeys(obj.Val, valid); err != nil {
return err
}

Expand Down Expand Up @@ -278,7 +277,7 @@ func parseRestartPolicy(final **api.RestartPolicy, list *ast.ObjectList) error {
"delay",
"mode",
}
if err := helper.CheckHCLKeys(obj.Val, valid); err != nil {
if err := checkHCLKeys(obj.Val, valid); err != nil {
return err
}

Expand Down Expand Up @@ -308,7 +307,7 @@ func parseVolumes(out *map[string]*api.VolumeRequest, list *ast.ObjectList) erro
hcl.DecodeObject(out, list)

for k, v := range *out {
err := helper.UnusedKeys(v)
err := unusedKeys(v)
if err != nil {
return err
}
Expand Down Expand Up @@ -343,7 +342,7 @@ func parseScalingPolicy(out **api.ScalingPolicy, list *ast.ObjectList) error {
"policy",
"enabled",
}
if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
if err := checkHCLKeys(o.Val, valid); err != nil {
return err
}

Expand Down
17 changes: 8 additions & 9 deletions jobspec/parse_job.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"github.com/hashicorp/hcl"
"github.com/hashicorp/hcl/hcl/ast"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/helper"
"github.com/mitchellh/mapstructure"
)

Expand Down Expand Up @@ -41,8 +40,8 @@ func parseJob(result *api.Job, list *ast.ObjectList) error {
delete(m, "multiregion")

// Set the ID and name to the object key
result.ID = helper.StringToPtr(obj.Keys[0].Token.Value().(string))
result.Name = helper.StringToPtr(*result.ID)
result.ID = stringToPtr(obj.Keys[0].Token.Value().(string))
result.Name = stringToPtr(*result.ID)

// Decode the rest
if err := mapstructure.WeakDecode(m, result); err != nil {
Expand Down Expand Up @@ -83,7 +82,7 @@ func parseJob(result *api.Job, list *ast.ObjectList) error {
"consul_token",
"multiregion",
}
if err := helper.CheckHCLKeys(listVal, valid); err != nil {
if err := checkHCLKeys(listVal, valid); err != nil {
return multierror.Prefix(err, "job:")
}

Expand Down Expand Up @@ -176,7 +175,7 @@ func parseJob(result *api.Job, list *ast.ObjectList) error {
result.TaskGroups = make([]*api.TaskGroup, len(tasks), len(tasks)*2)
for i, t := range tasks {
result.TaskGroups[i] = &api.TaskGroup{
Name: helper.StringToPtr(t.Name),
Name: stringToPtr(t.Name),
Tasks: []*api.Task{t},
}
}
Expand All @@ -192,8 +191,8 @@ func parseJob(result *api.Job, list *ast.ObjectList) error {
// If we have a vault block, then parse that
if o := listVal.Filter("vault"); len(o.Items) > 0 {
jobVault := &api.Vault{
Env: helper.BoolToPtr(true),
ChangeMode: helper.StringToPtr("restart"),
Env: boolToPtr(true),
ChangeMode: stringToPtr("restart"),
}

if err := parseVault(jobVault, o); err != nil {
Expand Down Expand Up @@ -234,7 +233,7 @@ func parsePeriodic(result **api.PeriodicConfig, list *ast.ObjectList) error {
"prohibit_overlap",
"time_zone",
}
if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
if err := checkHCLKeys(o.Val, valid); err != nil {
return err
}

Expand Down Expand Up @@ -281,7 +280,7 @@ func parseParameterizedJob(result **api.ParameterizedJobConfig, list *ast.Object
"meta_required",
"meta_optional",
}
if err := helper.CheckHCLKeys(o.Val, valid); err != nil {
if err := checkHCLKeys(o.Val, valid); err != nil {
return err
}

Expand Down
Loading

0 comments on commit 9fe74c9

Please sign in to comment.