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

     1  //go:build sync
     2  // +build sync
     3  
     4  package sync
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"os"
    12  	"path"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/containers/image/v5/types"
    17  	"github.com/opencontainers/go-digest"
    18  	ispec "github.com/opencontainers/image-spec/specs-go/v1"
    19  
    20  	zerr "zotregistry.dev/zot/errors"
    21  	"zotregistry.dev/zot/pkg/common"
    22  	"zotregistry.dev/zot/pkg/extensions/monitoring"
    23  	"zotregistry.dev/zot/pkg/log"
    24  	"zotregistry.dev/zot/pkg/meta"
    25  	mTypes "zotregistry.dev/zot/pkg/meta/types"
    26  	"zotregistry.dev/zot/pkg/storage"
    27  	storageCommon "zotregistry.dev/zot/pkg/storage/common"
    28  	"zotregistry.dev/zot/pkg/storage/local"
    29  	storageTypes "zotregistry.dev/zot/pkg/storage/types"
    30  )
    31  
    32  type DestinationRegistry struct {
    33  	storeController storage.StoreController
    34  	tempStorage     OciLayoutStorage
    35  	metaDB          mTypes.MetaDB
    36  	log             log.Logger
    37  }
    38  
    39  func NewDestinationRegistry(
    40  	storeController storage.StoreController, // local store controller
    41  	tempStoreController storage.StoreController, // temp store controller
    42  	metaDB mTypes.MetaDB,
    43  	log log.Logger,
    44  ) Destination {
    45  	return &DestinationRegistry{
    46  		storeController: storeController,
    47  		tempStorage:     NewOciLayoutStorage(tempStoreController),
    48  		metaDB:          metaDB,
    49  		// first we sync from remote (using containers/image copy from docker:// to oci:) to a temp imageStore
    50  		// then we copy the image from tempStorage to zot's storage using ImageStore APIs
    51  		log: log,
    52  	}
    53  }
    54  
    55  func (registry *DestinationRegistry) CanSkipImage(repo, tag string, imageDigest digest.Digest) (bool, error) {
    56  	// check image already synced
    57  	imageStore := registry.storeController.GetImageStore(repo)
    58  
    59  	_, localImageManifestDigest, _, err := imageStore.GetImageManifest(repo, tag)
    60  	if err != nil {
    61  		if errors.Is(err, zerr.ErrRepoNotFound) || errors.Is(err, zerr.ErrManifestNotFound) {
    62  			return false, nil
    63  		}
    64  
    65  		registry.log.Error().Str("errorType", common.TypeOf(err)).Str("repo", repo).Str("reference", tag).
    66  			Err(err).Msg("couldn't get local image manifest")
    67  
    68  		return false, err
    69  	}
    70  
    71  	if localImageManifestDigest != imageDigest {
    72  		registry.log.Info().Str("repo", repo).Str("reference", tag).
    73  			Str("localDigest", localImageManifestDigest.String()).
    74  			Str("remoteDigest", imageDigest.String()).
    75  			Msg("remote image digest changed, syncing again")
    76  
    77  		return false, nil
    78  	}
    79  
    80  	return true, nil
    81  }
    82  
    83  func (registry *DestinationRegistry) GetContext() *types.SystemContext {
    84  	return registry.tempStorage.GetContext()
    85  }
    86  
    87  func (registry *DestinationRegistry) GetImageReference(repo, reference string) (types.ImageReference, error) {
    88  	return registry.tempStorage.GetImageReference(repo, reference)
    89  }
    90  
    91  // finalize a syncing image.
    92  func (registry *DestinationRegistry) CommitImage(imageReference types.ImageReference, repo, reference string) error {
    93  	imageStore := registry.storeController.GetImageStore(repo)
    94  
    95  	tempImageStore := getImageStoreFromImageReference(imageReference, repo, reference)
    96  
    97  	defer os.RemoveAll(tempImageStore.RootDir())
    98  
    99  	registry.log.Info().Str("syncTempDir", path.Join(tempImageStore.RootDir(), repo)).Str("reference", reference).
   100  		Msg("pushing synced local image to local registry")
   101  
   102  	var lockLatency time.Time
   103  
   104  	manifestBlob, manifestDigest, mediaType, err := tempImageStore.GetImageManifest(repo, reference)
   105  	if err != nil {
   106  		registry.log.Error().Str("errorType", common.TypeOf(err)).
   107  			Err(err).Str("dir", path.Join(tempImageStore.RootDir(), repo)).Str("repo", repo).Str("reference", reference).
   108  			Msg("couldn't find synced manifest in temporary sync dir")
   109  
   110  		return err
   111  	}
   112  
   113  	// is image manifest
   114  	switch mediaType {
   115  	case ispec.MediaTypeImageManifest:
   116  		if err := registry.copyManifest(repo, manifestBlob, reference, tempImageStore); err != nil {
   117  			if errors.Is(err, zerr.ErrImageLintAnnotations) {
   118  				registry.log.Error().Str("errorType", common.TypeOf(err)).
   119  					Err(err).Msg("couldn't upload manifest because of missing annotations")
   120  
   121  				return nil
   122  			}
   123  
   124  			return err
   125  		}
   126  	case ispec.MediaTypeImageIndex:
   127  		// is image index
   128  		var indexManifest ispec.Index
   129  
   130  		if err := json.Unmarshal(manifestBlob, &indexManifest); err != nil {
   131  			registry.log.Error().Str("errorType", common.TypeOf(err)).
   132  				Err(err).Str("dir", path.Join(tempImageStore.RootDir(), repo)).
   133  				Msg("invalid JSON")
   134  
   135  			return err
   136  		}
   137  
   138  		for _, manifest := range indexManifest.Manifests {
   139  			tempImageStore.RLock(&lockLatency)
   140  			manifestBuf, err := tempImageStore.GetBlobContent(repo, manifest.Digest)
   141  			tempImageStore.RUnlock(&lockLatency)
   142  
   143  			if err != nil {
   144  				registry.log.Error().Str("errorType", common.TypeOf(err)).
   145  					Err(err).Str("dir", path.Join(tempImageStore.RootDir(), repo)).Str("digest", manifest.Digest.String()).
   146  					Msg("couldn't find manifest which is part of an image index")
   147  
   148  				return err
   149  			}
   150  
   151  			if err := registry.copyManifest(repo, manifestBuf, manifest.Digest.String(),
   152  				tempImageStore); err != nil {
   153  				if errors.Is(err, zerr.ErrImageLintAnnotations) {
   154  					registry.log.Error().Str("errorType", common.TypeOf(err)).
   155  						Err(err).Msg("couldn't upload manifest because of missing annotations")
   156  
   157  					return nil
   158  				}
   159  
   160  				return err
   161  			}
   162  		}
   163  
   164  		_, _, err = imageStore.PutImageManifest(repo, reference, mediaType, manifestBlob)
   165  		if err != nil {
   166  			registry.log.Error().Str("errorType", common.TypeOf(err)).Str("repo", repo).Str("reference", reference).
   167  				Err(err).Msg("couldn't upload manifest")
   168  
   169  			return err
   170  		}
   171  
   172  		if registry.metaDB != nil {
   173  			err = meta.SetImageMetaFromInput(context.Background(), repo, reference, mediaType,
   174  				manifestDigest, manifestBlob, imageStore, registry.metaDB, registry.log)
   175  			if err != nil {
   176  				return fmt.Errorf("failed to set metadata for image '%s %s': %w", repo, reference, err)
   177  			}
   178  
   179  			registry.log.Debug().Str("repo", repo).Str("reference", reference).Str("component", "metadb").
   180  				Msg("successfully set metadata for image")
   181  		}
   182  	}
   183  
   184  	registry.log.Info().Str("image", fmt.Sprintf("%s:%s", repo, reference)).Msg("successfully synced image")
   185  
   186  	return nil
   187  }
   188  
   189  func (registry *DestinationRegistry) CleanupImage(imageReference types.ImageReference, repo, reference string) error {
   190  	tmpDir := getTempRootDirFromImageReference(imageReference, repo, reference)
   191  
   192  	return os.RemoveAll(tmpDir)
   193  }
   194  
   195  func (registry *DestinationRegistry) copyManifest(repo string, manifestContent []byte, reference string,
   196  	tempImageStore storageTypes.ImageStore,
   197  ) error {
   198  	imageStore := registry.storeController.GetImageStore(repo)
   199  
   200  	var manifest ispec.Manifest
   201  
   202  	var err error
   203  
   204  	if err := json.Unmarshal(manifestContent, &manifest); err != nil {
   205  		registry.log.Error().Str("errorType", common.TypeOf(err)).
   206  			Err(err).Str("dir", path.Join(tempImageStore.RootDir(), repo)).
   207  			Msg("invalid JSON")
   208  
   209  		return err
   210  	}
   211  
   212  	for _, blob := range manifest.Layers {
   213  		if storageCommon.IsNonDistributable(blob.MediaType) {
   214  			continue
   215  		}
   216  
   217  		err = registry.copyBlob(repo, blob.Digest, blob.MediaType, tempImageStore)
   218  		if err != nil {
   219  			return err
   220  		}
   221  	}
   222  
   223  	err = registry.copyBlob(repo, manifest.Config.Digest, manifest.Config.MediaType, tempImageStore)
   224  	if err != nil {
   225  		return err
   226  	}
   227  
   228  	digest, _, err := imageStore.PutImageManifest(repo, reference,
   229  		ispec.MediaTypeImageManifest, manifestContent)
   230  	if err != nil {
   231  		registry.log.Error().Str("errorType", common.TypeOf(err)).
   232  			Err(err).Msg("couldn't upload manifest")
   233  
   234  		return err
   235  	}
   236  
   237  	if registry.metaDB != nil {
   238  		err = meta.SetImageMetaFromInput(context.Background(), repo, reference, ispec.MediaTypeImageManifest,
   239  			digest, manifestContent, imageStore, registry.metaDB, registry.log)
   240  		if err != nil {
   241  			registry.log.Error().Str("errorType", common.TypeOf(err)).
   242  				Err(err).Msg("couldn't set metadata from input")
   243  
   244  			return err
   245  		}
   246  
   247  		registry.log.Debug().Str("repo", repo).Str("reference", reference).Msg("successfully set metadata for image")
   248  	}
   249  
   250  	return nil
   251  }
   252  
   253  // Copy a blob from one image store to another image store.
   254  func (registry *DestinationRegistry) copyBlob(repo string, blobDigest digest.Digest, blobMediaType string,
   255  	tempImageStore storageTypes.ImageStore,
   256  ) error {
   257  	imageStore := registry.storeController.GetImageStore(repo)
   258  	if found, _, _ := imageStore.CheckBlob(repo, blobDigest); found {
   259  		// Blob is already at destination, nothing to do
   260  		return nil
   261  	}
   262  
   263  	blobReadCloser, _, err := tempImageStore.GetBlob(repo, blobDigest, blobMediaType)
   264  	if err != nil {
   265  		registry.log.Error().Str("errorType", common.TypeOf(err)).Err(err).
   266  			Str("dir", path.Join(tempImageStore.RootDir(), repo)).
   267  			Str("blob digest", blobDigest.String()).Str("media type", blobMediaType).
   268  			Msg("couldn't read blob")
   269  
   270  		return err
   271  	}
   272  	defer blobReadCloser.Close()
   273  
   274  	_, _, err = imageStore.FullBlobUpload(repo, blobReadCloser, blobDigest)
   275  	if err != nil {
   276  		registry.log.Error().Str("errorType", common.TypeOf(err)).Err(err).
   277  			Str("blob digest", blobDigest.String()).Str("media type", blobMediaType).
   278  			Msg("couldn't upload blob")
   279  	}
   280  
   281  	return err
   282  }
   283  
   284  // use only with local imageReferences.
   285  func getImageStoreFromImageReference(imageReference types.ImageReference, repo, reference string,
   286  ) storageTypes.ImageStore {
   287  	tmpRootDir := getTempRootDirFromImageReference(imageReference, repo, reference)
   288  
   289  	return getImageStore(tmpRootDir)
   290  }
   291  
   292  func getTempRootDirFromImageReference(imageReference types.ImageReference, repo, reference string) string {
   293  	var tmpRootDir string
   294  
   295  	if strings.HasSuffix(imageReference.StringWithinTransport(), reference) {
   296  		tmpRootDir = strings.ReplaceAll(imageReference.StringWithinTransport(), fmt.Sprintf("%s:%s", repo, reference), "")
   297  	} else {
   298  		tmpRootDir = strings.ReplaceAll(imageReference.StringWithinTransport(), fmt.Sprintf("%s:", repo), "")
   299  	}
   300  
   301  	return tmpRootDir
   302  }
   303  
   304  func getImageStore(rootDir string) storageTypes.ImageStore {
   305  	metrics := monitoring.NewMetricsServer(false, log.Logger{})
   306  
   307  	return local.NewImageStore(rootDir, false, false, log.Logger{}, metrics, nil, nil)
   308  }