github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/command/jsonformat/computed/renderers/block.go (about)

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