Skip to content

Commit 2d877aa

Browse files
fix(sync): also sync on demand digests, not only tags, closes #902 (#932)
Signed-off-by: Petu Eusebiu <[email protected]>
1 parent c6ffbce commit 2d877aa

File tree

4 files changed

+130
-46
lines changed

4 files changed

+130
-46
lines changed

pkg/extensions/sync/on_demand.go

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@ func (di *demandedImages) delete(key string) {
5151
}
5252

5353
func OneImage(cfg Config, storeController storage.StoreController,
54-
repo, tag string, isArtifact bool, log log.Logger,
54+
repo, reference string, isArtifact bool, log log.Logger,
5555
) error {
5656
// guard against multiple parallel requests
57-
demandedImage := fmt.Sprintf("%s:%s", repo, tag)
57+
demandedImage := fmt.Sprintf("%s:%s", repo, reference)
5858
// loadOrStore image-based channel
5959
imageChannel, found := demandedImgs.loadOrStoreChan(demandedImage, make(chan error))
6060
// if value found wait on channel receive or close
@@ -73,7 +73,7 @@ func OneImage(cfg Config, storeController storage.StoreController,
7373
defer demandedImgs.delete(demandedImage)
7474
defer close(imageChannel)
7575

76-
go syncOneImage(imageChannel, cfg, storeController, repo, tag, isArtifact, log)
76+
go syncOneImage(imageChannel, cfg, storeController, repo, reference, isArtifact, log)
7777

7878
err, ok := <-imageChannel
7979
if !ok {
@@ -84,7 +84,7 @@ func OneImage(cfg Config, storeController storage.StoreController,
8484
}
8585

8686
func syncOneImage(imageChannel chan error, cfg Config, storeController storage.StoreController,
87-
localRepo, tag string, isArtifact bool, log log.Logger,
87+
localRepo, reference string, isArtifact bool, log log.Logger,
8888
) {
8989
var credentialsFile CredentialsFile
9090

@@ -161,21 +161,21 @@ func syncOneImage(imageChannel chan error, cfg Config, storeController storage.S
161161
options := getCopyOptions(upstreamCtx, localCtx)
162162

163163
// demanded 'image' is a signature
164-
if isCosignTag(tag) {
164+
if isCosignTag(reference) {
165165
// at tis point we should already have images synced, but not their signatures.
166166
// is cosign signature
167-
cosignManifest, err := sig.getCosignManifest(upstreamRepo, tag)
167+
cosignManifest, err := sig.getCosignManifest(upstreamRepo, reference)
168168
if err != nil {
169169
log.Error().Str("errorType", TypeOf(err)).
170-
Err(err).Msgf("couldn't get upstream image %s:%s:%s cosign manifest", upstreamURL, upstreamRepo, tag)
170+
Err(err).Msgf("couldn't get upstream image %s:%s:%s cosign manifest", upstreamURL, upstreamRepo, reference)
171171

172172
continue
173173
}
174174

175-
err = sig.syncCosignSignature(localRepo, upstreamRepo, tag, cosignManifest)
175+
err = sig.syncCosignSignature(localRepo, upstreamRepo, reference, cosignManifest)
176176
if err != nil {
177177
log.Error().Str("errorType", TypeOf(err)).
178-
Err(err).Msgf("couldn't copy upstream image cosign signature %s/%s:%s", upstreamURL, upstreamRepo, tag)
178+
Err(err).Msgf("couldn't copy upstream image cosign signature %s/%s:%s", upstreamURL, upstreamRepo, reference)
179179

180180
continue
181181
}
@@ -185,18 +185,18 @@ func syncOneImage(imageChannel chan error, cfg Config, storeController storage.S
185185
return
186186
} else if isArtifact {
187187
// is notary signature
188-
refs, err := sig.getNotaryRefs(upstreamRepo, tag)
188+
refs, err := sig.getNotaryRefs(upstreamRepo, reference)
189189
if err != nil {
190190
log.Error().Str("errorType", TypeOf(err)).
191-
Err(err).Msgf("couldn't get upstream image %s/%s:%s notary references", upstreamURL, upstreamRepo, tag)
191+
Err(err).Msgf("couldn't get upstream image %s/%s:%s notary references", upstreamURL, upstreamRepo, reference)
192192

193193
continue
194194
}
195195

196-
err = sig.syncNotarySignature(localRepo, upstreamRepo, tag, refs)
196+
err = sig.syncNotarySignature(localRepo, upstreamRepo, reference, refs)
197197
if err != nil {
198198
log.Error().Str("errorType", TypeOf(err)).
199-
Err(err).Msgf("couldn't copy image signature %s/%s:%s", upstreamURL, upstreamRepo, tag)
199+
Err(err).Msgf("couldn't copy image signature %s/%s:%s", upstreamURL, upstreamRepo, reference)
200200

201201
continue
202202
}
@@ -214,13 +214,13 @@ func syncOneImage(imageChannel chan error, cfg Config, storeController storage.S
214214
copyOptions: options,
215215
}
216216

217-
skipped, copyErr := syncRun(regCfg, localRepo, upstreamRepo, tag, syncContextUtils, sig, log)
217+
skipped, copyErr := syncRun(regCfg, localRepo, upstreamRepo, reference, syncContextUtils, sig, log)
218218
if skipped {
219219
continue
220220
}
221221

222222
// key used to check if we already have a go routine syncing this image
223-
demandedImageRef := fmt.Sprintf("%s/%s:%s", upstreamAddr, upstreamRepo, tag)
223+
demandedImageRef := fmt.Sprintf("%s/%s:%s", upstreamAddr, upstreamRepo, reference)
224224

225225
if copyErr != nil {
226226
// don't retry in background if maxretry is 0
@@ -249,14 +249,18 @@ func syncOneImage(imageChannel chan error, cfg Config, storeController storage.S
249249
time.Sleep(retryOptions.Delay)
250250

251251
if err = retry.RetryIfNecessary(context.Background(), func() error {
252-
_, err := syncRun(regCfg, localRepo, upstreamRepo, tag, syncContextUtils, sig, log)
252+
_, err := syncRun(regCfg, localRepo, upstreamRepo, reference, syncContextUtils, sig, log)
253253

254254
return err
255255
}, retryOptions); err != nil {
256256
log.Error().Str("errorType", TypeOf(err)).
257257
Err(err).Msgf("sync routine: error while copying image %s", demandedImageRef)
258258
}
259259
}()
260+
} else {
261+
imageChannel <- nil
262+
263+
return
260264
}
261265
}
262266
}
@@ -265,24 +269,28 @@ func syncOneImage(imageChannel chan error, cfg Config, storeController storage.S
265269
}
266270

267271
func syncRun(regCfg RegistryConfig,
268-
localRepo, upstreamRepo, tag string, utils syncContextUtils, sig *signaturesCopier,
272+
localRepo, upstreamRepo, reference string, utils syncContextUtils, sig *signaturesCopier,
269273
log log.Logger,
270274
) (bool, error) {
271-
upstreamImageRef, err := getImageRef(utils.upstreamAddr, upstreamRepo, tag)
275+
upstreamImageDigest, refIsDigest := parseDigest(reference)
276+
277+
upstreamImageRef, err := getImageRef(utils.upstreamAddr, upstreamRepo, reference)
272278
if err != nil {
273279
log.Error().Str("errorType", TypeOf(err)).
274280
Err(err).Msgf("error creating docker reference for repository %s/%s:%s",
275-
utils.upstreamAddr, upstreamRepo, tag)
281+
utils.upstreamAddr, upstreamRepo, reference)
276282

277283
return false, err
278284
}
279285

280-
upstreamImageDigest, err := docker.GetDigest(context.Background(), utils.upstreamCtx, upstreamImageRef)
281-
if err != nil {
282-
log.Error().Str("errorType", TypeOf(err)).
283-
Err(err).Msgf("couldn't get upstream image %s manifest", upstreamImageRef.DockerReference())
286+
if !refIsDigest {
287+
upstreamImageDigest, err = docker.GetDigest(context.Background(), utils.upstreamCtx, upstreamImageRef)
288+
if err != nil {
289+
log.Error().Str("errorType", TypeOf(err)).
290+
Err(err).Msgf("couldn't get upstream image %s manifest", upstreamImageRef.DockerReference())
284291

285-
return false, err
292+
return false, err
293+
}
286294
}
287295

288296
// get upstream signatures
@@ -316,11 +324,11 @@ func syncRun(regCfg RegistryConfig,
316324
log.Error().Err(err).Msgf("couldn't get localCachePath for %s", localRepo)
317325
}
318326

319-
localImageRef, err := getLocalImageRef(localCachePath, localRepo, tag)
327+
localImageRef, err := getLocalImageRef(localCachePath, localRepo, reference)
320328
if err != nil {
321329
log.Error().Str("errorType", TypeOf(err)).
322330
Err(err).Msgf("couldn't obtain a valid image reference for reference %s/%s:%s",
323-
localCachePath, localRepo, tag)
331+
localCachePath, localRepo, reference)
324332

325333
return false, err
326334
}
@@ -338,32 +346,32 @@ func syncRun(regCfg RegistryConfig,
338346
return false, err
339347
}
340348

341-
err = pushSyncedLocalImage(localRepo, tag, localCachePath, imageStore, log)
349+
err = pushSyncedLocalImage(localRepo, reference, localCachePath, imageStore, log)
342350
if err != nil {
343351
log.Error().Str("errorType", TypeOf(err)).
344352
Err(err).Msgf("error while pushing synced cached image %s",
345-
fmt.Sprintf("%s/%s:%s", localCachePath, localRepo, tag))
353+
fmt.Sprintf("%s/%s:%s", localCachePath, localRepo, reference))
346354

347355
return false, err
348356
}
349357

350358
err = sig.syncCosignSignature(localRepo, upstreamRepo, upstreamImageDigest.String(), cosignManifest)
351359
if err != nil {
352360
log.Error().Str("errorType", TypeOf(err)).
353-
Err(err).Msgf("couldn't copy image cosign signature %s/%s:%s", utils.upstreamAddr, upstreamRepo, tag)
361+
Err(err).Msgf("couldn't copy image cosign signature %s/%s:%s", utils.upstreamAddr, upstreamRepo, reference)
354362

355363
return false, err
356364
}
357365

358366
err = sig.syncNotarySignature(localRepo, upstreamRepo, upstreamImageDigest.String(), refs)
359367
if err != nil {
360368
log.Error().Str("errorType", TypeOf(err)).
361-
Err(err).Msgf("couldn't copy image notary signature %s/%s:%s", utils.upstreamAddr, upstreamRepo, tag)
369+
Err(err).Msgf("couldn't copy image notary signature %s/%s:%s", utils.upstreamAddr, upstreamRepo, reference)
362370

363371
return false, err
364372
}
365373

366-
log.Info().Msgf("successfully synced %s/%s:%s", utils.upstreamAddr, upstreamRepo, tag)
374+
log.Info().Msgf("successfully synced %s/%s:%s", utils.upstreamAddr, upstreamRepo, reference)
367375

368376
return false, nil
369377
}

pkg/extensions/sync/sync.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,6 @@ func getCopyOptions(upstreamCtx, localCtx *types.SystemContext) copy.Options {
182182
SourceCtx: upstreamCtx,
183183
ReportWriter: io.Discard,
184184
ForceManifestMIMEType: ispec.MediaTypeImageManifest, // force only oci manifest MIME type
185-
PreserveDigests: true,
186185
ImageListSelection: copy.CopyAllImages,
187186
}
188187

pkg/extensions/sync/sync_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2865,6 +2865,56 @@ func TestOnDemandRetryGoroutine(t *testing.T) {
28652865
})
28662866
}
28672867

2868+
func TestOnDemandWithDigest(t *testing.T) {
2869+
Convey("Verify ondemand sync retries in background on error", t, func() {
2870+
_, srcBaseURL, _, _, _ := startUpstreamServer(t, false, false)
2871+
2872+
regex := ".*"
2873+
semver := true
2874+
var tlsVerify bool
2875+
2876+
syncRegistryConfig := sync.RegistryConfig{
2877+
Content: []sync.Content{
2878+
{
2879+
Prefix: testImage,
2880+
Tags: &sync.Tags{
2881+
Regex: &regex,
2882+
Semver: &semver,
2883+
},
2884+
},
2885+
},
2886+
URLs: []string{srcBaseURL},
2887+
OnDemand: true,
2888+
TLSVerify: &tlsVerify,
2889+
CertDir: "",
2890+
}
2891+
2892+
defaultVal := true
2893+
syncConfig := &sync.Config{
2894+
Enable: &defaultVal,
2895+
Registries: []sync.RegistryConfig{syncRegistryConfig},
2896+
}
2897+
2898+
dctlr, destBaseURL, destDir, destClient := startDownstreamServer(t, false, syncConfig)
2899+
defer os.RemoveAll(destDir)
2900+
2901+
defer func() {
2902+
dctlr.Shutdown()
2903+
}()
2904+
2905+
// get manifest digest from source
2906+
resp, err := destClient.R().Get(srcBaseURL + "/v2/" + testImage + "/manifests/" + testImageTag)
2907+
So(err, ShouldBeNil)
2908+
So(resp.StatusCode(), ShouldEqual, 200)
2909+
2910+
digest := godigest.FromBytes(resp.Body())
2911+
2912+
resp, err = destClient.R().Get(destBaseURL + "/v2/" + testImage + "/manifests/" + digest.String())
2913+
So(err, ShouldBeNil)
2914+
So(resp.StatusCode(), ShouldEqual, 200)
2915+
})
2916+
}
2917+
28682918
func TestOnDemandRetryGoroutineErr(t *testing.T) {
28692919
Convey("Verify ondemand sync retries in background on error", t, func() {
28702920
regex := ".*"

pkg/extensions/sync/utils.go

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -328,28 +328,28 @@ func getHTTPClient(regCfg *RegistryConfig, upstreamURL string, credentials Crede
328328
return client, registryURL, nil
329329
}
330330

331-
func pushSyncedLocalImage(localRepo, tag, localCachePath string,
331+
func pushSyncedLocalImage(localRepo, reference, localCachePath string,
332332
imageStore storage.ImageStore, log log.Logger,
333333
) error {
334-
log.Info().Msgf("pushing synced local image %s/%s:%s to local registry", localCachePath, localRepo, tag)
334+
log.Info().Msgf("pushing synced local image %s/%s:%s to local registry", localCachePath, localRepo, reference)
335335

336336
metrics := monitoring.NewMetricsServer(false, log)
337337

338338
cacheImageStore := local.NewImageStore(localCachePath, false,
339339
storage.DefaultGCDelay, false, false, log, metrics, nil)
340340

341-
manifestContent, _, mediaType, err := cacheImageStore.GetImageManifest(localRepo, tag)
341+
manifestContent, _, mediaType, err := cacheImageStore.GetImageManifest(localRepo, reference)
342342
if err != nil {
343343
log.Error().Str("errorType", TypeOf(err)).
344344
Err(err).Str("dir", path.Join(cacheImageStore.RootDir(), localRepo)).
345-
Msg("couldn't find index.json")
345+
Msgf("couldn't find %s manifest", reference)
346346

347347
return err
348348
}
349349

350350
// is image manifest
351351
if mediaType == ispec.MediaTypeImageManifest {
352-
if err := copyManifest(localRepo, manifestContent, tag, cacheImageStore, imageStore, log); err != nil {
352+
if err := copyManifest(localRepo, manifestContent, reference, cacheImageStore, imageStore, log); err != nil {
353353
if errors.Is(err, zerr.ErrImageLintAnnotations) {
354354
log.Error().Str("errorType", TypeOf(err)).
355355
Err(err).Msg("couldn't upload manifest because of missing annotations")
@@ -397,7 +397,7 @@ func pushSyncedLocalImage(localRepo, tag, localCachePath string,
397397
}
398398
}
399399

400-
_, err = imageStore.PutImageManifest(localRepo, tag, mediaType, manifestContent)
400+
_, err = imageStore.PutImageManifest(localRepo, reference, mediaType, manifestContent)
401401
if err != nil {
402402
log.Error().Str("errorType", TypeOf(err)).
403403
Err(err).Msg("couldn't upload manifest")
@@ -486,18 +486,28 @@ func StripRegistryTransport(url string) string {
486486
}
487487

488488
// get an ImageReference given the registry, repo and tag.
489-
func getImageRef(registryDomain, repo, tag string) (types.ImageReference, error) {
489+
func getImageRef(registryDomain, repo, ref string) (types.ImageReference, error) {
490490
repoRef, err := parseRepositoryReference(fmt.Sprintf("%s/%s", registryDomain, repo))
491491
if err != nil {
492492
return nil, err
493493
}
494494

495-
taggedRepoRef, err := reference.WithTag(repoRef, tag)
496-
if err != nil {
497-
return nil, err
495+
var namedRepoRef reference.Named
496+
497+
digest, ok := parseDigest(ref)
498+
if ok {
499+
namedRepoRef, err = reference.WithDigest(repoRef, digest)
500+
if err != nil {
501+
return nil, err
502+
}
503+
} else {
504+
namedRepoRef, err = reference.WithTag(repoRef, ref)
505+
if err != nil {
506+
return nil, err
507+
}
498508
}
499509

500-
imageRef, err := docker.NewReference(taggedRepoRef)
510+
imageRef, err := docker.NewReference(namedRepoRef)
501511
if err != nil {
502512
return nil, err
503513
}
@@ -506,15 +516,20 @@ func getImageRef(registryDomain, repo, tag string) (types.ImageReference, error)
506516
}
507517

508518
// get a local ImageReference used to temporary store one synced image.
509-
func getLocalImageRef(localCachePath, repo, tag string) (types.ImageReference, error) {
519+
func getLocalImageRef(localCachePath, repo, reference string) (types.ImageReference, error) {
510520
if _, err := os.ReadDir(localCachePath); err != nil {
511521
return nil, err
512522
}
513523

514524
localRepo := path.Join(localCachePath, repo)
515-
localTaggedRepo := fmt.Sprintf("%s:%s", localRepo, tag)
516525

517-
localImageRef, err := layout.ParseReference(localTaggedRepo)
526+
_, refIsDigest := parseDigest(reference)
527+
528+
if !refIsDigest {
529+
localRepo = fmt.Sprintf("%s:%s", localRepo, reference)
530+
}
531+
532+
localImageRef, err := layout.ParseReference(localRepo)
518533
if err != nil {
519534
return nil, err
520535
}
@@ -579,6 +594,18 @@ func canSkipImage(repo, tag string, digest godigest.Digest, imageStore storage.I
579594
return true, nil
580595
}
581596

597+
// parse a reference, return its digest and if it's valid.
598+
func parseDigest(reference string) (godigest.Digest, bool) {
599+
var ok bool
600+
601+
d, err := godigest.Parse(reference)
602+
if err == nil {
603+
ok = true
604+
}
605+
606+
return d, ok
607+
}
608+
582609
func manifestsEqual(manifest1, manifest2 ispec.Manifest) bool {
583610
if manifest1.Config.Digest == manifest2.Config.Digest &&
584611
manifest1.Config.MediaType == manifest2.Config.MediaType &&

0 commit comments

Comments
 (0)