Files
nomad/helper/pluginutils/hclutils/testing.go
Mahmood Ali 8879645ab9 docker: introduce a new hcl2-friendly mount syntax (#9635)
Introduce a new more-block friendly syntax for specifying mounts with a new `mount` block type with the target as label:

```hcl
config {
  image = "..."

  mount {
    type = "..."
    target = "target-path"
    volume_options { ... }
  }
}
```

The main benefit here is that by `mount` being a block, it can nest blocks and avoids the compatibility problems noted in https://github.com/hashicorp/nomad/pull/9634/files#diff-2161d829655a3a36ba2d916023e4eec125b9bd22873493c1c2e5e3f7ba92c691R128-R155 .

The intention is for us to promote this `mount` blocks and quietly deprecate the `mounts` type, while still honoring to preserve compatibility as much as we could.

This addresses the issue in https://github.com/hashicorp/nomad/issues/9604 .
2020-12-15 14:13:50 -05:00

134 lines
3.5 KiB
Go

package hclutils
import (
"testing"
"github.com/hashicorp/go-msgpack/codec"
"github.com/hashicorp/hcl"
"github.com/hashicorp/hcl/hcl/ast"
"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"
"github.com/mitchellh/mapstructure"
"github.com/stretchr/testify/require"
"github.com/zclconf/go-cty/cty"
)
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"]
}