Merge pull request #21555 from hashicorp/jbardin/re-validate
Allow providers to re-validate the final resource config
This commit is contained in:
commit
d33c5163a7
|
@ -95,7 +95,19 @@ func testResourceList() *schema.Resource {
|
||||||
Computed: true,
|
Computed: true,
|
||||||
Elem: &schema.Schema{Type: schema.TypeString},
|
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": {
|
"never_set": {
|
||||||
Type: schema.TypeList,
|
Type: schema.TypeList,
|
||||||
MaxItems: 1,
|
MaxItems: 1,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -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) {
|
func checkVals(t *testing.T, expected, got cty.Value) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if !cmp.Equal(expected, got, valueComparer, typeComparer, equateEmpty) {
|
if !cmp.Equal(expected, got, valueComparer, typeComparer, equateEmpty) {
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
// The provider gets an opportunity to customize the proposed new value,
|
||||||
// which in turn produces the _planned_ new value.
|
// which in turn produces the _planned_ new value.
|
||||||
resp := provider.PlanResourceChange(providers.PlanResourceChangeRequest{
|
resp := provider.PlanResourceChange(providers.PlanResourceChangeRequest{
|
||||||
|
|
|
@ -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
|
// If we get down here then our configuration is complete and we're read
|
||||||
// to actually call the provider to read the data.
|
// to actually call the provider to read the data.
|
||||||
log.Printf("[TRACE] EvalReadData: %s configuration is complete, so reading from provider", absAddr)
|
log.Printf("[TRACE] EvalReadData: %s configuration is complete, so reading from provider", absAddr)
|
||||||
|
|
Loading…
Reference in New Issue