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 }