github.com/oam-dev/kubevela@v1.9.11/pkg/addon/cache.go (about) 1 /* 2 Copyright 2021 The KubeVela Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package addon 18 19 import ( 20 "context" 21 "fmt" 22 "strings" 23 "sync" 24 "time" 25 26 "k8s.io/klog/v2" 27 28 "github.com/oam-dev/kubevela/pkg/utils" 29 "github.com/oam-dev/kubevela/pkg/utils/common" 30 ) 31 32 // We have three addon layer here 33 // 1. List metadata for all file path of one registry 34 // 2. UIData: Read file content that including README.md and other necessary things being used in UI apiserver 35 // 3. InstallPackage: All file content that used to be installation 36 37 // Cache package only cache for 1 and 2, we don't cache InstallPackage, and it only read for real installation 38 type Cache struct { 39 40 // uiData caches all the decoded UIData addons 41 // the key in the map is the registry name 42 uiData map[string][]*UIData 43 44 // registryMeta caches the addon metadata of every registry 45 // the key in the map is the registry name 46 registryMeta map[string]map[string]SourceMeta 47 48 registry map[string]Registry 49 50 versionedUIData map[string]map[string]*UIData 51 52 mutex *sync.RWMutex 53 54 ds RegistryDataStore 55 } 56 57 // NewCache will build a new cache instance 58 func NewCache(ds RegistryDataStore) *Cache { 59 return &Cache{ 60 uiData: make(map[string][]*UIData), 61 registryMeta: make(map[string]map[string]SourceMeta), 62 registry: make(map[string]Registry), 63 versionedUIData: make(map[string]map[string]*UIData), 64 mutex: new(sync.RWMutex), 65 ds: ds, 66 } 67 } 68 69 // DiscoverAndRefreshLoop will run a loop to automatically discovery and refresh addons from registry 70 func (u *Cache) DiscoverAndRefreshLoop(ctx context.Context, cacheTime time.Duration) { 71 ticker := time.NewTicker(cacheTime) 72 defer ticker.Stop() 73 74 // This is infinite loop, we can receive a channel for close 75 for { 76 select { 77 case <-ticker.C: 78 u.discoverAndRefreshRegistry() 79 case <-ctx.Done(): 80 return 81 } 82 } 83 } 84 85 // ListAddonMeta will list metadata from registry, if cache not found, it will find from source 86 func (u *Cache) ListAddonMeta(r Registry) (map[string]SourceMeta, error) { 87 registryMeta := u.getCachedAddonMeta(r.Name) 88 if registryMeta == nil { 89 return r.ListAddonMeta() 90 } 91 return registryMeta, nil 92 } 93 94 // GetUIData get addon data for UI display from cache, if cache not found, it will find from source 95 func (u *Cache) GetUIData(r Registry, addonName, version string) (*UIData, error) { 96 addon := u.getCachedUIData(r, addonName, version) 97 if addon != nil { 98 return addon, nil 99 } 100 var err error 101 if !IsVersionRegistry(r) { 102 registryMeta, err := u.ListAddonMeta(r) 103 if err != nil { 104 return nil, err 105 } 106 meta, ok := registryMeta[addonName] 107 if !ok { 108 return nil, ErrNotExist 109 } 110 addon, err = r.GetUIData(&meta, UIMetaOptions) 111 if err != nil { 112 return nil, err 113 } 114 } else { 115 versionedRegistry := BuildVersionedRegistry(r.Name, r.Helm.URL, &common.HTTPOption{ 116 Username: r.Helm.Username, 117 Password: r.Helm.Password, 118 InsecureSkipTLS: r.Helm.InsecureSkipTLS, 119 }) 120 addon, err = versionedRegistry.GetAddonUIData(context.Background(), addonName, version) 121 if err != nil { 122 klog.Errorf("fail to get addons from registry %s for cache updating, %v", utils.Sanitize(r.Name), err) 123 return nil, err 124 } 125 } 126 127 return addon, nil 128 } 129 130 // ListUIData will always list UIData from cache first, if not exist, read from source. 131 func (u *Cache) ListUIData(r Registry) ([]*UIData, error) { 132 var err error 133 var listAddons []*UIData 134 if !IsVersionRegistry(r) { 135 listAddons = u.listCachedUIData(r.Name) 136 if listAddons != nil { 137 return listAddons, nil 138 } 139 listAddons, err = u.listUIDataAndCache(r) 140 if err != nil { 141 return nil, err 142 } 143 } else { 144 listAddons = u.listVersionRegistryCachedUIData(r.Name) 145 if listAddons != nil { 146 return listAddons, nil 147 } 148 listAddons, err = u.listVersionRegistryUIDataAndCache(r) 149 if err != nil { 150 return nil, err 151 } 152 } 153 154 return listAddons, nil 155 } 156 157 func (u *Cache) getCachedUIData(registry Registry, addonName, version string) *UIData { 158 if !IsVersionRegistry(registry) { 159 addons := u.listCachedUIData(registry.Name) 160 for _, a := range addons { 161 if a.Name == addonName { 162 return a 163 } 164 } 165 } else { 166 if len(version) == 0 { 167 version = "latest" 168 } 169 return u.versionedUIData[registry.Name][fmt.Sprintf("%s-%s", addonName, version)] 170 } 171 return nil 172 } 173 174 // listCachedUIData will get cached addons from specified registry in cache 175 func (u *Cache) listCachedUIData(name string) []*UIData { 176 if u == nil { 177 return nil 178 } 179 u.mutex.RLock() 180 defer u.mutex.RUnlock() 181 d, ok := u.uiData[name] 182 if !ok { 183 return nil 184 } 185 return d 186 } 187 188 // listVersionRegistryCachedUIData will get cached addons from specified VersionRegistry in cache 189 func (u *Cache) listVersionRegistryCachedUIData(name string) []*UIData { 190 if u == nil { 191 return nil 192 } 193 u.mutex.RLock() 194 defer u.mutex.RUnlock() 195 d, ok := u.versionedUIData[name] 196 if !ok { 197 return nil 198 } 199 var uiDatas []*UIData 200 for version, uiData := range d { 201 if !strings.Contains(version, "-latest") { 202 uiDatas = append(uiDatas, uiData) 203 } 204 } 205 206 return uiDatas 207 } 208 209 // getCachedAddonMeta will get cached registry meta from specified registry in cache 210 func (u *Cache) getCachedAddonMeta(name string) map[string]SourceMeta { 211 if u == nil { 212 return nil 213 } 214 u.mutex.RLock() 215 defer u.mutex.RUnlock() 216 d, ok := u.registryMeta[name] 217 if !ok { 218 return nil 219 } 220 return d 221 } 222 223 func (u *Cache) putAddonUIData2Cache(name string, addons []*UIData) { 224 if u == nil { 225 return 226 } 227 228 u.mutex.Lock() 229 defer u.mutex.Unlock() 230 u.uiData[name] = addons 231 } 232 233 func (u *Cache) putAddonMeta2Cache(name string, addonMeta map[string]SourceMeta) { 234 if u == nil { 235 return 236 } 237 238 u.mutex.Lock() 239 defer u.mutex.Unlock() 240 u.registryMeta[name] = addonMeta 241 } 242 243 func (u *Cache) putRegistry2Cache(registry []Registry) { 244 if u == nil { 245 return 246 } 247 248 u.mutex.Lock() 249 defer u.mutex.Unlock() 250 for k := range u.registry { 251 var found = false 252 for _, r := range registry { 253 if r.Name == k { 254 found = true 255 break 256 } 257 } 258 // clean deleted registry 259 if !found { 260 delete(u.registry, k) 261 delete(u.registryMeta, k) 262 delete(u.uiData, k) 263 } 264 } 265 for _, r := range registry { 266 u.registry[r.Name] = r 267 } 268 } 269 270 func (u *Cache) putVersionedUIData2Cache(registryName, addonName, version string, uiData *UIData) { 271 if u == nil { 272 return 273 } 274 275 u.mutex.Lock() 276 defer u.mutex.Unlock() 277 278 if u.versionedUIData[registryName] == nil { 279 u.versionedUIData[registryName] = make(map[string]*UIData) 280 } 281 u.versionedUIData[registryName][fmt.Sprintf("%s-%s", addonName, version)] = uiData 282 } 283 284 func (u *Cache) discoverAndRefreshRegistry() { 285 registries, err := u.ds.ListRegistries(context.Background()) 286 if err != nil { 287 klog.Errorf("fail to get registry %v", err) 288 return 289 } 290 u.putRegistry2Cache(registries) 291 292 for _, r := range registries { 293 if !IsVersionRegistry(r) { 294 _, err = u.listUIDataAndCache(r) 295 if err != nil { 296 continue 297 } 298 } else { 299 _, err = u.listVersionRegistryUIDataAndCache(r) 300 if err != nil { 301 continue 302 } 303 } 304 } 305 } 306 307 func (u *Cache) listUIDataAndCache(r Registry) ([]*UIData, error) { 308 registryMeta, err := r.ListAddonMeta() 309 if err != nil { 310 klog.Errorf("fail to list registry %s metadata, %v", r.Name, err) 311 return nil, err 312 } 313 u.putAddonMeta2Cache(r.Name, registryMeta) 314 uiData, err := r.ListUIData(registryMeta, UIMetaOptions) 315 if err != nil { 316 klog.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err) 317 return nil, err 318 } 319 u.putAddonUIData2Cache(r.Name, uiData) 320 return uiData, nil 321 } 322 323 func (u *Cache) listVersionRegistryUIDataAndCache(r Registry) ([]*UIData, error) { 324 versionedRegistry := BuildVersionedRegistry(r.Name, r.Helm.URL, &common.HTTPOption{ 325 Username: r.Helm.Username, 326 Password: r.Helm.Password, 327 InsecureSkipTLS: r.Helm.InsecureSkipTLS, 328 }) 329 uiDatas, err := versionedRegistry.ListAddon() 330 if err != nil { 331 klog.Errorf("fail to get addons from registry %s for cache updating, %v", r.Name, err) 332 return nil, err 333 } 334 for _, addon := range uiDatas { 335 uiData, err := versionedRegistry.GetAddonUIData(context.Background(), addon.Name, addon.Version) 336 if err != nil { 337 klog.Errorf("fail to get addon from versioned registry %s, addon %s version %s for cache updating, %v", r.Name, addon.Name, addon.Version, err) 338 continue 339 } 340 // identity an addon from helm chart structure 341 if uiData.Name == "" { 342 addon.Name = "" 343 continue 344 } 345 u.putVersionedUIData2Cache(r.Name, addon.Name, addon.Version, uiData) 346 // we also no version key, if use get addonUIData without version will return this vale as latest data. 347 u.putVersionedUIData2Cache(r.Name, addon.Name, "latest", uiData) 348 } 349 // delete the addon which has been deleted from the addonRegistryCache 350 if addonUIData, ok := u.versionedUIData[r.Name]; ok { 351 for k := range addonUIData { 352 lastInd := strings.LastIndex(k, "-") 353 var needDelete = true 354 for _, addon := range uiDatas { 355 if k[:lastInd] == addon.Name { 356 needDelete = false 357 break 358 } 359 } 360 if needDelete { 361 delete(addonUIData, k) 362 } 363 } 364 } 365 return uiDatas, nil 366 }