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 }