From b097d8873dd789ccf5c44b8a5b29636ea3b0e00b Mon Sep 17 00:00:00 2001 From: Liam Cervante Date: Mon, 9 Jan 2023 12:27:36 +0100 Subject: [PATCH] Add support for the replace paths data in the structured renderer (#32392) * 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 --- internal/command/jsonformat/differ/value.go | 17 ++++-- .../command/jsonformat/differ/value_map.go | 21 +++++++ .../command/jsonformat/differ/value_test.go | 55 +++++++++++++++++++ 3 files changed, 89 insertions(+), 4 deletions(-) diff --git a/internal/command/jsonformat/differ/value.go b/internal/command/jsonformat/differ/value.go index 215cb69a94..3e5b23db25 100644 --- a/internal/command/jsonformat/differ/value.go +++ b/internal/command/jsonformat/differ/value.go @@ -82,7 +82,7 @@ type Value struct { // ReplacePaths generally contains nested slices that describe paths to // elements or attributes that are causing the overall resource to be // replaced. - ReplacePaths interface{} + ReplacePaths []interface{} } // ValueFromJsonChange unmarshals the raw []byte values in the jsonplan.Change @@ -94,7 +94,7 @@ func ValueFromJsonChange(change jsonplan.Change) Value { Unknown: unmarshalGeneric(change.AfterUnknown), BeforeSensitive: unmarshalGeneric(change.BeforeSensitive), AfterSensitive: unmarshalGeneric(change.AfterSensitive), - ReplacePaths: unmarshalGeneric(change.ReplacePaths), + ReplacePaths: decodePaths(unmarshalGeneric(change.ReplacePaths)), } } @@ -129,8 +129,10 @@ func (v Value) AsChange(renderer change.Renderer) change.Change { } func (v Value) replacePath() bool { - if replace, ok := v.ReplacePaths.(bool); ok { - return replace + for _, path := range v.ReplacePaths { + if len(path.([]interface{})) == 0 { + return true + } } return false } @@ -198,3 +200,10 @@ func unmarshalGeneric(raw json.RawMessage) interface{} { } return out } + +func decodePaths(paths interface{}) []interface{} { + if paths == nil { + return nil + } + return paths.([]interface{}) +} diff --git a/internal/command/jsonformat/differ/value_map.go b/internal/command/jsonformat/differ/value_map.go index 3cb803023c..252d006b46 100644 --- a/internal/command/jsonformat/differ/value_map.go +++ b/internal/command/jsonformat/differ/value_map.go @@ -20,6 +20,9 @@ type ValueMap struct { // AfterSensitive contains the after sensitive status of any // elements/attributes of this map/object. AfterSensitive map[string]interface{} + + // ReplacePaths matches the same attributes in Value exactly. + ReplacePaths []interface{} } func (v Value) asMap() ValueMap { @@ -29,6 +32,7 @@ func (v Value) asMap() ValueMap { Unknown: genericToMap(v.Unknown), BeforeSensitive: genericToMap(v.BeforeSensitive), AfterSensitive: genericToMap(v.AfterSensitive), + ReplacePaths: v.ReplacePaths, } } @@ -47,9 +51,26 @@ func (m ValueMap) getChild(key string) Value { Unknown: unknown, BeforeSensitive: beforeSensitive, AfterSensitive: afterSensitive, + ReplacePaths: m.processReplacePaths(key), } } +func (m ValueMap) processReplacePaths(key string) []interface{} { + var ret []interface{} + for _, p := range m.ReplacePaths { + path := p.([]interface{}) + + if len(path) == 0 { + continue + } + + if path[0].(string) == key { + ret = append(ret, path[1:]) + } + } + return ret +} + func getFromGenericMap(generic map[string]interface{}, key string) (interface{}, bool) { if generic == nil { return nil, false diff --git a/internal/command/jsonformat/differ/value_test.go b/internal/command/jsonformat/differ/value_test.go index 1d36bae014..d7981494e9 100644 --- a/internal/command/jsonformat/differ/value_test.go +++ b/internal/command/jsonformat/differ/value_test.go @@ -317,6 +317,48 @@ func TestValue_ObjectAttributes(t *testing.T) { validateAction: plans.NoOp, validateReplace: false, }, + "object_update_replace_self": { + input: Value{ + Before: map[string]interface{}{ + "attribute_one": "old", + }, + After: map[string]interface{}{ + "attribute_one": "new", + }, + ReplacePaths: []interface{}{ + []interface{}{}, + }, + }, + attributes: map[string]cty.Type{ + "attribute_one": cty.String, + }, + validateChanges: map[string]change.ValidateChangeFunc{ + "attribute_one": change.ValidatePrimitive(strptr("\"old\""), strptr("\"new\""), plans.Update, false), + }, + validateAction: plans.Update, + validateReplace: true, + }, + "object_update_replace_attribute": { + input: Value{ + Before: map[string]interface{}{ + "attribute_one": "old", + }, + After: map[string]interface{}{ + "attribute_one": "new", + }, + ReplacePaths: []interface{}{ + []interface{}{"attribute_one"}, + }, + }, + attributes: map[string]cty.Type{ + "attribute_one": cty.String, + }, + validateChanges: map[string]change.ValidateChangeFunc{ + "attribute_one": change.ValidatePrimitive(strptr("\"old\""), strptr("\"new\""), plans.Update, true), + }, + validateAction: plans.Update, + validateReplace: false, + }, } for name, tc := range tcs { @@ -484,6 +526,19 @@ func TestValue_Attribute(t *testing.T) { }, validateChange: change.ValidateComputed(change.ValidatePrimitive(strptr("\"old\""), nil, plans.Delete, false), plans.Update, false), }, + "primitive_update_replace": { + input: Value{ + Before: "old", + After: "new", + ReplacePaths: []interface{}{ + []interface{}{}, // An empty path suggests this attribute should be true. + }, + }, + attribute: &jsonprovider.Attribute{ + AttributeType: unmarshalType(t, cty.String), + }, + validateChange: change.ValidatePrimitive(strptr("\"old\""), strptr("\"new\""), plans.Update, true), + }, } for name, tc := range tcs { t.Run(name, func(t *testing.T) {