mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-19 17:59:05 -05:00
lang/eval: Provider instance to ephemeral resource instance dependencies
This completes a previously-missing piece of the "prepareToPlan" result, tracking which provider instances are relying on each ephemeral resource instance. This is important because the planning engine can "see" resource-instance-to-provider relationships in the state that the eval system isn't aware of, and so the planning engine must be able to keep a provider instance open long enough to deal with both config-driven and state-driven uses of it, which in turn means keeping open any ephemeral resource instances that those provider instances depend on. Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
This commit is contained in:
@@ -61,6 +61,7 @@ func (c *ConfigInstance) prepareToPlan(ctx context.Context) (*ResourceRelationsh
|
|||||||
if !ret.EphemeralResourceUsers.Has(dependeeAddr) {
|
if !ret.EphemeralResourceUsers.Has(dependeeAddr) {
|
||||||
ret.EphemeralResourceUsers.Put(dependeeAddr, EphemeralResourceInstanceUsers{
|
ret.EphemeralResourceUsers.Put(dependeeAddr, EphemeralResourceInstanceUsers{
|
||||||
ResourceInstances: addrs.MakeSet[addrs.AbsResourceInstance](),
|
ResourceInstances: addrs.MakeSet[addrs.AbsResourceInstance](),
|
||||||
|
ProviderInstances: addrs.MakeSet[addrs.AbsProviderInstanceCorrect](),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
set := ret.EphemeralResourceUsers.Get(dependeeAddr).ResourceInstances
|
set := ret.EphemeralResourceUsers.Get(dependeeAddr).ResourceInstances
|
||||||
@@ -79,6 +80,22 @@ func (c *ConfigInstance) prepareToPlan(ctx context.Context) (*ResourceRelationsh
|
|||||||
set.Add(dependerAddr)
|
set.Add(dependerAddr)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for depender := range evalglue.ProviderInstancesDeep(ctx, rootModuleInstance) {
|
||||||
|
dependerAddr := depender.Addr
|
||||||
|
for dependee := range depender.ResourceInstanceDependencies(ctx) {
|
||||||
|
dependeeAddr := dependee.Addr
|
||||||
|
if dependeeAddr.Resource.Resource.Mode == addrs.EphemeralResourceMode {
|
||||||
|
if !ret.EphemeralResourceUsers.Has(dependeeAddr) {
|
||||||
|
ret.EphemeralResourceUsers.Put(dependeeAddr, EphemeralResourceInstanceUsers{
|
||||||
|
ResourceInstances: addrs.MakeSet[addrs.AbsResourceInstance](),
|
||||||
|
ProviderInstances: addrs.MakeSet[addrs.AbsProviderInstanceCorrect](),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
set := ret.EphemeralResourceUsers.Get(dependeeAddr).ProviderInstances
|
||||||
|
set.Add(dependerAddr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return ret, diags
|
return ret, diags
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,23 +123,7 @@ type ResourceRelationships struct {
|
|||||||
|
|
||||||
type EphemeralResourceInstanceUsers struct {
|
type EphemeralResourceInstanceUsers struct {
|
||||||
ResourceInstances addrs.Set[addrs.AbsResourceInstance]
|
ResourceInstances addrs.Set[addrs.AbsResourceInstance]
|
||||||
|
ProviderInstances addrs.Set[addrs.AbsProviderInstanceCorrect]
|
||||||
// TODO: ProviderInstances
|
|
||||||
// This should be a set of provider instance addresses, but we don't
|
|
||||||
// currently have a single addrs type for "absolute provider instance" --
|
|
||||||
// [addrs.AbsProviderInstance] is a misnomer and should really be named
|
|
||||||
// [addrs.ConfigProviderConfig] or similar -- and we also haven't got
|
|
||||||
// support for resolving provider instance references in package
|
|
||||||
// [configgraph] anyway.
|
|
||||||
//
|
|
||||||
// We need to model provider instances directly, rather than just
|
|
||||||
// using the resource instance relationships as a proxy, because
|
|
||||||
// the planning phase also needs to ask providers to plan "orphaned"
|
|
||||||
// resource instances that are only tracked in state and so this
|
|
||||||
// eval package cannot take them into account when figuring out
|
|
||||||
// which resource instances belong to a particular provider instance.
|
|
||||||
// (The orphan-to-provider-instance relationships are tracked in the
|
|
||||||
// state, rather than in the config.)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProviderInstanceUsers struct {
|
type ProviderInstanceUsers struct {
|
||||||
|
|||||||
@@ -79,12 +79,24 @@ func TestPrepare_ephemeralResourceUsers(t *testing.T) {
|
|||||||
# ephemeral.foo.b[count.index],
|
# ephemeral.foo.b[count.index],
|
||||||
# ]
|
# ]
|
||||||
}
|
}
|
||||||
|
provider "foo" {
|
||||||
|
alias = "other"
|
||||||
|
|
||||||
|
name = ephemeral.foo.a[0].name
|
||||||
|
}
|
||||||
`),
|
`),
|
||||||
}),
|
}),
|
||||||
Providers: ProvidersForTesting(map[addrs.Provider]*providers.GetProviderSchemaResponse{
|
Providers: ProvidersForTesting(map[addrs.Provider]*providers.GetProviderSchemaResponse{
|
||||||
addrs.MustParseProviderSourceString("test/foo"): {
|
addrs.MustParseProviderSourceString("test/foo"): {
|
||||||
Provider: providers.Schema{
|
Provider: providers.Schema{
|
||||||
Block: &configschema.Block{},
|
Block: &configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"name": {
|
||||||
|
Type: cty.String,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
EphemeralResources: map[string]providers.Schema{
|
EphemeralResources: map[string]providers.Schema{
|
||||||
"foo": {
|
"foo": {
|
||||||
@@ -153,9 +165,16 @@ func TestPrepare_ephemeralResourceUsers(t *testing.T) {
|
|||||||
Provider: addrs.MustParseProviderSourceString("test/foo"),
|
Provider: addrs.MustParseProviderSourceString("test/foo"),
|
||||||
},
|
},
|
||||||
}.Instance(addrs.NoKey)
|
}.Instance(addrs.NoKey)
|
||||||
|
providerOtherInstAddr := addrs.AbsProviderConfigCorrect{
|
||||||
|
Module: addrs.RootModuleInstance,
|
||||||
|
Config: addrs.ProviderConfigCorrect{
|
||||||
|
Provider: addrs.MustParseProviderSourceString("test/foo"),
|
||||||
|
Alias: "other",
|
||||||
|
},
|
||||||
|
}.Instance(addrs.NoKey)
|
||||||
|
|
||||||
// The analysis should detect that:
|
// The analysis should detect that:
|
||||||
// - ephemeral.foo.a[0] is used by ephemeral.foo.b[0] and foo.c[0]
|
// - ephemeral.foo.a[0] is used by ephemeral.foo.b[0] and foo.c[0], and by the foo.other provider instance
|
||||||
// - ephemeral.foo.a[1] is used by ephemeral.foo.b[1] and foo.c[1]
|
// - ephemeral.foo.a[1] is used by ephemeral.foo.b[1] and foo.c[1]
|
||||||
// - ephemeral.foo.b[0] is used by only foo.c[0]
|
// - ephemeral.foo.b[0] is used by only foo.c[0]
|
||||||
// - ephemeral.foo.b[1] is used by only foo.c[1]
|
// - ephemeral.foo.b[1] is used by only foo.c[1]
|
||||||
@@ -183,22 +202,28 @@ func TestPrepare_ephemeralResourceUsers(t *testing.T) {
|
|||||||
fooB.Instance(inst0),
|
fooB.Instance(inst0),
|
||||||
fooC.Instance(inst0),
|
fooC.Instance(inst0),
|
||||||
),
|
),
|
||||||
|
ProviderInstances: addrs.MakeSet(
|
||||||
|
providerOtherInstAddr,
|
||||||
|
),
|
||||||
}),
|
}),
|
||||||
addrs.MakeMapElem(fooA.Instance(inst1), EphemeralResourceInstanceUsers{
|
addrs.MakeMapElem(fooA.Instance(inst1), EphemeralResourceInstanceUsers{
|
||||||
ResourceInstances: addrs.MakeSet(
|
ResourceInstances: addrs.MakeSet(
|
||||||
fooB.Instance(inst1),
|
fooB.Instance(inst1),
|
||||||
fooC.Instance(inst1),
|
fooC.Instance(inst1),
|
||||||
),
|
),
|
||||||
|
ProviderInstances: addrs.MakeSet[addrs.AbsProviderInstanceCorrect](),
|
||||||
}),
|
}),
|
||||||
addrs.MakeMapElem(fooB.Instance(inst0), EphemeralResourceInstanceUsers{
|
addrs.MakeMapElem(fooB.Instance(inst0), EphemeralResourceInstanceUsers{
|
||||||
ResourceInstances: addrs.MakeSet(
|
ResourceInstances: addrs.MakeSet(
|
||||||
fooC.Instance(inst0),
|
fooC.Instance(inst0),
|
||||||
),
|
),
|
||||||
|
ProviderInstances: addrs.MakeSet[addrs.AbsProviderInstanceCorrect](),
|
||||||
}),
|
}),
|
||||||
addrs.MakeMapElem(fooB.Instance(inst1), EphemeralResourceInstanceUsers{
|
addrs.MakeMapElem(fooB.Instance(inst1), EphemeralResourceInstanceUsers{
|
||||||
ResourceInstances: addrs.MakeSet(
|
ResourceInstances: addrs.MakeSet(
|
||||||
fooC.Instance(inst1),
|
fooC.Instance(inst1),
|
||||||
),
|
),
|
||||||
|
ProviderInstances: addrs.MakeSet[addrs.AbsProviderInstanceCorrect](),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -343,6 +368,7 @@ func TestPrepare_crossModuleReferences(t *testing.T) {
|
|||||||
ResourceInstances: addrs.MakeSet(
|
ResourceInstances: addrs.MakeSet(
|
||||||
fooB.Instance(addrs.NoKey),
|
fooB.Instance(addrs.NoKey),
|
||||||
),
|
),
|
||||||
|
ProviderInstances: addrs.MakeSet[addrs.AbsProviderInstanceCorrect](),
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ package configgraph
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"iter"
|
||||||
|
|
||||||
"github.com/apparentlymart/go-workgraph/workgraph"
|
"github.com/apparentlymart/go-workgraph/workgraph"
|
||||||
"github.com/hashicorp/hcl/v2"
|
"github.com/hashicorp/hcl/v2"
|
||||||
@@ -117,6 +118,29 @@ func (p *ProviderInstance) ConfigValue(ctx context.Context) (cty.Value, tfdiags.
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResourceInstanceDependencies returns a sequence of any resource instances
|
||||||
|
// whose results the configuration of this provider instance depends on.
|
||||||
|
//
|
||||||
|
// The result of this is trustworthy only if [ProviderInstance.CheckAll]
|
||||||
|
// returns without diagnostics. If errors are present then the result is
|
||||||
|
// best-effort but likely to be incomplete.
|
||||||
|
func (p *ProviderInstance) ResourceInstanceDependencies(ctx context.Context) iter.Seq[*ResourceInstance] {
|
||||||
|
// FIXME: This should also take into account:
|
||||||
|
// - explicit dependencies in the depends_on argument
|
||||||
|
// - ....anything else?
|
||||||
|
//
|
||||||
|
// We should NOT need to take into account dependencies of the parent
|
||||||
|
// provider config's InstanceSelector because substitutions of
|
||||||
|
// count.index/each.key/each.value will transfer those in automatically by
|
||||||
|
// the RepetitionData values being marked.
|
||||||
|
|
||||||
|
// We ignore diagnostics here because callers should always perform a
|
||||||
|
// CheckAll tree walk, including a visit to this provider instance object,
|
||||||
|
// before trusting anything else that any configgraph nodes report.
|
||||||
|
resultVal := diagsHandledElsewhere(p.ConfigValue(ctx))
|
||||||
|
return ContributingResourceInstances(resultVal)
|
||||||
|
}
|
||||||
|
|
||||||
// ValueSourceRange implements exprs.Valuer.
|
// ValueSourceRange implements exprs.Valuer.
|
||||||
func (p *ProviderInstance) ValueSourceRange() *tfdiags.SourceRange {
|
func (p *ProviderInstance) ValueSourceRange() *tfdiags.SourceRange {
|
||||||
// TODO: Does it make sense to return the source range of the provider
|
// TODO: Does it make sense to return the source range of the provider
|
||||||
|
|||||||
@@ -144,6 +144,18 @@ type CompiledModuleInstance interface {
|
|||||||
// resource.
|
// resource.
|
||||||
ResourceInstancesForResource(ctx context.Context, addr addrs.Resource) iter.Seq[*configgraph.ResourceInstance]
|
ResourceInstancesForResource(ctx context.Context, addr addrs.Resource) iter.Seq[*configgraph.ResourceInstance]
|
||||||
|
|
||||||
|
// ProviderInstances returns a sequence of all of the provider instances
|
||||||
|
// declared in the module.
|
||||||
|
//
|
||||||
|
// The set of provider instances can be decided dynamically based on
|
||||||
|
// references to other objects, and so reads from the returned sequence
|
||||||
|
// may block until the needed upstream objects have finished resolving.
|
||||||
|
//
|
||||||
|
// Some of the enumerated objects might be placeholders for zero or more
|
||||||
|
// instances where there isn't yet enough information to determine exactly
|
||||||
|
// which dynamic instances are declared.
|
||||||
|
ProviderInstances(ctx context.Context) iter.Seq[*configgraph.ProviderInstance]
|
||||||
|
|
||||||
// ProviderInstance returns the [configgraph.ProviderInstance]
|
// ProviderInstance returns the [configgraph.ProviderInstance]
|
||||||
// representation of the provider instance with the given address, or
|
// representation of the provider instance with the given address, or
|
||||||
// nil if there is no such instance declared.
|
// nil if there is no such instance declared.
|
||||||
@@ -251,6 +263,27 @@ func ResourceInstancesDeep(ctx context.Context, root CompiledModuleInstance) ite
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProviderInstancesDeep produces all of the provider instances across the given
|
||||||
|
// root module instance and all of its descendents.
|
||||||
|
//
|
||||||
|
// The decision about which instances exist can be made dynamically by arbitrary
|
||||||
|
// expressions, so any step in the returned sequence may block until further
|
||||||
|
// information becomes available.
|
||||||
|
//
|
||||||
|
// This is implemented in terms of [ModuleInstancesDeep].
|
||||||
|
func ProviderInstancesDeep(ctx context.Context, root CompiledModuleInstance) iter.Seq[*configgraph.ProviderInstance] {
|
||||||
|
ctx = grapheval.ContextWithNewWorker(ctx)
|
||||||
|
return func(yield func(*configgraph.ProviderInstance) bool) {
|
||||||
|
for _, moduleInst := range ModuleInstancesDeep(ctx, root) {
|
||||||
|
for providerInst := range moduleInst.ProviderInstances(ctx) {
|
||||||
|
if !yield(providerInst) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ProviderInstance digs through the tree of module instances with the given
|
// ProviderInstance digs through the tree of module instances with the given
|
||||||
// root to try to find the [configgraph.ProviderInstance] representation
|
// root to try to find the [configgraph.ProviderInstance] representation
|
||||||
// of the provider instance with the given address.
|
// of the provider instance with the given address.
|
||||||
|
|||||||
@@ -165,6 +165,20 @@ func (c *CompiledModuleInstance) ChildModuleInstancesForCall(ctx context.Context
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ProviderInstances implements evalglue.CompiledModuleInstance.
|
||||||
|
func (c *CompiledModuleInstance) ProviderInstances(ctx context.Context) iter.Seq[*configgraph.ProviderInstance] {
|
||||||
|
ctx = grapheval.ContextWithNewWorker(ctx)
|
||||||
|
return func(yield func(*configgraph.ProviderInstance) bool) {
|
||||||
|
for _, node := range c.providerConfigNodes {
|
||||||
|
for _, compiled := range node.Instances(ctx) {
|
||||||
|
if !yield(compiled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ProviderInstance implements evalglue.CompiledModuleInstance.
|
// ProviderInstance implements evalglue.CompiledModuleInstance.
|
||||||
func (c *CompiledModuleInstance) ProviderInstance(ctx context.Context, addr addrs.ProviderInstanceCorrect) *configgraph.ProviderInstance {
|
func (c *CompiledModuleInstance) ProviderInstance(ctx context.Context, addr addrs.ProviderInstanceCorrect) *configgraph.ProviderInstance {
|
||||||
localName, ok := c.providerLocalNames[addr.Config.Provider]
|
localName, ok := c.providerLocalNames[addr.Config.Provider]
|
||||||
@@ -221,24 +235,6 @@ func (c *CompiledModuleInstance) ResourceInstancesForResource(ctx context.Contex
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProviderInstancesDeep implements evalglue.CompiledModuleInstance.
|
|
||||||
func (c *CompiledModuleInstance) ProviderInstancesDeep(ctx context.Context) iter.Seq[*configgraph.ProviderInstance] {
|
|
||||||
return func(yield func(*configgraph.ProviderInstance) bool) {
|
|
||||||
for _, r := range c.providerConfigNodes {
|
|
||||||
// NOTE: r.Instances will block if the provider config's
|
|
||||||
// [InstanceSelector] depends on other parts of the configuration
|
|
||||||
// that aren't yet ready to produce their value.
|
|
||||||
for _, inst := range r.Instances(ctx) {
|
|
||||||
if !yield(inst) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Collect provider instances from child module calls too.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AnnounceAllGraphevalRequests implements evalglue.CompiledModuleInstance.
|
// AnnounceAllGraphevalRequests implements evalglue.CompiledModuleInstance.
|
||||||
func (c *CompiledModuleInstance) AnnounceAllGraphevalRequests(announce func(workgraph.RequestID, grapheval.RequestInfo)) {
|
func (c *CompiledModuleInstance) AnnounceAllGraphevalRequests(announce func(workgraph.RequestID, grapheval.RequestInfo)) {
|
||||||
c.moduleInstanceNode.AnnounceAllGraphevalRequests(announce)
|
c.moduleInstanceNode.AnnounceAllGraphevalRequests(announce)
|
||||||
|
|||||||
Reference in New Issue
Block a user