@@ -347,11 +347,17 @@ func (c *Context) planWalk(config *configs.Config, prevRunState *states.State, r
347347 diags = diags .Append (walkDiags )
348348 diags = diags .Append (c .postPlanValidateMoves (config , moveStmts , walker .InstanceExpander .AllInstances ()))
349349
350+ prevRunState = walker .PrevRunState .Close ()
351+ priorState := walker .RefreshState .Close ()
352+ driftedResources , driftDiags := c .driftedResources (config , prevRunState , priorState , moveResults )
353+ diags = diags .Append (driftDiags )
354+
350355 plan := & plans.Plan {
351- UIMode : opts .Mode ,
352- Changes : changes ,
353- PriorState : walker .RefreshState .Close (),
354- PrevRunState : walker .PrevRunState .Close (),
356+ UIMode : opts .Mode ,
357+ Changes : changes ,
358+ DriftedResources : driftedResources ,
359+ PrevRunState : prevRunState ,
360+ PriorState : priorState ,
355361
356362 // Other fields get populated by Context.Plan after we return
357363 }
@@ -398,6 +404,126 @@ func (c *Context) planGraph(config *configs.Config, prevRunState *states.State,
398404 }
399405}
400406
407+ func (c * Context ) driftedResources (config * configs.Config , oldState , newState * states.State , moves map [addrs.UniqueKey ]refactoring.MoveResult ) ([]* plans.ResourceInstanceChangeSrc , tfdiags.Diagnostics ) {
408+ var diags tfdiags.Diagnostics
409+
410+ if newState .ManagedResourcesEqual (oldState ) {
411+ // Nothing to do, because we only detect and report drift for managed
412+ // resource instances.
413+ return nil , diags
414+ }
415+
416+ schemas , schemaDiags := c .Schemas (config , newState )
417+ diags = diags .Append (schemaDiags )
418+ if diags .HasErrors () {
419+ return nil , diags
420+ }
421+
422+ var drs []* plans.ResourceInstanceChangeSrc
423+
424+ for _ , ms := range oldState .Modules {
425+ for _ , rs := range ms .Resources {
426+ if rs .Addr .Resource .Mode != addrs .ManagedResourceMode {
427+ // Drift reporting is only for managed resources
428+ continue
429+ }
430+
431+ provider := rs .ProviderConfig .Provider
432+ for key , oldIS := range rs .Instances {
433+ if oldIS .Current == nil {
434+ // Not interested in instances that only have deposed objects
435+ continue
436+ }
437+ addr := rs .Addr .Instance (key )
438+ newIS := newState .ResourceInstance (addr )
439+
440+ schema , _ := schemas .ResourceTypeConfig (
441+ provider ,
442+ addr .Resource .Resource .Mode ,
443+ addr .Resource .Resource .Type ,
444+ )
445+ if schema == nil {
446+ // This should never happen, but just in case
447+ return nil , diags .Append (tfdiags .Sourceless (
448+ tfdiags .Error ,
449+ "Missing resource schema from provider" ,
450+ fmt .Sprintf ("No resource schema found for %s." , addr .Resource .Resource .Type ),
451+ ))
452+ }
453+ ty := schema .ImpliedType ()
454+
455+ oldObj , err := oldIS .Current .Decode (ty )
456+ if err != nil {
457+ // This should also never happen
458+ return nil , diags .Append (tfdiags .Sourceless (
459+ tfdiags .Error ,
460+ "Failed to decode resource from state" ,
461+ fmt .Sprintf ("Error decoding %q from previous state: %s" , addr .String (), err ),
462+ ))
463+ }
464+
465+ var newObj * states.ResourceInstanceObject
466+ if newIS != nil && newIS .Current != nil {
467+ newObj , err = newIS .Current .Decode (ty )
468+ if err != nil {
469+ // This should also never happen
470+ return nil , diags .Append (tfdiags .Sourceless (
471+ tfdiags .Error ,
472+ "Failed to decode resource from state" ,
473+ fmt .Sprintf ("Error decoding %q from prior state: %s" , addr .String (), err ),
474+ ))
475+ }
476+ }
477+
478+ var oldVal , newVal cty.Value
479+ oldVal = oldObj .Value
480+ if newObj != nil {
481+ newVal = newObj .Value
482+ } else {
483+ newVal = cty .NullVal (ty )
484+ }
485+
486+ if oldVal .RawEquals (newVal ) {
487+ // No drift if the two values are semantically equivalent
488+ continue
489+ }
490+
491+ // We can only detect updates and deletes as drift.
492+ action := plans .Update
493+ if newVal .IsNull () {
494+ action = plans .Delete
495+ }
496+
497+ prevRunAddr := addr
498+ if move , ok := moves [addr .UniqueKey ()]; ok {
499+ prevRunAddr = move .From
500+ }
501+
502+ change := & plans.ResourceInstanceChange {
503+ Addr : addr ,
504+ PrevRunAddr : prevRunAddr ,
505+ ProviderAddr : rs .ProviderConfig ,
506+ Change : plans.Change {
507+ Action : action ,
508+ Before : oldVal ,
509+ After : newVal ,
510+ },
511+ }
512+
513+ changeSrc , err := change .Encode (ty )
514+ if err != nil {
515+ diags = diags .Append (err )
516+ return nil , diags
517+ }
518+
519+ drs = append (drs , changeSrc )
520+ }
521+ }
522+ }
523+
524+ return drs , diags
525+ }
526+
401527// PlanGraphForUI is a last vestage of graphs in the public interface of Context
402528// (as opposed to graphs as an implementation detail) intended only for use
403529// by the "terraform graph" command when asked to render a plan-time graph.
0 commit comments