appview,knotserver: immutable nix flakeref link header #741

open
opened by boltless.me targeting master from push-ptrrwwvnkmxq
Changed files
+251 -353
api
tangled
appview
knotserver
lexicons
nix
-41
api/tangled/repoarchive.go
···
-
// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT.
-
-
package tangled
-
-
// schema: sh.tangled.repo.archive
-
-
import (
-
"bytes"
-
"context"
-
-
"github.com/bluesky-social/indigo/lex/util"
-
)
-
-
const (
-
RepoArchiveNSID = "sh.tangled.repo.archive"
-
)
-
-
// RepoArchive calls the XRPC method "sh.tangled.repo.archive".
-
//
-
// format: Archive format
-
// prefix: Prefix for files in the archive
-
// ref: Git reference (branch, tag, or commit SHA)
-
// repo: Repository identifier in format 'did:plc:.../repoName'
-
func RepoArchive(ctx context.Context, c util.LexClient, format string, prefix string, ref string, repo string) ([]byte, error) {
-
buf := new(bytes.Buffer)
-
-
params := map[string]interface{}{}
-
if format != "" {
-
params["format"] = format
-
}
-
if prefix != "" {
-
params["prefix"] = prefix
-
}
-
params["ref"] = ref
-
params["repo"] = repo
-
if err := c.LexDo(ctx, util.Query, "", "sh.tangled.repo.archive", params, nil, buf); err != nil {
-
return nil, err
-
}
-
-
return buf.Bytes(), nil
-
}
-49
appview/repo/archive.go
···
-
package repo
-
-
import (
-
"fmt"
-
"net/http"
-
"net/url"
-
"strings"
-
-
"tangled.org/core/api/tangled"
-
xrpcclient "tangled.org/core/appview/xrpcclient"
-
-
indigoxrpc "github.com/bluesky-social/indigo/xrpc"
-
"github.com/go-chi/chi/v5"
-
"github.com/go-git/go-git/v5/plumbing"
-
)
-
-
func (rp *Repo) DownloadArchive(w http.ResponseWriter, r *http.Request) {
-
l := rp.logger.With("handler", "DownloadArchive")
-
ref := chi.URLParam(r, "ref")
-
ref, _ = url.PathUnescape(ref)
-
f, err := rp.repoResolver.Resolve(r)
-
if err != nil {
-
l.Error("failed to get repo and knot", "err", err)
-
return
-
}
-
scheme := "http"
-
if !rp.config.Core.Dev {
-
scheme = "https"
-
}
-
host := fmt.Sprintf("%s://%s", scheme, f.Knot)
-
xrpcc := &indigoxrpc.Client{
-
Host: host,
-
}
-
didSlashRepo := f.DidSlashRepo()
-
archiveBytes, err := tangled.RepoArchive(r.Context(), xrpcc, "tar.gz", "", ref, didSlashRepo)
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
-
l.Error("failed to call XRPC repo.archive", "err", xrpcerr)
-
rp.pages.Error503(w)
-
return
-
}
-
// Set headers for file download, just pass along whatever the knot specifies
-
safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(ref).Short(), "/", "-")
-
filename := fmt.Sprintf("%s-%s.tar.gz", f.Name, safeRefFilename)
-
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
-
w.Header().Set("Content-Type", "application/gzip")
-
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(archiveBytes)))
-
// Write the archive data directly
-
w.Write(archiveBytes)
-
}
-4
appview/repo/router.go
···
r.Get("/blob/{ref}/*", rp.Blob)
r.Get("/raw/{ref}/*", rp.RepoBlobRaw)
-
// intentionally doesn't use /* as this isn't
-
// a file path
-
r.Get("/archive/{ref}", rp.DownloadArchive)
-
r.Route("/fork", func(r chi.Router) {
r.Use(middleware.AuthMiddleware(rp.oauth))
r.Get("/", rp.ForkRepo)
-97
appview/state/git_http.go
···
-
package state
-
-
import (
-
"fmt"
-
"io"
-
"maps"
-
"net/http"
-
-
"github.com/bluesky-social/indigo/atproto/identity"
-
"github.com/go-chi/chi/v5"
-
"tangled.org/core/appview/models"
-
)
-
-
func (s *State) InfoRefs(w http.ResponseWriter, r *http.Request) {
-
user := r.Context().Value("resolvedId").(identity.Identity)
-
repo := r.Context().Value("repo").(*models.Repo)
-
-
scheme := "https"
-
if s.config.Core.Dev {
-
scheme = "http"
-
}
-
-
targetURL := fmt.Sprintf("%s://%s/%s/%s/info/refs?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery)
-
s.proxyRequest(w, r, targetURL)
-
-
}
-
-
func (s *State) UploadPack(w http.ResponseWriter, r *http.Request) {
-
user, ok := r.Context().Value("resolvedId").(identity.Identity)
-
if !ok {
-
http.Error(w, "failed to resolve user", http.StatusInternalServerError)
-
return
-
}
-
repo := r.Context().Value("repo").(*models.Repo)
-
-
scheme := "https"
-
if s.config.Core.Dev {
-
scheme = "http"
-
}
-
-
targetURL := fmt.Sprintf("%s://%s/%s/%s/git-upload-pack?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery)
-
s.proxyRequest(w, r, targetURL)
-
}
-
-
func (s *State) ReceivePack(w http.ResponseWriter, r *http.Request) {
-
user, ok := r.Context().Value("resolvedId").(identity.Identity)
-
if !ok {
-
http.Error(w, "failed to resolve user", http.StatusInternalServerError)
-
return
-
}
-
repo := r.Context().Value("repo").(*models.Repo)
-
-
scheme := "https"
-
if s.config.Core.Dev {
-
scheme = "http"
-
}
-
-
targetURL := fmt.Sprintf("%s://%s/%s/%s/git-receive-pack?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery)
-
s.proxyRequest(w, r, targetURL)
-
}
-
-
func (s *State) proxyRequest(w http.ResponseWriter, r *http.Request, targetURL string) {
-
client := &http.Client{}
-
-
// Create new request
-
proxyReq, err := http.NewRequest(r.Method, targetURL, r.Body)
-
if err != nil {
-
http.Error(w, err.Error(), http.StatusInternalServerError)
-
return
-
}
-
-
// Copy original headers
-
proxyReq.Header = r.Header
-
-
repoOwnerHandle := chi.URLParam(r, "user")
-
proxyReq.Header.Add("x-tangled-repo-owner-handle", repoOwnerHandle)
-
-
// Execute request
-
resp, err := client.Do(proxyReq)
-
if err != nil {
-
http.Error(w, err.Error(), http.StatusInternalServerError)
-
return
-
}
-
defer resp.Body.Close()
-
-
// Copy response headers
-
maps.Copy(w.Header(), resp.Header)
-
-
// Set response status code
-
w.WriteHeader(resp.StatusCode)
-
-
// Copy response body
-
if _, err := io.Copy(w, resp.Body); err != nil {
-
http.Error(w, err.Error(), http.StatusInternalServerError)
-
return
-
}
-
}
+167
appview/state/proxy_knot.go
···
+
package state
+
+
import (
+
"fmt"
+
"io"
+
"maps"
+
"net/http"
+
"strings"
+
+
"github.com/bluesky-social/indigo/atproto/identity"
+
indigoxrpc "github.com/bluesky-social/indigo/xrpc"
+
"github.com/go-chi/chi/v5"
+
"github.com/go-git/go-git/v5/plumbing"
+
"github.com/hashicorp/go-version"
+
"tangled.org/core/api/tangled"
+
"tangled.org/core/appview/models"
+
xrpcclient "tangled.org/core/appview/xrpcclient"
+
)
+
+
func (s *State) InfoRefs(w http.ResponseWriter, r *http.Request) {
+
user := r.Context().Value("resolvedId").(identity.Identity)
+
repo := r.Context().Value("repo").(*models.Repo)
+
+
scheme := "https"
+
if s.config.Core.Dev {
+
scheme = "http"
+
}
+
+
targetURL := fmt.Sprintf("%s://%s/%s/%s/info/refs?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery)
+
s.proxyRequest(w, r, targetURL)
+
+
}
+
+
func (s *State) UploadPack(w http.ResponseWriter, r *http.Request) {
+
user, ok := r.Context().Value("resolvedId").(identity.Identity)
+
if !ok {
+
http.Error(w, "failed to resolve user", http.StatusInternalServerError)
+
return
+
}
+
repo := r.Context().Value("repo").(*models.Repo)
+
+
scheme := "https"
+
if s.config.Core.Dev {
+
scheme = "http"
+
}
+
+
targetURL := fmt.Sprintf("%s://%s/%s/%s/git-upload-pack?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery)
+
s.proxyRequest(w, r, targetURL)
+
}
+
+
func (s *State) ReceivePack(w http.ResponseWriter, r *http.Request) {
+
user, ok := r.Context().Value("resolvedId").(identity.Identity)
+
if !ok {
+
http.Error(w, "failed to resolve user", http.StatusInternalServerError)
+
return
+
}
+
repo := r.Context().Value("repo").(*models.Repo)
+
+
scheme := "https"
+
if s.config.Core.Dev {
+
scheme = "http"
+
}
+
+
targetURL := fmt.Sprintf("%s://%s/%s/%s/git-receive-pack?%s", scheme, repo.Knot, user.DID, repo.Name, r.URL.RawQuery)
+
s.proxyRequest(w, r, targetURL)
+
}
+
+
var knotVersionDownloadArchiveConstraint = version.MustConstraints(version.NewConstraint(">= 1.12"))
+
+
func (s *State) DownloadArchive(w http.ResponseWriter, r *http.Request) {
+
l := s.logger.With("handler", "DownloadArchive")
+
ref := chi.URLParam(r, "ref")
+
+
user, ok := r.Context().Value("resolvedId").(identity.Identity)
+
if !ok {
+
l.Error("failed to resolve user")
+
http.Error(w, "failed to resolve user", http.StatusInternalServerError)
+
return
+
}
+
repo := r.Context().Value("repo").(*models.Repo)
+
+
scheme := "https"
+
if s.config.Core.Dev {
+
scheme = "http"
+
}
+
+
xrpcc := &indigoxrpc.Client{
+
Host: repo.Knot,
+
}
+
l = l.With("knot", repo.Knot)
+
+
isCompatible := func() bool {
+
out, err := tangled.KnotVersion(r.Context(), xrpcc)
+
if err != nil {
+
l.Warn("failed to get knot version", "err", err)
+
return false
+
}
+
+
v, err := version.NewVersion(out.Version)
+
if err != nil {
+
l.Warn("failed to parse knot version", "version", out.Version, "err", err)
+
return false
+
}
+
+
if !knotVersionDownloadArchiveConstraint.Check(v) {
+
l.Warn("knot version incompatible.", "version", v)
+
return false
+
}
+
return true
+
}()
+
l.Debug("knot compatibility check", "isCompatible", isCompatible)
+
if isCompatible {
+
targetURL := fmt.Sprintf("%s://%s/%s/%s/archive/%s", scheme, repo.Knot, user.DID, repo.Name, ref)
+
s.proxyRequest(w, r, targetURL)
+
} else {
+
l.Debug("requesting xrpc/sh.tangled.repo.archive")
+
archiveBytes, err := tangled.RepoArchive(r.Context(), xrpcc, "tar.gz", "", ref, repo.DidSlashRepo())
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
+
l.Error("failed to call XRPC repo.archive", "err", xrpcerr)
+
s.pages.Error503(w)
+
return
+
}
+
safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(ref).Short(), "/", "-")
+
filename := fmt.Sprintf("%s-%s.tar.gz", repo.Name, safeRefFilename)
+
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
+
w.Header().Set("Content-Type", "application/gzip")
+
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(archiveBytes)))
+
w.Write(archiveBytes)
+
}
+
}
+
+
func (s *State) proxyRequest(w http.ResponseWriter, r *http.Request, targetURL string) {
+
client := &http.Client{}
+
+
// Create new request
+
proxyReq, err := http.NewRequest(r.Method, targetURL, r.Body)
+
if err != nil {
+
http.Error(w, err.Error(), http.StatusInternalServerError)
+
return
+
}
+
+
// Copy original headers
+
proxyReq.Header = r.Header
+
+
repoOwnerHandle := chi.URLParam(r, "user")
+
proxyReq.Header.Add("x-tangled-repo-owner-handle", repoOwnerHandle)
+
+
// Execute request
+
resp, err := client.Do(proxyReq)
+
if err != nil {
+
http.Error(w, err.Error(), http.StatusInternalServerError)
+
return
+
}
+
defer resp.Body.Close()
+
+
// Copy response headers
+
maps.Copy(w.Header(), resp.Header)
+
+
// Set response status code
+
w.WriteHeader(resp.StatusCode)
+
+
// Copy response body
+
if _, err := io.Copy(w, resp.Body); err != nil {
+
http.Error(w, err.Error(), http.StatusInternalServerError)
+
return
+
}
+
}
+3 -1
appview/state/router.go
···
r.Get("/info/refs", s.InfoRefs)
r.Post("/git-upload-pack", s.UploadPack)
r.Post("/git-receive-pack", s.ReceivePack)
-
+
// intentionally doesn't use /* as this isn't
+
// a file path
+
r.Get("/archive/{ref}", s.DownloadArchive)
})
})
+1
go.mod
···
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
+
github.com/hashicorp/go-version v1.8.0 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/hcl v1.0.1-vault-7 // indirect
+2
go.sum
···
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw=
github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw=
+
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
+
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
+69
knotserver/archive.go
···
+
package knotserver
+
+
import (
+
"compress/gzip"
+
"fmt"
+
"net/http"
+
"strings"
+
+
securejoin "github.com/cyphar/filepath-securejoin"
+
"github.com/go-chi/chi/v5"
+
"github.com/go-git/go-git/v5/plumbing"
+
"tangled.org/core/knotserver/git"
+
)
+
+
func (h *Knot) Archive(w http.ResponseWriter, r *http.Request) {
+
var (
+
did = chi.URLParam(r, "did")
+
name = chi.URLParam(r, "name")
+
ref = chi.URLParam(r, "ref")
+
)
+
repo, err := securejoin.SecureJoin(did, name)
+
if err != nil {
+
gitError(w, "repository not found", http.StatusNotFound)
+
h.l.Error("git: failed to secure join repo path", "handler", "InfoRefs", "error", err)
+
return
+
}
+
+
repoPath, err := securejoin.SecureJoin(h.c.Repo.ScanPath, repo)
+
if err != nil {
+
gitError(w, "repository not found", http.StatusNotFound)
+
h.l.Error("git: failed to secure join repo path", "handler", "InfoRefs", "error", err)
+
return
+
}
+
+
gr, err := git.Open(repoPath, ref)
+
+
immutableLink := fmt.Sprintf(
+
"https://%s/%s/%s/archive/%s",
+
h.c.Server.Hostname,
+
did,
+
name,
+
gr.Hash(),
+
)
+
+
safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(ref).Short(), "/", "-")
+
filename := fmt.Sprintf("%s-%s.tar.gz", name, safeRefFilename)
+
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
+
w.Header().Set("Content-Type", "application/gzip")
+
w.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"immutable\"", immutableLink))
+
+
gw := gzip.NewWriter(w)
+
defer gw.Close()
+
+
err = gr.WriteTar(gw, "")
+
if err != nil {
+
// once we start writing to the body we can't report error anymore
+
// so we are only left with logging the error
+
h.l.Error("writing tar file", "error", err)
+
return
+
}
+
+
err = gw.Flush()
+
if err != nil {
+
// once we start writing to the body we can't report error anymore
+
// so we are only left with logging the error
+
h.l.Error("flushing", "error", err.Error())
+
return
+
}
+
}
+4
knotserver/git/git.go
···
return &g, nil
}
+
func (g *GitRepo) Hash() plumbing.Hash {
+
return g.h
+
}
+
// re-open a repository and update references
func (g *GitRepo) Refresh() error {
refreshed, err := PlainOpen(g.path)
+2
knotserver/router.go
···
r.Get("/info/refs", h.InfoRefs)
r.Post("/git-upload-pack", h.UploadPack)
r.Post("/git-receive-pack", h.ReceivePack)
+
// convenience routes
+
r.Get("/archive/{ref}", h.Archive)
})
})
-81
knotserver/xrpc/repo_archive.go
···
-
package xrpc
-
-
import (
-
"compress/gzip"
-
"fmt"
-
"net/http"
-
"strings"
-
-
"github.com/go-git/go-git/v5/plumbing"
-
-
"tangled.org/core/knotserver/git"
-
xrpcerr "tangled.org/core/xrpc/errors"
-
)
-
-
func (x *Xrpc) RepoArchive(w http.ResponseWriter, r *http.Request) {
-
repo := r.URL.Query().Get("repo")
-
repoPath, err := x.parseRepoParam(repo)
-
if err != nil {
-
writeError(w, err.(xrpcerr.XrpcError), http.StatusBadRequest)
-
return
-
}
-
-
ref := r.URL.Query().Get("ref")
-
// ref can be empty (git.Open handles this)
-
-
format := r.URL.Query().Get("format")
-
if format == "" {
-
format = "tar.gz" // default
-
}
-
-
prefix := r.URL.Query().Get("prefix")
-
-
if format != "tar.gz" {
-
writeError(w, xrpcerr.NewXrpcError(
-
xrpcerr.WithTag("InvalidRequest"),
-
xrpcerr.WithMessage("only tar.gz format is supported"),
-
), http.StatusBadRequest)
-
return
-
}
-
-
gr, err := git.Open(repoPath, ref)
-
if err != nil {
-
writeError(w, xrpcerr.RefNotFoundError, http.StatusNotFound)
-
return
-
}
-
-
repoParts := strings.Split(repo, "/")
-
repoName := repoParts[len(repoParts)-1]
-
-
safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(ref).Short(), "/", "-")
-
-
var archivePrefix string
-
if prefix != "" {
-
archivePrefix = prefix
-
} else {
-
archivePrefix = fmt.Sprintf("%s-%s", repoName, safeRefFilename)
-
}
-
-
filename := fmt.Sprintf("%s-%s.tar.gz", repoName, safeRefFilename)
-
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
-
w.Header().Set("Content-Type", "application/gzip")
-
-
gw := gzip.NewWriter(w)
-
defer gw.Close()
-
-
err = gr.WriteTar(gw, archivePrefix)
-
if err != nil {
-
// once we start writing to the body we can't report error anymore
-
// so we are only left with logging the error
-
x.Logger.Error("writing tar file", "error", err.Error())
-
return
-
}
-
-
err = gw.Flush()
-
if err != nil {
-
// once we start writing to the body we can't report error anymore
-
// so we are only left with logging the error
-
x.Logger.Error("flushing", "error", err.Error())
-
return
-
}
-
}
-1
knotserver/xrpc/xrpc.go
···
r.Get("/"+tangled.RepoCompareNSID, x.RepoCompare)
r.Get("/"+tangled.RepoGetDefaultBranchNSID, x.RepoGetDefaultBranch)
r.Get("/"+tangled.RepoBranchNSID, x.RepoBranch)
-
r.Get("/"+tangled.RepoArchiveNSID, x.RepoArchive)
r.Get("/"+tangled.RepoLanguagesNSID, x.RepoLanguages)
// knot query endpoints (no auth required)
-55
lexicons/repo/archive.json
···
-
{
-
"lexicon": 1,
-
"id": "sh.tangled.repo.archive",
-
"defs": {
-
"main": {
-
"type": "query",
-
"parameters": {
-
"type": "params",
-
"required": ["repo", "ref"],
-
"properties": {
-
"repo": {
-
"type": "string",
-
"description": "Repository identifier in format 'did:plc:.../repoName'"
-
},
-
"ref": {
-
"type": "string",
-
"description": "Git reference (branch, tag, or commit SHA)"
-
},
-
"format": {
-
"type": "string",
-
"description": "Archive format",
-
"enum": ["tar", "zip", "tar.gz", "tar.bz2", "tar.xz"],
-
"default": "tar.gz"
-
},
-
"prefix": {
-
"type": "string",
-
"description": "Prefix for files in the archive"
-
}
-
}
-
},
-
"output": {
-
"encoding": "*/*",
-
"description": "Binary archive data"
-
},
-
"errors": [
-
{
-
"name": "RepoNotFound",
-
"description": "Repository not found or access denied"
-
},
-
{
-
"name": "RefNotFound",
-
"description": "Git reference not found"
-
},
-
{
-
"name": "InvalidRequest",
-
"description": "Invalid request parameters"
-
},
-
{
-
"name": "ArchiveError",
-
"description": "Failed to create archive"
-
}
-
]
-
}
-
}
-
}
+3 -24
nix/gomod2nix.toml
···
[mod."github.com/davecgh/go-spew"]
version = "v1.1.2-0.20180830191138-d8f796af33cc"
hash = "sha256-fV9oI51xjHdOmEx6+dlq7Ku2Ag+m/bmbzPo6A4Y74qc="
-
[mod."github.com/decred/dcrd/dcrec/secp256k1/v4"]
-
version = "v4.4.0"
-
hash = "sha256-qrhEIwhDll3cxoVpMbm1NQ9/HTI42S7ms8Buzlo5HCg="
[mod."github.com/dgraph-io/ristretto"]
version = "v0.2.0"
hash = "sha256-bnpxX+oO/Qf7IJevA0gsbloVoqRx+5bh7RQ9d9eLNYw="
···
[mod."github.com/hashicorp/go-sockaddr"]
version = "v1.0.7"
hash = "sha256-p6eDOrGzN1jMmT/F/f/VJMq0cKNFhUcEuVVwTE6vSrs="
+
[mod."github.com/hashicorp/go-version"]
+
version = "v1.8.0"
+
hash = "sha256-KXtqERmYrWdpqPCViWcHbe6jnuH7k16bvBIcuJuevj8="
[mod."github.com/hashicorp/golang-lru"]
version = "v1.0.2"
hash = "sha256-yy+5botc6T5wXgOe2mfNXJP3wr+MkVlUZ2JBkmmrA48="
···
[mod."github.com/klauspost/cpuid/v2"]
version = "v2.3.0"
hash = "sha256-50JhbQyT67BK38HIdJihPtjV7orYp96HknI2VP7A9Yc="
-
[mod."github.com/lestrrat-go/blackmagic"]
-
version = "v1.0.4"
-
hash = "sha256-HmWOpwoPDNMwLdOi7onNn3Sb+ZsAa3Ai3gVBbXmQ0e8="
-
[mod."github.com/lestrrat-go/httpcc"]
-
version = "v1.0.1"
-
hash = "sha256-SMRSwJpqDIs/xL0l2e8vP0W65qtCHX2wigcOeqPJmos="
-
[mod."github.com/lestrrat-go/httprc"]
-
version = "v1.0.6"
-
hash = "sha256-mfZzePEhrmyyu/avEBd2MsDXyto8dq5+fyu5lA8GUWM="
-
[mod."github.com/lestrrat-go/iter"]
-
version = "v1.0.2"
-
hash = "sha256-30tErRf7Qu/NOAt1YURXY/XJSA6sCr6hYQfO8QqHrtw="
-
[mod."github.com/lestrrat-go/jwx/v2"]
-
version = "v2.1.6"
-
hash = "sha256-0LszXRZIba+X8AOrs3T4uanAUafBdlVB8/MpUNEFpbc="
-
[mod."github.com/lestrrat-go/option"]
-
version = "v1.0.1"
-
hash = "sha256-jVcIYYVsxElIS/l2akEw32vdEPR8+anR6oeT1FoYULI="
[mod."github.com/lucasb-eyer/go-colorful"]
version = "v1.2.0"
hash = "sha256-Gg9dDJFCTaHrKHRR1SrJgZ8fWieJkybljybkI9x0gyE="
···
[mod."github.com/ryanuber/go-glob"]
version = "v1.0.0"
hash = "sha256-YkMl1utwUhi3E0sHK23ISpAsPyj4+KeXyXKoFYGXGVY="
-
[mod."github.com/segmentio/asm"]
-
version = "v1.2.0"
-
hash = "sha256-zbNuKxNrUDUc6IlmRQNuJQzVe5Ol/mqp7srDg9IMMqs="
[mod."github.com/sergi/go-diff"]
version = "v1.1.0"
hash = "sha256-8NJMabldpf40uwQN20T6QXx5KORDibCBJL02KD661xY="