github.com/anuvu/zot@v1.3.4/pkg/storage/cache.go (about)

     1  package storage
     2  
     3  import (
     4  	"path"
     5  	"path/filepath"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/anuvu/zot/errors"
    10  	zlog "github.com/anuvu/zot/pkg/log"
    11  	"go.etcd.io/bbolt"
    12  )
    13  
    14  const (
    15  	BlobsCache              = "blobs"
    16  	dbCacheLockCheckTimeout = 10 * time.Second
    17  )
    18  
    19  type Cache struct {
    20  	rootDir string
    21  	db      *bbolt.DB
    22  	log     zlog.Logger
    23  }
    24  
    25  // Blob is a blob record.
    26  type Blob struct {
    27  	Path string
    28  }
    29  
    30  func NewCache(rootDir string, name string, log zlog.Logger) *Cache {
    31  	dbPath := path.Join(rootDir, name+".db")
    32  	dbOpts := &bbolt.Options{
    33  		Timeout:      dbCacheLockCheckTimeout,
    34  		FreelistType: bbolt.FreelistArrayType,
    35  	}
    36  	db, err := bbolt.Open(dbPath, 0600, dbOpts)
    37  
    38  	if err != nil {
    39  		log.Error().Err(err).Str("dbPath", dbPath).Msg("unable to create cache db")
    40  		return nil
    41  	}
    42  
    43  	if err := db.Update(func(tx *bbolt.Tx) error {
    44  		if _, err := tx.CreateBucketIfNotExists([]byte(BlobsCache)); err != nil {
    45  			// this is a serious failure
    46  			log.Error().Err(err).Str("dbPath", dbPath).Msg("unable to create a root bucket")
    47  			return err
    48  		}
    49  		return nil
    50  	}); err != nil {
    51  		// something went wrong
    52  		log.Error().Err(err).Msg("unable to create a cache")
    53  		return nil
    54  	}
    55  
    56  	return &Cache{rootDir: rootDir, db: db, log: log}
    57  }
    58  
    59  func (c *Cache) PutBlob(digest string, path string) error {
    60  	if path == "" {
    61  		c.log.Error().Err(errors.ErrEmptyValue).Str("digest", digest).Msg("empty path provided")
    62  
    63  		return errors.ErrEmptyValue
    64  	}
    65  
    66  	// use only relative (to rootDir) paths on blobs
    67  	relp, err := filepath.Rel(c.rootDir, path)
    68  	if err != nil {
    69  		c.log.Error().Err(err).Str("path", path).Msg("unable to get relative path")
    70  	}
    71  
    72  	if err := c.db.Update(func(tx *bbolt.Tx) error {
    73  		root := tx.Bucket([]byte(BlobsCache))
    74  		if root == nil {
    75  			// this is a serious failure
    76  			err := errors.ErrCacheRootBucket
    77  			c.log.Error().Err(err).Msg("unable to access root bucket")
    78  			return err
    79  		}
    80  		b, err := root.CreateBucketIfNotExists([]byte(digest))
    81  		if err != nil {
    82  			// this is a serious failure
    83  			c.log.Error().Err(err).Str("bucket", digest).Msg("unable to create a bucket")
    84  			return err
    85  		}
    86  		if err := b.Put([]byte(relp), nil); err != nil {
    87  			c.log.Error().Err(err).Str("bucket", digest).Str("value", relp).Msg("unable to put record")
    88  			return err
    89  		}
    90  		return nil
    91  	}); err != nil {
    92  		return err
    93  	}
    94  
    95  	return nil
    96  }
    97  
    98  func (c *Cache) GetBlob(digest string) (string, error) {
    99  	var blobPath strings.Builder
   100  
   101  	if err := c.db.View(func(tx *bbolt.Tx) error {
   102  		root := tx.Bucket([]byte(BlobsCache))
   103  		if root == nil {
   104  			// this is a serious failure
   105  			err := errors.ErrCacheRootBucket
   106  			c.log.Error().Err(err).Msg("unable to access root bucket")
   107  			return err
   108  		}
   109  
   110  		b := root.Bucket([]byte(digest))
   111  		if b != nil {
   112  			// get first key
   113  			c := b.Cursor()
   114  			k, _ := c.First()
   115  			blobPath.WriteString(string(k))
   116  			return nil
   117  		}
   118  
   119  		return errors.ErrCacheMiss
   120  	}); err != nil {
   121  		return "", err
   122  	}
   123  
   124  	return blobPath.String(), nil
   125  }
   126  
   127  func (c *Cache) HasBlob(digest string, blob string) bool {
   128  	if err := c.db.View(func(tx *bbolt.Tx) error {
   129  		root := tx.Bucket([]byte(BlobsCache))
   130  		if root == nil {
   131  			// this is a serious failure
   132  			err := errors.ErrCacheRootBucket
   133  			c.log.Error().Err(err).Msg("unable to access root bucket")
   134  			return err
   135  		}
   136  
   137  		b := root.Bucket([]byte(digest))
   138  		if b == nil {
   139  			return errors.ErrCacheMiss
   140  		}
   141  		if b.Get([]byte(blob)) == nil {
   142  			return errors.ErrCacheMiss
   143  		}
   144  
   145  		return nil
   146  	}); err != nil {
   147  		return false
   148  	}
   149  
   150  	return true
   151  }
   152  
   153  func (c *Cache) DeleteBlob(digest string, path string) error {
   154  	// use only relative (to rootDir) paths on blobs
   155  	relp, err := filepath.Rel(c.rootDir, path)
   156  	if err != nil {
   157  		c.log.Error().Err(err).Str("path", path).Msg("unable to get relative path")
   158  	}
   159  
   160  	if err := c.db.Update(func(tx *bbolt.Tx) error {
   161  		root := tx.Bucket([]byte(BlobsCache))
   162  		if root == nil {
   163  			// this is a serious failure
   164  			err := errors.ErrCacheRootBucket
   165  			c.log.Error().Err(err).Msg("unable to access root bucket")
   166  			return err
   167  		}
   168  
   169  		b := root.Bucket([]byte(digest))
   170  		if b == nil {
   171  			return errors.ErrCacheMiss
   172  		}
   173  
   174  		if err := b.Delete([]byte(relp)); err != nil {
   175  			c.log.Error().Err(err).Str("digest", digest).Str("path", relp).Msg("unable to delete")
   176  			return err
   177  		}
   178  
   179  		cur := b.Cursor()
   180  		k, _ := cur.First()
   181  
   182  		if k == nil {
   183  			c.log.Debug().Str("digest", digest).Str("path", relp).Msg("deleting empty bucket")
   184  			if err := root.DeleteBucket([]byte(digest)); err != nil {
   185  				c.log.Error().Err(err).Str("digest", digest).Str("path", relp).Msg("unable to delete")
   186  				return err
   187  			}
   188  		}
   189  
   190  		return nil
   191  	}); err != nil {
   192  		return err
   193  	}
   194  
   195  	return nil
   196  }