mirror of
https://github.com/project-zot/zot.git
synced 2026-06-17 21:17:58 +08:00
f408df0dac
* feat(repodb): implement a DB for image specific information using boltdb
(cherry picked from commit e3cb60b856)
Some other fixes/improvements on top (Andrei)
Global search: The last updated attribute on repo level is now computed correctly.
Global search: Fix and enhance tests: validate more fields, and fix CVE verification logic
RepoListWithNewestImage: The vendors and platforms at repo level are no longer containing duplicate entries
CVE: scan OCIUncompressedLayer instead of skiping them (used in tests)
bug(repodb): do no try to increment download counters for signatures
Signed-off-by: Andrei Aaron <andaaron@cisco.com>
Add filtering to global search API (Laurentiu)
(cherry picked from commit a87976d635ea876fe8ced532e8adb7c3bb24098f)
Original work by Laurentiu Niculae <niculae.laurentiu1@gmail.com>
Fix pagination bug
- when limit was bigger than the repo count result contained empty results
- now correctly returns only maximum available number of repo results
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
Add history to the fields returned from RepoDB
Consolidate fields used in packages
- pkg/extensions/search/common/common_test
- pkg/extensions/search/common/common
Refactor duplicate code in GlobalSearch verification
Add vulnerability scan results to image:tag reply
Signed-off-by: Andrei Aaron <andaaron@cisco.com>
Refactor ExpandedRepoInfo to using RepoDB
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
(cherry picked from commit fd7dc85c3a9d028fd8860d3791cad4df769ed005)
Init RepoDB at startup
- sync with storage
- ignore images without a tag
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
(cherry picked from commit 359898facd6541b2aa99ee95080f7aabf28c2650)
Update request to get image:tag to use repodb
Signed-off-by: Andrei Aaron <andaaron@cisco.com>
Sync RepoDB logging
- added logging for errors
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
(cherry picked from commit 2e128f4d01712b34c70b5468285100b0657001bb)
sync-repodb minor error checking fix
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
Improve tests for syncing RepoDB with storage
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
(cherry picked from commit b18408c6d64e01312849fc18b929e3a2a7931e9e)
Update scoring rule for repos
- now prioritize matches to the end of the repo name
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
(cherry picked from commit 6961346ccf02223132b3b12a2132c80bd1b6b33c)
Upgrade search filters to permit multiple values
- multiple values for os and arch
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
(cherry picked from commit 3ffb72c6fc0587ff827a03fe4f76a13b27b876a0)
feature(repodb): add pagination for RepoListWithNewestImage
Signed-off-by: Alex Stan <alexandrustan96@yahoo.ro>
(cherry picked from commit 32c917f2dc65363b0856345289353559a8027aee)
test(fix): fix tests failing since repodb is used for listing all repos
1. One of the tests was verifying disk/oci related erros and is not applicable
2. Another test was actually broken in an older PR, the default store and
the substore were using the same repo names (the substore ones were unprefixed),
which should not be the case, this was causing a single entry to show
in the RepoDB instead of two separate entries for each test image
Root cause in: https://github.com/project-zot/zot/commit/b61aff62cd5088314cee39fae42cec0ad855bd7c#diff-b86e11fa5a3102b336caebec3b30a9d35e26af554dd8658f124dba2404b7d24aR88
Signed-off-by: Andrei Aaron <andaaron@cisco.com>
chore: move code reponsible for transforming objects to gql_generated types to separate package
Signed-off-by: Andrei Aaron <andaaron@cisco.com>
Process input for global search
- Clean input: query, filter strings
- Add validation for global search input
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
(cherry picked from commit f1ca8670fbe4a4a327ea25cf459237dbf23bb78a)
fix: only call cve scanning for data shown to the user
Signed-off-by: Andrei Aaron <andaaron@cisco.com>
GQL omit scanning for CVE if field is not required
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
(cherry picked from commit 5479ce45d6cb2abcf5fbccadeaf6f3393c3f6bf1)
Fix filtering logic in RepoDB
- filter parameter was set to false instead of being calculator from the later image
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
(cherry picked from commit a82d2327e34e5da617af0b7ca78a2dba90999f0a)
bug(repodb): Checking signature returns error if signed image is not found
- we considere a signature image orfan when the image it signs is not found
- we need this to ignore such signatures in certain cases
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
(cherry picked from commit d0418505f76467accd8e1ee34fcc2b2a165efae5)
feat(repodb): CVE logic to use repoDB
Also update some method signatures to remove usage of:
github.com/google/go-containerregistry/pkg/v1
Signed-off-by: Andrei Aaron <andaaron@cisco.com>
* feat(repodb): refactor repodb update logic
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
* fix(repodb): minor fixes
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
* feat(repodb): move repodb logic inside meta directory under pkg
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
* feat(repodb): change factory class for repodb initialization with factory metrod
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
* feat(repodb): simplify repodb configuration
- repodb now shares config parameters with the cache
- config taken directly from storage config
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
* fix(authors): fix authors information to work properly with repodb
Ideally this commit would be squshed in the repodb commit
but as-is it is easier to cherry-pick on other branches
Signed-off-by: Andrei Aaron <andaaron@cisco.com>
* feat(repodb): dynamodb support for repodb
- clean-up repodb code + coverage improvements
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
* feat(dynamo): tables used by dynamo are created automatically if they don't exists
- if the table exists nothing happens
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
* test(repodb): coverage tests
- minor fix for CVEListForImage to fix the tests
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
* feat(repodb): add descriptor with media type
- to represent images and multi-arch images
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
* feat(repodb): support signatures on repo level
- added to follow the behavior of signing and signature verification tools
that work on a manifest level for each repo
- all images with different tags but the same manifest will be signed at once
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
* feat(repodb): old repodb version migration support
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
* feat(repodb): tests for coverage
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
* feat(repodb): WIP fixing tests
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
* feat(repodb): work on patchRepoDB tests
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
* fix(repodb): create dynamo tables only for linux amd
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
* fix(ci): fix a typo in ci-cd.yml
Signed-off-by: Andrei Aaron <aaaron@luxoft.com>
Signed-off-by: Andrei Aaron <andaaron@cisco.com>
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
Signed-off-by: Andrei Aaron <aaaron@luxoft.com>
Co-authored-by: Andrei Aaron <andaaron@cisco.com>
Co-authored-by: Andrei Aaron <aaaron@luxoft.com>
894 lines
22 KiB
Go
894 lines
22 KiB
Go
package bolt
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
"time"
|
|
|
|
godigest "github.com/opencontainers/go-digest"
|
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"github.com/pkg/errors"
|
|
"github.com/rs/zerolog"
|
|
bolt "go.etcd.io/bbolt"
|
|
|
|
zerr "zotregistry.io/zot/errors"
|
|
"zotregistry.io/zot/pkg/log"
|
|
"zotregistry.io/zot/pkg/meta/repodb"
|
|
"zotregistry.io/zot/pkg/meta/repodb/common"
|
|
"zotregistry.io/zot/pkg/meta/repodb/version"
|
|
localCtx "zotregistry.io/zot/pkg/requestcontext"
|
|
)
|
|
|
|
type DBParameters struct {
|
|
RootDir string
|
|
}
|
|
|
|
type DBWrapper struct {
|
|
DB *bolt.DB
|
|
Patches []func(DB *bolt.DB) error
|
|
Log log.Logger
|
|
}
|
|
|
|
func NewBoltDBWrapper(params DBParameters) (*DBWrapper, error) {
|
|
const perms = 0o600
|
|
|
|
boltDB, err := bolt.Open(path.Join(params.RootDir, "repo.db"), perms, &bolt.Options{Timeout: time.Second * 10})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = boltDB.Update(func(transaction *bolt.Tx) error {
|
|
versionBuck, err := transaction.CreateBucketIfNotExists([]byte(repodb.VersionBucket))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = versionBuck.Put([]byte(version.DBVersionKey), []byte(version.CurrentVersion))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = transaction.CreateBucketIfNotExists([]byte(repodb.ManifestDataBucket))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = transaction.CreateBucketIfNotExists([]byte(repodb.RepoMetadataBucket))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &DBWrapper{
|
|
DB: boltDB,
|
|
Patches: version.GetBoltDBPatches(),
|
|
Log: log.Logger{Logger: zerolog.New(os.Stdout)},
|
|
}, nil
|
|
}
|
|
|
|
func (bdw DBWrapper) SetManifestData(manifestDigest godigest.Digest, manifestData repodb.ManifestData) error {
|
|
err := bdw.DB.Update(func(tx *bolt.Tx) error {
|
|
buck := tx.Bucket([]byte(repodb.ManifestDataBucket))
|
|
|
|
mdBlob, err := json.Marshal(manifestData)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "repodb: error while calculating blob for manifest with digest %s", manifestDigest)
|
|
}
|
|
|
|
err = buck.Put([]byte(manifestDigest), mdBlob)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "repodb: error while setting manifest data with for digest %s", manifestDigest)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
func (bdw DBWrapper) GetManifestData(manifestDigest godigest.Digest) (repodb.ManifestData, error) {
|
|
var manifestData repodb.ManifestData
|
|
|
|
err := bdw.DB.View(func(tx *bolt.Tx) error {
|
|
buck := tx.Bucket([]byte(repodb.ManifestDataBucket))
|
|
|
|
mdBlob := buck.Get([]byte(manifestDigest))
|
|
|
|
if len(mdBlob) == 0 {
|
|
return zerr.ErrManifestDataNotFound
|
|
}
|
|
|
|
err := json.Unmarshal(mdBlob, &manifestData)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "repodb: error while unmashaling manifest meta for digest %s", manifestDigest)
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
return manifestData, err
|
|
}
|
|
|
|
func (bdw DBWrapper) SetManifestMeta(repo string, manifestDigest godigest.Digest, manifestMeta repodb.ManifestMetadata,
|
|
) error {
|
|
err := bdw.DB.Update(func(tx *bolt.Tx) error {
|
|
dataBuck := tx.Bucket([]byte(repodb.ManifestDataBucket))
|
|
repoBuck := tx.Bucket([]byte(repodb.RepoMetadataBucket))
|
|
|
|
repoMeta := repodb.RepoMetadata{
|
|
Name: repo,
|
|
Tags: map[string]repodb.Descriptor{},
|
|
Statistics: map[string]repodb.DescriptorStatistics{},
|
|
Signatures: map[string]repodb.ManifestSignatures{},
|
|
}
|
|
|
|
repoMetaBlob := repoBuck.Get([]byte(repo))
|
|
if len(repoMetaBlob) > 0 {
|
|
err := json.Unmarshal(repoMetaBlob, &repoMeta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
mdBlob, err := json.Marshal(repodb.ManifestData{
|
|
ManifestBlob: manifestMeta.ManifestBlob,
|
|
ConfigBlob: manifestMeta.ConfigBlob,
|
|
})
|
|
if err != nil {
|
|
return errors.Wrapf(err, "repodb: error while calculating blob for manifest with digest %s", manifestDigest)
|
|
}
|
|
|
|
err = dataBuck.Put([]byte(manifestDigest), mdBlob)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "repodb: error while setting manifest meta with for digest %s", manifestDigest)
|
|
}
|
|
|
|
updatedRepoMeta := common.UpdateManifestMeta(repoMeta, manifestDigest, manifestMeta)
|
|
|
|
updatedRepoMetaBlob, err := json.Marshal(updatedRepoMeta)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "repodb: error while calculating blob for updated repo meta '%s'", repo)
|
|
}
|
|
|
|
return repoBuck.Put([]byte(repo), updatedRepoMetaBlob)
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
func (bdw DBWrapper) GetManifestMeta(repo string, manifestDigest godigest.Digest) (repodb.ManifestMetadata, error) {
|
|
var manifestMetadata repodb.ManifestMetadata
|
|
|
|
err := bdw.DB.View(func(tx *bolt.Tx) error {
|
|
dataBuck := tx.Bucket([]byte(repodb.ManifestDataBucket))
|
|
repoBuck := tx.Bucket([]byte(repodb.RepoMetadataBucket))
|
|
|
|
mdBlob := dataBuck.Get([]byte(manifestDigest))
|
|
|
|
if len(mdBlob) == 0 {
|
|
return zerr.ErrManifestMetaNotFound
|
|
}
|
|
|
|
var manifestData repodb.ManifestData
|
|
|
|
err := json.Unmarshal(mdBlob, &manifestData)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "repodb: error while unmashaling manifest meta for digest %s", manifestDigest)
|
|
}
|
|
|
|
var repoMeta repodb.RepoMetadata
|
|
|
|
repoMetaBlob := repoBuck.Get([]byte(repo))
|
|
if len(repoMetaBlob) > 0 {
|
|
err = json.Unmarshal(repoMetaBlob, &repoMeta)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "repodb: error while unmashaling manifest meta for digest %s", manifestDigest)
|
|
}
|
|
}
|
|
|
|
manifestMetadata.ManifestBlob = manifestData.ManifestBlob
|
|
manifestMetadata.ConfigBlob = manifestData.ConfigBlob
|
|
manifestMetadata.DownloadCount = repoMeta.Statistics[manifestDigest.String()].DownloadCount
|
|
|
|
manifestMetadata.Signatures = repodb.ManifestSignatures{}
|
|
if repoMeta.Signatures[manifestDigest.String()] != nil {
|
|
manifestMetadata.Signatures = repoMeta.Signatures[manifestDigest.String()]
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
return manifestMetadata, err
|
|
}
|
|
|
|
func (bdw DBWrapper) SetRepoTag(repo string, tag string, manifestDigest godigest.Digest,
|
|
mediaType string,
|
|
) error {
|
|
if err := common.ValidateRepoTagInput(repo, tag, manifestDigest); err != nil {
|
|
return err
|
|
}
|
|
|
|
err := bdw.DB.Update(func(tx *bolt.Tx) error {
|
|
buck := tx.Bucket([]byte(repodb.RepoMetadataBucket))
|
|
|
|
repoMetaBlob := buck.Get([]byte(repo))
|
|
|
|
// object not found
|
|
if len(repoMetaBlob) == 0 {
|
|
// create a new object
|
|
repoMeta := repodb.RepoMetadata{
|
|
Name: repo,
|
|
Tags: map[string]repodb.Descriptor{
|
|
tag: {
|
|
Digest: manifestDigest.String(),
|
|
MediaType: mediaType,
|
|
},
|
|
},
|
|
Statistics: map[string]repodb.DescriptorStatistics{
|
|
manifestDigest.String(): {DownloadCount: 0},
|
|
},
|
|
Signatures: map[string]repodb.ManifestSignatures{
|
|
manifestDigest.String(): {},
|
|
},
|
|
}
|
|
|
|
repoMetaBlob, err := json.Marshal(repoMeta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return buck.Put([]byte(repo), repoMetaBlob)
|
|
}
|
|
|
|
// object found
|
|
var repoMeta repodb.RepoMetadata
|
|
|
|
err := json.Unmarshal(repoMetaBlob, &repoMeta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
repoMeta.Tags[tag] = repodb.Descriptor{
|
|
Digest: manifestDigest.String(),
|
|
MediaType: mediaType,
|
|
}
|
|
|
|
repoMetaBlob, err = json.Marshal(repoMeta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return buck.Put([]byte(repo), repoMetaBlob)
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
func (bdw DBWrapper) GetRepoMeta(repo string) (repodb.RepoMetadata, error) {
|
|
var repoMeta repodb.RepoMetadata
|
|
|
|
err := bdw.DB.Update(func(tx *bolt.Tx) error {
|
|
buck := tx.Bucket([]byte(repodb.RepoMetadataBucket))
|
|
|
|
repoMetaBlob := buck.Get([]byte(repo))
|
|
|
|
// object not found
|
|
if repoMetaBlob == nil {
|
|
return zerr.ErrRepoMetaNotFound
|
|
}
|
|
|
|
// object found
|
|
err := json.Unmarshal(repoMetaBlob, &repoMeta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
return repoMeta, err
|
|
}
|
|
|
|
func (bdw DBWrapper) DeleteRepoTag(repo string, tag string) error {
|
|
err := bdw.DB.Update(func(tx *bolt.Tx) error {
|
|
buck := tx.Bucket([]byte(repodb.RepoMetadataBucket))
|
|
|
|
repoMetaBlob := buck.Get([]byte(repo))
|
|
|
|
// object not found
|
|
if repoMetaBlob == nil {
|
|
return nil
|
|
}
|
|
|
|
// object found
|
|
var repoMeta repodb.RepoMetadata
|
|
|
|
err := json.Unmarshal(repoMetaBlob, &repoMeta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
delete(repoMeta.Tags, tag)
|
|
|
|
if len(repoMeta.Tags) == 0 {
|
|
return buck.Delete([]byte(repo))
|
|
}
|
|
|
|
repoMetaBlob, err = json.Marshal(repoMeta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return buck.Put([]byte(repo), repoMetaBlob)
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
func (bdw DBWrapper) IncrementRepoStars(repo string) error {
|
|
err := bdw.DB.Update(func(tx *bolt.Tx) error {
|
|
buck := tx.Bucket([]byte(repodb.RepoMetadataBucket))
|
|
|
|
repoMetaBlob := buck.Get([]byte(repo))
|
|
if repoMetaBlob == nil {
|
|
return zerr.ErrRepoMetaNotFound
|
|
}
|
|
|
|
var repoMeta repodb.RepoMetadata
|
|
|
|
err := json.Unmarshal(repoMetaBlob, &repoMeta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
repoMeta.Stars++
|
|
|
|
repoMetaBlob, err = json.Marshal(repoMeta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return buck.Put([]byte(repo), repoMetaBlob)
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
func (bdw DBWrapper) DecrementRepoStars(repo string) error {
|
|
err := bdw.DB.Update(func(tx *bolt.Tx) error {
|
|
buck := tx.Bucket([]byte(repodb.RepoMetadataBucket))
|
|
|
|
repoMetaBlob := buck.Get([]byte(repo))
|
|
if repoMetaBlob == nil {
|
|
return zerr.ErrRepoMetaNotFound
|
|
}
|
|
|
|
var repoMeta repodb.RepoMetadata
|
|
|
|
err := json.Unmarshal(repoMetaBlob, &repoMeta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if repoMeta.Stars > 0 {
|
|
repoMeta.Stars--
|
|
}
|
|
|
|
repoMetaBlob, err = json.Marshal(repoMeta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return buck.Put([]byte(repo), repoMetaBlob)
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
func (bdw DBWrapper) GetRepoStars(repo string) (int, error) {
|
|
stars := 0
|
|
|
|
err := bdw.DB.View(func(tx *bolt.Tx) error {
|
|
buck := tx.Bucket([]byte(repodb.RepoMetadataBucket))
|
|
|
|
buck.Get([]byte(repo))
|
|
repoMetaBlob := buck.Get([]byte(repo))
|
|
if repoMetaBlob == nil {
|
|
return zerr.ErrRepoMetaNotFound
|
|
}
|
|
|
|
var repoMeta repodb.RepoMetadata
|
|
|
|
err := json.Unmarshal(repoMetaBlob, &repoMeta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
stars = repoMeta.Stars
|
|
|
|
return nil
|
|
})
|
|
|
|
return stars, err
|
|
}
|
|
|
|
func (bdw DBWrapper) GetMultipleRepoMeta(ctx context.Context, filter func(repoMeta repodb.RepoMetadata) bool,
|
|
requestedPage repodb.PageInput,
|
|
) ([]repodb.RepoMetadata, error) {
|
|
var (
|
|
foundRepos = make([]repodb.RepoMetadata, 0)
|
|
pageFinder repodb.PageFinder
|
|
)
|
|
|
|
pageFinder, err := repodb.NewBaseRepoPageFinder(requestedPage.Limit, requestedPage.Offset, requestedPage.SortBy)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = bdw.DB.View(func(tx *bolt.Tx) error {
|
|
buck := tx.Bucket([]byte(repodb.RepoMetadataBucket))
|
|
|
|
cursor := buck.Cursor()
|
|
|
|
for repoName, repoMetaBlob := cursor.First(); repoName != nil; repoName, repoMetaBlob = cursor.Next() {
|
|
if ok, err := localCtx.RepoIsUserAvailable(ctx, string(repoName)); !ok || err != nil {
|
|
continue
|
|
}
|
|
|
|
repoMeta := repodb.RepoMetadata{}
|
|
|
|
err := json.Unmarshal(repoMetaBlob, &repoMeta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if filter(repoMeta) {
|
|
pageFinder.Add(repodb.DetailedRepoMeta{
|
|
RepoMeta: repoMeta,
|
|
})
|
|
}
|
|
}
|
|
|
|
foundRepos = pageFinder.Page()
|
|
|
|
return nil
|
|
})
|
|
|
|
return foundRepos, err
|
|
}
|
|
|
|
func (bdw DBWrapper) IncrementImageDownloads(repo string, reference string) error {
|
|
err := bdw.DB.Update(func(tx *bolt.Tx) error {
|
|
buck := tx.Bucket([]byte(repodb.RepoMetadataBucket))
|
|
|
|
repoMetaBlob := buck.Get([]byte(repo))
|
|
if repoMetaBlob == nil {
|
|
return zerr.ErrManifestMetaNotFound
|
|
}
|
|
|
|
var repoMeta repodb.RepoMetadata
|
|
|
|
err := json.Unmarshal(repoMetaBlob, &repoMeta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
manifestDigest := reference
|
|
|
|
if !common.ReferenceIsDigest(reference) {
|
|
// search digest for tag
|
|
descriptor, found := repoMeta.Tags[reference]
|
|
|
|
if !found {
|
|
return zerr.ErrManifestMetaNotFound
|
|
}
|
|
|
|
manifestDigest = descriptor.Digest
|
|
}
|
|
|
|
manifestStatistics := repoMeta.Statistics[manifestDigest]
|
|
manifestStatistics.DownloadCount++
|
|
repoMeta.Statistics[manifestDigest] = manifestStatistics
|
|
|
|
repoMetaBlob, err = json.Marshal(repoMeta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return buck.Put([]byte(repo), repoMetaBlob)
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
func (bdw DBWrapper) AddManifestSignature(repo string, signedManifestDigest godigest.Digest,
|
|
sygMeta repodb.SignatureMetadata,
|
|
) error {
|
|
err := bdw.DB.Update(func(tx *bolt.Tx) error {
|
|
buck := tx.Bucket([]byte(repodb.RepoMetadataBucket))
|
|
|
|
repoMetaBlob := buck.Get([]byte(repo))
|
|
if repoMetaBlob == nil {
|
|
return zerr.ErrManifestMetaNotFound
|
|
}
|
|
|
|
var repoMeta repodb.RepoMetadata
|
|
|
|
err := json.Unmarshal(repoMetaBlob, &repoMeta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var (
|
|
manifestSignatures repodb.ManifestSignatures
|
|
found bool
|
|
)
|
|
|
|
if manifestSignatures, found = repoMeta.Signatures[signedManifestDigest.String()]; !found {
|
|
manifestSignatures = repodb.ManifestSignatures{}
|
|
}
|
|
|
|
signatureSlice := manifestSignatures[sygMeta.SignatureType]
|
|
if !common.SignatureAlreadyExists(signatureSlice, sygMeta) {
|
|
if sygMeta.SignatureType == repodb.NotationType {
|
|
signatureSlice = append(signatureSlice, repodb.SignatureInfo{
|
|
SignatureManifestDigest: sygMeta.SignatureDigest,
|
|
LayersInfo: sygMeta.LayersInfo,
|
|
})
|
|
} else if sygMeta.SignatureType == repodb.CosignType {
|
|
signatureSlice = []repodb.SignatureInfo{{
|
|
SignatureManifestDigest: sygMeta.SignatureDigest,
|
|
LayersInfo: sygMeta.LayersInfo,
|
|
}}
|
|
}
|
|
}
|
|
|
|
manifestSignatures[sygMeta.SignatureType] = signatureSlice
|
|
|
|
repoMeta.Signatures[signedManifestDigest.String()] = manifestSignatures
|
|
|
|
repoMetaBlob, err = json.Marshal(repoMeta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return buck.Put([]byte(repo), repoMetaBlob)
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
func (bdw DBWrapper) DeleteSignature(repo string, signedManifestDigest godigest.Digest,
|
|
sigMeta repodb.SignatureMetadata,
|
|
) error {
|
|
err := bdw.DB.Update(func(tx *bolt.Tx) error {
|
|
buck := tx.Bucket([]byte(repodb.RepoMetadataBucket))
|
|
|
|
repoMetaBlob := buck.Get([]byte(repo))
|
|
if repoMetaBlob == nil {
|
|
return zerr.ErrManifestMetaNotFound
|
|
}
|
|
|
|
var repoMeta repodb.RepoMetadata
|
|
|
|
err := json.Unmarshal(repoMetaBlob, &repoMeta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
sigType := sigMeta.SignatureType
|
|
|
|
var (
|
|
manifestSignatures repodb.ManifestSignatures
|
|
found bool
|
|
)
|
|
|
|
if manifestSignatures, found = repoMeta.Signatures[signedManifestDigest.String()]; !found {
|
|
return zerr.ErrManifestMetaNotFound
|
|
}
|
|
|
|
signatureSlice := manifestSignatures[sigType]
|
|
|
|
newSignatureSlice := make([]repodb.SignatureInfo, 0, len(signatureSlice)-1)
|
|
|
|
for _, sigDigest := range signatureSlice {
|
|
if sigDigest.SignatureManifestDigest != sigMeta.SignatureDigest {
|
|
newSignatureSlice = append(newSignatureSlice, sigDigest)
|
|
}
|
|
}
|
|
|
|
manifestSignatures[sigType] = newSignatureSlice
|
|
|
|
repoMeta.Signatures[signedManifestDigest.String()] = manifestSignatures
|
|
|
|
repoMetaBlob, err = json.Marshal(repoMeta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return buck.Put([]byte(repo), repoMetaBlob)
|
|
})
|
|
|
|
return err
|
|
}
|
|
|
|
func (bdw DBWrapper) SearchRepos(ctx context.Context, searchText string, filter repodb.Filter,
|
|
requestedPage repodb.PageInput,
|
|
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, error) {
|
|
var (
|
|
foundRepos = make([]repodb.RepoMetadata, 0)
|
|
foundManifestMetadataMap = make(map[string]repodb.ManifestMetadata)
|
|
pageFinder repodb.PageFinder
|
|
)
|
|
|
|
pageFinder, err := repodb.NewBaseRepoPageFinder(requestedPage.Limit, requestedPage.Offset, requestedPage.SortBy)
|
|
if err != nil {
|
|
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, err
|
|
}
|
|
|
|
err = bdw.DB.View(func(tx *bolt.Tx) error {
|
|
var (
|
|
manifestMetadataMap = make(map[string]repodb.ManifestMetadata)
|
|
repoBuck = tx.Bucket([]byte(repodb.RepoMetadataBucket))
|
|
dataBuck = tx.Bucket([]byte(repodb.ManifestDataBucket))
|
|
)
|
|
|
|
cursor := repoBuck.Cursor()
|
|
|
|
for repoName, repoMetaBlob := cursor.First(); repoName != nil; repoName, repoMetaBlob = cursor.Next() {
|
|
if ok, err := localCtx.RepoIsUserAvailable(ctx, string(repoName)); !ok || err != nil {
|
|
continue
|
|
}
|
|
|
|
var repoMeta repodb.RepoMetadata
|
|
|
|
err := json.Unmarshal(repoMetaBlob, &repoMeta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if score := common.ScoreRepoName(searchText, string(repoName)); score != -1 {
|
|
var (
|
|
// specific values used for sorting that need to be calculated based on all manifests from the repo
|
|
repoDownloads = 0
|
|
repoLastUpdated time.Time
|
|
firstImageChecked = true
|
|
osSet = map[string]bool{}
|
|
archSet = map[string]bool{}
|
|
isSigned = false
|
|
)
|
|
|
|
for _, descriptor := range repoMeta.Tags {
|
|
var manifestMeta repodb.ManifestMetadata
|
|
|
|
manifestMeta, manifestDownloaded := manifestMetadataMap[descriptor.Digest]
|
|
|
|
if !manifestDownloaded {
|
|
manifestMetaBlob := dataBuck.Get([]byte(descriptor.Digest))
|
|
if manifestMetaBlob == nil {
|
|
return zerr.ErrManifestMetaNotFound
|
|
}
|
|
|
|
err := json.Unmarshal(manifestMetaBlob, &manifestMeta)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "repodb: error while unmarshaling manifest metadata for digest %s", descriptor.Digest)
|
|
}
|
|
}
|
|
|
|
// get fields related to filtering
|
|
var configContent ispec.Image
|
|
|
|
err = json.Unmarshal(manifestMeta.ConfigBlob, &configContent)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "repodb: error while unmarshaling config content for digest %s", descriptor.Digest)
|
|
}
|
|
|
|
osSet[configContent.OS] = true
|
|
archSet[configContent.Architecture] = true
|
|
|
|
// get fields related to sorting
|
|
repoDownloads += repoMeta.Statistics[descriptor.Digest].DownloadCount
|
|
|
|
imageLastUpdated := common.GetImageLastUpdatedTimestamp(configContent)
|
|
|
|
if firstImageChecked || repoLastUpdated.Before(imageLastUpdated) {
|
|
repoLastUpdated = imageLastUpdated
|
|
firstImageChecked = false
|
|
|
|
isSigned = common.CheckIsSigned(repoMeta.Signatures[descriptor.Digest])
|
|
}
|
|
|
|
manifestMetadataMap[descriptor.Digest] = manifestMeta
|
|
}
|
|
|
|
repoFilterData := repodb.FilterData{
|
|
OsList: common.GetMapKeys(osSet),
|
|
ArchList: common.GetMapKeys(archSet),
|
|
IsSigned: isSigned,
|
|
}
|
|
|
|
if !common.AcceptedByFilter(filter, repoFilterData) {
|
|
continue
|
|
}
|
|
|
|
pageFinder.Add(repodb.DetailedRepoMeta{
|
|
RepoMeta: repoMeta,
|
|
Score: score,
|
|
Downloads: repoDownloads,
|
|
UpdateTime: repoLastUpdated,
|
|
})
|
|
}
|
|
}
|
|
|
|
foundRepos = pageFinder.Page()
|
|
|
|
// keep just the manifestMeta we need
|
|
for _, repoMeta := range foundRepos {
|
|
for _, manifestDigest := range repoMeta.Tags {
|
|
foundManifestMetadataMap[manifestDigest.Digest] = manifestMetadataMap[manifestDigest.Digest]
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
return foundRepos, foundManifestMetadataMap, err
|
|
}
|
|
|
|
func (bdw DBWrapper) SearchTags(ctx context.Context, searchText string, filter repodb.Filter,
|
|
requestedPage repodb.PageInput,
|
|
) ([]repodb.RepoMetadata, map[string]repodb.ManifestMetadata, error) {
|
|
var (
|
|
foundRepos = make([]repodb.RepoMetadata, 0)
|
|
foundManifestMetadataMap = make(map[string]repodb.ManifestMetadata)
|
|
|
|
pageFinder repodb.PageFinder
|
|
)
|
|
|
|
pageFinder, err := repodb.NewBaseImagePageFinder(requestedPage.Limit, requestedPage.Offset, requestedPage.SortBy)
|
|
if err != nil {
|
|
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{}, err
|
|
}
|
|
|
|
searchedRepo, searchedTag, err := common.GetRepoTag(searchText)
|
|
if err != nil {
|
|
return []repodb.RepoMetadata{}, map[string]repodb.ManifestMetadata{},
|
|
errors.Wrap(err, "repodb: error while parsing search text, invalid format")
|
|
}
|
|
|
|
err = bdw.DB.View(func(tx *bolt.Tx) error {
|
|
var (
|
|
manifestMetadataMap = make(map[string]repodb.ManifestMetadata)
|
|
repoBuck = tx.Bucket([]byte(repodb.RepoMetadataBucket))
|
|
dataBuck = tx.Bucket([]byte(repodb.ManifestDataBucket))
|
|
cursor = repoBuck.Cursor()
|
|
)
|
|
|
|
repoName, repoMetaBlob := cursor.Seek([]byte(searchedRepo))
|
|
|
|
for ; repoName != nil; repoName, repoMetaBlob = cursor.Next() {
|
|
if ok, err := localCtx.RepoIsUserAvailable(ctx, string(repoName)); !ok || err != nil {
|
|
continue
|
|
}
|
|
|
|
repoMeta := repodb.RepoMetadata{}
|
|
|
|
err := json.Unmarshal(repoMetaBlob, &repoMeta)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if string(repoName) == searchedRepo {
|
|
matchedTags := make(map[string]repodb.Descriptor)
|
|
// take all manifestMetas
|
|
for tag, descriptor := range repoMeta.Tags {
|
|
if !strings.HasPrefix(tag, searchedTag) {
|
|
continue
|
|
}
|
|
|
|
matchedTags[tag] = descriptor
|
|
|
|
// in case tags reference the same manifest we don't download from DB multiple times
|
|
if manifestMeta, manifestExists := manifestMetadataMap[descriptor.Digest]; manifestExists {
|
|
manifestMetadataMap[descriptor.Digest] = manifestMeta
|
|
|
|
continue
|
|
}
|
|
|
|
manifestMetaBlob := dataBuck.Get([]byte(descriptor.Digest))
|
|
if manifestMetaBlob == nil {
|
|
return zerr.ErrManifestMetaNotFound
|
|
}
|
|
|
|
var manifestMeta repodb.ManifestMetadata
|
|
|
|
err := json.Unmarshal(manifestMetaBlob, &manifestMeta)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "repodb: error while unmashaling manifest metadata for digest %s", descriptor.Digest)
|
|
}
|
|
|
|
var configContent ispec.Image
|
|
|
|
err = json.Unmarshal(manifestMeta.ConfigBlob, &configContent)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "repodb: error while unmashaling manifest metadata for digest %s", descriptor.Digest)
|
|
}
|
|
|
|
imageFilterData := repodb.FilterData{
|
|
OsList: []string{configContent.OS},
|
|
ArchList: []string{configContent.Architecture},
|
|
IsSigned: false,
|
|
}
|
|
|
|
if !common.AcceptedByFilter(filter, imageFilterData) {
|
|
delete(matchedTags, tag)
|
|
delete(manifestMetadataMap, descriptor.Digest)
|
|
|
|
continue
|
|
}
|
|
|
|
manifestMetadataMap[descriptor.Digest] = manifestMeta
|
|
}
|
|
|
|
repoMeta.Tags = matchedTags
|
|
|
|
pageFinder.Add(repodb.DetailedRepoMeta{
|
|
RepoMeta: repoMeta,
|
|
})
|
|
}
|
|
}
|
|
|
|
foundRepos = pageFinder.Page()
|
|
|
|
// keep just the manifestMeta we need
|
|
for _, repoMeta := range foundRepos {
|
|
for _, descriptor := range repoMeta.Tags {
|
|
foundManifestMetadataMap[descriptor.Digest] = manifestMetadataMap[descriptor.Digest]
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
return foundRepos, foundManifestMetadataMap, err
|
|
}
|
|
|
|
func (bdw *DBWrapper) PatchDB() error {
|
|
var DBVersion string
|
|
|
|
err := bdw.DB.View(func(tx *bolt.Tx) error {
|
|
versionBuck := tx.Bucket([]byte(repodb.VersionBucket))
|
|
DBVersion = string(versionBuck.Get([]byte(version.DBVersionKey)))
|
|
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return errors.Wrapf(err, "patching the database failed, can't read db version")
|
|
}
|
|
|
|
if version.GetVersionIndex(DBVersion) == -1 {
|
|
return errors.New("DB has broken format, no version found")
|
|
}
|
|
|
|
for patchIndex, patch := range bdw.Patches {
|
|
if patchIndex < version.GetVersionIndex(DBVersion) {
|
|
continue
|
|
}
|
|
|
|
err := patch(bdw.DB)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|