github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/legacy/helper/schema/field_reader_diff.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package schema 5 6 import ( 7 "fmt" 8 "strings" 9 10 "github.com/mitchellh/mapstructure" 11 "github.com/terramate-io/tf/legacy/terraform" 12 ) 13 14 // DiffFieldReader reads fields out of a diff structures. 15 // 16 // It also requires access to a Reader that reads fields from the structure 17 // that the diff was derived from. This is usually the state. This is required 18 // because a diff on its own doesn't have complete data about full objects 19 // such as maps. 20 // 21 // The Source MUST be the data that the diff was derived from. If it isn't, 22 // the behavior of this struct is undefined. 23 // 24 // Reading fields from a DiffFieldReader is identical to reading from 25 // Source except the diff will be applied to the end result. 26 // 27 // The "Exists" field on the result will be set to true if the complete 28 // field exists whether its from the source, diff, or a combination of both. 29 // It cannot be determined whether a retrieved value is composed of 30 // diff elements. 31 type DiffFieldReader struct { 32 Diff *terraform.InstanceDiff 33 Source FieldReader 34 Schema map[string]*Schema 35 36 // cache for memoizing ReadField calls. 37 cache map[string]cachedFieldReadResult 38 } 39 40 type cachedFieldReadResult struct { 41 val FieldReadResult 42 err error 43 } 44 45 func (r *DiffFieldReader) ReadField(address []string) (FieldReadResult, error) { 46 if r.cache == nil { 47 r.cache = make(map[string]cachedFieldReadResult) 48 } 49 50 // Create the cache key by joining around a value that isn't a valid part 51 // of an address. This assumes that the Source and Schema are not changed 52 // for the life of this DiffFieldReader. 53 cacheKey := strings.Join(address, "|") 54 if cached, ok := r.cache[cacheKey]; ok { 55 return cached.val, cached.err 56 } 57 58 schemaList := addrToSchema(address, r.Schema) 59 if len(schemaList) == 0 { 60 r.cache[cacheKey] = cachedFieldReadResult{} 61 return FieldReadResult{}, nil 62 } 63 64 var res FieldReadResult 65 var err error 66 67 schema := schemaList[len(schemaList)-1] 68 switch schema.Type { 69 case TypeBool, TypeInt, TypeFloat, TypeString: 70 res, err = r.readPrimitive(address, schema) 71 case TypeList: 72 res, err = readListField(r, address, schema) 73 case TypeMap: 74 res, err = r.readMap(address, schema) 75 case TypeSet: 76 res, err = r.readSet(address, schema) 77 case typeObject: 78 res, err = readObjectField(r, address, schema.Elem.(map[string]*Schema)) 79 default: 80 panic(fmt.Sprintf("Unknown type: %#v", schema.Type)) 81 } 82 83 r.cache[cacheKey] = cachedFieldReadResult{ 84 val: res, 85 err: err, 86 } 87 return res, err 88 } 89 90 func (r *DiffFieldReader) readMap( 91 address []string, schema *Schema) (FieldReadResult, error) { 92 result := make(map[string]interface{}) 93 resultSet := false 94 95 // First read the map from the underlying source 96 source, err := r.Source.ReadField(address) 97 if err != nil { 98 return FieldReadResult{}, err 99 } 100 if source.Exists { 101 // readMap may return a nil value, or an unknown value placeholder in 102 // some cases, causing the type assertion to panic if we don't assign the ok value 103 result, _ = source.Value.(map[string]interface{}) 104 resultSet = true 105 } 106 107 // Next, read all the elements we have in our diff, and apply 108 // the diff to our result. 109 prefix := strings.Join(address, ".") + "." 110 for k, v := range r.Diff.Attributes { 111 if !strings.HasPrefix(k, prefix) { 112 continue 113 } 114 if strings.HasPrefix(k, prefix+"%") { 115 // Ignore the count field 116 continue 117 } 118 119 resultSet = true 120 121 k = k[len(prefix):] 122 if v.NewRemoved { 123 delete(result, k) 124 continue 125 } 126 127 result[k] = v.New 128 } 129 130 key := address[len(address)-1] 131 err = mapValuesToPrimitive(key, result, schema) 132 if err != nil { 133 return FieldReadResult{}, nil 134 } 135 136 var resultVal interface{} 137 if resultSet { 138 resultVal = result 139 } 140 141 return FieldReadResult{ 142 Value: resultVal, 143 Exists: resultSet, 144 }, nil 145 } 146 147 func (r *DiffFieldReader) readPrimitive( 148 address []string, schema *Schema) (FieldReadResult, error) { 149 result, err := r.Source.ReadField(address) 150 if err != nil { 151 return FieldReadResult{}, err 152 } 153 154 attrD, ok := r.Diff.Attributes[strings.Join(address, ".")] 155 if !ok { 156 return result, nil 157 } 158 159 var resultVal string 160 if !attrD.NewComputed { 161 resultVal = attrD.New 162 if attrD.NewExtra != nil { 163 result.ValueProcessed = resultVal 164 if err := mapstructure.WeakDecode(attrD.NewExtra, &resultVal); err != nil { 165 return FieldReadResult{}, err 166 } 167 } 168 } 169 170 result.Computed = attrD.NewComputed 171 result.Exists = true 172 result.Value, err = stringToPrimitive(resultVal, false, schema) 173 if err != nil { 174 return FieldReadResult{}, err 175 } 176 177 return result, nil 178 } 179 180 func (r *DiffFieldReader) readSet( 181 address []string, schema *Schema) (FieldReadResult, error) { 182 // copy address to ensure we don't modify the argument 183 address = append([]string(nil), address...) 184 185 prefix := strings.Join(address, ".") + "." 186 187 // Create the set that will be our result 188 set := schema.ZeroValue().(*Set) 189 190 // Go through the map and find all the set items 191 for k, d := range r.Diff.Attributes { 192 if d.NewRemoved { 193 // If the field is removed, we always ignore it 194 continue 195 } 196 if !strings.HasPrefix(k, prefix) { 197 continue 198 } 199 if strings.HasSuffix(k, "#") { 200 // Ignore any count field 201 continue 202 } 203 204 // Split the key, since it might be a sub-object like "idx.field" 205 parts := strings.Split(k[len(prefix):], ".") 206 idx := parts[0] 207 208 raw, err := r.ReadField(append(address, idx)) 209 if err != nil { 210 return FieldReadResult{}, err 211 } 212 if !raw.Exists { 213 // This shouldn't happen because we just verified it does exist 214 panic("missing field in set: " + k + "." + idx) 215 } 216 217 set.Add(raw.Value) 218 } 219 220 // Determine if the set "exists". It exists if there are items or if 221 // the diff explicitly wanted it empty. 222 exists := set.Len() > 0 223 if !exists { 224 // We could check if the diff value is "0" here but I think the 225 // existence of "#" on its own is enough to show it existed. This 226 // protects us in the future from the zero value changing from 227 // "0" to "" breaking us (if that were to happen). 228 if _, ok := r.Diff.Attributes[prefix+"#"]; ok { 229 exists = true 230 } 231 } 232 233 if !exists { 234 result, err := r.Source.ReadField(address) 235 if err != nil { 236 return FieldReadResult{}, err 237 } 238 if result.Exists { 239 return result, nil 240 } 241 } 242 243 return FieldReadResult{ 244 Value: set, 245 Exists: exists, 246 }, nil 247 }