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 }