diff --git a/builtin/providers/test/data_source.go b/builtin/providers/test/data_source.go index 3c34b9683..2a735703f 100644 --- a/builtin/providers/test/data_source.go +++ b/builtin/providers/test/data_source.go @@ -31,6 +31,17 @@ func testDataSource() *schema.Resource { Type: schema.TypeString, Computed: true, }, + + "input_map": { + Type: schema.TypeMap, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + }, + "output_map": { + Type: schema.TypeMap, + Elem: &schema.Schema{Type: schema.TypeString}, + Computed: true, + }, }, } } @@ -45,5 +56,8 @@ func testDataSourceRead(d *schema.ResourceData, meta interface{}) error { d.Set("output", "some output") } + if inputMap, hasInput := d.GetOk("input_map"); hasInput { + d.Set("output_map", inputMap) + } return nil } diff --git a/builtin/providers/test/data_source_test.go b/builtin/providers/test/data_source_test.go index 009aec5cd..bc7f1f73e 100644 --- a/builtin/providers/test/data_source_test.go +++ b/builtin/providers/test/data_source_test.go @@ -3,6 +3,7 @@ package test import ( "errors" "fmt" + "regexp" "strings" "testing" @@ -189,3 +190,52 @@ data "test_data_source" "y" { }, }) } + +// referencing test_data_source.one.output_map["a"] should produce an error when +// there's a count, but instead we end up with an unknown value after apply. +func TestDataSource_indexedCountOfOne(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: strings.TrimSpace(` +data "test_data_source" "one" { + count = 1 + input_map = { + "a" = "b" + } +} + +data "test_data_source" "two" { + input_map = { + "x" = data.test_data_source.one.output_map["a"] + } +} + `), + ExpectError: regexp.MustCompile("value does not have any attributes"), + }, + }, + }) +} + +// Verify that we can destroy when a data source references something with a +// count of 1. +func TestDataSource_countRefDestroyError(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: strings.TrimSpace(` +data "test_data_source" "one" { + count = 1 + input = "a" +} + +data "test_data_source" "two" { + input = data.test_data_source.one[0].output +} + `), + }, + }, + }) +} diff --git a/builtin/providers/test/resource_test.go b/builtin/providers/test/resource_test.go index 19c9f2611..b9eb06455 100644 --- a/builtin/providers/test/resource_test.go +++ b/builtin/providers/test/resource_test.go @@ -493,3 +493,31 @@ resource "test_resource" "foo" { }, }) } + +// Verify that we can destroy when a managed resource references something with +// a count of 1. +func TestResource_countRefDestroyError(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: strings.TrimSpace(` +resource "test_resource" "one" { + count = 1 + required = "ok" + required_map = { + key = "val" + } +} + +resource "test_resource" "two" { + required = test_resource.one[0].id + required_map = { + key = "val" + } +} + `), + }, + }, + }) +} diff --git a/terraform/eval_context_builtin.go b/terraform/eval_context_builtin.go index f01292abb..20b37933d 100644 --- a/terraform/eval_context_builtin.go +++ b/terraform/eval_context_builtin.go @@ -289,6 +289,7 @@ func (ctx *BuiltinEvalContext) EvaluationScope(self addrs.Referenceable, keyData Evaluator: ctx.Evaluator, ModulePath: ctx.PathValue, InstanceKeyData: keyData, + Operation: ctx.Evaluator.Operation, } return ctx.Evaluator.Scope(data, self) } diff --git a/terraform/evaluate.go b/terraform/evaluate.go index 85be320e6..8e503c3d1 100644 --- a/terraform/evaluate.go +++ b/terraform/evaluate.go @@ -91,6 +91,10 @@ type evaluationStateData struct { // since the user specifies in that case which variable name to locally // shadow.) InstanceKeyData InstanceKeyEvalData + + // Operation records the type of walk the evaluationStateData is being used + // for. + Operation walkOperation } // InstanceKeyEvalData is used during evaluation to specify which values, @@ -506,6 +510,12 @@ func (d *evaluationStateData) GetResourceInstance(addr addrs.ResourceInstance, r return cty.DynamicVal, diags } + // Break out early during validation, because resource may not be expanded + // yet and indexed references may show up as invalid. + if d.Operation == walkValidate { + return cty.DynamicVal, diags + } + schema := d.getResourceSchema(addr.ContainingResource(), rs.ProviderConfig) // If we are able to automatically convert to the "right" type of instance