zotregistry.dev/zot@v1.4.4-0.20240314164342-eec277e14d20/pkg/extensions/sync/references/oci.go (about)

     1  //go:build sync
     2  // +build sync
     3  
     4  package references
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"net/http"
    12  
    13  	godigest "github.com/opencontainers/go-digest"
    14  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    15  
    16  	zerr "zotregistry.dev/zot/errors"
    17  	"zotregistry.dev/zot/pkg/common"
    18  	"zotregistry.dev/zot/pkg/extensions/sync/constants"
    19  	client "zotregistry.dev/zot/pkg/extensions/sync/httpclient"
    20  	"zotregistry.dev/zot/pkg/log"
    21  	"zotregistry.dev/zot/pkg/meta"
    22  	mTypes "zotregistry.dev/zot/pkg/meta/types"
    23  	"zotregistry.dev/zot/pkg/storage"
    24  	storageTypes "zotregistry.dev/zot/pkg/storage/types"
    25  )
    26  
    27  type OciReferences struct {
    28  	client          *client.Client
    29  	storeController storage.StoreController
    30  	metaDB          mTypes.MetaDB
    31  	log             log.Logger
    32  }
    33  
    34  func NewOciReferences(httpClient *client.Client, storeController storage.StoreController,
    35  	metaDB mTypes.MetaDB, log log.Logger,
    36  ) OciReferences {
    37  	return OciReferences{
    38  		client:          httpClient,
    39  		storeController: storeController,
    40  		metaDB:          metaDB,
    41  		log:             log,
    42  	}
    43  }
    44  
    45  func (ref OciReferences) Name() string {
    46  	return constants.OCI
    47  }
    48  
    49  func (ref OciReferences) IsSigned(ctx context.Context, remoteRepo, subjectDigestStr string) bool {
    50  	// use artifactTypeFilter
    51  	index, err := ref.getIndex(ctx, remoteRepo, subjectDigestStr)
    52  	if err != nil {
    53  		return false
    54  	}
    55  
    56  	if len(getNotationManifestsFromOCIRefs(index)) > 0 || len(getCosignManifestsFromOCIRefs(index)) > 0 {
    57  		return true
    58  	}
    59  
    60  	return false
    61  }
    62  
    63  func (ref OciReferences) canSkipReferences(localRepo, subjectDigestStr string, index ispec.Index) (bool, error) {
    64  	imageStore := ref.storeController.GetImageStore(localRepo)
    65  	digest := godigest.Digest(subjectDigestStr)
    66  
    67  	// check oci references already synced
    68  	if len(index.Manifests) > 0 {
    69  		localRefs, err := imageStore.GetReferrers(localRepo, digest, nil)
    70  		if err != nil {
    71  			if errors.Is(err, zerr.ErrManifestNotFound) {
    72  				return false, nil
    73  			}
    74  
    75  			ref.log.Error().Str("errorType", common.TypeOf(err)).
    76  				Str("repository", localRepo).Str("subject", subjectDigestStr).
    77  				Err(err).Msg("couldn't get local oci references for image")
    78  
    79  			return false, err
    80  		}
    81  
    82  		if !descriptorsEqual(localRefs.Manifests, index.Manifests) {
    83  			ref.log.Info().Str("repository", localRepo).Str("subject", subjectDigestStr).
    84  				Msg("remote oci references for image changed, syncing again")
    85  
    86  			return false, nil
    87  		}
    88  	}
    89  
    90  	ref.log.Info().Str("repository", localRepo).Str("subject", subjectDigestStr).
    91  		Msg("skipping oci references for image, already synced")
    92  
    93  	return true, nil
    94  }
    95  
    96  func (ref OciReferences) SyncReferences(ctx context.Context, localRepo, remoteRepo, subjectDigestStr string) (
    97  	[]godigest.Digest, error,
    98  ) {
    99  	refsDigests := make([]godigest.Digest, 0, 10)
   100  
   101  	index, err := ref.getIndex(ctx, remoteRepo, subjectDigestStr)
   102  	if err != nil {
   103  		return refsDigests, err
   104  	}
   105  
   106  	skipOCIRefs, err := ref.canSkipReferences(localRepo, subjectDigestStr, index)
   107  	if err != nil {
   108  		ref.log.Error().Err(err).Str("repository", localRepo).Str("subject", subjectDigestStr).
   109  			Msg("couldn't check if the upstream oci references for image can be skipped")
   110  	}
   111  
   112  	if skipOCIRefs {
   113  		/* even if it's skip we need to return the digests,
   114  		because maybe in the meantime a reference pointing to this one was pushed */
   115  		for _, man := range index.Manifests {
   116  			refsDigests = append(refsDigests, man.Digest)
   117  		}
   118  
   119  		return refsDigests, nil
   120  	}
   121  
   122  	imageStore := ref.storeController.GetImageStore(localRepo)
   123  
   124  	ref.log.Info().Str("repository", localRepo).Str("subject", subjectDigestStr).
   125  		Msg("syncing oci references for image")
   126  
   127  	for _, referrer := range index.Manifests {
   128  		referenceBuf, referenceDigest, err := syncManifest(ctx, ref.client, imageStore, localRepo, remoteRepo,
   129  			referrer, subjectDigestStr, ref.log)
   130  		if err != nil {
   131  			return refsDigests, err
   132  		}
   133  
   134  		refsDigests = append(refsDigests, referenceDigest)
   135  
   136  		if ref.metaDB != nil {
   137  			ref.log.Debug().Str("repository", localRepo).Str("subject", subjectDigestStr).Str("component", "metadb").
   138  				Msg("trying to add oci references for image")
   139  
   140  			err = meta.SetImageMetaFromInput(ctx, localRepo, referenceDigest.String(), referrer.MediaType,
   141  				referenceDigest, referenceBuf, ref.storeController.GetImageStore(localRepo),
   142  				ref.metaDB, ref.log)
   143  			if err != nil {
   144  				return refsDigests, fmt.Errorf("failed to set metadata for oci reference in '%s@%s': %w",
   145  					localRepo, subjectDigestStr, err)
   146  			}
   147  
   148  			ref.log.Info().Str("repository", localRepo).Str("subject", subjectDigestStr).Str("component", "metadb").
   149  				Msg("successfully added oci references to MetaDB for image")
   150  		}
   151  	}
   152  
   153  	ref.log.Info().Str("repository", localRepo).Str("subject", subjectDigestStr).
   154  		Msg("successfully synced oci references for image")
   155  
   156  	return refsDigests, nil
   157  }
   158  
   159  func (ref OciReferences) getIndex(ctx context.Context, repo, subjectDigestStr string) (ispec.Index, error) {
   160  	var index ispec.Index
   161  
   162  	_, _, statusCode, err := ref.client.MakeGetRequest(ctx, &index, ispec.MediaTypeImageIndex,
   163  		"v2", repo, "referrers", subjectDigestStr)
   164  	if err != nil {
   165  		if statusCode == http.StatusNotFound {
   166  			ref.log.Debug().Str("repository", repo).Str("subject", subjectDigestStr).
   167  				Msg("couldn't find any oci reference for image, skipping")
   168  
   169  			return index, zerr.ErrSyncReferrerNotFound
   170  		}
   171  
   172  		return index, err
   173  	}
   174  
   175  	return index, nil
   176  }
   177  
   178  func syncManifest(ctx context.Context, client *client.Client, imageStore storageTypes.ImageStore, localRepo,
   179  	remoteRepo string, desc ispec.Descriptor, subjectDigestStr string, log log.Logger,
   180  ) ([]byte, godigest.Digest, error) {
   181  	var manifest ispec.Manifest
   182  
   183  	var refDigest godigest.Digest
   184  
   185  	OCIRefBuf, _, statusCode, err := client.MakeGetRequest(ctx, &manifest, ispec.MediaTypeImageManifest,
   186  		"v2", remoteRepo, "manifests", desc.Digest.String())
   187  	if err != nil {
   188  		if statusCode == http.StatusNotFound {
   189  			return []byte{}, refDigest, zerr.ErrSyncReferrerNotFound
   190  		}
   191  
   192  		log.Error().Str("errorType", common.TypeOf(err)).
   193  			Str("repository", localRepo).Str("subject", subjectDigestStr).
   194  			Err(err).Msg("couldn't get oci reference manifest for image")
   195  
   196  		return []byte{}, refDigest, err
   197  	}
   198  
   199  	if desc.MediaType == ispec.MediaTypeImageManifest {
   200  		// read manifest
   201  		var manifest ispec.Manifest
   202  
   203  		err = json.Unmarshal(OCIRefBuf, &manifest)
   204  		if err != nil {
   205  			log.Error().Str("errorType", common.TypeOf(err)).
   206  				Str("repository", localRepo).Str("subject", subjectDigestStr).
   207  				Err(err).Msg("couldn't unmarshal oci reference manifest for image")
   208  
   209  			return []byte{}, refDigest, err
   210  		}
   211  
   212  		for _, layer := range manifest.Layers {
   213  			if err := syncBlob(ctx, client, imageStore, localRepo, remoteRepo, layer.Digest, log); err != nil {
   214  				return []byte{}, refDigest, err
   215  			}
   216  		}
   217  
   218  		// sync config blob
   219  		if err := syncBlob(ctx, client, imageStore, localRepo, remoteRepo, manifest.Config.Digest, log); err != nil {
   220  			return []byte{}, refDigest, err
   221  		}
   222  	} else {
   223  		return []byte{}, refDigest, nil
   224  	}
   225  
   226  	refDigest, _, err = imageStore.PutImageManifest(localRepo, desc.Digest.String(),
   227  		desc.MediaType, OCIRefBuf)
   228  	if err != nil {
   229  		log.Error().Str("errorType", common.TypeOf(err)).
   230  			Str("repository", localRepo).Str("subject", subjectDigestStr).
   231  			Err(err).Msg("couldn't upload oci reference for image")
   232  
   233  		return []byte{}, refDigest, err
   234  	}
   235  
   236  	return OCIRefBuf, refDigest, nil
   237  }