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 }