zotregistry.io/zot@v1.4.4-0.20231124084042-02a8ed785457/pkg/extensions/sync/references/oras.go (about) 1 //go:build sync 2 // +build sync 3 4 package references 5 6 import ( 7 "context" 8 "errors" 9 "fmt" 10 "net/http" 11 12 godigest "github.com/opencontainers/go-digest" 13 oras "github.com/oras-project/artifacts-spec/specs-go/v1" 14 15 zerr "zotregistry.io/zot/errors" 16 apiConstants "zotregistry.io/zot/pkg/api/constants" 17 "zotregistry.io/zot/pkg/common" 18 "zotregistry.io/zot/pkg/extensions/sync/constants" 19 client "zotregistry.io/zot/pkg/extensions/sync/httpclient" 20 "zotregistry.io/zot/pkg/log" 21 "zotregistry.io/zot/pkg/meta" 22 mTypes "zotregistry.io/zot/pkg/meta/types" 23 "zotregistry.io/zot/pkg/storage" 24 ) 25 26 type ReferenceList struct { 27 References []oras.Descriptor `json:"references"` 28 } 29 30 type ORASReferences struct { 31 client *client.Client 32 storeController storage.StoreController 33 metaDB mTypes.MetaDB 34 log log.Logger 35 } 36 37 func NewORASReferences(httpClient *client.Client, storeController storage.StoreController, 38 metaDB mTypes.MetaDB, log log.Logger, 39 ) ORASReferences { 40 return ORASReferences{ 41 client: httpClient, 42 storeController: storeController, 43 metaDB: metaDB, 44 log: log, 45 } 46 } 47 48 func (ref ORASReferences) Name() string { 49 return constants.Oras 50 } 51 52 func (ref ORASReferences) IsSigned(ctx context.Context, remoteRepo, subjectDigestStr string) bool { 53 return false 54 } 55 56 func (ref ORASReferences) canSkipReferences(localRepo, subjectDigestStr string, referrers ReferenceList) (bool, error) { 57 imageStore := ref.storeController.GetImageStore(localRepo) 58 digest := godigest.Digest(subjectDigestStr) 59 60 // check oras artifacts already synced 61 if len(referrers.References) > 0 { 62 localRefs, err := imageStore.GetOrasReferrers(localRepo, digest, "") 63 if err != nil { 64 if errors.Is(err, zerr.ErrManifestNotFound) { 65 return false, nil 66 } 67 68 ref.log.Error().Str("errorType", common.TypeOf(err)).Str("repository", localRepo). 69 Str("subject", subjectDigestStr). 70 Err(err).Msg("couldn't get local ORAS artifact for image") 71 72 return false, err 73 } 74 75 if !artifactDescriptorsEqual(localRefs, referrers.References) { 76 ref.log.Info().Str("repository", localRepo).Str("subject", subjectDigestStr). 77 Msg("upstream ORAS artifacts for image changed, syncing again") 78 79 return false, nil 80 } 81 } 82 83 ref.log.Info().Str("repository", localRepo).Str("subject", subjectDigestStr). 84 Msg("skipping ORAS artifact for image, already synced") 85 86 return true, nil 87 } 88 89 func (ref ORASReferences) SyncReferences(ctx context.Context, localRepo, remoteRepo, subjectDigestStr string) ( 90 []godigest.Digest, error, 91 ) { 92 refsDigests := make([]godigest.Digest, 0, 10) 93 94 referrers, err := ref.getReferenceList(ctx, remoteRepo, subjectDigestStr) 95 if err != nil { 96 return refsDigests, err 97 } 98 99 skipORASRefs, err := ref.canSkipReferences(localRepo, subjectDigestStr, referrers) 100 if err != nil { 101 ref.log.Error().Err(err).Str("repository", localRepo).Str("subject", subjectDigestStr). 102 Msg("couldn't check if ORAS artifact for image can be skipped") 103 } 104 105 if skipORASRefs { 106 for _, man := range referrers.References { 107 refsDigests = append(refsDigests, man.Digest) 108 } 109 110 return refsDigests, nil 111 } 112 113 imageStore := ref.storeController.GetImageStore(localRepo) 114 115 ref.log.Info().Str("repository", localRepo).Str("subject", subjectDigestStr). 116 Msg("syncing ORAS artifacts for image") 117 118 for _, referrer := range referrers.References { 119 var artifactManifest oras.Manifest 120 121 orasBuf, _, statusCode, err := ref.client.MakeGetRequest(ctx, &artifactManifest, oras.MediaTypeDescriptor, 122 "v2", remoteRepo, "manifests", referrer.Digest.String()) 123 if err != nil { 124 if statusCode == http.StatusNotFound { 125 return refsDigests, zerr.ErrSyncReferrerNotFound 126 } 127 128 ref.log.Error().Str("errorType", common.TypeOf(err)). 129 Str("repository", localRepo).Str("subject", subjectDigestStr). 130 Err(err).Msg("couldn't get ORAS artifact for image") 131 132 return refsDigests, err 133 } 134 135 for _, blob := range artifactManifest.Blobs { 136 if err := syncBlob(ctx, ref.client, imageStore, localRepo, remoteRepo, blob.Digest, ref.log); err != nil { 137 return refsDigests, err 138 } 139 } 140 141 referenceDigest, _, err := imageStore.PutImageManifest(localRepo, referrer.Digest.String(), 142 oras.MediaTypeArtifactManifest, orasBuf) 143 if err != nil { 144 ref.log.Error().Str("errorType", common.TypeOf(err)). 145 Str("repository", localRepo).Str("subject", subjectDigestStr). 146 Err(err).Msg("couldn't upload ORAS artifact for image") 147 148 return refsDigests, err 149 } 150 151 refsDigests = append(refsDigests, referenceDigest) 152 153 if ref.metaDB != nil { 154 ref.log.Debug().Str("repository", localRepo).Str("subject", subjectDigestStr). 155 Msg("metaDB: trying to sync oras artifact for image") 156 157 err := meta.SetImageMetaFromInput(context.Background(), localRepo, //nolint:contextcheck 158 referenceDigest.String(), referrer.MediaType, 159 referenceDigest, orasBuf, ref.storeController.GetImageStore(localRepo), 160 ref.metaDB, ref.log) 161 if err != nil { 162 return refsDigests, fmt.Errorf("metaDB: failed to set metadata for oras artifact '%s@%s': %w", 163 localRepo, subjectDigestStr, err) 164 } 165 166 ref.log.Info().Str("repository", localRepo).Str("subject", subjectDigestStr). 167 Msg("metaDB: successfully added oras artifacts to MetaDB for image") 168 } 169 } 170 171 ref.log.Info().Str("repository", localRepo).Str("subject", subjectDigestStr). 172 Msg("successfully synced oras artifacts for image") 173 174 return refsDigests, nil 175 } 176 177 func (ref ORASReferences) getReferenceList(ctx context.Context, repo, subjectDigestStr string) (ReferenceList, error) { 178 var referrers ReferenceList 179 180 _, _, statusCode, err := ref.client.MakeGetRequest(ctx, &referrers, "application/json", 181 apiConstants.ArtifactSpecRoutePrefix, repo, "manifests", subjectDigestStr, "referrers") 182 if err != nil { 183 if statusCode == http.StatusNotFound || statusCode == http.StatusBadRequest { 184 ref.log.Debug().Str("repository", repo).Str("subject", subjectDigestStr).Err(err). 185 Msg("couldn't find any ORAS artifact for image") 186 187 return referrers, zerr.ErrSyncReferrerNotFound 188 } 189 190 return referrers, err 191 } 192 193 return referrers, nil 194 }