diff --git a/config/loader_hcl.go b/config/loader_hcl.go index 6214a3dea..8c81156e4 100644 --- a/config/loader_hcl.go +++ b/config/loader_hcl.go @@ -20,6 +20,7 @@ type hclConfigurable struct { func (t *hclConfigurable) Config() (*Config, error) { validKeys := map[string]struct{}{ "atlas": struct{}{}, + "data": struct{}{}, "module": struct{}{}, "output": struct{}{}, "provider": struct{}{}, @@ -113,12 +114,27 @@ func (t *hclConfigurable) Config() (*Config, error) { } // Build the resources - if resources := list.Filter("resource"); len(resources.Items) > 0 { + { var err error - config.Resources, err = loadResourcesHcl(resources) + managedResourceConfigs := list.Filter("resource") + dataResourceConfigs := list.Filter("data") + + config.Resources = make( + []*Resource, 0, + len(managedResourceConfigs.Items)+len(dataResourceConfigs.Items), + ) + + managedResources, err := loadManagedResourcesHcl(managedResourceConfigs) if err != nil { return nil, err } + dataResources, err := loadDataResourcesHcl(dataResourceConfigs) + if err != nil { + return nil, err + } + + config.Resources = append(config.Resources, dataResources...) + config.Resources = append(config.Resources, managedResources...) } // Build the outputs @@ -395,12 +411,130 @@ func loadProvidersHcl(list *ast.ObjectList) ([]*ProviderConfig, error) { } // Given a handle to a HCL object, this recurses into the structure -// and pulls out a list of resources. +// and pulls out a list of data sources. +// +// The resulting data sources may not be unique, but each one +// represents exactly one data definition in the HCL configuration. +// We leave it up to another pass to merge them together. +func loadDataResourcesHcl(list *ast.ObjectList) ([]*Resource, error) { + list = list.Children() + if len(list.Items) == 0 { + return nil, nil + } + + // Where all the results will go + var result []*Resource + + // Now go over all the types and their children in order to get + // all of the actual resources. + for _, item := range list.Items { + if len(item.Keys) != 2 { + return nil, fmt.Errorf( + "position %s: 'data' must be followed by exactly two strings: a type and a name", + item.Pos()) + } + + t := item.Keys[0].Token.Value().(string) + k := item.Keys[1].Token.Value().(string) + + var listVal *ast.ObjectList + if ot, ok := item.Val.(*ast.ObjectType); ok { + listVal = ot.List + } else { + return nil, fmt.Errorf("data sources %s[%s]: should be an object", t, k) + } + + var config map[string]interface{} + if err := hcl.DecodeObject(&config, item.Val); err != nil { + return nil, fmt.Errorf( + "Error reading config for %s[%s]: %s", + t, + k, + err) + } + + // Remove the fields we handle specially + delete(config, "depends_on") + delete(config, "provider") + + rawConfig, err := NewRawConfig(config) + if err != nil { + return nil, fmt.Errorf( + "Error reading config for %s[%s]: %s", + t, + k, + err) + } + + // If we have a count, then figure it out + var count string = "1" + if o := listVal.Filter("count"); len(o.Items) > 0 { + err = hcl.DecodeObject(&count, o.Items[0].Val) + if err != nil { + return nil, fmt.Errorf( + "Error parsing count for %s[%s]: %s", + t, + k, + err) + } + } + countConfig, err := NewRawConfig(map[string]interface{}{ + "count": count, + }) + if err != nil { + return nil, err + } + countConfig.Key = "count" + + // If we have depends fields, then add those in + var dependsOn []string + if o := listVal.Filter("depends_on"); len(o.Items) > 0 { + err := hcl.DecodeObject(&dependsOn, o.Items[0].Val) + if err != nil { + return nil, fmt.Errorf( + "Error reading depends_on for %s[%s]: %s", + t, + k, + err) + } + } + + // If we have a provider, then parse it out + var provider string + if o := listVal.Filter("provider"); len(o.Items) > 0 { + err := hcl.DecodeObject(&provider, o.Items[0].Val) + if err != nil { + return nil, fmt.Errorf( + "Error reading provider for %s[%s]: %s", + t, + k, + err) + } + } + + result = append(result, &Resource{ + Mode: DataResourceMode, + Name: k, + Type: t, + RawCount: countConfig, + RawConfig: rawConfig, + Provider: provider, + Provisioners: []*Provisioner{}, + DependsOn: dependsOn, + Lifecycle: ResourceLifecycle{}, + }) + } + + return result, nil +} + +// Given a handle to a HCL object, this recurses into the structure +// and pulls out a list of managed resources. // // The resulting resources may not be unique, but each resource -// represents exactly one resource definition in the HCL configuration. +// represents exactly one "resource" block in the HCL configuration. // We leave it up to another pass to merge them together. -func loadResourcesHcl(list *ast.ObjectList) ([]*Resource, error) { +func loadManagedResourcesHcl(list *ast.ObjectList) ([]*Resource, error) { list = list.Children() if len(list.Items) == 0 { return nil, nil diff --git a/config/loader_test.go b/config/loader_test.go index d4ebb1e5b..09f63e231 100644 --- a/config/loader_test.go +++ b/config/loader_test.go @@ -65,6 +65,17 @@ func TestLoadFile_resourceArityMistake(t *testing.T) { } } +func TestLoadFile_dataSourceArityMistake(t *testing.T) { + _, err := LoadFile(filepath.Join(fixtureDir, "data-source-arity-mistake.tf")) + if err == nil { + t.Fatal("should have error") + } + expected := "Error loading test-fixtures/data-source-arity-mistake.tf: position 2:6: 'data' must be followed by exactly two strings: a type and a name" + if err.Error() != expected { + t.Fatalf("expected:\n%s\ngot:\n%s", expected, err) + } +} + func TestLoadFileWindowsLineEndings(t *testing.T) { testFile := filepath.Join(fixtureDir, "windows-line-endings.tf") @@ -823,6 +834,11 @@ aws_instance.web (x1) resource: aws_security_group.firewall.foo user: var.foo aws_security_group.firewall (x5) +data.do.depends (x1) + dependsOn + data.do.simple +data.do.simple (x1) + foo ` const basicVariablesStr = ` @@ -866,6 +882,11 @@ aws_instance.web (x1) resource: aws_security_group.firewall.foo user: var.foo aws_security_group.firewall (x5) +data.do.depends (x1) + dependsOn + data.do.simple +data.do.simple (x1) + foo ` const dirBasicVariablesStr = ` @@ -903,6 +924,12 @@ aws_instance.web (x1) resource: aws_security_group.firewall.foo user: var.foo aws_security_group.firewall (x5) +data.do.depends (x1) + hello + dependsOn + data.do.simple +data.do.simple (x1) + foo ` const dirOverrideVariablesStr = ` diff --git a/config/test-fixtures/basic.tf b/config/test-fixtures/basic.tf index 42c1d681b..45314d54b 100644 --- a/config/test-fixtures/basic.tf +++ b/config/test-fixtures/basic.tf @@ -24,6 +24,14 @@ provider "do" { api_key = "${var.foo}" } +data "do" "simple" { + foo = "baz" +} + +data "do" "depends" { + depends_on = ["data.do.simple"] +} + resource "aws_security_group" "firewall" { count = 5 } diff --git a/config/test-fixtures/basic.tf.json b/config/test-fixtures/basic.tf.json index ba8aa9749..be86d5de5 100644 --- a/config/test-fixtures/basic.tf.json +++ b/config/test-fixtures/basic.tf.json @@ -26,6 +26,17 @@ } }, + "data": { + "do": { + "simple": { + "foo": "baz" + }, + "depends": { + "depends_on": ["data.do.simple"] + } + } + }, + "resource": { "aws_instance": { "db": { diff --git a/config/test-fixtures/data-source-arity-mistake.tf b/config/test-fixtures/data-source-arity-mistake.tf new file mode 100644 index 000000000..5d579a9db --- /dev/null +++ b/config/test-fixtures/data-source-arity-mistake.tf @@ -0,0 +1,3 @@ +# I forgot the data source name! +data "null" { +} diff --git a/config/test-fixtures/dir-basic/one.tf b/config/test-fixtures/dir-basic/one.tf index 1e049a87f..387c7b8d5 100644 --- a/config/test-fixtures/dir-basic/one.tf +++ b/config/test-fixtures/dir-basic/one.tf @@ -8,6 +8,10 @@ provider "aws" { secret_key = "bar" } +data "do" "simple" { + foo = "baz" +} + resource "aws_instance" "db" { security_groups = "${aws_security_group.firewall.*.id}" } diff --git a/config/test-fixtures/dir-basic/two.tf b/config/test-fixtures/dir-basic/two.tf index acbb4f2f9..f64a28263 100644 --- a/config/test-fixtures/dir-basic/two.tf +++ b/config/test-fixtures/dir-basic/two.tf @@ -2,6 +2,10 @@ provider "do" { api_key = "${var.foo}" } +data "do" "depends" { + depends_on = ["data.do.simple"] +} + resource "aws_security_group" "firewall" { count = 5 } diff --git a/config/test-fixtures/dir-override/foo_override.tf.json b/config/test-fixtures/dir-override/foo_override.tf.json index 93c351b00..4a43f09fc 100644 --- a/config/test-fixtures/dir-override/foo_override.tf.json +++ b/config/test-fixtures/dir-override/foo_override.tf.json @@ -1,4 +1,11 @@ { + "data": { + "do": { + "depends": { + "hello": "world" + } + } + }, "resource": { "aws_instance": { "web": { diff --git a/config/test-fixtures/dir-override/one.tf b/config/test-fixtures/dir-override/one.tf index 1e049a87f..5ef1fe719 100644 --- a/config/test-fixtures/dir-override/one.tf +++ b/config/test-fixtures/dir-override/one.tf @@ -8,6 +8,11 @@ provider "aws" { secret_key = "bar" } + +data "do" "simple" { + foo = "baz" +} + resource "aws_instance" "db" { security_groups = "${aws_security_group.firewall.*.id}" } diff --git a/config/test-fixtures/dir-override/two.tf b/config/test-fixtures/dir-override/two.tf index acbb4f2f9..f64a28263 100644 --- a/config/test-fixtures/dir-override/two.tf +++ b/config/test-fixtures/dir-override/two.tf @@ -2,6 +2,10 @@ provider "do" { api_key = "${var.foo}" } +data "do" "depends" { + depends_on = ["data.do.simple"] +} + resource "aws_security_group" "firewall" { count = 5 }