github.com/kubeshop/testkube@v1.17.23/pkg/imageinspector/configmapstorage.go (about) 1 package imageinspector 2 3 import ( 4 "context" 5 "encoding/json" 6 "slices" 7 "sync" 8 "time" 9 10 "github.com/pkg/errors" 11 k8serrors "k8s.io/apimachinery/pkg/api/errors" 12 13 "github.com/kubeshop/testkube/pkg/configmap" 14 ) 15 16 type configmapStorage struct { 17 client configmap.Interface 18 name string 19 avoidDirectGet bool // if there is memory storage prior to this one, all the contents will be copied there anyway 20 mu sync.RWMutex 21 } 22 23 func NewConfigMapStorage(client configmap.Interface, name string, avoidDirectGet bool) StorageWithTransfer { 24 return &configmapStorage{ 25 client: client, 26 name: name, 27 avoidDirectGet: avoidDirectGet, 28 } 29 } 30 31 func (c *configmapStorage) fetch(ctx context.Context) (map[string]string, error) { 32 c.mu.RLock() 33 defer c.mu.RUnlock() 34 cache, err := c.client.Get(ctx, c.name) 35 if err != nil && !k8serrors.IsNotFound(err) { 36 return nil, errors.Wrap(err, "getting configmap cache") 37 } 38 if cache == nil { 39 cache = map[string]string{} 40 } 41 return cache, nil 42 } 43 44 func cleanOldRecords(currentData *map[string]string) { 45 // Unmarshal the fetched date for the records 46 type Entry struct { 47 time time.Time 48 name string 49 } 50 dates := make([]Entry, 0, len(*currentData)) 51 var vv Info 52 for k := range *currentData { 53 _ = json.Unmarshal([]byte((*currentData)[k]), &vv) 54 dates = append(dates, Entry{time: vv.FetchedAt, name: k}) 55 } 56 slices.SortFunc(dates, func(a, b Entry) int { 57 if a.time.Before(b.time) { 58 return -1 59 } 60 return 1 61 }) 62 63 // Delete half of the records 64 for i := 0; i < len(*currentData)/2; i++ { 65 delete(*currentData, dates[i].name) 66 } 67 } 68 69 func (c *configmapStorage) save(ctx context.Context, serializedData map[string]string) error { 70 c.mu.Lock() 71 defer c.mu.Unlock() 72 73 // Save data 74 err := c.client.Apply(ctx, c.name, serializedData) 75 76 // When the cache is too big, delete the oldest items and try again 77 if err != nil && k8serrors.IsRequestEntityTooLargeError(err) { 78 cleanOldRecords(&serializedData) 79 err = c.client.Apply(ctx, c.name, serializedData) 80 } 81 return err 82 } 83 84 func (c *configmapStorage) StoreMany(ctx context.Context, data map[Hash]Info) (err error) { 85 if data == nil { 86 return 87 } 88 serialized, err := c.fetch(ctx) 89 if err != nil { 90 return 91 } 92 for k, v := range data { 93 serialized[string(k)], err = marshalInfo(v) 94 if err != nil { 95 return 96 } 97 } 98 return c.save(ctx, serialized) 99 } 100 101 func (c *configmapStorage) CopyTo(ctx context.Context, other ...StorageTransfer) (err error) { 102 serialized, err := c.fetch(ctx) 103 if err != nil { 104 return 105 } 106 data := make(map[Hash]Info, len(serialized)) 107 for k, v := range serialized { 108 data[Hash(k)], err = unmarshalInfo(v) 109 if err != nil { 110 return 111 } 112 } 113 for _, v := range other { 114 err = v.StoreMany(ctx, data) 115 if err != nil { 116 return 117 } 118 } 119 return 120 } 121 122 func (c *configmapStorage) Store(ctx context.Context, request RequestBase, info Info) error { 123 return c.StoreMany(ctx, map[Hash]Info{ 124 hash(request.Registry, request.Image): info, 125 }) 126 } 127 128 func (c *configmapStorage) Get(ctx context.Context, request RequestBase) (*Info, error) { 129 if c.avoidDirectGet { 130 return nil, nil 131 } 132 data, err := c.fetch(ctx) 133 if err != nil { 134 return nil, err 135 } 136 value, ok := data[string(hash(request.Registry, request.Image))] 137 if !ok { 138 return nil, nil 139 } 140 v, err := unmarshalInfo(value) 141 if err != nil { 142 return nil, err 143 } 144 return &v, nil 145 }