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)
|
||||
}`,
|
||||
},
|
||||
"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 {
|
||||
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 {
|
||||
return func(t *testing.T, change Change) {
|
||||
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 {
|
||||
if ctyType == cty.NilType {
|
||||
return v.ComputeChangeForOutput()
|
||||
}
|
||||
|
||||
switch {
|
||||
case ctyType.IsPrimitiveType():
|
||||
return v.computeAttributeChangeAsPrimitive(ctyType)
|
||||
|
@ -1,7 +1,86 @@
|
||||
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 {
|
||||
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) {
|
||||
// This function tests manipulating primitives: creating, deleting and
|
||||
// updating. It also automatically tests these operations within the
|
||||
|
Loading…
Reference in New Issue
Block a user