diff --git a/cmd/crproxy/main.go b/cmd/crproxy/main.go index 3c914db..bb04d67 100644 --- a/cmd/crproxy/main.go +++ b/cmd/crproxy/main.go @@ -74,6 +74,8 @@ var ( readmeURL string allowHeadMethod bool + + manifestCacheDuration time.Duration ) func init() { @@ -117,6 +119,8 @@ func init() { pflag.StringVar(&readmeURL, "readme-url", "", "redirect readme url when not found") pflag.BoolVar(&allowHeadMethod, "allow-head-method", false, "allow head method") + + pflag.DurationVar(&manifestCacheDuration, "manifest-cache-duration", 0, "manifest cache duration") pflag.Parse() } @@ -447,6 +451,10 @@ func main() { opts = append(opts, crproxy.WithAllowHeadMethod(allowHeadMethod)) } + if manifestCacheDuration != 0 { + opts = append(opts, crproxy.WithManifestCacheDuration(manifestCacheDuration)) + } + crp, err := crproxy.NewCRProxy(opts...) if err != nil { logger.Println("failed to NewCRProxy:", err) diff --git a/crproxy.go b/crproxy.go index 3459ff1..af1e48a 100644 --- a/crproxy.go +++ b/crproxy.go @@ -83,10 +83,19 @@ type CRProxy struct { privilegedFunc func(r *http.Request, info *ImageInfo) bool redirectToOriginBlobFunc func(r *http.Request, info *ImageInfo) bool allowHeadMethod bool + + manifestCache maps.SyncMap[string, time.Time] + manifestCacheDuration time.Duration } type Option func(c *CRProxy) +func WithManifestCacheDuration(d time.Duration) Option { + return func(c *CRProxy) { + c.manifestCacheDuration = d + } +} + func WithPrivilegedFunc(f func(r *http.Request, info *ImageInfo) bool) Option { return func(c *CRProxy) { c.privilegedFunc = f diff --git a/crproxy_manifest.go b/crproxy_manifest.go index c2e06a0..6fc5802 100644 --- a/crproxy_manifest.go +++ b/crproxy_manifest.go @@ -12,6 +12,7 @@ import ( "path" "strconv" "strings" + "time" "github.com/distribution/distribution/v3/registry/api/errcode" ) @@ -25,10 +26,14 @@ func manifestTagCachePath(host, image, tagOrBlob string) string { } func (c *CRProxy) cacheManifestResponse(rw http.ResponseWriter, r *http.Request, info *PathInfo) { + if c.cachedManifest(rw, r, info, true) { + return + } + cli := c.getClientset(info.Host, info.Image) resp, err := c.doWithAuth(cli, r, info.Host) if err != nil { - if c.cachedManifest(rw, r, info) { + if c.cachedManifest(rw, r, info, false) { return } if c.logger != nil { @@ -43,7 +48,7 @@ func (c *CRProxy) cacheManifestResponse(rw http.ResponseWriter, r *http.Request, switch resp.StatusCode { case http.StatusUnauthorized, http.StatusForbidden: - if c.cachedManifest(rw, r, info) { + if c.cachedManifest(rw, r, info, false) { return } errcode.ServeJSON(rw, errcode.ErrorCodeDenied) @@ -51,7 +56,7 @@ func (c *CRProxy) cacheManifestResponse(rw http.ResponseWriter, r *http.Request, } if resp.StatusCode >= http.StatusInternalServerError { - if c.cachedManifest(rw, r, info) { + if c.cachedManifest(rw, r, info, false) { return } } @@ -112,10 +117,17 @@ func (c *CRProxy) cacheManifestContent(ctx context.Context, info *PathInfo, cont return err } + if c.manifestCacheDuration > 0 { + c.manifestCache.Store(manifestLinkPath, time.Now()) + } return nil } -func (c *CRProxy) cachedManifest(rw http.ResponseWriter, r *http.Request, info *PathInfo) bool { +func (c *CRProxy) cachedManifest(rw http.ResponseWriter, r *http.Request, info *PathInfo, try bool) bool { + if try && c.manifestCacheDuration == 0 { + return false + } + ctx := r.Context() var manifestLinkPath string if strings.HasPrefix(info.Manifests, "sha256:") { @@ -124,6 +136,17 @@ func (c *CRProxy) cachedManifest(rw http.ResponseWriter, r *http.Request, info * manifestLinkPath = manifestTagCachePath(info.Host, info.Image, info.Manifests) } + if try { + last, ok := c.manifestCache.Load(manifestLinkPath) + if !ok { + return false + } + + if time.Since(last) > c.manifestCacheDuration { + return false + } + } + content, err := c.storageDriver.GetContent(ctx, manifestLinkPath) if err == nil { digest := string(content)