.github,cmd/cigocacher: add flags --version --stats --cigocached-host

Add flags:

* --cigocached-host to support alternative host resolution in other
  environments, like the corp repo.
* --stats to reduce the amount of bash script we need.
* --version to support a caching tool/cigocacher script that will
  download from GitHub releases.

Updates tailscale/corp#10808

Change-Id: Ib2447bc5f79058669a70f2c49cef6aedd7afc049
Signed-off-by: Tom Proctor <tomhjp@users.noreply.github.com>
This commit is contained in:
Tom Proctor
2025-12-12 13:58:16 +00:00
parent d7a5624841
commit d0d993f5d6
5 changed files with 99 additions and 76 deletions
+81 -20
View File
@@ -22,8 +22,11 @@ import (
"log"
"net"
"net/http"
"net/url"
"os"
"path/filepath"
"runtime/debug"
"strconv"
"strings"
"sync/atomic"
"time"
@@ -34,20 +37,56 @@ import (
func main() {
var (
auth = flag.Bool("auth", false, "auth with cigocached and exit, printing the access token as output")
token = flag.String("token", "", "the cigocached access token to use, as created using --auth")
cigocachedURL = flag.String("cigocached-url", "", "optional cigocached URL (scheme, host, and port). empty means to not use one.")
dir = flag.String("cache-dir", "", "cache directory; empty means automatic")
verbose = flag.Bool("verbose", false, "enable verbose logging")
version = flag.Bool("version", false, "print version and exit")
auth = flag.Bool("auth", false, "auth with cigocached and exit, printing the access token as output")
stats = flag.Bool("stats", false, "fetch and print cigocached stats and exit")
token = flag.String("token", "", "the cigocached access token to use, as created using --auth")
srvURL = flag.String("cigocached-url", "", "optional cigocached URL (scheme, host, and port). Empty means to not use one.")
srvHostDial = flag.String("cigocached-host", "", "optional cigocached host to dial instead of the host in the provided --cigocached-url. Useful for public TLS certs on private addresses.")
dir = flag.String("cache-dir", "", "cache directory; empty means automatic")
verbose = flag.Bool("verbose", false, "enable verbose logging")
)
flag.Parse()
if *version {
info, ok := debug.ReadBuildInfo()
if !ok {
log.Fatal("no build info")
}
var (
rev string
dirty bool
)
for _, s := range info.Settings {
switch s.Key {
case "vcs.revision":
rev = s.Value
case "vcs.modified":
dirty, _ = strconv.ParseBool(s.Value)
}
}
if dirty {
rev += "-dirty"
}
fmt.Println(rev)
return
}
var srvHost string
if *srvHostDial != "" && *srvURL != "" {
u, err := url.Parse(*srvURL)
if err != nil {
log.Fatal(err)
}
srvHost = u.Hostname()
}
if *auth {
if *cigocachedURL == "" {
if *srvURL == "" {
log.Print("--cigocached-url is empty, skipping auth")
return
}
tk, err := fetchAccessToken(httpClient(), os.Getenv("ACTIONS_ID_TOKEN_REQUEST_URL"), os.Getenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN"), *cigocachedURL)
tk, err := fetchAccessToken(httpClient(srvHost, *srvHostDial), os.Getenv("ACTIONS_ID_TOKEN_REQUEST_URL"), os.Getenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN"), *srvURL)
if err != nil {
log.Printf("error fetching access token, skipping auth: %v", err)
return
@@ -56,6 +95,28 @@ func main() {
return
}
if *stats {
if *srvURL == "" {
log.Fatal("--cigocached-url is empty; cannot fetch stats")
}
tk := *token
if tk == "" {
log.Fatal("--token is empty; cannot fetch stats")
}
c := &gocachedClient{
baseURL: *srvURL,
cl: httpClient(srvHost, *srvHostDial),
accessToken: tk,
verbose: *verbose,
}
stats, err := c.fetchStats()
if err != nil {
log.Fatalf("error fetching gocached stats: %v", err)
}
fmt.Println(stats)
return
}
if *dir == "" {
d, err := os.UserCacheDir()
if err != nil {
@@ -75,13 +136,13 @@ func main() {
},
verbose: *verbose,
}
if *cigocachedURL != "" {
if *srvURL != "" {
if *verbose {
log.Printf("Using cigocached at %s", *cigocachedURL)
log.Printf("Using cigocached at %s", *srvURL)
}
c.gocached = &gocachedClient{
baseURL: *cigocachedURL,
cl: httpClient(),
baseURL: *srvURL,
cl: httpClient(srvHost, *srvHostDial),
accessToken: *token,
verbose: *verbose,
}
@@ -104,18 +165,18 @@ func main() {
}
}
func httpClient() *http.Client {
func httpClient(srvHost, srvHostDial string) *http.Client {
if srvHost == "" || srvHostDial == "" {
return http.DefaultClient
}
return &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
host, port, err := net.SplitHostPort(addr)
if err == nil {
// This does not run in a tailnet. We serve corp.ts.net
// TLS certs, and override DNS resolution to lookup the
// private IP for the VM by its hostname.
if vm, ok := strings.CutSuffix(host, ".corp.ts.net"); ok {
addr = net.JoinHostPort(vm, port)
}
if host, port, err := net.SplitHostPort(addr); err == nil && host == srvHost {
// This allows us to serve a publicly trusted TLS cert
// while also minimising latency by explicitly using a
// private network address.
addr = net.JoinHostPort(srvHostDial, port)
}
var d net.Dialer
return d.DialContext(ctx, network, addr)
-6
View File
@@ -32,12 +32,6 @@ func tryReadErrorMessage(res *http.Response) []byte {
}
func (c *gocachedClient) get(ctx context.Context, actionID string) (outputID string, resp *http.Response, err error) {
// TODO(tomhjp): make sure we timeout if cigocached disappears, but for some
// reason, this seemed to tank network performance.
// // Set a generous upper limit on the time we'll wait for a response. We'll
// // shorten this deadline later once we know the content length.
// ctx, cancel := context.WithTimeout(ctx, time.Minute)
// defer cancel()
req, _ := http.NewRequestWithContext(ctx, "GET", c.baseURL+"/action/"+actionID, nil)
req.Header.Set("Want-Object", "1") // opt in to single roundtrip protocol
if c.accessToken != "" {