forked from mirrors/pkg-proxy
* Fix all golangci-lint issues across the codebase Resolve 77 lint issues reported by golangci-lint with gocritic, gocognit, gocyclo, maintidx, dupl, mnd, unparam, ireturn, goconst, and errcheck enabled. Net reduction of ~175 lines through shared helpers and deduplication. * Suppress staticcheck SA1019 for intentional deprecated field usage The Storage.Path field is deprecated but still read for backwards compatibility with existing configs that haven't migrated to the URL field.
125 lines
3.6 KiB
Go
125 lines
3.6 KiB
Go
package handler
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
condaUpstream = "https://conda.anaconda.org"
|
|
minCondaParts = 3 // name-version-build requires at least 3 hyphen-separated parts
|
|
)
|
|
|
|
// CondaHandler handles Conda/Anaconda registry protocol requests.
|
|
type CondaHandler struct {
|
|
proxy *Proxy
|
|
upstreamURL string
|
|
proxyURL string
|
|
}
|
|
|
|
// NewCondaHandler creates a new Conda protocol handler.
|
|
func NewCondaHandler(proxy *Proxy, proxyURL string) *CondaHandler {
|
|
return &CondaHandler{
|
|
proxy: proxy,
|
|
upstreamURL: condaUpstream,
|
|
proxyURL: strings.TrimSuffix(proxyURL, "/"),
|
|
}
|
|
}
|
|
|
|
// Routes returns the HTTP handler for Conda requests.
|
|
func (h *CondaHandler) Routes() http.Handler {
|
|
mux := http.NewServeMux()
|
|
|
|
// Channel index (repodata)
|
|
mux.HandleFunc("GET /{channel}/{arch}/repodata.json", h.proxyUpstream)
|
|
mux.HandleFunc("GET /{channel}/{arch}/repodata.json.bz2", h.proxyUpstream)
|
|
mux.HandleFunc("GET /{channel}/{arch}/current_repodata.json", h.proxyUpstream)
|
|
|
|
// Package downloads (cache these)
|
|
mux.HandleFunc("GET /{channel}/{arch}/{filename}", h.handleDownload)
|
|
|
|
return mux
|
|
}
|
|
|
|
// handleDownload serves a package file, fetching and caching from upstream if needed.
|
|
func (h *CondaHandler) handleDownload(w http.ResponseWriter, r *http.Request) {
|
|
channel := r.PathValue("channel")
|
|
arch := r.PathValue("arch")
|
|
filename := r.PathValue("filename")
|
|
|
|
// Only cache actual package files
|
|
if !h.isPackageFile(filename) {
|
|
h.proxyUpstream(w, r)
|
|
return
|
|
}
|
|
|
|
name, version := h.parseFilename(filename)
|
|
if name == "" {
|
|
h.proxyUpstream(w, r)
|
|
return
|
|
}
|
|
|
|
// Include channel in package name
|
|
packageName := channel + "/" + name
|
|
|
|
h.proxy.Logger.Info("conda download request",
|
|
"channel", channel, "arch", arch, "name", name, "version", version)
|
|
|
|
upstreamURL := h.upstreamURL + r.URL.Path
|
|
|
|
result, err := h.proxy.GetOrFetchArtifactFromURL(r.Context(), "conda", packageName, version, filename, upstreamURL)
|
|
if err != nil {
|
|
h.proxy.Logger.Error("failed to get artifact", "error", err)
|
|
http.Error(w, "failed to fetch package", http.StatusBadGateway)
|
|
return
|
|
}
|
|
|
|
ServeArtifact(w, result)
|
|
}
|
|
|
|
// isPackageFile returns true if the filename is a Conda package.
|
|
func (h *CondaHandler) isPackageFile(filename string) bool {
|
|
return strings.HasSuffix(filename, ".tar.bz2") || strings.HasSuffix(filename, ".conda")
|
|
}
|
|
|
|
// parseFilename extracts name and version from a Conda package filename.
|
|
// Conda filenames are: {name}-{version}-{build}.{ext}
|
|
// e.g., "numpy-1.24.0-py311h64a7726_0.conda"
|
|
func (h *CondaHandler) parseFilename(filename string) (name, version string) {
|
|
// Remove extension
|
|
base := filename
|
|
for _, ext := range []string{".conda", ".tar.bz2"} {
|
|
if strings.HasSuffix(base, ext) {
|
|
base = strings.TrimSuffix(base, ext)
|
|
break
|
|
}
|
|
}
|
|
|
|
// Split by hyphens, the format is name-version-build
|
|
// The name can contain hyphens, so we need to find version-build at the end
|
|
parts := strings.Split(base, "-")
|
|
if len(parts) < minCondaParts {
|
|
return "", ""
|
|
}
|
|
|
|
// Build is the last part, version is second to last
|
|
// Everything before is the name
|
|
build := parts[len(parts)-1]
|
|
version = parts[len(parts)-2]
|
|
name = strings.Join(parts[:len(parts)-2], "-")
|
|
|
|
// Version should start with a digit
|
|
if len(version) == 0 || version[0] < '0' || version[0] > '9' {
|
|
return "", ""
|
|
}
|
|
|
|
// Include build in version for uniqueness
|
|
_ = build
|
|
|
|
return name, version
|
|
}
|
|
|
|
// proxyUpstream forwards a request to Anaconda without caching.
|
|
func (h *CondaHandler) proxyUpstream(w http.ResponseWriter, r *http.Request) {
|
|
h.proxy.ProxyUpstream(w, r, h.upstreamURL+r.URL.Path, []string{"Accept-Encoding"})
|
|
}
|