plugins: update hclutils test

The test used old local copies of Docker structs and appeared to be
testing an outdated approach to task config decoding.

Updated to use real Docker structs so we can do end-to-end unit testing
of real Docker task configs.
This commit is contained in:
Michael Schurter 2019-02-02 12:20:26 -08:00
parent 6c0cc65b2e
commit 9bf4b38ab3
1 changed files with 154 additions and 187 deletions

View File

@ -1,82 +1,23 @@
package hclutils
package hclutils_test
import (
"testing"
"github.com/hashicorp/hcl"
"github.com/hashicorp/hcl/hcl/ast"
hcl2 "github.com/hashicorp/hcl2/hcl"
"github.com/hashicorp/hcl2/hcldec"
"github.com/hashicorp/nomad/helper"
"github.com/hashicorp/nomad/drivers/docker"
"github.com/hashicorp/nomad/helper/pluginutils/hclspecutils"
"github.com/hashicorp/nomad/helper/pluginutils/hclutils"
"github.com/hashicorp/nomad/nomad/structs"
"github.com/hashicorp/nomad/plugins/drivers"
"github.com/kr/pretty"
"github.com/mitchellh/mapstructure"
"github.com/stretchr/testify/require"
"github.com/ugorji/go/codec"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/gocty"
)
var (
dockerSpec hcldec.Spec = hcldec.ObjectSpec(map[string]hcldec.Spec{
"image": &hcldec.AttrSpec{
Name: "image",
Type: cty.String,
Required: true,
},
"args": &hcldec.AttrSpec{
Name: "args",
Type: cty.List(cty.String),
},
"pids_limit": &hcldec.AttrSpec{
Name: "pids_limit",
Type: cty.Number,
},
"port_map": &hcldec.BlockAttrsSpec{
TypeName: "port_map",
ElementType: cty.String,
},
"devices": &hcldec.BlockListSpec{
TypeName: "devices",
Nested: hcldec.ObjectSpec(map[string]hcldec.Spec{
"host_path": &hcldec.AttrSpec{
Name: "host_path",
Type: cty.String,
},
"container_path": &hcldec.AttrSpec{
Name: "container_path",
Type: cty.String,
},
"cgroup_permissions": &hcldec.DefaultSpec{
Primary: &hcldec.AttrSpec{
Name: "cgroup_permissions",
Type: cty.String,
},
Default: &hcldec.LiteralSpec{
Value: cty.StringVal(""),
},
},
}),
},
},
)
)
type dockerConfig struct {
Image string `cty:"image"`
Args []string `cty:"args"`
PidsLimit *int64 `cty:"pids_limit"`
PortMap map[string]string `cty:"port_map"`
Devices []DockerDevice `cty:"devices"`
}
type DockerDevice struct {
HostPath string `cty:"host_path"`
ContainerPath string `cty:"container_path"`
CgroupPermissions string `cty:"cgroup_permissions"`
}
func hclConfigToInterface(t *testing.T, config string) interface{} {
t.Helper()
@ -121,30 +62,22 @@ func jsonConfigToInterface(t *testing.T, config string) interface{} {
}
func TestParseHclInterface_Hcl(t *testing.T) {
defaultCtx := &hcl2.EvalContext{
Functions: GetStdlibFuncs(),
}
variableCtx := &hcl2.EvalContext{
Functions: GetStdlibFuncs(),
Variables: map[string]cty.Value{
dockerDriver := new(docker.Driver)
dockerSpec, err := dockerDriver.TaskConfigSchema()
require.NoError(t, err)
dockerDecSpec, diags := hclspecutils.Convert(dockerSpec)
require.False(t, diags.HasErrors())
vars := map[string]cty.Value{
"NOMAD_ALLOC_INDEX": cty.NumberIntVal(2),
"NOMAD_META_hello": cty.StringVal("world"),
},
}
// XXX Useful for determining what cty thinks the type is
//implied, err := gocty.ImpliedType(&dockerConfig{})
//if err != nil {
//t.Fatalf("implied type failed: %v", err)
//}
//t.Logf("Implied type: %v", implied.GoString())
cases := []struct {
name string
config interface{}
spec hcldec.Spec
ctx *hcl2.EvalContext
vars map[string]cty.Value
expected interface{}
expectedType interface{}
}{
@ -154,13 +87,13 @@ func TestParseHclInterface_Hcl(t *testing.T) {
config {
image = "redis:3.2"
}`),
spec: dockerSpec,
ctx: defaultCtx,
expected: &dockerConfig{
spec: dockerDecSpec,
expected: &docker.TaskConfig{
Image: "redis:3.2",
Devices: []DockerDevice{},
Devices: []docker.DockerDevice{},
Mounts: []docker.DockerMount{},
},
expectedType: &dockerConfig{},
expectedType: &docker.TaskConfig{},
},
{
name: "single string attr json",
@ -170,13 +103,13 @@ func TestParseHclInterface_Hcl(t *testing.T) {
"image": "redis:3.2"
}
}`),
spec: dockerSpec,
ctx: defaultCtx,
expected: &dockerConfig{
spec: dockerDecSpec,
expected: &docker.TaskConfig{
Image: "redis:3.2",
Devices: []DockerDevice{},
Devices: []docker.DockerDevice{},
Mounts: []docker.DockerMount{},
},
expectedType: &dockerConfig{},
expectedType: &docker.TaskConfig{},
},
{
name: "number attr",
@ -185,14 +118,14 @@ func TestParseHclInterface_Hcl(t *testing.T) {
image = "redis:3.2"
pids_limit = 2
}`),
spec: dockerSpec,
ctx: defaultCtx,
expected: &dockerConfig{
spec: dockerDecSpec,
expected: &docker.TaskConfig{
Image: "redis:3.2",
PidsLimit: helper.Int64ToPtr(2),
Devices: []DockerDevice{},
PidsLimit: 2,
Devices: []docker.DockerDevice{},
Mounts: []docker.DockerMount{},
},
expectedType: &dockerConfig{},
expectedType: &docker.TaskConfig{},
},
{
name: "number attr json",
@ -203,14 +136,14 @@ func TestParseHclInterface_Hcl(t *testing.T) {
"pids_limit": "2"
}
}`),
spec: dockerSpec,
ctx: defaultCtx,
expected: &dockerConfig{
spec: dockerDecSpec,
expected: &docker.TaskConfig{
Image: "redis:3.2",
PidsLimit: helper.Int64ToPtr(2),
Devices: []DockerDevice{},
PidsLimit: 2,
Devices: []docker.DockerDevice{},
Mounts: []docker.DockerMount{},
},
expectedType: &dockerConfig{},
expectedType: &docker.TaskConfig{},
},
{
name: "number attr interpolated",
@ -219,14 +152,14 @@ func TestParseHclInterface_Hcl(t *testing.T) {
image = "redis:3.2"
pids_limit = "${2 + 2}"
}`),
spec: dockerSpec,
ctx: defaultCtx,
expected: &dockerConfig{
spec: dockerDecSpec,
expected: &docker.TaskConfig{
Image: "redis:3.2",
PidsLimit: helper.Int64ToPtr(4),
Devices: []DockerDevice{},
PidsLimit: 4,
Devices: []docker.DockerDevice{},
Mounts: []docker.DockerMount{},
},
expectedType: &dockerConfig{},
expectedType: &docker.TaskConfig{},
},
{
name: "number attr interploated json",
@ -237,14 +170,14 @@ func TestParseHclInterface_Hcl(t *testing.T) {
"pids_limit": "${2 + 2}"
}
}`),
spec: dockerSpec,
ctx: defaultCtx,
expected: &dockerConfig{
spec: dockerDecSpec,
expected: &docker.TaskConfig{
Image: "redis:3.2",
PidsLimit: helper.Int64ToPtr(4),
Devices: []DockerDevice{},
PidsLimit: 4,
Devices: []docker.DockerDevice{},
Mounts: []docker.DockerMount{},
},
expectedType: &dockerConfig{},
expectedType: &docker.TaskConfig{},
},
{
name: "multi attr",
@ -253,14 +186,14 @@ func TestParseHclInterface_Hcl(t *testing.T) {
image = "redis:3.2"
args = ["foo", "bar"]
}`),
spec: dockerSpec,
ctx: defaultCtx,
expected: &dockerConfig{
spec: dockerDecSpec,
expected: &docker.TaskConfig{
Image: "redis:3.2",
Args: []string{"foo", "bar"},
Devices: []DockerDevice{},
Devices: []docker.DockerDevice{},
Mounts: []docker.DockerMount{},
},
expectedType: &dockerConfig{},
expectedType: &docker.TaskConfig{},
},
{
name: "multi attr json",
@ -271,14 +204,14 @@ func TestParseHclInterface_Hcl(t *testing.T) {
"args": ["foo", "bar"]
}
}`),
spec: dockerSpec,
ctx: defaultCtx,
expected: &dockerConfig{
spec: dockerDecSpec,
expected: &docker.TaskConfig{
Image: "redis:3.2",
Args: []string{"foo", "bar"},
Devices: []DockerDevice{},
Devices: []docker.DockerDevice{},
Mounts: []docker.DockerMount{},
},
expectedType: &dockerConfig{},
expectedType: &docker.TaskConfig{},
},
{
name: "multi attr variables",
@ -288,15 +221,16 @@ func TestParseHclInterface_Hcl(t *testing.T) {
args = ["${NOMAD_META_hello}", "${NOMAD_ALLOC_INDEX}"]
pids_limit = "${NOMAD_ALLOC_INDEX + 2}"
}`),
spec: dockerSpec,
ctx: variableCtx,
expected: &dockerConfig{
spec: dockerDecSpec,
vars: vars,
expected: &docker.TaskConfig{
Image: "redis:3.2",
Args: []string{"world", "2"},
PidsLimit: helper.Int64ToPtr(4),
Devices: []DockerDevice{},
PidsLimit: 4,
Devices: []docker.DockerDevice{},
Mounts: []docker.DockerMount{},
},
expectedType: &dockerConfig{},
expectedType: &docker.TaskConfig{},
},
{
name: "multi attr variables json",
@ -307,14 +241,14 @@ func TestParseHclInterface_Hcl(t *testing.T) {
"args": ["foo", "bar"]
}
}`),
spec: dockerSpec,
ctx: defaultCtx,
expected: &dockerConfig{
spec: dockerDecSpec,
expected: &docker.TaskConfig{
Image: "redis:3.2",
Args: []string{"foo", "bar"},
Devices: []DockerDevice{},
Devices: []docker.DockerDevice{},
Mounts: []docker.DockerMount{},
},
expectedType: &dockerConfig{},
expectedType: &docker.TaskConfig{},
},
{
name: "port_map",
@ -322,21 +256,21 @@ func TestParseHclInterface_Hcl(t *testing.T) {
config {
image = "redis:3.2"
port_map {
foo = "db"
bar = "db2"
foo = 1234
bar = 5678
}
}`),
spec: dockerSpec,
ctx: defaultCtx,
expected: &dockerConfig{
spec: dockerDecSpec,
expected: &docker.TaskConfig{
Image: "redis:3.2",
PortMap: map[string]string{
"foo": "db",
"bar": "db2",
PortMap: map[string]int{
"foo": 1234,
"bar": 5678,
},
Devices: []DockerDevice{},
Devices: []docker.DockerDevice{},
Mounts: []docker.DockerMount{},
},
expectedType: &dockerConfig{},
expectedType: &docker.TaskConfig{},
},
{
name: "port_map json",
@ -345,22 +279,22 @@ func TestParseHclInterface_Hcl(t *testing.T) {
"Config": {
"image": "redis:3.2",
"port_map": [{
"foo": "db",
"bar": "db2"
"foo": 1234,
"bar": 5678
}]
}
}`),
spec: dockerSpec,
ctx: defaultCtx,
expected: &dockerConfig{
spec: dockerDecSpec,
expected: &docker.TaskConfig{
Image: "redis:3.2",
PortMap: map[string]string{
"foo": "db",
"bar": "db2",
PortMap: map[string]int{
"foo": 1234,
"bar": 5678,
},
Devices: []DockerDevice{},
Devices: []docker.DockerDevice{},
Mounts: []docker.DockerMount{},
},
expectedType: &dockerConfig{},
expectedType: &docker.TaskConfig{},
},
{
name: "devices",
@ -379,11 +313,10 @@ func TestParseHclInterface_Hcl(t *testing.T) {
}
]
}`),
spec: dockerSpec,
ctx: defaultCtx,
expected: &dockerConfig{
spec: dockerDecSpec,
expected: &docker.TaskConfig{
Image: "redis:3.2",
Devices: []DockerDevice{
Devices: []docker.DockerDevice{
{
HostPath: "/dev/sda1",
ContainerPath: "/dev/xvdc",
@ -394,11 +327,40 @@ func TestParseHclInterface_Hcl(t *testing.T) {
ContainerPath: "/dev/xvdd",
},
},
Mounts: []docker.DockerMount{},
},
expectedType: &dockerConfig{},
expectedType: &docker.TaskConfig{},
},
{
name: "devices json",
name: "docker_logging",
config: hclConfigToInterface(t, `
config {
image = "redis:3.2"
network_mode = "host"
dns_servers = ["169.254.1.1"]
logging {
type = "syslog"
config {
tag = "driver-test"
}
}
}`),
spec: dockerDecSpec,
expected: &docker.TaskConfig{
Image: "redis:3.2",
NetworkMode: "host",
DNSServers: []string{"169.254.1.1"},
Logging: docker.DockerLogging{
Type: "syslog",
Config: map[string]string{
"tag": "driver-test",
},
},
},
expectedType: &docker.TaskConfig{},
},
{
name: "docker_json",
config: jsonConfigToInterface(t, `
{
"Config": {
@ -416,11 +378,10 @@ func TestParseHclInterface_Hcl(t *testing.T) {
]
}
}`),
spec: dockerSpec,
ctx: defaultCtx,
expected: &dockerConfig{
spec: dockerDecSpec,
expected: &docker.TaskConfig{
Image: "redis:3.2",
Devices: []DockerDevice{
Devices: []docker.DockerDevice{
{
HostPath: "/dev/sda1",
ContainerPath: "/dev/xvdc",
@ -431,16 +392,18 @@ func TestParseHclInterface_Hcl(t *testing.T) {
ContainerPath: "/dev/xvdd",
},
},
Mounts: []docker.DockerMount{},
},
expectedType: &dockerConfig{},
expectedType: &docker.TaskConfig{},
},
}
for _, c := range cases {
c := c
t.Run(c.name, func(t *testing.T) {
t.Logf("Val: % #v", pretty.Formatter(c.config))
// Parse the interface
ctyValue, diag := ParseHclInterface(c.config, c.spec, c.ctx)
ctyValue, diag := hclutils.ParseHclInterface(c.config, c.spec, c.vars)
if diag.HasErrors() {
for _, err := range diag.Errs() {
t.Error(err)
@ -448,8 +411,12 @@ func TestParseHclInterface_Hcl(t *testing.T) {
t.FailNow()
}
// Convert cty-value to go structs
require.NoError(t, gocty.FromCtyValue(ctyValue, c.expectedType))
// Test encoding
taskConfig := &drivers.TaskConfig{}
require.NoError(t, taskConfig.EncodeDriverConfig(ctyValue))
// Test decoding
require.NoError(t, taskConfig.DecodeDriverConfig(c.expectedType))
require.EqualValues(t, c.expected, c.expectedType)