@@ -3,12 +3,17 @@ package main
3
3
import (
4
4
"context"
5
5
"errors"
6
+ "expvar"
6
7
"fmt"
7
8
"io"
8
9
"log"
9
10
"net"
11
+ "net/http"
10
12
"os"
11
13
"os/signal"
14
+ "path"
15
+ "path/filepath"
16
+ "strings"
12
17
"syscall"
13
18
"time"
14
19
@@ -18,7 +23,10 @@ import (
18
23
"github.com/creachadair/gocache"
19
24
"github.com/creachadair/gocache/cachedir"
20
25
"github.com/creachadair/taskgroup"
26
+ "github.com/goproxy/goproxy"
21
27
"github.com/tailscale/go-cache-plugin/s3cache"
28
+ "github.com/tailscale/go-cache-plugin/s3proxy"
29
+ "tailscale.com/tsweb"
22
30
)
23
31
24
32
var flags struct {
@@ -35,26 +43,26 @@ var flags struct {
35
43
DebugLog bool `flag:"debug,default=$GOCACHE_DEBUG,Enable detailed per-request debug logging (noisy)"`
36
44
}
37
45
38
- func initCacheServer (env * command.Env ) (* gocache.Server , error ) {
46
+ func initCacheServer (env * command.Env ) (* gocache.Server , * s3. Client , error ) {
39
47
switch {
40
48
case flags .CacheDir == "" :
41
- return nil , env .Usagef ("you must provide a --cache-dir" )
49
+ return nil , nil , env .Usagef ("you must provide a --cache-dir" )
42
50
case flags .S3Bucket == "" :
43
- return nil , env .Usagef ("you must provide an S3 --bucket name" )
51
+ return nil , nil , env .Usagef ("you must provide an S3 --bucket name" )
44
52
}
45
53
region , err := getBucketRegion (env .Context (), flags .S3Bucket )
46
54
if err != nil {
47
- return nil , env .Usagef ("you must provide an S3 --region name" )
55
+ return nil , nil , env .Usagef ("you must provide an S3 --region name" )
48
56
}
49
57
50
58
dir , err := cachedir .New (flags .CacheDir )
51
59
if err != nil {
52
- return nil , fmt .Errorf ("create local cache: %w" , err )
60
+ return nil , nil , fmt .Errorf ("create local cache: %w" , err )
53
61
}
54
62
55
63
cfg , err := config .LoadDefaultConfig (env .Context (), config .WithRegion (region ))
56
64
if err != nil {
57
- return nil , fmt .Errorf ("laod AWS config: %w" , err )
65
+ return nil , nil , fmt .Errorf ("laod AWS config: %w" , err )
58
66
}
59
67
60
68
vprintf ("local cache directory: %s" , flags .CacheDir )
@@ -67,6 +75,8 @@ func initCacheServer(env *command.Env) (*gocache.Server, error) {
67
75
MinUploadSize : flags .MinUploadSize ,
68
76
UploadConcurrency : flags .S3Concurrency ,
69
77
}
78
+ cache .SetMetrics (env .Context (), expvar .NewMap ("gocache_host" ))
79
+
70
80
close := cache .Close
71
81
if flags .Expiration > 0 {
72
82
dirClose := dir .Cleanup (flags .Expiration )
@@ -83,13 +93,14 @@ func initCacheServer(env *command.Env) (*gocache.Server, error) {
83
93
Logf : vprintf ,
84
94
LogRequests : flags .DebugLog ,
85
95
}
86
- return s , nil
96
+ expvar .Publish ("gocache_server" , s .Metrics ().Get ("server" ))
97
+ return s , cache .S3Client , nil
87
98
}
88
99
89
100
// runDirect runs a cache communicating on stdin/stdout, for use as a direct
90
101
// GOCACHEPROG plugin.
91
102
func runDirect (env * command.Env ) error {
92
- s , err := initCacheServer (env )
103
+ s , _ , err := initCacheServer (env )
93
104
if err != nil {
94
105
return err
95
106
}
@@ -102,33 +113,35 @@ func runDirect(env *command.Env) error {
102
113
return nil
103
114
}
104
115
105
- var remoteFlags struct {
106
- Socket string `flag:"socket,default=$GOCACHE_SOCKET,Socket path (required)"`
116
+ var serveFlags struct {
117
+ Socket string `flag:"socket,default=$GOCACHE_SOCKET,Socket path (required)"`
118
+ ModProxy string `flag:"modproxy,default=$GOCACHE_MODPROXY,Module proxy service address ([host]:port)"`
119
+ SumDB string `flag:"sumdb,default=$GOCACHE_SUMDB,SumDB servers to proxy for (comma-separated)"`
107
120
}
108
121
109
122
func noopClose (context.Context ) error { return nil }
110
123
111
- // runRemote runs a cache communicating over a Unix-domain socket.
112
- func runRemote (env * command.Env ) error {
113
- if remoteFlags .Socket == "" {
124
+ // runServe runs a cache communicating over a Unix-domain socket.
125
+ func runServe (env * command.Env ) error {
126
+ if serveFlags .Socket == "" {
114
127
return env .Usagef ("you must provide a --socket path" )
115
128
}
116
129
117
130
// Initialize the cache server. Unlike a direct server, only close down and
118
131
// wait for cache cleanup when the whole process exits.
119
- s , err := initCacheServer (env )
132
+ s , s3c , err := initCacheServer (env )
120
133
if err != nil {
121
134
return err
122
135
}
123
136
closeHook := s .Close
124
137
s .Close = noopClose
125
138
126
139
// Listen for connections from the Go toolchain on the specified socket.
127
- lst , err := net .Listen ("unix" , remoteFlags .Socket )
140
+ lst , err := net .Listen ("unix" , serveFlags .Socket )
128
141
if err != nil {
129
142
return fmt .Errorf ("listen: %w" , err )
130
143
}
131
- defer os .Remove (remoteFlags .Socket ) // best-effort
144
+ defer os .Remove (serveFlags .Socket ) // best-effort
132
145
133
146
ctx , cancel := signal .NotifyContext (env .Context (), syscall .SIGINT , syscall .SIGTERM )
134
147
defer cancel ()
@@ -138,7 +151,60 @@ func runRemote(env *command.Env) error {
138
151
lst .Close ()
139
152
}()
140
153
154
+ // If a module proxy is enabled, start it.
141
155
var g taskgroup.Group
156
+ if serveFlags .ModProxy != "" {
157
+ modCachePath := filepath .Join (flags .CacheDir , "module" )
158
+ if err := os .MkdirAll (modCachePath , 0700 ); err != nil {
159
+ lst .Close ()
160
+ return fmt .Errorf ("create module cache: %w" , err )
161
+ }
162
+ cacher := & s3proxy.Cacher {
163
+ Local : modCachePath ,
164
+ S3Client : s3c ,
165
+ S3Bucket : flags .S3Bucket ,
166
+ KeyPrefix : path .Join (flags .KeyPrefix , "module" ),
167
+ MaxTasks : flags .S3Concurrency ,
168
+ LogRequests : flags .DebugLog ,
169
+ Logf : vprintf ,
170
+ }
171
+ defer func () {
172
+ vprintf ("close cacher (err=%v)" , cacher .Close ())
173
+ }()
174
+ proxy := & goproxy.Goproxy {
175
+ Fetcher : & goproxy.GoFetcher {
176
+ // As configured, the fetcher should never shell out to the go
177
+ // tool. Specifically, because we set GOPROXY and do not set any
178
+ // bypass via GONOPROXY, GOPRIVATE, etc., we will only attempt to
179
+ // proxy for the specific server(s) listed in Env.
180
+ GoBin : "/bin/false" ,
181
+ Env : []string {"GOPROXY=https://proxy.golang.org" },
182
+ },
183
+ Cacher : cacher ,
184
+ }
185
+ if serveFlags .SumDB != "" {
186
+ proxy .ProxiedSumDBs = strings .Split (serveFlags .SumDB , "," )
187
+ vprintf ("enabling sum DB proxy for %s" , strings .Join (proxy .ProxiedSumDBs , ", " ))
188
+ }
189
+ expvar .Publish ("modcache" , cacher .Metrics ())
190
+
191
+ // Run an HTTP server exporting the proxy and debug metrics.
192
+ mux := http .NewServeMux ()
193
+ mux .Handle ("/" , proxy )
194
+ tsweb .Debugger (mux )
195
+ srv := & http.Server {
196
+ Addr : serveFlags .ModProxy ,
197
+ Handler : mux ,
198
+ }
199
+ g .Go (srv .ListenAndServe )
200
+ vprintf ("started module proxy at %q" , serveFlags .ModProxy )
201
+ go func () {
202
+ <- ctx .Done ()
203
+ vprintf ("signal received, stopping module proxy" )
204
+ srv .Shutdown (context .Background ())
205
+ }()
206
+ }
207
+
142
208
for {
143
209
conn , err := lst .Accept ()
144
210
if err != nil {
0 commit comments