feat: virtual PyPI repos can merge local + remote members (#51)
ci/woodpecker/tag/docker Pipeline was successful
ci/woodpecker/tag/docker Pipeline was successful
## Summary - Virtual engine detects local members and generates indexes in-memory - MemberIndex.RepoType drives correct URL prefix in merged output - PyPI merger rewrites links to /api/v1/local/ or /api/v1/remote/ appropriately - Includes local PyPI support (cherry-picked from #50) ## Test plan - [x] Upload wheel to local PyPI → install from direct local URL - [x] Create virtual with local + remote → install from virtual URL - [x] Both paths produce correct absolute download URLs Reviewed-on: #51 Co-authored-by: Ben Vincent <ben@unkin.net> Co-committed-by: Ben Vincent <ben@unkin.net>
This commit was merged in pull request #51.
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"git.unkin.net/unkin/artifactapi/internal/database"
|
||||
@@ -13,13 +14,18 @@ import (
|
||||
"git.unkin.net/unkin/artifactapi/pkg/models"
|
||||
)
|
||||
|
||||
type LocalIndexGenerator interface {
|
||||
GeneratePyPIPackageHTML(ctx context.Context, repoName, packageName string) ([]byte, error)
|
||||
}
|
||||
|
||||
type Engine struct {
|
||||
db *database.DB
|
||||
proxyEngine *proxy.Engine
|
||||
localGen LocalIndexGenerator
|
||||
}
|
||||
|
||||
func NewEngine(db *database.DB, proxyEngine *proxy.Engine) *Engine {
|
||||
return &Engine{db: db, proxyEngine: proxyEngine}
|
||||
func NewEngine(db *database.DB, proxyEngine *proxy.Engine, localGen LocalIndexGenerator) *Engine {
|
||||
return &Engine{db: db, proxyEngine: proxyEngine, localGen: localGen}
|
||||
}
|
||||
|
||||
func (e *Engine) Fetch(ctx context.Context, virt models.Virtual, path string, proxyBaseURL string) ([]byte, string, error) {
|
||||
@@ -73,6 +79,16 @@ func (e *Engine) fetchMemberIndexes(ctx context.Context, virt models.Virtual, pa
|
||||
return
|
||||
}
|
||||
|
||||
if remote.RepoType == models.RepoTypeLocal {
|
||||
body, err := e.fetchLocalIndex(ctx, *remote, virt.PackageType, path)
|
||||
if err != nil {
|
||||
results[idx] = result{err: fmt.Errorf("local index %q: %w", name, err)}
|
||||
return
|
||||
}
|
||||
results[idx] = result{index: MemberIndex{RemoteName: name, RepoType: remote.RepoType, Body: body}}
|
||||
return
|
||||
}
|
||||
|
||||
prov, err := provider.Get(remote.PackageType)
|
||||
if err != nil {
|
||||
results[idx] = result{err: fmt.Errorf("provider %q: %w", remote.PackageType, err)}
|
||||
@@ -92,7 +108,7 @@ func (e *Engine) fetchMemberIndexes(ctx context.Context, virt models.Virtual, pa
|
||||
return
|
||||
}
|
||||
|
||||
results[idx] = result{index: MemberIndex{RemoteName: name, Body: body}}
|
||||
results[idx] = result{index: MemberIndex{RemoteName: name, RepoType: remote.RepoType, Body: body}}
|
||||
}(i, memberName)
|
||||
}
|
||||
|
||||
@@ -109,3 +125,20 @@ func (e *Engine) fetchMemberIndexes(ctx context.Context, virt models.Virtual, pa
|
||||
|
||||
return members, nil
|
||||
}
|
||||
|
||||
func (e *Engine) fetchLocalIndex(ctx context.Context, remote models.Remote, packageType models.PackageType, path string) ([]byte, error) {
|
||||
switch packageType {
|
||||
case models.PackagePyPI:
|
||||
if e.localGen == nil {
|
||||
return nil, fmt.Errorf("no local index generator configured")
|
||||
}
|
||||
parts := strings.SplitN(strings.TrimPrefix(path, "simple/"), "/", 2)
|
||||
pkgName := strings.TrimSuffix(parts[0], "/")
|
||||
if pkgName == "" {
|
||||
return nil, fmt.Errorf("cannot determine package name from path %q", path)
|
||||
}
|
||||
return e.localGen.GeneratePyPIPackageHTML(ctx, remote.Name, pkgName)
|
||||
default:
|
||||
return nil, fmt.Errorf("local index generation not supported for %q", packageType)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user