zotregistry.io/zot@v1.4.4-0.20231124084042-02a8ed785457/pkg/extensions/sync/references/oras.go (about)

     1  //go:build sync
     2  // +build sync
     3  
     4  package references
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"net/http"
    11  
    12  	godigest "github.com/opencontainers/go-digest"
    13  	oras "github.com/oras-project/artifacts-spec/specs-go/v1"
    14  
    15  	zerr "zotregistry.io/zot/errors"
    16  	apiConstants "zotregistry.io/zot/pkg/api/constants"
    17  	"zotregistry.io/zot/pkg/common"
    18  	"zotregistry.io/zot/pkg/extensions/sync/constants"
    19  	client "zotregistry.io/zot/pkg/extensions/sync/httpclient"
    20  	"zotregistry.io/zot/pkg/log"
    21  	"zotregistry.io/zot/pkg/meta"
    22  	mTypes "zotregistry.io/zot/pkg/meta/types"
    23  	"zotregistry.io/zot/pkg/storage"
    24  )
    25  
    26  type ReferenceList struct {
    27  	References []oras.Descriptor `json:"references"`
    28  }
    29  
    30  type ORASReferences struct {
    31  	client          *client.Client
    32  	storeController storage.StoreController
    33  	metaDB          mTypes.MetaDB
    34  	log             log.Logger
    35  }
    36  
    37  func NewORASReferences(httpClient *client.Client, storeController storage.StoreController,
    38  	metaDB mTypes.MetaDB, log log.Logger,
    39  ) ORASReferences {
    40  	return ORASReferences{
    41  		client:          httpClient,
    42  		storeController: storeController,
    43  		metaDB:          metaDB,
    44  		log:             log,
    45  	}
    46  }
    47  
    48  func (ref ORASReferences) Name() string {
    49  	return constants.Oras
    50  }
    51  
    52  func (ref ORASReferences) IsSigned(ctx context.Context, remoteRepo, subjectDigestStr string) bool {
    53  	return false
    54  }
    55  
    56  func (ref ORASReferences) canSkipReferences(localRepo, subjectDigestStr string, referrers ReferenceList) (bool, error) {
    57  	imageStore := ref.storeController.GetImageStore(localRepo)
    58  	digest := godigest.Digest(subjectDigestStr)
    59  
    60  	// check oras artifacts already synced
    61  	if len(referrers.References) > 0 {
    62  		localRefs, err := imageStore.GetOrasReferrers(localRepo, digest, "")
    63  		if err != nil {
    64  			if errors.Is(err, zerr.ErrManifestNotFound) {
    65  				return false, nil
    66  			}
    67  
    68  			ref.log.Error().Str("errorType", common.TypeOf(err)).Str("repository", localRepo).
    69  				Str("subject", subjectDigestStr).
    70  				Err(err).Msg("couldn't get local ORAS artifact for image")
    71  
    72  			return false, err
    73  		}
    74  
    75  		if !artifactDescriptorsEqual(localRefs, referrers.References) {
    76  			ref.log.Info().Str("repository", localRepo).Str("subject", subjectDigestStr).
    77  				Msg("upstream ORAS artifacts for image changed, syncing again")
    78  
    79  			return false, nil
    80  		}
    81  	}
    82  
    83  	ref.log.Info().Str("repository", localRepo).Str("subject", subjectDigestStr).
    84  		Msg("skipping ORAS artifact for image, already synced")
    85  
    86  	return true, nil
    87  }
    88  
    89  func (ref ORASReferences) SyncReferences(ctx context.Context, localRepo, remoteRepo, subjectDigestStr string) (
    90  	[]godigest.Digest, error,
    91  ) {
    92  	refsDigests := make([]godigest.Digest, 0, 10)
    93  
    94  	referrers, err := ref.getReferenceList(ctx, remoteRepo, subjectDigestStr)
    95  	if err != nil {
    96  		return refsDigests, err
    97  	}
    98  
    99  	skipORASRefs, err := ref.canSkipReferences(localRepo, subjectDigestStr, referrers)
   100  	if err != nil {
   101  		ref.log.Error().Err(err).Str("repository", localRepo).Str("subject", subjectDigestStr).
   102  			Msg("couldn't check if ORAS artifact for image can be skipped")
   103  	}
   104  
   105  	if skipORASRefs {
   106  		for _, man := range referrers.References {
   107  			refsDigests = append(refsDigests, man.Digest)
   108  		}
   109  
   110  		return refsDigests, nil
   111  	}
   112  
   113  	imageStore := ref.storeController.GetImageStore(localRepo)
   114  
   115  	ref.log.Info().Str("repository", localRepo).Str("subject", subjectDigestStr).
   116  		Msg("syncing ORAS artifacts for image")
   117  
   118  	for _, referrer := range referrers.References {
   119  		var artifactManifest oras.Manifest
   120  
   121  		orasBuf, _, statusCode, err := ref.client.MakeGetRequest(ctx, &artifactManifest, oras.MediaTypeDescriptor,
   122  			"v2", remoteRepo, "manifests", referrer.Digest.String())
   123  		if err != nil {
   124  			if statusCode == http.StatusNotFound {
   125  				return refsDigests, zerr.ErrSyncReferrerNotFound
   126  			}
   127  
   128  			ref.log.Error().Str("errorType", common.TypeOf(err)).
   129  				Str("repository", localRepo).Str("subject", subjectDigestStr).
   130  				Err(err).Msg("couldn't get ORAS artifact for image")
   131  
   132  			return refsDigests, err
   133  		}
   134  
   135  		for _, blob := range artifactManifest.Blobs {
   136  			if err := syncBlob(ctx, ref.client, imageStore, localRepo, remoteRepo, blob.Digest, ref.log); err != nil {
   137  				return refsDigests, err
   138  			}
   139  		}
   140  
   141  		referenceDigest, _, err := imageStore.PutImageManifest(localRepo, referrer.Digest.String(),
   142  			oras.MediaTypeArtifactManifest, orasBuf)
   143  		if err != nil {
   144  			ref.log.Error().Str("errorType", common.TypeOf(err)).
   145  				Str("repository", localRepo).Str("subject", subjectDigestStr).
   146  				Err(err).Msg("couldn't upload ORAS artifact for image")
   147  
   148  			return refsDigests, err
   149  		}
   150  
   151  		refsDigests = append(refsDigests, referenceDigest)
   152  
   153  		if ref.metaDB != nil {
   154  			ref.log.Debug().Str("repository", localRepo).Str("subject", subjectDigestStr).
   155  				Msg("metaDB: trying to sync oras artifact for image")
   156  
   157  			err := meta.SetImageMetaFromInput(context.Background(), localRepo, //nolint:contextcheck
   158  				referenceDigest.String(), referrer.MediaType,
   159  				referenceDigest, orasBuf, ref.storeController.GetImageStore(localRepo),
   160  				ref.metaDB, ref.log)
   161  			if err != nil {
   162  				return refsDigests, fmt.Errorf("metaDB: failed to set metadata for oras artifact '%s@%s': %w",
   163  					localRepo, subjectDigestStr, err)
   164  			}
   165  
   166  			ref.log.Info().Str("repository", localRepo).Str("subject", subjectDigestStr).
   167  				Msg("metaDB: successfully added oras artifacts to MetaDB for image")
   168  		}
   169  	}
   170  
   171  	ref.log.Info().Str("repository", localRepo).Str("subject", subjectDigestStr).
   172  		Msg("successfully synced oras artifacts for image")
   173  
   174  	return refsDigests, nil
   175  }
   176  
   177  func (ref ORASReferences) getReferenceList(ctx context.Context, repo, subjectDigestStr string) (ReferenceList, error) {
   178  	var referrers ReferenceList
   179  
   180  	_, _, statusCode, err := ref.client.MakeGetRequest(ctx, &referrers, "application/json",
   181  		apiConstants.ArtifactSpecRoutePrefix, repo, "manifests", subjectDigestStr, "referrers")
   182  	if err != nil {
   183  		if statusCode == http.StatusNotFound || statusCode == http.StatusBadRequest {
   184  			ref.log.Debug().Str("repository", repo).Str("subject", subjectDigestStr).Err(err).
   185  				Msg("couldn't find any ORAS artifact for image")
   186  
   187  			return referrers, zerr.ErrSyncReferrerNotFound
   188  		}
   189  
   190  		return referrers, err
   191  	}
   192  
   193  	return referrers, nil
   194  }