Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion builtin/providers/test/resource_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,19 @@ func testResourceList() *schema.Resource {
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},

"min_items": {
Type: schema.TypeList,
Optional: true,
MinItems: 2,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"val": {
Type: schema.TypeString,
Required: true,
},
},
},
},
"never_set": {
Type: schema.TypeList,
MaxItems: 1,
Expand Down
52 changes: 52 additions & 0 deletions builtin/providers/test/resource_list_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package test

import (
"regexp"
"strings"
"testing"

Expand Down Expand Up @@ -481,3 +482,54 @@ resource "test_resource_list" "b" {
},
})
}

func TestResourceList_dynamicMinItems(t *testing.T) {
resource.UnitTest(t, resource.TestCase{
Providers: testAccProviders,
CheckDestroy: testAccCheckResourceDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: strings.TrimSpace(`
variable "a" {
type = list(number)
default = [1]
}

resource "test_resource_list" "b" {
dynamic "min_items" {
for_each = var.a
content {
val = "foo"
}
}
}
`),
ExpectError: regexp.MustCompile(`attribute supports 2`),
},
resource.TestStep{
Config: strings.TrimSpace(`
resource "test_resource_list" "a" {
dependent_list {
val = "a"
}

dependent_list {
val = "b"
}
}
resource "test_resource_list" "b" {
list_block {
string = "constant"
}
dynamic "min_items" {
for_each = test_resource_list.a.computed_list
content {
val = min_items.value
}
}
}
`),
},
},
})
}
44 changes: 44 additions & 0 deletions terraform/context_plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package terraform

import (
"bytes"
"errors"
"fmt"
"os"
"reflect"
Expand Down Expand Up @@ -5724,6 +5725,49 @@ resource "aws_instance" "foo" {
}
}

func TestContext2Plan_variableValidation(t *testing.T) {
m := testModuleInline(t, map[string]string{
"main.tf": `
variable "x" {
default = "bar"
}

resource "aws_instance" "foo" {
foo = var.x
}`,
})

p := testProvider("aws")
p.ValidateResourceTypeConfigFn = func(req providers.ValidateResourceTypeConfigRequest) (resp providers.ValidateResourceTypeConfigResponse) {
foo := req.Config.GetAttr("foo").AsString()
if foo == "bar" {
resp.Diagnostics = resp.Diagnostics.Append(errors.New("foo cannot be bar"))
}
return
}

p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
resp.PlannedState = req.ProposedNewState
return
}

ctx := testContext2(t, &ContextOpts{
Config: m,
ProviderResolver: providers.ResolverFixed(
map[string]providers.Factory{
"aws": testProviderFuncFixed(p),
},
),
})

_, diags := ctx.Plan()
if !diags.HasErrors() {
// Should get this error:
// Unsupported attribute: This object does not have an attribute named "missing"
t.Fatal("succeeded; want errors")
}
}

func checkVals(t *testing.T, expected, got cty.Value) {
t.Helper()
if !cmp.Equal(expected, got, valueComparer, typeComparer, equateEmpty) {
Expand Down
40 changes: 40 additions & 0 deletions terraform/context_refresh_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1866,3 +1866,43 @@ test_thing.bar:
}
}
}

func TestContext2Refresh_dataValidation(t *testing.T) {
m := testModuleInline(t, map[string]string{
"main.tf": `
data "aws_data_source" "foo" {
foo = "bar"
}
`,
})

p := testProvider("aws")
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
resp.PlannedState = req.ProposedNewState
return
}
p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
resp.State = req.Config
return
}

ctx := testContext2(t, &ContextOpts{
Config: m,
ProviderResolver: providers.ResolverFixed(
map[string]providers.Factory{
"aws": testProviderFuncFixed(p),
},
),
})

_, diags := ctx.Refresh()
if diags.HasErrors() {
// Should get this error:
// Unsupported attribute: This object does not have an attribute named "missing"
t.Fatal(diags.Err())
}

if !p.ValidateDataSourceConfigCalled {
t.Fatal("ValidateDataSourceConfig not called during plan")
}
}
14 changes: 14 additions & 0 deletions terraform/eval_diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,20 @@ func (n *EvalDiff) Eval(ctx EvalContext) (interface{}, error) {
}
}

log.Printf("[TRACE] Re-validating config for %q", n.Addr.Absolute(ctx.Path()))
// Allow the provider to validate the final set of values.
// The config was statically validated early on, but there may have been
// unknown values which the provider could not validate at the time.
validateResp := provider.ValidateResourceTypeConfig(
providers.ValidateResourceTypeConfigRequest{
TypeName: n.Addr.Resource.Type,
Config: configVal,
},
)
if validateResp.Diagnostics.HasErrors() {
return nil, validateResp.Diagnostics.InConfigBody(config.Config).Err()
}

// The provider gets an opportunity to customize the proposed new value,
// which in turn produces the _planned_ new value.
resp := provider.PlanResourceChange(providers.PlanResourceChangeRequest{
Expand Down
11 changes: 11 additions & 0 deletions terraform/eval_read_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,17 @@ func (n *EvalReadData) Eval(ctx EvalContext) (interface{}, error) {
)
}

log.Printf("[TRACE] Re-validating config for %s", absAddr)
validateResp := provider.ValidateDataSourceConfig(
providers.ValidateDataSourceConfigRequest{
TypeName: n.Addr.Resource.Type,
Config: configVal,
},
)
if validateResp.Diagnostics.HasErrors() {
return nil, validateResp.Diagnostics.InConfigBody(n.Config.Config).Err()
}

// If we get down here then our configuration is complete and we're read
// to actually call the provider to read the data.
log.Printf("[TRACE] EvalReadData: %s configuration is complete, so reading from provider", absAddr)
Expand Down