github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/jsonformat/jsondiff/diff.go (about) 1 package jsondiff 2 3 import ( 4 "reflect" 5 6 "github.com/zclconf/go-cty/cty" 7 8 "github.com/hashicorp/terraform/internal/command/jsonformat/collections" 9 "github.com/hashicorp/terraform/internal/command/jsonformat/computed" 10 "github.com/hashicorp/terraform/internal/command/jsonformat/differ/attribute_path" 11 "github.com/hashicorp/terraform/internal/plans" 12 ) 13 14 type TransformPrimitiveJson func(before, after interface{}, ctype cty.Type, action plans.Action) computed.Diff 15 type TransformObjectJson func(map[string]computed.Diff, plans.Action) computed.Diff 16 type TransformArrayJson func([]computed.Diff, plans.Action) computed.Diff 17 type TransformTypeChangeJson func(before, after computed.Diff, action plans.Action) computed.Diff 18 19 // JsonOpts defines the external callback functions that callers should 20 // implement to process the supplied diffs. 21 type JsonOpts struct { 22 Primitive TransformPrimitiveJson 23 Object TransformObjectJson 24 Array TransformArrayJson 25 TypeChange TransformTypeChangeJson 26 } 27 28 // Transform accepts a generic before and after value that is assumed to be JSON 29 // formatted and transforms it into a computed.Diff, using the callbacks 30 // supplied in the JsonOpts class. 31 func (opts JsonOpts) Transform(before, after interface{}, beforeExplicit, afterExplicit bool, relevantAttributes attribute_path.Matcher) computed.Diff { 32 beforeType := GetType(before) 33 afterType := GetType(after) 34 35 deleted := afterType == Null && !afterExplicit 36 created := beforeType == Null && !beforeExplicit 37 38 if beforeType == afterType || (created || deleted) { 39 targetType := beforeType 40 if targetType == Null { 41 targetType = afterType 42 } 43 return opts.processUpdate(before, after, beforeExplicit, afterExplicit, targetType, relevantAttributes) 44 } 45 46 b := opts.processUpdate(before, nil, beforeExplicit, false, beforeType, relevantAttributes) 47 a := opts.processUpdate(nil, after, false, afterExplicit, afterType, relevantAttributes) 48 return opts.TypeChange(b, a, plans.Update) 49 } 50 51 func (opts JsonOpts) processUpdate(before, after interface{}, beforeExplicit, afterExplicit bool, jtype Type, relevantAttributes attribute_path.Matcher) computed.Diff { 52 switch jtype { 53 case Null: 54 return opts.processPrimitive(before, after, beforeExplicit, afterExplicit, cty.NilType) 55 case Bool: 56 return opts.processPrimitive(before, after, beforeExplicit, afterExplicit, cty.Bool) 57 case String: 58 return opts.processPrimitive(before, after, beforeExplicit, afterExplicit, cty.String) 59 case Number: 60 return opts.processPrimitive(before, after, beforeExplicit, afterExplicit, cty.Number) 61 case Object: 62 var b, a map[string]interface{} 63 64 if before != nil { 65 b = before.(map[string]interface{}) 66 } 67 68 if after != nil { 69 a = after.(map[string]interface{}) 70 } 71 72 return opts.processObject(b, a, relevantAttributes) 73 case Array: 74 var b, a []interface{} 75 76 if before != nil { 77 b = before.([]interface{}) 78 } 79 80 if after != nil { 81 a = after.([]interface{}) 82 } 83 84 return opts.processArray(b, a) 85 default: 86 panic("unrecognized json type: " + jtype) 87 } 88 } 89 90 func (opts JsonOpts) processPrimitive(before, after interface{}, beforeExplicit, afterExplicit bool, ctype cty.Type) computed.Diff { 91 beforeMissing := before == nil && !beforeExplicit 92 afterMissing := after == nil && !afterExplicit 93 94 var action plans.Action 95 switch { 96 case beforeMissing && !afterMissing: 97 action = plans.Create 98 case !beforeMissing && afterMissing: 99 action = plans.Delete 100 case reflect.DeepEqual(before, after): 101 action = plans.NoOp 102 default: 103 action = plans.Update 104 } 105 106 return opts.Primitive(before, after, ctype, action) 107 } 108 109 func (opts JsonOpts) processArray(before, after []interface{}) computed.Diff { 110 processIndices := func(beforeIx, afterIx int) computed.Diff { 111 var b, a interface{} 112 113 beforeExplicit := false 114 afterExplicit := false 115 116 if beforeIx >= 0 && beforeIx < len(before) { 117 b = before[beforeIx] 118 beforeExplicit = true 119 } 120 if afterIx >= 0 && afterIx < len(after) { 121 a = after[afterIx] 122 afterExplicit = true 123 } 124 125 // It's actually really difficult to render the diffs when some indices 126 // within a list are relevant and others aren't. To make this simpler 127 // we just treat all children of a relevant list as also relevant. 128 // 129 // Interestingly the terraform plan builder also agrees with this, and 130 // never sets relevant attributes beneath lists or sets. We're just 131 // going to enforce this logic here as well. If the list is relevant 132 // (decided elsewhere), then every element in the list is also relevant. 133 return opts.Transform(b, a, beforeExplicit, afterExplicit, attribute_path.AlwaysMatcher()) 134 } 135 136 isObjType := func(value interface{}) bool { 137 return GetType(value) == Object 138 } 139 140 return opts.Array(collections.TransformSlice(before, after, processIndices, isObjType)) 141 } 142 143 func (opts JsonOpts) processObject(before, after map[string]interface{}, relevantAttributes attribute_path.Matcher) computed.Diff { 144 return opts.Object(collections.TransformMap(before, after, func(key string) computed.Diff { 145 beforeChild, beforeExplicit := before[key] 146 afterChild, afterExplicit := after[key] 147 148 childRelevantAttributes := relevantAttributes.GetChildWithKey(key) 149 if !childRelevantAttributes.MatchesPartial() { 150 // Mark non-relevant attributes as unchanged. 151 afterChild = beforeChild 152 afterExplicit = beforeExplicit 153 154 } 155 156 return opts.Transform(beforeChild, afterChild, beforeExplicit, afterExplicit, childRelevantAttributes) 157 })) 158 }