Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/go-cache-plugin: add optional module proxy support #1

Merged
merged 2 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 82 additions & 16 deletions cmd/go-cache-plugin/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@ package main
import (
"context"
"errors"
"expvar"
"fmt"
"io"
"log"
"net"
"net/http"
"os"
"os/signal"
"path"
"path/filepath"
"strings"
"syscall"
"time"

Expand All @@ -18,7 +23,10 @@ import (
"github.com/creachadair/gocache"
"github.com/creachadair/gocache/cachedir"
"github.com/creachadair/taskgroup"
"github.com/goproxy/goproxy"
"github.com/tailscale/go-cache-plugin/s3cache"
"github.com/tailscale/go-cache-plugin/s3proxy"
"tailscale.com/tsweb"
)

var flags struct {
Expand All @@ -35,26 +43,26 @@ var flags struct {
DebugLog bool `flag:"debug,default=$GOCACHE_DEBUG,Enable detailed per-request debug logging (noisy)"`
}

func initCacheServer(env *command.Env) (*gocache.Server, error) {
func initCacheServer(env *command.Env) (*gocache.Server, *s3.Client, error) {
switch {
case flags.CacheDir == "":
return nil, env.Usagef("you must provide a --cache-dir")
return nil, nil, env.Usagef("you must provide a --cache-dir")
case flags.S3Bucket == "":
return nil, env.Usagef("you must provide an S3 --bucket name")
return nil, nil, env.Usagef("you must provide an S3 --bucket name")
}
region, err := getBucketRegion(env.Context(), flags.S3Bucket)
if err != nil {
return nil, env.Usagef("you must provide an S3 --region name")
return nil, nil, env.Usagef("you must provide an S3 --region name")
}

dir, err := cachedir.New(flags.CacheDir)
if err != nil {
return nil, fmt.Errorf("create local cache: %w", err)
return nil, nil, fmt.Errorf("create local cache: %w", err)
}

cfg, err := config.LoadDefaultConfig(env.Context(), config.WithRegion(region))
if err != nil {
return nil, fmt.Errorf("laod AWS config: %w", err)
return nil, nil, fmt.Errorf("laod AWS config: %w", err)
}

vprintf("local cache directory: %s", flags.CacheDir)
Expand All @@ -67,6 +75,8 @@ func initCacheServer(env *command.Env) (*gocache.Server, error) {
MinUploadSize: flags.MinUploadSize,
UploadConcurrency: flags.S3Concurrency,
}
cache.SetMetrics(env.Context(), expvar.NewMap("gocache_host"))

close := cache.Close
if flags.Expiration > 0 {
dirClose := dir.Cleanup(flags.Expiration)
Expand All @@ -83,13 +93,14 @@ func initCacheServer(env *command.Env) (*gocache.Server, error) {
Logf: vprintf,
LogRequests: flags.DebugLog,
}
return s, nil
expvar.Publish("gocache_server", s.Metrics().Get("server"))
return s, cache.S3Client, nil
}

// runDirect runs a cache communicating on stdin/stdout, for use as a direct
// GOCACHEPROG plugin.
func runDirect(env *command.Env) error {
s, err := initCacheServer(env)
s, _, err := initCacheServer(env)
if err != nil {
return err
}
Expand All @@ -102,33 +113,35 @@ func runDirect(env *command.Env) error {
return nil
}

var remoteFlags struct {
Socket string `flag:"socket,default=$GOCACHE_SOCKET,Socket path (required)"`
var serveFlags struct {
Socket string `flag:"socket,default=$GOCACHE_SOCKET,Socket path (required)"`
ModProxy string `flag:"modproxy,default=$GOCACHE_MODPROXY,Module proxy service address ([host]:port)"`
SumDB string `flag:"sumdb,default=$GOCACHE_SUMDB,SumDB servers to proxy for (comma-separated)"`
}

func noopClose(context.Context) error { return nil }

// runRemote runs a cache communicating over a Unix-domain socket.
func runRemote(env *command.Env) error {
if remoteFlags.Socket == "" {
// runServe runs a cache communicating over a Unix-domain socket.
func runServe(env *command.Env) error {
if serveFlags.Socket == "" {
return env.Usagef("you must provide a --socket path")
}

// Initialize the cache server. Unlike a direct server, only close down and
// wait for cache cleanup when the whole process exits.
s, err := initCacheServer(env)
s, s3c, err := initCacheServer(env)
if err != nil {
return err
}
closeHook := s.Close
s.Close = noopClose

// Listen for connections from the Go toolchain on the specified socket.
lst, err := net.Listen("unix", remoteFlags.Socket)
lst, err := net.Listen("unix", serveFlags.Socket)
if err != nil {
return fmt.Errorf("listen: %w", err)
}
defer os.Remove(remoteFlags.Socket) // best-effort
defer os.Remove(serveFlags.Socket) // best-effort

ctx, cancel := signal.NotifyContext(env.Context(), syscall.SIGINT, syscall.SIGTERM)
defer cancel()
Expand All @@ -138,7 +151,60 @@ func runRemote(env *command.Env) error {
lst.Close()
}()

// If a module proxy is enabled, start it.
var g taskgroup.Group
if serveFlags.ModProxy != "" {
modCachePath := filepath.Join(flags.CacheDir, "module")
if err := os.MkdirAll(modCachePath, 0700); err != nil {
lst.Close()
return fmt.Errorf("create module cache: %w", err)
}
cacher := &s3proxy.Cacher{
Local: modCachePath,
S3Client: s3c,
S3Bucket: flags.S3Bucket,
KeyPrefix: path.Join(flags.KeyPrefix, "module"),
MaxTasks: flags.S3Concurrency,
LogRequests: flags.DebugLog,
Logf: vprintf,
}
defer func() {
vprintf("close cacher (err=%v)", cacher.Close())
}()
proxy := &goproxy.Goproxy{
Fetcher: &goproxy.GoFetcher{
// As configured, the fetcher should never shell out to the go
// tool. Specifically, because we set GOPROXY and do not set any
// bypass via GONOPROXY, GOPRIVATE, etc., we will only attempt to
// proxy for the specific server(s) listed in Env.
GoBin: "/bin/false",
Env: []string{"GOPROXY=https://proxy.golang.org"},
},
Cacher: cacher,
}
if serveFlags.SumDB != "" {
proxy.ProxiedSumDBs = strings.Split(serveFlags.SumDB, ",")
vprintf("enabling sum DB proxy for %s", strings.Join(proxy.ProxiedSumDBs, ", "))
}
expvar.Publish("modcache", cacher.Metrics())

// Run an HTTP server exporting the proxy and debug metrics.
mux := http.NewServeMux()
mux.Handle("/", proxy)
tsweb.Debugger(mux)
srv := &http.Server{
Addr: serveFlags.ModProxy,
Handler: mux,
}
g.Go(srv.ListenAndServe)
vprintf("started module proxy at %q", serveFlags.ModProxy)
go func() {
<-ctx.Done()
vprintf("signal received, stopping module proxy")
srv.Shutdown(context.Background())
}()
}

for {
conn, err := lst.Accept()
if err != nil {
Expand Down
10 changes: 7 additions & 3 deletions cmd/go-cache-plugin/go-cache-plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,14 @@ the --cache-dir flag or GOCACHE_DIR environment.`,

In this mode, the cache server listens for connections on a socket instead of
serving directly over stdin/stdout. The "connect" command adapts the direct
interface to this one.`,
interface to this one.

SetFlags: command.Flags(flax.MustBind, &remoteFlags),
Run: command.Adapt(runRemote),
By default, only the build cache is exported via the --socket path.
If --modcache is set, the server also exports a caching module proxy at the
specified address.`,

SetFlags: command.Flags(flax.MustBind, &serveFlags),
Run: command.Adapt(runServe),
},
{
Name: "connect",
Expand Down
29 changes: 22 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@ toolchain go1.23.0
require (
github.com/aws/aws-sdk-go-v2/config v1.27.28
github.com/aws/aws-sdk-go-v2/service/s3 v1.59.0
github.com/creachadair/atomicfile v0.3.4
github.com/creachadair/command v0.1.13
github.com/creachadair/flax v0.0.1
github.com/creachadair/gocache v0.0.0-20240822140526-6ffd1bf075ac
github.com/creachadair/gocache v0.0.0-20240823022559-6258109e93d6
github.com/creachadair/mds v0.17.1
github.com/creachadair/taskgroup v0.9.1
github.com/goproxy/goproxy v0.17.2
golang.org/x/sync v0.7.0
honnef.co/go/tools v0.5.1
tailscale.com v1.72.1
)

require (
Expand All @@ -32,10 +37,20 @@ require (
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.5 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.30.4 // indirect
github.com/aws/smithy-go v1.20.4 // indirect
github.com/creachadair/atomicfile v0.3.4 // indirect
github.com/creachadair/mds v0.17.1 // indirect
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/tools v0.21.1-0.20240531212143-b6235391adb3 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/prometheus/client_golang v1.19.1 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.48.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
go4.org/mem v0.0.0-20220726221520-4f986261bf13 // indirect
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
golang.org/x/crypto v0.25.0 // indirect
golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a // indirect
golang.org/x/mod v0.19.0 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/tools v0.23.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
)
58 changes: 50 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -36,29 +36,71 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.30.4 h1:iAckBT2OeEK/kBDyN/jDtpEExhje
github.com/aws/aws-sdk-go-v2/service/sts v1.30.4/go.mod h1:vmSqFK+BVIwVpDAGZB3CoCXHzurt4qBE8lf+I/kRTh0=
github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4=
github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/creachadair/atomicfile v0.3.4 h1:AjNK7To+S1p+nk7uJXJMZFpcV9XHOyAaULyDeU6LEqM=
github.com/creachadair/atomicfile v0.3.4/go.mod h1:ByEUbfQyms+tRtE7Wk7WdS6PZeyMzfSFlNX1VoKEh6E=
github.com/creachadair/command v0.1.13 h1:UDKPF3QYPRS/quZPVYZ7sW1JLxLLOgiyVSLQ+7wwI2o=
github.com/creachadair/command v0.1.13/go.mod h1:YKwUE49nAi8qxLl8jCQ0GMPvwdxmIBkJW3LqxgZ7ljk=
github.com/creachadair/flax v0.0.1 h1:it+joEf9aEUalaV7XWll/pk6zA4/FbNvMImW9q/lS8o=
github.com/creachadair/flax v0.0.1/go.mod h1:K8bFvn8hMdAljQkaKNc7I3os5Wk36JxkyCkfdZ7S8d4=
github.com/creachadair/gocache v0.0.0-20240822140526-6ffd1bf075ac h1:Q+WacVFlOTpCN5EQcPQ06VvYxeuZM3usF34uvyLcUJU=
github.com/creachadair/gocache v0.0.0-20240822140526-6ffd1bf075ac/go.mod h1:iqnJUdWeHbNfn8xWEkLWGQnOo3dqjkZPH9Rk0rjVk0U=
github.com/creachadair/gocache v0.0.0-20240823022559-6258109e93d6 h1:cLdDDdt2WR4ogKxQDOTNIFZxw5ub15tAbMNWrr0zM5M=
github.com/creachadair/gocache v0.0.0-20240823022559-6258109e93d6/go.mod h1:iqnJUdWeHbNfn8xWEkLWGQnOo3dqjkZPH9Rk0rjVk0U=
github.com/creachadair/mds v0.17.1 h1:lXQbTGKmb3nE3aK6OEp29L1gCx6B5ynzlQ6c1KOBurc=
github.com/creachadair/mds v0.17.1/go.mod h1:4b//mUiL8YldH6TImXjmW45myzTLNS1LLjOmrk888eg=
github.com/creachadair/taskgroup v0.9.1 h1:oam4POtt6PpmUr4us+ycUUfb2mPWc0RmIycte2oWoWw=
github.com/creachadair/taskgroup v0.9.1/go.mod h1:9oDDPt/5QPS4iylvPMC81GRlj+1je8AFDbjUh4zaQWo=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0 h1:ymLjT4f35nQbASLnvxEde4XOBL+Sn7rFuV+FOJqkljg=
github.com/go-json-experiment/json v0.0.0-20231102232822-2e55bd4e08b0/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 h1:1P7xPZEwZMoBoz0Yze5Nx2/4pxj6nw9ZqHWXqP0iRgQ=
golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/goproxy/goproxy v0.17.2 h1:6Kzz8V6RyeITfiHY5oGw9tPEOMIamTOzJWoWPi0/Hbs=
github.com/goproxy/goproxy v0.17.2/go.mod h1:IxJ9/TarFFk0NBAWdkwvYqxb6zbk+JdA17nfBIHtblI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
go4.org/mem v0.0.0-20220726221520-4f986261bf13 h1:CbZeCBZ0aZj8EfVgnqQcYZgf0lpZ3H9rmp5nkDTAst8=
go4.org/mem v0.0.0-20220726221520-4f986261bf13/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M=
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=
golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08=
golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a h1:8qmSSA8Gz/1kTrCe0nqR0R3Gb/NDhykzWw2q2mWZydM=
golang.org/x/exp/typeparams v0.0.0-20240119083558-1b970713d09a/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/tools v0.21.1-0.20240531212143-b6235391adb3 h1:SHq4Rl+B7WvyM4XODon1LXtP7gcG49+7Jubt1gWWswY=
golang.org/x/tools v0.21.1-0.20240531212143-b6235391adb3/go.mod h1:bqv7PJ/TtlrzgJKhOAGdDUkUltQapRik/UEHubLVBWo=
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I=
honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs=
tailscale.com v1.72.1 h1:hk82jek36ph2S3Tfsh57NVWKEm/pZ9nfUonvlowpfaA=
tailscale.com v1.72.1/go.mod h1:v7OHtg0KLAnhOVf81Z8WrjNefj238QbFhgkWJQoKxbs=
Loading
Loading