forked from TrueCloudLab/distribution
1d33874951
Go 1.13 and up enforce import paths to be versioned if a project contains a go.mod and has released v2 or up. The current v2.x branches (and releases) do not yet have a go.mod, and therefore are still allowed to be imported with a non-versioned import path (go modules add a `+incompatible` annotation in that case). However, now that this project has a `go.mod` file, incompatible import paths will not be accepted by go modules, and attempting to use code from this repository will fail. This patch uses `v3` for the import-paths (not `v2`), because changing import paths itself is a breaking change, which means that the next release should increment the "major" version to comply with SemVer (as go modules dictate). Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
78 lines
2.1 KiB
Go
78 lines
2.1 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/distribution/distribution/v3"
|
|
"github.com/distribution/distribution/v3/registry/storage/driver"
|
|
"github.com/opencontainers/go-digest"
|
|
)
|
|
|
|
// TODO(stevvooe): This should configurable in the future.
|
|
const blobCacheControlMaxAge = 365 * 24 * time.Hour
|
|
|
|
// blobServer simply serves blobs from a driver instance using a path function
|
|
// to identify paths and a descriptor service to fill in metadata.
|
|
type blobServer struct {
|
|
driver driver.StorageDriver
|
|
statter distribution.BlobStatter
|
|
pathFn func(dgst digest.Digest) (string, error)
|
|
redirect bool // allows disabling URLFor redirects
|
|
}
|
|
|
|
func (bs *blobServer) ServeBlob(ctx context.Context, w http.ResponseWriter, r *http.Request, dgst digest.Digest) error {
|
|
desc, err := bs.statter.Stat(ctx, dgst)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
path, err := bs.pathFn(desc.Digest)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if bs.redirect {
|
|
redirectURL, err := bs.driver.URLFor(ctx, path, map[string]interface{}{"method": r.Method})
|
|
switch err.(type) {
|
|
case nil:
|
|
// Redirect to storage URL.
|
|
http.Redirect(w, r, redirectURL, http.StatusTemporaryRedirect)
|
|
return err
|
|
|
|
case driver.ErrUnsupportedMethod:
|
|
// Fallback to serving the content directly.
|
|
default:
|
|
// Some unexpected error.
|
|
return err
|
|
}
|
|
}
|
|
|
|
br, err := newFileReader(ctx, bs.driver, path, desc.Size)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer br.Close()
|
|
|
|
w.Header().Set("ETag", fmt.Sprintf(`"%s"`, desc.Digest)) // If-None-Match handled by ServeContent
|
|
w.Header().Set("Cache-Control", fmt.Sprintf("max-age=%.f", blobCacheControlMaxAge.Seconds()))
|
|
|
|
if w.Header().Get("Docker-Content-Digest") == "" {
|
|
w.Header().Set("Docker-Content-Digest", desc.Digest.String())
|
|
}
|
|
|
|
if w.Header().Get("Content-Type") == "" {
|
|
// Set the content type if not already set.
|
|
w.Header().Set("Content-Type", desc.MediaType)
|
|
}
|
|
|
|
if w.Header().Get("Content-Length") == "" {
|
|
// Set the content length if not already set.
|
|
w.Header().Set("Content-Length", fmt.Sprint(desc.Size))
|
|
}
|
|
|
|
http.ServeContent(w, r, desc.Digest.String(), time.Time{}, br)
|
|
return nil
|
|
}
|