istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/util/yml/cache.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 package yml 16 17 import ( 18 "fmt" 19 "os" 20 "path" 21 "strings" 22 "sync" 23 ) 24 25 // Cache tracks the life-cycle of Yaml based resources. It stores single-part Yaml files on disk and updates the 26 // files as needed, as the resources change. 27 type Cache struct { 28 mu sync.Mutex 29 30 discriminator int64 31 resources map[CacheKey]*resourceState 32 dir string 33 } 34 35 // CacheKey is a key representing a tracked Yaml based resource. 36 type CacheKey struct { 37 group string 38 kind string 39 namespace string 40 name string 41 } 42 43 type resourceState struct { 44 part Part 45 file string 46 } 47 48 // NewCache returns a new Cache instance 49 func NewCache(dir string) *Cache { 50 return &Cache{ 51 resources: make(map[CacheKey]*resourceState), 52 dir: dir, 53 } 54 } 55 56 // Apply adds the given yamlText contents as part of a given context name. If there is an existing context 57 // with the given name, then a diffgram will be generated. 58 func (c *Cache) Apply(yamlText string) ([]CacheKey, error) { 59 c.mu.Lock() 60 defer c.mu.Unlock() 61 62 parts, err := Parse(yamlText) 63 if err != nil { 64 return nil, err 65 } 66 67 var result []CacheKey 68 newKeys := make(map[CacheKey]struct{}) 69 70 for _, p := range parts { 71 key := toKey(p.Descriptor) 72 result = append(result, key) 73 newKeys[key] = struct{}{} 74 75 state, found := c.resources[key] 76 if found { 77 if err = c.deleteFile(state.file); err != nil { 78 return nil, err 79 } 80 } else { 81 state = &resourceState{} 82 c.resources[key] = state 83 } 84 state.file = c.generateFileName(key) 85 state.part = p 86 87 if err = c.writeFile(state.file, p.Contents); err != nil { 88 return nil, err 89 } 90 } 91 92 return result, nil 93 } 94 95 // Delete the resources from the given yamlText 96 func (c *Cache) Delete(yamlText string) error { 97 c.mu.Lock() 98 defer c.mu.Unlock() 99 100 parts, err := Parse(yamlText) 101 if err != nil { 102 return err 103 } 104 105 for _, p := range parts { 106 key := toKey(p.Descriptor) 107 108 state, found := c.resources[key] 109 if found { 110 if err = c.deleteFile(state.file); err != nil { 111 return err 112 } 113 114 delete(c.resources, key) 115 } 116 } 117 118 return nil 119 } 120 121 // AllKeys returns all resource keys in the tracker. 122 func (c *Cache) AllKeys() []CacheKey { 123 c.mu.Lock() 124 defer c.mu.Unlock() 125 126 var result []CacheKey 127 128 for k := range c.resources { 129 result = append(result, k) 130 } 131 132 return result 133 } 134 135 // Clear all tracked yaml content. 136 func (c *Cache) Clear() error { 137 c.mu.Lock() 138 defer c.mu.Unlock() 139 140 for _, s := range c.resources { 141 if err := c.deleteFile(s.file); err != nil { 142 return err 143 } 144 } 145 146 c.resources = make(map[CacheKey]*resourceState) 147 148 return nil 149 } 150 151 // GetFileFor returns the file that keeps the on-disk state for the given key. 152 func (c *Cache) GetFileFor(k CacheKey) string { 153 c.mu.Lock() 154 defer c.mu.Unlock() 155 156 state, found := c.resources[k] 157 if !found { 158 return "" 159 } 160 return state.file 161 } 162 163 func (c *Cache) writeFile(file string, contents string) error { 164 return os.WriteFile(file, []byte(contents), os.ModePerm) 165 } 166 167 func (c *Cache) deleteFile(file string) error { 168 return os.Remove(file) 169 } 170 171 func (c *Cache) generateFileName(key CacheKey) string { 172 c.discriminator++ 173 d := c.discriminator 174 175 name := fmt.Sprintf("%s_%s_%s_%s-%d.yaml", 176 sanitize(key.group), sanitize(key.kind), sanitize(key.namespace), sanitize(key.name), d) 177 178 return path.Join(c.dir, name) 179 } 180 181 func sanitize(c string) string { 182 return strings.Replace( 183 strings.Replace(c, "/", "", -1), 184 ".", "_", -1) 185 } 186 187 func toKey(d Descriptor) CacheKey { 188 return CacheKey{ 189 group: d.Group, 190 kind: d.Kind, 191 namespace: d.Metadata.Namespace, 192 name: d.Metadata.Name, 193 } 194 }