mirror of
https://github.com/juanfont/headscale.git
synced 2026-09-26 18:24:54 +09:00
c3d771e23e
Keeps the interlocked pins current and reports what it could not move. Areas apply, gate and commit one at a time, so a dependency that breaks the build costs one commit rather than the whole pull request.
137 lines
3.7 KiB
Go
137 lines
3.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
|
|
"golang.org/x/mod/modfile"
|
|
"golang.org/x/mod/module"
|
|
)
|
|
|
|
// proxyBase is the module mirror. It is deliberately the same source the go
|
|
// command already trusts: immutable per version, so the go.mod read back here
|
|
// is the one that will actually be resolved, not whatever a branch tip happens
|
|
// to hold at fetch time.
|
|
const proxyBase = "https://proxy.golang.org"
|
|
|
|
const proxyTimeout = 30 * time.Second
|
|
|
|
// errNoLockstepSource means the partner version could not be established. The
|
|
// caller must move neither half of the pair: a libc without its sqlite is the
|
|
// exact breakage the lockstep rule exists to prevent.
|
|
var errNoLockstepSource = errors.New("lockstep source unavailable")
|
|
|
|
// fetch performs a GET and returns the body, or an error naming the status.
|
|
func fetch(ctx context.Context, url string) ([]byte, error) {
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("building request for %s: %w", url, err)
|
|
}
|
|
|
|
client := &http.Client{Timeout: proxyTimeout}
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("GET %s: %w", url, err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("GET %s: %w: %s", url, errHTTPStatus, resp.Status)
|
|
}
|
|
|
|
b, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading %s: %w", url, err)
|
|
}
|
|
|
|
return b, nil
|
|
}
|
|
|
|
var errHTTPStatus = errors.New("unexpected status")
|
|
|
|
// latestVersion resolves a module's newest version through the proxy.
|
|
func latestVersion(ctx context.Context, path string) (string, error) {
|
|
esc, err := module.EscapePath(path)
|
|
if err != nil {
|
|
return "", fmt.Errorf("escaping %s: %w", path, err)
|
|
}
|
|
|
|
body, err := fetch(ctx, proxyBase+"/"+esc+"/@latest")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
var info struct {
|
|
Version string `json:"Version"`
|
|
}
|
|
|
|
if err := json.Unmarshal(body, &info); err != nil { //nolint:noinlineerr
|
|
return "", fmt.Errorf("decoding @latest for %s: %w", path, err)
|
|
}
|
|
|
|
if info.Version == "" {
|
|
return "", fmt.Errorf("%w: empty @latest for %s", errNoLockstepSource, path)
|
|
}
|
|
|
|
return info.Version, nil
|
|
}
|
|
|
|
// modFileOf fetches and parses the go.mod of the exact path@version.
|
|
func modFileOf(ctx context.Context, path, version string) (*modfile.File, error) {
|
|
escPath, err := module.EscapePath(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("escaping %s: %w", path, err)
|
|
}
|
|
|
|
escVer, err := module.EscapeVersion(version)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("escaping version %s: %w", version, err)
|
|
}
|
|
|
|
body, err := fetch(ctx, proxyBase+"/"+escPath+"/@v/"+escVer+".mod")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
f, err := modfile.Parse(path+"@"+version+"/go.mod", body, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing go.mod of %s@%s: %w", path, version, err)
|
|
}
|
|
|
|
return f, nil
|
|
}
|
|
|
|
// requiredVersion reports the version of dep that f requires.
|
|
func requiredVersion(f *modfile.File, dep string) (string, bool) {
|
|
for _, req := range f.Require {
|
|
if req.Mod.Path == dep {
|
|
return req.Mod.Version, true
|
|
}
|
|
}
|
|
|
|
return "", false
|
|
}
|
|
|
|
// partnerVersion resolves the version of dep that owner@ownerVersion pins. It
|
|
// is the whole of the lockstep rule: the partner is never guessed, only read
|
|
// off the owner's own go.mod.
|
|
func partnerVersion(ctx context.Context, owner, ownerVersion, dep string) (string, error) {
|
|
f, err := modFileOf(ctx, owner, ownerVersion)
|
|
if err != nil {
|
|
return "", fmt.Errorf("%w: %w", errNoLockstepSource, err)
|
|
}
|
|
|
|
v, ok := requiredVersion(f, dep)
|
|
if !ok {
|
|
return "", fmt.Errorf("%w: %s@%s does not require %s", errNoLockstepSource, owner, ownerVersion, dep)
|
|
}
|
|
|
|
return v, nil
|
|
}
|