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  }