Allow known references in import blocks (#1105)

Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
Christian Mesh 2024-01-12 07:23:25 -05:00 committed by GitHub
parent 5071fdc233
commit c996be8d7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 117 additions and 33 deletions

View File

@ -18,6 +18,7 @@ ENHANCEMENTS:
BUG FIXES:
* `tofu test` resources cleanup at the end of tests changed to use simple reverse run block order. ([#1043](https://github.com/opentofu/opentofu/pull/1043))
* Fix access to known references when using a import block for module resources ([#1105](https://github.com/opentofu/opentofu/pull/1105))
## Previous Releases

View File

@ -4631,6 +4631,39 @@ func TestContext2Plan_importIdVariable(t *testing.T) {
}
}
func TestContext2Plan_importIdReference(t *testing.T) {
p := testProvider("aws")
m := testModule(t, "import-id-reference")
ctx := testContext2(t, &ContextOpts{
Providers: map[addrs.Provider]providers.Factory{
addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
},
})
p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
ImportedResources: []providers.ImportedResource{
{
TypeName: "aws_instance",
State: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("foo"),
}),
},
},
}
_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
SetVariables: InputValues{
"the_id": &InputValue{
// let var take its default value
Value: cty.NilVal,
},
},
})
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
}
func TestContext2Plan_importIdFunc(t *testing.T) {
p := testProvider("aws")
m := testModule(t, "import-id-func")

View File

@ -4,6 +4,7 @@ import (
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/lang/marks"
"github.com/opentofu/opentofu/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
@ -22,6 +23,10 @@ func evaluateImportIdExpression(expr hcl.Expression, ctx EvalContext) (string, t
})
}
// The import expression is declared within the root module
// We need to explicitly use that context
ctx = ctx.WithPath(addrs.RootModuleInstance)
importIdVal, evalDiags := ctx.EvaluateExpr(expr, cty.String, nil)
diags = diags.Append(evalDiags)

View File

@ -111,6 +111,7 @@ var (
_ GraphNodeModuleInstance = (*NodeAbstractResourceInstance)(nil)
_ GraphNodeReferenceable = (*NodeAbstractResourceInstance)(nil)
_ GraphNodeReferencer = (*NodeAbstractResourceInstance)(nil)
_ GraphNodeRootReferencer = (*NodeAbstractResourceInstance)(nil)
_ GraphNodeProviderConsumer = (*NodeAbstractResourceInstance)(nil)
_ GraphNodeProvisionerConsumer = (*NodeAbstractResourceInstance)(nil)
_ GraphNodeConfigResource = (*NodeAbstractResourceInstance)(nil)
@ -205,12 +206,18 @@ func (n *NodeAbstractResource) References() []*addrs.Reference {
}
}
return result
}
func (n *NodeAbstractResource) RootReferences() []*addrs.Reference {
var root []*addrs.Reference
for _, importTarget := range n.importTargets {
refs, _ := lang.ReferencesInExpr(addrs.ParseRef, importTarget.ID)
result = append(result, refs...)
root = append(root, refs...)
}
return result
return root
}
func (n *NodeAbstractResource) DependsOn() []*addrs.Reference {

View File

@ -0,0 +1,13 @@
variable "the_id" {
default = "123"
}
module "refmod" {
source = "./mod"
}
import {
to = module.refmod.aws_instance.foo
id = var.the_id
}

View File

@ -0,0 +1,2 @@
resource "aws_instance" "foo" {
}

View File

@ -42,6 +42,14 @@ type GraphNodeReferencer interface {
References() []*addrs.Reference
}
// GraphNodeRootReferencer is implemented by nodes that reference the root
// module, for example module imports
type GraphNodeRootReferencer interface {
GraphNodeReferencer
RootReferences() []*addrs.Reference
}
type GraphNodeAttachDependencies interface {
GraphNodeConfigResource
AttachDependencies([]addrs.ConfigResource)
@ -295,40 +303,55 @@ func (m ReferenceMap) References(v dag.Vertex) []dag.Vertex {
var matches []dag.Vertex
for _, ref := range rn.References() {
subject := ref.Subject
key := m.referenceMapKey(v, subject)
if _, exists := m[key]; !exists {
// If what we were looking for was a ResourceInstance then we
// might be in a resource-oriented graph rather than an
// instance-oriented graph, and so we'll see if we have the
// resource itself instead.
switch ri := subject.(type) {
case addrs.ResourceInstance:
subject = ri.ContainingResource()
case addrs.ResourceInstancePhase:
subject = ri.ContainingResource()
case addrs.ModuleCallInstanceOutput:
subject = ri.ModuleCallOutput()
case addrs.ModuleCallInstance:
subject = ri.Call
default:
log.Printf("[INFO] ReferenceTransformer: reference not found: %q", subject)
continue
}
key = m.referenceMapKey(v, subject)
}
vertices := m[key]
for _, rv := range vertices {
// don't include self-references
if rv == v {
continue
}
matches = append(matches, rv)
if rrn, ok := rn.(GraphNodeRootReferencer); ok {
for _, ref := range rrn.RootReferences() {
matches = append(matches, m.addReference(addrs.RootModule, v, ref)...)
}
}
for _, ref := range rn.References() {
matches = append(matches, m.addReference(vertexReferencePath(v), v, ref)...)
}
return matches
}
// addReferences returns the set of vertices that the given reference requires
// within a given module. It additionally excludes the current vertex.
func (m ReferenceMap) addReference(path addrs.Module, current dag.Vertex, ref *addrs.Reference) []dag.Vertex {
var matches []dag.Vertex
subject := ref.Subject
key := m.mapKey(path, subject)
if _, exists := m[key]; !exists {
// If what we were looking for was a ResourceInstance then we
// might be in a resource-oriented graph rather than an
// instance-oriented graph, and so we'll see if we have the
// resource itself instead.
switch ri := subject.(type) {
case addrs.ResourceInstance:
subject = ri.ContainingResource()
case addrs.ResourceInstancePhase:
subject = ri.ContainingResource()
case addrs.ModuleCallInstanceOutput:
subject = ri.ModuleCallOutput()
case addrs.ModuleCallInstance:
subject = ri.Call
default:
log.Printf("[INFO] ReferenceTransformer: reference not found: %q", subject)
return nil
}
key = m.mapKey(path, subject)
}
vertices := m[key]
for _, rv := range vertices {
// don't include self-references
if rv == current {
continue
}
matches = append(matches, rv)
}
return matches
}