mirror of
https://github.com/project-zot/zot.git
synced 2024-12-30 22:34:13 -05:00
18235ca254
* fix(oras)!: remove ORAS artifact references support ORAS artifacts/references predated OCI dist-spec 1.1.0 which now has the same functionality and likely to see wider adoption. Signed-off-by: Ramkumar Chinchani <rchincha@cisco.com> * test: update to released official images So that they are unlikely to be deleted. *-rc images may be cleaned up over time. Signed-off-by: Ramkumar Chinchani <rchincha@cisco.com> --------- Signed-off-by: Ramkumar Chinchani <rchincha@cisco.com>
234 lines
6.7 KiB
Go
234 lines
6.7 KiB
Go
//go:build sync
|
|
// +build sync
|
|
|
|
package references
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
godigest "github.com/opencontainers/go-digest"
|
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"github.com/sigstore/cosign/v2/pkg/oci/static"
|
|
|
|
zerr "zotregistry.dev/zot/errors"
|
|
"zotregistry.dev/zot/pkg/common"
|
|
"zotregistry.dev/zot/pkg/extensions/sync/constants"
|
|
"zotregistry.dev/zot/pkg/extensions/sync/features"
|
|
client "zotregistry.dev/zot/pkg/extensions/sync/httpclient"
|
|
"zotregistry.dev/zot/pkg/log"
|
|
mTypes "zotregistry.dev/zot/pkg/meta/types"
|
|
"zotregistry.dev/zot/pkg/storage"
|
|
storageTypes "zotregistry.dev/zot/pkg/storage/types"
|
|
)
|
|
|
|
type Reference interface {
|
|
// Returns name of reference (OCIReference/CosignReference)
|
|
Name() string
|
|
// Returns whether or not image is signed
|
|
IsSigned(ctx context.Context, upstreamRepo, subjectDigestStr string) bool
|
|
// Sync recursively all references for a subject digest (can be image/artifacts/signatures)
|
|
SyncReferences(ctx context.Context, localRepo, upstreamRepo, subjectDigestStr string) ([]godigest.Digest, error)
|
|
}
|
|
|
|
type References struct {
|
|
referenceList []Reference
|
|
features *features.Map
|
|
log log.Logger
|
|
}
|
|
|
|
func NewReferences(httpClient *client.Client, storeController storage.StoreController,
|
|
metaDB mTypes.MetaDB, log log.Logger,
|
|
) References {
|
|
refs := References{features: features.New(), log: log}
|
|
|
|
refs.referenceList = append(refs.referenceList, NewCosignReference(httpClient, storeController, metaDB, log))
|
|
refs.referenceList = append(refs.referenceList, NewTagReferences(httpClient, storeController, metaDB, log))
|
|
refs.referenceList = append(refs.referenceList, NewOciReferences(httpClient, storeController, metaDB, log))
|
|
|
|
return refs
|
|
}
|
|
|
|
func (refs References) IsSigned(ctx context.Context, upstreamRepo, subjectDigestStr string) bool {
|
|
for _, ref := range refs.referenceList {
|
|
ok := ref.IsSigned(ctx, upstreamRepo, subjectDigestStr)
|
|
if ok {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (refs References) SyncAll(ctx context.Context, localRepo, upstreamRepo, subjectDigestStr string) error {
|
|
seen := &[]godigest.Digest{}
|
|
|
|
return refs.syncAll(ctx, localRepo, upstreamRepo, subjectDigestStr, seen)
|
|
}
|
|
|
|
func (refs References) syncAll(ctx context.Context, localRepo, upstreamRepo,
|
|
subjectDigestStr string, seen *[]godigest.Digest,
|
|
) error {
|
|
var err error
|
|
|
|
var syncedRefsDigests []godigest.Digest
|
|
|
|
// mark subject digest as seen as soon as it comes in
|
|
*seen = append(*seen, godigest.Digest(subjectDigestStr))
|
|
|
|
// for each reference type(cosign/oci reference)
|
|
for _, ref := range refs.referenceList {
|
|
supported, ok := refs.features.Get(ref.Name(), upstreamRepo)
|
|
if !supported && ok {
|
|
continue
|
|
}
|
|
|
|
syncedRefsDigests, err = ref.SyncReferences(ctx, localRepo, upstreamRepo, subjectDigestStr)
|
|
if err != nil {
|
|
// for all referrers we can stop querying same repo (for ten minutes) if the errors are different than 404
|
|
if !errors.Is(err, zerr.ErrSyncReferrerNotFound) {
|
|
refs.features.Set(ref.Name(), upstreamRepo, false)
|
|
}
|
|
|
|
// in the case of oci referrers, it will return 404 only if the repo is not found or refferers API is not supported
|
|
// no need to continue to make requests to the same repo
|
|
if ref.Name() == constants.OCI && errors.Is(err, zerr.ErrSyncReferrerNotFound) {
|
|
refs.features.Set(ref.Name(), upstreamRepo, false)
|
|
}
|
|
|
|
refs.log.Debug().Err(err).
|
|
Str("reference type", ref.Name()).
|
|
Str("image", fmt.Sprintf("%s:%s", upstreamRepo, subjectDigestStr)).
|
|
Msg("couldn't sync image referrer")
|
|
} else {
|
|
refs.features.Set(ref.Name(), upstreamRepo, true)
|
|
}
|
|
|
|
// for each synced references
|
|
for _, refDigest := range syncedRefsDigests {
|
|
if !common.Contains(*seen, refDigest) {
|
|
// sync all references pointing to this one
|
|
err = refs.syncAll(ctx, localRepo, upstreamRepo, refDigest.String(), seen)
|
|
}
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (refs References) SyncReference(ctx context.Context, localRepo, upstreamRepo,
|
|
subjectDigestStr, referenceType string,
|
|
) error {
|
|
var err error
|
|
|
|
var syncedRefsDigests []godigest.Digest
|
|
|
|
for _, ref := range refs.referenceList {
|
|
if ref.Name() == referenceType {
|
|
syncedRefsDigests, err = ref.SyncReferences(ctx, localRepo, upstreamRepo, subjectDigestStr)
|
|
if err != nil {
|
|
refs.log.Debug().Err(err).
|
|
Str("reference type", ref.Name()).
|
|
Str("image", fmt.Sprintf("%s:%s", upstreamRepo, subjectDigestStr)).
|
|
Msg("couldn't sync image referrer")
|
|
|
|
return err
|
|
}
|
|
|
|
for _, refDigest := range syncedRefsDigests {
|
|
err = refs.SyncAll(ctx, localRepo, upstreamRepo, refDigest.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func syncBlob(ctx context.Context, client *client.Client, imageStore storageTypes.ImageStore,
|
|
localRepo, remoteRepo string, digest godigest.Digest, log log.Logger,
|
|
) error {
|
|
var resultPtr interface{}
|
|
|
|
body, _, statusCode, err := client.MakeGetRequest(ctx, resultPtr, "", "v2", remoteRepo, "blobs", digest.String())
|
|
if err != nil {
|
|
if statusCode != http.StatusOK {
|
|
log.Info().Str("repo", remoteRepo).Str("digest", digest.String()).Msg("couldn't get remote blob")
|
|
|
|
return err
|
|
}
|
|
}
|
|
|
|
_, _, err = imageStore.FullBlobUpload(localRepo, bytes.NewBuffer(body), digest)
|
|
if err != nil {
|
|
log.Error().Str("errorType", common.TypeOf(err)).Str("digest", digest.String()).Str("repo", localRepo).
|
|
Err(err).Msg("couldn't upload blob")
|
|
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func manifestsEqual(manifest1, manifest2 ispec.Manifest) bool {
|
|
if manifest1.Config.Digest == manifest2.Config.Digest &&
|
|
manifest1.Config.MediaType == manifest2.Config.MediaType &&
|
|
manifest1.Config.Size == manifest2.Config.Size {
|
|
if descriptorsEqual(manifest1.Layers, manifest2.Layers) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func descriptorsEqual(desc1, desc2 []ispec.Descriptor) bool {
|
|
if len(desc1) != len(desc2) {
|
|
return false
|
|
}
|
|
|
|
for id, desc := range desc1 {
|
|
if !descriptorEqual(desc, desc2[id]) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func descriptorEqual(desc1, desc2 ispec.Descriptor) bool {
|
|
if desc1.Size == desc2.Size &&
|
|
desc1.Digest == desc2.Digest &&
|
|
desc1.MediaType == desc2.MediaType &&
|
|
desc1.Annotations[static.SignatureAnnotationKey] == desc2.Annotations[static.SignatureAnnotationKey] {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func getNotationManifestsFromOCIRefs(ociRefs ispec.Index) []ispec.Descriptor {
|
|
notaryManifests := []ispec.Descriptor{}
|
|
|
|
for _, ref := range ociRefs.Manifests {
|
|
if ref.ArtifactType == common.ArtifactTypeNotation {
|
|
notaryManifests = append(notaryManifests, ref)
|
|
}
|
|
}
|
|
|
|
return notaryManifests
|
|
}
|
|
|
|
func getCosignManifestsFromOCIRefs(ociRefs ispec.Index) []ispec.Descriptor {
|
|
cosignManifests := []ispec.Descriptor{}
|
|
|
|
for _, ref := range ociRefs.Manifests {
|
|
if ref.ArtifactType == common.ArtifactTypeCosign {
|
|
cosignManifests = append(cosignManifests, ref)
|
|
}
|
|
}
|
|
|
|
return cosignManifests
|
|
}
|