github.com/opentofu/opentofu@v1.7.1/internal/command/jsonformat/computed/renderers/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 renderers
     7  
     8  import (
     9  	"bytes"
    10  	"fmt"
    11  	"sort"
    12  
    13  	"github.com/opentofu/opentofu/internal/command/jsonformat/computed"
    14  
    15  	"github.com/opentofu/opentofu/internal/plans"
    16  )
    17  
    18  var (
    19  	_ computed.DiffRenderer = (*blockRenderer)(nil)
    20  
    21  	importantAttributes = []string{
    22  		"id",
    23  		"name",
    24  		"tags",
    25  	}
    26  )
    27  
    28  func importantAttribute(attr string) bool {
    29  	for _, attribute := range importantAttributes {
    30  		if attribute == attr {
    31  			return true
    32  		}
    33  	}
    34  	return false
    35  }
    36  
    37  func Block(attributes map[string]computed.Diff, blocks Blocks) computed.DiffRenderer {
    38  	return &blockRenderer{
    39  		attributes: attributes,
    40  		blocks:     blocks,
    41  	}
    42  }
    43  
    44  type blockRenderer struct {
    45  	NoWarningsRenderer
    46  
    47  	attributes map[string]computed.Diff
    48  	blocks     Blocks
    49  }
    50  
    51  func (renderer blockRenderer) RenderHuman(diff computed.Diff, indent int, opts computed.RenderHumanOpts) string {
    52  	if len(renderer.attributes) == 0 && len(renderer.blocks.GetAllKeys()) == 0 {
    53  		return fmt.Sprintf("{}%s", forcesReplacement(diff.Replace, opts))
    54  	}
    55  
    56  	unchangedAttributes := 0
    57  	unchangedBlocks := 0
    58  
    59  	maximumAttributeKeyLen := 0
    60  	var attributeKeys []string
    61  	escapedAttributeKeys := make(map[string]string)
    62  	for key := range renderer.attributes {
    63  		attributeKeys = append(attributeKeys, key)
    64  		escapedKey := EnsureValidAttributeName(key)
    65  		escapedAttributeKeys[key] = escapedKey
    66  		if maximumAttributeKeyLen < len(escapedKey) {
    67  			maximumAttributeKeyLen = len(escapedKey)
    68  		}
    69  	}
    70  	sort.Strings(attributeKeys)
    71  
    72  	importantAttributeOpts := opts.Clone()
    73  	importantAttributeOpts.ShowUnchangedChildren = true
    74  
    75  	attributeOpts := opts.Clone()
    76  
    77  	var buf bytes.Buffer
    78  	buf.WriteString(fmt.Sprintf("{%s\n", forcesReplacement(diff.Replace, opts)))
    79  	for _, key := range attributeKeys {
    80  		attribute := renderer.attributes[key]
    81  		if importantAttribute(key) {
    82  
    83  			// Always display the important attributes.
    84  			for _, warning := range attribute.WarningsHuman(indent+1, importantAttributeOpts) {
    85  				buf.WriteString(fmt.Sprintf("%s%s\n", formatIndent(indent+1), warning))
    86  			}
    87  			buf.WriteString(fmt.Sprintf("%s%s%-*s = %s\n", formatIndent(indent+1), writeDiffActionSymbol(attribute.Action, importantAttributeOpts), maximumAttributeKeyLen, key, attribute.RenderHuman(indent+1, importantAttributeOpts)))
    88  			continue
    89  		}
    90  		if attribute.Action == plans.NoOp && !opts.ShowUnchangedChildren {
    91  			unchangedAttributes++
    92  			continue
    93  		}
    94  
    95  		for _, warning := range attribute.WarningsHuman(indent+1, opts) {
    96  			buf.WriteString(fmt.Sprintf("%s%s\n", formatIndent(indent+1), warning))
    97  		}
    98  		buf.WriteString(fmt.Sprintf("%s%s%-*s = %s\n", formatIndent(indent+1), writeDiffActionSymbol(attribute.Action, attributeOpts), maximumAttributeKeyLen, escapedAttributeKeys[key], attribute.RenderHuman(indent+1, attributeOpts)))
    99  	}
   100  
   101  	if unchangedAttributes > 0 {
   102  		buf.WriteString(fmt.Sprintf("%s%s%s\n", formatIndent(indent+1), writeDiffActionSymbol(plans.NoOp, opts), unchanged("attribute", unchangedAttributes, opts)))
   103  	}
   104  
   105  	blockKeys := renderer.blocks.GetAllKeys()
   106  	for _, key := range blockKeys {
   107  
   108  		foundChangedBlock := false
   109  		renderBlock := func(diff computed.Diff, mapKey string, opts computed.RenderHumanOpts) {
   110  
   111  			creatingSensitiveValue := diff.Action == plans.Create && renderer.blocks.AfterSensitiveBlocks[key]
   112  			deletingSensitiveValue := diff.Action == plans.Delete && renderer.blocks.BeforeSensitiveBlocks[key]
   113  			modifyingSensitiveValue := (diff.Action == plans.Update || diff.Action == plans.NoOp) && (renderer.blocks.AfterSensitiveBlocks[key] || renderer.blocks.BeforeSensitiveBlocks[key])
   114  
   115  			if creatingSensitiveValue || deletingSensitiveValue || modifyingSensitiveValue {
   116  				// Intercept the renderer here if the sensitive data was set
   117  				// across all the blocks instead of individually.
   118  				action := diff.Action
   119  				if diff.Action == plans.NoOp && renderer.blocks.BeforeSensitiveBlocks[key] != renderer.blocks.AfterSensitiveBlocks[key] {
   120  					action = plans.Update
   121  				}
   122  
   123  				diff = computed.NewDiff(SensitiveBlock(diff, renderer.blocks.BeforeSensitiveBlocks[key], renderer.blocks.AfterSensitiveBlocks[key]), action, diff.Replace)
   124  			}
   125  
   126  			if diff.Action == plans.NoOp && !opts.ShowUnchangedChildren {
   127  				unchangedBlocks++
   128  				return
   129  			}
   130  
   131  			if !foundChangedBlock && len(renderer.attributes) > 0 {
   132  				// We always want to put an extra new line between the
   133  				// attributes and blocks, and between groups of blocks.
   134  				buf.WriteString("\n")
   135  				foundChangedBlock = true
   136  			}
   137  
   138  			// If the force replacement metadata was set for every entry in the
   139  			// block we need to override that here. Our child blocks will only
   140  			// know about the replace function if it was set on them
   141  			// specifically, and not if it was set for all the blocks.
   142  			blockOpts := opts.Clone()
   143  			blockOpts.OverrideForcesReplacement = renderer.blocks.ReplaceBlocks[key]
   144  
   145  			for _, warning := range diff.WarningsHuman(indent+1, blockOpts) {
   146  				buf.WriteString(fmt.Sprintf("%s%s\n", formatIndent(indent+1), warning))
   147  			}
   148  			buf.WriteString(fmt.Sprintf("%s%s%s%s %s\n", formatIndent(indent+1), writeDiffActionSymbol(diff.Action, blockOpts), EnsureValidAttributeName(key), mapKey, diff.RenderHuman(indent+1, blockOpts)))
   149  
   150  		}
   151  
   152  		switch {
   153  		case renderer.blocks.IsSingleBlock(key):
   154  			renderBlock(renderer.blocks.SingleBlocks[key], "", opts)
   155  		case renderer.blocks.IsMapBlock(key):
   156  			var keys []string
   157  			for key := range renderer.blocks.MapBlocks[key] {
   158  				keys = append(keys, key)
   159  			}
   160  			sort.Strings(keys)
   161  
   162  			for _, innerKey := range keys {
   163  				renderBlock(renderer.blocks.MapBlocks[key][innerKey], fmt.Sprintf(" %q", innerKey), opts)
   164  			}
   165  		case renderer.blocks.IsSetBlock(key):
   166  
   167  			setOpts := opts.Clone()
   168  			setOpts.OverrideForcesReplacement = diff.Replace
   169  
   170  			for _, block := range renderer.blocks.SetBlocks[key] {
   171  				renderBlock(block, "", opts)
   172  			}
   173  		case renderer.blocks.IsListBlock(key):
   174  			for _, block := range renderer.blocks.ListBlocks[key] {
   175  				renderBlock(block, "", opts)
   176  			}
   177  		}
   178  	}
   179  
   180  	if unchangedBlocks > 0 {
   181  		buf.WriteString(fmt.Sprintf("\n%s%s%s\n", formatIndent(indent+1), writeDiffActionSymbol(plans.NoOp, opts), unchanged("block", unchangedBlocks, opts)))
   182  	}
   183  
   184  	buf.WriteString(fmt.Sprintf("%s%s}", formatIndent(indent), writeDiffActionSymbol(plans.NoOp, opts)))
   185  	return buf.String()
   186  }