mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Merge pull request #30916 from hashicorp/jbardin/speculative-apply-graph
core: build a test apply graph during plan
This commit is contained in:
commit
1252f8c9c5
@ -204,9 +204,25 @@ The -target option is not for routine use, and is provided only for exceptional
|
||||
diags = diags.Append(rDiags)
|
||||
|
||||
plan.RelevantAttributes = relevantAttrs
|
||||
diags = diags.Append(c.checkApplyGraph(plan, config))
|
||||
|
||||
return plan, diags
|
||||
}
|
||||
|
||||
// checkApplyGraph builds the apply graph out of the current plan to
|
||||
// check for any errors that may arise once the planned changes are added to
|
||||
// the graph. This allows terraform to report errors (mostly cycles) during
|
||||
// plan that would otherwise only crop up during apply
|
||||
func (c *Context) checkApplyGraph(plan *plans.Plan, config *configs.Config) tfdiags.Diagnostics {
|
||||
if plan.Changes.Empty() {
|
||||
log.Println("[DEBUG] no planned changes, skipping apply graph check")
|
||||
return nil
|
||||
}
|
||||
log.Println("[DEBUG] building apply graph to check for errors")
|
||||
_, _, diags := c.applyGraph(plan, config, true)
|
||||
return diags
|
||||
}
|
||||
|
||||
var DefaultPlanOpts = &PlanOpts{
|
||||
Mode: plans.NormalMode,
|
||||
}
|
||||
|
@ -3096,3 +3096,60 @@ data "test_object" "a" {
|
||||
_, diags := ctx.Plan(m, state, DefaultPlanOpts)
|
||||
assertNoErrors(t, diags)
|
||||
}
|
||||
|
||||
func TestContext2Plan_applyGraphError(t *testing.T) {
|
||||
m := testModuleInline(t, map[string]string{
|
||||
"main.tf": `
|
||||
resource "test_object" "a" {
|
||||
}
|
||||
resource "test_object" "b" {
|
||||
depends_on = [test_object.a]
|
||||
}
|
||||
`,
|
||||
})
|
||||
|
||||
p := simpleMockProvider()
|
||||
|
||||
// Here we introduce a cycle via state which only shows up in the apply
|
||||
// graph where the actual destroy instances are connected in the graph.
|
||||
// This could happen for example when a user has an existing state with
|
||||
// stored dependencies, and changes the config in such a way that
|
||||
// contradicts the stored dependencies.
|
||||
state := states.NewState()
|
||||
root := state.EnsureModule(addrs.RootModuleInstance)
|
||||
root.SetResourceInstanceCurrent(
|
||||
mustResourceInstanceAddr("test_object.a").Resource,
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectTainted,
|
||||
AttrsJSON: []byte(`{"test_string":"a"}`),
|
||||
Dependencies: []addrs.ConfigResource{mustResourceInstanceAddr("test_object.b").ContainingResource().Config()},
|
||||
},
|
||||
mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
|
||||
)
|
||||
root.SetResourceInstanceCurrent(
|
||||
mustResourceInstanceAddr("test_object.b").Resource,
|
||||
&states.ResourceInstanceObjectSrc{
|
||||
Status: states.ObjectTainted,
|
||||
AttrsJSON: []byte(`{"test_string":"b"}`),
|
||||
},
|
||||
mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
|
||||
)
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
_, diags := ctx.Plan(m, state, &PlanOpts{
|
||||
Mode: plans.NormalMode,
|
||||
})
|
||||
if !diags.HasErrors() {
|
||||
t.Fatal("cycle error not detected")
|
||||
}
|
||||
|
||||
msg := diags.ErrWithWarnings().Error()
|
||||
if !strings.Contains(msg, "Cycle") {
|
||||
t.Fatalf("no cycle error found:\n got: %s\n", msg)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user