Skip to content

Commit

Permalink
feat: redirect to download urls of blobs
Browse files Browse the repository at this point in the history
Signed-off-by: Lukas Hoehl <[email protected]>
  • Loading branch information
hown3d committed Feb 14, 2025
1 parent 88efa50 commit f593526
Show file tree
Hide file tree
Showing 8 changed files with 67 additions and 11 deletions.
1 change: 1 addition & 0 deletions errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ var (
ErrBadBlob = errors.New("bad blob")
ErrBadBlobDigest = errors.New("bad blob digest")
ErrBlobReferenced = errors.New("blob referenced by manifest")
ErrBlobRedirectURLNotSupported = errors.New("blob redirect url not supported")
ErrManifestReferenced = errors.New("manifest referenced by index image")
ErrUnknownCode = errors.New("unknown error code")
ErrBadCACert = errors.New("invalid tls ca cert")
Expand Down
21 changes: 11 additions & 10 deletions pkg/api/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,17 @@ var (
)

type StorageConfig struct {
RootDirectory string
Dedupe bool
RemoteCache bool
GC bool
Commit bool
GCDelay time.Duration // applied for blobs
GCInterval time.Duration
Retention ImageRetention
StorageDriver map[string]interface{} `mapstructure:",omitempty"`
CacheDriver map[string]interface{} `mapstructure:",omitempty"`
RootDirectory string
Dedupe bool
RemoteCache bool
GC bool
Commit bool
GCDelay time.Duration // applied for blobs
GCInterval time.Duration
Retention ImageRetention
StorageDriver map[string]interface{} `mapstructure:",omitempty"`
CacheDriver map[string]interface{} `mapstructure:",omitempty"`
DisableRedirect bool
}

type ImageRetention struct {
Expand Down
14 changes: 13 additions & 1 deletion pkg/api/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -1080,6 +1080,8 @@ func (rh *RouteHandler) GetBlob(response http.ResponseWriter, request *http.Requ

mediaType := request.Header.Get("Accept")

redirect := !rh.c.Config.Storage.StorageConfig.DisableRedirect

/* content range is supported for resumbale pulls */
partial := false

Expand Down Expand Up @@ -1108,11 +1110,16 @@ func (rh *RouteHandler) GetBlob(response http.ResponseWriter, request *http.Requ
}

var repo io.ReadCloser

var redirectURL string
var blen, bsize int64

if partial {
repo, blen, bsize, err = imgStore.GetBlobPartial(name, digest, mediaType, from, to)
} else if redirect {
redirectURL, err = imgStore.GetBlobURL(request, name, digest, mediaType)
if errors.Is(err, zerr.ErrBlobRedirectURLNotSupported) {
repo, blen, err = imgStore.GetBlob(name, digest, mediaType)
}
} else {
repo, blen, err = imgStore.GetBlob(name, digest, mediaType)
}
Expand All @@ -1139,6 +1146,11 @@ func (rh *RouteHandler) GetBlob(response http.ResponseWriter, request *http.Requ
return
}

if redirectURL != "" {
http.Redirect(response, request, redirectURL, http.StatusTemporaryRedirect)
return
}

defer repo.Close()

response.Header().Set("Content-Length", strconv.FormatInt(blen, 10))
Expand Down
18 changes: 18 additions & 0 deletions pkg/storage/imagestore/imagestore.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"io"
"net/http"
"path"
"path/filepath"
"strings"
Expand Down Expand Up @@ -1535,6 +1536,23 @@ func (is *ImageStore) GetBlob(repo string, digest godigest.Digest, mediaType str
return blobReadCloser, binfo.Size(), nil
}

func (is *ImageStore) GetBlobURL(request *http.Request, repo string, digest godigest.Digest, mediaType string) (string, error) {
var lockLatency time.Time

if err := digest.Validate(); err != nil {
return "", err
}

is.RLock(&lockLatency)
defer is.RUnlock(&lockLatency)

binfo, err := is.originalBlobInfo(repo, digest)
if err != nil {
return "", err
}
return is.storeDriver.URLFor(request, binfo.Path())
}

// GetBlobContent returns blob contents, the caller function MUST lock from outside.
// Should be used for small files(manifests/config blobs).
func (is *ImageStore) GetBlobContent(repo string, digest godigest.Digest) ([]byte, error) {
Expand Down
5 changes: 5 additions & 0 deletions pkg/storage/local/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"context"
"errors"
"io"
"net/http"
"os"
"path"
"sort"
Expand Down Expand Up @@ -58,6 +59,10 @@ func (driver *Driver) DirExists(path string) bool {
return true
}

func (driver *Driver) URLFor(_ *http.Request, _ string) (string, error) {
return "", zerr.ErrBlobRedirectURLNotSupported
}

func (driver *Driver) Reader(path string, offset int64) (io.ReadCloser, error) {
file, err := os.OpenFile(path, os.O_RDONLY, storageConstants.DefaultFilePerms)
if err != nil {
Expand Down
5 changes: 5 additions & 0 deletions pkg/storage/s3/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package s3
import (
"context"
"io"
"net/http"

// Add s3 support.
"github.com/distribution/distribution/v3/registry/storage/driver"
Expand Down Expand Up @@ -35,6 +36,10 @@ func (driver *Driver) DirExists(path string) bool {
return false
}

func (driver *Driver) URLFor(r *http.Request, path string) (string, error) {
return driver.store.RedirectURL(r, path)
}

func (driver *Driver) Reader(path string, offset int64) (io.ReadCloser, error) {
return driver.store.Reader(context.Background(), path, offset)
}
Expand Down
3 changes: 3 additions & 0 deletions pkg/storage/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package types
import (
"context"
"io"
"net/http"
"time"

storagedriver "github.com/distribution/distribution/v3/registry/storage/driver"
Expand Down Expand Up @@ -53,6 +54,7 @@ type ImageStore interface { //nolint:interfacebloat
CheckBlob(repo string, digest godigest.Digest) (bool, int64, error)
StatBlob(repo string, digest godigest.Digest) (bool, int64, time.Time, error)
GetBlob(repo string, digest godigest.Digest, mediaType string) (io.ReadCloser, int64, error)
GetBlobURL(r *http.Request, repo string, digest godigest.Digest, mediaType string) (string, error)
GetBlobPartial(repo string, digest godigest.Digest, mediaType string, from, to int64,
) (io.ReadCloser, int64, int64, error)
DeleteBlob(repo string, digest godigest.Digest) error
Expand All @@ -75,6 +77,7 @@ type Driver interface { //nolint:interfacebloat
Name() string
EnsureDir(path string) error
DirExists(path string) bool
URLFor(request *http.Request, path string) (string, error)
Reader(path string, offset int64) (io.ReadCloser, error)
ReadFile(path string) ([]byte, error)
Delete(path string) error
Expand Down
11 changes: 11 additions & 0 deletions pkg/test/mocks/image_store_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package mocks
import (
"context"
"io"
"net/http"
"time"

godigest "github.com/opencontainers/go-digest"
Expand Down Expand Up @@ -44,6 +45,7 @@ type MockedImageStore struct {
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)
GetBlobURLFn func(request *http.Request, repo string, digest godigest.Digest, mediaType string) (string, error)
DeleteBlobFn func(repo string, digest godigest.Digest) error
GetIndexContentFn func(repo string) ([]byte, error)
GetBlobContentFn func(repo string, digest godigest.Digest) ([]byte, error)
Expand Down Expand Up @@ -339,6 +341,15 @@ func (is MockedImageStore) GetBlob(repo string, digest godigest.Digest, mediaTyp
return io.NopCloser(&io.LimitedReader{}), 0, nil
}

func (is MockedImageStore) GetBlobURL(request *http.Request, repo string, digest godigest.Digest, mediaType string,
) (string, error) {
if is.GetBlobURLFn != nil {
return is.GetBlobURLFn(request, repo, digest, mediaType)
}

return "", nil
}

func (is MockedImageStore) DeleteBlobUpload(repo string, uuid string) error {
if is.DeleteBlobUploadFn != nil {
return is.DeleteBlobUploadFn(repo, uuid)
Expand Down

0 comments on commit f593526

Please sign in to comment.