package hclutils import ( "testing" "github.com/hashicorp/go-msgpack/codec" "github.com/hashicorp/hcl" "github.com/hashicorp/hcl/hcl/ast" "github.com/mitchellh/mapstructure" "github.com/stretchr/testify/require" "github.com/zclconf/go-cty/cty" "github.com/hashicorp/nomad/helper/pluginutils/hclspecutils" "github.com/hashicorp/nomad/nomad/structs" "github.com/hashicorp/nomad/plugins/drivers" "github.com/hashicorp/nomad/plugins/shared/hclspec" ) type HCLParser struct { spec *hclspec.Spec vars map[string]cty.Value } // NewConfigParser return a helper for parsing drivers TaskConfig // Parser is an immutable object can be used in multiple tests func NewConfigParser(spec *hclspec.Spec) *HCLParser { return &HCLParser{ spec: spec, } } // WithVars returns a new parser that uses passed vars when interpolated strings in config func (b *HCLParser) WithVars(vars map[string]cty.Value) *HCLParser { return &HCLParser{ spec: b.spec, vars: vars, } } // ParseJson parses the json config string and decode it into the `out` parameter. // out parameter should be a golang reference to a driver specific TaskConfig reference. // The function terminates and reports errors if any is found during conversion. // // Sample invocation would be // // ``` // var tc *TaskConfig // hclutils.NewConfigParser(spec).ParseJson(t, configString, &tc) // ``` func (b *HCLParser) ParseJson(t *testing.T, configStr string, out interface{}) { config := JsonConfigToInterface(t, configStr) b.parse(t, config, out) } // ParseHCL parses the hcl config string and decode it into the `out` parameter. // out parameter should be a golang reference to a driver specific TaskConfig reference. // The function terminates and reports errors if any is found during conversion. // // Sample invocation would be // // ``` // var tc *TaskConfig // hclutils.NewConfigParser(spec).ParseHCL(t, configString, &tc) // ``` func (b *HCLParser) ParseHCL(t *testing.T, configStr string, out interface{}) { config := HclConfigToInterface(t, configStr) b.parse(t, config, out) } func (b *HCLParser) parse(t *testing.T, config, out interface{}) { decSpec, diags := hclspecutils.Convert(b.spec) require.Empty(t, diags) ctyValue, diag, errs := ParseHclInterface(config, decSpec, b.vars) if len(errs) > 1 { t.Error("unexpected errors parsing file") for _, err := range errs { t.Errorf(" * %v", err) } t.FailNow() } require.Empty(t, diag) // encode dtc := &drivers.TaskConfig{} require.NoError(t, dtc.EncodeDriverConfig(ctyValue)) // decode require.NoError(t, dtc.DecodeDriverConfig(out)) } func HclConfigToInterface(t *testing.T, config string) interface{} { t.Helper() // Parse as we do in the jobspec parser root, err := hcl.Parse(config) if err != nil { t.Fatalf("failed to hcl parse the config: %v", err) } // Top-level item should be a list list, ok := root.Node.(*ast.ObjectList) if !ok { t.Fatalf("root should be an object") } var m map[string]interface{} if err := hcl.DecodeObject(&m, list.Items[0]); err != nil { t.Fatalf("failed to decode object: %v", err) } var m2 map[string]interface{} if err := mapstructure.WeakDecode(m, &m2); err != nil { t.Fatalf("failed to weak decode object: %v", err) } return m2["config"] } func JsonConfigToInterface(t *testing.T, config string) interface{} { t.Helper() // Decode from json dec := codec.NewDecoderBytes([]byte(config), structs.JsonHandle) var m map[string]interface{} err := dec.Decode(&m) if err != nil { t.Fatalf("failed to decode: %v", err) } return m["Config"] }