refactor: modular local provider interfaces
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/build Pipeline was successful

Move package-type-specific local repo logic into provider packages via
optional interfaces, eliminating switch statements from handlers.

- provider.LocalUploader: ValidateUpload + UploadResponse
- provider.LocalIndexer: ServeLocalIndex + GenerateLocalIndex
- provider.FileStore: interface for querying local files (implemented
  by database.DB)

PyPI and Terraform providers now implement both interfaces. The local
handler and v1 proxy use type assertions to dispatch — adding a new
local repo type only requires implementing the interfaces in its
provider package, no handler changes needed.

local.go: 468 → 163 lines (removed all PyPI/Terraform specifics)
proxy.go: 211 → 136 lines (removed switch + helper methods)
engine.go: removed LocalIndexGenerator, uses provider.LocalIndexer
This commit is contained in:
2026-06-23 22:41:29 +10:00
parent 7b13644421
commit 5b830fc3de
8 changed files with 400 additions and 369 deletions
+3 -55
View File
@@ -6,8 +6,6 @@ import (
"io"
"log/slog"
"net/http"
"regexp"
"strings"
"github.com/go-chi/chi/v5"
@@ -17,11 +15,8 @@ import (
"git.unkin.net/unkin/artifactapi/internal/proxy"
"git.unkin.net/unkin/artifactapi/internal/storage"
"git.unkin.net/unkin/artifactapi/internal/virtual"
"git.unkin.net/unkin/artifactapi/pkg/models"
)
var semverRe = regexp.MustCompile(`^[0-9]+\.[0-9]+\.[0-9]+(?:-[a-zA-Z0-9.]+)?$`)
type ProxyHandler struct {
engine *proxy.Engine
virtualEngine *virtual.Engine
@@ -115,13 +110,9 @@ func (h *ProxyHandler) handleLocal(w http.ResponseWriter, r *http.Request) {
return
}
switch remote.PackageType {
case models.PackageTerraform:
if h.serveTerraformMirror(w, r, remote, path) {
return
}
case models.PackagePyPI:
if h.servePyPIMirror(w, r, remote, path) {
prov, _ := provider.Get(remote.PackageType)
if indexer, ok := prov.(provider.LocalIndexer); ok {
if indexer.ServeLocalIndex(w, r, h.db, remote.Name, path) {
return
}
}
@@ -129,49 +120,6 @@ func (h *ProxyHandler) handleLocal(w http.ResponseWriter, r *http.Request) {
h.serveLocalFile(w, r, localName, path)
}
func (h *ProxyHandler) serveTerraformMirror(w http.ResponseWriter, r *http.Request, remote *models.Remote, path string) bool {
parts := strings.Split(path, "/")
if len(parts) < 3 {
return false
}
namespace, typeName := parts[0], parts[1]
tail := parts[2]
if tail == "index.json" {
h.local.ServeTerraformIndex(w, r, remote.Name, namespace, typeName)
return true
}
if strings.HasSuffix(tail, ".json") {
version := strings.TrimSuffix(tail, ".json")
if semverRe.MatchString(version) {
h.local.ServeTerraformVersionDoc(w, r, remote.Name, namespace, typeName, version)
return true
}
}
return false
}
func (h *ProxyHandler) servePyPIMirror(w http.ResponseWriter, r *http.Request, remote *models.Remote, path string) bool {
if path == "simple" || path == "simple/" {
h.local.ServePyPIIndex(w, r, remote.Name)
return true
}
if strings.HasPrefix(path, "simple/") {
pkg := strings.TrimPrefix(path, "simple/")
pkg = strings.TrimSuffix(pkg, "/")
if pkg != "" && !strings.Contains(pkg, "/") {
h.local.ServePyPIPackageIndex(w, r, remote.Name, pkg)
return true
}
}
return false
}
func (h *ProxyHandler) serveLocalFile(w http.ResponseWriter, r *http.Request, repoName, path string) {
file, err := h.db.GetLocalFile(r.Context(), repoName, path)
if err != nil {