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
|
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.
|
// Change captures a change to a single block, element or attribute.
|
||||||
//
|
//
|
||||||
@ -73,3 +77,9 @@ func (change Change) forcesReplacement() string {
|
|||||||
}
|
}
|
||||||
return ""
|
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
|
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
|
var beforeValue, afterValue string
|
||||||
|
|
||||||
if render.before != nil {
|
if renderer.before != nil {
|
||||||
beforeValue = *render.before
|
beforeValue = *renderer.before
|
||||||
} else {
|
} else {
|
||||||
beforeValue = "[dark_gray]null[reset]"
|
beforeValue = "[dark_gray]null[reset]"
|
||||||
}
|
}
|
||||||
|
|
||||||
if render.after != nil {
|
if renderer.after != nil {
|
||||||
afterValue = *render.after
|
afterValue = *renderer.after
|
||||||
} else {
|
} else {
|
||||||
afterValue = "[dark_gray]null[reset]"
|
afterValue = "[dark_gray]null[reset]"
|
||||||
}
|
}
|
||||||
|
@ -73,6 +73,30 @@ func TestRenderers(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expected: "0 -> 1",
|
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 {
|
for name, tc := range tcs {
|
||||||
t.Run(name, func(t *testing.T) {
|
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 {
|
func (v Value) ComputeChangeForType(ctyType cty.Type) change.Change {
|
||||||
|
|
||||||
|
if sensitive, ok := v.CheckForSensitive(); ok {
|
||||||
|
return sensitive
|
||||||
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case ctyType.IsPrimitiveType():
|
case ctyType.IsPrimitiveType():
|
||||||
return v.computeAttributeChangeAsPrimitive(ctyType)
|
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())
|
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 {
|
func (v Value) replacePath() bool {
|
||||||
if replace, ok := v.ReplacePaths.(bool); ok {
|
if replace, ok := v.ReplacePaths.(bool); ok {
|
||||||
return replace
|
return replace
|
||||||
|
@ -76,6 +76,46 @@ func TestValue_Attribute(t *testing.T) {
|
|||||||
expectedReplace: false,
|
expectedReplace: false,
|
||||||
validateChange: change.ValidatePrimitive(nil, strptr("\"new\"")),
|
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 {
|
for name, tc := range tcs {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
|
Loading…
Reference in New Issue
Block a user