Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Resolution order: package override, then ecosystem override, then global default
| Conda | Python/R | Yes | ✓ |
| CRAN | R | | ✓ |
| Julia | Julia | | ✓ |
| Swift | Swift | | ✓ |
| Container | Docker/OCI | | ✓ |
| Debian | Debian/Ubuntu | | ✓ |
| RPM | RHEL/Fedora | | ✓ |
Expand All @@ -47,7 +48,6 @@ Resolution order: package override, then ecosystem override, then global default
| Chef | Chef | | ✗ |
| Generic | Any | | ✗ |
| Helm | Kubernetes | | ✗ |
| Swift | Swift | | ✗ |
| Vagrant | Vagrant | | ✗ |

Cooldown requires publish timestamps in metadata. Registries without a "Yes" in the cooldown column either don't expose timestamps or haven't been wired up yet.
Expand Down Expand Up @@ -340,6 +340,25 @@ ENV["JULIA_PKG_SERVER"] = "http://localhost:8080/julia"
using Pkg; Pkg.update()
```

### Swift

Configure the proxy as the default registry for the current Swift package:

```bash
swift package-registry set --allow-insecure-http http://localhost:8080/swift
```

Registry dependencies use their scoped package identifier in `Package.swift`:

```swift
dependencies: [
.package(id: "apple.swift-argument-parser", from: "1.2.0")
]
```

The proxy supports dependency resolution and source downloads. Publishing with
`swift package-registry publish` is not supported.

### Docker / Container Registry

Configure Docker to use the proxy as a registry mirror in `/etc/docker/daemon.json`:
Expand Down Expand Up @@ -473,6 +492,7 @@ PROXY_DATABASE_URL=postgres://user:pass@localhost/proxy?sslmode=disable
PROXY_LOG_LEVEL=info
PROXY_LOG_FORMAT=text
PROXY_ACCESS_LOG_PATH=/var/log/proxy/access.jsonl
PROXY_UPSTREAM_SWIFT=https://tuist.dev/api/registry/swift
```

### Configuration File
Expand Down Expand Up @@ -500,6 +520,7 @@ access_log:
upstream:
npm: "https://registry.npmjs.org"
cargo: "https://index.crates.io"
swift: "https://tuist.dev/api/registry/swift"

# Optional: version cooldown (see above)
cooldown:
Expand Down Expand Up @@ -669,6 +690,7 @@ Recently cached:
| `GET /conda/*` | Conda/Anaconda protocol |
| `GET /cran/*` | CRAN (R) protocol |
| `GET /julia/*` | Julia Pkg server protocol |
| `GET /swift/*` | Swift Package Registry v1 protocol |
| `GET /helm/{repository}/*` | HTTP Helm chart repository protocol |
| `GET /v2/*` | OCI/Docker registry protocol |
| `GET /debian/*` | Debian/APT repository protocol |
Expand Down
1 change: 1 addition & 0 deletions cmd/proxy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ func runServe() {
fmt.Fprintf(os.Stderr, " PROXY_ACCESS_LOG_PATH JSONL access log path\n")
fmt.Fprintf(os.Stderr, " PROXY_UPSTREAM_MAVEN Maven repository upstream URL\n")
fmt.Fprintf(os.Stderr, " PROXY_UPSTREAM_GRADLE_PLUGIN_PORTAL Gradle Plugin Portal upstream URL\n")
fmt.Fprintf(os.Stderr, " PROXY_UPSTREAM_SWIFT Swift Package Registry upstream URL\n")
fmt.Fprintf(os.Stderr, " PROXY_GRADLE_BUILD_CACHE_READ_ONLY Disable Gradle PUT uploads\n")
fmt.Fprintf(os.Stderr, " PROXY_GRADLE_BUILD_CACHE_MAX_UPLOAD_SIZE Max Gradle PUT request body size\n")
fmt.Fprintf(os.Stderr, " PROXY_GRADLE_BUILD_CACHE_MAX_AGE Gradle cache max age eviction\n")
Expand Down
3 changes: 3 additions & 0 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ upstream:
# Cargo crate download URL
cargo_download: "https://static.crates.io/crates"

# Swift Package Registry URL (used by /swift endpoint)
swift: "https://tuist.dev/api/registry/swift"

# Debian/APT repository URL (used by /debian endpoint)
debian: "http://deb.debian.org/debian"

Expand Down
4 changes: 4 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,10 @@ HTTP protocol handlers for each registry type.
- `handleIndex()` - Proxy sparse index
- `handleDownload()` - Serve cached crate

**SwiftHandler:**
- Proxies the Swift Package Registry v1 read endpoints
- Rewrites release URLs and caches source archives

### `internal/server`

HTTP server setup, web UI, and API handlers.
Expand Down
1 change: 1 addition & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ upstream:
gradle_plugin_portal: "https://plugins.gradle.org/m2"
cargo: "https://index.crates.io"
cargo_download: "https://static.crates.io/crates"
swift: "https://tuist.dev/api/registry/swift"

# Named HTTP Helm chart repositories, served at /helm/{name}/.
helm:
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ require (
github.com/git-pkgs/cooldown v0.1.1
github.com/git-pkgs/enrichment v0.6.5
github.com/git-pkgs/magic v0.2.0
github.com/git-pkgs/purl v0.1.16
github.com/git-pkgs/purl v0.1.17
github.com/git-pkgs/registries v0.7.0
github.com/git-pkgs/spdx v0.3.1
github.com/git-pkgs/vers v0.3.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,8 @@ github.com/git-pkgs/packageurl-go v0.3.1 h1:WM3RBABQZLaRBxgKyYughc3cVBE8KyQxbSC6
github.com/git-pkgs/packageurl-go v0.3.1/go.mod h1:rcIxiG37BlQLB6FZfgdj9Fm7yjhRQd3l+5o7J0QPAk4=
github.com/git-pkgs/pom v0.1.5 h1:TGT8Az2OMxGWsXnSagtUMGzZm7Oax8HrSCteA+mi0qY=
github.com/git-pkgs/pom v0.1.5/go.mod h1:ufdMBe1lKzqOeP9IUb9NPZ458xKV8E8NvuyBMxOfwIk=
github.com/git-pkgs/purl v0.1.16 h1:VAX6tv0hhdTENbkrGMoPZbOAl1Y8U1/ZnzoCsYuNBYM=
github.com/git-pkgs/purl v0.1.16/go.mod h1:7u7ora8tQdrkS7Auclr5v8dCJdjN4ej6AbrvYZi2b7k=
github.com/git-pkgs/purl v0.1.17 h1:oRSd8tqllTLl74Wa4WnuqU500hXd9OdUnImOEswQUVE=
github.com/git-pkgs/purl v0.1.17/go.mod h1:7u7ora8tQdrkS7Auclr5v8dCJdjN4ej6AbrvYZi2b7k=
github.com/git-pkgs/registries v0.7.0 h1:+LbOOMHbvjmXGfsi88hcGH+SfTXYsXA3UY5KYI5mB7s=
github.com/git-pkgs/registries v0.7.0/go.mod h1:VCD4q+ZW0fInopzseg9rAmBEL553R2JQe60UHXtv26w=
github.com/git-pkgs/spdx v0.3.1 h1:58JPY5X9pYpXvnzzZIgehItlBykeOOw52pNc4OBcS+c=
Expand Down
10 changes: 10 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ import (
"gopkg.in/yaml.v3"
)

// DefaultSwiftUpstream is the Swift Package Registry used when none is configured.
const DefaultSwiftUpstream = "https://tuist.dev/api/registry/swift"

// Config holds all configuration for the proxy server.
type Config struct {
// Listen is the address to listen on (e.g., ":8080", "127.0.0.1:8080").
Expand Down Expand Up @@ -313,6 +316,10 @@ type UpstreamConfig struct {
// Default: https://static.crates.io/crates
CargoDownload string `json:"cargo_download" yaml:"cargo_download"`

// Swift is the upstream Swift Package Registry URL.
// Default: https://tuist.dev/api/registry/swift
Swift string `json:"swift" yaml:"swift"`

// Debian is the upstream APT repository base URL.
// Example: http://archive.ubuntu.com/ubuntu would get Ubuntu.
// Default: http://deb.debian.org/debian
Expand Down Expand Up @@ -475,6 +482,7 @@ func Default() *Config {
GradlePluginPortal: "https://plugins.gradle.org/m2",
Cargo: "https://index.crates.io",
CargoDownload: "https://static.crates.io/crates",
Swift: DefaultSwiftUpstream,
Debian: "http://deb.debian.org/debian",
},
Gradle: GradleConfig{
Expand Down Expand Up @@ -546,6 +554,7 @@ func setEnvBool(dst *bool, key string) {
// - PROXY_LOG_LEVEL
// - PROXY_LOG_FORMAT
// - PROXY_ACCESS_LOG_PATH
// - PROXY_UPSTREAM_SWIFT
// - PROXY_HEALTH_STORAGE_PROBE_INTERVAL
func (c *Config) LoadFromEnv() {
setEnvString(&c.Listen, "PROXY_LISTEN")
Expand All @@ -565,6 +574,7 @@ func (c *Config) LoadFromEnv() {
setEnvString(&c.AccessLog.Path, "PROXY_ACCESS_LOG_PATH")
setEnvString(&c.Upstream.Maven, "PROXY_UPSTREAM_MAVEN")
setEnvString(&c.Upstream.GradlePluginPortal, "PROXY_UPSTREAM_GRADLE_PLUGIN_PORTAL")
setEnvString(&c.Upstream.Swift, "PROXY_UPSTREAM_SWIFT")
setEnvString(&c.Upstream.Debian, "PROXY_UPSTREAM_DEBIAN")
setEnvString(&c.Cooldown.Default, "PROXY_COOLDOWN_DEFAULT")
setEnvBool(&c.CacheMetadata, "PROXY_CACHE_METADATA")
Expand Down
7 changes: 7 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ func TestDefault(t *testing.T) {
if cfg.Upstream.GradlePluginPortal != "https://plugins.gradle.org/m2" {
t.Errorf("Upstream.GradlePluginPortal = %q, want %q", cfg.Upstream.GradlePluginPortal, "https://plugins.gradle.org/m2")
}
if cfg.Upstream.Swift != "https://tuist.dev/api/registry/swift" {
t.Errorf("Upstream.Swift = %q, want %q", cfg.Upstream.Swift, "https://tuist.dev/api/registry/swift")
}
if cfg.Upstream.Debian != "http://deb.debian.org/debian" {
t.Errorf("Upstream.Debian = %q, want %q", cfg.Upstream.Debian, "http://deb.debian.org/debian")
}
Expand Down Expand Up @@ -286,6 +289,7 @@ func TestLoadFromEnv(t *testing.T) {
t.Setenv("PROXY_ACCESS_LOG_PATH", "/tmp/proxy-access.jsonl")
t.Setenv("PROXY_UPSTREAM_MAVEN", "https://maven.example.com/repository/maven-public")
t.Setenv("PROXY_UPSTREAM_GRADLE_PLUGIN_PORTAL", "https://plugins.example.com/m2")
t.Setenv("PROXY_UPSTREAM_SWIFT", "https://swift.example.com/registry")
t.Setenv("PROXY_UPSTREAM_DEBIAN", "http://archive.ubuntu.com/ubuntu")
t.Setenv("PROXY_GRADLE_BUILD_CACHE_READ_ONLY", "true")
t.Setenv("PROXY_GRADLE_BUILD_CACHE_MAX_UPLOAD_SIZE", "32MB")
Expand Down Expand Up @@ -319,6 +323,9 @@ func TestLoadFromEnv(t *testing.T) {
if cfg.Upstream.GradlePluginPortal != "https://plugins.example.com/m2" {
t.Errorf("Upstream.GradlePluginPortal = %q, want %q", cfg.Upstream.GradlePluginPortal, "https://plugins.example.com/m2")
}
if cfg.Upstream.Swift != "https://swift.example.com/registry" {
t.Errorf("Upstream.Swift = %q, want %q", cfg.Upstream.Swift, "https://swift.example.com/registry")
}
if cfg.Upstream.Debian != "http://archive.ubuntu.com/ubuntu" {
t.Errorf("Upstream.Debian = %q, want %q", cfg.Upstream.Debian, "http://archive.ubuntu.com/ubuntu")
}
Expand Down
54 changes: 41 additions & 13 deletions internal/enrichment/enrichment.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"sync"
"time"

"github.com/git-pkgs/proxy/internal/packageurl"
"github.com/git-pkgs/purl"
"github.com/git-pkgs/registries"
_ "github.com/git-pkgs/registries/all" // Import all registry implementations
Expand Down Expand Up @@ -67,7 +68,10 @@ type VulnInfo struct {

// EnrichPackage fetches metadata for a package from registry APIs.
func (s *Service) EnrichPackage(ctx context.Context, ecosystem, name string) (*PackageInfo, error) {
purlStr := purl.MakePURLString(ecosystem, name, "")
purlStr := packageurl.MakeString(ecosystem, name, "")
if purlStr == "" {
return nil, nil
}

pkg, err := registries.FetchPackageFromPURL(ctx, purlStr, s.regClient)
if err != nil {
Expand Down Expand Up @@ -102,7 +106,10 @@ func (s *Service) EnrichPackage(ctx context.Context, ecosystem, name string) (*P

// EnrichVersion fetches metadata for a specific package version.
func (s *Service) EnrichVersion(ctx context.Context, ecosystem, name, version string) (*VersionInfo, error) {
purlStr := purl.MakePURLString(ecosystem, name, version)
purlStr := packageurl.MakeString(ecosystem, name, version)
if purlStr == "" {
return nil, nil
}

ver, err := registries.FetchVersionFromPURL(ctx, purlStr, s.regClient)
if err != nil {
Expand Down Expand Up @@ -134,9 +141,14 @@ func (s *Service) EnrichVersion(ctx context.Context, ecosystem, name, version st

// BulkEnrichPackages fetches metadata for multiple packages in parallel.
func (s *Service) BulkEnrichPackages(ctx context.Context, packages []struct{ Ecosystem, Name string }) map[string]*PackageInfo {
purls := make([]string, len(packages))
for i, pkg := range packages {
purls[i] = purl.MakePURLString(pkg.Ecosystem, pkg.Name, "")
purls := make([]string, 0, len(packages))
for _, pkg := range packages {
if purlStr := packageurl.MakeString(pkg.Ecosystem, pkg.Name, ""); purlStr != "" {
purls = append(purls, purlStr)
}
}
if len(purls) == 0 {
return map[string]*PackageInfo{}
}

pkgData := registries.BulkFetchPackages(ctx, purls, s.regClient)
Expand All @@ -147,7 +159,10 @@ func (s *Service) BulkEnrichPackages(ctx context.Context, packages []struct{ Eco
continue
}

p, _ := purl.Parse(purlStr)
p, err := purl.Parse(purlStr)
if err != nil {
continue
}
info := &PackageInfo{
Ecosystem: p.Type,
Name: pkg.Name,
Expand All @@ -174,7 +189,10 @@ func (s *Service) BulkEnrichPackages(ctx context.Context, packages []struct{ Eco

// CheckVulnerabilities queries for vulnerabilities affecting a package version.
func (s *Service) CheckVulnerabilities(ctx context.Context, ecosystem, name, version string) ([]VulnInfo, error) {
p := purl.MakePURL(ecosystem, name, version)
p := packageurl.Make(ecosystem, name, version)
if p == nil {
return nil, nil
}

vulnList, err := s.vulnSource.Query(ctx, p)
if err != nil {
Expand Down Expand Up @@ -203,20 +221,27 @@ func (s *Service) CheckVulnerabilities(ctx context.Context, ecosystem, name, ver

// BulkCheckVulnerabilities queries vulnerabilities for multiple package versions.
func (s *Service) BulkCheckVulnerabilities(ctx context.Context, packages []struct{ Ecosystem, Name, Version string }) (map[string][]VulnInfo, error) {
purls := make([]*purl.PURL, len(packages))
purls := make([]*purl.PURL, 0, len(packages))
supported := make([]int, 0, len(packages))
for i, pkg := range packages {
purls[i] = purl.MakePURL(pkg.Ecosystem, pkg.Name, pkg.Version)
if packagePURL := packageurl.Make(pkg.Ecosystem, pkg.Name, pkg.Version); packagePURL != nil {
purls = append(purls, packagePURL)
supported = append(supported, i)
}
}
result := make(map[string][]VulnInfo, len(purls))
if len(purls) == 0 {
return result, nil
}

vulnResults, err := s.vulnSource.QueryBatch(ctx, purls)
if err != nil {
return nil, err
}

result := make(map[string][]VulnInfo, len(packages))
for i, vulnList := range vulnResults {
pkg := packages[i]
key := purl.MakePURLString(pkg.Ecosystem, pkg.Name, pkg.Version)
pkg := packages[supported[i]]
key := purls[i].String()

var infos []VulnInfo
for _, v := range vulnList {
Expand Down Expand Up @@ -248,7 +273,10 @@ func (s *Service) IsOutdated(currentVersion, latestVersion string) bool {

// GetLatestVersion fetches the latest version for a package.
func (s *Service) GetLatestVersion(ctx context.Context, ecosystem, name string) (string, error) {
purlStr := purl.MakePURLString(ecosystem, name, "")
purlStr := packageurl.MakeString(ecosystem, name, "")
if purlStr == "" {
return "", nil
}

latest, err := registries.FetchLatestVersionFromPURL(ctx, purlStr, s.regClient)
if err != nil {
Expand Down
Loading