mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Add support for unknown/computed values in the structured renderer (#32378)
* 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 * Add support for unknown/computed values in the structured renderer * delete missing unit tests
This commit is contained in:
parent
6ab277f6ba
commit
b8b1a8d430
29
internal/command/jsonformat/change/computed.go
Normal file
29
internal/command/jsonformat/change/computed.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package change
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/internal/plans"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Computed(before Change) Renderer {
|
||||||
|
return &computedRenderer{
|
||||||
|
before: before,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type computedRenderer struct {
|
||||||
|
NoWarningsRenderer
|
||||||
|
|
||||||
|
before Change
|
||||||
|
}
|
||||||
|
|
||||||
|
func (renderer computedRenderer) Render(change Change, indent int, opts RenderOpts) string {
|
||||||
|
if change.action == plans.Create {
|
||||||
|
return "(known after apply)"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Never render null suffix for children of computed changes.
|
||||||
|
opts.overrideNullSuffix = true
|
||||||
|
return fmt.Sprintf("%s -> (known after apply)", renderer.before.Render(indent, opts))
|
||||||
|
}
|
@ -7,7 +7,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Primitive(before, after *string) Renderer {
|
func Primitive(before, after *string) Renderer {
|
||||||
return primitiveRenderer{
|
return &primitiveRenderer{
|
||||||
before: before,
|
before: before,
|
||||||
after: after,
|
after: after,
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,6 @@ func TestRenderers(t *testing.T) {
|
|||||||
change: Change{
|
change: Change{
|
||||||
renderer: Primitive(nil, strptr("1")),
|
renderer: Primitive(nil, strptr("1")),
|
||||||
action: plans.Create,
|
action: plans.Create,
|
||||||
replace: false,
|
|
||||||
},
|
},
|
||||||
expected: "1",
|
expected: "1",
|
||||||
},
|
},
|
||||||
@ -36,7 +35,6 @@ func TestRenderers(t *testing.T) {
|
|||||||
change: Change{
|
change: Change{
|
||||||
renderer: Primitive(strptr("1"), nil),
|
renderer: Primitive(strptr("1"), nil),
|
||||||
action: plans.Delete,
|
action: plans.Delete,
|
||||||
replace: false,
|
|
||||||
},
|
},
|
||||||
expected: "1 -> null",
|
expected: "1 -> null",
|
||||||
},
|
},
|
||||||
@ -44,7 +42,6 @@ func TestRenderers(t *testing.T) {
|
|||||||
change: Change{
|
change: Change{
|
||||||
renderer: Primitive(strptr("1"), nil),
|
renderer: Primitive(strptr("1"), nil),
|
||||||
action: plans.Delete,
|
action: plans.Delete,
|
||||||
replace: false,
|
|
||||||
},
|
},
|
||||||
opts: RenderOpts{overrideNullSuffix: true},
|
opts: RenderOpts{overrideNullSuffix: true},
|
||||||
expected: "1",
|
expected: "1",
|
||||||
@ -53,7 +50,6 @@ func TestRenderers(t *testing.T) {
|
|||||||
change: Change{
|
change: Change{
|
||||||
renderer: Primitive(strptr("1"), nil),
|
renderer: Primitive(strptr("1"), nil),
|
||||||
action: plans.Update,
|
action: plans.Update,
|
||||||
replace: false,
|
|
||||||
},
|
},
|
||||||
expected: "1 -> null",
|
expected: "1 -> null",
|
||||||
},
|
},
|
||||||
@ -61,7 +57,6 @@ func TestRenderers(t *testing.T) {
|
|||||||
change: Change{
|
change: Change{
|
||||||
renderer: Primitive(nil, strptr("1")),
|
renderer: Primitive(nil, strptr("1")),
|
||||||
action: plans.Update,
|
action: plans.Update,
|
||||||
replace: false,
|
|
||||||
},
|
},
|
||||||
expected: "null -> 1",
|
expected: "null -> 1",
|
||||||
},
|
},
|
||||||
@ -69,7 +64,6 @@ func TestRenderers(t *testing.T) {
|
|||||||
change: Change{
|
change: Change{
|
||||||
renderer: Primitive(strptr("0"), strptr("1")),
|
renderer: Primitive(strptr("0"), strptr("1")),
|
||||||
action: plans.Update,
|
action: plans.Update,
|
||||||
replace: false,
|
|
||||||
},
|
},
|
||||||
expected: "0 -> 1",
|
expected: "0 -> 1",
|
||||||
},
|
},
|
||||||
@ -85,7 +79,6 @@ func TestRenderers(t *testing.T) {
|
|||||||
change: Change{
|
change: Change{
|
||||||
renderer: Sensitive("0", "1", true, true),
|
renderer: Sensitive("0", "1", true, true),
|
||||||
action: plans.Update,
|
action: plans.Update,
|
||||||
replace: false,
|
|
||||||
},
|
},
|
||||||
expected: "(sensitive)",
|
expected: "(sensitive)",
|
||||||
},
|
},
|
||||||
@ -97,6 +90,23 @@ func TestRenderers(t *testing.T) {
|
|||||||
},
|
},
|
||||||
expected: "(sensitive) # forces replacement",
|
expected: "(sensitive) # forces replacement",
|
||||||
},
|
},
|
||||||
|
"computed_create": {
|
||||||
|
change: Change{
|
||||||
|
renderer: Computed(Change{}),
|
||||||
|
action: plans.Create,
|
||||||
|
},
|
||||||
|
expected: "(known after apply)",
|
||||||
|
},
|
||||||
|
"computed_update": {
|
||||||
|
change: Change{
|
||||||
|
renderer: Computed(Change{
|
||||||
|
renderer: Primitive(strptr("0"), nil),
|
||||||
|
action: plans.Delete,
|
||||||
|
}),
|
||||||
|
action: plans.Update,
|
||||||
|
},
|
||||||
|
expected: "0 -> (known after apply)",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for name, tc := range tcs {
|
for name, tc := range tcs {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Sensitive(before, after interface{}, beforeSensitive, afterSensitive bool) Renderer {
|
func Sensitive(before, after interface{}, beforeSensitive, afterSensitive bool) Renderer {
|
||||||
return sensitiveRenderer{
|
return &sensitiveRenderer{
|
||||||
before: before,
|
before: before,
|
||||||
after: after,
|
after: after,
|
||||||
beforeSensitive: beforeSensitive,
|
beforeSensitive: beforeSensitive,
|
||||||
|
@ -20,7 +20,7 @@ func ValidateChange(t *testing.T, f ValidateChangeFunc, change Change, expectedA
|
|||||||
|
|
||||||
func ValidatePrimitive(before, after *string) ValidateChangeFunc {
|
func ValidatePrimitive(before, after *string) ValidateChangeFunc {
|
||||||
return func(t *testing.T, change Change) {
|
return func(t *testing.T, change Change) {
|
||||||
primitive, ok := change.renderer.(primitiveRenderer)
|
primitive, ok := change.renderer.(*primitiveRenderer)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("invalid renderer type: %T", change.renderer)
|
t.Fatalf("invalid renderer type: %T", change.renderer)
|
||||||
}
|
}
|
||||||
@ -36,7 +36,7 @@ func ValidatePrimitive(before, after *string) ValidateChangeFunc {
|
|||||||
|
|
||||||
func ValidateSensitive(before, after interface{}, beforeSensitive, afterSensitive bool) ValidateChangeFunc {
|
func ValidateSensitive(before, after interface{}, beforeSensitive, afterSensitive bool) ValidateChangeFunc {
|
||||||
return func(t *testing.T, change Change) {
|
return func(t *testing.T, change Change) {
|
||||||
sensitive, ok := change.renderer.(sensitiveRenderer)
|
sensitive, ok := change.renderer.(*sensitiveRenderer)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("invalid renderer type: %T", change.renderer)
|
t.Fatalf("invalid renderer type: %T", change.renderer)
|
||||||
}
|
}
|
||||||
@ -53,3 +53,25 @@ func ValidateSensitive(before, after interface{}, beforeSensitive, afterSensitiv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ValidateComputed(before ValidateChangeFunc) ValidateChangeFunc {
|
||||||
|
return func(t *testing.T, change Change) {
|
||||||
|
computed, ok := change.renderer.(*computedRenderer)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("invalid renderer type: %T", change.renderer)
|
||||||
|
}
|
||||||
|
|
||||||
|
if before == nil {
|
||||||
|
if computed.before.renderer != nil {
|
||||||
|
t.Fatalf("did not expect a before renderer, but found one")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if computed.before.renderer == nil {
|
||||||
|
t.Fatalf("expected a before renderer, but found none")
|
||||||
|
}
|
||||||
|
|
||||||
|
before(t, computed.before)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -14,10 +14,14 @@ 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 {
|
if sensitive, ok := v.checkForSensitive(); ok {
|
||||||
return sensitive
|
return sensitive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if computed, ok := v.checkForComputed(ctyType); ok {
|
||||||
|
return computed
|
||||||
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case ctyType.IsPrimitiveType():
|
case ctyType.IsPrimitiveType():
|
||||||
return v.computeAttributeChangeAsPrimitive(ctyType)
|
return v.computeAttributeChangeAsPrimitive(ctyType)
|
||||||
|
41
internal/command/jsonformat/differ/computed.go
Normal file
41
internal/command/jsonformat/differ/computed.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package differ
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/internal/command/jsonformat/change"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (v Value) checkForComputed(ctyType cty.Type) (change.Change, bool) {
|
||||||
|
unknown := v.isUnknown()
|
||||||
|
|
||||||
|
if !unknown {
|
||||||
|
return change.Change{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// No matter what we do here, we want to treat the after value as explicit.
|
||||||
|
// This is because it is going to be null in the value, and we don't want
|
||||||
|
// the functions in this package to assume this means it has been deleted.
|
||||||
|
v.AfterExplicit = true
|
||||||
|
|
||||||
|
if v.Before == nil {
|
||||||
|
return v.AsChange(change.Computed(change.Change{})), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here, then we have a before value. We're going to model a
|
||||||
|
// delete operation and our renderer later can render the overall change
|
||||||
|
// accurately.
|
||||||
|
|
||||||
|
beforeValue := Value{
|
||||||
|
Before: v.Before,
|
||||||
|
BeforeSensitive: v.BeforeSensitive,
|
||||||
|
}
|
||||||
|
return v.AsChange(change.Computed(beforeValue.ComputeChangeForType(ctyType))), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v Value) isUnknown() bool {
|
||||||
|
if unknown, ok := v.Unknown.(bool); ok {
|
||||||
|
return unknown
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
@ -2,7 +2,7 @@ package differ
|
|||||||
|
|
||||||
import "github.com/hashicorp/terraform/internal/command/jsonformat/change"
|
import "github.com/hashicorp/terraform/internal/command/jsonformat/change"
|
||||||
|
|
||||||
func (v Value) CheckForSensitive() (change.Change, bool) {
|
func (v Value) checkForSensitive() (change.Change, bool) {
|
||||||
beforeSensitive := v.isBeforeSensitive()
|
beforeSensitive := v.isBeforeSensitive()
|
||||||
afterSensitive := v.isAfterSensitive()
|
afterSensitive := v.isAfterSensitive()
|
||||||
|
|
||||||
|
@ -23,9 +23,8 @@ func TestValue_Attribute(t *testing.T) {
|
|||||||
attribute: &jsonprovider.Attribute{
|
attribute: &jsonprovider.Attribute{
|
||||||
AttributeType: []byte("\"string\""),
|
AttributeType: []byte("\"string\""),
|
||||||
},
|
},
|
||||||
expectedAction: plans.Create,
|
expectedAction: plans.Create,
|
||||||
expectedReplace: false,
|
validateChange: change.ValidatePrimitive(nil, strptr("\"new\"")),
|
||||||
validateChange: change.ValidatePrimitive(nil, strptr("\"new\"")),
|
|
||||||
},
|
},
|
||||||
"primitive_delete": {
|
"primitive_delete": {
|
||||||
input: Value{
|
input: Value{
|
||||||
@ -34,9 +33,8 @@ func TestValue_Attribute(t *testing.T) {
|
|||||||
attribute: &jsonprovider.Attribute{
|
attribute: &jsonprovider.Attribute{
|
||||||
AttributeType: []byte("\"string\""),
|
AttributeType: []byte("\"string\""),
|
||||||
},
|
},
|
||||||
expectedAction: plans.Delete,
|
expectedAction: plans.Delete,
|
||||||
expectedReplace: false,
|
validateChange: change.ValidatePrimitive(strptr("\"old\""), nil),
|
||||||
validateChange: change.ValidatePrimitive(strptr("\"old\""), nil),
|
|
||||||
},
|
},
|
||||||
"primitive_update": {
|
"primitive_update": {
|
||||||
input: Value{
|
input: Value{
|
||||||
@ -46,9 +44,8 @@ func TestValue_Attribute(t *testing.T) {
|
|||||||
attribute: &jsonprovider.Attribute{
|
attribute: &jsonprovider.Attribute{
|
||||||
AttributeType: []byte("\"string\""),
|
AttributeType: []byte("\"string\""),
|
||||||
},
|
},
|
||||||
expectedAction: plans.Update,
|
expectedAction: plans.Update,
|
||||||
expectedReplace: false,
|
validateChange: change.ValidatePrimitive(strptr("\"old\""), strptr("\"new\"")),
|
||||||
validateChange: change.ValidatePrimitive(strptr("\"old\""), strptr("\"new\"")),
|
|
||||||
},
|
},
|
||||||
"primitive_set_explicit_null": {
|
"primitive_set_explicit_null": {
|
||||||
input: Value{
|
input: Value{
|
||||||
@ -59,9 +56,8 @@ func TestValue_Attribute(t *testing.T) {
|
|||||||
attribute: &jsonprovider.Attribute{
|
attribute: &jsonprovider.Attribute{
|
||||||
AttributeType: []byte("\"string\""),
|
AttributeType: []byte("\"string\""),
|
||||||
},
|
},
|
||||||
expectedAction: plans.Update,
|
expectedAction: plans.Update,
|
||||||
expectedReplace: false,
|
validateChange: change.ValidatePrimitive(strptr("\"old\""), nil),
|
||||||
validateChange: change.ValidatePrimitive(strptr("\"old\""), nil),
|
|
||||||
},
|
},
|
||||||
"primitive_unset_explicit_null": {
|
"primitive_unset_explicit_null": {
|
||||||
input: Value{
|
input: Value{
|
||||||
@ -72,9 +68,8 @@ func TestValue_Attribute(t *testing.T) {
|
|||||||
attribute: &jsonprovider.Attribute{
|
attribute: &jsonprovider.Attribute{
|
||||||
AttributeType: []byte("\"string\""),
|
AttributeType: []byte("\"string\""),
|
||||||
},
|
},
|
||||||
expectedAction: plans.Update,
|
expectedAction: plans.Update,
|
||||||
expectedReplace: false,
|
validateChange: change.ValidatePrimitive(nil, strptr("\"new\"")),
|
||||||
validateChange: change.ValidatePrimitive(nil, strptr("\"new\"")),
|
|
||||||
},
|
},
|
||||||
"primitive_create_sensitive": {
|
"primitive_create_sensitive": {
|
||||||
input: Value{
|
input: Value{
|
||||||
@ -85,9 +80,8 @@ func TestValue_Attribute(t *testing.T) {
|
|||||||
attribute: &jsonprovider.Attribute{
|
attribute: &jsonprovider.Attribute{
|
||||||
AttributeType: []byte("\"string\""),
|
AttributeType: []byte("\"string\""),
|
||||||
},
|
},
|
||||||
expectedAction: plans.Create,
|
expectedAction: plans.Create,
|
||||||
expectedReplace: false,
|
validateChange: change.ValidateSensitive(nil, "new", false, true),
|
||||||
validateChange: change.ValidateSensitive(nil, "new", false, true),
|
|
||||||
},
|
},
|
||||||
"primitive_delete_sensitive": {
|
"primitive_delete_sensitive": {
|
||||||
input: Value{
|
input: Value{
|
||||||
@ -98,9 +92,8 @@ func TestValue_Attribute(t *testing.T) {
|
|||||||
attribute: &jsonprovider.Attribute{
|
attribute: &jsonprovider.Attribute{
|
||||||
AttributeType: []byte("\"string\""),
|
AttributeType: []byte("\"string\""),
|
||||||
},
|
},
|
||||||
expectedAction: plans.Delete,
|
expectedAction: plans.Delete,
|
||||||
expectedReplace: false,
|
validateChange: change.ValidateSensitive("old", nil, true, false),
|
||||||
validateChange: change.ValidateSensitive("old", nil, true, false),
|
|
||||||
},
|
},
|
||||||
"primitive_update_sensitive": {
|
"primitive_update_sensitive": {
|
||||||
input: Value{
|
input: Value{
|
||||||
@ -112,9 +105,32 @@ func TestValue_Attribute(t *testing.T) {
|
|||||||
attribute: &jsonprovider.Attribute{
|
attribute: &jsonprovider.Attribute{
|
||||||
AttributeType: []byte("\"string\""),
|
AttributeType: []byte("\"string\""),
|
||||||
},
|
},
|
||||||
expectedAction: plans.Update,
|
expectedAction: plans.Update,
|
||||||
expectedReplace: false,
|
validateChange: change.ValidateSensitive("old", "new", true, true),
|
||||||
validateChange: change.ValidateSensitive("old", "new", true, true),
|
},
|
||||||
|
"primitive_create_computed": {
|
||||||
|
input: Value{
|
||||||
|
Before: nil,
|
||||||
|
After: nil,
|
||||||
|
Unknown: true,
|
||||||
|
},
|
||||||
|
attribute: &jsonprovider.Attribute{
|
||||||
|
AttributeType: []byte("\"string\""),
|
||||||
|
},
|
||||||
|
expectedAction: plans.Create,
|
||||||
|
validateChange: change.ValidateComputed(nil),
|
||||||
|
},
|
||||||
|
"primitive_update_computed": {
|
||||||
|
input: Value{
|
||||||
|
Before: "old",
|
||||||
|
After: nil,
|
||||||
|
Unknown: true,
|
||||||
|
},
|
||||||
|
attribute: &jsonprovider.Attribute{
|
||||||
|
AttributeType: []byte("\"string\""),
|
||||||
|
},
|
||||||
|
expectedAction: plans.Update,
|
||||||
|
validateChange: change.ValidateComputed(change.ValidatePrimitive(strptr("\"old\""), nil)),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for name, tc := range tcs {
|
for name, tc := range tcs {
|
||||||
|
Loading…
Reference in New Issue
Block a user