mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Add support for outputs in the structured renderer (#32426)
* 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 * Add support for object attributes in the structured renderer * goimports * Add support for the replace paths data in the structured renderer * Add support for maps in the structured renderer * Add support for lists in the structured renderer * goimports * Add support for sets in the structured renderer * goimports * Add support for blocks in the structured renderer * goimports * Add support for outputs in the structured renderer * fix ordering of blocks * remove unused test stub * fix typo
This commit is contained in:
parent
05f1764a0d
commit
9bc5ded27a
@ -1471,6 +1471,44 @@ func TestRenderers(t *testing.T) {
|
|||||||
# (2 unchanged blocks hidden)
|
# (2 unchanged blocks hidden)
|
||||||
}`,
|
}`,
|
||||||
},
|
},
|
||||||
|
"output_map_to_list": {
|
||||||
|
change: Change{
|
||||||
|
renderer: TypeChange(Change{
|
||||||
|
renderer: Map(map[string]Change{
|
||||||
|
"element_one": {
|
||||||
|
renderer: Primitive(strptr("0"), nil),
|
||||||
|
action: plans.Delete,
|
||||||
|
},
|
||||||
|
"element_two": {
|
||||||
|
renderer: Primitive(strptr("1"), nil),
|
||||||
|
action: plans.Delete,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
action: plans.Delete,
|
||||||
|
}, Change{
|
||||||
|
renderer: List([]Change{
|
||||||
|
{
|
||||||
|
renderer: Primitive(nil, strptr("0")),
|
||||||
|
action: plans.Create,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
renderer: Primitive(nil, strptr("1")),
|
||||||
|
action: plans.Create,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
action: plans.Create,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
expected: `
|
||||||
|
{
|
||||||
|
- "element_one" = 0
|
||||||
|
- "element_two" = 1
|
||||||
|
} -> [
|
||||||
|
+ 0,
|
||||||
|
+ 1,
|
||||||
|
]
|
||||||
|
`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for name, tc := range tcs {
|
for name, tc := range tcs {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
|
@ -243,6 +243,20 @@ func ValidateBlock(attributes map[string]ValidateChangeFunc, blocks map[string][
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ValidateTypeChange(before, after ValidateChangeFunc, action plans.Action, replace bool) ValidateChangeFunc {
|
||||||
|
return func(t *testing.T, change Change) {
|
||||||
|
validateChange(t, change, action, replace)
|
||||||
|
|
||||||
|
typeChange, ok := change.renderer.(*typeChangeRenderer)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("invalid renderer type: %T", change.renderer)
|
||||||
|
}
|
||||||
|
|
||||||
|
before(t, typeChange.before)
|
||||||
|
after(t, typeChange.after)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ValidateSensitive(before, after interface{}, beforeSensitive, afterSensitive bool, action plans.Action, replace bool) ValidateChangeFunc {
|
func ValidateSensitive(before, after interface{}, beforeSensitive, afterSensitive bool, action plans.Action, replace bool) ValidateChangeFunc {
|
||||||
return func(t *testing.T, change Change) {
|
return func(t *testing.T, change Change) {
|
||||||
validateChange(t, change, action, replace)
|
validateChange(t, change, action, replace)
|
||||||
|
22
internal/command/jsonformat/change/type_change.go
Normal file
22
internal/command/jsonformat/change/type_change.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package change
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func TypeChange(before, after Change) Renderer {
|
||||||
|
return &typeChangeRenderer{
|
||||||
|
before: before,
|
||||||
|
after: after,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type typeChangeRenderer struct {
|
||||||
|
NoWarningsRenderer
|
||||||
|
|
||||||
|
before Change
|
||||||
|
after Change
|
||||||
|
}
|
||||||
|
|
||||||
|
func (renderer typeChangeRenderer) Render(change Change, indent int, opts RenderOpts) string {
|
||||||
|
opts.overrideNullSuffix = true // Never render null suffix for children of type changes.
|
||||||
|
return fmt.Sprintf("%s [yellow]->[reset] %s", renderer.before.Render(indent, opts), renderer.after.Render(indent, opts))
|
||||||
|
}
|
@ -31,6 +31,10 @@ func (v Value) computeChangeForNestedAttribute(attribute *jsonprovider.NestedTyp
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v Value) computeChangeForType(ctyType cty.Type) change.Change {
|
func (v Value) computeChangeForType(ctyType cty.Type) change.Change {
|
||||||
|
if ctyType == cty.NilType {
|
||||||
|
return v.ComputeChangeForOutput()
|
||||||
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case ctyType.IsPrimitiveType():
|
case ctyType.IsPrimitiveType():
|
||||||
return v.computeAttributeChangeAsPrimitive(ctyType)
|
return v.computeAttributeChangeAsPrimitive(ctyType)
|
||||||
|
@ -1,7 +1,86 @@
|
|||||||
package differ
|
package differ
|
||||||
|
|
||||||
import "github.com/hashicorp/terraform/internal/command/jsonformat/change"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/internal/command/jsonformat/change"
|
||||||
|
"github.com/hashicorp/terraform/internal/plans"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
jsonNumber = "number"
|
||||||
|
jsonObject = "object"
|
||||||
|
jsonArray = "array"
|
||||||
|
jsonBool = "bool"
|
||||||
|
jsonString = "string"
|
||||||
|
jsonNull = "null"
|
||||||
|
)
|
||||||
|
|
||||||
func (v Value) ComputeChangeForOutput() change.Change {
|
func (v Value) ComputeChangeForOutput() change.Change {
|
||||||
panic("not implemented")
|
beforeType := getJsonType(v.Before)
|
||||||
|
afterType := getJsonType(v.After)
|
||||||
|
|
||||||
|
valueToAttribute := func(v Value, jsonType string) change.Change {
|
||||||
|
var res change.Change
|
||||||
|
|
||||||
|
switch jsonType {
|
||||||
|
case jsonNull:
|
||||||
|
res = v.computeAttributeChangeAsPrimitive(cty.NilType)
|
||||||
|
case jsonBool:
|
||||||
|
res = v.computeAttributeChangeAsPrimitive(cty.Bool)
|
||||||
|
case jsonString:
|
||||||
|
res = v.computeAttributeChangeAsPrimitive(cty.String)
|
||||||
|
case jsonNumber:
|
||||||
|
res = v.computeAttributeChangeAsPrimitive(cty.Number)
|
||||||
|
case jsonObject:
|
||||||
|
res = v.computeAttributeChangeAsMap(cty.NilType)
|
||||||
|
case jsonArray:
|
||||||
|
res = v.computeAttributeChangeAsList(cty.NilType)
|
||||||
|
default:
|
||||||
|
panic("unrecognized json type: " + jsonType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
if beforeType == afterType || (beforeType == jsonNull || afterType == jsonNull) {
|
||||||
|
targetType := beforeType
|
||||||
|
if targetType == jsonNull {
|
||||||
|
targetType = afterType
|
||||||
|
}
|
||||||
|
return valueToAttribute(v, targetType)
|
||||||
|
}
|
||||||
|
|
||||||
|
before := valueToAttribute(Value{
|
||||||
|
Before: v.Before,
|
||||||
|
BeforeSensitive: v.BeforeSensitive,
|
||||||
|
}, beforeType)
|
||||||
|
|
||||||
|
after := valueToAttribute(Value{
|
||||||
|
After: v.After,
|
||||||
|
AfterSensitive: v.AfterSensitive,
|
||||||
|
Unknown: v.Unknown,
|
||||||
|
}, afterType)
|
||||||
|
|
||||||
|
return change.New(change.TypeChange(before, after), plans.Update, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getJsonType(json interface{}) string {
|
||||||
|
switch json.(type) {
|
||||||
|
case []interface{}:
|
||||||
|
return jsonArray
|
||||||
|
case float64:
|
||||||
|
return jsonNumber
|
||||||
|
case string:
|
||||||
|
return jsonString
|
||||||
|
case bool:
|
||||||
|
return jsonBool
|
||||||
|
case nil:
|
||||||
|
return jsonNull
|
||||||
|
case map[string]interface{}:
|
||||||
|
return jsonObject
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unrecognized json type %T", json))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1208,6 +1208,231 @@ func TestValue_BlockAttributesAndNestedBlocks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValue_Outputs(t *testing.T) {
|
||||||
|
tcs := map[string]struct {
|
||||||
|
input Value
|
||||||
|
validateChange change.ValidateChangeFunc
|
||||||
|
}{
|
||||||
|
"primitive_create": {
|
||||||
|
input: Value{
|
||||||
|
Before: nil,
|
||||||
|
After: "new",
|
||||||
|
},
|
||||||
|
validateChange: change.ValidatePrimitive(nil, strptr("\"new\""), plans.Create, false),
|
||||||
|
},
|
||||||
|
"map_create": {
|
||||||
|
input: Value{
|
||||||
|
Before: nil,
|
||||||
|
After: map[string]interface{}{
|
||||||
|
"element_one": "new_one",
|
||||||
|
"element_two": "new_two",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validateChange: change.ValidateMap(map[string]change.ValidateChangeFunc{
|
||||||
|
"element_one": change.ValidatePrimitive(nil, strptr("\"new_one\""), plans.Create, false),
|
||||||
|
"element_two": change.ValidatePrimitive(nil, strptr("\"new_two\""), plans.Create, false),
|
||||||
|
}, plans.Create, false),
|
||||||
|
},
|
||||||
|
"list_create": {
|
||||||
|
input: Value{
|
||||||
|
Before: nil,
|
||||||
|
After: []interface{}{
|
||||||
|
"new_one",
|
||||||
|
"new_two",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validateChange: change.ValidateList([]change.ValidateChangeFunc{
|
||||||
|
change.ValidatePrimitive(nil, strptr("\"new_one\""), plans.Create, false),
|
||||||
|
change.ValidatePrimitive(nil, strptr("\"new_two\""), plans.Create, false),
|
||||||
|
}, plans.Create, false),
|
||||||
|
},
|
||||||
|
"primitive_update": {
|
||||||
|
input: Value{
|
||||||
|
Before: "old",
|
||||||
|
After: "new",
|
||||||
|
},
|
||||||
|
validateChange: change.ValidatePrimitive(strptr("\"old\""), strptr("\"new\""), plans.Update, false),
|
||||||
|
},
|
||||||
|
"map_update": {
|
||||||
|
input: Value{
|
||||||
|
Before: map[string]interface{}{
|
||||||
|
"element_one": "old_one",
|
||||||
|
"element_two": "old_two",
|
||||||
|
},
|
||||||
|
After: map[string]interface{}{
|
||||||
|
"element_one": "new_one",
|
||||||
|
"element_two": "new_two",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validateChange: change.ValidateMap(map[string]change.ValidateChangeFunc{
|
||||||
|
"element_one": change.ValidatePrimitive(strptr("\"old_one\""), strptr("\"new_one\""), plans.Update, false),
|
||||||
|
"element_two": change.ValidatePrimitive(strptr("\"old_two\""), strptr("\"new_two\""), plans.Update, false),
|
||||||
|
}, plans.Update, false),
|
||||||
|
},
|
||||||
|
"list_update": {
|
||||||
|
input: Value{
|
||||||
|
Before: []interface{}{
|
||||||
|
"old_one",
|
||||||
|
"old_two",
|
||||||
|
},
|
||||||
|
After: []interface{}{
|
||||||
|
"new_one",
|
||||||
|
"new_two",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validateChange: change.ValidateList([]change.ValidateChangeFunc{
|
||||||
|
change.ValidatePrimitive(strptr("\"old_one\""), nil, plans.Delete, false),
|
||||||
|
change.ValidatePrimitive(strptr("\"old_two\""), nil, plans.Delete, false),
|
||||||
|
change.ValidatePrimitive(nil, strptr("\"new_one\""), plans.Create, false),
|
||||||
|
change.ValidatePrimitive(nil, strptr("\"new_two\""), plans.Create, false),
|
||||||
|
}, plans.Update, false),
|
||||||
|
},
|
||||||
|
"primitive_delete": {
|
||||||
|
input: Value{
|
||||||
|
Before: "old",
|
||||||
|
After: nil,
|
||||||
|
},
|
||||||
|
validateChange: change.ValidatePrimitive(strptr("\"old\""), nil, plans.Delete, false),
|
||||||
|
},
|
||||||
|
"map_delete": {
|
||||||
|
input: Value{
|
||||||
|
Before: map[string]interface{}{
|
||||||
|
"element_one": "old_one",
|
||||||
|
"element_two": "old_two",
|
||||||
|
},
|
||||||
|
After: nil,
|
||||||
|
},
|
||||||
|
validateChange: change.ValidateMap(map[string]change.ValidateChangeFunc{
|
||||||
|
"element_one": change.ValidatePrimitive(strptr("\"old_one\""), nil, plans.Delete, false),
|
||||||
|
"element_two": change.ValidatePrimitive(strptr("\"old_two\""), nil, plans.Delete, false),
|
||||||
|
}, plans.Delete, false),
|
||||||
|
},
|
||||||
|
"list_delete": {
|
||||||
|
input: Value{
|
||||||
|
Before: []interface{}{
|
||||||
|
"old_one",
|
||||||
|
"old_two",
|
||||||
|
},
|
||||||
|
After: nil,
|
||||||
|
},
|
||||||
|
validateChange: change.ValidateList([]change.ValidateChangeFunc{
|
||||||
|
change.ValidatePrimitive(strptr("\"old_one\""), nil, plans.Delete, false),
|
||||||
|
change.ValidatePrimitive(strptr("\"old_two\""), nil, plans.Delete, false),
|
||||||
|
}, plans.Delete, false),
|
||||||
|
},
|
||||||
|
"primitive_to_list": {
|
||||||
|
input: Value{
|
||||||
|
Before: "old",
|
||||||
|
After: []interface{}{
|
||||||
|
"new_one",
|
||||||
|
"new_two",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validateChange: change.ValidateTypeChange(
|
||||||
|
change.ValidatePrimitive(strptr("\"old\""), nil, plans.Delete, false),
|
||||||
|
change.ValidateList([]change.ValidateChangeFunc{
|
||||||
|
change.ValidatePrimitive(nil, strptr("\"new_one\""), plans.Create, false),
|
||||||
|
change.ValidatePrimitive(nil, strptr("\"new_two\""), plans.Create, false),
|
||||||
|
}, plans.Create, false), plans.Update, false),
|
||||||
|
},
|
||||||
|
"primitive_to_map": {
|
||||||
|
input: Value{
|
||||||
|
Before: "old",
|
||||||
|
After: map[string]interface{}{
|
||||||
|
"element_one": "new_one",
|
||||||
|
"element_two": "new_two",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validateChange: change.ValidateTypeChange(
|
||||||
|
change.ValidatePrimitive(strptr("\"old\""), nil, plans.Delete, false),
|
||||||
|
change.ValidateMap(map[string]change.ValidateChangeFunc{
|
||||||
|
"element_one": change.ValidatePrimitive(nil, strptr("\"new_one\""), plans.Create, false),
|
||||||
|
"element_two": change.ValidatePrimitive(nil, strptr("\"new_two\""), plans.Create, false),
|
||||||
|
}, plans.Create, false), plans.Update, false),
|
||||||
|
},
|
||||||
|
"list_to_primitive": {
|
||||||
|
input: Value{
|
||||||
|
Before: []interface{}{
|
||||||
|
"old_one",
|
||||||
|
"old_two",
|
||||||
|
},
|
||||||
|
After: "new",
|
||||||
|
},
|
||||||
|
validateChange: change.ValidateTypeChange(
|
||||||
|
change.ValidateList([]change.ValidateChangeFunc{
|
||||||
|
change.ValidatePrimitive(strptr("\"old_one\""), nil, plans.Delete, false),
|
||||||
|
change.ValidatePrimitive(strptr("\"old_two\""), nil, plans.Delete, false),
|
||||||
|
}, plans.Delete, false),
|
||||||
|
change.ValidatePrimitive(nil, strptr("\"new\""), plans.Create, false),
|
||||||
|
plans.Update, false),
|
||||||
|
},
|
||||||
|
"list_to_map": {
|
||||||
|
input: Value{
|
||||||
|
Before: []interface{}{
|
||||||
|
"old_one",
|
||||||
|
"old_two",
|
||||||
|
},
|
||||||
|
After: map[string]interface{}{
|
||||||
|
"element_one": "new_one",
|
||||||
|
"element_two": "new_two",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validateChange: change.ValidateTypeChange(
|
||||||
|
change.ValidateList([]change.ValidateChangeFunc{
|
||||||
|
change.ValidatePrimitive(strptr("\"old_one\""), nil, plans.Delete, false),
|
||||||
|
change.ValidatePrimitive(strptr("\"old_two\""), nil, plans.Delete, false),
|
||||||
|
}, plans.Delete, false),
|
||||||
|
change.ValidateMap(map[string]change.ValidateChangeFunc{
|
||||||
|
"element_one": change.ValidatePrimitive(nil, strptr("\"new_one\""), plans.Create, false),
|
||||||
|
"element_two": change.ValidatePrimitive(nil, strptr("\"new_two\""), plans.Create, false),
|
||||||
|
}, plans.Create, false), plans.Update, false),
|
||||||
|
},
|
||||||
|
"map_to_primitive": {
|
||||||
|
input: Value{
|
||||||
|
Before: map[string]interface{}{
|
||||||
|
"element_one": "old_one",
|
||||||
|
"element_two": "old_two",
|
||||||
|
},
|
||||||
|
After: "new",
|
||||||
|
},
|
||||||
|
validateChange: change.ValidateTypeChange(
|
||||||
|
change.ValidateMap(map[string]change.ValidateChangeFunc{
|
||||||
|
"element_one": change.ValidatePrimitive(strptr("\"old_one\""), nil, plans.Delete, false),
|
||||||
|
"element_two": change.ValidatePrimitive(strptr("\"old_two\""), nil, plans.Delete, false),
|
||||||
|
}, plans.Delete, false),
|
||||||
|
change.ValidatePrimitive(nil, strptr("\"new\""), plans.Create, false),
|
||||||
|
plans.Update, false),
|
||||||
|
},
|
||||||
|
"map_to_list": {
|
||||||
|
input: Value{
|
||||||
|
Before: map[string]interface{}{
|
||||||
|
"element_one": "old_one",
|
||||||
|
"element_two": "old_two",
|
||||||
|
},
|
||||||
|
After: []interface{}{
|
||||||
|
"new_one",
|
||||||
|
"new_two",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
validateChange: change.ValidateTypeChange(
|
||||||
|
change.ValidateMap(map[string]change.ValidateChangeFunc{
|
||||||
|
"element_one": change.ValidatePrimitive(strptr("\"old_one\""), nil, plans.Delete, false),
|
||||||
|
"element_two": change.ValidatePrimitive(strptr("\"old_two\""), nil, plans.Delete, false),
|
||||||
|
}, plans.Delete, false),
|
||||||
|
change.ValidateList([]change.ValidateChangeFunc{
|
||||||
|
change.ValidatePrimitive(nil, strptr("\"new_one\""), plans.Create, false),
|
||||||
|
change.ValidatePrimitive(nil, strptr("\"new_two\""), plans.Create, false),
|
||||||
|
}, plans.Create, false), plans.Update, false),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range tcs {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
tc.validateChange(t, tc.input.ComputeChange(cty.NilType))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestValue_PrimitiveAttributes(t *testing.T) {
|
func TestValue_PrimitiveAttributes(t *testing.T) {
|
||||||
// This function tests manipulating primitives: creating, deleting and
|
// This function tests manipulating primitives: creating, deleting and
|
||||||
// updating. It also automatically tests these operations within the
|
// updating. It also automatically tests these operations within the
|
||||||
|
Loading…
Reference in New Issue
Block a user