core: Don't fail on dynamic attribute values during refresh

Our post-refresh safety check had the constraint and real type inverted,
so previously any refresh of a resource type with a dynamically-typed
attribute would fail this type check.

Also includes a small tweak to the error message from this check since the
old one was a little awkward to read in practice when the error is a
cty.PathError rendered with an attribute path prefix.
This commit is contained in:
Martin Atkins 2019-03-16 15:36:53 -07:00
parent 87fe6cbecd
commit 04cbf249aa
3 changed files with 78 additions and 2 deletions

View File

@ -89,6 +89,79 @@ func TestContext2Refresh(t *testing.T) {
} }
} }
func TestContext2Refresh_dynamicAttr(t *testing.T) {
m := testModule(t, "refresh-dynamic")
startingState := states.BuildState(func(ss *states.SyncState) {
ss.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
AttrsJSON: []byte(`{"dynamic":{"type":"string","value":"hello"}}`),
},
addrs.ProviderConfig{
Type: "test",
}.Absolute(addrs.RootModuleInstance),
)
})
readStateVal := cty.ObjectVal(map[string]cty.Value{
"dynamic": cty.EmptyTupleVal,
})
p := testProvider("test")
p.GetSchemaReturn = &ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"test_instance": {
Attributes: map[string]*configschema.Attribute{
"dynamic": {Type: cty.DynamicPseudoType, Optional: true},
},
},
},
}
p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
return providers.ReadResourceResponse{
NewState: readStateVal,
}
}
ctx := testContext2(t, &ContextOpts{
Config: m,
ProviderResolver: providers.ResolverFixed(
map[string]providers.Factory{
"test": testProviderFuncFixed(p),
},
),
State: startingState,
})
schema := p.GetSchemaReturn.ResourceTypes["test_instance"]
ty := schema.ImpliedType()
s, diags := ctx.Refresh()
if diags.HasErrors() {
t.Fatal(diags.Err())
}
if !p.ReadResourceCalled {
t.Fatal("ReadResource should be called")
}
mod := s.RootModule()
newState, err := mod.Resources["test_instance.foo"].Instances[addrs.NoKey].Current.Decode(ty)
if err != nil {
t.Fatal(err)
}
if !cmp.Equal(readStateVal, newState.Value, valueComparer) {
t.Error(cmp.Diff(newState.Value, readStateVal, valueComparer, equateEmpty))
}
}
func TestContext2Refresh_dataComputedModuleVar(t *testing.T) { func TestContext2Refresh_dataComputedModuleVar(t *testing.T) {
p := testProvider("aws") p := testProvider("aws")
m := testModule(t, "refresh-data-module-var") m := testModule(t, "refresh-data-module-var")

View File

@ -71,12 +71,12 @@ func (n *EvalRefresh) Eval(ctx EvalContext) (interface{}, error) {
panic("new state is cty.NilVal") panic("new state is cty.NilVal")
} }
for _, err := range schema.ImpliedType().TestConformance(resp.NewState.Type()) { for _, err := range resp.NewState.Type().TestConformance(schema.ImpliedType()) {
diags = diags.Append(tfdiags.Sourceless( diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error, tfdiags.Error,
"Provider produced invalid object", "Provider produced invalid object",
fmt.Sprintf( fmt.Sprintf(
"Provider %q planned an invalid value for %s: %s during refresh.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.", "Provider %q planned an invalid value for %s during refresh: %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.",
n.ProviderAddr.ProviderConfig.Type, absAddr, tfdiags.FormatError(err), n.ProviderAddr.ProviderConfig.Type, absAddr, tfdiags.FormatError(err),
), ),
)) ))

View File

@ -0,0 +1,3 @@
resource "test_instance" "foo" {
dynamic = {}
}