1/* 2Copyright 2019 The Kubernetes Authors. 3Licensed under the Apache License, Version 2.0 (the "License"); 4you may not use this file except in compliance with the License. 5You may obtain a copy of the License at 6 http://www.apache.org/licenses/LICENSE-2.0 7Unless required by applicable law or agreed to in writing, software 8distributed under the License is distributed on an "AS IS" BASIS, 9WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10See the License for the specific language governing permissions and 11limitations under the License. 12*/ 13 14package typed 15 16import ( 17 "sigs.k8s.io/structured-merge-diff/v4/fieldpath" 18 "sigs.k8s.io/structured-merge-diff/v4/schema" 19 "sigs.k8s.io/structured-merge-diff/v4/value" 20) 21 22type removingWalker struct { 23 value value.Value 24 out interface{} 25 schema *schema.Schema 26 toRemove *fieldpath.Set 27 allocator value.Allocator 28 shouldExtract bool 29} 30 31// removeItemsWithSchema will walk the given value and look for items from the toRemove set. 32// Depending on whether shouldExtract is set true or false, it will return a modified version 33// of the input value with either: 34// 1. only the items in the toRemove set (when shouldExtract is true) or 35// 2. the items from the toRemove set removed from the value (when shouldExtract is false). 36func removeItemsWithSchema(val value.Value, toRemove *fieldpath.Set, schema *schema.Schema, typeRef schema.TypeRef, shouldExtract bool) value.Value { 37 w := &removingWalker{ 38 value: val, 39 schema: schema, 40 toRemove: toRemove, 41 allocator: value.NewFreelistAllocator(), 42 shouldExtract: shouldExtract, 43 } 44 resolveSchema(schema, typeRef, val, w) 45 return value.NewValueInterface(w.out) 46} 47 48func (w *removingWalker) doScalar(t *schema.Scalar) ValidationErrors { 49 w.out = w.value.Unstructured() 50 return nil 51} 52 53func (w *removingWalker) doList(t *schema.List) (errs ValidationErrors) { 54 l := w.value.AsListUsing(w.allocator) 55 defer w.allocator.Free(l) 56 // If list is null, empty, or atomic just return 57 if l == nil || l.Length() == 0 || t.ElementRelationship == schema.Atomic { 58 return nil 59 } 60 61 var newItems []interface{} 62 iter := l.RangeUsing(w.allocator) 63 defer w.allocator.Free(iter) 64 for iter.Next() { 65 i, item := iter.Item() 66 // Ignore error because we have already validated this list 67 pe, _ := listItemToPathElement(w.allocator, w.schema, t, i, item) 68 path, _ := fieldpath.MakePath(pe) 69 // save items on the path when we shouldExtract 70 // but ignore them when we are removing (i.e. !w.shouldExtract) 71 if w.toRemove.Has(path) { 72 if w.shouldExtract { 73 newItems = append(newItems, item.Unstructured()) 74 } else { 75 continue 76 } 77 } 78 if subset := w.toRemove.WithPrefix(pe); !subset.Empty() { 79 item = removeItemsWithSchema(item, subset, w.schema, t.ElementType, w.shouldExtract) 80 } else { 81 // don't save items not on the path when we shouldExtract. 82 if w.shouldExtract { 83 continue 84 } 85 } 86 newItems = append(newItems, item.Unstructured()) 87 } 88 if len(newItems) > 0 { 89 w.out = newItems 90 } 91 return nil 92} 93 94func (w *removingWalker) doMap(t *schema.Map) ValidationErrors { 95 m := w.value.AsMapUsing(w.allocator) 96 if m != nil { 97 defer w.allocator.Free(m) 98 } 99 // If map is null, empty, or atomic just return 100 if m == nil || m.Empty() || t.ElementRelationship == schema.Atomic { 101 return nil 102 } 103 104 fieldTypes := map[string]schema.TypeRef{} 105 for _, structField := range t.Fields { 106 fieldTypes[structField.Name] = structField.Type 107 } 108 109 newMap := map[string]interface{}{} 110 m.Iterate(func(k string, val value.Value) bool { 111 pe := fieldpath.PathElement{FieldName: &k} 112 path, _ := fieldpath.MakePath(pe) 113 fieldType := t.ElementType 114 if ft, ok := fieldTypes[k]; ok { 115 fieldType = ft 116 } 117 // save values on the path when we shouldExtract 118 // but ignore them when we are removing (i.e. !w.shouldExtract) 119 if w.toRemove.Has(path) { 120 if w.shouldExtract { 121 newMap[k] = val.Unstructured() 122 } 123 return true 124 } 125 if subset := w.toRemove.WithPrefix(pe); !subset.Empty() { 126 val = removeItemsWithSchema(val, subset, w.schema, fieldType, w.shouldExtract) 127 } else { 128 // don't save values not on the path when we shouldExtract. 129 if w.shouldExtract { 130 return true 131 } 132 } 133 newMap[k] = val.Unstructured() 134 return true 135 }) 136 if len(newMap) > 0 { 137 w.out = newMap 138 } 139 return nil 140} 141