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