d02cd46b72
This was allowing very strange input to be allowed through to Terraform since some encryped output will contain null characters (such as from git crypt).
1118 lines
20 KiB
Go
1118 lines
20 KiB
Go
package hcl
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"path/filepath"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/davecgh/go-spew/spew"
|
|
"github.com/hashicorp/hcl/hcl/ast"
|
|
"github.com/hashicorp/hcl/testhelper"
|
|
)
|
|
|
|
func TestDecode_interface(t *testing.T) {
|
|
cases := []struct {
|
|
File string
|
|
Err bool
|
|
Out interface{}
|
|
}{
|
|
{
|
|
"basic.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"foo": "bar",
|
|
"bar": "${file(\"bing/bong.txt\")}",
|
|
},
|
|
},
|
|
{
|
|
"basic_squish.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"foo": "bar",
|
|
"bar": "${file(\"bing/bong.txt\")}",
|
|
"foo-bar": "baz",
|
|
},
|
|
},
|
|
{
|
|
"empty.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"resource": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"foo": []map[string]interface{}{
|
|
map[string]interface{}{},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"tfvars.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"regularvar": "Should work",
|
|
"map.key1": "Value",
|
|
"map.key2": "Other value",
|
|
},
|
|
},
|
|
{
|
|
"escape.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"foo": "bar\"baz\\n",
|
|
"qux": "back\\slash",
|
|
"bar": "new\nline",
|
|
"qax": `slash\:colon`,
|
|
"nested": `${HH\\:mm\\:ss}`,
|
|
"nestedquotes": `${"\"stringwrappedinquotes\""}`,
|
|
},
|
|
},
|
|
{
|
|
"float.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"a": 1.02,
|
|
},
|
|
},
|
|
{
|
|
"multiline_bad.hcl",
|
|
true,
|
|
nil,
|
|
},
|
|
{
|
|
"multiline_literal.hcl",
|
|
false,
|
|
map[string]interface{}{"multiline_literal": testhelper.Unix2dos(`hello
|
|
world`)},
|
|
},
|
|
{
|
|
"multiline_no_marker.hcl",
|
|
true,
|
|
nil,
|
|
},
|
|
{
|
|
"multiline.hcl",
|
|
false,
|
|
map[string]interface{}{"foo": testhelper.Unix2dos("bar\nbaz\n")},
|
|
},
|
|
{
|
|
"multiline_indented.hcl",
|
|
false,
|
|
map[string]interface{}{"foo": testhelper.Unix2dos(" bar\n baz\n")},
|
|
},
|
|
{
|
|
"multiline_no_hanging_indent.hcl",
|
|
false,
|
|
map[string]interface{}{"foo": testhelper.Unix2dos(" baz\n bar\n foo\n")},
|
|
},
|
|
{
|
|
"multiline_no_eof.hcl",
|
|
false,
|
|
map[string]interface{}{"foo": testhelper.Unix2dos("bar\nbaz\n"), "key": "value"},
|
|
},
|
|
{
|
|
"multiline.json",
|
|
false,
|
|
map[string]interface{}{"foo": "bar\nbaz"},
|
|
},
|
|
{
|
|
"null_strings.json",
|
|
false,
|
|
map[string]interface{}{
|
|
"module": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"app": []map[string]interface{}{
|
|
map[string]interface{}{"foo": ""},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"scientific.json",
|
|
false,
|
|
map[string]interface{}{
|
|
"a": 1e-10,
|
|
"b": 1e+10,
|
|
"c": 1e10,
|
|
"d": 1.2e-10,
|
|
"e": 1.2e+10,
|
|
"f": 1.2e10,
|
|
},
|
|
},
|
|
{
|
|
"scientific.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"a": 1e-10,
|
|
"b": 1e+10,
|
|
"c": 1e10,
|
|
"d": 1.2e-10,
|
|
"e": 1.2e+10,
|
|
"f": 1.2e10,
|
|
},
|
|
},
|
|
{
|
|
"terraform_heroku.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"name": "terraform-test-app",
|
|
"config_vars": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"FOO": "bar",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"structure_multi.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"foo": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"baz": []map[string]interface{}{
|
|
map[string]interface{}{"key": 7},
|
|
},
|
|
},
|
|
map[string]interface{}{
|
|
"bar": []map[string]interface{}{
|
|
map[string]interface{}{"key": 12},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"structure_multi.json",
|
|
false,
|
|
map[string]interface{}{
|
|
"foo": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"baz": []map[string]interface{}{
|
|
map[string]interface{}{"key": 7},
|
|
},
|
|
},
|
|
map[string]interface{}{
|
|
"bar": []map[string]interface{}{
|
|
map[string]interface{}{"key": 12},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"list_of_maps.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"foo": []interface{}{
|
|
map[string]interface{}{"somekey1": "someval1"},
|
|
map[string]interface{}{"somekey2": "someval2", "someextrakey": "someextraval"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"assign_deep.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"resource": []interface{}{
|
|
map[string]interface{}{
|
|
"foo": []interface{}{
|
|
map[string]interface{}{
|
|
"bar": []map[string]interface{}{
|
|
map[string]interface{}{}}}}}}},
|
|
},
|
|
{
|
|
"structure_list.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"foo": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"key": 7,
|
|
},
|
|
map[string]interface{}{
|
|
"key": 12,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"structure_list.json",
|
|
false,
|
|
map[string]interface{}{
|
|
"foo": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"key": 7,
|
|
},
|
|
map[string]interface{}{
|
|
"key": 12,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"structure_list_deep.json",
|
|
false,
|
|
map[string]interface{}{
|
|
"bar": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"foo": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"name": "terraform_example",
|
|
"ingress": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"from_port": 22,
|
|
},
|
|
map[string]interface{}{
|
|
"from_port": 80,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
{
|
|
"structure_list_empty.json",
|
|
false,
|
|
map[string]interface{}{
|
|
"foo": []interface{}{},
|
|
},
|
|
},
|
|
|
|
{
|
|
"nested_block_comment.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"bar": "value",
|
|
},
|
|
},
|
|
|
|
{
|
|
"unterminated_block_comment.hcl",
|
|
true,
|
|
nil,
|
|
},
|
|
|
|
{
|
|
"unterminated_brace.hcl",
|
|
true,
|
|
nil,
|
|
},
|
|
|
|
{
|
|
"nested_provider_bad.hcl",
|
|
true,
|
|
nil,
|
|
},
|
|
|
|
{
|
|
"object_list.json",
|
|
false,
|
|
map[string]interface{}{
|
|
"resource": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"aws_instance": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"db": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"vpc": "foo",
|
|
"provisioner": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"file": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"source": "foo",
|
|
"destination": "bar",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
// Terraform GH-8295 sanity test that basic decoding into
|
|
// interface{} works.
|
|
{
|
|
"terraform_variable_invalid.json",
|
|
false,
|
|
map[string]interface{}{
|
|
"variable": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"whatever": "abc123",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
{
|
|
"interpolate.json",
|
|
false,
|
|
map[string]interface{}{
|
|
"default": `${replace("europe-west", "-", " ")}`,
|
|
},
|
|
},
|
|
|
|
{
|
|
"block_assign.hcl",
|
|
true,
|
|
nil,
|
|
},
|
|
|
|
{
|
|
"escape_backslash.hcl",
|
|
false,
|
|
map[string]interface{}{
|
|
"output": []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"one": `${replace(var.sub_domain, ".", "\\.")}`,
|
|
"two": `${replace(var.sub_domain, ".", "\\\\.")}`,
|
|
"many": `${replace(var.sub_domain, ".", "\\\\\\\\.")}`,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
{
|
|
"git_crypt.hcl",
|
|
true,
|
|
nil,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Logf("Testing: %s", tc.File)
|
|
d, err := ioutil.ReadFile(filepath.Join(fixtureDir, tc.File))
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
var out interface{}
|
|
err = Decode(&out, string(d))
|
|
if (err != nil) != tc.Err {
|
|
t.Fatalf("Input: %s\n\nError: %s", tc.File, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(out, tc.Out) {
|
|
t.Fatalf("Input: %s. Actual, Expected.\n\n%#v\n\n%#v", tc.File, out, tc.Out)
|
|
}
|
|
|
|
var v interface{}
|
|
err = Unmarshal(d, &v)
|
|
if (err != nil) != tc.Err {
|
|
t.Fatalf("Input: %s\n\nError: %s", tc.File, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(v, tc.Out) {
|
|
t.Fatalf("Input: %s. Actual, Expected.\n\n%#v\n\n%#v", tc.File, out, tc.Out)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDecode_interfaceInline(t *testing.T) {
|
|
cases := []struct {
|
|
Value string
|
|
Err bool
|
|
Out interface{}
|
|
}{
|
|
{"t t e{{}}", true, nil},
|
|
{"t=0t d {}", true, map[string]interface{}{"t": 0}},
|
|
{"v=0E0v d{}", true, map[string]interface{}{"v": float64(0)}},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Logf("Testing: %q", tc.Value)
|
|
|
|
var out interface{}
|
|
err := Decode(&out, tc.Value)
|
|
if (err != nil) != tc.Err {
|
|
t.Fatalf("Input: %q\n\nError: %s", tc.Value, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(out, tc.Out) {
|
|
t.Fatalf("Input: %q. Actual, Expected.\n\n%#v\n\n%#v", tc.Value, out, tc.Out)
|
|
}
|
|
|
|
var v interface{}
|
|
err = Unmarshal([]byte(tc.Value), &v)
|
|
if (err != nil) != tc.Err {
|
|
t.Fatalf("Input: %q\n\nError: %s", tc.Value, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(v, tc.Out) {
|
|
t.Fatalf("Input: %q. Actual, Expected.\n\n%#v\n\n%#v", tc.Value, out, tc.Out)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDecode_equal(t *testing.T) {
|
|
cases := []struct {
|
|
One, Two string
|
|
}{
|
|
{
|
|
"basic.hcl",
|
|
"basic.json",
|
|
},
|
|
{
|
|
"float.hcl",
|
|
"float.json",
|
|
},
|
|
/*
|
|
{
|
|
"structure.hcl",
|
|
"structure.json",
|
|
},
|
|
*/
|
|
{
|
|
"structure.hcl",
|
|
"structure_flat.json",
|
|
},
|
|
{
|
|
"terraform_heroku.hcl",
|
|
"terraform_heroku.json",
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
p1 := filepath.Join(fixtureDir, tc.One)
|
|
p2 := filepath.Join(fixtureDir, tc.Two)
|
|
|
|
d1, err := ioutil.ReadFile(p1)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
d2, err := ioutil.ReadFile(p2)
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
var i1, i2 interface{}
|
|
err = Decode(&i1, string(d1))
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
err = Decode(&i2, string(d2))
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(i1, i2) {
|
|
t.Fatalf(
|
|
"%s != %s\n\n%#v\n\n%#v",
|
|
tc.One, tc.Two,
|
|
i1, i2)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDecode_flatMap(t *testing.T) {
|
|
var val map[string]map[string]string
|
|
|
|
err := Decode(&val, testReadFile(t, "structure_flatmap.hcl"))
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
expected := map[string]map[string]string{
|
|
"foo": map[string]string{
|
|
"foo": "bar",
|
|
"key": "7",
|
|
},
|
|
}
|
|
|
|
if !reflect.DeepEqual(val, expected) {
|
|
t.Fatalf("Actual: %#v\n\nExpected: %#v", val, expected)
|
|
}
|
|
}
|
|
|
|
func TestDecode_structure(t *testing.T) {
|
|
type Embedded interface{}
|
|
|
|
type V struct {
|
|
Embedded `hcl:"-"`
|
|
Key int
|
|
Foo string
|
|
}
|
|
|
|
var actual V
|
|
|
|
err := Decode(&actual, testReadFile(t, "flat.hcl"))
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
expected := V{
|
|
Key: 7,
|
|
Foo: "bar",
|
|
}
|
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("Actual: %#v\n\nExpected: %#v", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestDecode_structurePtr(t *testing.T) {
|
|
type V struct {
|
|
Key int
|
|
Foo string
|
|
}
|
|
|
|
var actual *V
|
|
|
|
err := Decode(&actual, testReadFile(t, "flat.hcl"))
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
expected := &V{
|
|
Key: 7,
|
|
Foo: "bar",
|
|
}
|
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("Actual: %#v\n\nExpected: %#v", actual, expected)
|
|
}
|
|
}
|
|
|
|
func TestDecode_structureArray(t *testing.T) {
|
|
// This test is extracted from a failure in Consul (consul.io),
|
|
// hence the interesting structure naming.
|
|
|
|
type KeyPolicyType string
|
|
|
|
type KeyPolicy struct {
|
|
Prefix string `hcl:",key"`
|
|
Policy KeyPolicyType
|
|
}
|
|
|
|
type Policy struct {
|
|
Keys []KeyPolicy `hcl:"key,expand"`
|
|
}
|
|
|
|
expected := Policy{
|
|
Keys: []KeyPolicy{
|
|
KeyPolicy{
|
|
Prefix: "",
|
|
Policy: "read",
|
|
},
|
|
KeyPolicy{
|
|
Prefix: "foo/",
|
|
Policy: "write",
|
|
},
|
|
KeyPolicy{
|
|
Prefix: "foo/bar/",
|
|
Policy: "read",
|
|
},
|
|
KeyPolicy{
|
|
Prefix: "foo/bar/baz",
|
|
Policy: "deny",
|
|
},
|
|
},
|
|
}
|
|
|
|
files := []string{
|
|
"decode_policy.hcl",
|
|
"decode_policy.json",
|
|
}
|
|
|
|
for _, f := range files {
|
|
var actual Policy
|
|
|
|
err := Decode(&actual, testReadFile(t, f))
|
|
if err != nil {
|
|
t.Fatalf("Input: %s\n\nerr: %s", f, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("Input: %s\n\nActual: %#v\n\nExpected: %#v", f, actual, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDecode_sliceExpand(t *testing.T) {
|
|
type testInner struct {
|
|
Name string `hcl:",key"`
|
|
Key string
|
|
}
|
|
|
|
type testStruct struct {
|
|
Services []testInner `hcl:"service,expand"`
|
|
}
|
|
|
|
expected := testStruct{
|
|
Services: []testInner{
|
|
testInner{
|
|
Name: "my-service-0",
|
|
Key: "value",
|
|
},
|
|
testInner{
|
|
Name: "my-service-1",
|
|
Key: "value",
|
|
},
|
|
},
|
|
}
|
|
|
|
files := []string{
|
|
"slice_expand.hcl",
|
|
}
|
|
|
|
for _, f := range files {
|
|
t.Logf("Testing: %s", f)
|
|
|
|
var actual testStruct
|
|
err := Decode(&actual, testReadFile(t, f))
|
|
if err != nil {
|
|
t.Fatalf("Input: %s\n\nerr: %s", f, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("Input: %s\n\nActual: %#v\n\nExpected: %#v", f, actual, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDecode_structureMap(t *testing.T) {
|
|
// This test is extracted from a failure in Terraform (terraform.io),
|
|
// hence the interesting structure naming.
|
|
|
|
type hclVariable struct {
|
|
Default interface{}
|
|
Description string
|
|
Fields []string `hcl:",decodedFields"`
|
|
}
|
|
|
|
type rawConfig struct {
|
|
Variable map[string]hclVariable
|
|
}
|
|
|
|
expected := rawConfig{
|
|
Variable: map[string]hclVariable{
|
|
"foo": hclVariable{
|
|
Default: "bar",
|
|
Description: "bar",
|
|
Fields: []string{"Default", "Description"},
|
|
},
|
|
|
|
"amis": hclVariable{
|
|
Default: []map[string]interface{}{
|
|
map[string]interface{}{
|
|
"east": "foo",
|
|
},
|
|
},
|
|
Fields: []string{"Default"},
|
|
},
|
|
},
|
|
}
|
|
|
|
files := []string{
|
|
"decode_tf_variable.hcl",
|
|
"decode_tf_variable.json",
|
|
}
|
|
|
|
for _, f := range files {
|
|
t.Logf("Testing: %s", f)
|
|
|
|
var actual rawConfig
|
|
err := Decode(&actual, testReadFile(t, f))
|
|
if err != nil {
|
|
t.Fatalf("Input: %s\n\nerr: %s", f, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(actual, expected) {
|
|
t.Fatalf("Input: %s\n\nActual: %#v\n\nExpected: %#v", f, actual, expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDecode_structureMapInvalid(t *testing.T) {
|
|
// Terraform GH-8295
|
|
|
|
type hclVariable struct {
|
|
Default interface{}
|
|
Description string
|
|
Fields []string `hcl:",decodedFields"`
|
|
}
|
|
|
|
type rawConfig struct {
|
|
Variable map[string]*hclVariable
|
|
}
|
|
|
|
var actual rawConfig
|
|
err := Decode(&actual, testReadFile(t, "terraform_variable_invalid.json"))
|
|
if err == nil {
|
|
t.Fatal("expected error")
|
|
}
|
|
}
|
|
|
|
func TestDecode_interfaceNonPointer(t *testing.T) {
|
|
var value interface{}
|
|
err := Decode(value, testReadFile(t, "basic_int_string.hcl"))
|
|
if err == nil {
|
|
t.Fatal("should error")
|
|
}
|
|
}
|
|
|
|
func TestDecode_intString(t *testing.T) {
|
|
var value struct {
|
|
Count int
|
|
}
|
|
|
|
err := Decode(&value, testReadFile(t, "basic_int_string.hcl"))
|
|
if err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
|
|
if value.Count != 3 {
|
|
t.Fatalf("bad: %#v", value.Count)
|
|
}
|
|
}
|
|
|
|
func TestDecode_Node(t *testing.T) {
|
|
// given
|
|
var value struct {
|
|
Content ast.Node
|
|
Nested struct {
|
|
Content ast.Node
|
|
}
|
|
}
|
|
|
|
content := `
|
|
content {
|
|
hello = "world"
|
|
}
|
|
`
|
|
|
|
// when
|
|
err := Decode(&value, content)
|
|
|
|
// then
|
|
if err != nil {
|
|
t.Errorf("unable to decode content, %v", err)
|
|
return
|
|
}
|
|
|
|
// verify ast.Node can be decoded later
|
|
var v map[string]interface{}
|
|
err = DecodeObject(&v, value.Content)
|
|
if err != nil {
|
|
t.Errorf("unable to decode content, %v", err)
|
|
return
|
|
}
|
|
|
|
if v["hello"] != "world" {
|
|
t.Errorf("expected mapping to be returned")
|
|
}
|
|
}
|
|
|
|
func TestDecode_NestedNode(t *testing.T) {
|
|
// given
|
|
var value struct {
|
|
Nested struct {
|
|
Content ast.Node
|
|
}
|
|
}
|
|
|
|
content := `
|
|
nested "content" {
|
|
hello = "world"
|
|
}
|
|
`
|
|
|
|
// when
|
|
err := Decode(&value, content)
|
|
|
|
// then
|
|
if err != nil {
|
|
t.Errorf("unable to decode content, %v", err)
|
|
return
|
|
}
|
|
|
|
// verify ast.Node can be decoded later
|
|
var v map[string]interface{}
|
|
err = DecodeObject(&v, value.Nested.Content)
|
|
if err != nil {
|
|
t.Errorf("unable to decode content, %v", err)
|
|
return
|
|
}
|
|
|
|
if v["hello"] != "world" {
|
|
t.Errorf("expected mapping to be returned")
|
|
}
|
|
}
|
|
|
|
// https://github.com/hashicorp/hcl/issues/60
|
|
func TestDecode_topLevelKeys(t *testing.T) {
|
|
type Template struct {
|
|
Source string
|
|
}
|
|
|
|
templates := struct {
|
|
Templates []*Template `hcl:"template"`
|
|
}{}
|
|
|
|
err := Decode(&templates, `
|
|
template {
|
|
source = "blah"
|
|
}
|
|
|
|
template {
|
|
source = "blahblah"
|
|
}`)
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if templates.Templates[0].Source != "blah" {
|
|
t.Errorf("bad source: %s", templates.Templates[0].Source)
|
|
}
|
|
|
|
if templates.Templates[1].Source != "blahblah" {
|
|
t.Errorf("bad source: %s", templates.Templates[1].Source)
|
|
}
|
|
}
|
|
|
|
func TestDecode_flattenedJSON(t *testing.T) {
|
|
// make sure we can also correctly extract a Name key too
|
|
type V struct {
|
|
Name string `hcl:",key"`
|
|
Description string
|
|
Default map[string]string
|
|
}
|
|
type Vars struct {
|
|
Variable []*V
|
|
}
|
|
|
|
cases := []struct {
|
|
JSON string
|
|
Out interface{}
|
|
Expected interface{}
|
|
}{
|
|
{ // Nested object, no sibling keys
|
|
JSON: `
|
|
{
|
|
"var_name": {
|
|
"default": {
|
|
"key1": "a",
|
|
"key2": "b"
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
Out: &[]*V{},
|
|
Expected: &[]*V{
|
|
&V{
|
|
Name: "var_name",
|
|
Default: map[string]string{"key1": "a", "key2": "b"},
|
|
},
|
|
},
|
|
},
|
|
|
|
{ // Nested object with a sibling key (this worked previously)
|
|
JSON: `
|
|
{
|
|
"var_name": {
|
|
"description": "Described",
|
|
"default": {
|
|
"key1": "a",
|
|
"key2": "b"
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
Out: &[]*V{},
|
|
Expected: &[]*V{
|
|
&V{
|
|
Name: "var_name",
|
|
Description: "Described",
|
|
Default: map[string]string{"key1": "a", "key2": "b"},
|
|
},
|
|
},
|
|
},
|
|
|
|
{ // Multiple nested objects, one with a sibling key
|
|
JSON: `
|
|
{
|
|
"variable": {
|
|
"var_1": {
|
|
"default": {
|
|
"key1": "a",
|
|
"key2": "b"
|
|
}
|
|
},
|
|
"var_2": {
|
|
"description": "Described",
|
|
"default": {
|
|
"key1": "a",
|
|
"key2": "b"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
Out: &Vars{},
|
|
Expected: &Vars{
|
|
Variable: []*V{
|
|
&V{
|
|
Name: "var_1",
|
|
Default: map[string]string{"key1": "a", "key2": "b"},
|
|
},
|
|
&V{
|
|
Name: "var_2",
|
|
Description: "Described",
|
|
Default: map[string]string{"key1": "a", "key2": "b"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
{ // Nested object to maps
|
|
JSON: `
|
|
{
|
|
"variable": {
|
|
"var_name": {
|
|
"description": "Described",
|
|
"default": {
|
|
"key1": "a",
|
|
"key2": "b"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
Out: &[]map[string]interface{}{},
|
|
Expected: &[]map[string]interface{}{
|
|
{
|
|
"variable": []map[string]interface{}{
|
|
{
|
|
"var_name": []map[string]interface{}{
|
|
{
|
|
"description": "Described",
|
|
"default": []map[string]interface{}{
|
|
{
|
|
"key1": "a",
|
|
"key2": "b",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
{ // Nested object to maps without a sibling key should decode the same as above
|
|
JSON: `
|
|
{
|
|
"variable": {
|
|
"var_name": {
|
|
"default": {
|
|
"key1": "a",
|
|
"key2": "b"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
Out: &[]map[string]interface{}{},
|
|
Expected: &[]map[string]interface{}{
|
|
{
|
|
"variable": []map[string]interface{}{
|
|
{
|
|
"var_name": []map[string]interface{}{
|
|
{
|
|
"default": []map[string]interface{}{
|
|
{
|
|
"key1": "a",
|
|
"key2": "b",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
|
|
{ // Nested objects, one with a sibling key, and one without
|
|
JSON: `
|
|
{
|
|
"variable": {
|
|
"var_1": {
|
|
"default": {
|
|
"key1": "a",
|
|
"key2": "b"
|
|
}
|
|
},
|
|
"var_2": {
|
|
"description": "Described",
|
|
"default": {
|
|
"key1": "a",
|
|
"key2": "b"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`,
|
|
Out: &[]map[string]interface{}{},
|
|
Expected: &[]map[string]interface{}{
|
|
{
|
|
"variable": []map[string]interface{}{
|
|
{
|
|
"var_1": []map[string]interface{}{
|
|
{
|
|
"default": []map[string]interface{}{
|
|
{
|
|
"key1": "a",
|
|
"key2": "b",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"variable": []map[string]interface{}{
|
|
{
|
|
"var_2": []map[string]interface{}{
|
|
{
|
|
"description": "Described",
|
|
"default": []map[string]interface{}{
|
|
{
|
|
"key1": "a",
|
|
"key2": "b",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for i, tc := range cases {
|
|
err := Decode(tc.Out, tc.JSON)
|
|
if err != nil {
|
|
t.Fatalf("[%d] err: %s", i, err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(tc.Out, tc.Expected) {
|
|
t.Fatalf("[%d]\ngot: %s\nexpected: %s\n", i, spew.Sdump(tc.Out), spew.Sdump(tc.Expected))
|
|
}
|
|
}
|
|
}
|