github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/jsonformat/computed/renderers/primitive.go (about) 1 package renderers 2 3 import ( 4 "fmt" 5 "math/big" 6 "strings" 7 8 "github.com/zclconf/go-cty/cty" 9 10 "github.com/hashicorp/terraform/internal/command/jsonformat/collections" 11 "github.com/hashicorp/terraform/internal/command/jsonformat/computed" 12 "github.com/hashicorp/terraform/internal/command/jsonformat/differ/attribute_path" 13 "github.com/hashicorp/terraform/internal/plans" 14 ) 15 16 var _ computed.DiffRenderer = (*primitiveRenderer)(nil) 17 18 func Primitive(before, after interface{}, ctype cty.Type) computed.DiffRenderer { 19 return &primitiveRenderer{ 20 before: before, 21 after: after, 22 ctype: ctype, 23 } 24 } 25 26 type primitiveRenderer struct { 27 NoWarningsRenderer 28 29 before interface{} 30 after interface{} 31 ctype cty.Type 32 } 33 34 func (renderer primitiveRenderer) RenderHuman(diff computed.Diff, indent int, opts computed.RenderHumanOpts) string { 35 if renderer.ctype == cty.String { 36 return renderer.renderStringDiff(diff, indent, opts) 37 } 38 39 beforeValue := renderPrimitiveValue(renderer.before, renderer.ctype, opts) 40 afterValue := renderPrimitiveValue(renderer.after, renderer.ctype, opts) 41 42 switch diff.Action { 43 case plans.Create: 44 return fmt.Sprintf("%s%s", afterValue, forcesReplacement(diff.Replace, opts)) 45 case plans.Delete: 46 return fmt.Sprintf("%s%s%s", beforeValue, nullSuffix(diff.Action, opts), forcesReplacement(diff.Replace, opts)) 47 case plans.NoOp: 48 return fmt.Sprintf("%s%s", beforeValue, forcesReplacement(diff.Replace, opts)) 49 default: 50 return fmt.Sprintf("%s %s %s%s", beforeValue, opts.Colorize.Color("[yellow]->[reset]"), afterValue, forcesReplacement(diff.Replace, opts)) 51 } 52 } 53 54 func renderPrimitiveValue(value interface{}, t cty.Type, opts computed.RenderHumanOpts) string { 55 if value == nil { 56 return opts.Colorize.Color("[dark_gray]null[reset]") 57 } 58 59 switch { 60 case t == cty.Bool: 61 if value.(bool) { 62 return "true" 63 } 64 return "false" 65 case t == cty.Number: 66 bf := big.NewFloat(value.(float64)) 67 return bf.Text('f', -1) 68 default: 69 panic("unrecognized primitive type: " + t.FriendlyName()) 70 } 71 } 72 73 func (renderer primitiveRenderer) renderStringDiff(diff computed.Diff, indent int, opts computed.RenderHumanOpts) string { 74 75 // We process multiline strings at the end of the switch statement. 76 var lines []string 77 78 switch diff.Action { 79 case plans.Create, plans.NoOp: 80 str := evaluatePrimitiveString(renderer.after, opts) 81 82 if str.Json != nil { 83 if diff.Action == plans.NoOp { 84 return renderer.renderStringDiffAsJson(diff, indent, opts, str, str) 85 } else { 86 return renderer.renderStringDiffAsJson(diff, indent, opts, evaluatedString{}, str) 87 } 88 } 89 90 if !str.IsMultiline { 91 return fmt.Sprintf("%q%s", str.String, forcesReplacement(diff.Replace, opts)) 92 } 93 94 // We are creating a single multiline string, so let's split by the new 95 // line character. While we are doing this, we are going to insert our 96 // indents and make sure each line is formatted correctly. 97 lines = strings.Split(strings.ReplaceAll(str.String, "\n", fmt.Sprintf("\n%s%s", formatIndent(indent+1), writeDiffActionSymbol(plans.NoOp, opts))), "\n") 98 99 // We now just need to do the same for the first entry in lines, because 100 // we split on the new line characters which won't have been at the 101 // beginning of the first line. 102 lines[0] = fmt.Sprintf("%s%s%s", formatIndent(indent+1), writeDiffActionSymbol(plans.NoOp, opts), lines[0]) 103 case plans.Delete: 104 str := evaluatePrimitiveString(renderer.before, opts) 105 106 if str.Json != nil { 107 return renderer.renderStringDiffAsJson(diff, indent, opts, str, evaluatedString{}) 108 } 109 110 if !str.IsMultiline { 111 return fmt.Sprintf("%q%s%s", str.String, nullSuffix(diff.Action, opts), forcesReplacement(diff.Replace, opts)) 112 } 113 114 // We are creating a single multiline string, so let's split by the new 115 // line character. While we are doing this, we are going to insert our 116 // indents and make sure each line is formatted correctly. 117 lines = strings.Split(strings.ReplaceAll(str.String, "\n", fmt.Sprintf("\n%s%s", formatIndent(indent+1), writeDiffActionSymbol(plans.NoOp, opts))), "\n") 118 119 // We now just need to do the same for the first entry in lines, because 120 // we split on the new line characters which won't have been at the 121 // beginning of the first line. 122 lines[0] = fmt.Sprintf("%s%s%s", formatIndent(indent+1), writeDiffActionSymbol(plans.NoOp, opts), lines[0]) 123 default: 124 beforeString := evaluatePrimitiveString(renderer.before, opts) 125 afterString := evaluatePrimitiveString(renderer.after, opts) 126 127 if beforeString.Json != nil && afterString.Json != nil { 128 return renderer.renderStringDiffAsJson(diff, indent, opts, beforeString, afterString) 129 } 130 131 if beforeString.Json != nil || afterString.Json != nil { 132 // This means one of the strings is JSON and one isn't. We're going 133 // to be a little inefficient here, but we can just reuse another 134 // renderer for this so let's keep it simple. 135 return computed.NewDiff( 136 TypeChange( 137 computed.NewDiff(Primitive(renderer.before, nil, cty.String), plans.Delete, false), 138 computed.NewDiff(Primitive(nil, renderer.after, cty.String), plans.Create, false)), 139 diff.Action, 140 diff.Replace).RenderHuman(indent, opts) 141 } 142 143 if !beforeString.IsMultiline && !afterString.IsMultiline { 144 return fmt.Sprintf("%q %s %q%s", beforeString.String, opts.Colorize.Color("[yellow]->[reset]"), afterString.String, forcesReplacement(diff.Replace, opts)) 145 } 146 147 beforeLines := strings.Split(beforeString.String, "\n") 148 afterLines := strings.Split(afterString.String, "\n") 149 150 processIndices := func(beforeIx, afterIx int) { 151 if beforeIx < 0 || beforeIx >= len(beforeLines) { 152 lines = append(lines, fmt.Sprintf("%s%s%s", formatIndent(indent+1), writeDiffActionSymbol(plans.Create, opts), afterLines[afterIx])) 153 return 154 } 155 156 if afterIx < 0 || afterIx >= len(afterLines) { 157 lines = append(lines, fmt.Sprintf("%s%s%s", formatIndent(indent+1), writeDiffActionSymbol(plans.Delete, opts), beforeLines[beforeIx])) 158 return 159 } 160 161 lines = append(lines, fmt.Sprintf("%s%s%s", formatIndent(indent+1), writeDiffActionSymbol(plans.NoOp, opts), beforeLines[beforeIx])) 162 } 163 isObjType := func(_ string) bool { 164 return false 165 } 166 167 collections.ProcessSlice(beforeLines, afterLines, processIndices, isObjType) 168 } 169 170 // We return early if we find non-multiline strings or JSON strings, so we 171 // know here that we just render the lines slice properly. 172 return fmt.Sprintf("<<-EOT%s\n%s\n%s%sEOT%s", 173 forcesReplacement(diff.Replace, opts), 174 strings.Join(lines, "\n"), 175 formatIndent(indent), 176 writeDiffActionSymbol(plans.NoOp, opts), 177 nullSuffix(diff.Action, opts)) 178 } 179 180 func (renderer primitiveRenderer) renderStringDiffAsJson(diff computed.Diff, indent int, opts computed.RenderHumanOpts, before evaluatedString, after evaluatedString) string { 181 jsonDiff := RendererJsonOpts().Transform(before.Json, after.Json, diff.Action != plans.Create, diff.Action != plans.Delete, attribute_path.AlwaysMatcher()) 182 183 action := diff.Action 184 185 jsonOpts := opts.Clone() 186 jsonOpts.OverrideNullSuffix = true 187 188 var whitespace, replace string 189 if jsonDiff.Action == plans.NoOp && diff.Action == plans.Update { 190 // Then this means we are rendering a whitespace only change. The JSON 191 // differ will have ignored the whitespace changes so that makes the 192 // diff we are about to print out very confusing without extra 193 // explanation. 194 if diff.Replace { 195 whitespace = " # whitespace changes force replacement" 196 } else { 197 whitespace = " # whitespace changes" 198 } 199 200 // Because we'd be showing no changes otherwise: 201 jsonOpts.ShowUnchangedChildren = true 202 203 // Whitespace changes should not appear as if edited. 204 action = plans.NoOp 205 } else { 206 // We only show the replace suffix if we didn't print something out 207 // about whitespace changes. 208 replace = forcesReplacement(diff.Replace, opts) 209 } 210 211 renderedJsonDiff := jsonDiff.RenderHuman(indent+1, jsonOpts) 212 213 if diff.Action == plans.Create || diff.Action == plans.Delete { 214 // We don't display the '+' or '-' symbols on the JSON diffs, we should 215 // still display the '~' for an update action though. 216 action = plans.NoOp 217 } 218 219 if strings.Contains(renderedJsonDiff, "\n") { 220 return fmt.Sprintf("jsonencode(%s\n%s%s%s%s\n%s%s)%s", whitespace, formatIndent(indent+1), writeDiffActionSymbol(action, opts), renderedJsonDiff, replace, formatIndent(indent), writeDiffActionSymbol(plans.NoOp, opts), nullSuffix(diff.Action, opts)) 221 } 222 return fmt.Sprintf("jsonencode(%s)%s%s", renderedJsonDiff, whitespace, replace) 223 }