mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Merge pull request #32900 from hashicorp/jbardin/target-drift-upgrade
External changes report can fail with schema migrations and `-target`
This commit is contained in:
commit
fdb00b9a46
@ -643,6 +643,12 @@ func (c *Context) planGraph(config *configs.Config, prevRunState *states.State,
|
||||
}
|
||||
}
|
||||
|
||||
// driftedResources is a best-effort attempt to compare the current and prior
|
||||
// state. If we cannot decode the prior state for some reason, this should only
|
||||
// return warnings to help the user correlate any missing resources in the
|
||||
// report. This is known to happen when targeting a subset of resources,
|
||||
// because the excluded instances will have been removed from the plan and
|
||||
// not upgraded.
|
||||
func (c *Context) driftedResources(config *configs.Config, oldState, newState *states.State, moves refactoring.MoveResults) ([]*plans.ResourceInstanceChangeSrc, tfdiags.Diagnostics) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
@ -690,35 +696,35 @@ func (c *Context) driftedResources(config *configs.Config, oldState, newState *s
|
||||
addr.Resource.Resource.Type,
|
||||
)
|
||||
if schema == nil {
|
||||
// This should never happen, but just in case
|
||||
return nil, diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Warning,
|
||||
"Missing resource schema from provider",
|
||||
fmt.Sprintf("No resource schema found for %s.", addr.Resource.Resource.Type),
|
||||
fmt.Sprintf("No resource schema found for %s when decoding prior state", addr.Resource.Resource.Type),
|
||||
))
|
||||
continue
|
||||
}
|
||||
ty := schema.ImpliedType()
|
||||
|
||||
oldObj, err := oldIS.Current.Decode(ty)
|
||||
if err != nil {
|
||||
// This should also never happen
|
||||
return nil, diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Warning,
|
||||
"Failed to decode resource from state",
|
||||
fmt.Sprintf("Error decoding %q from previous state: %s", addr.String(), err),
|
||||
fmt.Sprintf("Error decoding %q from prior state: %s", addr.String(), err),
|
||||
))
|
||||
continue
|
||||
}
|
||||
|
||||
var newObj *states.ResourceInstanceObject
|
||||
if newIS != nil && newIS.Current != nil {
|
||||
newObj, err = newIS.Current.Decode(ty)
|
||||
if err != nil {
|
||||
// This should also never happen
|
||||
return nil, diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Error,
|
||||
diags = diags.Append(tfdiags.Sourceless(
|
||||
tfdiags.Warning,
|
||||
"Failed to decode resource from state",
|
||||
fmt.Sprintf("Error decoding %q from prior state: %s", addr.String(), err),
|
||||
))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1705,6 +1705,60 @@ The -target option is not for routine use, and is provided only for exceptional
|
||||
})
|
||||
}
|
||||
|
||||
func TestContext2Plan_untargetedResourceSchemaChange(t *testing.T) {
|
||||
// an untargeted resource which requires a schema migration should not
|
||||
// block planning due external changes in the plan.
|
||||
addrA := mustResourceInstanceAddr("test_object.a")
|
||||
addrB := mustResourceInstanceAddr("test_object.b")
|
||||
m := testModuleInline(t, map[string]string{
|
||||
"main.tf": `
|
||||
resource "test_object" "a" {
|
||||
}
|
||||
resource "test_object" "b" {
|
||||
}`,
|
||||
})
|
||||
|
||||
state := states.BuildState(func(s *states.SyncState) {
|
||||
s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{
|
||||
AttrsJSON: []byte(`{}`),
|
||||
Status: states.ObjectReady,
|
||||
}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
|
||||
s.SetResourceInstanceCurrent(addrB, &states.ResourceInstanceObjectSrc{
|
||||
// old_list is no longer in the schema
|
||||
AttrsJSON: []byte(`{"old_list":["used to be","a list here"]}`),
|
||||
Status: states.ObjectReady,
|
||||
}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
|
||||
})
|
||||
|
||||
p := simpleMockProvider()
|
||||
|
||||
// external changes trigger a "drift report", but because test_object.b was
|
||||
// not targeted, the state was not fixed to match the schema and cannot be
|
||||
// deocded for the report.
|
||||
p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
|
||||
obj := req.PriorState.AsValueMap()
|
||||
// test_number changed externally
|
||||
obj["test_number"] = cty.NumberIntVal(1)
|
||||
resp.NewState = cty.ObjectVal(obj)
|
||||
return resp
|
||||
}
|
||||
|
||||
ctx := testContext2(t, &ContextOpts{
|
||||
Providers: map[addrs.Provider]providers.Factory{
|
||||
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
|
||||
},
|
||||
})
|
||||
|
||||
_, diags := ctx.Plan(m, state, &PlanOpts{
|
||||
Mode: plans.NormalMode,
|
||||
Targets: []addrs.Targetable{
|
||||
addrA,
|
||||
},
|
||||
})
|
||||
//
|
||||
assertNoErrors(t, diags)
|
||||
}
|
||||
|
||||
func TestContext2Plan_movedResourceRefreshOnly(t *testing.T) {
|
||||
addrA := mustResourceInstanceAddr("test_object.a")
|
||||
addrB := mustResourceInstanceAddr("test_object.b")
|
||||
|
Loading…
Reference in New Issue
Block a user