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 }