istio.io/istio@v0.0.0-20240520182934-d79c90f27776/operator/pkg/tpath/tree.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 /* 16 tree.go contains functions for traversing and updating a tree constructed from yaml or json.Unmarshal. 17 Nodes in such trees have the form map[interface{}]interface{} or map[interface{}][]interface{}. 18 For some tree updates, like delete or append, it's necessary to have access to the parent node. PathContext is a 19 tree constructed during tree traversal that gives access to ancestor nodes all the way up to the root, which can be 20 used for this purpose. 21 */ 22 package tpath 23 24 import ( 25 "encoding/json" 26 "errors" 27 "fmt" 28 "reflect" 29 "regexp" 30 "strconv" 31 "strings" 32 33 "gopkg.in/yaml.v2" 34 yaml2 "sigs.k8s.io/yaml" 35 36 "istio.io/istio/operator/pkg/util" 37 "istio.io/istio/pkg/log" 38 ) 39 40 var scope = log.RegisterScope("tpath", "tree traverser") 41 42 // PathContext provides a means for traversing a tree towards the root. 43 type PathContext struct { 44 // Parent in the Parent of this PathContext. 45 Parent *PathContext 46 // KeyToChild is the key required to reach the child. 47 KeyToChild any 48 // Node is the actual Node in the data tree. 49 Node any 50 } 51 52 // String implements the Stringer interface. 53 func (nc *PathContext) String() string { 54 ret := "\n--------------- NodeContext ------------------\n" 55 if nc.Parent != nil { 56 ret += fmt.Sprintf("Parent.Node=\n%s\n", nc.Parent.Node) 57 ret += fmt.Sprintf("KeyToChild=%v\n", nc.Parent.KeyToChild) 58 } 59 60 ret += fmt.Sprintf("Node=\n%s\n", nc.Node) 61 ret += "----------------------------------------------\n" 62 63 return ret 64 } 65 66 // GetPathContext returns the PathContext for the Node which has the given path from root. 67 // It returns false and no error if the given path is not found, or an error code in other error situations, like 68 // a malformed path. 69 // It also creates a tree of PathContexts during the traversal so that Parent nodes can be updated if required. This is 70 // required when (say) appending to a list, where the parent list itself must be updated. 71 func GetPathContext(root any, path util.Path, createMissing bool) (*PathContext, bool, error) { 72 return getPathContext(&PathContext{Node: root}, path, path, createMissing) 73 } 74 75 // WritePathContext writes the given value to the Node in the given PathContext. 76 func WritePathContext(nc *PathContext, value any, merge bool) error { 77 scope.Debugf("WritePathContext PathContext=%s, value=%v", nc, value) 78 79 if !util.IsValueNil(value) { 80 return setPathContext(nc, value, merge) 81 } 82 83 scope.Debug("delete") 84 if nc.Parent == nil { 85 return errors.New("cannot delete root element") 86 } 87 88 switch { 89 case isSliceOrPtrInterface(nc.Parent.Node): 90 if err := util.DeleteFromSlicePtr(nc.Parent.Node, nc.Parent.KeyToChild.(int)); err != nil { 91 return err 92 } 93 if isMapOrInterface(nc.Parent.Parent.Node) { 94 return util.InsertIntoMap(nc.Parent.Parent.Node, nc.Parent.Parent.KeyToChild, nc.Parent.Node) 95 } 96 // TODO: The case of deleting a list.list.node element is not currently supported. 97 return fmt.Errorf("cannot delete path: unsupported parent.parent type %T for delete", nc.Parent.Parent.Node) 98 case util.IsMap(nc.Parent.Node): 99 return util.DeleteFromMap(nc.Parent.Node, nc.Parent.KeyToChild) 100 default: 101 } 102 return fmt.Errorf("cannot delete path: unsupported parent type %T for delete", nc.Parent.Node) 103 } 104 105 // WriteNode writes value to the tree in root at the given path, creating any required missing internal nodes in path. 106 func WriteNode(root any, path util.Path, value any) error { 107 pc, _, err := getPathContext(&PathContext{Node: root}, path, path, true) 108 if err != nil { 109 return err 110 } 111 return WritePathContext(pc, value, false) 112 } 113 114 // MergeNode merges value to the tree in root at the given path, creating any required missing internal nodes in path. 115 func MergeNode(root any, path util.Path, value any) error { 116 pc, _, err := getPathContext(&PathContext{Node: root}, path, path, true) 117 if err != nil { 118 return err 119 } 120 return WritePathContext(pc, value, true) 121 } 122 123 // Find returns the value at path from the given tree, or false if the path does not exist. 124 // It behaves differently from GetPathContext in that it never creates map entries at the leaf and does not provide 125 // a way to mutate the parent of the found node. 126 func Find(inputTree map[string]any, path util.Path) (any, bool, error) { 127 scope.Debugf("Find path=%s", path) 128 if len(path) == 0 { 129 return nil, false, fmt.Errorf("path is empty") 130 } 131 node, found := find(inputTree, path) 132 return node, found, nil 133 } 134 135 // Delete sets value at path of input untyped tree to nil 136 func Delete(root map[string]any, path util.Path) (bool, error) { 137 pc, _, err := getPathContext(&PathContext{Node: root}, path, path, false) 138 if err != nil { 139 return false, err 140 } 141 return true, WritePathContext(pc, nil, false) 142 } 143 144 // getPathContext is the internal implementation of GetPathContext. 145 // If createMissing is true, it creates any missing map (but NOT list) path entries in root. 146 func getPathContext(nc *PathContext, fullPath, remainPath util.Path, createMissing bool) (*PathContext, bool, error) { 147 scope.Debugf("getPathContext remainPath=%s, Node=%v", remainPath, nc.Node) 148 if len(remainPath) == 0 { 149 return nc, true, nil 150 } 151 pe := remainPath[0] 152 153 if nc.Node == nil { 154 if !createMissing { 155 return nil, false, fmt.Errorf("node %s is zero", pe) 156 } 157 if util.IsNPathElement(pe) || util.IsKVPathElement(pe) { 158 nc.Node = []any{} 159 } else { 160 nc.Node = make(map[string]any) 161 } 162 } 163 164 v := reflect.ValueOf(nc.Node) 165 if v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface { 166 v = v.Elem() 167 } 168 ncNode := v.Interface() 169 170 // For list types, we need a key to identify the selected list item. This can be either a value key of the 171 // form :matching_value in the case of a leaf list, or a matching key:value in the case of a non-leaf list. 172 if lst, ok := ncNode.([]any); ok { 173 scope.Debug("list type") 174 // If the path element has the form [N], a list element is being selected by index. Return the element at index 175 // N if it exists. 176 if util.IsNPathElement(pe) { 177 idx, err := util.PathN(pe) 178 if err != nil { 179 return nil, false, fmt.Errorf("path %s, index %s: %s", fullPath, pe, err) 180 } 181 var foundNode any 182 if idx >= len(lst) || idx < 0 { 183 if !createMissing { 184 return nil, false, fmt.Errorf("index %d exceeds list length %d at path %s", idx, len(lst), remainPath) 185 } 186 idx = len(lst) 187 foundNode = make(map[string]any) 188 } else { 189 foundNode = lst[idx] 190 } 191 nn := &PathContext{ 192 Parent: nc, 193 Node: foundNode, 194 } 195 nc.KeyToChild = idx 196 return getPathContext(nn, fullPath, remainPath[1:], createMissing) 197 } 198 199 // Otherwise the path element must have form [key:value]. In this case, go through all list elements, which 200 // must have map type, and try to find one which has a matching key:value. 201 for idx, le := range lst { 202 // non-leaf list, expect to match item by key:value. 203 if lm, ok := le.(map[any]any); ok { 204 k, v, err := util.PathKV(pe) 205 if err != nil { 206 return nil, false, fmt.Errorf("path %s: %s", fullPath, err) 207 } 208 if stringsEqual(lm[k], v) { 209 scope.Debugf("found matching kv %v:%v", k, v) 210 nn := &PathContext{ 211 Parent: nc, 212 Node: lm, 213 } 214 nc.KeyToChild = idx 215 nn.KeyToChild = k 216 if len(remainPath) == 1 { 217 scope.Debug("KV terminate") 218 return nn, true, nil 219 } 220 return getPathContext(nn, fullPath, remainPath[1:], createMissing) 221 } 222 continue 223 } 224 // repeat of the block above for the case where tree unmarshals to map[string]interface{}. There doesn't 225 // seem to be a way to merge this case into the above block. 226 if lm, ok := le.(map[string]any); ok { 227 k, v, err := util.PathKV(pe) 228 if err != nil { 229 return nil, false, fmt.Errorf("path %s: %s", fullPath, err) 230 } 231 if stringsEqual(lm[k], v) { 232 scope.Debugf("found matching kv %v:%v", k, v) 233 nn := &PathContext{ 234 Parent: nc, 235 Node: lm, 236 } 237 nc.KeyToChild = idx 238 nn.KeyToChild = k 239 if len(remainPath) == 1 { 240 scope.Debug("KV terminate") 241 return nn, true, nil 242 } 243 return getPathContext(nn, fullPath, remainPath[1:], createMissing) 244 } 245 continue 246 } 247 // leaf list, expect path element [V], match based on value V. 248 v, err := util.PathV(pe) 249 if err != nil { 250 return nil, false, fmt.Errorf("path %s: %s", fullPath, err) 251 } 252 if matchesRegex(v, le) { 253 scope.Debugf("found matching key %v, index %d", le, idx) 254 nn := &PathContext{ 255 Parent: nc, 256 Node: le, 257 } 258 nc.KeyToChild = idx 259 return getPathContext(nn, fullPath, remainPath[1:], createMissing) 260 } 261 } 262 return nil, false, fmt.Errorf("path %s: element %s not found", fullPath, pe) 263 } 264 265 if util.IsMap(ncNode) { 266 scope.Debug("map type") 267 var nn any 268 if m, ok := ncNode.(map[any]any); ok { 269 nn, ok = m[pe] 270 if !ok { 271 // remainPath == 1 means the patch is creation of a new leaf. 272 if createMissing || len(remainPath) == 1 { 273 m[pe] = make(map[any]any) 274 nn = m[pe] 275 } else { 276 return nil, false, fmt.Errorf("path not found at element %s in path %s", pe, fullPath) 277 } 278 } 279 } 280 if reflect.ValueOf(ncNode).IsNil() { 281 ncNode = make(map[string]any) 282 nc.Node = ncNode 283 } 284 if m, ok := ncNode.(map[string]any); ok { 285 nn, ok = m[pe] 286 if !ok { 287 // remainPath == 1 means the patch is creation of a new leaf. 288 if createMissing || len(remainPath) == 1 { 289 nextElementNPath := len(remainPath) > 1 && util.IsNPathElement(remainPath[1]) 290 if nextElementNPath { 291 scope.Debug("map type, slice child") 292 m[pe] = make([]any, 0) 293 } else { 294 scope.Debug("map type, map child") 295 m[pe] = make(map[string]any) 296 } 297 nn = m[pe] 298 } else { 299 return nil, false, fmt.Errorf("path not found at element %s in path %s", pe, fullPath) 300 } 301 } 302 } 303 304 npc := &PathContext{ 305 Parent: nc, 306 Node: nn, 307 } 308 // for slices, use the address so that the slice can be mutated. 309 if util.IsSlice(nn) { 310 npc.Node = &nn 311 } 312 nc.KeyToChild = pe 313 return getPathContext(npc, fullPath, remainPath[1:], createMissing) 314 } 315 316 return nil, false, fmt.Errorf("leaf type %T in non-leaf Node %s", nc.Node, remainPath) 317 } 318 319 // setPathContext writes the given value to the Node in the given PathContext, 320 // enlarging all PathContext lists to ensure all indexes are valid. 321 func setPathContext(nc *PathContext, value any, merge bool) error { 322 processParent, err := setValueContext(nc, value, merge) 323 if err != nil || !processParent { 324 return err 325 } 326 327 // If the path included insertions, process them now 328 if nc.Parent.Parent == nil { 329 return nil 330 } 331 return setPathContext(nc.Parent, nc.Parent.Node, false) // note: tail recursive 332 } 333 334 // setValueContext writes the given value to the Node in the given PathContext. 335 // If setting the value requires growing the final slice, grows it. 336 func setValueContext(nc *PathContext, value any, merge bool) (bool, error) { 337 if nc.Parent == nil { 338 return false, nil 339 } 340 341 vv, mapFromString := tryToUnmarshalStringToYAML(value) 342 343 switch parentNode := nc.Parent.Node.(type) { 344 case *any: 345 switch vParentNode := (*parentNode).(type) { 346 case []any: 347 idx := nc.Parent.KeyToChild.(int) 348 if idx == -1 { 349 // Treat -1 as insert-at-end of list 350 idx = len(vParentNode) 351 } 352 353 if idx >= len(vParentNode) { 354 newElements := make([]any, idx-len(vParentNode)+1) 355 vParentNode = append(vParentNode, newElements...) 356 *parentNode = vParentNode 357 } 358 359 merged, err := mergeConditional(vv, nc.Node, merge) 360 if err != nil { 361 return false, err 362 } 363 364 vParentNode[idx] = merged 365 nc.Node = merged 366 default: 367 return false, fmt.Errorf("don't know about vtype %T", vParentNode) 368 } 369 case map[string]any: 370 key := nc.Parent.KeyToChild.(string) 371 372 // Update is treated differently depending on whether the value is a scalar or map type. If scalar, 373 // insert a new element into the terminal node, otherwise replace the terminal node with the new subtree. 374 if ncNode, ok := nc.Node.(*any); ok && !mapFromString { 375 switch vNcNode := (*ncNode).(type) { 376 case []any: 377 switch vv.(type) { 378 case map[string]any: 379 // the vv is a map, and the node is a slice 380 mergedValue := append(vNcNode, vv) 381 parentNode[key] = mergedValue 382 case *any: 383 merged, err := mergeConditional(vv, vNcNode, merge) 384 if err != nil { 385 return false, err 386 } 387 388 parentNode[key] = merged 389 nc.Node = merged 390 default: 391 // the vv is an basic JSON type (int, float, string, bool) 392 vv = append(vNcNode, vv) 393 parentNode[key] = vv 394 nc.Node = vv 395 } 396 default: 397 return false, fmt.Errorf("don't know about vnc type %T", vNcNode) 398 } 399 } else { 400 // For map passed as string type, the root is the new key. 401 if mapFromString { 402 if err := util.DeleteFromMap(nc.Parent.Node, nc.Parent.KeyToChild); err != nil { 403 return false, err 404 } 405 vm := vv.(map[string]any) 406 newKey := getTreeRoot(vm) 407 return false, util.InsertIntoMap(nc.Parent.Node, newKey, vm[newKey]) 408 } 409 parentNode[key] = vv 410 nc.Node = vv 411 } 412 // TODO `map[interface{}]interface{}` is used by tests in operator/cmd/mesh, we should add our own tests 413 case map[any]any: 414 key := nc.Parent.KeyToChild.(string) 415 parentNode[key] = vv 416 nc.Node = vv 417 default: 418 return false, fmt.Errorf("don't know about type %T", parentNode) 419 } 420 421 return true, nil 422 } 423 424 // mergeConditional returns a merge of newVal and originalVal if merge is true, otherwise it returns newVal. 425 func mergeConditional(newVal, originalVal any, merge bool) (any, error) { 426 if !merge || util.IsValueNilOrDefault(originalVal) { 427 return newVal, nil 428 } 429 newS, err := yaml.Marshal(newVal) 430 if err != nil { 431 return nil, err 432 } 433 if util.IsYAMLEmpty(string(newS)) { 434 return originalVal, nil 435 } 436 originalS, err := yaml.Marshal(originalVal) 437 if err != nil { 438 return nil, err 439 } 440 if util.IsYAMLEmpty(string(originalS)) { 441 return newVal, nil 442 } 443 444 mergedS, err := util.OverlayYAML(string(originalS), string(newS)) 445 if err != nil { 446 return nil, err 447 } 448 449 if util.IsMap(originalVal) { 450 // For JSON compatibility 451 out := make(map[string]any) 452 if err := yaml.Unmarshal([]byte(mergedS), &out); err != nil { 453 return nil, err 454 } 455 return out, nil 456 } 457 // For scalars and slices, copy the type 458 out := originalVal 459 if err := yaml.Unmarshal([]byte(mergedS), &out); err != nil { 460 return nil, err 461 } 462 return out, nil 463 } 464 465 // find returns the value at path from the given tree, or false if the path does not exist. 466 func find(treeNode any, path util.Path) (any, bool) { 467 if len(path) == 0 || treeNode == nil { 468 return nil, false 469 } 470 switch nt := treeNode.(type) { 471 case map[any]any: 472 val := nt[path[0]] 473 if val == nil { 474 return nil, false 475 } 476 if len(path) == 1 { 477 return val, true 478 } 479 return find(val, path[1:]) 480 case map[string]any: 481 val := nt[path[0]] 482 if val == nil { 483 return nil, false 484 } 485 if len(path) == 1 { 486 return val, true 487 } 488 return find(val, path[1:]) 489 case []any: 490 idx, err := strconv.Atoi(path[0]) 491 if err != nil { 492 return nil, false 493 } 494 if idx >= len(nt) { 495 return nil, false 496 } 497 val := nt[idx] 498 return find(val, path[1:]) 499 default: 500 return nil, false 501 } 502 } 503 504 // stringsEqual reports whether the string representations of a and b are equal. a and b may have different types. 505 func stringsEqual(a, b any) bool { 506 return fmt.Sprint(a) == fmt.Sprint(b) 507 } 508 509 // matchesRegex reports whether str regex matches pattern. 510 func matchesRegex(pattern, str any) bool { 511 match, err := regexp.MatchString(fmt.Sprint(pattern), fmt.Sprint(str)) 512 if err != nil { 513 log.Errorf("bad regex expression %s", fmt.Sprint(pattern)) 514 return false 515 } 516 scope.Debugf("%v regex %v? %v\n", pattern, str, match) 517 return match 518 } 519 520 // isSliceOrPtrInterface reports whether v is a slice, a ptr to slice or interface to slice. 521 func isSliceOrPtrInterface(v any) bool { 522 vv := reflect.ValueOf(v) 523 if vv.Kind() == reflect.Ptr { 524 vv = vv.Elem() 525 } 526 if vv.Kind() == reflect.Interface { 527 vv = vv.Elem() 528 } 529 return vv.Kind() == reflect.Slice 530 } 531 532 // isMapOrInterface reports whether v is a map, or interface to a map. 533 func isMapOrInterface(v any) bool { 534 vv := reflect.ValueOf(v) 535 if vv.Kind() == reflect.Interface { 536 vv = vv.Elem() 537 } 538 return vv.Kind() == reflect.Map 539 } 540 541 // tryToUnmarshalStringToYAML tries to unmarshal something that may be a YAML list or map into a structure. If not 542 // possible, returns original scalar value. 543 func tryToUnmarshalStringToYAML(s any) (any, bool) { 544 // If value type is a string it could either be a literal string or a map type passed as a string. Try to unmarshal 545 // to discover it's the latter. 546 vv := s 547 548 if reflect.TypeOf(vv).Kind() == reflect.String { 549 sv := strings.Split(vv.(string), "\n") 550 // Need to be careful not to transform string literals into maps unless they really are maps, since scalar handling 551 // is different for inserts. 552 if len(sv) == 1 && strings.Contains(s.(string), ": ") || 553 len(sv) > 1 && strings.Contains(s.(string), ":") { 554 nv := make(map[string]any) 555 if err := json.Unmarshal([]byte(vv.(string)), &nv); err == nil { 556 // treat JSON as string 557 return vv, false 558 } 559 if err := yaml2.Unmarshal([]byte(vv.(string)), &nv); err == nil { 560 return nv, true 561 } 562 } 563 } 564 // looks like a literal or failed unmarshal, return original type. 565 return vv, false 566 } 567 568 // getTreeRoot returns the first key found in m. It assumes a single root tree. 569 func getTreeRoot(m map[string]any) string { 570 for k := range m { 571 return k 572 } 573 return "" 574 }