From bb8032ba0efffb31b441f7e56d51d9381de93266 Mon Sep 17 00:00:00 2001 From: Matej Risek Date: Fri, 30 Jan 2026 15:02:25 +0100 Subject: [PATCH] WIP --- .../terraform/context_apply_target_test.go | 48 +++++++++++++++++++ internal/terraform/context_plan.go | 3 ++ internal/terraform/graph_builder_plan.go | 7 +++ internal/terraform/transform_targets.go | 19 +++++--- 4 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 internal/terraform/context_apply_target_test.go diff --git a/internal/terraform/context_apply_target_test.go b/internal/terraform/context_apply_target_test.go new file mode 100644 index 0000000000..075e4d3cdc --- /dev/null +++ b/internal/terraform/context_apply_target_test.go @@ -0,0 +1,48 @@ +package terraform + +import ( + "testing" + + "github.com/hashicorp/terraform/internal/addrs" + provider "github.com/hashicorp/terraform/internal/providers" + "github.com/hashicorp/terraform/internal/states" + "github.com/hashicorp/terraform/internal/tfdiags" +) + +func TestContextApply_TargetInstance(t *testing.T) { + testConfig := testModuleInline(t, map[string]string{ + "main.tf": ` + resource "test_object" "foo" { + count = 2 + test_string = join("_", ["foo", count.index]) + } + + resource "test_object" "bar" { + count = 2 + test_string = resource.test_object.foo[count.index].test_string + } +`, + }) + + p := simpleMockProvider() + ctx := testContext2( + t, + &ContextOpts{ + Providers: map[addrs.Provider]provider.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }, + ) + + testTarget := mustResourceInstanceAddr("test_object.bar[0]") + planOptions := DefaultPlanOpts + planOptions.Targets = []addrs.Targetable{ + testTarget, + } + planOptions.SafeTargeting = true + pr, diags := ctx.Plan(testConfig, states.NewState(), planOptions) + tfdiags.AssertNoErrors(t, diags) + + _ = pr.Changes.Resources + t.Log("test") +} diff --git a/internal/terraform/context_plan.go b/internal/terraform/context_plan.go index d6861f8fc2..73e613ef6c 100644 --- a/internal/terraform/context_plan.go +++ b/internal/terraform/context_plan.go @@ -67,6 +67,8 @@ type PlanOpts struct { // warnings as part of the planning result. Targets []addrs.Targetable + SafeTargeting bool + // ActionTargets represents the actions that should be triggered by this // execution. This is incompatible with the `Targets` attribute, only one // can be set. Also, Mode must be plans.RefreshOnly when using @@ -1005,6 +1007,7 @@ func (c *Context) planGraph(config *configs.Config, prevRunState *states.State, ExternalProviderConfigs: externalProviderConfigs, Plugins: c.plugins, Targets: opts.Targets, + SafeTargeting: opts.SafeTargeting, ForceReplace: opts.ForceReplace, skipRefresh: opts.SkipRefresh, preDestroyRefresh: opts.PreDestroyRefresh, diff --git a/internal/terraform/graph_builder_plan.go b/internal/terraform/graph_builder_plan.go index 3ef6ae3e18..d6f33b1dd7 100644 --- a/internal/terraform/graph_builder_plan.go +++ b/internal/terraform/graph_builder_plan.go @@ -132,6 +132,9 @@ type PlanGraphBuilder struct { // or test runtimes, where the root modules as Terraform sees them aren't // the actual root modules. AllowRootEphemeralOutputs bool + + // TODO: Add comment + SafeTargeting bool } // See GraphBuilder @@ -303,6 +306,10 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer { // Target &TargetsTransformer{Targets: b.Targets}, + // TODO_SAMS: Check whether we can create a dummy safe target transformer + // which checks references and trims down the produce of targetsTransformer + // down to what instances are actually targeted by the defined target. + // Filter the graph to only include nodes that are relevant to the query operation. &QueryTransformer{queryPlan: b.queryPlan, validate: b.Operation == walkValidate}, diff --git a/internal/terraform/transform_targets.go b/internal/terraform/transform_targets.go index 10fabdae37..e98c78689a 100644 --- a/internal/terraform/transform_targets.go +++ b/internal/terraform/transform_targets.go @@ -197,13 +197,18 @@ func (t *TargetsTransformer) nodeIsTarget(v dag.Vertex, targets []addrs.Targetab return false } -// addVertexDependenciesToTargetedNodes adds dependencies of the targeted vertex to the -// targetedNodes set. This includes all ancestors in the graph. -// It also includes all action trigger nodes in the graph. Actions are planned after the -// triggering node has planned so that we can ensure the actions are only planned if the triggering -// resource has an action (Create / Update) corresponding to one of the events in the action trigger -// blocks event list. -func (t *TargetsTransformer) addVertexDependenciesToTargetedNodes(g *Graph, v dag.Vertex, targetedNodes dag.Set, addrs []addrs.Targetable) { +// addVertexDependenciesToTargetedNodes adds dependencies of the targeted vertex +// to the targetedNodes set. This includes all ancestors in the graph. +// It also includes all action trigger nodes in the graph. Actions are planned +// after the triggering node has planned so that we can ensure the actions are +// only planned if the triggering resource has an action (Create / Update) +// corresponding to one of the events in the action trigger blocks event list. +func (t *TargetsTransformer) addVertexDependenciesToTargetedNodes( + g *Graph, + v dag.Vertex, + targetedNodes dag.Set, + addrs []addrs.Targetable, +) { if targetedNodes.Include(v) { return }