github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/configs/hcl2shim/paths.go (about) 1 package hcl2shim 2 3 import ( 4 "fmt" 5 "reflect" 6 "strconv" 7 "strings" 8 9 "github.com/zclconf/go-cty/cty" 10 ) 11 12 // RequiresReplace takes a list of flatmapped paths from a 13 // InstanceDiff.Attributes along with the corresponding cty.Type, and returns 14 // the list of the cty.Paths that are flagged as causing the resource 15 // replacement (RequiresNew). 16 // This will filter out redundant paths, paths that refer to flatmapped indexes 17 // (e.g. "#", "%"), and will return any changes within a set as the path to the 18 // set itself. 19 func RequiresReplace(attrs []string, ty cty.Type) ([]cty.Path, error) { 20 var paths []cty.Path 21 22 for _, attr := range attrs { 23 p, err := requiresReplacePath(attr, ty) 24 if err != nil { 25 return nil, err 26 } 27 28 paths = append(paths, p) 29 } 30 31 // now trim off any trailing paths that aren't GetAttrSteps, since only an 32 // attribute itself can require replacement 33 paths = trimPaths(paths) 34 35 // There may be redundant paths due to set elements or index attributes 36 // Do some ugly n^2 filtering, but these are always fairly small sets. 37 for i := 0; i < len(paths)-1; i++ { 38 for j := i + 1; j < len(paths); j++ { 39 if reflect.DeepEqual(paths[i], paths[j]) { 40 // swap the tail and slice it off 41 paths[j], paths[len(paths)-1] = paths[len(paths)-1], paths[j] 42 paths = paths[:len(paths)-1] 43 j-- 44 } 45 } 46 } 47 48 return paths, nil 49 } 50 51 // trimPaths removes any trailing steps that aren't of type GetAttrSet, since 52 // only an attribute itself can require replacement 53 func trimPaths(paths []cty.Path) []cty.Path { 54 var trimmed []cty.Path 55 for _, path := range paths { 56 path = trimPath(path) 57 if len(path) > 0 { 58 trimmed = append(trimmed, path) 59 } 60 } 61 return trimmed 62 } 63 64 func trimPath(path cty.Path) cty.Path { 65 for len(path) > 0 { 66 _, isGetAttr := path[len(path)-1].(cty.GetAttrStep) 67 if isGetAttr { 68 break 69 } 70 path = path[:len(path)-1] 71 } 72 return path 73 } 74 75 // requiresReplacePath takes a key from a flatmap along with the cty.Type 76 // describing the structure, and returns the cty.Path that would be used to 77 // reference the nested value in the data structure. 78 // This is used specifically to record the RequiresReplace attributes from a 79 // ResourceInstanceDiff. 80 func requiresReplacePath(k string, ty cty.Type) (cty.Path, error) { 81 if k == "" { 82 return nil, nil 83 } 84 if !ty.IsObjectType() { 85 panic(fmt.Sprintf("requires replace path on non-object type: %#v", ty)) 86 } 87 88 path, err := pathFromFlatmapKeyObject(k, ty.AttributeTypes()) 89 if err != nil { 90 return path, fmt.Errorf("[%s] %s", k, err) 91 } 92 return path, nil 93 } 94 95 func pathSplit(p string) (string, string) { 96 parts := strings.SplitN(p, ".", 2) 97 head := parts[0] 98 rest := "" 99 if len(parts) > 1 { 100 rest = parts[1] 101 } 102 return head, rest 103 } 104 105 func pathFromFlatmapKeyObject(key string, atys map[string]cty.Type) (cty.Path, error) { 106 k, rest := pathSplit(key) 107 108 path := cty.Path{cty.GetAttrStep{Name: k}} 109 110 ty, ok := atys[k] 111 if !ok { 112 return path, fmt.Errorf("attribute %q not found", k) 113 } 114 115 if rest == "" { 116 return path, nil 117 } 118 119 p, err := pathFromFlatmapKeyValue(rest, ty) 120 if err != nil { 121 return path, err 122 } 123 124 return append(path, p...), nil 125 } 126 127 func pathFromFlatmapKeyValue(key string, ty cty.Type) (cty.Path, error) { 128 var path cty.Path 129 var err error 130 131 switch { 132 case ty.IsPrimitiveType(): 133 err = fmt.Errorf("invalid step %q with type %#v", key, ty) 134 case ty.IsObjectType(): 135 path, err = pathFromFlatmapKeyObject(key, ty.AttributeTypes()) 136 case ty.IsTupleType(): 137 path, err = pathFromFlatmapKeyTuple(key, ty.TupleElementTypes()) 138 case ty.IsMapType(): 139 path, err = pathFromFlatmapKeyMap(key, ty) 140 case ty.IsListType(): 141 path, err = pathFromFlatmapKeyList(key, ty) 142 case ty.IsSetType(): 143 path, err = pathFromFlatmapKeySet(key, ty) 144 default: 145 err = fmt.Errorf("unrecognized type: %s", ty.FriendlyName()) 146 } 147 148 if err != nil { 149 return path, err 150 } 151 152 return path, nil 153 } 154 155 func pathFromFlatmapKeyTuple(key string, etys []cty.Type) (cty.Path, error) { 156 var path cty.Path 157 var err error 158 159 k, rest := pathSplit(key) 160 161 // we don't need to convert the index keys to paths 162 if k == "#" { 163 return path, nil 164 } 165 166 idx, err := strconv.Atoi(k) 167 if err != nil { 168 return path, err 169 } 170 171 path = cty.Path{cty.IndexStep{Key: cty.NumberIntVal(int64(idx))}} 172 173 if idx >= len(etys) { 174 return path, fmt.Errorf("index %s out of range in %#v", key, etys) 175 } 176 177 if rest == "" { 178 return path, nil 179 } 180 181 ty := etys[idx] 182 183 p, err := pathFromFlatmapKeyValue(rest, ty.ElementType()) 184 if err != nil { 185 return path, err 186 } 187 188 return append(path, p...), nil 189 } 190 191 func pathFromFlatmapKeyMap(key string, ty cty.Type) (cty.Path, error) { 192 var path cty.Path 193 var err error 194 195 k, rest := key, "" 196 if !ty.ElementType().IsPrimitiveType() { 197 k, rest = pathSplit(key) 198 } 199 200 // we don't need to convert the index keys to paths 201 if k == "%" { 202 return path, nil 203 } 204 205 path = cty.Path{cty.IndexStep{Key: cty.StringVal(k)}} 206 207 if rest == "" { 208 return path, nil 209 } 210 211 p, err := pathFromFlatmapKeyValue(rest, ty.ElementType()) 212 if err != nil { 213 return path, err 214 } 215 216 return append(path, p...), nil 217 } 218 219 func pathFromFlatmapKeyList(key string, ty cty.Type) (cty.Path, error) { 220 var path cty.Path 221 var err error 222 223 k, rest := pathSplit(key) 224 225 // we don't need to convert the index keys to paths 226 if key == "#" { 227 return path, nil 228 } 229 230 idx, err := strconv.Atoi(k) 231 if err != nil { 232 return path, err 233 } 234 235 path = cty.Path{cty.IndexStep{Key: cty.NumberIntVal(int64(idx))}} 236 237 if rest == "" { 238 return path, nil 239 } 240 241 p, err := pathFromFlatmapKeyValue(rest, ty.ElementType()) 242 if err != nil { 243 return path, err 244 } 245 246 return append(path, p...), nil 247 } 248 249 func pathFromFlatmapKeySet(key string, ty cty.Type) (cty.Path, error) { 250 // once we hit a set, we can't return consistent paths, so just mark the 251 // set as a whole changed. 252 return nil, nil 253 } 254 255 // FlatmapKeyFromPath returns the flatmap equivalent of the given cty.Path for 256 // use in generating legacy style diffs. 257 func FlatmapKeyFromPath(path cty.Path) string { 258 var parts []string 259 260 for _, step := range path { 261 switch step := step.(type) { 262 case cty.GetAttrStep: 263 parts = append(parts, step.Name) 264 case cty.IndexStep: 265 switch ty := step.Key.Type(); { 266 case ty == cty.String: 267 parts = append(parts, step.Key.AsString()) 268 case ty == cty.Number: 269 i, _ := step.Key.AsBigFloat().Int64() 270 parts = append(parts, strconv.Itoa(int(i))) 271 } 272 } 273 } 274 275 return strings.Join(parts, ".") 276 }