Commit Graph

184 Commits

Author SHA1 Message Date
Martin Atkins
2e09323694 engine/applying: First somewhat-working implementation
This is just enough to connect all of the apply engine parts together into
a functional whole which can at least attempt to apply a plan. There are
various gaps here that make this not fully work in practice, but the goal
of this commit is to just get all of the components connected together in
their current form and then subsequent commits will gradually build on this
to make it work better.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-01-16 09:50:36 -08:00
Martin Atkins
4e07f8949c lang/eval: Populate DesiredResourceInstance.RequiredResourceInstances
The dependencies of a resource instance are based primarily on the marks
in the configuration value, and so we'll decide them as part of the same
function that decides the configuration value.

We then need to propagate that result through all of the intermediate
layers to the code that actually constructs the DesiredResourceInstance
object, at which point it's then available to the planning engine. In a
future commit the planning engine will use it to record the resource
instance dependencies explicitly in the generated execution graph.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-01-16 09:50:36 -08:00
Martin Atkins
1792e85e0b configgraph: ResourceInstance.Value should use grapheval.Once
In e879e9060f I introduced the manually-written memoization that was
previously here, as a quick fix for a problem I noticed while working on
something else.

However, using sync.Mutex directly here is not correct because it makes it
possible for this function to deadlock if used incorrectly elsewhere in the
system. The "grapheval" utilities are there to avoid exactly that by making
a call fail immediately with an error if a self-dependency problem arises.

Therefore this will now use grapheval.Once to better encapsulate the
memoization. This both ensures that we cooperate well with the rest of the
system using grapheval and requires less boilerplate inside the Value
method because grapheval.Once is already tailored for exactly this sort of
memoization.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-01-16 07:45:10 -08:00
Martin Atkins
e879e9060f configgraph: Only ask ResourceInstanceGlue once for each value
Most of what we interact with in configgraph is other parts of the
evaluator that automatically get memoized by patterns like OnceValuer, but
the value for a resource instance is always provided by something outside
of the evaluator that won't typically be able to use those mechanisms, and
so the evaluator's ResourceInstance.Value implementation will now provide
memoization on behalf of that external component, to ensure that we end
up with only one value for each resource instance regardless of how that
external component behaves.

In the case of the current planning phase, in particular this means that
we'll now only try to plan each resource instance once, whereas before
we would ask it to make a separate plan for each call to Value.

For now this is just retrofitted in an minimally-invasive way as part of
our "walking skeleton" phase where we're just trying to wire the existing
parts together end-to-end and then decide at the end whether we want to
refactor things more. If this need for general-purpose memoization ends
up appearing in other places too then maybe we'll choose to structure this
a little differently.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-12-12 07:03:52 -08:00
Christian Mesh
ffc9c4d556 Split out provider schemas vs instances in new engine (#3530)
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
2025-12-01 13:09:58 -05:00
Martin Atkins
102449c9ec backend/local: Use experimental runtime for planning, when enabled
This is a relatively uninteresting milestone where it's possible to load
and plan a root module that contains nothing except local values and
output values.

The module loader currently supports only local sources and the plugin
APIs just immediately return errors, so configurations more complicated
than that are likely to just fail immediately with one or more errors.
We'll gradually improve on this in later commits.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-11-21 09:20:59 -08:00
Martin Atkins
02f48f7b32 lang/eval: Re-export evalglue.UncompiledModule
We currently re-export evalglue.ExternalModules, but the method in that
interface requires returning evalglue.UncompiledModule and so we need to
export that too or else it's impossible to actually implement the interface
from outside this family of packages.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-11-21 09:20:59 -08:00
Andrei Ciobanu
481798ab36 Unify core functions address handling (#3445)
Signed-off-by: Andrei Ciobanu <andrei.ciobanu@opentofu.org>
2025-10-31 08:41:52 +02:00
Andrei Ciobanu
af43817e57 Extract the provider functions references from the dynamic blocks (#3429)
Signed-off-by: Andrei Ciobanu <andrei.ciobanu@opentofu.org>
2025-10-28 17:10:07 +02:00
Martin Atkins
639dff15c3 lang/eval: Clear some linter warnings
Based on discussion so far it seems that we have consensus about merging
the lang/eval and engine-related commits that precede this one as a
starting point for ongoing work in future (hopefully smaller) PRs.

This commit therefore mostly just acknowledges a small amount of unused
code in a way that it won't cause linter noise, since the future work is
likely to make these be used and so it'll be helpful to have them around as
examples to build on. (Which is, after all, the whole point of merging this
as dead code in the first place!)

This does _actually_ remove an old, now-redundant declaration of the
"compiled module instance" interface though, since that eventually ended
up in a different place and I just forgot to clean up this initial form
of it in later refactoring.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
4218231438 execgraph: Most of the "compiler" machinery
This covers most of the logic required to turn a source graph into a
compiled graph ready for execution. There's currently only support for one
of the opcodes though, so subsequent commits will sketch those out more
and then add some tests and fix any problems that inevitably exist here but
aren't yet visible because there are no tests.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
b6cdfd1afa lang/eval: Bind PlanningOracle to PlanGlue
Previously the PlanGlue methods all took PlanningOracle pointers as one
of their arguments, which is annoying since all of them should end up with
pointers to the same object and it makes it hard for the PlanGlue
implementation to do any work outside of and between the PlanGlue method
calls.

Instead then we'll have DrivePlanning take a function for building a
PlanGlue implementation given a PlanningOracle pointer, and then the
planning engine returns an implementation that binds a planContext to a
PlanningOracle it can then use to do all of its work.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
4772944e91 engine/planning: Provider instances and stub of data resource planning
This is a sketch of starting and configuring provider instances once they
are needed and then closing them once they are no longer needed. The
stubby implementation of data resource instance planning is there mainly
just to illustrate how this is intended to work, but it's not yet complete
enough to actually function.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
4ea51e4faf 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>
2025-10-27 10:15:41 -07:00
Martin Atkins
6b9cebaf8b engine/planning: Find "orphan" resource instances
The eval system gradually reports the "desired" declarations at various
different levels of granularity, and so the planning engine should compare
that with the instances in the previous run state to notice when any
existing resource instance is no longer in the desired state.

This doesn't yet include a real implementation of planning the deletion of
such resource instances. Real planning behaviors depend on us having some
sort of provider instance and ephemeral resource instance manager to be
able to make requests to configured providers, so that will follow in
subsequent commits before we can implement the actual planning behaviors.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
a82da0f674 lang/eval: Call PlanGlue.Plan*Orphans methods
In a new async recursive tree walk we'll gradually traverse the tree of
module calls and announce the desired state objects at each level, so that
the PlanGlue implementation can then compare that with its own record of
the prior state to determine by omission which resource instances are
"orphans" that should be planned for deletion.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
450fe5c7ab lang/eval: More stubbing for PlanGlue.Plan*Orphans calls
This puts the infrastructure in place to allow us to notify the PlanGlue
implementation about what's declared in the configuration so it can detect
orphaned resource instances by omission, but stops short of actually doing
that because it'll require another concurrent recursive tree walk which
is likely to be complex enough to deserve to live in its own later commit.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
557026c5db lang/eval: A different approach to "orphan" resource instances
Previously this had the idea that the planning engine would work in two
subphases where first it would deal with the desired state (decided by
evaluating the configuration) and then it would do a second pass to plan
to delete any resource instances that didn't show up in the desired state.

Although that would technically work, we need to keep configured provider
instances open for the whole timespan between planning the first
desired resource instance and the final orphan instance using each
provider, and so splitting into two phases effectively means we'd have
provider instances sitting open but idle waiting for the desired state
subphase to complete to learn if they are needed for the orphan subphase.

This commit stubs out (but does not yet implement) a new approach where
the orphan planning can happen concurrently with the desired state
planning. This works by having the evaluator gradually describe to the
planning engine which module calls, module call instances, resources, and
resource instances _are_ present in the desired state -- as soon as that
information becomes available -- so that the planning engine can notice
by omission what subset of the prior state resource instances are no longer
desired and are therefore orphans.

This means that the planning engine should now have all of the information
it needs to recognize when a provider can be closed: the PlanningOracle
reports which _desired_ resource instances (or placeholders thereof) are
expected to need each provider instance, and the prior state tracks
exactly which provider instance each prior resource instance was associated
with, and so a provider's work is finished once all instances in the union
of those two sets have had their action planned.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
7e5378bea2 lang/eval: Expose EvalContext for caller use
Although our EvalContext API is primarily focused on what the eval system
needs, it's desirable for higher-level subsystems that wrap the evaluator
to be able to guarantee they are using the same provider and other
dependencies, so we'll expose the EvalContext that each ConfigInstance is
associated with in the public API.

The Providers interface now also grows to include NewConfiguredProvider as
a way to instantiate a configured provider instance. The eval system
doesn't need that itself, but exposing it here means that a caller can be
confident that they are definitely instantiating and configuring the same
provider that the eval system had previously used to validate the
configuration.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
eb1d686806 lang/eval: PlanningOracle methods have their own workgraph workers
As was already noted in a comment in the PlanningOracle declaration, the
PlanningOracle API is an exported entrypoint usable from outside lang/eval
and so each exported method should establish its own workgraph worker to
make sure it's handling those concerns correctly regardless of how the
caller chooses to use them.

For example, a caller might choose to make concurrent calls into the oracle
from multiple goroutines, which would violate the assumption that each
concurrently-running codepath is associated with a separate workgraph
worker.

Even if a caller is managing their own workgraph workers in the expected
way, this is functionally harmless and relatively cheap.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
7cab34815a lang/eval: PlanningOracle.AwaitResourceInstancesCompletion
This is a building block for use by a planning engine implemented elsewhere
in OpenTofu so that it can be notified (by the completion of a call to the
function) when all resource instances that use a particular provider
instance or ephemeral resource instance have completed their plan-time
evaluation work.

Existing methods of PlanningOracle already expose sets of addresses to
wait for, but this method is separate from those because the planning
engine also needs the dependency information as data to know when an
ephemeral resource instance might need to be left open beyond the part of
the planning process driven by lang/eval in order to plan the delete
actions for any "orphan" resource instances that are in the prior state
but no longer declared in the configuration.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
ab6af47df7 lang/eval: Move PlanningOracle to its own file
Continuing to split up some of the source files that have grown long over
ongoing experimentation, this is the start of splitting the config_plan.go
file into smaller parts.

For now only PlanningOracle gets its own file, but other types related to
planning might follow in later commits.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
52a27d1865 lang/eval: PlanningOracle can now return a provider instance config
This was the big known missing piece for making a "real" implementation
of planning using this new package, so that the planning engine can know
what configuration to use when preparing a configured provider instance
for planning requests.

I'm sure there are other missing pieces that I've just not learned about
yet, but this is still a good checkpoint to stop iterating on lang/eval
for a little while and start stubbing out the planning engine.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
db8af25531 lang/eval: Rework how module calls work
The previous approach had the problem that we only had the
CompiledModuleInstance object for a child module briefly in a local
variable while evaluating the child call, which is sufficient for just
evaluation but is not enough to support full config tree traversal for
questions like "what resource instances are declared throughout the
configuration?" and "what configgraph node corresponds to this provider
configuration instance address?"

This moves the separation of concerns around a little to be perhaps more
like how resource instances work, where the ModuleCallInstance.Value
method just wraps a function provided by the "compile" layer which then
takes the config value and compiles the child module instance.

This means that the "compile" layer can then hold on to the
CompiledModuleInstance object for the child module instance as part of
the "glue" in the ModuleCallInstance object and then use it to deal with
the config-tree-traversing methods in its own CompiledModuleInstance
implementation.

The new test of ConfigInstance.PrepareToPlan in lang/eval illustrates that
the system is now finally able to walk the whole configuration tree to find
resource instances and the provider instances they depend on.

(The handling of inheritance of providers between parent and child module
instances is still not working like the current system does, because the
"providers sidechannel" mechanism remains incomplete.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
372501516c lang/eval/internal/tofu2024: Split up compile.go
This file was originally in lang/eval directly and so it had all of the
compile-related functions grouped together to separate them from everything
else in that package.

Now that the "compile" process has its own package we have more room to
have many smaller files instead of one huge file, making this a little
easier to navigate now that the logic has grown.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
af3bd79a03 lang/eval: Work-in-progress changes to modeling of module tree
Exposing only "ResourceInstancesDeep" on CompiledModuleInstance seemed
attractive at first when it was only partially implemented anyway, but
trying to finish implementing it revealed that the current design couldn't
actually support that API because we don't currently keep the
CompiledModuleInstance object for a child module instance after we've used
it to evaluate the child module instance's output values.

This commit doesn't actually solve that problem, but it does rework the
CompiledModuleInstance API so that the problem of finding and enumerating
child module instances is separated from the problem of finding resource
instances in just the current module instance, and then I'll try to find
a good way to satisfy this new, slightly-generalized API in future commits.

(The ultimate goal here is to be able to enumerate all of the resource
instances throughout the configuration for dependency-tracking purposes,
and to be able to find a specific ProviderInstance object given its
absolute address so that the plan or apply engine can request the
configuration value to configure the provider with.)

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
a532c43eb1 lang/eval: Evaluation of provider configurations
This introduces the "compiler" changes and the configgraph changes to let
us evaluate the configuration for a particular provider instance.

A future commit will expose this through the public API of lang/eval so
that other systems like the planning engine will be able to ask for the
configuration for whatever provider instance a given resource instance
belongs to.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
ffe8a79658 lang/eval: Slightly more complete handling of provider configurations
This is still not completely true to how current OpenTofu behaves, but it's
at least a little closer and avoids hard-coding a specific fixed provider
address in favor of actually following what's declared in the
configuration.

The system doesn't actually support evaluating the configuration for a
provider instance yet, so this remains just a placeholder for the
relatively simple tests in this package. Support for actually evaluating
the provider configurations will follow in later commits.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
635b444c8e lang/eval: Some deeper stubbing out of the DrivePlanning design
This is a little too busy/complicated for my liking but it's a place to
start and hopefully we'll be able to cut this down a little after we see
how the planning engine implementation turns out.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
64ad4cc66a lang/eval: Some initial support for child module calls
The way this all fits together doesn't feel quite right yet since there's
a bunch of wrapping layers that aren't really adding anything except
indirection between the packages, but this is at least a starting point
for child module support.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
b06287111b lang/eval: Temporary shimmed support for our core functions
This borrows the function table preparation from our previous lang.Scope
API (which this experiment would, if successful, ultimately replace) and
also intentionally implements the exprs.Evalable interface a little
improperly for now in the hope that we'll update our HCL patch for
function inspections to use hcl.StaticCall instead of misusing
hcl.Traversal at some point in the future.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
1a7dd21fc2 lang/evalglue: Different approach to EvalContext defaults for tests
In other packages we've tried being pragmatic by making the main code
compensate for missing arguments in older tests, so that we don't always
need to update every test across the whole system every time we add a new
feature.

However, that compromise unfortunately tends to lead to non-test code
inadvertently relying on the compensations too, so this is an attempt at
a slightly different compromise where the main code will panic if called
incorrectly but there's an extra helper function for tests to fill in
reasonable "inert" defaults for the fields they leave unpopulated.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
3a31ce9c28 configgraph: ContributingResourceInstances uses ValueMarksOfTypeDeep
Recent changes in cty have included some new functions for working with
marks more efficiently. This particular function allows us to iterate over
the deep marks on a value without creating a new unmarked copy of the
value and without building an intermediate map of marks.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
352e23e8f3 lang/eval: A benchmark and a note about a hot path
This also further highlighted that the marks-related codepaths in cty have
lots of room for performance improvement. In particular, we're spending
a lot of time in cty.Value.UnmarkDeep rebuilding values that actually
don't need to change and could likely get some benefit from rewriting
that to use the recently-added WrangleMarksDeep that is able to avoid
constructing new values for parts of the tree where no marks are changing.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
c64b02a1af lang/eval: Start of supporting module calls
This stops short of actually implementing the evaluation of a module call,
but gets the compilation behavior in place and the usual boilerplate for
handling multi-instance module calls.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
a277aafff9 configgraph: Some unit tests for some "Value" methods
A big part of the motivation of the design of configgraph vs. package tofu
was to keep each node type self-contained and avoid the need for a big
shared "god object" that everything interacts with, and in turn part of
the reason to be interested in that is because it should makes it actually
practical and useful to write unit tests for these node types.

This commit introduces a few unit tests mainly just as a proof-of-concept
to see if the design has lived up to the ambition. So far that seems to be
true, although I'll reserve judgement until there's at least some coverage
on every on of the node types in this package.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
49780f47ca tofu2024: compileModuleProvidersSideChannel doesn't need module instance
Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
9bf2516f68 lang/eval: Start of splitting the "compile" layer into its own package
During early experiments the logic for "compiling" a configs.Module into
a bunch of configgraph node objects was just a bunch of unexported
functions directly in package eval, because it wasn't clear yet what
abstraction would make most sense between those two.

Now that this has solidified a little more this commit is a first attempt
at drawing a line between packages implementing "compilation" for specific
editions of the language (for which there is currently only one, which
I've named "tofu2024" as a placeholder because Terraform's only existing
language was similarly called "TF2021") and the top-level evaluation
package that tries to be as edition-agnostic as possible, although it is
currently still the one responsible for deciding to call into tofu2024.

There doesn't seem much point in abstracting away the decision about what
edition to use until we actually have a plan to introduce a new edition
and therefore have some idea of what the design constraints for that might
turn out to be.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
16f31733eb lang/eval: instance local scope is now a "compiler" concern
Completing the reorganization of all of the scope-shape-related decisions
into the "compiler" code instead of package configgraph, this now deals
with the scope that contains the each.key/each.value/count.index local
symbols.

Package configgraph now no longer knows anything about the shape of the
scopes except for a few pragmatic mentions of specific conventions in
error messages where it doesn't seem worth the complexity to try to
organize that differently until we know concretely what problem we're
trying to solve.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
4703a3baf9 lang/exprs: "compilation" of CheckRule
The pattern of having the "compiler" logic in lang/exprs always deal with
building suitable exprs.Valuers and configgraph just evaluating whatever
it was given has been working out in other contexts like the resource to
resource instance journey, so we'll follow that pattern for CheckRule too:

If the evaluation of a CheckRule depends on some value that's generated
by logic in configgraph then configgraph has a callback to ask the
"compiler" layer to dynamically construct zero or more CheckRule objects
that are pre-bound to the relevant value. For checks that only rely on
the global scope, such as output value preconditions, they are still
precompiled into a static slice in the parent object.

This also continues the progress towards all of the "expression scopes"
living with the "compiler" code instead of in configgraph, so that in
future different modules can potentially be bound to differently-shaped
scopes as we continue to evolve the language.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
14c2f398d3 lang/eval: module instance scope no longer in configgraph
On reflection it seems inconsistent that the "compile" logic in lang/eval
is responsible for deciding which expressions get evaluated in which scopes
but not for deciding what's actually in the top-level scope, since the
scope contents are something quite likely to vary between language
editions and language experiments.

With that in mind, this separates the idea of the "module instance scope"
from the actual ModuleInstance type, making it instead a wrapper type.
This continues the trend toward the grapheval package being mostly agnostic
to how the surface-level language is designed and instead just modeling
the relationships that are implied by the configuration.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
47d93fc3dc lang/eval: Deciding a provider instance for each resource instance
This introduces the machinery for representing provider configs, provider
instances, and the relationships between resource instances and provider
instances.

Unfortunately there's a lot of accumulated complexity in the rules for
how OpenTofu currently decides which provider configs are available for
use in a module, including lots of implicit behavior for
backward-compatibility, so right now that is stubbed out with just a
hard-coded implementation that puts a "test/foo" provider with local name
"foo" in every module instance. We'll deal with a real implementation of
all that in later commits.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
105a84d24c lang/eval: Reshuffle concerns for resource instances a little
Previously the way this was dealing with "compiling" resource instances
once we've determined the instance keys was a little inconsistent with the
intended architecture because the configgraph.Resource type needed to know
how to pass various bits of context down into each of the instances, but
everywhere else we've tried to keep the configgraph types pretty agnostic
about such details and let the "compiler" layer worry about that.

Now the entire task of "compiling" a resource instance is delegated to
the "compiler" layer using a callback it provides. This makes things easier
because that layer already has all of the resource-level information it
needs available anyway, and so it can just plug that information directly
into the ResourceInstance fields itself in the same way as it builds
other types like ModuleInstance and Resource.

This also introduces an interface type to represent the callback API from
the ResourceInstance itself back to the compiler layer, which means we can
now split config validation and "get result value" into two methods, which
makes the implementation of each of those methods considerably simpler --
they are both now one-liners, and the data flow from validation to
get result value is encapsulated in the configgraph package where it will
be shared between all phases.

Overall the idea here is to make Resource and ResourceInstance independent
of one another so that, as is the goal for everything in configgraph, it's
more practical to write unit tests that don't need to mock out an entire
world just to test one small unit.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
aa4743f23f lang/eval+lang/exprs: More exprs.EvalError markings
It doesn't hurt to be liberal in handling this everywhere that we know
there's an error because applying the mark where it was already present
is effectively a no-op, so we'll introduce more of these both for
robustness and to help folks who are reading this code in future to
learn the EvalError patterns by observation.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
5cb04694eb lang/eval: ConfigInstance.PrepareToPlan analyzes ephemeral resources
There's now enough here to produce a data structure where each ephemeral
resource instance is associated with all of the resource instances that
must complete planning before it can be closed.

This also doubles as the first demonstration of dynamic analysis of
resource instance dependencies using cty marks, with
TestPrepare_ephemeralResourceUsers illustrating the more precise results.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
e092db420e lang/eval: Stub of ConfigInstance.prepareToPlan
This is my current idea for how to resolve the chicken/egg problem of the
planning phase needing to know who uses ephemeral resource instances and
provider instances but us needing to evaluate the configuration to know
those relationships.

This borrows the evaluation behavior previously used for
ConfigInstance.Validate to produce a conservative evaluation of the
configuration without depending on any configured providers or ephemeral
resource instances, which we (in future commits) will use to discover
the relationships between those objects so that the real walk during the
planning phase can know when to start and stop them.

This relies on the idea that an evaluation with values stubbed out as
unknown should produce _at least_ the relationships that would occur with
fewer unknown values, though it might also report additional dependencies
that would vanish once values become more known. This gives the plan phase
something to start with and then we'll learn a tighter set of dependencies
during the planning phase which will form the basis of the apply-time
execution graph.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
cf84d991ff lang/exprs: EvalableHCLBodyWithDynamicBlocks
The HCL "dynblock" extension blurs the distinction between static
structure and dynamic expressions by allowing the use of expressions to
generate zero or more nested blocks.

Because this feature is a bit of a layering violation it requires some
different usage patterns to get the correct result, and so this new
EvalableHCLBodyWithDynamicBlocks aims to encapsulate those details so that
the caller can just treat the result like a normal Evalable.

lang/eval now uses this for the body of a resource configuration, so that
"dynamic" blocks can work in there similarly to how they do in the previous
language runtime.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
0d4fa0e66e lang/eval: ModuleInstance exprs.Scope methods in separate file
The module_instance.go file was getting quite long and hard to navigate,
and the Valuer vs. Scope distinction seems like a reasonable place to
draw a line between two different files related to module instances.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
9d7eb9742e lang/eval: Enough resource support for basic validation to work
Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00
Martin Atkins
507055bc2f lang/eval: Shuffle resource instance result handling a little more
The separation of concerns here still doesn't feel _quite_ right since
a resource instance node needs to know what provider it belongs to even
though that package doesn't otherwise interact with providers directly
at all, but this at least clarifies exactly one location that's responsible
for getting the config value for the result value callback to use.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-10-27 10:15:41 -07:00