mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Add support for sensitive values in the structured renderer (#32375)
* prep for processing the structured run output * undo unwanted change to a json key * Add skeleton functions and API for refactored renderer * goimports * Fix documentation of the RenderOpts struct * Add rendering functionality for primitives to the structured renderer * add test case for override * Add support for parsing and rendering sensitive values in the renderer
This commit is contained in:
parent
71daef058f
commit
6ab277f6ba
@ -1,6 +1,10 @@
|
||||
package change
|
||||
|
||||
import "github.com/hashicorp/terraform/internal/plans"
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/internal/plans"
|
||||
)
|
||||
|
||||
// Change captures a change to a single block, element or attribute.
|
||||
//
|
||||
@ -73,3 +77,9 @@ func (change Change) forcesReplacement() string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// indent returns whitespace that is the required length for the specified
|
||||
// indent.
|
||||
func (change Change) indent(indent int) string {
|
||||
return strings.Repeat(" ", indent)
|
||||
}
|
||||
|
@ -20,17 +20,17 @@ type primitiveRenderer struct {
|
||||
after *string
|
||||
}
|
||||
|
||||
func (render primitiveRenderer) Render(result Change, indent int, opts RenderOpts) string {
|
||||
func (renderer primitiveRenderer) Render(result Change, indent int, opts RenderOpts) string {
|
||||
var beforeValue, afterValue string
|
||||
|
||||
if render.before != nil {
|
||||
beforeValue = *render.before
|
||||
if renderer.before != nil {
|
||||
beforeValue = *renderer.before
|
||||
} else {
|
||||
beforeValue = "[dark_gray]null[reset]"
|
||||
}
|
||||
|
||||
if render.after != nil {
|
||||
afterValue = *render.after
|
||||
if renderer.after != nil {
|
||||
afterValue = *renderer.after
|
||||
} else {
|
||||
afterValue = "[dark_gray]null[reset]"
|
||||
}
|
||||
|
@ -73,6 +73,30 @@ func TestRenderers(t *testing.T) {
|
||||
},
|
||||
expected: "0 -> 1",
|
||||
},
|
||||
"primitive_update_replace": {
|
||||
change: Change{
|
||||
renderer: Primitive(strptr("0"), strptr("1")),
|
||||
action: plans.Update,
|
||||
replace: true,
|
||||
},
|
||||
expected: "0 -> 1 # forces replacement",
|
||||
},
|
||||
"sensitive_update": {
|
||||
change: Change{
|
||||
renderer: Sensitive("0", "1", true, true),
|
||||
action: plans.Update,
|
||||
replace: false,
|
||||
},
|
||||
expected: "(sensitive)",
|
||||
},
|
||||
"sensitive_update_replace": {
|
||||
change: Change{
|
||||
renderer: Sensitive("0", "1", true, true),
|
||||
action: plans.Update,
|
||||
replace: true,
|
||||
},
|
||||
expected: "(sensitive) # forces replacement",
|
||||
},
|
||||
}
|
||||
for name, tc := range tcs {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
|
48
internal/command/jsonformat/change/sensitive.go
Normal file
48
internal/command/jsonformat/change/sensitive.go
Normal file
@ -0,0 +1,48 @@
|
||||
package change
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func Sensitive(before, after interface{}, beforeSensitive, afterSensitive bool) Renderer {
|
||||
return sensitiveRenderer{
|
||||
before: before,
|
||||
after: after,
|
||||
beforeSensitive: beforeSensitive,
|
||||
afterSensitive: afterSensitive,
|
||||
}
|
||||
}
|
||||
|
||||
type sensitiveRenderer struct {
|
||||
before interface{}
|
||||
after interface{}
|
||||
|
||||
beforeSensitive bool
|
||||
afterSensitive bool
|
||||
}
|
||||
|
||||
func (renderer sensitiveRenderer) Render(change Change, indent int, opts RenderOpts) string {
|
||||
return fmt.Sprintf("(sensitive)%s%s", change.nullSuffix(opts.overrideNullSuffix), change.forcesReplacement())
|
||||
}
|
||||
|
||||
func (renderer sensitiveRenderer) Warnings(change Change, indent int) []string {
|
||||
if (renderer.beforeSensitive == renderer.afterSensitive) || renderer.before == nil || renderer.after == nil {
|
||||
// Only display warnings for sensitive values if they are changing from
|
||||
// being sensitive or to being sensitive or if they are not being
|
||||
// destroyed or created.
|
||||
return []string{}
|
||||
}
|
||||
|
||||
var warning string
|
||||
if renderer.beforeSensitive {
|
||||
warning = fmt.Sprintf(" # [yellow]Warning[reset]: this attribute value will no longer be marked as sensitive\n%s # after applying this change.", change.indent(indent))
|
||||
} else {
|
||||
warning = fmt.Sprintf(" # [yellow]Warning[reset]: this attribute value will be marked as sensitive and will not\n%s # display in UI output after applying this change.", change.indent(indent))
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(renderer.before, renderer.after) {
|
||||
return []string{fmt.Sprintf("%s The value is unchanged.", warning)}
|
||||
}
|
||||
return []string{warning}
|
||||
}
|
@ -33,3 +33,23 @@ func ValidatePrimitive(before, after *string) ValidateChangeFunc {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ValidateSensitive(before, after interface{}, beforeSensitive, afterSensitive bool) ValidateChangeFunc {
|
||||
return func(t *testing.T, change Change) {
|
||||
sensitive, ok := change.renderer.(sensitiveRenderer)
|
||||
if !ok {
|
||||
t.Fatalf("invalid renderer type: %T", change.renderer)
|
||||
}
|
||||
|
||||
if beforeSensitive != sensitive.beforeSensitive || afterSensitive != sensitive.afterSensitive {
|
||||
t.Fatalf("before or after sensitive values don't match:\n\texpected; before: %t after: %t\n\tactual; before: %t, after: %t", beforeSensitive, afterSensitive, sensitive.beforeSensitive, sensitive.afterSensitive)
|
||||
}
|
||||
|
||||
beforeDiff := cmp.Diff(sensitive.before, before)
|
||||
afterDiff := cmp.Diff(sensitive.after, after)
|
||||
|
||||
if len(beforeDiff) > 0 || len(afterDiff) > 0 {
|
||||
t.Fatalf("before diff: (%s), after diff: (%s)", beforeDiff, afterDiff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,11 @@ func (v Value) ComputeChangeForAttribute(attribute *jsonprovider.Attribute) chan
|
||||
}
|
||||
|
||||
func (v Value) ComputeChangeForType(ctyType cty.Type) change.Change {
|
||||
|
||||
if sensitive, ok := v.CheckForSensitive(); ok {
|
||||
return sensitive
|
||||
}
|
||||
|
||||
switch {
|
||||
case ctyType.IsPrimitiveType():
|
||||
return v.computeAttributeChangeAsPrimitive(ctyType)
|
||||
|
28
internal/command/jsonformat/differ/sensitive.go
Normal file
28
internal/command/jsonformat/differ/sensitive.go
Normal file
@ -0,0 +1,28 @@
|
||||
package differ
|
||||
|
||||
import "github.com/hashicorp/terraform/internal/command/jsonformat/change"
|
||||
|
||||
func (v Value) CheckForSensitive() (change.Change, bool) {
|
||||
beforeSensitive := v.isBeforeSensitive()
|
||||
afterSensitive := v.isAfterSensitive()
|
||||
|
||||
if !beforeSensitive && !afterSensitive {
|
||||
return change.Change{}, false
|
||||
}
|
||||
|
||||
return v.AsChange(change.Sensitive(v.Before, v.After, beforeSensitive, afterSensitive)), true
|
||||
}
|
||||
|
||||
func (v Value) isBeforeSensitive() bool {
|
||||
if sensitive, ok := v.BeforeSensitive.(bool); ok {
|
||||
return sensitive
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (v Value) isAfterSensitive() bool {
|
||||
if sensitive, ok := v.AfterSensitive.(bool); ok {
|
||||
return sensitive
|
||||
}
|
||||
return false
|
||||
}
|
@ -118,20 +118,6 @@ func (v Value) AsChange(renderer change.Renderer) change.Change {
|
||||
return change.New(renderer, v.calculateChange(), v.replacePath())
|
||||
}
|
||||
|
||||
func (v Value) isBeforeSensitive() bool {
|
||||
if sensitive, ok := v.BeforeSensitive.(bool); ok {
|
||||
return sensitive
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (v Value) isAfterSensitive() bool {
|
||||
if sensitive, ok := v.AfterSensitive.(bool); ok {
|
||||
return sensitive
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (v Value) replacePath() bool {
|
||||
if replace, ok := v.ReplacePaths.(bool); ok {
|
||||
return replace
|
||||
|
@ -76,6 +76,46 @@ func TestValue_Attribute(t *testing.T) {
|
||||
expectedReplace: false,
|
||||
validateChange: change.ValidatePrimitive(nil, strptr("\"new\"")),
|
||||
},
|
||||
"primitive_create_sensitive": {
|
||||
input: Value{
|
||||
Before: nil,
|
||||
After: "new",
|
||||
AfterSensitive: true,
|
||||
},
|
||||
attribute: &jsonprovider.Attribute{
|
||||
AttributeType: []byte("\"string\""),
|
||||
},
|
||||
expectedAction: plans.Create,
|
||||
expectedReplace: false,
|
||||
validateChange: change.ValidateSensitive(nil, "new", false, true),
|
||||
},
|
||||
"primitive_delete_sensitive": {
|
||||
input: Value{
|
||||
Before: "old",
|
||||
BeforeSensitive: true,
|
||||
After: nil,
|
||||
},
|
||||
attribute: &jsonprovider.Attribute{
|
||||
AttributeType: []byte("\"string\""),
|
||||
},
|
||||
expectedAction: plans.Delete,
|
||||
expectedReplace: false,
|
||||
validateChange: change.ValidateSensitive("old", nil, true, false),
|
||||
},
|
||||
"primitive_update_sensitive": {
|
||||
input: Value{
|
||||
Before: "old",
|
||||
BeforeSensitive: true,
|
||||
After: "new",
|
||||
AfterSensitive: true,
|
||||
},
|
||||
attribute: &jsonprovider.Attribute{
|
||||
AttributeType: []byte("\"string\""),
|
||||
},
|
||||
expectedAction: plans.Update,
|
||||
expectedReplace: false,
|
||||
validateChange: change.ValidateSensitive("old", "new", true, true),
|
||||
},
|
||||
}
|
||||
for name, tc := range tcs {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
|
Loading…
Reference in New Issue
Block a user