2020-02-17 13:57:15 -08:00
|
|
|
package storage
|
|
|
|
|
|
|
|
import (
|
|
|
|
"path"
|
2020-05-27 14:27:35 -07:00
|
|
|
"path/filepath"
|
2020-02-17 13:57:15 -08:00
|
|
|
"strings"
|
2021-10-21 16:05:50 +03:00
|
|
|
"time"
|
2020-02-17 13:57:15 -08:00
|
|
|
|
|
|
|
"go.etcd.io/bbolt"
|
2021-12-04 03:50:58 +00:00
|
|
|
"zotregistry.io/zot/errors"
|
|
|
|
zlog "zotregistry.io/zot/pkg/log"
|
2020-02-17 13:57:15 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2021-10-21 16:05:50 +03:00
|
|
|
BlobsCache = "blobs"
|
2022-04-12 13:01:04 +03:00
|
|
|
DBExtensionName = ".db"
|
2021-10-21 16:05:50 +03:00
|
|
|
dbCacheLockCheckTimeout = 10 * time.Second
|
2022-09-08 01:12:14 +03:00
|
|
|
// always mark the first key inserted in the BlobsCache with a value.
|
|
|
|
firstKeyValue = "first"
|
2020-02-17 13:57:15 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
type Cache struct {
|
2022-04-12 13:01:04 +03:00
|
|
|
rootDir string
|
|
|
|
db *bbolt.DB
|
|
|
|
log zlog.Logger
|
|
|
|
useRelPaths bool // weather or not to use relative paths, should be true for filesystem and false for s3
|
2020-02-17 13:57:15 -08:00
|
|
|
}
|
|
|
|
|
2022-04-12 13:01:04 +03:00
|
|
|
func NewCache(rootDir string, name string, useRelPaths bool, log zlog.Logger) *Cache {
|
|
|
|
dbPath := path.Join(rootDir, name+DBExtensionName)
|
2021-10-21 16:05:50 +03:00
|
|
|
dbOpts := &bbolt.Options{
|
|
|
|
Timeout: dbCacheLockCheckTimeout,
|
|
|
|
FreelistType: bbolt.FreelistArrayType,
|
|
|
|
}
|
2020-02-17 13:57:15 -08:00
|
|
|
|
2021-12-13 19:23:31 +00:00
|
|
|
cacheDB, err := bbolt.Open(dbPath, 0o600, dbOpts) //nolint:gomnd
|
2020-02-17 13:57:15 -08:00
|
|
|
if err != nil {
|
|
|
|
log.Error().Err(err).Str("dbPath", dbPath).Msg("unable to create cache db")
|
2021-12-13 19:23:31 +00:00
|
|
|
|
2020-02-17 13:57:15 -08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-12-13 19:23:31 +00:00
|
|
|
if err := cacheDB.Update(func(tx *bbolt.Tx) error {
|
2020-02-17 13:57:15 -08:00
|
|
|
if _, err := tx.CreateBucketIfNotExists([]byte(BlobsCache)); err != nil {
|
|
|
|
// this is a serious failure
|
|
|
|
log.Error().Err(err).Str("dbPath", dbPath).Msg("unable to create a root bucket")
|
2021-12-13 19:23:31 +00:00
|
|
|
|
2020-02-17 13:57:15 -08:00
|
|
|
return err
|
|
|
|
}
|
2021-12-13 19:23:31 +00:00
|
|
|
|
2020-02-17 13:57:15 -08:00
|
|
|
return nil
|
|
|
|
}); err != nil {
|
|
|
|
// something went wrong
|
|
|
|
log.Error().Err(err).Msg("unable to create a cache")
|
2021-12-13 19:23:31 +00:00
|
|
|
|
2020-02-17 13:57:15 -08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-04-12 13:01:04 +03:00
|
|
|
return &Cache{rootDir: rootDir, db: cacheDB, useRelPaths: useRelPaths, log: log}
|
2020-02-17 13:57:15 -08:00
|
|
|
}
|
|
|
|
|
2022-03-21 17:37:23 +00:00
|
|
|
func (c *Cache) PutBlob(digest, path string) error {
|
2021-07-20 14:04:10 -07:00
|
|
|
if path == "" {
|
|
|
|
c.log.Error().Err(errors.ErrEmptyValue).Str("digest", digest).Msg("empty path provided")
|
|
|
|
|
|
|
|
return errors.ErrEmptyValue
|
|
|
|
}
|
|
|
|
|
2020-05-27 14:27:35 -07:00
|
|
|
// use only relative (to rootDir) paths on blobs
|
2022-04-12 13:01:04 +03:00
|
|
|
var err error
|
|
|
|
if c.useRelPaths {
|
|
|
|
path, err = filepath.Rel(c.rootDir, path)
|
|
|
|
if err != nil {
|
|
|
|
c.log.Error().Err(err).Str("path", path).Msg("unable to get relative path")
|
|
|
|
}
|
2020-05-27 14:27:35 -07:00
|
|
|
}
|
|
|
|
|
2020-02-17 13:57:15 -08:00
|
|
|
if err := c.db.Update(func(tx *bbolt.Tx) error {
|
|
|
|
root := tx.Bucket([]byte(BlobsCache))
|
|
|
|
if root == nil {
|
|
|
|
// this is a serious failure
|
|
|
|
err := errors.ErrCacheRootBucket
|
|
|
|
c.log.Error().Err(err).Msg("unable to access root bucket")
|
2021-12-13 19:23:31 +00:00
|
|
|
|
2020-02-17 13:57:15 -08:00
|
|
|
return err
|
|
|
|
}
|
2021-12-13 19:23:31 +00:00
|
|
|
|
2022-09-08 01:12:14 +03:00
|
|
|
var value string
|
2021-12-13 19:23:31 +00:00
|
|
|
|
2022-09-08 01:12:14 +03:00
|
|
|
bucket := root.Bucket([]byte(digest))
|
|
|
|
if bucket == nil {
|
|
|
|
/* mark first key in bucket
|
|
|
|
in the context of s3 we need to know which blob is real
|
|
|
|
and we know that the first one is always the real, so mark them.
|
|
|
|
*/
|
|
|
|
value = firstKeyValue
|
|
|
|
bucket, err = root.CreateBucket([]byte(digest))
|
|
|
|
if err != nil {
|
|
|
|
// this is a serious failure
|
|
|
|
c.log.Error().Err(err).Str("bucket", digest).Msg("unable to create a bucket")
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
2020-02-17 13:57:15 -08:00
|
|
|
}
|
2021-12-13 19:23:31 +00:00
|
|
|
|
2022-09-08 01:12:14 +03:00
|
|
|
if err := bucket.Put([]byte(path), []byte(value)); err != nil {
|
2022-04-12 13:01:04 +03:00
|
|
|
c.log.Error().Err(err).Str("bucket", digest).Str("value", path).Msg("unable to put record")
|
2021-12-13 19:23:31 +00:00
|
|
|
|
2020-02-17 13:57:15 -08:00
|
|
|
return err
|
|
|
|
}
|
2021-12-13 19:23:31 +00:00
|
|
|
|
2020-02-17 13:57:15 -08:00
|
|
|
return nil
|
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Cache) GetBlob(digest string) (string, error) {
|
|
|
|
var blobPath strings.Builder
|
|
|
|
|
|
|
|
if err := c.db.View(func(tx *bbolt.Tx) error {
|
|
|
|
root := tx.Bucket([]byte(BlobsCache))
|
|
|
|
if root == nil {
|
|
|
|
// this is a serious failure
|
|
|
|
err := errors.ErrCacheRootBucket
|
|
|
|
c.log.Error().Err(err).Msg("unable to access root bucket")
|
2021-12-13 19:23:31 +00:00
|
|
|
|
2020-02-17 13:57:15 -08:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
b := root.Bucket([]byte(digest))
|
|
|
|
if b != nil {
|
2022-09-08 01:12:14 +03:00
|
|
|
if err := b.ForEach(func(k, v []byte) error {
|
|
|
|
// always return the key with 'first' value
|
|
|
|
if string(v) == firstKeyValue {
|
|
|
|
blobPath.WriteString(string(k))
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}); err != nil {
|
|
|
|
c.log.Error().Err(err).Msg("unable to access digest bucket")
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
2021-12-13 19:23:31 +00:00
|
|
|
|
2020-02-17 13:57:15 -08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return errors.ErrCacheMiss
|
|
|
|
}); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return blobPath.String(), nil
|
|
|
|
}
|
|
|
|
|
2022-03-21 17:37:23 +00:00
|
|
|
func (c *Cache) HasBlob(digest, blob string) bool {
|
2020-02-17 13:57:15 -08:00
|
|
|
if err := c.db.View(func(tx *bbolt.Tx) error {
|
|
|
|
root := tx.Bucket([]byte(BlobsCache))
|
|
|
|
if root == nil {
|
|
|
|
// this is a serious failure
|
|
|
|
err := errors.ErrCacheRootBucket
|
|
|
|
c.log.Error().Err(err).Msg("unable to access root bucket")
|
2021-12-13 19:23:31 +00:00
|
|
|
|
2020-02-17 13:57:15 -08:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
b := root.Bucket([]byte(digest))
|
|
|
|
if b == nil {
|
|
|
|
return errors.ErrCacheMiss
|
|
|
|
}
|
2021-12-13 19:23:31 +00:00
|
|
|
|
2020-02-17 13:57:15 -08:00
|
|
|
if b.Get([]byte(blob)) == nil {
|
|
|
|
return errors.ErrCacheMiss
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}); err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2022-03-21 17:37:23 +00:00
|
|
|
func (c *Cache) DeleteBlob(digest, path string) error {
|
2020-05-27 14:27:35 -07:00
|
|
|
// use only relative (to rootDir) paths on blobs
|
2022-04-12 13:01:04 +03:00
|
|
|
var err error
|
|
|
|
if c.useRelPaths {
|
|
|
|
path, err = filepath.Rel(c.rootDir, path)
|
|
|
|
if err != nil {
|
|
|
|
c.log.Error().Err(err).Str("path", path).Msg("unable to get relative path")
|
|
|
|
}
|
2020-05-27 14:27:35 -07:00
|
|
|
}
|
|
|
|
|
2020-02-17 13:57:15 -08:00
|
|
|
if err := c.db.Update(func(tx *bbolt.Tx) error {
|
|
|
|
root := tx.Bucket([]byte(BlobsCache))
|
|
|
|
if root == nil {
|
|
|
|
// this is a serious failure
|
|
|
|
err := errors.ErrCacheRootBucket
|
|
|
|
c.log.Error().Err(err).Msg("unable to access root bucket")
|
2021-12-13 19:23:31 +00:00
|
|
|
|
2020-02-17 13:57:15 -08:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-12-13 19:23:31 +00:00
|
|
|
bucket := root.Bucket([]byte(digest))
|
|
|
|
if bucket == nil {
|
2020-02-17 13:57:15 -08:00
|
|
|
return errors.ErrCacheMiss
|
|
|
|
}
|
|
|
|
|
2022-09-08 01:12:14 +03:00
|
|
|
value := bucket.Get([]byte(path))
|
|
|
|
|
2022-04-12 13:01:04 +03:00
|
|
|
if err := bucket.Delete([]byte(path)); err != nil {
|
|
|
|
c.log.Error().Err(err).Str("digest", digest).Str("path", path).Msg("unable to delete")
|
2021-12-13 19:23:31 +00:00
|
|
|
|
2020-02-17 13:57:15 -08:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-12-13 19:23:31 +00:00
|
|
|
cur := bucket.Cursor()
|
2020-04-06 18:17:24 -07:00
|
|
|
|
2022-09-08 01:12:14 +03:00
|
|
|
key, _ := cur.First()
|
|
|
|
if key == nil {
|
2022-04-12 13:01:04 +03:00
|
|
|
c.log.Debug().Str("digest", digest).Str("path", path).Msg("deleting empty bucket")
|
2020-04-06 18:17:24 -07:00
|
|
|
if err := root.DeleteBucket([]byte(digest)); err != nil {
|
2022-04-12 13:01:04 +03:00
|
|
|
c.log.Error().Err(err).Str("digest", digest).Str("path", path).Msg("unable to delete")
|
2021-12-13 19:23:31 +00:00
|
|
|
|
2022-09-08 01:12:14 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
// if deleted key has value 'first' then move this value to the next key
|
|
|
|
} else if string(value) == firstKeyValue {
|
|
|
|
if err := bucket.Put(key, value); err != nil {
|
|
|
|
c.log.Error().Err(err).Str("bucket", digest).Str("value", path).Msg("unable to put record")
|
|
|
|
|
2020-04-06 18:17:24 -07:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-17 13:57:15 -08:00
|
|
|
return nil
|
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|