From 8a3559560dd9a04f8b869d734374f9b1b768ae41 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 25 Aug 2016 17:18:18 -0700 Subject: [PATCH] config: JSON resource keys with only one item load properly GH-5140 When a resource has only a single key set, the HCL parser treats that key as part of the overall set of object keys. This isn't valid since we expect resources to have exactly two keys. In this scenario, we have to "unwrap" the keys back into a set of objects. --- config/loader_hcl.go | 31 +++++++++++++++++++ config/loader_test.go | 21 +++++++++++++ config/test-fixtures/resource-no-name.tf.json | 11 +++++++ 3 files changed, 63 insertions(+) create mode 100644 config/test-fixtures/resource-no-name.tf.json diff --git a/config/loader_hcl.go b/config/loader_hcl.go index 8c81156e4..2988db19f 100644 --- a/config/loader_hcl.go +++ b/config/loader_hcl.go @@ -555,6 +555,37 @@ func loadManagedResourcesHcl(list *ast.ObjectList) ([]*Resource, error) { item.Pos()) } + // HCL special case: if we're parsing JSON then directly nested + // items will show up as additional "keys". We need to unwrap them + // since we expect only two keys. Example: + // + // { "foo": { "bar": { "baz": {} } } } + // + // Will show up with Keys being: []string{"foo", "bar", "baz"} + // when we really just want the first two. To fix this we unwrap + // them into the right value. + if len(item.Keys) > 2 && item.Keys[0].Token.JSON { + for len(item.Keys) > 2 { + // Pop off the last key + n := len(item.Keys) + key := item.Keys[n-1] + item.Keys[n-1] = nil + item.Keys = item.Keys[:n-1] + + // Wrap our value in a list + item.Val = &ast.ObjectType{ + List: &ast.ObjectList{ + Items: []*ast.ObjectItem{ + &ast.ObjectItem{ + Keys: []*ast.ObjectKey{key}, + Val: item.Val, + }, + }, + }, + } + } + } + if len(item.Keys) != 2 { return nil, fmt.Errorf( "position %s: resource must be followed by exactly two strings, a type and a name", diff --git a/config/loader_test.go b/config/loader_test.go index b7d53dee5..09b16f352 100644 --- a/config/loader_test.go +++ b/config/loader_test.go @@ -327,6 +327,22 @@ func TestLoadJSONBasic(t *testing.T) { } } +func TestLoadFileBasic_jsonNoName(t *testing.T) { + c, err := LoadFile(filepath.Join(fixtureDir, "resource-no-name.tf.json")) + if err != nil { + t.Fatalf("err: %s", err) + } + + if c == nil { + t.Fatal("config should not be nil") + } + + actual := resourcesStr(c.Resources) + if actual != strings.TrimSpace(basicJsonNoNameResourcesStr) { + t.Fatalf("bad:\n%s", actual) + } +} + func TestLoadFile_variables(t *testing.T) { c, err := LoadFile(filepath.Join(fixtureDir, "variables.tf")) if err != nil { @@ -837,6 +853,11 @@ foo bar ` +const basicJsonNoNameResourcesStr = ` +aws_security_group.allow_external_http_https (x1) + tags +` + const dirBasicOutputsStr = ` web_ip vars diff --git a/config/test-fixtures/resource-no-name.tf.json b/config/test-fixtures/resource-no-name.tf.json new file mode 100644 index 000000000..8dbf3e08c --- /dev/null +++ b/config/test-fixtures/resource-no-name.tf.json @@ -0,0 +1,11 @@ +{ + "resource" : { + "aws_security_group" : { + "allow_external_http_https" : { + "tags" : { + "Name" : "allow_external_http_https" + } + } + } + } +}