github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/helper/pluginutils/hclutils/testing.go (about) 1 package hclutils 2 3 import ( 4 "testing" 5 6 "github.com/hashicorp/go-msgpack/codec" 7 "github.com/hashicorp/hcl" 8 "github.com/hashicorp/hcl/hcl/ast" 9 "github.com/hashicorp/nomad/helper/pluginutils/hclspecutils" 10 "github.com/hashicorp/nomad/nomad/structs" 11 "github.com/hashicorp/nomad/plugins/drivers" 12 "github.com/hashicorp/nomad/plugins/shared/hclspec" 13 "github.com/mitchellh/mapstructure" 14 "github.com/stretchr/testify/require" 15 "github.com/zclconf/go-cty/cty" 16 ) 17 18 type HCLParser struct { 19 spec *hclspec.Spec 20 vars map[string]cty.Value 21 } 22 23 // NewConfigParser return a helper for parsing drivers TaskConfig 24 // Parser is an immutable object can be used in multiple tests 25 func NewConfigParser(spec *hclspec.Spec) *HCLParser { 26 return &HCLParser{ 27 spec: spec, 28 } 29 } 30 31 // WithVars returns a new parser that uses passed vars when interpolated strings in config 32 func (b *HCLParser) WithVars(vars map[string]cty.Value) *HCLParser { 33 return &HCLParser{ 34 spec: b.spec, 35 vars: vars, 36 } 37 } 38 39 // ParseJson parses the json config string and decode it into the `out` parameter. 40 // out parameter should be a golang reference to a driver specific TaskConfig reference. 41 // The function terminates and reports errors if any is found during conversion. 42 // 43 // var tc *TaskConfig 44 // hclutils.NewConfigParser(spec).ParseJson(t, configString, &tc) 45 func (b *HCLParser) ParseJson(t *testing.T, configStr string, out interface{}) { 46 config := JsonConfigToInterface(t, configStr) 47 b.parse(t, config, out) 48 } 49 50 // ParseHCL parses the hcl config string and decode it into the `out` parameter. 51 // out parameter should be a golang reference to a driver specific TaskConfig reference. 52 // The function terminates and reports errors if any is found during conversion. 53 // 54 // # Sample invocation would be 55 // 56 // ``` 57 // var tc *TaskConfig 58 // hclutils.NewConfigParser(spec).ParseHCL(t, configString, &tc) 59 // ``` 60 func (b *HCLParser) ParseHCL(t *testing.T, configStr string, out interface{}) { 61 config := HclConfigToInterface(t, configStr) 62 b.parse(t, config, out) 63 } 64 65 func (b *HCLParser) parse(t *testing.T, config, out interface{}) { 66 decSpec, diags := hclspecutils.Convert(b.spec) 67 require.Empty(t, diags) 68 69 ctyValue, diag, errs := ParseHclInterface(config, decSpec, b.vars) 70 if len(errs) > 1 { 71 t.Error("unexpected errors parsing file") 72 for _, err := range errs { 73 t.Errorf(" * %v", err) 74 75 } 76 t.FailNow() 77 } 78 require.Empty(t, diag) 79 80 // encode 81 dtc := &drivers.TaskConfig{} 82 require.NoError(t, dtc.EncodeDriverConfig(ctyValue)) 83 84 // decode 85 require.NoError(t, dtc.DecodeDriverConfig(out)) 86 } 87 88 func HclConfigToInterface(t *testing.T, config string) interface{} { 89 t.Helper() 90 91 // Parse as we do in the jobspec parser 92 root, err := hcl.Parse(config) 93 if err != nil { 94 t.Fatalf("failed to hcl parse the config: %v", err) 95 } 96 97 // Top-level item should be a list 98 list, ok := root.Node.(*ast.ObjectList) 99 if !ok { 100 t.Fatalf("root should be an object") 101 } 102 103 var m map[string]interface{} 104 if err := hcl.DecodeObject(&m, list.Items[0]); err != nil { 105 t.Fatalf("failed to decode object: %v", err) 106 } 107 108 var m2 map[string]interface{} 109 if err := mapstructure.WeakDecode(m, &m2); err != nil { 110 t.Fatalf("failed to weak decode object: %v", err) 111 } 112 113 return m2["config"] 114 } 115 116 func JsonConfigToInterface(t *testing.T, config string) interface{} { 117 t.Helper() 118 119 // Decode from json 120 dec := codec.NewDecoderBytes([]byte(config), structs.JsonHandle) 121 122 var m map[string]interface{} 123 err := dec.Decode(&m) 124 if err != nil { 125 t.Fatalf("failed to decode: %v", err) 126 } 127 128 return m["Config"] 129 }