Watch
1
0
Fork
You've already forked pkg-proxy
1
mirror of https://github.com/git-pkgs/proxy.git synced 2026-11-09 07:39:19 -05:00
pkg-proxy/internal/mirror/files.go
Simon Chrzanowski 9458dc84e1 Share one PURL-to-handler mapping between the mirror and the inventory
packageurl.HandlerPackage returns the ecosystem and name the handlers store
a package URL under. The mirror and the inventory both use it, which
settles where they disagreed: distribution namespaces of apk, alpm, bsd,
deb and rpm are dropped, PyPI and NuGet names use purl's comparison form,
and Maven names are group:artifact as the handler stores them, where the
mirror used group/artifact.
2026-10-30 19:29:11 +01:00

118 lines
3.7 KiB
Go

package mirror
import (
"fmt"
"slices"
"strings"
"github.com/git-pkgs/proxy/internal/handler"
"github.com/git-pkgs/registries"
)
// fileEcosystem describes an ecosystem whose versions publish several files,
// each mirrored on its own.
type fileEcosystem struct {
// qualifiers are the PURL qualifiers that select files. A file is
// mirrored when each one given matches the value attrs reports for it.
qualifiers []string
attrs func(file registries.Artifact) map[string]string
// cacheKey returns the name, version and filename the download handler
// caches file under, or false when the handler would not cache it.
cacheKey func(pv PackageVersion, file registries.Artifact) (name, version, filename string, ok bool)
// check refuses a package the ecosystem can't mirror, before its files
// are listed.
check func(pv PackageVersion) error
}
var fileEcosystems = map[string]fileEcosystem{
"pypi": {
qualifiers: []string{"file_name"},
attrs: func(file registries.Artifact) map[string]string {
return map[string]string{"file_name": file.Filename}
},
cacheKey: func(pv PackageVersion, file registries.Artifact) (string, string, string, bool) {
name, version := handler.PyPIFilenameIdentity(file.Filename)
if name == "" {
name, version = pv.Name, pv.Version
}
return name, version, file.Filename, true
},
},
"conda": {
qualifiers: []string{"subdir", "build", "type"},
attrs: func(file registries.Artifact) map[string]string {
subdir, _ := file.Metadata["subdir"].(string)
build, _ := file.Metadata["build"].(string)
return map[string]string{"subdir": subdir, "build": build, "type": condaFileType(file.Filename)}
},
cacheKey: func(pv PackageVersion, file registries.Artifact) (string, string, string, bool) {
channel, _, _ := strings.Cut(pv.Name, "/")
subdir, _ := file.Metadata["subdir"].(string)
name, version := handler.CondaFilenameIdentity(file.Filename)
if name == "" || subdir == "" || condaFileType(file.Filename) == "" {
return "", "", "", false
}
return channel + "/" + name, version, subdir + "/" + file.Filename, true
},
check: func(pv PackageVersion) error {
if !strings.Contains(pv.Name, "/") {
return fmt.Errorf("conda packages need a channel, such as pkg:conda/conda-forge/%s", pv.Name)
}
return nil
},
},
}
// condaFileType returns the PURL type qualifier of a conda package file.
func condaFileType(filename string) string {
switch {
case strings.HasSuffix(filename, ".conda"):
return "conda"
case strings.HasSuffix(filename, ".tar.bz2"):
return "tar.bz2"
default:
return ""
}
}
// selection returns the qualifiers of pv that select files, as
// "key=value" pairs, for error messages.
func (e fileEcosystem) selection(pv PackageVersion) string {
var pairs []string
for _, key := range e.qualifiers {
if value := pv.Qualifiers[key]; value != "" {
pairs = append(pairs, key+"="+value)
}
}
return strings.Join(pairs, " ")
}
// selects reports whether file matches every selecting qualifier of pv.
func (e fileEcosystem) selects(pv PackageVersion, file registries.Artifact) bool {
attrs := e.attrs(file)
for _, key := range e.qualifiers {
if want := pv.Qualifiers[key]; want != "" && attrs[key] != want {
return false
}
}
return true
}
// fileQualifiers keeps the qualifiers of a PURL that select files of its
// ecosystem, or returns nil.
func fileQualifiers(ecosystem string, qualifiers map[string]string) map[string]string {
e, ok := fileEcosystems[ecosystem]
if !ok {
return nil
}
var kept map[string]string
for key, value := range qualifiers {
if value != "" && slices.Contains(e.qualifiers, key) {
if kept == nil {
kept = map[string]string{}
}
kept[key] = value
}
}
return kept
}