2023-01-09 15:37:44 -05:00
|
|
|
package repodb
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
|
|
|
|
godigest "github.com/opencontainers/go-digest"
|
|
|
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
|
|
|
|
|
|
zerr "zotregistry.io/zot/errors"
|
|
|
|
"zotregistry.io/zot/pkg/log"
|
|
|
|
"zotregistry.io/zot/pkg/storage"
|
|
|
|
)
|
|
|
|
|
2023-03-10 13:37:29 -05:00
|
|
|
// ParseStorage will sync all repos found in the rootdirectory of the oci layout that zot was deployed on with the
|
|
|
|
// ParseStorage database.
|
|
|
|
func ParseStorage(repoDB RepoDB, storeController storage.StoreController, log log.Logger) error {
|
2023-01-09 15:37:44 -05:00
|
|
|
allRepos, err := getAllRepos(storeController)
|
|
|
|
if err != nil {
|
|
|
|
rootDir := storeController.DefaultStore.RootDir()
|
2023-03-10 13:37:29 -05:00
|
|
|
log.Error().Err(err).Msgf("load-local-layout: failed to get all repo names present under %s", rootDir)
|
2023-01-09 15:37:44 -05:00
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, repo := range allRepos {
|
2023-03-10 13:37:29 -05:00
|
|
|
err := ParseRepo(repo, repoDB, storeController, log)
|
2023-01-09 15:37:44 -05:00
|
|
|
if err != nil {
|
2023-03-10 13:37:29 -05:00
|
|
|
log.Error().Err(err).Msgf("load-local-layout: failed to sync repo %s", repo)
|
2023-01-09 15:37:44 -05:00
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-03-10 13:37:29 -05:00
|
|
|
// ParseRepo reads the contents of a repo and syncs all images and signatures found.
|
|
|
|
func ParseRepo(repo string, repoDB RepoDB, storeController storage.StoreController, log log.Logger) error {
|
2023-01-09 15:37:44 -05:00
|
|
|
imageStore := storeController.GetImageStore(repo)
|
|
|
|
|
|
|
|
indexBlob, err := imageStore.GetIndexContent(repo)
|
|
|
|
if err != nil {
|
2023-03-10 13:37:29 -05:00
|
|
|
log.Error().Err(err).Msgf("load-repo: failed to read index.json for repo %s", repo)
|
2023-01-09 15:37:44 -05:00
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
var indexContent ispec.Index
|
|
|
|
|
|
|
|
err = json.Unmarshal(indexBlob, &indexContent)
|
|
|
|
if err != nil {
|
2023-03-10 13:37:29 -05:00
|
|
|
log.Error().Err(err).Msgf("load-repo: failed to unmarshal index.json for repo %s", repo)
|
2023-01-09 15:37:44 -05:00
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = resetRepoMetaTags(repo, repoDB, log)
|
|
|
|
if err != nil && !errors.Is(err, zerr.ErrRepoMetaNotFound) {
|
2023-03-10 13:37:29 -05:00
|
|
|
log.Error().Err(err).Msgf("load-repo: failed to reset tag field in RepoMetadata for repo %s", repo)
|
2023-01-09 15:37:44 -05:00
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
type foundSignatureData struct {
|
|
|
|
repo string
|
|
|
|
tag string
|
|
|
|
signatureType string
|
|
|
|
signedManifestDigest string
|
|
|
|
signatureDigest string
|
|
|
|
}
|
|
|
|
|
|
|
|
var signaturesFound []foundSignatureData
|
|
|
|
|
|
|
|
for _, manifest := range indexContent.Manifests {
|
|
|
|
tag, hasTag := manifest.Annotations[ispec.AnnotationRefName]
|
|
|
|
|
|
|
|
manifestMetaIsPresent, err := isManifestMetaPresent(repo, manifest, repoDB)
|
|
|
|
if err != nil {
|
2023-03-10 13:37:29 -05:00
|
|
|
log.Error().Err(err).Msgf("load-repo: error checking manifestMeta in RepoDB")
|
2023-01-09 15:37:44 -05:00
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
if manifestMetaIsPresent && hasTag {
|
2023-03-09 13:41:48 -05:00
|
|
|
err = repoDB.SetRepoReference(repo, tag, manifest.Digest, manifest.MediaType)
|
2023-01-09 15:37:44 -05:00
|
|
|
if err != nil {
|
2023-03-10 13:37:29 -05:00
|
|
|
log.Error().Err(err).Msgf("load-repo: failed to set repo tag for %s:%s", repo, tag)
|
2023-01-09 15:37:44 -05:00
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
manifestBlob, digest, _, err := imageStore.GetImageManifest(repo, manifest.Digest.String())
|
|
|
|
if err != nil {
|
2023-03-10 13:37:29 -05:00
|
|
|
log.Error().Err(err).Msgf("load-repo: failed to set repo tag for %s:%s", repo, tag)
|
2023-01-09 15:37:44 -05:00
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
isSignature, signatureType, signedManifestDigest, err := storage.CheckIsImageSignature(repo,
|
|
|
|
manifestBlob, tag, storeController)
|
|
|
|
if err != nil {
|
|
|
|
if errors.Is(err, zerr.ErrOrphanSignature) {
|
|
|
|
continue
|
|
|
|
} else {
|
2023-03-10 13:37:29 -05:00
|
|
|
log.Error().Err(err).Msgf("load-repo: failed checking if image is signature for %s:%s", repo, tag)
|
2023-01-09 15:37:44 -05:00
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if isSignature {
|
|
|
|
// We'll ignore signatures now because the order in which the signed image and signature are added into
|
|
|
|
// the DB matters. First we add the normal images then the signatures
|
|
|
|
signaturesFound = append(signaturesFound, foundSignatureData{
|
|
|
|
repo: repo,
|
|
|
|
tag: tag,
|
|
|
|
signatureType: signatureType,
|
|
|
|
signedManifestDigest: signedManifestDigest.String(),
|
|
|
|
signatureDigest: digest.String(),
|
|
|
|
})
|
|
|
|
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
reference := tag
|
2023-01-09 15:37:44 -05:00
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
if tag == "" {
|
|
|
|
reference = manifest.Digest.String()
|
2023-01-09 15:37:44 -05:00
|
|
|
}
|
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
err = SetMetadataFromInput(repo, reference, manifest.MediaType, manifest.Digest, manifestBlob,
|
2023-03-09 13:41:48 -05:00
|
|
|
imageStore, repoDB, log)
|
2023-01-09 15:37:44 -05:00
|
|
|
if err != nil {
|
2023-03-10 13:37:29 -05:00
|
|
|
log.Error().Err(err).Msgf("load-repo: failed to set metadata for %s:%s", repo, tag)
|
2023-01-09 15:37:44 -05:00
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// manage the signatures found
|
|
|
|
for _, sigData := range signaturesFound {
|
2023-03-10 13:37:29 -05:00
|
|
|
err := repoDB.AddManifestSignature(repo, godigest.Digest(sigData.signedManifestDigest),
|
|
|
|
SignatureMetadata{
|
|
|
|
SignatureType: sigData.signatureType,
|
|
|
|
SignatureDigest: sigData.signatureDigest,
|
|
|
|
})
|
2023-01-09 15:37:44 -05:00
|
|
|
if err != nil {
|
2023-03-10 13:37:29 -05:00
|
|
|
log.Error().Err(err).Msgf("load-repo: failed set signature meta for signed image %s:%s manifest digest %s ",
|
2023-01-09 15:37:44 -05:00
|
|
|
sigData.repo, sigData.tag, sigData.signedManifestDigest)
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// resetRepoMetaTags will delete all tags from a repometadata.
|
|
|
|
func resetRepoMetaTags(repo string, repoDB RepoDB, log log.Logger) error {
|
|
|
|
repoMeta, err := repoDB.GetRepoMeta(repo)
|
|
|
|
if err != nil && !errors.Is(err, zerr.ErrRepoMetaNotFound) {
|
2023-03-10 13:37:29 -05:00
|
|
|
log.Error().Err(err).Msgf("load-repo: failed to get RepoMeta for repo %s", repo)
|
2023-01-09 15:37:44 -05:00
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if errors.Is(err, zerr.ErrRepoMetaNotFound) {
|
2023-03-10 13:37:29 -05:00
|
|
|
log.Info().Msgf("load-repo: RepoMeta not found for repo %s, new RepoMeta will be created", repo)
|
2023-01-09 15:37:44 -05:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
for tag := range repoMeta.Tags {
|
|
|
|
// We should have a way to delete all tags at once
|
|
|
|
err := repoDB.DeleteRepoTag(repo, tag)
|
|
|
|
if err != nil {
|
2023-03-10 13:37:29 -05:00
|
|
|
log.Error().Err(err).Msgf("load-repo: failed to delete tag %s from RepoMeta for repo %s", tag, repo)
|
2023-01-09 15:37:44 -05:00
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func getAllRepos(storeController storage.StoreController) ([]string, error) {
|
|
|
|
allRepos, err := storeController.DefaultStore.GetRepositories()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if storeController.SubStore != nil {
|
|
|
|
for _, store := range storeController.SubStore {
|
|
|
|
substoreRepos, err := store.GetRepositories()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
allRepos = append(allRepos, substoreRepos...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return allRepos, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// isManifestMetaPresent checks if the manifest with a certain digest is present in a certain repo.
|
|
|
|
func isManifestMetaPresent(repo string, manifest ispec.Descriptor, repoDB RepoDB) (bool, error) {
|
|
|
|
_, err := repoDB.GetManifestMeta(repo, manifest.Digest)
|
|
|
|
if err != nil && !errors.Is(err, zerr.ErrManifestMetaNotFound) {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if errors.Is(err, zerr.ErrManifestMetaNotFound) {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewManifestMeta takes raw data about an image and createa a new ManifestMetadate object.
|
2023-03-10 13:37:29 -05:00
|
|
|
func NewManifestData(repoName string, manifestBlob []byte, imageStore storage.ImageStore,
|
2023-01-09 15:37:44 -05:00
|
|
|
) (ManifestData, error) {
|
|
|
|
var (
|
|
|
|
manifestContent ispec.Manifest
|
|
|
|
configContent ispec.Image
|
|
|
|
manifestData ManifestData
|
|
|
|
)
|
|
|
|
|
|
|
|
err := json.Unmarshal(manifestBlob, &manifestContent)
|
|
|
|
if err != nil {
|
|
|
|
return ManifestData{}, err
|
|
|
|
}
|
|
|
|
|
2023-03-10 13:37:29 -05:00
|
|
|
configBlob, err := imageStore.GetBlobContent(repoName, manifestContent.Config.Digest)
|
2023-01-09 15:37:44 -05:00
|
|
|
if err != nil {
|
|
|
|
return ManifestData{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = json.Unmarshal(configBlob, &configContent)
|
|
|
|
if err != nil {
|
|
|
|
return ManifestData{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
manifestData.ManifestBlob = manifestBlob
|
|
|
|
manifestData.ConfigBlob = configBlob
|
|
|
|
|
|
|
|
return manifestData, nil
|
|
|
|
}
|
2023-02-27 14:23:18 -05:00
|
|
|
|
2023-03-10 13:37:29 -05:00
|
|
|
func NewIndexData(repoName string, indexBlob []byte, imageStore storage.ImageStore,
|
2023-02-27 14:23:18 -05:00
|
|
|
) IndexData {
|
|
|
|
indexData := IndexData{}
|
|
|
|
|
|
|
|
indexData.IndexBlob = indexBlob
|
|
|
|
|
|
|
|
return indexData
|
|
|
|
}
|
|
|
|
|
2023-03-10 13:37:29 -05:00
|
|
|
func NewArtifactData(repo string, descriptorBlob []byte, imageStore storage.ImageStore,
|
|
|
|
) ArtifactData {
|
|
|
|
return ArtifactData{
|
|
|
|
ManifestBlob: descriptorBlob,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
// SetMetadataFromInput tries to set manifest metadata and update repo metadata by adding the current tag
|
|
|
|
// (in case the reference is a tag). The function expects image manifests and indexes (multi arch images).
|
|
|
|
func SetMetadataFromInput(repo, reference, mediaType string, digest godigest.Digest, descriptorBlob []byte,
|
2023-03-09 13:41:48 -05:00
|
|
|
imageStore storage.ImageStore, repoDB RepoDB, log log.Logger,
|
2023-02-27 14:23:18 -05:00
|
|
|
) error {
|
|
|
|
switch mediaType {
|
|
|
|
case ispec.MediaTypeImageManifest:
|
2023-03-09 13:41:48 -05:00
|
|
|
imageData, err := NewManifestData(repo, descriptorBlob, imageStore)
|
2023-02-27 14:23:18 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = repoDB.SetManifestData(digest, imageData)
|
|
|
|
if err != nil {
|
|
|
|
log.Error().Err(err).Msg("repodb: error while putting manifest meta")
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
case ispec.MediaTypeImageIndex:
|
2023-03-10 13:37:29 -05:00
|
|
|
indexData := NewIndexData(repo, descriptorBlob, imageStore)
|
2023-02-27 14:23:18 -05:00
|
|
|
|
|
|
|
err := repoDB.SetIndexData(digest, indexData)
|
|
|
|
if err != nil {
|
|
|
|
log.Error().Err(err).Msg("repodb: error while putting index data")
|
|
|
|
|
2023-03-10 13:37:29 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
case ispec.MediaTypeArtifactManifest:
|
|
|
|
artifactData := NewArtifactData(repo, descriptorBlob, imageStore)
|
|
|
|
|
|
|
|
err := repoDB.SetArtifactData(digest, artifactData)
|
|
|
|
if err != nil {
|
|
|
|
log.Error().Err(err).Msg("repodb: error while putting artifact data")
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if refferredDigest, hasSubject := GetReferredSubject(descriptorBlob); hasSubject {
|
|
|
|
err := repoDB.SetReferrer(repo, refferredDigest, Descriptor{
|
|
|
|
Digest: digest.String(),
|
|
|
|
MediaType: mediaType,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
log.Error().Err(err).Msg("repodb: error while settingg referrer")
|
|
|
|
|
2023-02-27 14:23:18 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-09 13:41:48 -05:00
|
|
|
err := repoDB.SetRepoReference(repo, reference, digest, mediaType)
|
2023-02-27 14:23:18 -05:00
|
|
|
if err != nil {
|
|
|
|
log.Error().Err(err).Msg("repodb: error while putting repo meta")
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2023-03-10 13:37:29 -05:00
|
|
|
|
|
|
|
func GetReferredSubject(descriptorBlob []byte) (godigest.Digest, bool) {
|
|
|
|
var manifest ispec.Manifest
|
|
|
|
|
|
|
|
err := json.Unmarshal(descriptorBlob, &manifest)
|
|
|
|
if err != nil {
|
|
|
|
return "", false
|
|
|
|
}
|
|
|
|
|
|
|
|
if manifest.Subject == nil || manifest.Subject.Digest.String() == "" {
|
|
|
|
return "", false
|
|
|
|
}
|
|
|
|
|
|
|
|
return manifest.Subject.Digest, true
|
|
|
|
}
|