unknown evaluation of missing instances in import

Because import does not yet plan new instances as part of the import
process, we can end up evaluating references to resources which have no
state at all. The fallback for this situation could result in slightly
better values during import. The count and for_each values were
technically incorrect, since the length is not known to be zero, and the
single instance does have a concrete type which we can return.
This commit is contained in:
James Bardin 2022-09-27 12:02:44 -04:00
parent c1e0b046b8
commit 041d9d3eec
3 changed files with 94 additions and 1 deletions

View File

@ -915,6 +915,76 @@ resource "test_resource" "unused" {
} }
} }
// New resources in the config during import won't exist for evaluation
// purposes (until import is upgraded to using a complete plan). This means
// that references to them are unknown, but in the case of single instances, we
// can at least know the type of unknown value.
func TestContextImport_newResourceUnknown(t *testing.T) {
p := testProvider("aws")
m := testModuleInline(t, map[string]string{
"main.tf": `
resource "test_resource" "one" {
}
resource "test_resource" "two" {
count = length(flatten([test_resource.one.id]))
}
resource "test_resource" "test" {
}
`})
p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"test_resource": {
Attributes: map[string]*configschema.Attribute{
"id": {Type: cty.String, Computed: true},
},
},
},
})
p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
ImportedResources: []providers.ImportedResource{
{
TypeName: "test_resource",
State: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("test"),
}),
},
},
}
ctx := testContext2(t, &ContextOpts{
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
},
})
state, diags := ctx.Import(m, states.NewState(), &ImportOpts{
Targets: []*ImportTarget{
{
Addr: addrs.RootModuleInstance.ResourceInstance(
addrs.ManagedResourceMode, "test_resource", "test", addrs.NoKey,
),
ID: "test",
},
},
})
if diags.HasErrors() {
t.Fatal(diags.ErrWithWarnings())
}
ri := state.ResourceInstance(mustResourceInstanceAddr("test_resource.test"))
expected := `{"id":"test"}`
if ri == nil || ri.Current == nil {
t.Fatal("no state is recorded for resource instance test_resource.test")
}
if string(ri.Current.AttrsJSON) != expected {
t.Fatalf("expected %q, got %q\n", expected, ri.Current.AttrsJSON)
}
}
const testImportStr = ` const testImportStr = `
aws_instance.foo: aws_instance.foo:
ID = foo ID = foo

View File

@ -695,6 +695,29 @@ func (d *evaluationStateData) GetResource(addr addrs.Resource, rng tfdiags.Sourc
return cty.DynamicVal, diags return cty.DynamicVal, diags
} }
case walkImport:
// Import does not yet plan resource changes, so new resources from
// config are not going to be found here. Once walkImport fully
// plans resources, this case should not longer be needed.
// In the single instance case, we can return a typed unknown value
// for the instance to better satisfy other expressions using the
// value. This of course will not help if statically known
// attributes are expected to be known elsewhere, but reduces the
// number of problematic configs for now.
// Unlike in plan and apply above we can't be sure the count or
// for_each instances are empty, so we return a DynamicVal. We
// don't really have a good value to return otherwise -- empty
// values will fail for direct index expressions, and unknown
// Lists and Maps could fail in some type unifications.
switch {
case config.Count != nil:
return cty.DynamicVal, diags
case config.ForEach != nil:
return cty.DynamicVal, diags
default:
return cty.UnknownVal(ty), diags
}
default: default:
// We should only end up here during the validate walk, // We should only end up here during the validate walk,
// since later walks should have at least partial states populated // since later walks should have at least partial states populated

View File

@ -1,5 +1,5 @@
provider "aws" { provider "aws" {
value = "${test_instance.bar.value}" value = "${test_instance.bar.id}"
} }
resource "aws_instance" "foo" { resource "aws_instance" "foo" {