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