github.com/richardmarshall/terraform@v0.9.5-0.20170429023105-15704cc6ee35/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 34 func (r *DiffFieldReader) ReadField(address []string) (FieldReadResult, error) { 35 schemaList := addrToSchema(address, r.Schema) 36 if len(schemaList) == 0 { 37 return FieldReadResult{}, nil 38 } 39 40 schema := schemaList[len(schemaList)-1] 41 switch schema.Type { 42 case TypeBool, TypeInt, TypeFloat, TypeString: 43 return r.readPrimitive(address, schema) 44 case TypeList: 45 return readListField(r, address, schema) 46 case TypeMap: 47 return r.readMap(address, schema) 48 case TypeSet: 49 return r.readSet(address, schema) 50 case typeObject: 51 return readObjectField(r, address, schema.Elem.(map[string]*Schema)) 52 default: 53 panic(fmt.Sprintf("Unknown type: %#v", schema.Type)) 54 } 55 } 56 57 func (r *DiffFieldReader) readMap( 58 address []string, schema *Schema) (FieldReadResult, error) { 59 result := make(map[string]interface{}) 60 resultSet := false 61 62 // First read the map from the underlying source 63 source, err := r.Source.ReadField(address) 64 if err != nil { 65 return FieldReadResult{}, err 66 } 67 if source.Exists { 68 result = source.Value.(map[string]interface{}) 69 resultSet = true 70 } 71 72 // Next, read all the elements we have in our diff, and apply 73 // the diff to our result. 74 prefix := strings.Join(address, ".") + "." 75 for k, v := range r.Diff.Attributes { 76 if !strings.HasPrefix(k, prefix) { 77 continue 78 } 79 if strings.HasPrefix(k, prefix+"%") { 80 // Ignore the count field 81 continue 82 } 83 84 resultSet = true 85 86 k = k[len(prefix):] 87 if v.NewRemoved { 88 delete(result, k) 89 continue 90 } 91 92 result[k] = v.New 93 } 94 95 err = mapValuesToPrimitive(result, schema) 96 if err != nil { 97 return FieldReadResult{}, nil 98 } 99 100 var resultVal interface{} 101 if resultSet { 102 resultVal = result 103 } 104 105 return FieldReadResult{ 106 Value: resultVal, 107 Exists: resultSet, 108 }, nil 109 } 110 111 func (r *DiffFieldReader) readPrimitive( 112 address []string, schema *Schema) (FieldReadResult, error) { 113 result, err := r.Source.ReadField(address) 114 if err != nil { 115 return FieldReadResult{}, err 116 } 117 118 attrD, ok := r.Diff.Attributes[strings.Join(address, ".")] 119 if !ok { 120 return result, nil 121 } 122 123 var resultVal string 124 if !attrD.NewComputed { 125 resultVal = attrD.New 126 if attrD.NewExtra != nil { 127 result.ValueProcessed = resultVal 128 if err := mapstructure.WeakDecode(attrD.NewExtra, &resultVal); err != nil { 129 return FieldReadResult{}, err 130 } 131 } 132 } 133 134 result.Computed = attrD.NewComputed 135 result.Exists = true 136 result.Value, err = stringToPrimitive(resultVal, false, schema) 137 if err != nil { 138 return FieldReadResult{}, err 139 } 140 141 return result, nil 142 } 143 144 func (r *DiffFieldReader) readSet( 145 address []string, schema *Schema) (FieldReadResult, error) { 146 prefix := strings.Join(address, ".") + "." 147 148 // Create the set that will be our result 149 set := schema.ZeroValue().(*Set) 150 151 // Go through the map and find all the set items 152 for k, d := range r.Diff.Attributes { 153 if d.NewRemoved { 154 // If the field is removed, we always ignore it 155 continue 156 } 157 if !strings.HasPrefix(k, prefix) { 158 continue 159 } 160 if strings.HasSuffix(k, "#") { 161 // Ignore any count field 162 continue 163 } 164 165 // Split the key, since it might be a sub-object like "idx.field" 166 parts := strings.Split(k[len(prefix):], ".") 167 idx := parts[0] 168 169 raw, err := r.ReadField(append(address, idx)) 170 if err != nil { 171 return FieldReadResult{}, err 172 } 173 if !raw.Exists { 174 // This shouldn't happen because we just verified it does exist 175 panic("missing field in set: " + k + "." + idx) 176 } 177 178 set.Add(raw.Value) 179 } 180 181 // Determine if the set "exists". It exists if there are items or if 182 // the diff explicitly wanted it empty. 183 exists := set.Len() > 0 184 if !exists { 185 // We could check if the diff value is "0" here but I think the 186 // existence of "#" on its own is enough to show it existed. This 187 // protects us in the future from the zero value changing from 188 // "0" to "" breaking us (if that were to happen). 189 if _, ok := r.Diff.Attributes[prefix+"#"]; ok { 190 exists = true 191 } 192 } 193 194 if !exists { 195 result, err := r.Source.ReadField(address) 196 if err != nil { 197 return FieldReadResult{}, err 198 } 199 if result.Exists { 200 return result, nil 201 } 202 } 203 204 return FieldReadResult{ 205 Value: set, 206 Exists: exists, 207 }, nil 208 }