github.com/opentofu/opentofu@v1.7.1/internal/command/jsonformat/differ/block.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package differ
     7  
     8  import (
     9  	"github.com/opentofu/opentofu/internal/command/jsonformat/collections"
    10  	"github.com/opentofu/opentofu/internal/command/jsonformat/computed"
    11  	"github.com/opentofu/opentofu/internal/command/jsonformat/computed/renderers"
    12  	"github.com/opentofu/opentofu/internal/command/jsonformat/structured"
    13  	"github.com/opentofu/opentofu/internal/command/jsonprovider"
    14  	"github.com/opentofu/opentofu/internal/plans"
    15  )
    16  
    17  func ComputeDiffForBlock(change structured.Change, block *jsonprovider.Block) computed.Diff {
    18  	if sensitive, ok := checkForSensitiveBlock(change, block); ok {
    19  		return sensitive
    20  	}
    21  
    22  	if unknown, ok := checkForUnknownBlock(change, block); ok {
    23  		return unknown
    24  	}
    25  
    26  	current := change.GetDefaultActionForIteration()
    27  
    28  	blockValue := change.AsMap()
    29  
    30  	attributes := make(map[string]computed.Diff)
    31  	for key, attr := range block.Attributes {
    32  		childValue := blockValue.GetChild(key)
    33  
    34  		if !childValue.RelevantAttributes.MatchesPartial() {
    35  			// Mark non-relevant attributes as unchanged.
    36  			childValue = childValue.AsNoOp()
    37  		}
    38  
    39  		// Empty strings in blocks should be considered null for legacy reasons.
    40  		// The SDK doesn't support null strings yet, so we work around this now.
    41  		if before, ok := childValue.Before.(string); ok && len(before) == 0 {
    42  			childValue.Before = nil
    43  		}
    44  		if after, ok := childValue.After.(string); ok && len(after) == 0 {
    45  			childValue.After = nil
    46  		}
    47  
    48  		// Always treat changes to blocks as implicit.
    49  		childValue.BeforeExplicit = false
    50  		childValue.AfterExplicit = false
    51  
    52  		childChange := ComputeDiffForAttribute(childValue, attr)
    53  		if childChange.Action == plans.NoOp && childValue.Before == nil && childValue.After == nil {
    54  			// Don't record nil values at all in blocks.
    55  			continue
    56  		}
    57  
    58  		attributes[key] = childChange
    59  		current = collections.CompareActions(current, childChange.Action)
    60  	}
    61  
    62  	blocks := renderers.Blocks{
    63  		ReplaceBlocks:         make(map[string]bool),
    64  		BeforeSensitiveBlocks: make(map[string]bool),
    65  		AfterSensitiveBlocks:  make(map[string]bool),
    66  		SingleBlocks:          make(map[string]computed.Diff),
    67  		ListBlocks:            make(map[string][]computed.Diff),
    68  		SetBlocks:             make(map[string][]computed.Diff),
    69  		MapBlocks:             make(map[string]map[string]computed.Diff),
    70  	}
    71  
    72  	for key, blockType := range block.BlockTypes {
    73  		childValue := blockValue.GetChild(key)
    74  
    75  		if !childValue.RelevantAttributes.MatchesPartial() {
    76  			// Mark non-relevant attributes as unchanged.
    77  			childValue = childValue.AsNoOp()
    78  		}
    79  
    80  		beforeSensitive := childValue.IsBeforeSensitive()
    81  		afterSensitive := childValue.IsAfterSensitive()
    82  		forcesReplacement := childValue.ReplacePaths.Matches()
    83  
    84  		switch NestingMode(blockType.NestingMode) {
    85  		case nestingModeSet:
    86  			diffs, action := computeBlockDiffsAsSet(childValue, blockType.Block)
    87  			if action == plans.NoOp && childValue.Before == nil && childValue.After == nil {
    88  				// Don't record nil values in blocks.
    89  				continue
    90  			}
    91  			blocks.AddAllSetBlock(key, diffs, forcesReplacement, beforeSensitive, afterSensitive)
    92  			current = collections.CompareActions(current, action)
    93  		case nestingModeList:
    94  			diffs, action := computeBlockDiffsAsList(childValue, blockType.Block)
    95  			if action == plans.NoOp && childValue.Before == nil && childValue.After == nil {
    96  				// Don't record nil values in blocks.
    97  				continue
    98  			}
    99  			blocks.AddAllListBlock(key, diffs, forcesReplacement, beforeSensitive, afterSensitive)
   100  			current = collections.CompareActions(current, action)
   101  		case nestingModeMap:
   102  			diffs, action := computeBlockDiffsAsMap(childValue, blockType.Block)
   103  			if action == plans.NoOp && childValue.Before == nil && childValue.After == nil {
   104  				// Don't record nil values in blocks.
   105  				continue
   106  			}
   107  			blocks.AddAllMapBlocks(key, diffs, forcesReplacement, beforeSensitive, afterSensitive)
   108  			current = collections.CompareActions(current, action)
   109  		case nestingModeSingle, nestingModeGroup:
   110  			diff := ComputeDiffForBlock(childValue, blockType.Block)
   111  			if diff.Action == plans.NoOp && childValue.Before == nil && childValue.After == nil {
   112  				// Don't record nil values in blocks.
   113  				continue
   114  			}
   115  			blocks.AddSingleBlock(key, diff, forcesReplacement, beforeSensitive, afterSensitive)
   116  			current = collections.CompareActions(current, diff.Action)
   117  		default:
   118  			panic("unrecognized nesting mode: " + blockType.NestingMode)
   119  		}
   120  	}
   121  
   122  	return computed.NewDiff(renderers.Block(attributes, blocks), current, change.ReplacePaths.Matches())
   123  }