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:
Liam Cervante 2023-01-09 11:40:47 +01:00 committed by GitHub
parent 71daef058f
commit 6ab277f6ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 181 additions and 20 deletions

View File

@ -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)
}

View File

@ -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]"
}

View File

@ -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) {

View 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}
}

View File

@ -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)
}
}
}

View File

@ -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)

View 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
}

View File

@ -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

View File

@ -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) {