diff --git a/errors/errors.go b/errors/errors.go index f46569ea2..86c87bced 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -173,4 +173,5 @@ var ( ErrInvalidSearchQuery = errors.New("invalid search query") ErrImageNotFound = errors.New("image not found") ErrAmbiguousInput = errors.New("input is not specific enough") + ErrPullByNonOCIDigest = errors.New("pull image with Non OCI digest") ) diff --git a/pkg/api/routes.go b/pkg/api/routes.go index 97774058f..a2b4bbfdc 100644 --- a/pkg/api/routes.go +++ b/pkg/api/routes.go @@ -2023,6 +2023,15 @@ func getImageManifest(ctx context.Context, routeHandler *RouteHandler, imgStore Msg("trying to get updated image by syncing on demand") if errSync := routeHandler.c.SyncOnDemand.SyncImage(ctx, name, reference); errSync != nil { + if errors.Is(errSync, zerr.ErrPullByNonOCIDigest) { + routeHandler.c.Log.Err(errSync).Str("repository", name).Str("reference", reference). + Msg("sync failed cause image is not OCI image, return data directly from remote") + details := zerr.GetDetails(errSync) + d, err := godigest.Parse(details["srcDigest"]) + if err == nil { + return []byte(details["srcManifest"]), d, details["srcMediaType"], err + } + } routeHandler.c.Log.Err(errSync).Str("repository", name).Str("reference", reference). Msg("failed to sync image") } diff --git a/pkg/extensions/sync/on_demand.go b/pkg/extensions/sync/on_demand.go index 02ef21be4..e3e612de1 100644 --- a/pkg/extensions/sync/on_demand.go +++ b/pkg/extensions/sync/on_demand.go @@ -122,7 +122,7 @@ func (onDemand *BaseOnDemand) syncImage(ctx context.Context, repo, reference str err = service.SyncImage(ctx, repo, reference) } - if err != nil || isPingErr { + if (err != nil || isPingErr) && !errors.Is(err, zerr.ErrPullByNonOCIDigest) { if errors.Is(err, zerr.ErrManifestNotFound) || errors.Is(err, zerr.ErrSyncImageFilteredOut) || errors.Is(err, zerr.ErrSyncImageNotSigned) { diff --git a/pkg/extensions/sync/remote.go b/pkg/extensions/sync/remote.go index fef205d42..344c0a71c 100644 --- a/pkg/extensions/sync/remote.go +++ b/pkg/extensions/sync/remote.go @@ -117,6 +117,24 @@ func (registry *RemoteRegistry) GetImageReference(repo, reference string) (types return imageRef, nil } +func (registry *RemoteRegistry) GetSrcManifestContent(imageReference types.ImageReference) ( + []byte, string, digest.Digest, error, +) { + imageSource, err := imageReference.NewImageSource(context.Background(), registry.GetContext()) + if err != nil { + return []byte{}, "", "", err + } + + defer imageSource.Close() + + manifestBuf, mediaType, err := imageSource.GetManifest(context.Background(), nil) + if err != nil { + return []byte{}, "", "", err + } + + return manifestBuf, mediaType, digest.FromBytes(manifestBuf), nil +} + func (registry *RemoteRegistry) GetManifestContent(imageReference types.ImageReference) ( []byte, string, digest.Digest, error, ) { diff --git a/pkg/extensions/sync/service.go b/pkg/extensions/sync/service.go index 4f1fca23d..27d759085 100644 --- a/pkg/extensions/sync/service.go +++ b/pkg/extensions/sync/service.go @@ -453,6 +453,34 @@ func (service *BaseService) syncTag(ctx context.Context, destinationRepo, remote } if !skipImage { + _, err = digest.Parse(tag) + if err == nil { + if tag != manifestDigest.String() { + // its a digest pull. we add details to err and let upper level handling this + buf, mt, md, err := service.remote.GetSrcManifestContent(remoteImageRef) + if err != nil { + service.log.Error().Err(err).Str("errortype", common.TypeOf(err)). + Str("remote", remoteImageRef.DockerReference().String()). + Str("reference", tag). + Msg("get manifest info from remote error") + return "", err + } + + if md.String() != tag { + return "", zerr.NewError(zerr.ErrManifestNotFound) + } + + return manifestDigest, zerr.NewError(zerr.ErrPullByNonOCIDigest).AddDetail( + "srcDigest", tag, + ).AddDetail( + "srcMediaType", mt, + ).AddDetail( + "srcManifest", string(buf), + ).AddDetail( + "srcRepo", remoteRepo, + ) + } + } localImageRef, err := service.destination.GetImageReference(destinationRepo, tag) if err != nil { service.log.Error().Err(err).Str("errortype", common.TypeOf(err)). diff --git a/pkg/extensions/sync/sync.go b/pkg/extensions/sync/sync.go index 1afd11172..da0e8e807 100644 --- a/pkg/extensions/sync/sync.go +++ b/pkg/extensions/sync/sync.go @@ -65,6 +65,8 @@ type Remote interface { GetRepoTags(repo string) ([]string, error) // Get manifest content, mediaType, digest given an ImageReference GetManifestContent(imageReference types.ImageReference) ([]byte, string, digest.Digest, error) + // Get manifest from remote without convert to OCI + GetSrcManifestContent(imageReference types.ImageReference) ([]byte, string, digest.Digest, error) // In the case of public dockerhub images 'library' namespace is added to the repo names of images // eg: alpine -> library/alpine GetDockerRemoteRepo(repo string) string