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