github.com/sylr/terraform@v0.11.12-beta1/helper/schema/field_reader_diff.go (about) 1 package schema 2 3 import ( 4 "fmt" 5 "strings" 6 7 "github.com/hashicorp/terraform/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 result = source.Value.(map[string]interface{}) 99 resultSet = true 100 } 101 102 // Next, read all the elements we have in our diff, and apply 103 // the diff to our result. 104 prefix := strings.Join(address, ".") + "." 105 for k, v := range r.Diff.Attributes { 106 if !strings.HasPrefix(k, prefix) { 107 continue 108 } 109 if strings.HasPrefix(k, prefix+"%") { 110 // Ignore the count field 111 continue 112 } 113 114 resultSet = true 115 116 k = k[len(prefix):] 117 if v.NewRemoved { 118 delete(result, k) 119 continue 120 } 121 122 result[k] = v.New 123 } 124 125 key := address[len(address)-1] 126 err = mapValuesToPrimitive(key, result, schema) 127 if err != nil { 128 return FieldReadResult{}, nil 129 } 130 131 var resultVal interface{} 132 if resultSet { 133 resultVal = result 134 } 135 136 return FieldReadResult{ 137 Value: resultVal, 138 Exists: resultSet, 139 }, nil 140 } 141 142 func (r *DiffFieldReader) readPrimitive( 143 address []string, schema *Schema) (FieldReadResult, error) { 144 result, err := r.Source.ReadField(address) 145 if err != nil { 146 return FieldReadResult{}, err 147 } 148 149 attrD, ok := r.Diff.Attributes[strings.Join(address, ".")] 150 if !ok { 151 return result, nil 152 } 153 154 var resultVal string 155 if !attrD.NewComputed { 156 resultVal = attrD.New 157 if attrD.NewExtra != nil { 158 result.ValueProcessed = resultVal 159 if err := mapstructure.WeakDecode(attrD.NewExtra, &resultVal); err != nil { 160 return FieldReadResult{}, err 161 } 162 } 163 } 164 165 result.Computed = attrD.NewComputed 166 result.Exists = true 167 result.Value, err = stringToPrimitive(resultVal, false, schema) 168 if err != nil { 169 return FieldReadResult{}, err 170 } 171 172 return result, nil 173 } 174 175 func (r *DiffFieldReader) readSet( 176 address []string, schema *Schema) (FieldReadResult, error) { 177 prefix := strings.Join(address, ".") + "." 178 179 // Create the set that will be our result 180 set := schema.ZeroValue().(*Set) 181 182 // Go through the map and find all the set items 183 for k, d := range r.Diff.Attributes { 184 if d.NewRemoved { 185 // If the field is removed, we always ignore it 186 continue 187 } 188 if !strings.HasPrefix(k, prefix) { 189 continue 190 } 191 if strings.HasSuffix(k, "#") { 192 // Ignore any count field 193 continue 194 } 195 196 // Split the key, since it might be a sub-object like "idx.field" 197 parts := strings.Split(k[len(prefix):], ".") 198 idx := parts[0] 199 200 raw, err := r.ReadField(append(address, idx)) 201 if err != nil { 202 return FieldReadResult{}, err 203 } 204 if !raw.Exists { 205 // This shouldn't happen because we just verified it does exist 206 panic("missing field in set: " + k + "." + idx) 207 } 208 209 set.Add(raw.Value) 210 } 211 212 // Determine if the set "exists". It exists if there are items or if 213 // the diff explicitly wanted it empty. 214 exists := set.Len() > 0 215 if !exists { 216 // We could check if the diff value is "0" here but I think the 217 // existence of "#" on its own is enough to show it existed. This 218 // protects us in the future from the zero value changing from 219 // "0" to "" breaking us (if that were to happen). 220 if _, ok := r.Diff.Attributes[prefix+"#"]; ok { 221 exists = true 222 } 223 } 224 225 if !exists { 226 result, err := r.Source.ReadField(address) 227 if err != nil { 228 return FieldReadResult{}, err 229 } 230 if result.Exists { 231 return result, nil 232 } 233 } 234 235 return FieldReadResult{ 236 Value: set, 237 Exists: exists, 238 }, nil 239 }