mirror of
https://github.com/opentofu/opentofu.git
synced 2026-02-18 18:17:54 -05:00
Allow ephemeral resource in plan during -refresh-only (#3776)
Signed-off-by: Andrei Ciobanu <andrei.ciobanu@opentofu.org>
This commit is contained in:
parent
70c1ab9be6
commit
3d399a6bb8
3 changed files with 110 additions and 2 deletions
|
|
@ -73,6 +73,23 @@ func (c *Changes) Empty() bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// ActionableResources returns all the [Changes.Resources] that are changes that would actually
|
||||
// update the resources.
|
||||
// This method's main purpose is to exclude from [Changes.Resources] the changes that are
|
||||
// in the plan strictly for building the graph and are not going to change the resource state.
|
||||
// In case of [Open] actions, these are needed to build the required ephemeral nodes
|
||||
// in [DiffTransformer].
|
||||
func (c *Changes) ActionableResources() []*ResourceInstanceChangeSrc {
|
||||
var ret []*ResourceInstanceChangeSrc
|
||||
for _, r := range c.Resources {
|
||||
if r.Action == Open {
|
||||
continue
|
||||
}
|
||||
ret = append(ret, r)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// ResourceInstance returns the planned change for the current object of the
|
||||
// resource instance of the given address, if any. Returns nil if no change is
|
||||
// planned.
|
||||
|
|
|
|||
|
|
@ -386,10 +386,13 @@ func (c *Context) refreshOnlyPlan(ctx context.Context, config *configs.Config, p
|
|||
// to refresh only, the set of resource changes should always be empty.
|
||||
// We'll safety-check that here so we can return a clear message about it,
|
||||
// rather than probably just generating confusing output at the UI layer.
|
||||
if len(plan.Changes.Resources) != 0 {
|
||||
// Because the ephemeral resources changes in the plan are meant to be used
|
||||
// later to build the apply graph, those shouldn't be counted when we are
|
||||
// doing this check.
|
||||
if changes := plan.Changes.ActionableResources(); len(changes) != 0 {
|
||||
// Some extra context in the logs in case the user reports this message
|
||||
// as a bug, as a starting point for debugging.
|
||||
for _, rc := range plan.Changes.Resources {
|
||||
for _, rc := range changes {
|
||||
if depKey := rc.DeposedKey; depKey == states.NotDeposed {
|
||||
log.Printf("[DEBUG] Refresh-only plan includes %s change for %s", rc.Action, rc.Addr)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -2683,6 +2683,94 @@ func TestContext2Plan_refreshOnlyMode(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_refreshOnlyMode_ephemeral(t *testing.T) {
|
||||
addr := mustResourceInstanceAddr("ephemeral.test_object.a")
|
||||
|
||||
// The configuration, the prior state, and the refresh result intentionally
|
||||
// have different values for "test_string" so we can observe that the
|
||||
// refresh took effect but the configuration change wasn't considered.
|
||||
m := testModuleInline(t, map[string]string{
|
||||
"main.tf": `
|
||||
ephemeral "test_object" "a" {
|
||||
arg = "after"
|
||||
}
|
||||
`,
|
||||
})
|
||||
state := states.NewState()
|
||||
|
||||
p := simpleMockProvider()
|
||||
p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
|
||||
Provider: providers.Schema{Block: simpleTestSchema()},
|
||||
EphemeralResources: map[string]providers.Schema{
|
||||
"test_object": {
|
||||
Block: &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"arg": {Type: cty.String, Optional: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
p.OpenEphemeralResourceFn = func(req providers.OpenEphemeralResourceRequest) providers.OpenEphemeralResourceResponse {
|
||||
newVal, err := cty.Transform(req.Config, func(path cty.Path, v cty.Value) (cty.Value, error) {
|
||||
if len(path) == 1 && path[0] == (cty.GetAttrStep{Name: "arg"}) {
|
||||
return cty.StringVal("current"), nil
|
||||
}
|
||||
return v, nil
|
||||
})
|
||||
if err != nil {
|
||||
// shouldn't get here
|
||||
t.Fatalf("OpenResourceFn transform failed")
|
||||
return providers.OpenEphemeralResourceResponse{}
|
||||
}
|
||||
return providers.OpenEphemeralResourceResponse{
|
||||
Result: newVal,
|
||||
}
|
||||
}
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
plan, diags := ctx.Plan(context.Background(), m, state, &PlanOpts{
|
||||
Mode: plans.RefreshOnlyMode,
|
||||
})
|
||||
if diags.HasErrors() {
|
||||
t.Fatalf("unexpected errors\n%s", diags.Err().Error())
|
||||
}
|
||||
|
||||
if !p.OpenEphemeralResourceCalled {
|
||||
t.Errorf("Provider's OpenEphemeralResource wasn't called; should've been")
|
||||
}
|
||||
|
||||
if got, want := len(plan.Changes.Resources), 1; got != want {
|
||||
t.Fatalf("expected to have exactly %d resource but got %d", want, got)
|
||||
}
|
||||
if gotResAddr := plan.Changes.Resources[0].Addr; !gotResAddr.Equal(addr) {
|
||||
t.Errorf("plan contains one resource and that's NOT an ephemeral as expected; instead, got %s", gotResAddr)
|
||||
}
|
||||
if got, want := len(plan.Changes.ActionableResources()), 0; got != want {
|
||||
t.Errorf(
|
||||
"changes.ActionableResources() returned more than %d resources, meaning that didn't exclude ephemeral resources. Instead returned %d\nChanges:\n%s",
|
||||
want,
|
||||
got,
|
||||
spew.Sdump(plan.Changes.Resources),
|
||||
)
|
||||
}
|
||||
|
||||
if instState := plan.PlannedState.ResourceInstance(addr); instState == nil {
|
||||
t.Errorf("%s has no planned state, but it should have since it's needed to build the apply graph correctly", addr)
|
||||
} else {
|
||||
want := `{"arg":"current"}`
|
||||
got := string(instState.Current.AttrsJSON)
|
||||
if diff := cmp.Diff(want, got); diff != "" {
|
||||
t.Fatalf("unexpected attributes for the planned ephemeral:\n%s", diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestContext2Plan_refreshOnlyMode_deposed(t *testing.T) {
|
||||
addr := mustResourceInstanceAddr("test_object.a")
|
||||
deposedKey := states.DeposedKey("byebye")
|
||||
|
|
|
|||
Loading…
Reference in a new issue