zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/meta/boltdb/boltdb.go (about)

     1  package boltdb
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"strings"
     9  	"time"
    10  
    11  	godigest "github.com/opencontainers/go-digest"
    12  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    13  	"go.etcd.io/bbolt"
    14  	"google.golang.org/protobuf/proto"
    15  	"google.golang.org/protobuf/types/known/timestamppb"
    16  
    17  	zerr "zotregistry.dev/zot/errors"
    18  	"zotregistry.dev/zot/pkg/api/constants"
    19  	zcommon "zotregistry.dev/zot/pkg/common"
    20  	"zotregistry.dev/zot/pkg/log"
    21  	"zotregistry.dev/zot/pkg/meta/common"
    22  	mConvert "zotregistry.dev/zot/pkg/meta/convert"
    23  	proto_go "zotregistry.dev/zot/pkg/meta/proto/gen"
    24  	mTypes "zotregistry.dev/zot/pkg/meta/types"
    25  	"zotregistry.dev/zot/pkg/meta/version"
    26  	reqCtx "zotregistry.dev/zot/pkg/requestcontext"
    27  )
    28  
    29  type BoltDB struct {
    30  	DB            *bbolt.DB
    31  	Patches       []func(DB *bbolt.DB) error
    32  	imgTrustStore mTypes.ImageTrustStore
    33  	Log           log.Logger
    34  }
    35  
    36  func New(boltDB *bbolt.DB, log log.Logger) (*BoltDB, error) {
    37  	err := boltDB.Update(func(transaction *bbolt.Tx) error {
    38  		versionBuck, err := transaction.CreateBucketIfNotExists([]byte(VersionBucket))
    39  		if err != nil {
    40  			return err
    41  		}
    42  
    43  		err = versionBuck.Put([]byte(version.DBVersionKey), []byte(version.CurrentVersion))
    44  		if err != nil {
    45  			return err
    46  		}
    47  
    48  		_, err = transaction.CreateBucketIfNotExists([]byte(UserDataBucket))
    49  		if err != nil {
    50  			return err
    51  		}
    52  
    53  		_, err = transaction.CreateBucketIfNotExists([]byte(UserAPIKeysBucket))
    54  		if err != nil {
    55  			return err
    56  		}
    57  
    58  		_, err = transaction.CreateBucketIfNotExists([]byte(ImageMetaBuck))
    59  		if err != nil {
    60  			return err
    61  		}
    62  
    63  		_, err = transaction.CreateBucketIfNotExists([]byte(RepoMetaBuck))
    64  		if err != nil {
    65  			return err
    66  		}
    67  
    68  		repoBlobsBuck, err := transaction.CreateBucketIfNotExists([]byte(RepoBlobsBuck))
    69  		if err != nil {
    70  			return err
    71  		}
    72  
    73  		_, err = repoBlobsBuck.CreateBucketIfNotExists([]byte(RepoLastUpdatedBuck))
    74  		if err != nil {
    75  			return err
    76  		}
    77  
    78  		return nil
    79  	})
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  
    84  	return &BoltDB{
    85  		DB:            boltDB,
    86  		Patches:       version.GetBoltDBPatches(),
    87  		imgTrustStore: nil,
    88  		Log:           log,
    89  	}, nil
    90  }
    91  
    92  func (bdw *BoltDB) GetAllRepoNames() ([]string, error) {
    93  	repoNames := []string{}
    94  
    95  	err := bdw.DB.View(func(tx *bbolt.Tx) error {
    96  		repoMetaBuck := tx.Bucket([]byte(RepoMetaBuck))
    97  
    98  		return repoMetaBuck.ForEach(func(repo, _ []byte) error {
    99  			repoNames = append(repoNames, string(repo))
   100  
   101  			return nil
   102  		})
   103  	})
   104  
   105  	return repoNames, err
   106  }
   107  
   108  func (bdw *BoltDB) GetRepoLastUpdated(repo string) time.Time {
   109  	lastUpdated := time.Time{}
   110  
   111  	err := bdw.DB.View(func(tx *bbolt.Tx) error {
   112  		repoBlobsBuck := tx.Bucket([]byte(RepoBlobsBuck))
   113  
   114  		lastUpdatedBuck := repoBlobsBuck.Bucket([]byte(RepoLastUpdatedBuck))
   115  
   116  		lastUpdatedBlob := lastUpdatedBuck.Get([]byte(repo))
   117  		if len(lastUpdatedBlob) == 0 {
   118  			return zerr.ErrRepoMetaNotFound
   119  		}
   120  
   121  		protoTime := &timestamppb.Timestamp{}
   122  
   123  		err := proto.Unmarshal(lastUpdatedBlob, protoTime)
   124  		if err != nil {
   125  			return err
   126  		}
   127  
   128  		lastUpdated = *mConvert.GetTime(protoTime)
   129  
   130  		return nil
   131  	})
   132  	if err != nil {
   133  		return time.Time{}
   134  	}
   135  
   136  	return lastUpdated
   137  }
   138  
   139  func (bdw *BoltDB) SetImageMeta(digest godigest.Digest, imageMeta mTypes.ImageMeta) error {
   140  	err := bdw.DB.Update(func(tx *bbolt.Tx) error {
   141  		buck := tx.Bucket([]byte(ImageMetaBuck))
   142  
   143  		protoImageMeta := &proto_go.ImageMeta{}
   144  
   145  		switch imageMeta.MediaType {
   146  		case ispec.MediaTypeImageManifest:
   147  			manifest := imageMeta.Manifests[0]
   148  
   149  			protoImageMeta = mConvert.GetProtoImageManifestData(manifest.Manifest, manifest.Config,
   150  				manifest.Size, manifest.Digest.String())
   151  		case ispec.MediaTypeImageIndex:
   152  			protoImageMeta = mConvert.GetProtoImageIndexMeta(*imageMeta.Index, imageMeta.Size, imageMeta.Digest.String())
   153  		}
   154  
   155  		pImageMetaBlob, err := proto.Marshal(protoImageMeta)
   156  		if err != nil {
   157  			return fmt.Errorf("failed to calculate blob for manifest with digest %s %w", digest, err)
   158  		}
   159  
   160  		err = buck.Put([]byte(digest), pImageMetaBlob)
   161  		if err != nil {
   162  			return fmt.Errorf("failed to set manifest data with for digest %s %w", digest, err)
   163  		}
   164  
   165  		return nil
   166  	})
   167  
   168  	return err
   169  }
   170  
   171  func (bdw *BoltDB) SetRepoReference(ctx context.Context, repo string, reference string, imageMeta mTypes.ImageMeta,
   172  ) error {
   173  	if err := common.ValidateRepoReferenceInput(repo, reference, imageMeta.Digest); err != nil {
   174  		return err
   175  	}
   176  
   177  	var userid string
   178  
   179  	userAc, err := reqCtx.UserAcFromContext(ctx)
   180  	if err == nil {
   181  		userid = userAc.GetUsername()
   182  	}
   183  
   184  	err = bdw.DB.Update(func(tx *bbolt.Tx) error {
   185  		repoBuck := tx.Bucket([]byte(RepoMetaBuck))
   186  		repoBlobsBuck := tx.Bucket([]byte(RepoBlobsBuck))
   187  		repoLastUpdatedBuck := repoBlobsBuck.Bucket([]byte(RepoLastUpdatedBuck))
   188  		imageBuck := tx.Bucket([]byte(ImageMetaBuck))
   189  
   190  		// 1. Add image data to db if needed
   191  
   192  		protoImageMeta := mConvert.GetProtoImageMeta(imageMeta)
   193  
   194  		imageMetaBlob, err := proto.Marshal(protoImageMeta)
   195  		if err != nil {
   196  			return err
   197  		}
   198  
   199  		err = imageBuck.Put([]byte(imageMeta.Digest), imageMetaBlob)
   200  		if err != nil {
   201  			return err
   202  		}
   203  
   204  		protoRepoMeta, err := getProtoRepoMeta(repo, repoBuck)
   205  		if err != nil && !errors.Is(err, zerr.ErrRepoMetaNotFound) {
   206  			return err
   207  		}
   208  
   209  		// 2. Referrers
   210  		if subject := mConvert.GetImageSubject(protoImageMeta); subject != nil {
   211  			refInfo := &proto_go.ReferrersInfo{}
   212  			if protoRepoMeta.Referrers[subject.Digest.String()] != nil {
   213  				refInfo = protoRepoMeta.Referrers[subject.Digest.String()]
   214  			}
   215  
   216  			foundReferrer := false
   217  
   218  			for i := range refInfo.List {
   219  				if refInfo.List[i].Digest == mConvert.GetImageDigestStr(protoImageMeta) {
   220  					foundReferrer = true
   221  					refInfo.List[i].Count += 1
   222  
   223  					break
   224  				}
   225  			}
   226  
   227  			if !foundReferrer {
   228  				refInfo.List = append(refInfo.List, &proto_go.ReferrerInfo{
   229  					Count:        1,
   230  					MediaType:    protoImageMeta.MediaType,
   231  					Digest:       mConvert.GetImageDigestStr(protoImageMeta),
   232  					ArtifactType: mConvert.GetImageArtifactType(protoImageMeta),
   233  					Size:         mConvert.GetImageManifestSize(protoImageMeta),
   234  					Annotations:  mConvert.GetImageAnnotations(protoImageMeta),
   235  				})
   236  			}
   237  
   238  			protoRepoMeta.Referrers[subject.Digest.String()] = refInfo
   239  		}
   240  
   241  		// 3. Update tag
   242  		if !common.ReferenceIsDigest(reference) {
   243  			protoRepoMeta.Tags[reference] = &proto_go.TagDescriptor{
   244  				Digest:    imageMeta.Digest.String(),
   245  				MediaType: imageMeta.MediaType,
   246  			}
   247  		}
   248  
   249  		if _, ok := protoRepoMeta.Statistics[imageMeta.Digest.String()]; !ok {
   250  			protoRepoMeta.Statistics[imageMeta.Digest.String()] = &proto_go.DescriptorStatistics{
   251  				DownloadCount:     0,
   252  				LastPullTimestamp: &timestamppb.Timestamp{},
   253  				PushTimestamp:     timestamppb.Now(),
   254  				PushedBy:          userid,
   255  			}
   256  		} else if protoRepoMeta.Statistics[imageMeta.Digest.String()].PushTimestamp.AsTime().IsZero() {
   257  			protoRepoMeta.Statistics[imageMeta.Digest.String()].PushTimestamp = timestamppb.Now()
   258  		}
   259  
   260  		if _, ok := protoRepoMeta.Signatures[imageMeta.Digest.String()]; !ok {
   261  			protoRepoMeta.Signatures[imageMeta.Digest.String()] = &proto_go.ManifestSignatures{
   262  				Map: map[string]*proto_go.SignaturesInfo{"": {}},
   263  			}
   264  		}
   265  
   266  		if _, ok := protoRepoMeta.Referrers[imageMeta.Digest.String()]; !ok {
   267  			protoRepoMeta.Referrers[imageMeta.Digest.String()] = &proto_go.ReferrersInfo{
   268  				List: []*proto_go.ReferrerInfo{},
   269  			}
   270  		}
   271  
   272  		// 4. Blobs
   273  		repoBlobsBytes := repoBlobsBuck.Get([]byte(repo))
   274  
   275  		repoBlobs, err := unmarshalProtoRepoBlobs(repo, repoBlobsBytes)
   276  		if err != nil {
   277  			return err
   278  		}
   279  
   280  		protoRepoMeta, repoBlobs = common.AddImageMetaToRepoMeta(protoRepoMeta, repoBlobs, reference, imageMeta)
   281  
   282  		err = setRepoLastUpdated(repo, time.Now(), repoLastUpdatedBuck)
   283  		if err != nil {
   284  			return err
   285  		}
   286  
   287  		err = setProtoRepoBlobs(repoBlobs, repoBlobsBuck)
   288  		if err != nil {
   289  			return err
   290  		}
   291  
   292  		return setProtoRepoMeta(protoRepoMeta, repoBuck)
   293  	})
   294  
   295  	return err
   296  }
   297  
   298  func setRepoLastUpdated(repo string, lastUpdated time.Time, repoLastUpdatedBuck *bbolt.Bucket) error {
   299  	protoTime := timestamppb.New(lastUpdated)
   300  
   301  	protoTimeBlob, err := proto.Marshal(protoTime)
   302  	if err != nil {
   303  		return err
   304  	}
   305  
   306  	return repoLastUpdatedBuck.Put([]byte(repo), protoTimeBlob)
   307  }
   308  
   309  func unmarshalProtoRepoBlobs(repo string, repoBlobsBytes []byte) (*proto_go.RepoBlobs, error) {
   310  	repoBlobs := &proto_go.RepoBlobs{
   311  		Name: repo,
   312  	}
   313  
   314  	if len(repoBlobsBytes) > 0 {
   315  		err := proto.Unmarshal(repoBlobsBytes, repoBlobs)
   316  		if err != nil {
   317  			return nil, err
   318  		}
   319  	}
   320  
   321  	if repoBlobs.Blobs == nil {
   322  		repoBlobs.Blobs = map[string]*proto_go.BlobInfo{"": {}}
   323  	}
   324  
   325  	return repoBlobs, nil
   326  }
   327  
   328  func setProtoRepoBlobs(repoBlobs *proto_go.RepoBlobs, repoBlobsBuck *bbolt.Bucket) error {
   329  	repoBlobsBytes, err := proto.Marshal(repoBlobs)
   330  	if err != nil {
   331  		return err
   332  	}
   333  
   334  	return repoBlobsBuck.Put([]byte(repoBlobs.Name), repoBlobsBytes)
   335  }
   336  
   337  func getProtoRepoMeta(repo string, repoMetaBuck *bbolt.Bucket) (*proto_go.RepoMeta, error) {
   338  	repoMetaBlob := repoMetaBuck.Get([]byte(repo))
   339  
   340  	return unmarshalProtoRepoMeta(repo, repoMetaBlob)
   341  }
   342  
   343  // unmarshalProtoRepoMeta will unmarshal the repoMeta blob and initialize nil maps. If the blob is empty
   344  // an empty initialized object is returned.
   345  func unmarshalProtoRepoMeta(repo string, repoMetaBlob []byte) (*proto_go.RepoMeta, error) {
   346  	protoRepoMeta := &proto_go.RepoMeta{
   347  		Name: repo,
   348  	}
   349  
   350  	if len(repoMetaBlob) > 0 {
   351  		err := proto.Unmarshal(repoMetaBlob, protoRepoMeta)
   352  		if err != nil {
   353  			return protoRepoMeta, err
   354  		}
   355  	}
   356  
   357  	if protoRepoMeta.Tags == nil {
   358  		protoRepoMeta.Tags = map[string]*proto_go.TagDescriptor{"": {}}
   359  	}
   360  
   361  	if protoRepoMeta.Statistics == nil {
   362  		protoRepoMeta.Statistics = map[string]*proto_go.DescriptorStatistics{"": {}}
   363  	}
   364  
   365  	if protoRepoMeta.Signatures == nil {
   366  		protoRepoMeta.Signatures = map[string]*proto_go.ManifestSignatures{"": {}}
   367  	}
   368  
   369  	if protoRepoMeta.Referrers == nil {
   370  		protoRepoMeta.Referrers = map[string]*proto_go.ReferrersInfo{"": {}}
   371  	}
   372  
   373  	if len(repoMetaBlob) == 0 {
   374  		return protoRepoMeta, zerr.ErrRepoMetaNotFound
   375  	}
   376  
   377  	return protoRepoMeta, nil
   378  }
   379  
   380  func setProtoRepoMeta(repoMeta *proto_go.RepoMeta, repoBuck *bbolt.Bucket) error {
   381  	repoMetaBlob, err := proto.Marshal(repoMeta)
   382  	if err != nil {
   383  		return err
   384  	}
   385  
   386  	return repoBuck.Put([]byte(repoMeta.Name), repoMetaBlob)
   387  }
   388  
   389  func (bdw *BoltDB) FilterImageMeta(ctx context.Context, digests []string,
   390  ) (map[string]mTypes.ImageMeta, error) {
   391  	imageMetaMap := map[string]mTypes.ImageMeta{}
   392  
   393  	err := bdw.DB.View(func(transaction *bbolt.Tx) error {
   394  		imageBuck := transaction.Bucket([]byte(ImageMetaBuck))
   395  
   396  		for _, digest := range digests {
   397  			protoImageMeta, err := getProtoImageMeta(imageBuck, digest)
   398  			if err != nil {
   399  				return err
   400  			}
   401  
   402  			if protoImageMeta.MediaType == ispec.MediaTypeImageIndex {
   403  				manifestDataList := make([]*proto_go.ManifestMeta, 0, len(protoImageMeta.Index.Index.Manifests))
   404  
   405  				for _, manifest := range protoImageMeta.Index.Index.Manifests {
   406  					imageManifestData, err := getProtoImageMeta(imageBuck, manifest.Digest)
   407  					if err != nil {
   408  						return err
   409  					}
   410  
   411  					manifestDataList = append(manifestDataList, imageManifestData.Manifests[0])
   412  				}
   413  
   414  				protoImageMeta.Manifests = manifestDataList
   415  			}
   416  
   417  			imageMetaMap[digest] = mConvert.GetImageMeta(protoImageMeta)
   418  		}
   419  
   420  		return nil
   421  	})
   422  
   423  	return imageMetaMap, err
   424  }
   425  
   426  func (bdw *BoltDB) SearchRepos(ctx context.Context, searchText string,
   427  ) ([]mTypes.RepoMeta, error) {
   428  	repos := []mTypes.RepoMeta{}
   429  
   430  	err := bdw.DB.View(func(transaction *bbolt.Tx) error {
   431  		var (
   432  			repoBuck      = transaction.Bucket([]byte(RepoMetaBuck))
   433  			userBookmarks = getUserBookmarks(ctx, transaction)
   434  			userStars     = getUserStars(ctx, transaction)
   435  		)
   436  
   437  		cursor := repoBuck.Cursor()
   438  
   439  		for repoName, repoMetaBlob := cursor.First(); repoName != nil; repoName, repoMetaBlob = cursor.Next() {
   440  			if ok, err := reqCtx.RepoIsUserAvailable(ctx, string(repoName)); !ok || err != nil {
   441  				continue
   442  			}
   443  
   444  			rank := common.RankRepoName(searchText, string(repoName))
   445  			if rank == -1 {
   446  				continue
   447  			}
   448  
   449  			protoRepoMeta, err := unmarshalProtoRepoMeta(string(repoName), repoMetaBlob)
   450  			if err != nil {
   451  				return err
   452  			}
   453  
   454  			delete(protoRepoMeta.Tags, "")
   455  
   456  			if len(protoRepoMeta.Tags) == 0 {
   457  				continue
   458  			}
   459  
   460  			protoRepoMeta.Rank = int32(rank)
   461  			protoRepoMeta.IsStarred = zcommon.Contains(userStars, protoRepoMeta.Name)
   462  			protoRepoMeta.IsBookmarked = zcommon.Contains(userBookmarks, protoRepoMeta.Name)
   463  
   464  			repos = append(repos, mConvert.GetRepoMeta(protoRepoMeta))
   465  		}
   466  
   467  		return nil
   468  	})
   469  
   470  	return repos, err
   471  }
   472  
   473  func getProtoImageMeta(imageBuck *bbolt.Bucket, digest string) (*proto_go.ImageMeta, error) {
   474  	imageMetaBlob := imageBuck.Get([]byte(digest))
   475  
   476  	if len(imageMetaBlob) == 0 {
   477  		return nil, zerr.ErrImageMetaNotFound
   478  	}
   479  
   480  	imageMeta := proto_go.ImageMeta{}
   481  
   482  	err := proto.Unmarshal(imageMetaBlob, &imageMeta)
   483  	if err != nil {
   484  		return nil, err
   485  	}
   486  
   487  	return &imageMeta, nil
   488  }
   489  
   490  func (bdw *BoltDB) SearchTags(ctx context.Context, searchText string,
   491  ) ([]mTypes.FullImageMeta, error) {
   492  	images := []mTypes.FullImageMeta{}
   493  
   494  	searchedRepo, searchedTag, err := common.GetRepoTag(searchText)
   495  	if err != nil {
   496  		return []mTypes.FullImageMeta{},
   497  			fmt.Errorf("failed to parse search text, invalid format %w", err)
   498  	}
   499  
   500  	err = bdw.DB.View(func(transaction *bbolt.Tx) error {
   501  		var (
   502  			repoBuck      = transaction.Bucket([]byte(RepoMetaBuck))
   503  			imageBuck     = transaction.Bucket([]byte(ImageMetaBuck))
   504  			userBookmarks = getUserBookmarks(ctx, transaction)
   505  			userStars     = getUserStars(ctx, transaction)
   506  		)
   507  
   508  		repoName, repoMetaBlob := repoBuck.Cursor().Seek([]byte(searchedRepo))
   509  
   510  		if string(repoName) != searchedRepo {
   511  			return nil
   512  		}
   513  
   514  		if ok, err := reqCtx.RepoIsUserAvailable(ctx, string(repoName)); !ok || err != nil {
   515  			return err
   516  		}
   517  
   518  		protoRepoMeta, err := unmarshalProtoRepoMeta(string(repoName), repoMetaBlob)
   519  		if err != nil {
   520  			return err
   521  		}
   522  
   523  		delete(protoRepoMeta.Tags, "")
   524  
   525  		protoRepoMeta.IsBookmarked = zcommon.Contains(userBookmarks, protoRepoMeta.Name)
   526  		protoRepoMeta.IsStarred = zcommon.Contains(userStars, protoRepoMeta.Name)
   527  
   528  		for tag, descriptor := range protoRepoMeta.Tags {
   529  			if !strings.HasPrefix(tag, searchedTag) || tag == "" {
   530  				continue
   531  			}
   532  
   533  			var protoImageMeta *proto_go.ImageMeta
   534  
   535  			switch descriptor.MediaType {
   536  			case ispec.MediaTypeImageManifest:
   537  				manifestDigest := descriptor.Digest
   538  
   539  				imageManifestData, err := getProtoImageMeta(imageBuck, manifestDigest)
   540  				if err != nil {
   541  					return fmt.Errorf("failed to fetch manifest meta for manifest with digest %s %w",
   542  						manifestDigest, err)
   543  				}
   544  
   545  				protoImageMeta = imageManifestData
   546  			case ispec.MediaTypeImageIndex:
   547  				indexDigest := descriptor.Digest
   548  
   549  				imageIndexData, err := getProtoImageMeta(imageBuck, indexDigest)
   550  				if err != nil {
   551  					return fmt.Errorf("failed to fetch manifest meta for manifest with digest %s %w",
   552  						indexDigest, err)
   553  				}
   554  
   555  				manifestDataList := make([]*proto_go.ManifestMeta, 0, len(imageIndexData.Index.Index.Manifests))
   556  
   557  				for _, manifest := range imageIndexData.Index.Index.Manifests {
   558  					imageManifestData, err := getProtoImageMeta(imageBuck, manifest.Digest)
   559  					if err != nil {
   560  						return err
   561  					}
   562  
   563  					manifestDataList = append(manifestDataList, imageManifestData.Manifests[0])
   564  				}
   565  
   566  				imageIndexData.Manifests = manifestDataList
   567  
   568  				protoImageMeta = imageIndexData
   569  			default:
   570  				bdw.Log.Error().Str("mediaType", descriptor.MediaType).Msg("unsupported media type")
   571  
   572  				continue
   573  			}
   574  
   575  			images = append(images, mConvert.GetFullImageMetaFromProto(tag, protoRepoMeta, protoImageMeta))
   576  		}
   577  
   578  		return nil
   579  	})
   580  
   581  	return images, err
   582  }
   583  
   584  func (bdw *BoltDB) FilterTags(ctx context.Context, filterRepoTag mTypes.FilterRepoTagFunc,
   585  	filterFunc mTypes.FilterFunc,
   586  ) ([]mTypes.FullImageMeta, error) {
   587  	images := []mTypes.FullImageMeta{}
   588  
   589  	err := bdw.DB.View(func(transaction *bbolt.Tx) error {
   590  		var (
   591  			repoBuck      = transaction.Bucket([]byte(RepoMetaBuck))
   592  			imageMetaBuck = transaction.Bucket([]byte(ImageMetaBuck))
   593  			userBookmarks = getUserBookmarks(ctx, transaction)
   594  			userStars     = getUserStars(ctx, transaction)
   595  			viewError     error
   596  		)
   597  
   598  		cursor := repoBuck.Cursor()
   599  		repoName, repoMetaBlob := cursor.First()
   600  
   601  		for ; repoName != nil; repoName, repoMetaBlob = cursor.Next() {
   602  			if ok, err := reqCtx.RepoIsUserAvailable(ctx, string(repoName)); !ok || err != nil {
   603  				continue
   604  			}
   605  
   606  			protoRepoMeta, err := unmarshalProtoRepoMeta(string(repoName), repoMetaBlob)
   607  			if err != nil {
   608  				viewError = errors.Join(viewError, err)
   609  
   610  				continue
   611  			}
   612  
   613  			delete(protoRepoMeta.Tags, "")
   614  			protoRepoMeta.IsBookmarked = zcommon.Contains(userBookmarks, protoRepoMeta.Name)
   615  			protoRepoMeta.IsStarred = zcommon.Contains(userStars, protoRepoMeta.Name)
   616  			repoMeta := mConvert.GetRepoMeta(protoRepoMeta)
   617  
   618  			for tag, descriptor := range protoRepoMeta.Tags {
   619  				if !filterRepoTag(string(repoName), tag) {
   620  					continue
   621  				}
   622  
   623  				switch descriptor.MediaType {
   624  				case ispec.MediaTypeImageManifest:
   625  					manifestDigest := descriptor.Digest
   626  
   627  					imageManifestData, err := getProtoImageMeta(imageMetaBuck, manifestDigest)
   628  					if err != nil {
   629  						viewError = errors.Join(viewError, err)
   630  
   631  						continue
   632  					}
   633  
   634  					imageMeta := mConvert.GetImageMeta(imageManifestData)
   635  
   636  					if filterFunc(repoMeta, imageMeta) {
   637  						images = append(images, mConvert.GetFullImageMetaFromProto(tag, protoRepoMeta, imageManifestData))
   638  					}
   639  				case ispec.MediaTypeImageIndex:
   640  					indexDigest := descriptor.Digest
   641  
   642  					protoImageIndexMeta, err := getProtoImageMeta(imageMetaBuck, indexDigest)
   643  					if err != nil {
   644  						viewError = errors.Join(viewError, err)
   645  
   646  						continue
   647  					}
   648  
   649  					imageIndexMeta := mConvert.GetImageMeta(protoImageIndexMeta)
   650  					matchedManifests := []*proto_go.ManifestMeta{}
   651  
   652  					for _, manifest := range protoImageIndexMeta.Index.Index.Manifests {
   653  						manifestDigest := manifest.Digest
   654  
   655  						imageManifestData, err := getProtoImageMeta(imageMetaBuck, manifestDigest)
   656  						if err != nil {
   657  							viewError = errors.Join(viewError, err)
   658  
   659  							continue
   660  						}
   661  
   662  						imageMeta := mConvert.GetImageMeta(imageManifestData)
   663  						partialImageMeta := common.GetPartialImageMeta(imageIndexMeta, imageMeta)
   664  
   665  						if filterFunc(repoMeta, partialImageMeta) {
   666  							matchedManifests = append(matchedManifests, imageManifestData.Manifests[0])
   667  						}
   668  					}
   669  
   670  					if len(matchedManifests) > 0 {
   671  						protoImageIndexMeta.Manifests = matchedManifests
   672  
   673  						images = append(images, mConvert.GetFullImageMetaFromProto(tag, protoRepoMeta, protoImageIndexMeta))
   674  					}
   675  				default:
   676  					bdw.Log.Error().Str("mediaType", descriptor.MediaType).Msg("unsupported media type")
   677  
   678  					continue
   679  				}
   680  			}
   681  		}
   682  
   683  		return viewError
   684  	})
   685  
   686  	return images, err
   687  }
   688  
   689  func (bdw *BoltDB) FilterRepos(ctx context.Context, acceptName mTypes.FilterRepoNameFunc,
   690  	filter mTypes.FilterFullRepoFunc,
   691  ) ([]mTypes.RepoMeta, error) {
   692  	repos := []mTypes.RepoMeta{}
   693  
   694  	err := bdw.DB.View(func(tx *bbolt.Tx) error {
   695  		var (
   696  			buck          = tx.Bucket([]byte(RepoMetaBuck))
   697  			cursor        = buck.Cursor()
   698  			userBookmarks = getUserBookmarks(ctx, tx)
   699  			userStars     = getUserStars(ctx, tx)
   700  		)
   701  
   702  		for repoName, repoMetaBlob := cursor.First(); repoName != nil; repoName, repoMetaBlob = cursor.Next() {
   703  			if ok, err := reqCtx.RepoIsUserAvailable(ctx, string(repoName)); !ok || err != nil {
   704  				continue
   705  			}
   706  
   707  			if !acceptName(string(repoName)) {
   708  				continue
   709  			}
   710  
   711  			repoMeta, err := unmarshalProtoRepoMeta(string(repoName), repoMetaBlob)
   712  			if err != nil {
   713  				return err
   714  			}
   715  
   716  			repoMeta.IsBookmarked = zcommon.Contains(userBookmarks, repoMeta.Name)
   717  			repoMeta.IsStarred = zcommon.Contains(userStars, repoMeta.Name)
   718  
   719  			fullRepoMeta := mConvert.GetRepoMeta(repoMeta)
   720  
   721  			if filter(fullRepoMeta) {
   722  				repos = append(repos, fullRepoMeta)
   723  			}
   724  		}
   725  
   726  		return nil
   727  	})
   728  	if err != nil {
   729  		return []mTypes.RepoMeta{}, err
   730  	}
   731  
   732  	return repos, err
   733  }
   734  
   735  func (bdw *BoltDB) GetRepoMeta(ctx context.Context, repo string) (mTypes.RepoMeta, error) {
   736  	var protoRepoMeta *proto_go.RepoMeta
   737  
   738  	err := bdw.DB.View(func(tx *bbolt.Tx) error {
   739  		buck := tx.Bucket([]byte(RepoMetaBuck))
   740  		userBookmarks := getUserBookmarks(ctx, tx)
   741  		userStars := getUserStars(ctx, tx)
   742  
   743  		repoMetaBlob := buck.Get([]byte(repo))
   744  
   745  		var err error
   746  
   747  		protoRepoMeta, err = unmarshalProtoRepoMeta(repo, repoMetaBlob)
   748  		if err != nil {
   749  			return err
   750  		}
   751  
   752  		delete(protoRepoMeta.Tags, "")
   753  		protoRepoMeta.IsBookmarked = zcommon.Contains(userBookmarks, repo)
   754  		protoRepoMeta.IsStarred = zcommon.Contains(userStars, repo)
   755  
   756  		return nil
   757  	})
   758  
   759  	return mConvert.GetRepoMeta(protoRepoMeta), err
   760  }
   761  
   762  func (bdw *BoltDB) GetFullImageMeta(ctx context.Context, repo string, tag string) (mTypes.FullImageMeta, error) {
   763  	protoRepoMeta := &proto_go.RepoMeta{}
   764  	protoImageMeta := &proto_go.ImageMeta{}
   765  
   766  	err := bdw.DB.View(func(tx *bbolt.Tx) error {
   767  		buck := tx.Bucket([]byte(RepoMetaBuck))
   768  		imageBuck := tx.Bucket([]byte(ImageMetaBuck))
   769  		userBookmarks := getUserBookmarks(ctx, tx)
   770  		userStars := getUserStars(ctx, tx)
   771  
   772  		repoMetaBlob := buck.Get([]byte(repo))
   773  
   774  		// object not found
   775  		if len(repoMetaBlob) == 0 {
   776  			return zerr.ErrRepoMetaNotFound
   777  		}
   778  
   779  		var err error
   780  
   781  		protoRepoMeta, err = unmarshalProtoRepoMeta(repo, repoMetaBlob)
   782  		if err != nil {
   783  			return err
   784  		}
   785  
   786  		delete(protoRepoMeta.Tags, "")
   787  		protoRepoMeta.IsBookmarked = zcommon.Contains(userBookmarks, repo)
   788  		protoRepoMeta.IsStarred = zcommon.Contains(userStars, repo)
   789  
   790  		descriptor, ok := protoRepoMeta.Tags[tag]
   791  		if !ok {
   792  			return zerr.ErrImageMetaNotFound
   793  		}
   794  
   795  		protoImageMeta, err = getProtoImageMeta(imageBuck, descriptor.Digest)
   796  		if err != nil {
   797  			return err
   798  		}
   799  
   800  		if protoImageMeta.MediaType == ispec.MediaTypeImageIndex {
   801  			manifestDataList := make([]*proto_go.ManifestMeta, 0, len(protoImageMeta.Index.Index.Manifests))
   802  
   803  			for _, manifest := range protoImageMeta.Index.Index.Manifests {
   804  				imageManifestData, err := getProtoImageMeta(imageBuck, manifest.Digest)
   805  				if err != nil {
   806  					return err
   807  				}
   808  
   809  				manifestDataList = append(manifestDataList, imageManifestData.Manifests[0])
   810  			}
   811  
   812  			protoImageMeta.Manifests = manifestDataList
   813  		}
   814  
   815  		return nil
   816  	})
   817  
   818  	return mConvert.GetFullImageMetaFromProto(tag, protoRepoMeta, protoImageMeta), err
   819  }
   820  
   821  func (bdw *BoltDB) GetImageMeta(digest godigest.Digest) (mTypes.ImageMeta, error) {
   822  	imageMeta := mTypes.ImageMeta{}
   823  
   824  	err := bdw.DB.View(func(tx *bbolt.Tx) error {
   825  		imageBuck := tx.Bucket([]byte(ImageMetaBuck))
   826  
   827  		protoImageMeta, err := getProtoImageMeta(imageBuck, digest.String())
   828  		if err != nil {
   829  			return err
   830  		}
   831  
   832  		if protoImageMeta.MediaType == ispec.MediaTypeImageIndex {
   833  			manifestDataList := make([]*proto_go.ManifestMeta, 0, len(protoImageMeta.Index.Index.Manifests))
   834  
   835  			for _, manifest := range protoImageMeta.Index.Index.Manifests {
   836  				imageManifestData, err := getProtoImageMeta(imageBuck, manifest.Digest)
   837  				if err != nil {
   838  					return err
   839  				}
   840  
   841  				manifestDataList = append(manifestDataList, imageManifestData.Manifests[0])
   842  			}
   843  
   844  			protoImageMeta.Manifests = manifestDataList
   845  		}
   846  
   847  		imageMeta = mConvert.GetImageMeta(protoImageMeta)
   848  
   849  		return nil
   850  	})
   851  
   852  	return imageMeta, err
   853  }
   854  
   855  func (bdw *BoltDB) GetMultipleRepoMeta(ctx context.Context, filter func(repoMeta mTypes.RepoMeta) bool,
   856  ) ([]mTypes.RepoMeta, error) {
   857  	foundRepos := []mTypes.RepoMeta{}
   858  
   859  	err := bdw.DB.View(func(tx *bbolt.Tx) error {
   860  		buck := tx.Bucket([]byte(RepoMetaBuck))
   861  
   862  		cursor := buck.Cursor()
   863  
   864  		for repoName, repoMetaBlob := cursor.First(); repoName != nil; repoName, repoMetaBlob = cursor.Next() {
   865  			if ok, err := reqCtx.RepoIsUserAvailable(ctx, string(repoName)); !ok || err != nil {
   866  				continue
   867  			}
   868  
   869  			protoRepoMeta, err := unmarshalProtoRepoMeta(string(repoName), repoMetaBlob)
   870  			if err != nil {
   871  				return err
   872  			}
   873  
   874  			delete(protoRepoMeta.Tags, "")
   875  
   876  			repoMeta := mConvert.GetRepoMeta(protoRepoMeta)
   877  
   878  			if filter(repoMeta) {
   879  				foundRepos = append(foundRepos, repoMeta)
   880  			}
   881  		}
   882  
   883  		return nil
   884  	})
   885  
   886  	return foundRepos, err
   887  }
   888  
   889  func (bdw *BoltDB) AddManifestSignature(repo string, signedManifestDigest godigest.Digest,
   890  	sigMeta mTypes.SignatureMetadata,
   891  ) error {
   892  	err := bdw.DB.Update(func(tx *bbolt.Tx) error {
   893  		repoMetaBuck := tx.Bucket([]byte(RepoMetaBuck))
   894  
   895  		repoMetaBlob := repoMetaBuck.Get([]byte(repo))
   896  
   897  		if len(repoMetaBlob) == 0 {
   898  			var err error
   899  			// create a new object
   900  			repoMeta := proto_go.RepoMeta{
   901  				Name: repo,
   902  				Tags: map[string]*proto_go.TagDescriptor{"": {}},
   903  				Signatures: map[string]*proto_go.ManifestSignatures{
   904  					signedManifestDigest.String(): {
   905  						Map: map[string]*proto_go.SignaturesInfo{
   906  							sigMeta.SignatureType: {
   907  								List: []*proto_go.SignatureInfo{
   908  									{
   909  										SignatureManifestDigest: sigMeta.SignatureDigest,
   910  										LayersInfo:              mConvert.GetProtoLayersInfo(sigMeta.LayersInfo),
   911  									},
   912  								},
   913  							},
   914  						},
   915  					},
   916  				},
   917  				Referrers:  map[string]*proto_go.ReferrersInfo{"": {}},
   918  				Statistics: map[string]*proto_go.DescriptorStatistics{"": {}},
   919  			}
   920  
   921  			repoMetaBlob, err = proto.Marshal(&repoMeta)
   922  			if err != nil {
   923  				return err
   924  			}
   925  
   926  			return repoMetaBuck.Put([]byte(repo), repoMetaBlob)
   927  		}
   928  
   929  		protoRepoMeta, err := unmarshalProtoRepoMeta(repo, repoMetaBlob)
   930  		if err != nil {
   931  			return err
   932  		}
   933  
   934  		var (
   935  			manifestSignatures *proto_go.ManifestSignatures
   936  			found              bool
   937  		)
   938  
   939  		if manifestSignatures, found = protoRepoMeta.Signatures[signedManifestDigest.String()]; !found {
   940  			manifestSignatures = &proto_go.ManifestSignatures{Map: map[string]*proto_go.SignaturesInfo{"": {}}}
   941  		}
   942  
   943  		signatureSlice := &proto_go.SignaturesInfo{List: []*proto_go.SignatureInfo{}}
   944  		if sigSlice, found := manifestSignatures.Map[sigMeta.SignatureType]; found {
   945  			signatureSlice = sigSlice
   946  		}
   947  
   948  		if !common.ProtoSignatureAlreadyExists(signatureSlice.List, sigMeta) {
   949  			switch sigMeta.SignatureType {
   950  			case zcommon.NotationSignature:
   951  				signatureSlice.List = append(signatureSlice.List, &proto_go.SignatureInfo{
   952  					SignatureManifestDigest: sigMeta.SignatureDigest,
   953  					LayersInfo:              mConvert.GetProtoLayersInfo(sigMeta.LayersInfo),
   954  				})
   955  			case zcommon.CosignSignature:
   956  				newCosignSig := &proto_go.SignatureInfo{
   957  					SignatureManifestDigest: sigMeta.SignatureDigest,
   958  					LayersInfo:              mConvert.GetProtoLayersInfo(sigMeta.LayersInfo),
   959  				}
   960  
   961  				if zcommon.IsCosignTag(sigMeta.SignatureTag) {
   962  					// the entry for "sha256-{digest}.sig" signatures should be overwritten if
   963  					// it exists or added on the first position if it doesn't exist
   964  					if len(signatureSlice.GetList()) == 0 {
   965  						signatureSlice.List = []*proto_go.SignatureInfo{newCosignSig}
   966  					} else {
   967  						signatureSlice.List[0] = newCosignSig
   968  					}
   969  				} else {
   970  					// the first position should be reserved for "sha256-{digest}.sig" signatures
   971  					if len(signatureSlice.GetList()) == 0 {
   972  						signatureSlice.List = []*proto_go.SignatureInfo{{
   973  							SignatureManifestDigest: "",
   974  							LayersInfo:              []*proto_go.LayersInfo{},
   975  						}}
   976  					}
   977  
   978  					signatureSlice.List = append(signatureSlice.List, newCosignSig)
   979  				}
   980  			}
   981  		}
   982  
   983  		manifestSignatures.Map[sigMeta.SignatureType] = signatureSlice
   984  		protoRepoMeta.Signatures[signedManifestDigest.String()] = manifestSignatures
   985  
   986  		return setProtoRepoMeta(protoRepoMeta, repoMetaBuck)
   987  	})
   988  
   989  	return err
   990  }
   991  
   992  func (bdw *BoltDB) DeleteSignature(repo string, signedManifestDigest godigest.Digest,
   993  	sigMeta mTypes.SignatureMetadata,
   994  ) error {
   995  	err := bdw.DB.Update(func(tx *bbolt.Tx) error {
   996  		repoMetaBuck := tx.Bucket([]byte(RepoMetaBuck))
   997  
   998  		repoMetaBlob := repoMetaBuck.Get([]byte(repo))
   999  		if len(repoMetaBlob) == 0 {
  1000  			return zerr.ErrImageMetaNotFound
  1001  		}
  1002  
  1003  		protoRepoMeta, err := unmarshalProtoRepoMeta(repo, repoMetaBlob)
  1004  		if err != nil {
  1005  			return err
  1006  		}
  1007  
  1008  		manifestSignatures, found := protoRepoMeta.Signatures[signedManifestDigest.String()]
  1009  		if !found {
  1010  			return zerr.ErrImageMetaNotFound
  1011  		}
  1012  
  1013  		signatureSlice := manifestSignatures.Map[sigMeta.SignatureType]
  1014  
  1015  		newSignatureSlice := make([]*proto_go.SignatureInfo, 0, len(signatureSlice.List))
  1016  
  1017  		for _, sigInfo := range signatureSlice.List {
  1018  			if sigInfo.SignatureManifestDigest != sigMeta.SignatureDigest {
  1019  				newSignatureSlice = append(newSignatureSlice, sigInfo)
  1020  			}
  1021  		}
  1022  
  1023  		manifestSignatures.Map[sigMeta.SignatureType] = &proto_go.SignaturesInfo{List: newSignatureSlice}
  1024  
  1025  		protoRepoMeta.Signatures[signedManifestDigest.String()] = manifestSignatures
  1026  
  1027  		return setProtoRepoMeta(protoRepoMeta, repoMetaBuck)
  1028  	})
  1029  
  1030  	return err
  1031  }
  1032  
  1033  func (bdw *BoltDB) IncrementRepoStars(repo string) error {
  1034  	err := bdw.DB.Update(func(tx *bbolt.Tx) error {
  1035  		repoMetaBuck := tx.Bucket([]byte(RepoMetaBuck))
  1036  
  1037  		repoMetaBlob := repoMetaBuck.Get([]byte(repo))
  1038  		if len(repoMetaBlob) == 0 {
  1039  			return zerr.ErrRepoMetaNotFound
  1040  		}
  1041  
  1042  		protoRepoMeta, err := unmarshalProtoRepoMeta(repo, repoMetaBlob)
  1043  		if err != nil {
  1044  			return err
  1045  		}
  1046  
  1047  		protoRepoMeta.Stars++
  1048  
  1049  		return setProtoRepoMeta(protoRepoMeta, repoMetaBuck)
  1050  	})
  1051  
  1052  	return err
  1053  }
  1054  
  1055  func (bdw *BoltDB) DecrementRepoStars(repo string) error {
  1056  	err := bdw.DB.Update(func(tx *bbolt.Tx) error {
  1057  		repoMetaBuck := tx.Bucket([]byte(RepoMetaBuck))
  1058  
  1059  		repoMetaBlob := repoMetaBuck.Get([]byte(repo))
  1060  		if len(repoMetaBlob) == 0 {
  1061  			return zerr.ErrRepoMetaNotFound
  1062  		}
  1063  
  1064  		protoRepoMeta, err := unmarshalProtoRepoMeta(repo, repoMetaBlob)
  1065  		if err != nil {
  1066  			return err
  1067  		}
  1068  
  1069  		if protoRepoMeta.Stars == 0 {
  1070  			return nil
  1071  		}
  1072  
  1073  		protoRepoMeta.Stars--
  1074  
  1075  		return setProtoRepoMeta(protoRepoMeta, repoMetaBuck)
  1076  	})
  1077  
  1078  	return err
  1079  }
  1080  
  1081  func (bdw *BoltDB) SetRepoMeta(repo string, repoMeta mTypes.RepoMeta) error {
  1082  	err := bdw.DB.Update(func(tx *bbolt.Tx) error {
  1083  		buck := tx.Bucket([]byte(RepoMetaBuck))
  1084  		repoLastUpdatedBuck := tx.Bucket([]byte(RepoBlobsBuck)).Bucket([]byte(RepoLastUpdatedBuck))
  1085  
  1086  		repoMeta.Name = repo
  1087  
  1088  		repoMetaBlob, err := proto.Marshal(mConvert.GetProtoRepoMeta(repoMeta))
  1089  		if err != nil {
  1090  			return err
  1091  		}
  1092  
  1093  		err = buck.Put([]byte(repo), repoMetaBlob)
  1094  		if err != nil {
  1095  			return err
  1096  		}
  1097  
  1098  		// The last update time is set to 0 in order to force an update in case of a next storage parsing
  1099  		return setRepoLastUpdated(repo, time.Time{}, repoLastUpdatedBuck)
  1100  	})
  1101  
  1102  	return err
  1103  }
  1104  
  1105  func (bdw *BoltDB) DeleteRepoMeta(repo string) error {
  1106  	err := bdw.DB.Update(func(tx *bbolt.Tx) error {
  1107  		repoBuck := tx.Bucket([]byte(RepoMetaBuck))
  1108  		repoBlobsBuck := tx.Bucket([]byte(RepoBlobsBuck))
  1109  		repoLastUpdatedBuck := repoBlobsBuck.Bucket([]byte(RepoLastUpdatedBuck))
  1110  
  1111  		err := repoBuck.Delete([]byte(repo))
  1112  		if err != nil {
  1113  			return err
  1114  		}
  1115  
  1116  		err = repoBlobsBuck.Delete([]byte(repo))
  1117  		if err != nil {
  1118  			return err
  1119  		}
  1120  
  1121  		return repoLastUpdatedBuck.Delete([]byte(repo))
  1122  	})
  1123  
  1124  	return err
  1125  }
  1126  
  1127  func (bdw *BoltDB) ResetRepoReferences(repo string) error {
  1128  	err := bdw.DB.Update(func(tx *bbolt.Tx) error {
  1129  		buck := tx.Bucket([]byte(RepoMetaBuck))
  1130  
  1131  		repoMetaBlob := buck.Get([]byte(repo))
  1132  
  1133  		protoRepoMeta, err := unmarshalProtoRepoMeta(repo, repoMetaBlob)
  1134  		if err != nil && !errors.Is(err, zerr.ErrRepoMetaNotFound) {
  1135  			return err
  1136  		}
  1137  
  1138  		repoMetaBlob, err = proto.Marshal(&proto_go.RepoMeta{
  1139  			Name:       repo,
  1140  			Statistics: protoRepoMeta.Statistics,
  1141  			Stars:      protoRepoMeta.Stars,
  1142  			Tags:       map[string]*proto_go.TagDescriptor{"": {}},
  1143  			Signatures: map[string]*proto_go.ManifestSignatures{"": {Map: map[string]*proto_go.SignaturesInfo{"": {}}}},
  1144  			Referrers:  map[string]*proto_go.ReferrersInfo{"": {}},
  1145  		})
  1146  		if err != nil {
  1147  			return err
  1148  		}
  1149  
  1150  		return buck.Put([]byte(repo), repoMetaBlob)
  1151  	})
  1152  
  1153  	return err
  1154  }
  1155  
  1156  func (bdw *BoltDB) GetReferrersInfo(repo string, referredDigest godigest.Digest, artifactTypes []string,
  1157  ) ([]mTypes.ReferrerInfo, error) {
  1158  	referrersInfoResult := []mTypes.ReferrerInfo{}
  1159  
  1160  	err := bdw.DB.View(func(tx *bbolt.Tx) error {
  1161  		buck := tx.Bucket([]byte(RepoMetaBuck))
  1162  
  1163  		repoMetaBlob := buck.Get([]byte(repo))
  1164  
  1165  		protoRepoMeta, err := unmarshalProtoRepoMeta(repo, repoMetaBlob)
  1166  		if err != nil {
  1167  			return err
  1168  		}
  1169  
  1170  		referrersInfo := protoRepoMeta.Referrers[referredDigest.String()].List
  1171  
  1172  		for i := range referrersInfo {
  1173  			if !common.MatchesArtifactTypes(referrersInfo[i].ArtifactType, artifactTypes) {
  1174  				continue
  1175  			}
  1176  
  1177  			referrersInfoResult = append(referrersInfoResult, mTypes.ReferrerInfo{
  1178  				Digest:       referrersInfo[i].Digest,
  1179  				MediaType:    referrersInfo[i].MediaType,
  1180  				ArtifactType: referrersInfo[i].ArtifactType,
  1181  				Size:         int(referrersInfo[i].Size),
  1182  				Annotations:  referrersInfo[i].Annotations,
  1183  			})
  1184  		}
  1185  
  1186  		return nil
  1187  	})
  1188  
  1189  	return referrersInfoResult, err
  1190  }
  1191  
  1192  func (bdw *BoltDB) UpdateStatsOnDownload(repo string, reference string) error {
  1193  	err := bdw.DB.Update(func(tx *bbolt.Tx) error {
  1194  		repoMetaBuck := tx.Bucket([]byte(RepoMetaBuck))
  1195  
  1196  		repoMetaBlob := repoMetaBuck.Get([]byte(repo))
  1197  		if len(repoMetaBlob) == 0 {
  1198  			return zerr.ErrRepoMetaNotFound
  1199  		}
  1200  
  1201  		protoRepoMeta, err := unmarshalProtoRepoMeta(repo, repoMetaBlob)
  1202  		if err != nil {
  1203  			return err
  1204  		}
  1205  
  1206  		manifestDigest := reference
  1207  
  1208  		if common.ReferenceIsTag(reference) {
  1209  			descriptor, found := protoRepoMeta.Tags[reference]
  1210  
  1211  			if !found {
  1212  				return zerr.ErrImageMetaNotFound
  1213  			}
  1214  
  1215  			manifestDigest = descriptor.Digest
  1216  		}
  1217  
  1218  		manifestStatistics, ok := protoRepoMeta.Statistics[manifestDigest]
  1219  		if !ok {
  1220  			return zerr.ErrImageMetaNotFound
  1221  		}
  1222  
  1223  		manifestStatistics.DownloadCount++
  1224  		manifestStatistics.LastPullTimestamp = timestamppb.Now()
  1225  		protoRepoMeta.Statistics[manifestDigest] = manifestStatistics
  1226  
  1227  		return setProtoRepoMeta(protoRepoMeta, repoMetaBuck)
  1228  	})
  1229  
  1230  	return err
  1231  }
  1232  
  1233  func (bdw *BoltDB) UpdateSignaturesValidity(ctx context.Context, repo string, manifestDigest godigest.Digest) error {
  1234  	err := bdw.DB.Update(func(transaction *bbolt.Tx) error {
  1235  		imgTrustStore := bdw.ImageTrustStore()
  1236  
  1237  		if imgTrustStore == nil {
  1238  			return nil
  1239  		}
  1240  
  1241  		// get ManifestData of signed manifest
  1242  		imageMetaBuck := transaction.Bucket([]byte(ImageMetaBuck))
  1243  		idBlob := imageMetaBuck.Get([]byte(manifestDigest))
  1244  
  1245  		if len(idBlob) == 0 {
  1246  			// manifest meta not found, updating signatures with details about validity and author will not be performed
  1247  			return nil
  1248  		}
  1249  
  1250  		protoImageMeta := proto_go.ImageMeta{}
  1251  
  1252  		err := proto.Unmarshal(idBlob, &protoImageMeta)
  1253  		if err != nil {
  1254  			return err
  1255  		}
  1256  
  1257  		// update signatures with details about validity and author
  1258  		repoBuck := transaction.Bucket([]byte(RepoMetaBuck))
  1259  
  1260  		repoMetaBlob := repoBuck.Get([]byte(repo))
  1261  		if len(repoMetaBlob) == 0 {
  1262  			return zerr.ErrRepoMetaNotFound
  1263  		}
  1264  
  1265  		protoRepoMeta, err := unmarshalProtoRepoMeta(repo, repoMetaBlob)
  1266  		if err != nil {
  1267  			return err
  1268  		}
  1269  
  1270  		manifestSignatures := proto_go.ManifestSignatures{Map: map[string]*proto_go.SignaturesInfo{"": {}}}
  1271  		for sigType, sigs := range protoRepoMeta.Signatures[manifestDigest.String()].Map {
  1272  			if zcommon.IsContextDone(ctx) {
  1273  				return ctx.Err()
  1274  			}
  1275  
  1276  			signaturesInfo := []*proto_go.SignatureInfo{}
  1277  
  1278  			for _, sigInfo := range sigs.List {
  1279  				layersInfo := []*proto_go.LayersInfo{}
  1280  
  1281  				for _, layerInfo := range sigInfo.LayersInfo {
  1282  					author, date, isTrusted, _ := imgTrustStore.VerifySignature(sigType, layerInfo.LayerContent,
  1283  						layerInfo.SignatureKey, manifestDigest, mConvert.GetImageMeta(&protoImageMeta), repo)
  1284  
  1285  					if isTrusted {
  1286  						layerInfo.Signer = author
  1287  					}
  1288  
  1289  					if !date.IsZero() {
  1290  						layerInfo.Signer = author
  1291  						layerInfo.Date = timestamppb.New(date)
  1292  					}
  1293  
  1294  					layersInfo = append(layersInfo, layerInfo)
  1295  				}
  1296  
  1297  				signaturesInfo = append(signaturesInfo, &proto_go.SignatureInfo{
  1298  					SignatureManifestDigest: sigInfo.SignatureManifestDigest,
  1299  					LayersInfo:              layersInfo,
  1300  				})
  1301  			}
  1302  
  1303  			manifestSignatures.Map[sigType] = &proto_go.SignaturesInfo{List: signaturesInfo}
  1304  		}
  1305  
  1306  		protoRepoMeta.Signatures[manifestDigest.String()] = &manifestSignatures
  1307  
  1308  		return setProtoRepoMeta(protoRepoMeta, repoBuck)
  1309  	})
  1310  
  1311  	return err
  1312  }
  1313  
  1314  func (bdw *BoltDB) RemoveRepoReference(repo, reference string, manifestDigest godigest.Digest) error {
  1315  	err := bdw.DB.Update(func(tx *bbolt.Tx) error {
  1316  		repoMetaBuck := tx.Bucket([]byte(RepoMetaBuck))
  1317  		imageMetaBuck := tx.Bucket([]byte(ImageMetaBuck))
  1318  		repoBlobsBuck := tx.Bucket([]byte(RepoBlobsBuck))
  1319  		repoLastUpdatedBuck := repoBlobsBuck.Bucket([]byte(RepoLastUpdatedBuck))
  1320  
  1321  		protoRepoMeta, err := getProtoRepoMeta(repo, repoMetaBuck)
  1322  		if err != nil {
  1323  			if errors.Is(err, zerr.ErrRepoMetaNotFound) {
  1324  				return nil
  1325  			}
  1326  
  1327  			return err
  1328  		}
  1329  
  1330  		protoImageMeta, err := getProtoImageMeta(imageMetaBuck, manifestDigest.String())
  1331  		if err != nil {
  1332  			if errors.Is(err, zerr.ErrImageMetaNotFound) {
  1333  				return nil
  1334  			}
  1335  
  1336  			return err
  1337  		}
  1338  
  1339  		// Remove Referrers
  1340  		if subject := mConvert.GetImageSubject(protoImageMeta); subject != nil {
  1341  			referredDigest := subject.Digest.String()
  1342  			refInfo := &proto_go.ReferrersInfo{}
  1343  
  1344  			if protoRepoMeta.Referrers[referredDigest] != nil {
  1345  				refInfo = protoRepoMeta.Referrers[referredDigest]
  1346  			}
  1347  
  1348  			referrers := refInfo.List
  1349  
  1350  			for i := range referrers {
  1351  				if referrers[i].Digest == manifestDigest.String() {
  1352  					referrers[i].Count -= 1
  1353  
  1354  					if referrers[i].Count == 0 || common.ReferenceIsDigest(reference) {
  1355  						referrers = append(referrers[:i], referrers[i+1:]...)
  1356  					}
  1357  
  1358  					break
  1359  				}
  1360  			}
  1361  
  1362  			refInfo.List = referrers
  1363  
  1364  			protoRepoMeta.Referrers[referredDigest] = refInfo
  1365  		}
  1366  
  1367  		if !common.ReferenceIsDigest(reference) {
  1368  			delete(protoRepoMeta.Tags, reference)
  1369  		} else {
  1370  			// remove all tags pointing to this digest
  1371  			for tag, desc := range protoRepoMeta.Tags {
  1372  				if desc.Digest == reference {
  1373  					delete(protoRepoMeta.Tags, tag)
  1374  				}
  1375  			}
  1376  		}
  1377  
  1378  		/* try to find at least one tag pointing to manifestDigest
  1379  		if not found then we can also remove everything related to this digest */
  1380  		var foundTag bool
  1381  		for _, desc := range protoRepoMeta.Tags {
  1382  			if desc.Digest == manifestDigest.String() {
  1383  				foundTag = true
  1384  			}
  1385  		}
  1386  
  1387  		if !foundTag {
  1388  			delete(protoRepoMeta.Statistics, manifestDigest.String())
  1389  			delete(protoRepoMeta.Signatures, manifestDigest.String())
  1390  			delete(protoRepoMeta.Referrers, manifestDigest.String())
  1391  		}
  1392  
  1393  		repoBlobsBytes := repoBlobsBuck.Get([]byte(protoRepoMeta.Name))
  1394  
  1395  		repoBlobs, err := unmarshalProtoRepoBlobs(repo, repoBlobsBytes)
  1396  		if err != nil {
  1397  			return err
  1398  		}
  1399  
  1400  		err = setRepoLastUpdated(repo, time.Now(), repoLastUpdatedBuck)
  1401  		if err != nil {
  1402  			return err
  1403  		}
  1404  
  1405  		protoRepoMeta, repoBlobs = common.RemoveImageFromRepoMeta(protoRepoMeta, repoBlobs, reference)
  1406  
  1407  		repoBlobsBytes, err = proto.Marshal(repoBlobs)
  1408  		if err != nil {
  1409  			return err
  1410  		}
  1411  
  1412  		err = repoBlobsBuck.Put([]byte(protoRepoMeta.Name), repoBlobsBytes)
  1413  		if err != nil {
  1414  			return err
  1415  		}
  1416  
  1417  		return setProtoRepoMeta(protoRepoMeta, repoMetaBuck)
  1418  	})
  1419  
  1420  	return err
  1421  }
  1422  
  1423  func (bdw *BoltDB) ImageTrustStore() mTypes.ImageTrustStore {
  1424  	return bdw.imgTrustStore
  1425  }
  1426  
  1427  func (bdw *BoltDB) SetImageTrustStore(imgTrustStore mTypes.ImageTrustStore) {
  1428  	bdw.imgTrustStore = imgTrustStore
  1429  }
  1430  
  1431  func (bdw *BoltDB) ToggleStarRepo(ctx context.Context, repo string) (mTypes.ToggleState, error) {
  1432  	userAc, err := reqCtx.UserAcFromContext(ctx)
  1433  	if err != nil {
  1434  		return mTypes.NotChanged, err
  1435  	}
  1436  
  1437  	if userAc.IsAnonymous() || !userAc.Can(constants.ReadPermission, repo) {
  1438  		return mTypes.NotChanged, zerr.ErrUserDataNotAllowed
  1439  	}
  1440  
  1441  	userid := userAc.GetUsername()
  1442  
  1443  	var res mTypes.ToggleState
  1444  
  1445  	if err := bdw.DB.Update(func(tx *bbolt.Tx) error { //nolint:varnamelen
  1446  		var userData mTypes.UserData
  1447  
  1448  		err := bdw.getUserData(userid, tx, &userData)
  1449  		if err != nil && !errors.Is(err, zerr.ErrUserDataNotFound) {
  1450  			return err
  1451  		}
  1452  
  1453  		isRepoStarred := zcommon.Contains(userData.StarredRepos, repo)
  1454  
  1455  		if isRepoStarred {
  1456  			res = mTypes.Removed
  1457  			userData.StarredRepos = zcommon.RemoveFrom(userData.StarredRepos, repo)
  1458  		} else {
  1459  			res = mTypes.Added
  1460  			userData.StarredRepos = append(userData.StarredRepos, repo)
  1461  		}
  1462  
  1463  		err = bdw.setUserData(userid, tx, userData)
  1464  		if err != nil {
  1465  			return err
  1466  		}
  1467  
  1468  		repoBuck := tx.Bucket([]byte(RepoMetaBuck))
  1469  
  1470  		repoMetaBlob := repoBuck.Get([]byte(repo))
  1471  		if len(repoMetaBlob) == 0 {
  1472  			return zerr.ErrRepoMetaNotFound
  1473  		}
  1474  
  1475  		protoRepoMeta, err := unmarshalProtoRepoMeta(repo, repoMetaBlob)
  1476  		if err != nil {
  1477  			return err
  1478  		}
  1479  
  1480  		switch res {
  1481  		case mTypes.Added:
  1482  			protoRepoMeta.Stars++
  1483  		case mTypes.Removed:
  1484  			protoRepoMeta.Stars--
  1485  		}
  1486  
  1487  		return setProtoRepoMeta(protoRepoMeta, repoBuck)
  1488  	}); err != nil {
  1489  		return mTypes.NotChanged, err
  1490  	}
  1491  
  1492  	return res, nil
  1493  }
  1494  
  1495  func (bdw *BoltDB) GetStarredRepos(ctx context.Context) ([]string, error) {
  1496  	userData, err := bdw.GetUserData(ctx)
  1497  	if errors.Is(err, zerr.ErrUserDataNotFound) || errors.Is(err, zerr.ErrUserDataNotAllowed) {
  1498  		return []string{}, nil
  1499  	}
  1500  
  1501  	return userData.StarredRepos, err
  1502  }
  1503  
  1504  func (bdw *BoltDB) ToggleBookmarkRepo(ctx context.Context, repo string) (mTypes.ToggleState, error) {
  1505  	userAc, err := reqCtx.UserAcFromContext(ctx)
  1506  	if err != nil {
  1507  		return mTypes.NotChanged, err
  1508  	}
  1509  
  1510  	if userAc.IsAnonymous() || !userAc.Can(constants.ReadPermission, repo) {
  1511  		return mTypes.NotChanged, zerr.ErrUserDataNotAllowed
  1512  	}
  1513  
  1514  	userid := userAc.GetUsername()
  1515  
  1516  	var res mTypes.ToggleState
  1517  
  1518  	if err := bdw.DB.Update(func(transaction *bbolt.Tx) error { //nolint:dupl
  1519  		var userData mTypes.UserData
  1520  
  1521  		err := bdw.getUserData(userid, transaction, &userData)
  1522  		if err != nil && !errors.Is(err, zerr.ErrUserDataNotFound) {
  1523  			return err
  1524  		}
  1525  
  1526  		isRepoBookmarked := zcommon.Contains(userData.BookmarkedRepos, repo)
  1527  
  1528  		if isRepoBookmarked {
  1529  			res = mTypes.Removed
  1530  			userData.BookmarkedRepos = zcommon.RemoveFrom(userData.BookmarkedRepos, repo)
  1531  		} else {
  1532  			res = mTypes.Added
  1533  			userData.BookmarkedRepos = append(userData.BookmarkedRepos, repo)
  1534  		}
  1535  
  1536  		return bdw.setUserData(userid, transaction, userData)
  1537  	}); err != nil {
  1538  		return mTypes.NotChanged, err
  1539  	}
  1540  
  1541  	return res, nil
  1542  }
  1543  
  1544  func (bdw *BoltDB) GetBookmarkedRepos(ctx context.Context) ([]string, error) {
  1545  	userData, err := bdw.GetUserData(ctx)
  1546  	if errors.Is(err, zerr.ErrUserDataNotFound) || errors.Is(err, zerr.ErrUserDataNotAllowed) {
  1547  		return []string{}, nil
  1548  	}
  1549  
  1550  	return userData.BookmarkedRepos, err
  1551  }
  1552  
  1553  func (bdw *BoltDB) PatchDB() error {
  1554  	var DBVersion string
  1555  
  1556  	err := bdw.DB.View(func(tx *bbolt.Tx) error {
  1557  		versionBuck := tx.Bucket([]byte(VersionBucket))
  1558  		DBVersion = string(versionBuck.Get([]byte(version.DBVersionKey)))
  1559  
  1560  		return nil
  1561  	})
  1562  	if err != nil {
  1563  		return fmt.Errorf("patching the database failed, can't read db version %w", err)
  1564  	}
  1565  
  1566  	if version.GetVersionIndex(DBVersion) == -1 {
  1567  		return fmt.Errorf("DB has broken format, no version found %w", err)
  1568  	}
  1569  
  1570  	for patchIndex, patch := range bdw.Patches {
  1571  		if patchIndex < version.GetVersionIndex(DBVersion) {
  1572  			continue
  1573  		}
  1574  
  1575  		err := patch(bdw.DB)
  1576  		if err != nil {
  1577  			return err
  1578  		}
  1579  	}
  1580  
  1581  	return nil
  1582  }
  1583  
  1584  func getUserStars(ctx context.Context, transaction *bbolt.Tx) []string {
  1585  	userAc, err := reqCtx.UserAcFromContext(ctx)
  1586  	if err != nil {
  1587  		return []string{}
  1588  	}
  1589  
  1590  	var (
  1591  		userData mTypes.UserData
  1592  		userid   = userAc.GetUsername()
  1593  		userdb   = transaction.Bucket([]byte(UserDataBucket))
  1594  	)
  1595  
  1596  	if userid == "" || userdb == nil {
  1597  		return []string{}
  1598  	}
  1599  
  1600  	mdata := userdb.Get([]byte(userid))
  1601  	if mdata == nil {
  1602  		return []string{}
  1603  	}
  1604  
  1605  	if err := json.Unmarshal(mdata, &userData); err != nil {
  1606  		return []string{}
  1607  	}
  1608  
  1609  	return userData.StarredRepos
  1610  }
  1611  
  1612  func getUserBookmarks(ctx context.Context, transaction *bbolt.Tx) []string {
  1613  	userAc, err := reqCtx.UserAcFromContext(ctx)
  1614  	if err != nil {
  1615  		return []string{}
  1616  	}
  1617  
  1618  	var (
  1619  		userData mTypes.UserData
  1620  		userid   = userAc.GetUsername()
  1621  		userdb   = transaction.Bucket([]byte(UserDataBucket))
  1622  	)
  1623  
  1624  	if userid == "" || userdb == nil {
  1625  		return []string{}
  1626  	}
  1627  
  1628  	mdata := userdb.Get([]byte(userid))
  1629  	if mdata == nil {
  1630  		return []string{}
  1631  	}
  1632  
  1633  	if err := json.Unmarshal(mdata, &userData); err != nil {
  1634  		return []string{}
  1635  	}
  1636  
  1637  	return userData.BookmarkedRepos
  1638  }
  1639  
  1640  func (bdw *BoltDB) SetUserGroups(ctx context.Context, groups []string) error {
  1641  	userAc, err := reqCtx.UserAcFromContext(ctx)
  1642  	if err != nil {
  1643  		return err
  1644  	}
  1645  
  1646  	if userAc.IsAnonymous() {
  1647  		return zerr.ErrUserDataNotAllowed
  1648  	}
  1649  
  1650  	userid := userAc.GetUsername()
  1651  
  1652  	err = bdw.DB.Update(func(tx *bbolt.Tx) error { //nolint:varnamelen
  1653  		var userData mTypes.UserData
  1654  
  1655  		err := bdw.getUserData(userid, tx, &userData)
  1656  		if err != nil && !errors.Is(err, zerr.ErrUserDataNotFound) {
  1657  			return err
  1658  		}
  1659  
  1660  		userData.Groups = append(userData.Groups, groups...)
  1661  
  1662  		err = bdw.setUserData(userid, tx, userData)
  1663  
  1664  		return err
  1665  	})
  1666  
  1667  	return err
  1668  }
  1669  
  1670  func (bdw *BoltDB) GetUserGroups(ctx context.Context) ([]string, error) {
  1671  	userData, err := bdw.GetUserData(ctx)
  1672  
  1673  	return userData.Groups, err
  1674  }
  1675  
  1676  func (bdw *BoltDB) UpdateUserAPIKeyLastUsed(ctx context.Context, hashedKey string) error {
  1677  	userAc, err := reqCtx.UserAcFromContext(ctx)
  1678  	if err != nil {
  1679  		return err
  1680  	}
  1681  
  1682  	if userAc.IsAnonymous() {
  1683  		return zerr.ErrUserDataNotAllowed
  1684  	}
  1685  
  1686  	userid := userAc.GetUsername()
  1687  
  1688  	err = bdw.DB.Update(func(tx *bbolt.Tx) error { //nolint:varnamelen
  1689  		var userData mTypes.UserData
  1690  
  1691  		err := bdw.getUserData(userid, tx, &userData)
  1692  		if err != nil {
  1693  			return err
  1694  		}
  1695  
  1696  		apiKeyDetails := userData.APIKeys[hashedKey]
  1697  		apiKeyDetails.LastUsed = time.Now()
  1698  
  1699  		userData.APIKeys[hashedKey] = apiKeyDetails
  1700  
  1701  		err = bdw.setUserData(userid, tx, userData)
  1702  
  1703  		return err
  1704  	})
  1705  
  1706  	return err
  1707  }
  1708  
  1709  func (bdw *BoltDB) IsAPIKeyExpired(ctx context.Context, hashedKey string) (bool, error) {
  1710  	userAc, err := reqCtx.UserAcFromContext(ctx)
  1711  	if err != nil {
  1712  		return false, err
  1713  	}
  1714  
  1715  	if userAc.IsAnonymous() {
  1716  		return false, zerr.ErrUserDataNotAllowed
  1717  	}
  1718  
  1719  	userid := userAc.GetUsername()
  1720  
  1721  	var isExpired bool
  1722  
  1723  	err = bdw.DB.Update(func(tx *bbolt.Tx) error { //nolint:varnamelen
  1724  		var userData mTypes.UserData
  1725  
  1726  		err := bdw.getUserData(userid, tx, &userData)
  1727  		if err != nil {
  1728  			return err
  1729  		}
  1730  
  1731  		apiKeyDetails := userData.APIKeys[hashedKey]
  1732  		if apiKeyDetails.IsExpired {
  1733  			isExpired = true
  1734  
  1735  			return nil
  1736  		}
  1737  
  1738  		// if expiresAt is not nil value
  1739  		if !apiKeyDetails.ExpirationDate.Equal(time.Time{}) && time.Now().After(apiKeyDetails.ExpirationDate) {
  1740  			isExpired = true
  1741  			apiKeyDetails.IsExpired = true
  1742  		}
  1743  
  1744  		userData.APIKeys[hashedKey] = apiKeyDetails
  1745  
  1746  		err = bdw.setUserData(userid, tx, userData)
  1747  
  1748  		return err
  1749  	})
  1750  
  1751  	return isExpired, err
  1752  }
  1753  
  1754  func (bdw *BoltDB) GetUserAPIKeys(ctx context.Context) ([]mTypes.APIKeyDetails, error) {
  1755  	apiKeys := make([]mTypes.APIKeyDetails, 0)
  1756  
  1757  	userAc, err := reqCtx.UserAcFromContext(ctx)
  1758  	if err != nil {
  1759  		return nil, err
  1760  	}
  1761  
  1762  	if userAc.IsAnonymous() {
  1763  		return nil, zerr.ErrUserDataNotAllowed
  1764  	}
  1765  
  1766  	userid := userAc.GetUsername()
  1767  
  1768  	err = bdw.DB.Update(func(transaction *bbolt.Tx) error {
  1769  		var userData mTypes.UserData
  1770  
  1771  		err = bdw.getUserData(userid, transaction, &userData)
  1772  		if err != nil && !errors.Is(err, zerr.ErrUserDataNotFound) {
  1773  			return err
  1774  		}
  1775  
  1776  		for hashedKey, apiKeyDetails := range userData.APIKeys {
  1777  			// if expiresAt is not nil value
  1778  			if !apiKeyDetails.ExpirationDate.Equal(time.Time{}) && time.Now().After(apiKeyDetails.ExpirationDate) {
  1779  				apiKeyDetails.IsExpired = true
  1780  			}
  1781  
  1782  			userData.APIKeys[hashedKey] = apiKeyDetails
  1783  
  1784  			err = bdw.setUserData(userid, transaction, userData)
  1785  			if err != nil {
  1786  				return err
  1787  			}
  1788  
  1789  			apiKeys = append(apiKeys, apiKeyDetails)
  1790  		}
  1791  
  1792  		return nil
  1793  	})
  1794  
  1795  	return apiKeys, err
  1796  }
  1797  
  1798  func (bdw *BoltDB) AddUserAPIKey(ctx context.Context, hashedKey string, apiKeyDetails *mTypes.APIKeyDetails) error {
  1799  	userAc, err := reqCtx.UserAcFromContext(ctx)
  1800  	if err != nil {
  1801  		return err
  1802  	}
  1803  
  1804  	if userAc.IsAnonymous() {
  1805  		return zerr.ErrUserDataNotAllowed
  1806  	}
  1807  
  1808  	userid := userAc.GetUsername()
  1809  
  1810  	err = bdw.DB.Update(func(transaction *bbolt.Tx) error {
  1811  		var userData mTypes.UserData
  1812  
  1813  		apiKeysbuck := transaction.Bucket([]byte(UserAPIKeysBucket))
  1814  		if apiKeysbuck == nil {
  1815  			return zerr.ErrBucketDoesNotExist
  1816  		}
  1817  
  1818  		err := apiKeysbuck.Put([]byte(hashedKey), []byte(userid))
  1819  		if err != nil {
  1820  			return fmt.Errorf("failed to set userData for identity %s %w", userid, err)
  1821  		}
  1822  
  1823  		err = bdw.getUserData(userid, transaction, &userData)
  1824  		if err != nil && !errors.Is(err, zerr.ErrUserDataNotFound) {
  1825  			return err
  1826  		}
  1827  
  1828  		if userData.APIKeys == nil {
  1829  			userData.APIKeys = make(map[string]mTypes.APIKeyDetails)
  1830  		}
  1831  
  1832  		userData.APIKeys[hashedKey] = *apiKeyDetails
  1833  
  1834  		err = bdw.setUserData(userid, transaction, userData)
  1835  
  1836  		return err
  1837  	})
  1838  
  1839  	return err
  1840  }
  1841  
  1842  func (bdw *BoltDB) DeleteUserAPIKey(ctx context.Context, keyID string) error {
  1843  	userAc, err := reqCtx.UserAcFromContext(ctx)
  1844  	if err != nil {
  1845  		return err
  1846  	}
  1847  
  1848  	if userAc.IsAnonymous() {
  1849  		return zerr.ErrUserDataNotAllowed
  1850  	}
  1851  
  1852  	userid := userAc.GetUsername()
  1853  
  1854  	err = bdw.DB.Update(func(transaction *bbolt.Tx) error {
  1855  		var userData mTypes.UserData
  1856  
  1857  		apiKeysbuck := transaction.Bucket([]byte(UserAPIKeysBucket))
  1858  		if apiKeysbuck == nil {
  1859  			return zerr.ErrBucketDoesNotExist
  1860  		}
  1861  
  1862  		err := bdw.getUserData(userid, transaction, &userData)
  1863  		if err != nil {
  1864  			return err
  1865  		}
  1866  
  1867  		for hash, apiKeyDetails := range userData.APIKeys {
  1868  			if apiKeyDetails.UUID == keyID {
  1869  				delete(userData.APIKeys, hash)
  1870  
  1871  				err := apiKeysbuck.Delete([]byte(hash))
  1872  				if err != nil {
  1873  					return fmt.Errorf("failed to delete userAPIKey entry for hash %s %w", hash, err)
  1874  				}
  1875  			}
  1876  		}
  1877  
  1878  		return bdw.setUserData(userid, transaction, userData)
  1879  	})
  1880  
  1881  	return err
  1882  }
  1883  
  1884  func (bdw *BoltDB) GetUserAPIKeyInfo(hashedKey string) (string, error) {
  1885  	var userid string
  1886  	err := bdw.DB.View(func(tx *bbolt.Tx) error {
  1887  		buck := tx.Bucket([]byte(UserAPIKeysBucket))
  1888  		if buck == nil {
  1889  			return zerr.ErrBucketDoesNotExist
  1890  		}
  1891  
  1892  		uiBlob := buck.Get([]byte(hashedKey))
  1893  		if len(uiBlob) == 0 {
  1894  			return zerr.ErrUserAPIKeyNotFound
  1895  		}
  1896  
  1897  		userid = string(uiBlob)
  1898  
  1899  		return nil
  1900  	})
  1901  
  1902  	return userid, err
  1903  }
  1904  
  1905  func (bdw *BoltDB) GetUserData(ctx context.Context) (mTypes.UserData, error) {
  1906  	var userData mTypes.UserData
  1907  
  1908  	userAc, err := reqCtx.UserAcFromContext(ctx)
  1909  	if err != nil {
  1910  		return userData, err
  1911  	}
  1912  
  1913  	if userAc.IsAnonymous() {
  1914  		return userData, zerr.ErrUserDataNotAllowed
  1915  	}
  1916  
  1917  	userid := userAc.GetUsername()
  1918  
  1919  	err = bdw.DB.View(func(tx *bbolt.Tx) error {
  1920  		return bdw.getUserData(userid, tx, &userData)
  1921  	})
  1922  
  1923  	return userData, err
  1924  }
  1925  
  1926  func (bdw *BoltDB) getUserData(userid string, transaction *bbolt.Tx, res *mTypes.UserData) error {
  1927  	buck := transaction.Bucket([]byte(UserDataBucket))
  1928  	if buck == nil {
  1929  		return zerr.ErrBucketDoesNotExist
  1930  	}
  1931  
  1932  	upBlob := buck.Get([]byte(userid))
  1933  
  1934  	if len(upBlob) == 0 {
  1935  		return zerr.ErrUserDataNotFound
  1936  	}
  1937  
  1938  	err := json.Unmarshal(upBlob, res)
  1939  	if err != nil {
  1940  		return err
  1941  	}
  1942  
  1943  	return nil
  1944  }
  1945  
  1946  func (bdw *BoltDB) SetUserData(ctx context.Context, userData mTypes.UserData) error {
  1947  	userAc, err := reqCtx.UserAcFromContext(ctx)
  1948  	if err != nil {
  1949  		return err
  1950  	}
  1951  
  1952  	if userAc.IsAnonymous() {
  1953  		return zerr.ErrUserDataNotAllowed
  1954  	}
  1955  
  1956  	userid := userAc.GetUsername()
  1957  
  1958  	err = bdw.DB.Update(func(tx *bbolt.Tx) error {
  1959  		return bdw.setUserData(userid, tx, userData)
  1960  	})
  1961  
  1962  	return err
  1963  }
  1964  
  1965  func (bdw *BoltDB) setUserData(userid string, transaction *bbolt.Tx, userData mTypes.UserData) error {
  1966  	buck := transaction.Bucket([]byte(UserDataBucket))
  1967  	if buck == nil {
  1968  		return zerr.ErrBucketDoesNotExist
  1969  	}
  1970  
  1971  	upBlob, err := json.Marshal(userData)
  1972  	if err != nil {
  1973  		return err
  1974  	}
  1975  
  1976  	err = buck.Put([]byte(userid), upBlob)
  1977  	if err != nil {
  1978  		return fmt.Errorf("failed to set userData for identity %s %w", userid, err)
  1979  	}
  1980  
  1981  	return nil
  1982  }
  1983  
  1984  func (bdw *BoltDB) DeleteUserData(ctx context.Context) error {
  1985  	userAc, err := reqCtx.UserAcFromContext(ctx)
  1986  	if err != nil {
  1987  		return err
  1988  	}
  1989  
  1990  	if userAc.IsAnonymous() {
  1991  		return zerr.ErrUserDataNotAllowed
  1992  	}
  1993  
  1994  	userid := userAc.GetUsername()
  1995  
  1996  	err = bdw.DB.Update(func(tx *bbolt.Tx) error {
  1997  		buck := tx.Bucket([]byte(UserDataBucket))
  1998  		if buck == nil {
  1999  			return zerr.ErrBucketDoesNotExist
  2000  		}
  2001  
  2002  		err := buck.Delete([]byte(userid))
  2003  		if err != nil {
  2004  			return fmt.Errorf("failed to delete userData for identity %s %w", userid, err)
  2005  		}
  2006  
  2007  		return nil
  2008  	})
  2009  
  2010  	return err
  2011  }
  2012  
  2013  func (bdw *BoltDB) ResetDB() error {
  2014  	err := bdw.DB.Update(func(transaction *bbolt.Tx) error {
  2015  		err := resetBucket(transaction, RepoMetaBuck)
  2016  		if err != nil {
  2017  			return err
  2018  		}
  2019  
  2020  		err = resetBucket(transaction, ImageMetaBuck)
  2021  		if err != nil {
  2022  			return err
  2023  		}
  2024  
  2025  		err = resetBucket(transaction, RepoBlobsBuck)
  2026  		if err != nil {
  2027  			return err
  2028  		}
  2029  
  2030  		err = resetBucket(transaction, UserAPIKeysBucket)
  2031  		if err != nil {
  2032  			return err
  2033  		}
  2034  
  2035  		err = resetBucket(transaction, UserDataBucket)
  2036  		if err != nil {
  2037  			return err
  2038  		}
  2039  
  2040  		return nil
  2041  	})
  2042  
  2043  	return err
  2044  }
  2045  
  2046  func resetBucket(transaction *bbolt.Tx, bucketName string) error {
  2047  	bucket := transaction.Bucket([]byte(bucketName))
  2048  	if bucket == nil {
  2049  		return nil
  2050  	}
  2051  
  2052  	// we need to create the sub buckets if they exits, we'll presume the sub-buckets are not nested more than 1 layer
  2053  	subBuckets := [][]byte{}
  2054  
  2055  	err := bucket.ForEachBucket(func(bucketName []byte) error {
  2056  		subBuckets = append(subBuckets, bucketName)
  2057  
  2058  		return nil
  2059  	})
  2060  	if err != nil {
  2061  		return err
  2062  	}
  2063  
  2064  	err = transaction.DeleteBucket([]byte(bucketName))
  2065  	if err != nil {
  2066  		return err
  2067  	}
  2068  
  2069  	bucket, err = transaction.CreateBucketIfNotExists([]byte(bucketName))
  2070  	if err != nil {
  2071  		return err
  2072  	}
  2073  
  2074  	for _, subBucket := range subBuckets {
  2075  		_, err := bucket.CreateBucketIfNotExists(subBucket)
  2076  		if err != nil {
  2077  			return err
  2078  		}
  2079  	}
  2080  
  2081  	return err
  2082  }