cmd/cigocacher,go.mod: add cigocacher cmd
Adds cmd/cigocacher as the client to cigocached for Go caching over HTTP. The HTTP cache is best-effort only, and builds will fall back to disk-only cache if it's not available, much like regular builds. Not yet used in CI; that will follow in another PR once we have runners available in this repo with the right network setup for reaching cigocached. Updates tailscale/corp#10808 Change-Id: I13ae1a12450eb2a05bd9843f358474243989e967 Signed-off-by: Tom Proctor <tomhjp@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type gocachedClient struct {
|
||||
baseURL string // base URL of the cacher server, like "http://localhost:31364".
|
||||
cl *http.Client // http.Client to use.
|
||||
accessToken string // Bearer token to use in the Authorization header.
|
||||
verbose bool
|
||||
}
|
||||
|
||||
// drainAndClose reads and throws away a small bounded amount of data. This is a
|
||||
// best-effort attempt to allow connection reuse; Go's HTTP/1 Transport won't
|
||||
// reuse a TCP connection unless you fully consume HTTP responses.
|
||||
func drainAndClose(body io.ReadCloser) {
|
||||
io.CopyN(io.Discard, body, 4<<10)
|
||||
body.Close()
|
||||
}
|
||||
|
||||
func tryReadErrorMessage(res *http.Response) []byte {
|
||||
msg, _ := io.ReadAll(io.LimitReader(res.Body, 4<<10))
|
||||
return msg
|
||||
}
|
||||
|
||||
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 != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+c.accessToken)
|
||||
}
|
||||
|
||||
res, err := c.cl.Do(req)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
defer func() {
|
||||
if resp == nil {
|
||||
drainAndClose(res.Body)
|
||||
}
|
||||
}()
|
||||
if res.StatusCode == http.StatusNotFound {
|
||||
return "", nil, nil
|
||||
}
|
||||
if res.StatusCode != http.StatusOK {
|
||||
msg := tryReadErrorMessage(res)
|
||||
if c.verbose {
|
||||
log.Printf("error GET /action/%s: %v, %s", actionID, res.Status, msg)
|
||||
}
|
||||
return "", nil, fmt.Errorf("unexpected GET /action/%s status %v", actionID, res.Status)
|
||||
}
|
||||
|
||||
outputID = res.Header.Get("Go-Output-Id")
|
||||
if outputID == "" {
|
||||
return "", nil, fmt.Errorf("missing Go-Output-Id header in response")
|
||||
}
|
||||
if res.ContentLength == -1 {
|
||||
return "", nil, fmt.Errorf("no Content-Length from server")
|
||||
}
|
||||
return outputID, res, nil
|
||||
}
|
||||
|
||||
func (c *gocachedClient) put(ctx context.Context, actionID, outputID string, size int64, body io.Reader) error {
|
||||
req, _ := http.NewRequestWithContext(ctx, "PUT", c.baseURL+"/"+actionID+"/"+outputID, body)
|
||||
req.ContentLength = size
|
||||
if c.accessToken != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+c.accessToken)
|
||||
}
|
||||
res, err := c.cl.Do(req)
|
||||
if err != nil {
|
||||
if c.verbose {
|
||||
log.Printf("error PUT /%s/%s: %v", actionID, outputID, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusNoContent {
|
||||
msg := tryReadErrorMessage(res)
|
||||
if c.verbose {
|
||||
log.Printf("error PUT /%s/%s: %v, %s", actionID, outputID, res.Status, msg)
|
||||
}
|
||||
return fmt.Errorf("unexpected PUT /%s/%s status %v", actionID, outputID, res.Status)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *gocachedClient) fetchStats() (string, error) {
|
||||
req, _ := http.NewRequest("GET", c.baseURL+"/session/stats", nil)
|
||||
req.Header.Set("Authorization", "Bearer "+c.accessToken)
|
||||
resp, err := c.cl.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(b), nil
|
||||
}
|
||||
Reference in New Issue
Block a user