// Copyright (c) Tailscale Inc & AUTHORS // SPDX-License-Identifier: BSD-3-Clause // Package modproxy implements components of a Go module proxy that caches // files locally on disk, backed by objects in an S3 bucket. package modproxy import ( "bytes" "context" "crypto/sha256" "errors" "expvar" "fmt" "io" "io/fs" "os" "path" "path/filepath" "runtime" "sync" "time" "github.com/creachadair/atomicfile" "github.com/creachadair/taskgroup" "github.com/goproxy/goproxy" "github.com/tailscale/go-cache-plugin/lib/s3util" "golang.org/x/sync/semaphore" ) var _ goproxy.Cacher = (*S3Cacher)(nil) // S3Cacher implements the [github.com/goproxy/goproxy.Cacher] interface using // a local disk cache backed by an S3 bucket. // // # Cache Layout // // Module cache files are stored under a SHA256 digest of the filename // presented to the cache, encoded as hex and partitioned by the first two // bytes of the digest: // // For example: // // SHA256("fizzlepug") → 160db4d719252162c87a9169e26deda33d2340770d0d540fd4c580c55008b2d6 // /module/16/160db4d719252162c87a9169e26deda33d2340770d0d540fd4c580c55008b2d6 // // When files are stored in S3, the same naming convention is used, but with // the specified key prefix instead: // // /module/16/0db4d719252162c87a9169e26deda33d2340770d0d540fd4c580c55008b2d6 type S3Cacher struct { // Local is the path of a local cache directory where modules are cached. // It must be non-empty. Local string // S3Client is the S3 client used to read and write cache entries to the // backing store. It must be non-nil. S3Client *s3util.Client // KeyPrefix, if non-empty, is prepended to each key stored into S3, with an // intervening slash. KeyPrefix string // MaxTasks, if positive, limits the number of concurrent tasks that may be // interacting with S3. If zero or negative, the default is // [runtime.NumCPU]. MaxTasks int // Logf, if non-nil, is used to write log messages. If nil, logs are // discarded. Logf func(string, ...any) // LogRequests, if true, enables detailed (but noisy) debug logging of all // requests handled by the cache. Logs are written to Logf. // // Each result is presented in the format: // // B "" () // E "", err=,