| // Copyright (c) The OpenTofu Authors | |
| // SPDX-License-Identifier: MPL-2.0 | |
| // Copyright (c) 2023 HashiCorp, Inc. | |
| // SPDX-License-Identifier: MPL-2.0 | |
| package renderers | |
| import ( | |
| "bytes" | |
| "fmt" | |
| "sort" | |
| "github.com/opentofu/opentofu/internal/command/jsonformat/computed" | |
| "github.com/opentofu/opentofu/internal/plans" | |
| ) | |
| var ( | |
| _ computed.DiffRenderer = (*blockRenderer)(nil) | |
| importantAttributes = []string{ | |
| "id", | |
| "name", | |
| "tags", | |
| } | |
| ) | |
| func importantAttribute(attr string) bool { | |
| for _, attribute := range importantAttributes { | |
| if attribute == attr { | |
| return true | |
| } | |
| } | |
| return false | |
| } | |
| func Block(attributes map[string]computed.Diff, blocks Blocks) computed.DiffRenderer { | |
| return &blockRenderer{ | |
| attributes: attributes, | |
| blocks: blocks, | |
| } | |
| } | |
| type blockRenderer struct { | |
| NoWarningsRenderer | |
| attributes map[string]computed.Diff | |
| blocks Blocks | |
| } | |
| func (renderer blockRenderer) RenderHuman(diff computed.Diff, indent int, opts computed.RenderHumanOpts) string { | |
| if len(renderer.attributes) == 0 && len(renderer.blocks.GetAllKeys()) == 0 { | |
| return fmt.Sprintf("{}%s", forcesReplacement(diff.Replace, opts)) | |
| } | |
| unchangedAttributes := 0 | |
| unchangedBlocks := 0 | |
| maximumAttributeKeyLen := 0 | |
| var attributeKeys []string | |
| escapedAttributeKeys := make(map[string]string) | |
| for key := range renderer.attributes { | |
| attributeKeys = append(attributeKeys, key) | |
| escapedKey := EnsureValidAttributeName(key) | |
| escapedAttributeKeys[key] = escapedKey | |
| if maximumAttributeKeyLen < len(escapedKey) { | |
| maximumAttributeKeyLen = len(escapedKey) | |
| } | |
| } | |
| sort.Strings(attributeKeys) | |
| importantAttributeOpts := opts.Clone() | |
| importantAttributeOpts.ShowUnchangedChildren = true | |
| attributeOpts := opts.Clone() | |
| var buf bytes.Buffer | |
| fmt.Fprintf(&buf, "{%s\n", forcesReplacement(diff.Replace, opts)) | |
| for _, key := range attributeKeys { | |
| attribute := renderer.attributes[key] | |
| if importantAttribute(key) { | |
| // Always display the important attributes. | |
| for _, warning := range attribute.WarningsHuman(indent+1, importantAttributeOpts) { | |
| fmt.Fprintf(&buf, "%s%s\n", formatIndent(indent+1), warning) | |
| } | |
| fmt.Fprintf(&buf, "%s%s%-*s = %s\n", formatIndent(indent+1), writeDiffActionSymbol(attribute.Action, importantAttributeOpts), maximumAttributeKeyLen, key, attribute.RenderHuman(indent+1, importantAttributeOpts)) | |
| continue | |
| } | |
| if attribute.Action == plans.NoOp && !opts.ShowUnchangedChildren { | |
| unchangedAttributes++ | |
| continue | |
| } | |
| for _, warning := range attribute.WarningsHuman(indent+1, opts) { | |
| fmt.Fprintf(&buf, "%s%s\n", formatIndent(indent+1), warning) | |
| } | |
| fmt.Fprintf(&buf, "%s%s%-*s = %s\n", formatIndent(indent+1), writeDiffActionSymbol(attribute.Action, attributeOpts), maximumAttributeKeyLen, escapedAttributeKeys[key], attribute.RenderHuman(indent+1, attributeOpts)) | |
| } | |
| if unchangedAttributes > 0 { | |
| fmt.Fprintf(&buf, "%s%s%s\n", formatIndent(indent+1), writeDiffActionSymbol(plans.NoOp, opts), unchanged("attribute", unchangedAttributes, opts)) | |
| } | |
| blockKeys := renderer.blocks.GetAllKeys() | |
| for _, key := range blockKeys { | |
| foundChangedBlock := false | |
| renderBlock := func(diff computed.Diff, mapKey string, opts computed.RenderHumanOpts) { | |
| creatingSensitiveValue := diff.Action == plans.Create && renderer.blocks.AfterSensitiveBlocks[key] | |
| deletingSensitiveValue := diff.Action == plans.Delete && renderer.blocks.BeforeSensitiveBlocks[key] | |
| modifyingSensitiveValue := (diff.Action == plans.Update || diff.Action == plans.NoOp) && (renderer.blocks.AfterSensitiveBlocks[key] || renderer.blocks.BeforeSensitiveBlocks[key]) | |
| if creatingSensitiveValue || deletingSensitiveValue || modifyingSensitiveValue { | |
| // Intercept the renderer here if the sensitive data was set | |
| // across all the blocks instead of individually. | |
| action := diff.Action | |
| if diff.Action == plans.NoOp && renderer.blocks.BeforeSensitiveBlocks[key] != renderer.blocks.AfterSensitiveBlocks[key] { | |
| action = plans.Update | |
| } | |
| diff = computed.NewDiff(SensitiveBlock(diff, renderer.blocks.BeforeSensitiveBlocks[key], renderer.blocks.AfterSensitiveBlocks[key]), action, diff.Replace) | |
| } | |
| if diff.Action == plans.NoOp && !opts.ShowUnchangedChildren { | |
| unchangedBlocks++ | |
| return | |
| } | |
| if !foundChangedBlock && len(renderer.attributes) > 0 { | |
| // We always want to put an extra new line between the | |
| // attributes and blocks, and between groups of blocks. | |
| buf.WriteString("\n") | |
| foundChangedBlock = true | |
| } | |
| // If the force replacement metadata was set for every entry in the | |
| // block we need to override that here. Our child blocks will only | |
| // know about the replace function if it was set on them | |
| // specifically, and not if it was set for all the blocks. | |
| blockOpts := opts.Clone() | |
| blockOpts.OverrideForcesReplacement = renderer.blocks.ReplaceBlocks[key] | |
| for _, warning := range diff.WarningsHuman(indent+1, blockOpts) { | |
| fmt.Fprintf(&buf, "%s%s\n", formatIndent(indent+1), warning) | |
| } | |
| fmt.Fprintf(&buf, "%s%s%s%s %s\n", formatIndent(indent+1), writeDiffActionSymbol(diff.Action, blockOpts), EnsureValidAttributeName(key), mapKey, diff.RenderHuman(indent+1, blockOpts)) | |
| } | |
| switch { | |
| case renderer.blocks.IsSingleBlock(key): | |
| renderBlock(renderer.blocks.SingleBlocks[key], "", opts) | |
| case renderer.blocks.IsMapBlock(key): | |
| var keys []string | |
| for key := range renderer.blocks.MapBlocks[key] { | |
| keys = append(keys, key) | |
| } | |
| sort.Strings(keys) | |
| for _, innerKey := range keys { | |
| renderBlock(renderer.blocks.MapBlocks[key][innerKey], fmt.Sprintf(" %q", innerKey), opts) | |
| } | |
| case renderer.blocks.IsSetBlock(key): | |
| setOpts := opts.Clone() | |
| setOpts.OverrideForcesReplacement = diff.Replace | |
| for _, block := range renderer.blocks.SetBlocks[key] { | |
| renderBlock(block, "", setOpts) | |
| } | |
| case renderer.blocks.IsListBlock(key): | |
| for _, block := range renderer.blocks.ListBlocks[key] { | |
| renderBlock(block, "", opts) | |
| } | |
| } | |
| } | |
| if unchangedBlocks > 0 { | |
| fmt.Fprintf(&buf, "\n%s%s%s\n", formatIndent(indent+1), writeDiffActionSymbol(plans.NoOp, opts), unchanged("block", unchangedBlocks, opts)) | |
| } | |
| fmt.Fprintf(&buf, "%s%s}", formatIndent(indent), writeDiffActionSymbol(plans.NoOp, opts)) | |
| return buf.String() | |
| } |