From 923a0bfb59cae7f4c4e1a2f74d33a2254116469a Mon Sep 17 00:00:00 2001 From: silverwind Date: Thu, 22 Oct 2026 16:28:49 +0000 Subject: [PATCH] refactor: remove bolthold and gofakes3 deps (#1336) 1. Replace bolthold with direct bbolt access in the cache server. bolthold has not changed since March 2024 and kept `encoding/gob` in the binary although the store was already JSON. The on-disk layout stays the same, so existing caches keep working, and the index buckets bolthold left behind are dropped on the first sweep. Queries now run against an in-memory copy of the entries that is only re-read after another process wrote to the store, so lookups and sweeps no longer decode the whole store. 2. Replace gofakes3 with a small in-memory S3 fake in the one test that used it, which removes aws-sdk-go-v2, afero, mgo and x/tools from the module graph. Reviewed-on: https://gitea.com/gitea/runner/pulls/1336 Reviewed-by: bircni Co-authored-by: silverwind --- act/artifactcache/handler.go | 315 ++++++++++++++++++--------- act/artifactcache/handler_test.go | 44 ++-- act/artifactcache/model.go | 14 +- act/artifactcache/s3_storage_test.go | 35 ++- go.mod | 5 - go.sum | 57 ----- 6 files changed, 276 insertions(+), 194 deletions(-) diff --git a/act/artifactcache/handler.go b/act/artifactcache/handler.go index 46e17e9b..193da429 100644 --- a/act/artifactcache/handler.go +++ b/act/artifactcache/handler.go @@ -32,7 +32,6 @@ import ( "gitea.com/gitea/runner/act/common/logging" "gitea.com/gitea/runner/internal/pkg/disk" - "github.com/timshannon/bolthold" "go.etcd.io/bbolt" berrors "go.etcd.io/bbolt/errors" ) @@ -88,7 +87,8 @@ type Handler struct { server *http.Server logger *logging.Logger - dbLock chan struct{} // queues store openers without polling bolt's file lock + dbLock chan struct{} // queues store openers without polling bolt's file lock + entries cacheEntries // guarded by dbLock gcing atomic.Bool gcAt time.Time @@ -424,15 +424,147 @@ func (h *Handler) Close() error { return retErr } +// cacheBucket holds each entry as JSON under its decimal ID, the layout bolthold wrote, so older stores stay readable. +var cacheBucket = []byte("Cache") + +var errCacheNotFound = errors.New("cache not found") + type cacheDB struct { - *bolthold.Store - close func() error + bolt *bbolt.DB + close func() error + entries *cacheEntries +} + +// cacheEntries mirrors the bucket as of transaction tx, so queries decode it again only after another process wrote. +type cacheEntries struct { + tx int + byID map[uint64]*Cache } func (db *cacheDB) Close() error { return db.close() } +func cacheKey(id uint64) []byte { + return strconv.AppendUint(nil, id, 10) +} + +func (db *cacheDB) get(id uint64) (*Cache, error) { + if err := db.bolt.View(db.load); err != nil { + return nil, err + } + cache, ok := db.entries.byID[id] + if !ok { + return nil, errCacheNotFound + } + copied := *cache + return &copied, nil +} + +// find returns copies of the matching entries in ID order, so ties break the same way every time. +func (db *cacheDB) find(match func(*Cache) bool) ([]*Cache, error) { + if err := db.bolt.View(db.load); err != nil { + return nil, err + } + var caches []*Cache + for _, cache := range db.entries.byID { + if match(cache) { + copied := *cache + caches = append(caches, &copied) + } + } + slices.SortFunc(caches, func(left, right *Cache) int { return cmp.Compare(left.ID, right.ID) }) + return caches, nil +} + +func (db *cacheDB) load(tx *bbolt.Tx) error { + if db.entries.byID != nil && db.entries.tx == tx.ID() { + return nil + } + byID := make(map[uint64]*Cache) + if bucket := tx.Bucket(cacheBucket); bucket != nil { + if err := bucket.ForEach(func(key, data []byte) (err error) { + cache := &Cache{} + if err := json.Unmarshal(data, cache); err != nil { + return err + } + cache.ID, err = strconv.ParseUint(string(key), 10, 64) // an interrupted legacy insert left "id":0 in the value + byID[cache.ID] = cache + return err + }); err != nil { + return err + } + } + db.entries.byID, db.entries.tx = byID, tx.ID() + return nil +} + +// update keeps the mirror in step with its own write, and drops it when another process wrote in between or the write rolled back. +func (db *cacheDB) update(fn func(*bbolt.Bucket) error) error { + err := db.bolt.Update(func(tx *bbolt.Tx) error { + if db.entries.tx != tx.ID()-1 { + db.entries.byID = nil + } + db.entries.tx = tx.ID() + bucket, err := tx.CreateBucketIfNotExists(cacheBucket) + if err != nil { + return err + } + return fn(bucket) + }) + if err != nil { + db.entries.byID = nil + } + return err +} + +func (db *cacheDB) put(bucket *bbolt.Bucket, cache *Cache) error { + data, err := json.Marshal(cache) + if err != nil { + return err + } + if db.entries.byID != nil { + copied := *cache + db.entries.byID[cache.ID] = &copied + } + return bucket.Put(cacheKey(cache.ID), data) +} + +func (db *cacheDB) delete(bucket *bbolt.Bucket, id uint64) error { + delete(db.entries.byID, id) + return bucket.Delete(cacheKey(id)) +} + +// dropLegacyIndexes deletes the index buckets bolthold kept, all created together and never recreated, checking first so later runs do not write. +func (db *cacheDB) dropLegacyIndexes() error { + var found bool + _ = db.bolt.View(func(tx *bbolt.Tx) error { + found = tx.Bucket([]byte("_index:Cache:Key")) != nil + return nil + }) + if !found { + return nil + } + return db.bolt.Update(func(tx *bbolt.Tx) error { + for _, field := range []string{"Repo", "Key", "Version", "Complete", "UsedAt", "CreatedAt"} { + if err := tx.DeleteBucket([]byte("_index:Cache:" + field)); err != nil && !errors.Is(err, berrors.ErrBucketNotFound) { + return err + } + } + return nil + }) +} + +// save rewrites a stored entry, failing once a sweep has removed it so a late commit cannot resurrect it. +func (db *cacheDB) save(cache *Cache) error { + return db.update(func(bucket *bbolt.Bucket) error { + if bucket.Get(cacheKey(cache.ID)) == nil { + return errCacheNotFound + } + return db.put(bucket, cache) + }) +} + func (h *Handler) openDB() (store *cacheDB, err error) { select { case h.dbLock <- struct{}{}: @@ -444,19 +576,15 @@ func (h *Handler) openDB() (store *cacheDB, err error) { <-h.dbLock } }() - db, err := bolthold.Open(filepath.Join(h.dir, "bolt.db"), 0o644, &bolthold.Options{ - Encoder: func(value any) ([]byte, error) { return json.Marshal(value) }, - Decoder: func(data []byte, value any) error { return json.Unmarshal(data, value) }, - Options: &bbolt.Options{ - Timeout: dbOpenTimeout, - NoGrowSync: bbolt.DefaultOptions.NoGrowSync, - FreelistType: bbolt.DefaultOptions.FreelistType, - }, + db, err := bbolt.Open(filepath.Join(h.dir, "bolt.db"), 0o644, &bbolt.Options{ + Timeout: dbOpenTimeout, + NoGrowSync: bbolt.DefaultOptions.NoGrowSync, + FreelistType: bbolt.DefaultOptions.FreelistType, }) if err != nil { return nil, err } - return &cacheDB{Store: db, close: sync.OnceValue(func() error { + return &cacheDB{bolt: db, entries: &h.entries, close: sync.OnceValue(func() error { defer func() { <-h.dbLock }() return db.Close() })}, nil @@ -484,7 +612,7 @@ func (h *Handler) importMetadata(ctx context.Context) error { if err != nil { return err } - local := h.caches(db, bolthold.Where("Complete").Eq(true).Index("Complete")) + local := h.caches(db, func(cache *Cache) bool { return cache.Complete }) db.Close() published, err := h.s3.published(ctx) @@ -512,15 +640,15 @@ func (h *Handler) importMetadata(ctx context.Context) error { return err } defer db.Close() - return db.Bolt().Update(func(tx *bbolt.Tx) error { + return db.update(func(bucket *bbolt.Bucket) error { for _, cache := range added { - if err := db.TxUpsert(tx, cache.ID, cache); err != nil { + if err := db.put(bucket, cache); err != nil { return err } } for _, cache := range gone { _ = h.s3.Storage.Remove(cache.ID) // a blob kept on disk before the runner used S3 - if err := db.TxDelete(tx, cache.ID, cache); err != nil && !errors.Is(err, bolthold.ErrNotFound) { + if err := db.delete(bucket, cache.ID); err != nil { return err } } @@ -620,15 +748,15 @@ func (h *Handler) upload(w http.ResponseWriter, r *http.Request) { return } - cache := &Cache{} db, err := h.openDB() if err != nil { h.responseJSON(w, r, 500, err) return } defer db.Close() - if err := db.Get(id, cache); err != nil { - if errors.Is(err, bolthold.ErrNotFound) { + cache, err := db.get(uint64(id)) + if err != nil { + if errors.Is(err, errCacheNotFound) { h.responseJSON(w, r, 400, fmt.Errorf("cache %d: not reserved", id)) return } @@ -668,15 +796,15 @@ func (h *Handler) commit(w http.ResponseWriter, r *http.Request) { return } - cache := &Cache{} db, err := h.openDB() if err != nil { h.responseJSON(w, r, 500, err) return } defer db.Close() - if err := db.Get(id, cache); err != nil { - if errors.Is(err, bolthold.ErrNotFound) { + cache, err := db.get(uint64(id)) + if err != nil { + if errors.Is(err, errCacheNotFound) { h.responseJSON(w, r, 400, fmt.Errorf("cache %d: not reserved", id)) return } @@ -728,7 +856,7 @@ func (h *Handler) commitCache(cache *Cache) error { return err } defer db.Close() - if err := db.Update(cache.ID, cache); err != nil { + if err := db.save(cache); err != nil { return err } if h.s3 != nil { @@ -943,7 +1071,6 @@ func (h *Handler) signedArtifactURL(cred JobCredential, cacheID uint64, exp time // if not found, return (nil, nil) instead of an error. func findCache(db *cacheDB, repo string, keys []string, version string) (*Cache, error) { - cache := &Cache{} for _, prefix := range keys { // if a key in the list matches exactly, don't return partial matches exact, err := findExactCache(db, repo, prefix, version, true) @@ -953,22 +1080,26 @@ func findCache(db *cacheDB, repo string, keys []string, version string) (*Cache, if exact != nil { return exact, nil } - if err := db.FindOne(cache, - bolthold.Where("Repo").Eq(repo).Index("Repo"). - And("Key").MatchFunc(func(key string) (bool, error) { return strings.HasPrefix(key, prefix), nil }). - And("Version").Eq(version). - And("Complete").Eq(true). - SortBy("CreatedAt").Reverse()); err != nil { - if errors.Is(err, bolthold.ErrNotFound) { - continue - } + caches, err := db.find(func(cache *Cache) bool { + return cache.Repo == repo && strings.HasPrefix(cache.Key, prefix) && cache.Version == version && cache.Complete + }) + if err != nil { return nil, fmt.Errorf("find cache: %w", err) } - return cache, nil + if cache := latest(caches, func(cache *Cache) int64 { return cache.CreatedAt }); cache != nil { + return cache, nil + } } return nil, nil //nolint:nilnil // pre-existing issue from nektos/act } +func latest(caches []*Cache, stamp func(*Cache) int64) *Cache { + if len(caches) == 0 { + return nil + } + return slices.MaxFunc(caches, func(left, right *Cache) int { return cmp.Compare(stamp(left), stamp(right)) }) +} + // findExactCache returns the entry for exactly this key and version, or (nil, nil) if there is // none. Unlike findCache it never falls back to a prefix (restore-key) match, which is what both // its callers need: a new key that is only a prefix of an existing key is not the same entry. @@ -977,42 +1108,32 @@ func findCache(db *cacheDB, repo string, keys []string, version string) (*Cache, // reservation being uploaded to, sorted by when it was last written to, because the upload route // touches UsedAt on every part. func findExactCache(db *cacheDB, repo, key, version string, complete bool) (*Cache, error) { - sortBy := "UsedAt" - if complete { - sortBy = "CreatedAt" - } - cache := &Cache{} - err := db.FindOne(cache, - bolthold.Where("Key").Eq(key).Index("Key"). - And("Repo").Eq(repo). - And("Version").Eq(version). - And("Complete").Eq(complete). - SortBy(sortBy).Reverse()) - if errors.Is(err, bolthold.ErrNotFound) { - return nil, nil //nolint:nilnil // absence is not an error here - } + caches, err := db.find(func(cache *Cache) bool { + return cache.Key == key && cache.Repo == repo && cache.Version == version && cache.Complete == complete + }) if err != nil { return nil, fmt.Errorf("find cache: %w", err) } - return cache, nil + stamp := func(cache *Cache) int64 { return cache.UsedAt } + if complete { + stamp = func(cache *Cache) int64 { return cache.CreatedAt } + } + return latest(caches, stamp), nil } func (h *Handler) insertCache(db *cacheDB, cache *Cache) error { - if h.s3 != nil { - var id [8]byte - _, _ = rand.Read(id[:]) - cache.ID = binary.BigEndian.Uint64(id[:]) >> 11 // IDs must be unique across runners, and v1 clients parse an ID as a JS number - return db.Insert(cache.ID, cache) - } - return db.Bolt().Update(func(tx *bbolt.Tx) error { - if err := db.TxInsert(tx, bolthold.NextSequence(), cache); err != nil { + return db.update(func(bucket *bbolt.Bucket) (err error) { + if h.s3 != nil { + var id [8]byte + _, _ = rand.Read(id[:]) + cache.ID = binary.BigEndian.Uint64(id[:]) >> 11 // IDs must be unique across runners, and v1 clients parse an ID as a JS number + } else if cache.ID, err = bucket.NextSequence(); err != nil { return fmt.Errorf("insert cache: %w", err) } - // write back id to db - if err := db.TxUpdate(tx, cache.ID, cache); err != nil { - return fmt.Errorf("write back id to db: %w", err) + if bucket.Get(cacheKey(cache.ID)) != nil { + return fmt.Errorf("insert cache: id %d is taken", cache.ID) } - return nil + return db.put(bucket, cache) }) } @@ -1028,11 +1149,11 @@ func (h *Handler) touchCache(id uint64, requireIncomplete bool) error { } defer db.Close() - cache := &Cache{} - if err := db.Get(id, cache); err != nil { - if errors.Is(err, bolthold.ErrNotFound) { - return nil - } + cache, err := db.get(id) + if errors.Is(err, errCacheNotFound) { + return nil + } + if err != nil { return err } if requireIncomplete && cache.Complete { @@ -1042,7 +1163,7 @@ func (h *Handler) touchCache(id uint64, requireIncomplete bool) error { return nil } cache.UsedAt = time.Now().Unix() - return db.Update(cache.ID, cache) + return db.save(cache) } const ( @@ -1109,6 +1230,9 @@ func (h *Handler) gcCache() { } defer db.Close() + if err := db.dropLegacyIndexes(); err != nil { + h.logger.Warnf("drop legacy indexes: %v", err) + } h.evictIncomplete(db) if h.s3 != nil { return // lifecycle rules on the bucket expire its entries @@ -1142,10 +1266,8 @@ func (h *Handler) evictForFreeSpace(db *cacheDB) { // evictIncomplete removes uploads that stopped part way, which are most likely broken. func (h *Handler) evictIncomplete(db *cacheDB) { - h.sweep(db, bolthold. - Where("UsedAt").Lt(time.Now().Add(-uploadStallTimeout).Unix()). - And("Complete").Eq(false). - Index("UsedAt")) + stalled := time.Now().Add(-uploadStallTimeout).Unix() + h.sweep(db, func(cache *Cache) bool { return cache.UsedAt < stalled && !cache.Complete }) } func (h *Handler) evictExpired(db *cacheDB) { @@ -1153,26 +1275,21 @@ func (h *Handler) evictExpired(db *cacheDB) { return } // Never below inUseGrace, or a short retention would outrun a signed URL already issued. - window := max(h.policy.Retention+touchStale, inUseGrace) - h.sweep(db, bolthold.Where("UsedAt").Lt(time.Now().Add(-window).Unix()).Index("UsedAt")) + expired := time.Now().Add(-max(h.policy.Retention+touchStale, inUseGrace)).Unix() + h.sweep(db, func(cache *Cache) bool { return cache.UsedAt < expired }) } // evictSuperseded removes entries a newer one with the same key and version replaced. The -// aggregation includes Repo so two repos sharing a (key, version) do not evict each other. +// grouping includes Repo so two repos sharing a (key, version) do not evict each other. func (h *Handler) evictSuperseded(db *cacheDB) { - results, err := db.FindAggregate(&Cache{}, bolthold.Where("Complete").Eq(true).Index("Complete"), "Repo", "Key", "Version") - if err != nil { - h.logger.Warnf("find aggregate caches: %v", err) - return + type entry struct{ repo, key, version string } + groups := make(map[entry][]*Cache) + for _, cache := range h.caches(db, func(cache *Cache) bool { return cache.Complete }) { + group := entry{cache.Repo, cache.Key, cache.Version} + groups[group] = append(groups[group], cache) } - var caches []*Cache - for _, result := range results { - if result.Count() <= 1 { - continue - } - result.Sort("CreatedAt") - caches = caches[:0] - result.Reduction(&caches) + for _, caches := range groups { + slices.SortFunc(caches, func(left, right *Cache) int { return cmp.Compare(left.CreatedAt, right.CreatedAt) }) for _, cache := range caches[:len(caches)-1] { if inUse(cache) { continue @@ -1211,7 +1328,7 @@ func (h *Handler) evictRepo(db *cacheDB, repo string) { if h.policy.RepoSizeLimit <= 0 { return } - h.evictTo(db, h.cachesByUse(db, bolthold.Where("Repo").Eq(repo).And("Complete").Eq(true).Index("Repo")), h.policy.RepoSizeLimit, "repository "+repo) + h.evictTo(db, h.cachesByUse(db, func(cache *Cache) bool { return cache.Repo == repo && cache.Complete }), h.policy.RepoSizeLimit, "repository "+repo) } // evictTo deletes until caches fit limit. caches must be ordered by UsedAt ascending. @@ -1267,36 +1384,34 @@ func (h *Handler) touch(db *cacheDB, cache *Cache) { return } cache.UsedAt = time.Now().Unix() - if err := db.Update(cache.ID, cache); err != nil { + if err := db.save(cache); err != nil { h.logger.Warnf("touch cache: %v", err) } } -func (h *Handler) sweep(db *cacheDB, query *bolthold.Query) { - for _, cache := range h.caches(db, query) { +func (h *Handler) sweep(db *cacheDB, match func(*Cache) bool) { + for _, cache := range h.caches(db, match) { h.deleteCache(db, cache) } } -func (h *Handler) caches(db *cacheDB, query *bolthold.Query) []*Cache { - var caches []*Cache - if err := db.Find(&caches, query); err != nil { +func (h *Handler) caches(db *cacheDB, match func(*Cache) bool) []*Cache { + caches, err := db.find(match) + if err != nil { h.logger.Warnf("find caches: %v", err) } return caches } -// cachesByUse returns matches least recently accessed first, sorting here rather than with -// bolthold's SortBy, which reflects over every field it compares. -func (h *Handler) cachesByUse(db *cacheDB, query *bolthold.Query) []*Cache { - caches := h.caches(db, query) +func (h *Handler) cachesByUse(db *cacheDB, match func(*Cache) bool) []*Cache { + caches := h.caches(db, match) slices.SortFunc(caches, func(a, b *Cache) int { return cmp.Compare(a.UsedAt, b.UsedAt) }) return caches } // completedByUse returns every entry the size limits count, least recently accessed first. func (h *Handler) completedByUse(db *cacheDB) []*Cache { - return h.cachesByUse(db, bolthold.Where("Complete").Eq(true).Index("Complete")) + return h.cachesByUse(db, func(cache *Cache) bool { return cache.Complete }) } func totalSize(caches []*Cache) int64 { @@ -1318,7 +1433,7 @@ func (h *Handler) deleteCache(db *cacheDB, cache *Cache) bool { h.logger.Warnf("remove cache blob: %v", err) return false } - if err := db.Delete(cache.ID, cache); err != nil { + if err := db.update(func(bucket *bbolt.Bucket) error { return db.delete(bucket, cache.ID) }); err != nil { h.logger.Warnf("delete cache: %v", err) return false } diff --git a/act/artifactcache/handler_test.go b/act/artifactcache/handler_test.go index b95730f4..0833356b 100644 --- a/act/artifactcache/handler_test.go +++ b/act/artifactcache/handler_test.go @@ -22,7 +22,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/timshannon/bolthold" "go.etcd.io/bbolt" berrors "go.etcd.io/bbolt/errors" ) @@ -141,7 +140,7 @@ func TestHandler(t *testing.T) { db, err := handler.openDB() require.NoError(t, err) defer db.Close() - require.NoError(t, db.Bolt().View(func(tx *bbolt.Tx) error { + require.NoError(t, db.bolt.View(func(tx *bbolt.Tx) error { return tx.Bucket([]byte("Cache")).ForEach(func(k, v []byte) error { t.Logf("%s: %s", k, v) return nil @@ -729,11 +728,11 @@ func backdateCache(t *testing.T, handler *Handler, key string, age time.Duration require.NoError(t, err) defer db.Close() - var caches []*Cache - require.NoError(t, db.Find(&caches, bolthold.Where("Key").Eq(key))) + caches, err := db.find(func(cache *Cache) bool { return cache.Key == key }) + require.NoError(t, err) require.Len(t, caches, 1) caches[0].CreatedAt = time.Now().Add(-age).Unix() - require.NoError(t, db.Update(caches[0].ID, caches[0])) + require.NoError(t, db.save(caches[0])) } func uploadCacheNormally(t *testing.T, base, key, version string, content []byte) { @@ -897,12 +896,11 @@ func TestHandler_gcCache(t *testing.T) { require.NoError(t, err) for i, v := range cases { t.Run(fmt.Sprintf("%d_%s", i, v.Cache.Key), func(t *testing.T) { - cache := &Cache{} - err = db.Get(v.Cache.ID, cache) + _, err = db.get(v.Cache.ID) if v.Kept { assert.NoError(t, err) } else { - assert.ErrorIs(t, err, bolthold.ErrNotFound) + assert.ErrorIs(t, err, errCacheNotFound) } }) } @@ -1088,11 +1086,10 @@ func TestHandler_FindProtectsFromEviction(t *testing.T) { // to drive gcCache would race its read. db, err := handler.openDB() require.NoError(t, err) - defer func() { require.NoError(t, db.Close()) }() handler.evictOversized(db) + require.NoError(t, db.Close()) - require.NoError(t, db.Get(wanted.ID, &Cache{}), "the entry just promised to a job must survive") - assert.ErrorIs(t, db.Get(other.ID, &Cache{}), bolthold.ErrNotFound, "the next least recently used goes instead") + assert.Equal(t, []string{wanted.Key}, keptKeys(t, handler, []*Cache{wanted, other}), "the entry just promised to a job survives, the next least recently used goes instead") } // TestHandler_evictOnCommit proves a repository that goes over its limit gets space back at @@ -1127,7 +1124,7 @@ func TestHandler_touchCacheSkipsFreshCompletedEntries(t *testing.T) { require.NoError(t, err) defer db.Close() var id int - require.NoError(t, db.Bolt().View(func(tx *bbolt.Tx) error { id = tx.ID(); return nil })) + require.NoError(t, db.bolt.View(func(tx *bbolt.Tx) error { id = tx.ID(); return nil })) return id } @@ -1172,7 +1169,7 @@ func keptKeys(t *testing.T, handler *Handler, entries []*Cache) []string { var kept []string for _, e := range entries { - if err := db.Get(e.ID, &Cache{}); err == nil { + if _, err := db.get(e.ID); err == nil { kept = append(kept, e.Key) } } @@ -1409,6 +1406,23 @@ func TestHandler_SecretPersistsAcrossRestarts(t *testing.T) { assert.Equal(t, sig, second.computeSignature("", 42, exp)) } +func TestHandler_SeesWritesOfOtherProcesses(t *testing.T) { + handler := newTestHandler(t, Policy{}) + insert := func(by *Handler, cache *Cache) { + db, err := by.openDB() + require.NoError(t, err) + require.NoError(t, by.insertCache(db, cache)) + require.NoError(t, db.Close()) + } + other := &Handler{dir: handler.dir, dbLock: make(chan struct{}, 1)} + theirs, later, ours := &Cache{Key: "theirs"}, &Cache{Key: "later"}, &Cache{Key: "ours"} + insert(other, theirs) + assert.Equal(t, []string{"theirs"}, keptKeys(t, handler, []*Cache{theirs})) + insert(other, later) + insert(handler, ours) + assert.Equal(t, []string{"theirs", "later", "ours"}, keptKeys(t, handler, []*Cache{theirs, later, ours})) +} + // TestHandler_ArtifactSignatureDownload is a happy-path round trip that // ensures a real reserve/upload/commit/find/download flow still works after // the auth refactor. @@ -1521,8 +1535,8 @@ func TestHandler_GC_PerRepoDedup(t *testing.T) { db, err = handler.openDB() require.NoError(t, err) defer db.Close() - var after []Cache - require.NoError(t, db.Find(&after, bolthold.Where("Key").Eq(key).And("Version").Eq(version))) + after, err := db.find(func(cache *Cache) bool { return cache.Key == key && cache.Version == version }) + require.NoError(t, err) repos := make(map[string]bool) for _, c := range after { diff --git a/act/artifactcache/model.go b/act/artifactcache/model.go index 976bb73a..def1d441 100644 --- a/act/artifactcache/model.go +++ b/act/artifactcache/model.go @@ -11,13 +11,13 @@ type Request struct { } type Cache struct { - ID uint64 `json:"id" boltholdKey:"ID"` - Repo string `json:"repo" boltholdIndex:"Repo"` - Key string `json:"key" boltholdIndex:"Key"` - Version string `json:"version" boltholdIndex:"Version"` + ID uint64 `json:"id"` + Repo string `json:"repo"` + Key string `json:"key"` + Version string `json:"version"` Size int64 `json:"cacheSize"` Owner string `json:"owner"` // hashed bearer, so a job's own retry is not another job's reservation - Complete bool `json:"complete" boltholdIndex:"Complete"` - UsedAt int64 `json:"usedAt" boltholdIndex:"UsedAt"` - CreatedAt int64 `json:"createdAt" boltholdIndex:"CreatedAt"` + Complete bool `json:"complete"` + UsedAt int64 `json:"usedAt"` + CreatedAt int64 `json:"createdAt"` } diff --git a/act/artifactcache/s3_storage_test.go b/act/artifactcache/s3_storage_test.go index c614ce76..90c23748 100644 --- a/act/artifactcache/s3_storage_test.go +++ b/act/artifactcache/s3_storage_test.go @@ -9,26 +9,42 @@ import ( "net/http" "net/http/httptest" "strings" + "sync" "sync/atomic" "testing" "time" - "github.com/johannesboyne/gofakes3" - "github.com/johannesboyne/gofakes3/backend/s3mem" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestHandler_S3SharesEntriesAndDropsExpiredOnes(t *testing.T) { - backend := s3mem.New() - require.NoError(t, backend.CreateBucket("cache")) + var objects sync.Map var uploads atomic.Int32 - handler := gofakes3.New(backend).Server() server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { - if strings.Contains(request.URL.Path, "/blobs/") && (request.Method == http.MethodPut || request.Method == http.MethodPost) { - uploads.Add(1) + key := strings.TrimPrefix(request.URL.Path, "/cache/") + switch { + case request.Method == http.MethodPut: + if strings.Contains(key, "/blobs/") { + uploads.Add(1) + } + body, err := io.ReadAll(request.Body) + assert.NoError(t, err) + objects.Store(key, body) + case request.URL.Query().Has("list-type"): + _, _ = io.WriteString(writer, "") + objects.Range(func(stored, _ any) bool { + _, _ = io.WriteString(writer, ""+stored.(string)+"") + return true + }) + _, _ = io.WriteString(writer, "") + default: + if body, ok := objects.Load(key); ok { + _, _ = writer.Write(body.([]byte)) + } else { + writer.WriteHeader(http.StatusNotFound) + } } - handler.ServeHTTP(writer, request) })) t.Cleanup(server.Close) opts := Options{S3: &S3Options{Endpoint: server.URL, Bucket: "cache", Prefix: "runners", AccessKey: "key", SecretKey: "secret"}} @@ -43,8 +59,7 @@ func TestHandler_S3SharesEntriesAndDropsExpiredOnes(t *testing.T) { require.Equal(t, true, found["ok"]) assert.Equal(t, []byte("archive"), getURL(t, found["signed_download_url"].(string))) - require.NoError(t, backend.ForceDeleteBucket("cache")) - require.NoError(t, backend.CreateBucket("cache")) + objects.Clear() require.NoError(t, restorer.importMetadata(t.Context())) assert.Equal(t, true, v2Call(t, restorer, testClient, "CreateCacheEntry", entry)["ok"]) } diff --git a/go.mod b/go.mod index cb553dcc..4934b30d 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,6 @@ require ( github.com/docker/cli v29.8.2+incompatible github.com/docker/go-connections v0.8.1 github.com/docker/go-units v0.5.0 - github.com/johannesboyne/gofakes3 v1.2.0 github.com/joho/godotenv v1.5.1 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/moby/moby/api v1.56.1 @@ -27,7 +26,6 @@ require ( github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.12.1 - github.com/timshannon/bolthold v0.0.0-20240314194003-30aac6950928 go.etcd.io/bbolt v1.5.0 go.opentelemetry.io/otel v1.47.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.47.0 @@ -59,13 +57,10 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/prometheus/common v0.70.1 // indirect github.com/prometheus/procfs v0.21.1 // indirect - github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect github.com/stretchr/objx v0.5.3 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0 // indirect go.opentelemetry.io/otel/log v1.47.0 // indirect go.opentelemetry.io/otel/metric v1.47.0 // indirect - go.shabbyrobe.org/gocovmerge v0.0.0-20230507111327-fa4f82cfbf4d // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect - golang.org/x/tools v0.49.0 // indirect ) diff --git a/go.sum b/go.sum index d5d71ea3..0c75f724 100644 --- a/go.sum +++ b/go.sum @@ -4,40 +4,12 @@ gitea.dev/actionslib v1.5.0 h1:dC3v2HTWbbvyNBSdg2W3kd4WNC/p+FnZaKpl1MdzM00= gitea.dev/actionslib v1.5.0/go.mod h1:C9wmOFgDBCXyRvg4A3KTX8Vszs8v9+elH4QLGVDolqU= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY= -github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI= -github.com/aws/aws-sdk-go-v2/credentials v1.17.67 h1:9KxtdcIA/5xPNQyZRgUSpYOE6j9Bc4+D7nZua0KGYOM= -github.com/aws/aws-sdk-go-v2/credentials v1.17.67/go.mod h1:p3C44m+cfnbv763s52gCqrjaqyPikj9Sg47kUVaNZQQ= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.75 h1:S61/E3N01oral6B3y9hZ2E1iFDqCZPPOBoBQretCnBI= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.75/go.mod h1:bDMQbkI1vJbNjnvJYpPTSNYBkI/VIv18ngWb/K84tkk= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 h1:JRaIgADQS/U6uXDqlPiefP32yXTda7Kqfx+LgspooZM= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13/go.mod h1:CEuVn5WqOMilYl+tbccq8+N2ieCy0gVn3OtRb0vBNNM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ= -github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 h1:HwxWTbTrIHm5qY+CAEur0s/figc3qwvLWsNkF4RPToo= -github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM= -github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng= -github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bmatcuk/doublestar/v4 v4.10.3 h1:1UIv1+fqH3++ViVkpnaPTB+Pi1UHycnPFAq14zgK+gI= github.com/bmatcuk/doublestar/v4 v4.10.3/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cevatbarisyilmaz/ara v0.0.4 h1:SGH10hXpBJhhTlObuZzTuFn1rrdmjQImITXnZVPSodc= -github.com/cevatbarisyilmaz/ara v0.0.4/go.mod h1:BfFOxnUd6Mj6xmcvRxHN3Sr21Z1T3U2MYkYOmoQe4Ts= github.com/coder/websocket v1.8.15 h1:6B2JPeOGlpff2Uz6vOEH1Vzpi0iUz20A+lPVhPHtNUA= github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= @@ -47,8 +19,6 @@ github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmC github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/cli v29.8.2+incompatible h1:2zgdFuoFst2T80oS42vhKGxkd0JRo305eIEKTsxR7pQ= @@ -70,8 +40,6 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/johannesboyne/gofakes3 v1.2.0 h1:I9VEzPWvvAUAGzDlhYFoZjF0AXMlkcEyZlmBwiI6Oms= -github.com/johannesboyne/gofakes3 v1.2.0/go.mod h1:UHhRZRod9rENGFrUWTYnQHZqlNgSmjOq8DaD/ATQYRM= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= @@ -94,7 +62,6 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.24.1 h1:JnJkREXzWxUdCuPFpIWZiPispT9xVV59uiuyR2bPlnU= github.com/prometheus/client_golang v1.24.1/go.mod h1:F+oSRECHg4sse5ucfYpYDeIv/hu68Zo0uoHKetWnzcE= github.com/prometheus/client_model v0.6.3 h1:O0jaTVAYNxTHYInEPFJt5I3+sN8zqBtVMPTB1qyxiEo= @@ -104,31 +71,17 @@ github.com/prometheus/common v0.70.1/go.mod h1:VdFUQDMZK3VLkurFUVhia6uys/0suUp86 github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI= github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8= -github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8= -github.com/spf13/afero v1.2.1 h1:qgMbHoJbPbw579P+1zVY+6n4nIFuIchaIjzZ/I/Yq8M= -github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= -github.com/timshannon/bolthold v0.0.0-20240314194003-30aac6950928 h1:zjNCuOOhh1TKRU0Ru3PPPJt80z7eReswCao91gBLk00= -github.com/timshannon/bolthold v0.0.0-20240314194003-30aac6950928/go.mod h1:PCFYfAEfKT+Nd6zWvUpsXduMR1bXFLf0uGSlEF05MCI= -go.etcd.io/bbolt v1.3.8/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.etcd.io/bbolt v1.5.0 h1:S7GAl7Fxv12yohbwFfIbQCGDWbQbtDGPET4P/bD4lxU= go.etcd.io/bbolt v1.5.0/go.mod h1:mkltfYE5aUHQxUct9N9V+Kp7aSjFqjgrhcXIS70Lrdk= -go.etcd.io/gofail v0.1.0/go.mod h1:VZBCXYGZhHAinaBiiqYvuDynvahNsAyLFwB3kEHKz1M= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0 h1:LMuyCAyfalSjDyjdC65nK6N0zoTT63+E/u95X0JovZI= @@ -149,8 +102,6 @@ go.opentelemetry.io/otel/trace v1.47.0 h1:JOjX/Oci8K94QHddo+bbfya/Ai/nf6/dt9ZfrF go.opentelemetry.io/otel/trace v1.47.0/go.mod h1:jNaSLa2PZEYFG6fRjJABAu+bw4FS08uDmPg28lTghu0= go.opentelemetry.io/proto/otlp v1.11.1 h1:SCrPqH9NE5CmBrOqpjNCiT0X5mGwMyGERXdogfR7Yjw= go.opentelemetry.io/proto/otlp v1.11.1/go.mod h1:wq8W4aSf6bOy8RZyESOeQFr9Pu7wydWX/TY4sgvrTPg= -go.shabbyrobe.org/gocovmerge v0.0.0-20230507111327-fa4f82cfbf4d h1:Ns9kd1Rwzw7t0BR8XMphenji4SmIoNZPn8zhYmaVKP8= -go.shabbyrobe.org/gocovmerge v0.0.0-20230507111327-fa4f82cfbf4d/go.mod h1:92Uoe3l++MlthCm+koNi0tcUCX3anayogF0Pa/sp24k= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= @@ -164,23 +115,15 @@ golang.org/x/net v0.60.0 h1:79p50tfZlm0J9YfoDsSi639qSXNGVwEzOPLCxM2FsYU= golang.org/x/net v0.60.0/go.mod h1:2DA/G1UfVbCpQPeWTmMPGY7Cs2PkBkwu743bVX5PIVg= golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA= golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA= golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og= golang.org/x/term v0.46.0 h1:3+OXuTbaKDgwk8jTi3aSLHRlmWqHEUDUtxnbFigO4YE= golang.org/x/term v0.46.0/go.mod h1:+K02xbkittuwc0Am4abfA3Fc+XRGXkvBXNO88NCXPoc= golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= -golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= -golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU= -gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=