mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-21 10:47:34 -05:00
182 lines
6.2 KiB
Go
182 lines
6.2 KiB
Go
// 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
|
|
buf.WriteString(fmt.Sprintf("{%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) {
|
|
buf.WriteString(fmt.Sprintf("%s%s\n", formatIndent(indent+1), warning))
|
|
}
|
|
buf.WriteString(fmt.Sprintf("%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) {
|
|
buf.WriteString(fmt.Sprintf("%s%s\n", formatIndent(indent+1), warning))
|
|
}
|
|
buf.WriteString(fmt.Sprintf("%s%s%-*s = %s\n", formatIndent(indent+1), writeDiffActionSymbol(attribute.Action, attributeOpts), maximumAttributeKeyLen, escapedAttributeKeys[key], attribute.RenderHuman(indent+1, attributeOpts)))
|
|
}
|
|
|
|
if unchangedAttributes > 0 {
|
|
buf.WriteString(fmt.Sprintf("%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) {
|
|
buf.WriteString(fmt.Sprintf("%s%s\n", formatIndent(indent+1), warning))
|
|
}
|
|
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)))
|
|
}
|
|
|
|
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 {
|
|
buf.WriteString(fmt.Sprintf("\n%s%s%s\n", formatIndent(indent+1), writeDiffActionSymbol(plans.NoOp, opts), unchanged("block", unchangedBlocks, opts)))
|
|
}
|
|
|
|
buf.WriteString(fmt.Sprintf("%s%s}", formatIndent(indent), writeDiffActionSymbol(plans.NoOp, opts)))
|
|
return buf.String()
|
|
}
|