mirror of
https://github.com/project-zot/zot.git
synced 2026-06-17 21:17:58 +08:00
55b68228da
* feat(storage): redirect blob pulls to backend URLs * fix: rebase conflicts Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com> * refactor: rename redirect field Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com> * test: relax brittle TestPeriodicGC substore log assertion Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com> * feat(storage): improve blob redirect config handling and validation Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com> * fix(storage): address PR review feedback for blob redirect Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com> * feat(storage): apply latest PR review fixes for blob redirect Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com> * test: fix blob redirect and verify test regressions Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com> * fix(storage): enforce redirectBlobURL validation and add redirect tests Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com> * fix(storage): fix err113/noctx lint errors in storage driver tests - Replace httptest.NewRequest with httptest.NewRequestWithContext in s3, gcs, and imagestore driver tests (noctx) - Replace dynamic errors.New in s3 driver test with a package-level static sentinel error (err113) Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com> * test(storage): use temp dirs in imagestore redirect tests Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com> * fix: handle ranged blob redirects and add regression tests Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com> * fix: validate blob digest consistently in GetBlob Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com> * test: fix GetBlobPartialFn mock return values for range requests The test 'does not redirect ranged blob requests' was failing because the mock was returning incorrect length values. For a range request 'bytes=0-0' (1 byte), it was returning 4 bytes, which caused a length mismatch check in GetBlob to return HTTP 500. Fix the mock to dynamically calculate the correct length: to - from + 1 Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com> * fix(storage): preserve signed URL bytes in normalizeBlobRedirectURL Preserve the original URL bytes from backend storage drivers (important for signed/presigned URLs) while only lowercasing the scheme prefix. URL re-serialization via net/url can invalidate signatures through path escaping or canonicalization. Add regression tests covering signed URL query parameters and mixed-case scheme handling. Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com> * fix(storage): address PR review comments for blob redirect - Return signed redirect URLs unchanged; validate scheme/CRLF/host only, no URL normalization that would corrupt signed URL bytes - Add inline comments for all non-obvious decisions: range bypass, soft fallback on invalid URL, local driver empty return, subpath resolution, redirectBlobURL config constraint on local/empty driver - Expand TestNormalizeBlobRedirectURL to cover allowed schemes (http/https), parse failure, missing host, and CRLF injection cases - Add TestIsBlobRedirectEnabled covering subpath-only enablement with default store disabled Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com> * test(storage): address remaining blob redirect review comments Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com> * fix: gofumpt formatting in routes_test.go Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com> --------- Signed-off-by: Ramkumar Chinchani <rchincha.dev@gmail.com> Co-authored-by: Akash Kumar <meakash7902@gmail.com>
474 lines
13 KiB
Go
474 lines
13 KiB
Go
package mocks
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
|
|
godigest "github.com/opencontainers/go-digest"
|
|
ispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
|
|
"zotregistry.dev/zot/v2/pkg/scheduler"
|
|
storageTypes "zotregistry.dev/zot/v2/pkg/storage/types"
|
|
)
|
|
|
|
type MockedImageStore struct {
|
|
NameFn func() string
|
|
DirExistsFn func(d string) bool
|
|
RootDirFn func() string
|
|
InitRepoFn func(name string) error
|
|
ValidateRepoFn func(name string) (bool, error)
|
|
GetRepositoriesFn func() ([]string, error)
|
|
GetNextRepositoryFn func(processedRepos map[string]struct{}) (string, error)
|
|
GetNextRepositoriesFn func(lastRepo string, maxEntries int, fn storageTypes.FilterRepoFunc) ([]string, bool, error)
|
|
GetImageTagsFn func(repo string) ([]string, error)
|
|
GetImageManifestFn func(repo string, reference string) ([]byte, godigest.Digest, string, error)
|
|
PutImageManifestFn func(repo string, reference string, mediaType string, body []byte,
|
|
extraTags []string) (godigest.Digest, godigest.Digest, error)
|
|
DeleteImageManifestFn func(repo string, reference string, detectCollision bool) error
|
|
BlobUploadPathFn func(repo string, uuid string) string
|
|
StatBlobUploadFn func(repo string, uuid string) (bool, int64, time.Time, error)
|
|
ListBlobUploadsFn func(repo string) ([]string, error)
|
|
NewBlobUploadFn func(repo string) (string, error)
|
|
GetBlobUploadFn func(repo string, uuid string) (int64, error)
|
|
BlobUploadInfoFn func(repo string, uuid string) (int64, error)
|
|
PutBlobChunkStreamedFn func(repo string, uuid string, body io.Reader) (int64, error)
|
|
PutBlobChunkFn func(repo string, uuid string, from int64, to int64, body io.Reader) (int64, error)
|
|
FinishBlobUploadFn func(repo string, uuid string, body io.Reader, digest godigest.Digest) error
|
|
FullBlobUploadFn func(repo string, body io.Reader, digest godigest.Digest) (string, int64, error)
|
|
DedupeBlobFn func(src string, dstDigest godigest.Digest, dstRepo, dst string) error
|
|
DeleteBlobUploadFn func(repo string, uuid string) error
|
|
BlobPathFn func(repo string, digest godigest.Digest) string
|
|
CheckBlobFn func(repo string, digest godigest.Digest) (bool, int64, error)
|
|
StatBlobFn func(repo string, digest godigest.Digest) (bool, int64, time.Time, error)
|
|
GetBlobPartialFn func(repo string, digest godigest.Digest, mediaType string, from, to int64,
|
|
) (io.ReadCloser, int64, int64, error)
|
|
GetBlobFn func(repo string, digest godigest.Digest, mediaType string) (io.ReadCloser, int64, error)
|
|
DeleteBlobFn func(repo string, digest godigest.Digest) error
|
|
GetIndexContentFn func(repo string) ([]byte, error)
|
|
GetBlobContentFn func(repo string, digest godigest.Digest) ([]byte, error)
|
|
GetReferrersFn func(repo string, digest godigest.Digest, artifactTypes []string) (ispec.Index, error)
|
|
URLForPathFn func(path string) (string, error)
|
|
RunGCRepoFn func(repo string) error
|
|
RunGCPeriodicallyFn func(interval time.Duration, sch *scheduler.Scheduler)
|
|
RunDedupeBlobsFn func(interval time.Duration, sch *scheduler.Scheduler)
|
|
RunDedupeForDigestFn func(ctx context.Context, digest godigest.Digest, dedupe bool,
|
|
duplicateBlobs []string) error
|
|
GetNextDigestWithBlobPathsFn func(repos []string, lastDigests []godigest.Digest) (godigest.Digest, []string, error)
|
|
GetAllBlobsFn func(repo string) ([]godigest.Digest, error)
|
|
CleanupRepoFn func(repo string, blobs []godigest.Digest, removeRepo bool) (int, error)
|
|
PutIndexContentFn func(repo string, index ispec.Index) error
|
|
PopulateStorageMetricsFn func(interval time.Duration, sch *scheduler.Scheduler)
|
|
StatIndexFn func(repo string) (bool, int64, time.Time, error)
|
|
VerifyBlobDigestValueFn func(repo string, digest godigest.Digest) error
|
|
GetAllDedupeReposCandidatesFn func(digest godigest.Digest) ([]string, error)
|
|
GetBlobRedirectURLFn func(r *http.Request, repo string, digest godigest.Digest) (string, error)
|
|
}
|
|
|
|
func (is MockedImageStore) StatIndex(repo string) (bool, int64, time.Time, error) {
|
|
if is.StatIndexFn != nil {
|
|
return is.StatIndexFn(repo)
|
|
}
|
|
|
|
return true, 0, time.Time{}, nil
|
|
}
|
|
|
|
func (is MockedImageStore) Lock(t *time.Time) {
|
|
}
|
|
|
|
func (is MockedImageStore) Unlock(t *time.Time) {
|
|
}
|
|
|
|
func (is MockedImageStore) RUnlock(t *time.Time) {
|
|
}
|
|
|
|
func (is MockedImageStore) RLock(t *time.Time) {
|
|
}
|
|
|
|
func (is MockedImageStore) Name() string {
|
|
if is.NameFn != nil {
|
|
return is.NameFn()
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func (is MockedImageStore) DirExists(d string) bool {
|
|
if is.DirExistsFn != nil {
|
|
return is.DirExistsFn(d)
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (is MockedImageStore) RootDir() string {
|
|
if is.RootDirFn != nil {
|
|
return is.RootDirFn()
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func (is MockedImageStore) InitRepo(name string) error {
|
|
if is.InitRepoFn != nil {
|
|
return is.InitRepoFn(name)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (is MockedImageStore) ValidateRepo(name string) (bool, error) {
|
|
if is.ValidateRepoFn != nil {
|
|
return is.ValidateRepoFn(name)
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (is MockedImageStore) GetRepositories() ([]string, error) {
|
|
if is.GetRepositoriesFn != nil {
|
|
return is.GetRepositoriesFn()
|
|
}
|
|
|
|
return []string{}, nil
|
|
}
|
|
|
|
func (is MockedImageStore) GetNextRepository(processedRepos map[string]struct{}) (string, error) {
|
|
if is.GetNextRepositoryFn != nil {
|
|
return is.GetNextRepositoryFn(processedRepos)
|
|
}
|
|
|
|
return "", nil
|
|
}
|
|
|
|
func (is MockedImageStore) GetNextRepositories(lastRepo string, maxEntries int,
|
|
fn storageTypes.FilterRepoFunc,
|
|
) ([]string, bool, error) {
|
|
if is.GetNextRepositoriesFn != nil {
|
|
return is.GetNextRepositoriesFn(lastRepo, maxEntries, fn)
|
|
}
|
|
|
|
return []string{}, false, nil
|
|
}
|
|
|
|
func (is MockedImageStore) GetImageManifest(repo string, reference string) ([]byte, godigest.Digest, string, error) {
|
|
if is.GetImageManifestFn != nil {
|
|
return is.GetImageManifestFn(repo, reference)
|
|
}
|
|
|
|
return []byte{}, "", "", nil
|
|
}
|
|
|
|
func (is MockedImageStore) PutImageManifest(
|
|
repo string,
|
|
reference string,
|
|
mediaType string,
|
|
body []byte,
|
|
extraTags []string,
|
|
) (godigest.Digest, godigest.Digest, error) {
|
|
if is.PutImageManifestFn != nil {
|
|
return is.PutImageManifestFn(repo, reference, mediaType, body, extraTags)
|
|
}
|
|
|
|
return "", "", nil
|
|
}
|
|
|
|
func (is MockedImageStore) GetImageTags(name string) ([]string, error) {
|
|
if is.GetImageTagsFn != nil {
|
|
return is.GetImageTagsFn(name)
|
|
}
|
|
|
|
return []string{}, nil
|
|
}
|
|
|
|
func (is MockedImageStore) GetAllBlobs(repo string) ([]godigest.Digest, error) {
|
|
if is.GetAllBlobsFn != nil {
|
|
return is.GetAllBlobsFn(repo)
|
|
}
|
|
|
|
return []godigest.Digest{}, nil
|
|
}
|
|
|
|
func (is MockedImageStore) DeleteImageManifest(name string, reference string, detectCollision bool) error {
|
|
if is.DeleteImageManifestFn != nil {
|
|
return is.DeleteImageManifestFn(name, reference, detectCollision)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (is MockedImageStore) ListBlobUploads(repo string) ([]string, error) {
|
|
if is.ListBlobUploadsFn != nil {
|
|
return is.ListBlobUploadsFn(repo)
|
|
}
|
|
|
|
return []string{}, nil
|
|
}
|
|
|
|
func (is MockedImageStore) StatBlobUpload(repo string, uuid string) (bool, int64, time.Time, error) {
|
|
if is.StatBlobUploadFn != nil {
|
|
return is.StatBlobUploadFn(repo, uuid)
|
|
}
|
|
|
|
return true, 0, time.Time{}, nil
|
|
}
|
|
|
|
func (is MockedImageStore) NewBlobUpload(repo string) (string, error) {
|
|
if is.NewBlobUploadFn != nil {
|
|
return is.NewBlobUploadFn(repo)
|
|
}
|
|
|
|
return "", nil
|
|
}
|
|
|
|
func (is MockedImageStore) GetBlobUpload(repo string, uuid string) (int64, error) {
|
|
if is.GetBlobUploadFn != nil {
|
|
return is.GetBlobUploadFn(repo, uuid)
|
|
}
|
|
|
|
return 0, nil
|
|
}
|
|
|
|
func (is MockedImageStore) BlobUploadInfo(repo string, uuid string) (int64, error) {
|
|
if is.BlobUploadInfoFn != nil {
|
|
return is.BlobUploadInfoFn(repo, uuid)
|
|
}
|
|
|
|
return 0, nil
|
|
}
|
|
|
|
func (is MockedImageStore) BlobUploadPath(repo string, uuid string) string {
|
|
if is.BlobUploadPathFn != nil {
|
|
return is.BlobUploadPathFn(repo, uuid)
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func (is MockedImageStore) PutBlobChunkStreamed(repo string, uuid string, body io.Reader) (int64, error) {
|
|
if is.PutBlobChunkStreamedFn != nil {
|
|
return is.PutBlobChunkStreamedFn(repo, uuid, body)
|
|
}
|
|
|
|
return 0, nil
|
|
}
|
|
|
|
func (is MockedImageStore) PutBlobChunk(
|
|
repo string,
|
|
uuid string,
|
|
from int64,
|
|
to int64,
|
|
body io.Reader,
|
|
) (int64, error) {
|
|
if is.PutBlobChunkFn != nil {
|
|
return is.PutBlobChunkFn(repo, uuid, from, to, body)
|
|
}
|
|
|
|
return 0, nil
|
|
}
|
|
|
|
func (is MockedImageStore) FinishBlobUpload(repo string, uuid string, body io.Reader, digest godigest.Digest) error {
|
|
if is.FinishBlobUploadFn != nil {
|
|
return is.FinishBlobUploadFn(repo, uuid, body, digest)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (is MockedImageStore) FullBlobUpload(repo string, body io.Reader, digest godigest.Digest) (string, int64, error) {
|
|
if is.FullBlobUploadFn != nil {
|
|
return is.FullBlobUploadFn(repo, body, digest)
|
|
}
|
|
|
|
return "", 0, nil
|
|
}
|
|
|
|
func (is MockedImageStore) DedupeBlob(src string, dstDigest godigest.Digest, dstRepo, dst string) error {
|
|
if is.DedupeBlobFn != nil {
|
|
return is.DedupeBlobFn(src, dstDigest, dstRepo, dst)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (is MockedImageStore) DeleteBlob(repo string, digest godigest.Digest) error {
|
|
if is.DeleteBlobFn != nil {
|
|
return is.DeleteBlobFn(repo, digest)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (is MockedImageStore) BlobPath(repo string, digest godigest.Digest) string {
|
|
if is.BlobPathFn != nil {
|
|
return is.BlobPathFn(repo, digest)
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func (is MockedImageStore) CheckBlob(repo string, digest godigest.Digest) (bool, int64, error) {
|
|
if is.CheckBlobFn != nil {
|
|
return is.CheckBlobFn(repo, digest)
|
|
}
|
|
|
|
return true, 0, nil
|
|
}
|
|
|
|
func (is MockedImageStore) StatBlob(repo string, digest godigest.Digest) (bool, int64, time.Time, error) {
|
|
if is.StatBlobFn != nil {
|
|
return is.StatBlobFn(repo, digest)
|
|
}
|
|
|
|
return true, 0, time.Time{}, nil
|
|
}
|
|
|
|
func (is MockedImageStore) GetBlobPartial(repo string, digest godigest.Digest, mediaType string, from, to int64,
|
|
) (io.ReadCloser, int64, int64, error) {
|
|
if is.GetBlobPartialFn != nil {
|
|
return is.GetBlobPartialFn(repo, digest, mediaType, from, to)
|
|
}
|
|
|
|
return io.NopCloser(&io.LimitedReader{}), 0, 0, nil
|
|
}
|
|
|
|
func (is MockedImageStore) GetBlob(repo string, digest godigest.Digest, mediaType string,
|
|
) (io.ReadCloser, int64, error) {
|
|
if is.GetBlobFn != nil {
|
|
return is.GetBlobFn(repo, digest, mediaType)
|
|
}
|
|
|
|
return io.NopCloser(&io.LimitedReader{}), 0, nil
|
|
}
|
|
|
|
func (is MockedImageStore) GetBlobRedirectURL(
|
|
r *http.Request, repo string, digest godigest.Digest,
|
|
) (string, error) {
|
|
if is.GetBlobRedirectURLFn != nil {
|
|
return is.GetBlobRedirectURLFn(r, repo, digest)
|
|
}
|
|
|
|
return "", nil
|
|
}
|
|
|
|
func (is MockedImageStore) DeleteBlobUpload(repo string, uuid string) error {
|
|
if is.DeleteBlobUploadFn != nil {
|
|
return is.DeleteBlobUploadFn(repo, uuid)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (is MockedImageStore) GetIndexContent(repo string) ([]byte, error) {
|
|
if is.GetIndexContentFn != nil {
|
|
return is.GetIndexContentFn(repo)
|
|
}
|
|
|
|
return []byte{}, nil
|
|
}
|
|
|
|
func (is MockedImageStore) GetBlobContent(repo string, digest godigest.Digest) ([]byte, error) {
|
|
if is.GetBlobContentFn != nil {
|
|
return is.GetBlobContentFn(repo, digest)
|
|
}
|
|
|
|
return []byte{}, nil
|
|
}
|
|
|
|
func (is MockedImageStore) GetReferrers(
|
|
repo string, digest godigest.Digest,
|
|
artifactTypes []string,
|
|
) (ispec.Index, error) {
|
|
if is.GetReferrersFn != nil {
|
|
return is.GetReferrersFn(repo, digest, artifactTypes)
|
|
}
|
|
|
|
return ispec.Index{}, nil
|
|
}
|
|
|
|
func (is MockedImageStore) URLForPath(path string) (string, error) {
|
|
if is.URLForPathFn != nil {
|
|
return is.URLForPathFn(path)
|
|
}
|
|
|
|
return "", nil
|
|
}
|
|
|
|
func (is MockedImageStore) RunGCRepo(repo string) error {
|
|
if is.RunGCRepoFn != nil {
|
|
return is.RunGCRepoFn(repo)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (is MockedImageStore) RunGCPeriodically(interval time.Duration, sch *scheduler.Scheduler) {
|
|
if is.RunGCPeriodicallyFn != nil {
|
|
is.RunGCPeriodicallyFn(interval, sch)
|
|
}
|
|
}
|
|
|
|
func (is MockedImageStore) RunDedupeBlobs(interval time.Duration, sch *scheduler.Scheduler) {
|
|
if is.RunDedupeBlobsFn != nil {
|
|
is.RunDedupeBlobsFn(interval, sch)
|
|
}
|
|
}
|
|
|
|
func (is MockedImageStore) RunDedupeForDigest(ctx context.Context, digest godigest.Digest, dedupe bool,
|
|
duplicateBlobs []string,
|
|
) error {
|
|
if is.RunDedupeForDigestFn != nil {
|
|
return is.RunDedupeForDigestFn(ctx, digest, dedupe, duplicateBlobs)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (is MockedImageStore) GetNextDigestWithBlobPaths(repos []string, lastDigests []godigest.Digest,
|
|
) (godigest.Digest, []string, error) {
|
|
if is.GetNextDigestWithBlobPathsFn != nil {
|
|
return is.GetNextDigestWithBlobPathsFn(repos, lastDigests)
|
|
}
|
|
|
|
return "", []string{}, nil
|
|
}
|
|
|
|
func (is MockedImageStore) CleanupRepo(repo string, blobs []godigest.Digest, removeRepo bool) (int, error) {
|
|
if is.CleanupRepoFn != nil {
|
|
return is.CleanupRepoFn(repo, blobs, removeRepo)
|
|
}
|
|
|
|
return 0, nil
|
|
}
|
|
|
|
func (is MockedImageStore) PutIndexContent(repo string, index ispec.Index) error {
|
|
if is.PutIndexContentFn != nil {
|
|
return is.PutIndexContentFn(repo, index)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (is MockedImageStore) PopulateStorageMetrics(interval time.Duration, sch *scheduler.Scheduler) {
|
|
if is.PopulateStorageMetricsFn != nil {
|
|
is.PopulateStorageMetricsFn(interval, sch)
|
|
}
|
|
}
|
|
|
|
func (is MockedImageStore) VerifyBlobDigestValue(repo string, digest godigest.Digest) error {
|
|
if is.VerifyBlobDigestValueFn != nil {
|
|
return is.VerifyBlobDigestValueFn(repo, digest)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (is MockedImageStore) GetAllDedupeReposCandidates(digest godigest.Digest) ([]string, error) {
|
|
if is.GetAllBlobsFn != nil {
|
|
return is.GetAllDedupeReposCandidatesFn(digest)
|
|
}
|
|
|
|
return []string{}, nil
|
|
}
|