mirror of
https://github.com/git-pkgs/proxy.git
synced 2026-08-23 12:24:57 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a64d9ad9a |
||
|
|
7f04f4faac |
||
|
|
535617197d |
||
|
|
92cae1e1e7 |
||
|
|
53f2fee3c3 |
||
|
|
0af8d6b654 |
||
|
|
38264bdd65 |
36 changed files with 2842 additions and 403 deletions
|
|
@ -39,7 +39,7 @@ proxy/
|
||||||
│ │ └── queries.go # CRUD operations
|
│ │ └── queries.go # CRUD operations
|
||||||
│ ├── storage/ # Artifact file storage
|
│ ├── storage/ # Artifact file storage
|
||||||
│ │ ├── storage.go # Storage interface
|
│ │ ├── storage.go # Storage interface
|
||||||
│ │ └── blob.go # gocloud.dev/blob backends (file, S3, Azure)
|
│ │ └── filesystem.go # Local filesystem impl
|
||||||
│ ├── upstream/ # Upstream registry clients
|
│ ├── upstream/ # Upstream registry clients
|
||||||
│ │ ├── fetcher.go # HTTP artifact fetching
|
│ │ ├── fetcher.go # HTTP artifact fetching
|
||||||
│ │ └── resolver.go # Download URL resolution
|
│ │ └── resolver.go # Download URL resolution
|
||||||
|
|
@ -72,7 +72,7 @@ Key types:
|
||||||
|
|
||||||
### `internal/storage`
|
### `internal/storage`
|
||||||
|
|
||||||
Artifact file storage abstraction backed by `gocloud.dev/blob`. Supports local filesystem (`file://`), S3 (`s3://`), and Azure (`azblob://`) URLs.
|
Artifact file storage abstraction. Currently implements local filesystem storage. Designed to allow future backends (S3, GCS).
|
||||||
|
|
||||||
Interface:
|
Interface:
|
||||||
```go
|
```go
|
||||||
|
|
|
||||||
24
README.md
24
README.md
|
|
@ -39,6 +39,7 @@ Resolution order: package override, then ecosystem override, then global default
|
||||||
| Conda | Python/R | Yes | ✓ |
|
| Conda | Python/R | Yes | ✓ |
|
||||||
| CRAN | R | | ✓ |
|
| CRAN | R | | ✓ |
|
||||||
| Julia | Julia | | ✓ |
|
| Julia | Julia | | ✓ |
|
||||||
|
| Swift | Swift | | ✓ |
|
||||||
| Container | Docker/OCI | | ✓ |
|
| Container | Docker/OCI | | ✓ |
|
||||||
| Debian | Debian/Ubuntu | | ✓ |
|
| Debian | Debian/Ubuntu | | ✓ |
|
||||||
| RPM | RHEL/Fedora | | ✓ |
|
| RPM | RHEL/Fedora | | ✓ |
|
||||||
|
|
@ -47,7 +48,6 @@ Resolution order: package override, then ecosystem override, then global default
|
||||||
| Chef | Chef | | ✗ |
|
| Chef | Chef | | ✗ |
|
||||||
| Generic | Any | | ✗ |
|
| Generic | Any | | ✗ |
|
||||||
| Helm | Kubernetes | | ✗ |
|
| Helm | Kubernetes | | ✗ |
|
||||||
| Swift | Swift | | ✗ |
|
|
||||||
| Vagrant | Vagrant | | ✗ |
|
| 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.
|
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.
|
||||||
|
|
@ -340,6 +340,25 @@ ENV["JULIA_PKG_SERVER"] = "http://localhost:8080/julia"
|
||||||
using Pkg; Pkg.update()
|
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
|
### Docker / Container Registry
|
||||||
|
|
||||||
Configure Docker to use the proxy as a registry mirror in `/etc/docker/daemon.json`:
|
Configure Docker to use the proxy as a registry mirror in `/etc/docker/daemon.json`:
|
||||||
|
|
@ -473,6 +492,7 @@ PROXY_DATABASE_URL=postgres://user:pass@localhost/proxy?sslmode=disable
|
||||||
PROXY_LOG_LEVEL=info
|
PROXY_LOG_LEVEL=info
|
||||||
PROXY_LOG_FORMAT=text
|
PROXY_LOG_FORMAT=text
|
||||||
PROXY_ACCESS_LOG_PATH=/var/log/proxy/access.jsonl
|
PROXY_ACCESS_LOG_PATH=/var/log/proxy/access.jsonl
|
||||||
|
PROXY_UPSTREAM_SWIFT=https://tuist.dev/api/registry/swift
|
||||||
```
|
```
|
||||||
|
|
||||||
### Configuration File
|
### Configuration File
|
||||||
|
|
@ -500,6 +520,7 @@ access_log:
|
||||||
upstream:
|
upstream:
|
||||||
npm: "https://registry.npmjs.org"
|
npm: "https://registry.npmjs.org"
|
||||||
cargo: "https://index.crates.io"
|
cargo: "https://index.crates.io"
|
||||||
|
swift: "https://tuist.dev/api/registry/swift"
|
||||||
|
|
||||||
# Optional: version cooldown (see above)
|
# Optional: version cooldown (see above)
|
||||||
cooldown:
|
cooldown:
|
||||||
|
|
@ -669,6 +690,7 @@ Recently cached:
|
||||||
| `GET /conda/*` | Conda/Anaconda protocol |
|
| `GET /conda/*` | Conda/Anaconda protocol |
|
||||||
| `GET /cran/*` | CRAN (R) protocol |
|
| `GET /cran/*` | CRAN (R) protocol |
|
||||||
| `GET /julia/*` | Julia Pkg server protocol |
|
| `GET /julia/*` | Julia Pkg server protocol |
|
||||||
|
| `GET /swift/*` | Swift Package Registry v1 protocol |
|
||||||
| `GET /helm/{repository}/*` | HTTP Helm chart repository protocol |
|
| `GET /helm/{repository}/*` | HTTP Helm chart repository protocol |
|
||||||
| `GET /v2/*` | OCI/Docker registry protocol |
|
| `GET /v2/*` | OCI/Docker registry protocol |
|
||||||
| `GET /debian/*` | Debian/APT repository protocol |
|
| `GET /debian/*` | Debian/APT repository protocol |
|
||||||
|
|
|
||||||
|
|
@ -208,6 +208,7 @@ func runServe() {
|
||||||
fmt.Fprintf(os.Stderr, " PROXY_ACCESS_LOG_PATH JSONL access log path\n")
|
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_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_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_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_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")
|
fmt.Fprintf(os.Stderr, " PROXY_GRADLE_BUILD_CACHE_MAX_AGE Gradle cache max age eviction\n")
|
||||||
|
|
@ -275,10 +276,7 @@ func runServe() {
|
||||||
logger := setupLogger(cfg.Log.Level, cfg.Log.Format)
|
logger := setupLogger(cfg.Log.Level, cfg.Log.Format)
|
||||||
|
|
||||||
// Create and start server
|
// Create and start server
|
||||||
srv, err := server.New(cfg, logger, server.BuildInfo{
|
srv, err := server.New(cfg, logger)
|
||||||
Version: Version,
|
|
||||||
Commit: Commit,
|
|
||||||
})
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("failed to create server", "error", err)
|
logger.Error("failed to create server", "error", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,9 @@ upstream:
|
||||||
# Cargo crate download URL
|
# Cargo crate download URL
|
||||||
cargo_download: "https://static.crates.io/crates"
|
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/APT repository URL (used by /debian endpoint)
|
||||||
debian: "http://deb.debian.org/debian"
|
debian: "http://deb.debian.org/debian"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -269,6 +269,10 @@ HTTP protocol handlers for each registry type.
|
||||||
- `handleIndex()` - Proxy sparse index
|
- `handleIndex()` - Proxy sparse index
|
||||||
- `handleDownload()` - Serve cached crate
|
- `handleDownload()` - Serve cached crate
|
||||||
|
|
||||||
|
**SwiftHandler:**
|
||||||
|
- Proxies the Swift Package Registry v1 read endpoints
|
||||||
|
- Rewrites release URLs and caches source archives
|
||||||
|
|
||||||
### `internal/server`
|
### `internal/server`
|
||||||
|
|
||||||
HTTP server setup, web UI, and API handlers.
|
HTTP server setup, web UI, and API handlers.
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,7 @@ upstream:
|
||||||
gradle_plugin_portal: "https://plugins.gradle.org/m2"
|
gradle_plugin_portal: "https://plugins.gradle.org/m2"
|
||||||
cargo: "https://index.crates.io"
|
cargo: "https://index.crates.io"
|
||||||
cargo_download: "https://static.crates.io/crates"
|
cargo_download: "https://static.crates.io/crates"
|
||||||
|
swift: "https://tuist.dev/api/registry/swift"
|
||||||
|
|
||||||
# Named HTTP Helm chart repositories, served at /helm/{name}/.
|
# Named HTTP Helm chart repositories, served at /helm/{name}/.
|
||||||
helm:
|
helm:
|
||||||
|
|
|
||||||
158
go.mod
158
go.mod
|
|
@ -1,6 +1,6 @@
|
||||||
module github.com/git-pkgs/proxy
|
module github.com/git-pkgs/proxy
|
||||||
|
|
||||||
go 1.26.0
|
go 1.25.6
|
||||||
|
|
||||||
toolchain go1.26.6
|
toolchain go1.26.6
|
||||||
|
|
||||||
|
|
@ -13,7 +13,7 @@ require (
|
||||||
github.com/git-pkgs/integrity v0.1.1
|
github.com/git-pkgs/integrity v0.1.1
|
||||||
github.com/git-pkgs/magic v0.2.0
|
github.com/git-pkgs/magic v0.2.0
|
||||||
github.com/git-pkgs/purl v0.1.17
|
github.com/git-pkgs/purl v0.1.17
|
||||||
github.com/git-pkgs/registries v0.8.1
|
github.com/git-pkgs/registries v0.7.0
|
||||||
github.com/git-pkgs/spdx v0.3.1
|
github.com/git-pkgs/spdx v0.3.1
|
||||||
github.com/git-pkgs/vers v0.3.1
|
github.com/git-pkgs/vers v0.3.1
|
||||||
github.com/git-pkgs/vulns v0.2.2
|
github.com/git-pkgs/vulns v0.2.2
|
||||||
|
|
@ -32,48 +32,45 @@ require (
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
4d63.com/gocheckcompilerdirectives v1.4.0 // indirect
|
4d63.com/gocheckcompilerdirectives v1.3.0 // indirect
|
||||||
4d63.com/gochecknoglobals v0.2.2 // indirect
|
4d63.com/gochecknoglobals v0.2.2 // indirect
|
||||||
charm.land/lipgloss/v2 v2.0.6 // indirect
|
cloud.google.com/go/auth v0.18.2 // indirect
|
||||||
cloud.google.com/go/auth v0.21.0 // indirect
|
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||||
codeberg.org/chavacava/garif v0.2.0 // indirect
|
codeberg.org/chavacava/garif v0.2.0 // indirect
|
||||||
codeberg.org/polyfloyd/go-errorlint v1.9.0 // indirect
|
codeberg.org/polyfloyd/go-errorlint v1.9.0 // indirect
|
||||||
dev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect
|
dev.gaijin.team/go/exhaustruct/v4 v4.0.0 // indirect
|
||||||
dev.gaijin.team/go/exhaustruct/v5 v5.0.3 // indirect
|
dev.gaijin.team/go/golib v0.6.0 // indirect
|
||||||
dev.gaijin.team/go/golib v0.8.1 // indirect
|
|
||||||
github.com/4meepo/tagalign v1.4.3 // indirect
|
github.com/4meepo/tagalign v1.4.3 // indirect
|
||||||
github.com/Abirdcfly/dupword v0.1.8 // indirect
|
github.com/Abirdcfly/dupword v0.1.7 // indirect
|
||||||
github.com/AdminBenni/iota-mixing v1.0.0 // indirect
|
github.com/AdminBenni/iota-mixing v1.0.0 // indirect
|
||||||
github.com/AlwxSin/noinlineerr v1.0.6 // indirect
|
github.com/AlwxSin/noinlineerr v1.0.5 // indirect
|
||||||
github.com/Antonboom/errname v1.1.2 // indirect
|
github.com/Antonboom/errname v1.1.1 // indirect
|
||||||
github.com/Antonboom/nilnil v1.1.2 // indirect
|
github.com/Antonboom/nilnil v1.1.1 // indirect
|
||||||
github.com/Antonboom/testifylint v1.6.4 // indirect
|
github.com/Antonboom/testifylint v1.6.4 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4 // indirect
|
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.4 // indirect
|
||||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0 // indirect
|
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0 // indirect
|
||||||
github.com/ClickHouse/clickhouse-go-linter v1.2.1 // indirect
|
|
||||||
github.com/Djarvur/go-err113 v0.1.1 // indirect
|
github.com/Djarvur/go-err113 v0.1.1 // indirect
|
||||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||||
github.com/Masterminds/semver/v3 v3.5.0 // indirect
|
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
||||||
github.com/MirrexOne/unqueryvet v1.5.4 // indirect
|
github.com/MirrexOne/unqueryvet v1.5.3 // indirect
|
||||||
github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect
|
github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect
|
||||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||||
github.com/alecthomas/chroma/v2 v2.27.0 // indirect
|
github.com/alecthomas/chroma/v2 v2.23.1 // indirect
|
||||||
github.com/alecthomas/go-check-sumtype v0.3.1 // indirect
|
github.com/alecthomas/go-check-sumtype v0.3.1 // indirect
|
||||||
github.com/alexkohler/nakedret/v2 v2.0.6 // indirect
|
github.com/alexkohler/nakedret/v2 v2.0.6 // indirect
|
||||||
github.com/alexkohler/prealloc v1.1.0 // indirect
|
github.com/alexkohler/prealloc v1.0.2 // indirect
|
||||||
github.com/alfatraining/structtag v1.0.0 // indirect
|
github.com/alfatraining/structtag v1.0.0 // indirect
|
||||||
github.com/alingse/asasalint v0.0.11 // indirect
|
github.com/alingse/asasalint v0.0.11 // indirect
|
||||||
github.com/alingse/nilnesserr v0.2.0 // indirect
|
github.com/alingse/nilnesserr v0.2.0 // indirect
|
||||||
github.com/anchore/go-struct-converter v0.1.0 // indirect
|
github.com/anchore/go-struct-converter v0.1.0 // indirect
|
||||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||||
github.com/ashanbrown/forbidigo/v2 v2.3.1 // indirect
|
github.com/ashanbrown/forbidigo/v2 v2.3.0 // indirect
|
||||||
github.com/ashanbrown/makezero/v2 v2.2.1 // indirect
|
github.com/ashanbrown/makezero/v2 v2.1.0 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2 v1.41.9 // indirect
|
github.com/aws/aws-sdk-go-v2 v1.41.9 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.11 // indirect
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.11 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.32.20 // indirect
|
github.com/aws/aws-sdk-go-v2/config v1.32.20 // indirect
|
||||||
|
|
@ -93,49 +90,48 @@ require (
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.2 // indirect
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.2 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.42.3 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sts v1.42.3 // indirect
|
||||||
github.com/aws/smithy-go v1.26.0 // indirect
|
github.com/aws/smithy-go v1.26.0 // indirect
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bkielbasa/cyclop v1.2.3 // indirect
|
github.com/bkielbasa/cyclop v1.2.3 // indirect
|
||||||
github.com/blizzy78/varnamelen v0.8.0 // indirect
|
github.com/blizzy78/varnamelen v0.8.0 // indirect
|
||||||
github.com/bombsimon/wsl/v4 v4.7.0 // indirect
|
github.com/bombsimon/wsl/v4 v4.7.0 // indirect
|
||||||
github.com/bombsimon/wsl/v5 v5.9.0 // indirect
|
github.com/bombsimon/wsl/v5 v5.6.0 // indirect
|
||||||
github.com/breml/bidichk v0.3.3 // indirect
|
github.com/breml/bidichk v0.3.3 // indirect
|
||||||
github.com/breml/errchkjson v0.4.1 // indirect
|
github.com/breml/errchkjson v0.4.1 // indirect
|
||||||
github.com/butuzov/ireturn v0.4.1 // indirect
|
github.com/butuzov/ireturn v0.4.0 // indirect
|
||||||
github.com/butuzov/mirror v1.3.3 // indirect
|
github.com/butuzov/mirror v1.3.0 // indirect
|
||||||
github.com/catenacyber/perfsprint v0.10.1 // indirect
|
github.com/catenacyber/perfsprint v0.10.1 // indirect
|
||||||
github.com/ccojocar/zxcvbn-go v1.0.4 // indirect
|
github.com/ccojocar/zxcvbn-go v1.0.4 // indirect
|
||||||
github.com/cenk/backoff v2.2.1+incompatible // indirect
|
github.com/cenk/backoff v2.2.1+incompatible // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/charithe/durationcheck v0.0.11 // indirect
|
github.com/charithe/durationcheck v0.0.11 // indirect
|
||||||
github.com/charmbracelet/colorprofile v0.4.3 // indirect
|
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
||||||
github.com/charmbracelet/ultraviolet v0.0.0-20260811164956-006e29f97886 // indirect
|
github.com/charmbracelet/lipgloss v1.1.0 // indirect
|
||||||
github.com/charmbracelet/x/ansi v0.11.8 // indirect
|
github.com/charmbracelet/x/ansi v0.10.1 // indirect
|
||||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
|
||||||
github.com/charmbracelet/x/termios v0.1.1 // indirect
|
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||||
github.com/charmbracelet/x/windows v0.2.2 // indirect
|
|
||||||
github.com/ckaznocha/intrange v0.3.1 // indirect
|
github.com/ckaznocha/intrange v0.3.1 // indirect
|
||||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
|
||||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||||
github.com/curioswitch/go-reassign v0.3.0 // indirect
|
github.com/curioswitch/go-reassign v0.3.0 // indirect
|
||||||
github.com/daixiang0/gci v0.13.7 // indirect
|
github.com/daixiang0/gci v0.13.7 // indirect
|
||||||
github.com/dave/dst v0.27.3 // indirect
|
github.com/dave/dst v0.27.3 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/denis-tingaikin/go-header v0.5.0 // indirect
|
github.com/denis-tingaikin/go-header v0.5.0 // indirect
|
||||||
github.com/dlclark/regexp2/v2 v2.2.1 // indirect
|
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/ecosyste-ms/ecosystems-go v0.4.0 // indirect
|
github.com/ecosyste-ms/ecosystems-go v0.4.0 // indirect
|
||||||
github.com/ettle/strcase v0.2.0 // indirect
|
github.com/ettle/strcase v0.2.0 // indirect
|
||||||
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect
|
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect
|
||||||
github.com/fatih/color v1.19.0 // indirect
|
github.com/fatih/color v1.18.0 // indirect
|
||||||
github.com/fatih/structtag v1.2.0 // indirect
|
github.com/fatih/structtag v1.2.0 // indirect
|
||||||
github.com/firefart/nonamedreturns v1.0.8 // indirect
|
github.com/firefart/nonamedreturns v1.0.6 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
github.com/fzipp/gocyclo v0.6.0 // indirect
|
github.com/fzipp/gocyclo v0.6.0 // indirect
|
||||||
github.com/ghostiam/protogetter v0.3.21 // indirect
|
github.com/ghostiam/protogetter v0.3.20 // indirect
|
||||||
github.com/git-pkgs/packageurl-go v0.3.1 // indirect
|
github.com/git-pkgs/packageurl-go v0.3.1 // indirect
|
||||||
github.com/git-pkgs/pom v0.1.5 // indirect
|
github.com/git-pkgs/pom v0.1.5 // indirect
|
||||||
github.com/github/go-spdx/v2 v2.7.0 // indirect
|
github.com/github/go-spdx/v2 v2.7.0 // indirect
|
||||||
github.com/go-critic/go-critic v0.14.4 // indirect
|
github.com/go-critic/go-critic v0.14.3 // indirect
|
||||||
github.com/go-logr/logr v1.4.3 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||||
|
|
@ -156,82 +152,83 @@ require (
|
||||||
github.com/gofrs/flock v0.13.0 // indirect
|
github.com/gofrs/flock v0.13.0 // indirect
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
|
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
|
||||||
github.com/golangci/asciicheck v0.5.0 // indirect
|
github.com/golangci/asciicheck v0.5.0 // indirect
|
||||||
github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202 // indirect
|
github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect
|
||||||
github.com/golangci/go-printf-func-name v0.1.1 // indirect
|
github.com/golangci/go-printf-func-name v0.1.1 // indirect
|
||||||
github.com/golangci/gofmt v0.0.0-20260820135601-e84e05053792 // indirect
|
github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d // indirect
|
||||||
github.com/golangci/golangci-lint/v2 v2.13.1 // indirect
|
github.com/golangci/golangci-lint/v2 v2.10.1 // indirect
|
||||||
github.com/golangci/golines v0.15.0 // indirect
|
github.com/golangci/golines v0.15.0 // indirect
|
||||||
github.com/golangci/misspell v0.8.0 // indirect
|
github.com/golangci/misspell v0.8.0 // indirect
|
||||||
github.com/golangci/plugin-module-register v0.1.2 // indirect
|
github.com/golangci/plugin-module-register v0.1.2 // indirect
|
||||||
github.com/golangci/revgrep v0.8.0 // indirect
|
github.com/golangci/revgrep v0.8.0 // indirect
|
||||||
github.com/golangci/rowserrcheck v0.0.0-20260419091836-c5f79b8a11ba // indirect
|
|
||||||
github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e // indirect
|
github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e // indirect
|
||||||
github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e // indirect
|
github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e // indirect
|
||||||
github.com/google/go-cmp v0.7.0 // indirect
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
github.com/google/s2a-go v0.1.9 // indirect
|
github.com/google/s2a-go v0.1.9 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/google/wire v0.7.0 // indirect
|
github.com/google/wire v0.7.0 // indirect
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.18 // indirect
|
github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
|
||||||
github.com/googleapis/gax-go/v2 v2.23.0 // indirect
|
github.com/googleapis/gax-go/v2 v2.19.0 // indirect
|
||||||
github.com/gordonklaus/ineffassign v0.2.0 // indirect
|
github.com/gordonklaus/ineffassign v0.2.0 // indirect
|
||||||
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
|
github.com/gostaticanalysis/analysisutil v0.7.1 // indirect
|
||||||
github.com/gostaticanalysis/comment v1.5.0 // indirect
|
github.com/gostaticanalysis/comment v1.5.0 // indirect
|
||||||
github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect
|
github.com/gostaticanalysis/forcetypeassert v0.2.0 // indirect
|
||||||
github.com/gostaticanalysis/nilerr v0.1.2 // indirect
|
github.com/gostaticanalysis/nilerr v0.1.2 // indirect
|
||||||
github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect
|
github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect
|
||||||
github.com/hashicorp/go-version v1.9.0 // indirect
|
github.com/hashicorp/go-version v1.8.0 // indirect
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/hexops/gotextdiff v1.0.3 // indirect
|
github.com/hexops/gotextdiff v1.0.3 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/jgautheron/goconst v1.11.0 // indirect
|
github.com/jgautheron/goconst v1.8.2 // indirect
|
||||||
|
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
|
||||||
github.com/jjti/go-spancheck v0.6.5 // indirect
|
github.com/jjti/go-spancheck v0.6.5 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
github.com/julz/importas v0.2.0 // indirect
|
github.com/julz/importas v0.2.0 // indirect
|
||||||
github.com/karamaru-alpha/copyloopvar v1.2.2 // indirect
|
github.com/karamaru-alpha/copyloopvar v1.2.2 // indirect
|
||||||
github.com/kisielk/errcheck v1.20.0 // indirect
|
github.com/kisielk/errcheck v1.9.0 // indirect
|
||||||
github.com/kkHAIKE/contextcheck v1.1.6 // indirect
|
github.com/kkHAIKE/contextcheck v1.1.6 // indirect
|
||||||
github.com/kulti/thelper v0.7.1 // indirect
|
github.com/kulti/thelper v0.7.1 // indirect
|
||||||
github.com/kunwardeep/paralleltest v1.0.15 // indirect
|
github.com/kunwardeep/paralleltest v1.0.15 // indirect
|
||||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||||
github.com/lasiar/canonicalheader v1.1.2 // indirect
|
github.com/lasiar/canonicalheader v1.1.2 // indirect
|
||||||
github.com/ldez/exptostd v0.4.5 // indirect
|
github.com/ldez/exptostd v0.4.5 // indirect
|
||||||
github.com/ldez/gomoddirectives v0.9.0 // indirect
|
github.com/ldez/gomoddirectives v0.8.0 // indirect
|
||||||
github.com/ldez/grignotin v0.10.1 // indirect
|
github.com/ldez/grignotin v0.10.1 // indirect
|
||||||
github.com/ldez/structtags v0.6.1 // indirect
|
github.com/ldez/structtags v0.6.1 // indirect
|
||||||
github.com/ldez/tagliatelle v0.7.2 // indirect
|
github.com/ldez/tagliatelle v0.7.2 // indirect
|
||||||
github.com/ldez/usetesting v0.5.0 // indirect
|
github.com/ldez/usetesting v0.5.0 // indirect
|
||||||
github.com/leonklingele/grouper v1.1.2 // indirect
|
github.com/leonklingele/grouper v1.1.2 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.4.1 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/macabu/inamedparam v0.2.0 // indirect
|
github.com/macabu/inamedparam v0.2.0 // indirect
|
||||||
github.com/magiconair/properties v1.8.6 // indirect
|
github.com/magiconair/properties v1.8.6 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/manuelarte/embeddedstructfieldcheck v0.4.0 // indirect
|
github.com/manuelarte/embeddedstructfieldcheck v0.4.0 // indirect
|
||||||
github.com/manuelarte/funcorder v0.6.0 // indirect
|
github.com/manuelarte/funcorder v0.5.0 // indirect
|
||||||
github.com/maratori/testableexamples v1.0.1 // indirect
|
github.com/maratori/testableexamples v1.0.1 // indirect
|
||||||
github.com/maratori/testpackage v1.1.2 // indirect
|
github.com/maratori/testpackage v1.1.2 // indirect
|
||||||
github.com/matoous/godox v1.1.0 // indirect
|
github.com/matoous/godox v1.1.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.15 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.24 // indirect
|
github.com/mattn/go-isatty v0.0.24 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.24 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
github.com/mgechev/revive v1.15.0 // indirect
|
github.com/mgechev/revive v1.14.0 // indirect
|
||||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/moricho/tparallel v0.3.2 // indirect
|
github.com/moricho/tparallel v0.3.2 // indirect
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
github.com/muesli/termenv v0.16.0 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/nakabonne/nestif v0.3.1 // indirect
|
github.com/nakabonne/nestif v0.3.1 // indirect
|
||||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||||
github.com/nishanths/exhaustive v0.12.0 // indirect
|
github.com/nishanths/exhaustive v0.12.0 // indirect
|
||||||
github.com/nishanths/predeclared v0.2.2 // indirect
|
github.com/nishanths/predeclared v0.2.2 // indirect
|
||||||
github.com/nunnatsa/ginkgolinter v0.24.0 // indirect
|
github.com/nunnatsa/ginkgolinter v0.23.0 // indirect
|
||||||
github.com/oapi-codegen/nullable v1.2.0 // indirect
|
github.com/oapi-codegen/nullable v1.2.0 // indirect
|
||||||
github.com/oapi-codegen/runtime v1.6.0 // indirect
|
github.com/oapi-codegen/runtime v1.6.0 // indirect
|
||||||
github.com/package-url/packageurl-go v0.1.6 // indirect
|
github.com/package-url/packageurl-go v0.1.6 // indirect
|
||||||
github.com/pandatix/go-cvss v0.6.2 // indirect
|
github.com/pandatix/go-cvss v0.6.2 // indirect
|
||||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.4.3 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/common v0.70.1 // indirect
|
github.com/prometheus/common v0.70.1 // indirect
|
||||||
github.com/prometheus/procfs v0.21.1 // indirect
|
github.com/prometheus/procfs v0.21.1 // indirect
|
||||||
github.com/quasilyte/go-ruleguard v0.4.5 // indirect
|
github.com/quasilyte/go-ruleguard v0.4.5 // indirect
|
||||||
|
|
@ -239,25 +236,24 @@ require (
|
||||||
github.com/quasilyte/gogrep v0.5.0 // indirect
|
github.com/quasilyte/gogrep v0.5.0 // indirect
|
||||||
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
|
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect
|
||||||
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
|
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect
|
||||||
github.com/raeperd/recvcheck v0.3.0 // indirect
|
github.com/raeperd/recvcheck v0.2.0 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.16.0 // indirect
|
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||||
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 // indirect
|
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 // indirect
|
||||||
github.com/rubyist/circuitbreaker v2.2.1+incompatible // indirect
|
github.com/rubyist/circuitbreaker v2.2.1+incompatible // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
github.com/ryancurrah/gomodguard v1.4.1 // indirect
|
github.com/ryancurrah/gomodguard v1.4.1 // indirect
|
||||||
github.com/ryancurrah/gomodguard/v2 v2.1.3 // indirect
|
github.com/ryanrolds/sqlclosecheck v0.5.1 // indirect
|
||||||
github.com/ryanrolds/sqlclosecheck v0.6.0 // indirect
|
|
||||||
github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect
|
github.com/sanposhiho/wastedassign/v2 v2.1.0 // indirect
|
||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 // indirect
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
|
||||||
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
|
github.com/sashamelentyev/interfacebloat v1.1.0 // indirect
|
||||||
github.com/sashamelentyev/usestdlibvars v1.29.0 // indirect
|
github.com/sashamelentyev/usestdlibvars v1.29.0 // indirect
|
||||||
github.com/securego/gosec/v2 v2.28.0 // indirect
|
github.com/securego/gosec/v2 v2.23.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.10.1 // indirect
|
github.com/sirupsen/logrus v1.9.4 // indirect
|
||||||
github.com/sivchari/containedctx v1.0.3 // indirect
|
github.com/sivchari/containedctx v1.0.3 // indirect
|
||||||
github.com/sonatard/noctx v0.5.1 // indirect
|
github.com/sonatard/noctx v0.4.0 // indirect
|
||||||
github.com/sourcegraph/go-diff v0.8.0 // indirect
|
github.com/sourcegraph/go-diff v0.7.0 // indirect
|
||||||
github.com/spf13/afero v1.15.0 // indirect
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
github.com/spf13/cast v1.5.0 // indirect
|
github.com/spf13/cast v1.5.0 // indirect
|
||||||
github.com/spf13/cobra v1.10.2 // indirect
|
github.com/spf13/cobra v1.10.2 // indirect
|
||||||
|
|
@ -266,11 +262,11 @@ require (
|
||||||
github.com/spf13/viper v1.12.0 // indirect
|
github.com/spf13/viper v1.12.0 // indirect
|
||||||
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
|
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
|
||||||
github.com/stbenjam/no-sprintf-host-port v0.3.1 // indirect
|
github.com/stbenjam/no-sprintf-host-port v0.3.1 // indirect
|
||||||
github.com/stretchr/objx v0.5.3 // indirect
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
github.com/stretchr/testify v1.12.1 // indirect
|
github.com/stretchr/testify v1.11.1 // indirect
|
||||||
github.com/subosito/gotenv v1.4.1 // indirect
|
github.com/subosito/gotenv v1.4.1 // indirect
|
||||||
github.com/tetafro/godot v1.5.6 // indirect
|
github.com/tetafro/godot v1.5.4 // indirect
|
||||||
github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4 // indirect
|
github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 // indirect
|
||||||
github.com/timonwong/loggercheck v0.11.0 // indirect
|
github.com/timonwong/loggercheck v0.11.0 // indirect
|
||||||
github.com/tomarrell/wrapcheck/v2 v2.12.0 // indirect
|
github.com/tomarrell/wrapcheck/v2 v2.12.0 // indirect
|
||||||
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
|
github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect
|
||||||
|
|
@ -278,8 +274,8 @@ require (
|
||||||
github.com/ultraware/funlen v0.2.0 // indirect
|
github.com/ultraware/funlen v0.2.0 // indirect
|
||||||
github.com/ultraware/whitespace v0.2.0 // indirect
|
github.com/ultraware/whitespace v0.2.0 // indirect
|
||||||
github.com/urfave/cli/v2 v2.3.0 // indirect
|
github.com/urfave/cli/v2 v2.3.0 // indirect
|
||||||
github.com/uudashr/gocognit v1.2.1 // indirect
|
github.com/uudashr/gocognit v1.2.0 // indirect
|
||||||
github.com/uudashr/iface v1.5.0 // indirect
|
github.com/uudashr/iface v1.4.1 // indirect
|
||||||
github.com/xen0n/gosmopolitan v1.3.0 // indirect
|
github.com/xen0n/gosmopolitan v1.3.0 // indirect
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
github.com/yagipy/maintidx v1.0.0 // indirect
|
github.com/yagipy/maintidx v1.0.0 // indirect
|
||||||
|
|
@ -287,9 +283,9 @@ require (
|
||||||
github.com/ykadowak/zerologlint v0.1.5 // indirect
|
github.com/ykadowak/zerologlint v0.1.5 // indirect
|
||||||
gitlab.com/bosi/decorder v0.4.2 // indirect
|
gitlab.com/bosi/decorder v0.4.2 // indirect
|
||||||
go-simpler.org/musttag v0.14.0 // indirect
|
go-simpler.org/musttag v0.14.0 // indirect
|
||||||
go-simpler.org/sloglint v0.12.0 // indirect
|
go-simpler.org/sloglint v0.11.1 // indirect
|
||||||
go.augendre.info/arangolint v0.4.0 // indirect
|
go.augendre.info/arangolint v0.4.0 // indirect
|
||||||
go.augendre.info/fatcontext v0.10.0 // indirect
|
go.augendre.info/fatcontext v0.9.0 // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||||
go.opentelemetry.io/otel v1.44.0 // indirect
|
go.opentelemetry.io/otel v1.44.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.44.0 // indirect
|
go.opentelemetry.io/otel/metric v1.44.0 // indirect
|
||||||
|
|
@ -299,28 +295,28 @@ require (
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.uber.org/zap v1.27.1 // indirect
|
go.uber.org/zap v1.27.1 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||||
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
golang.org/x/crypto v0.55.0 // indirect
|
golang.org/x/crypto v0.54.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
|
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
|
||||||
golang.org/x/exp/typeparams v0.0.0-20260811152304-ee035b5b010f // indirect
|
golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 // indirect
|
||||||
golang.org/x/mod v0.40.0 // indirect
|
golang.org/x/mod v0.37.0 // indirect
|
||||||
golang.org/x/net v0.58.0 // indirect
|
golang.org/x/net v0.57.0 // indirect
|
||||||
golang.org/x/oauth2 v0.36.0 // indirect
|
golang.org/x/oauth2 v0.36.0 // indirect
|
||||||
golang.org/x/sys v0.47.0 // indirect
|
golang.org/x/sys v0.47.0 // indirect
|
||||||
golang.org/x/text v0.41.0 // indirect
|
golang.org/x/text v0.40.0 // indirect
|
||||||
golang.org/x/tools v0.49.0 // indirect
|
golang.org/x/tools v0.47.0 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
|
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
|
||||||
google.golang.org/api v0.288.0 // indirect
|
google.golang.org/api v0.272.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect
|
||||||
google.golang.org/grpc v1.82.1 // indirect
|
google.golang.org/grpc v1.82.1 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
honnef.co/go/tools v0.8.0 // indirect
|
honnef.co/go/tools v0.7.0 // indirect
|
||||||
modernc.org/libc v1.74.4 // indirect
|
modernc.org/libc v1.74.4 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.11.0 // indirect
|
modernc.org/memory v1.11.0 // indirect
|
||||||
mvdan.cc/gofumpt v0.11.0 // indirect
|
mvdan.cc/gofumpt v0.9.2 // indirect
|
||||||
mvdan.cc/unparam v0.0.0-20260818115549-3f964bcb5673 // indirect
|
mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 // indirect
|
||||||
sigs.k8s.io/yaml v1.6.0 // indirect
|
sigs.k8s.io/yaml v1.6.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
371
go.sum
371
go.sum
|
|
@ -1,15 +1,13 @@
|
||||||
4d63.com/gocheckcompilerdirectives v1.4.0 h1:ZLq62rbGWVmQhiZ8kuNVIT/M09xCSTdJz9K3xOdT/CY=
|
4d63.com/gocheckcompilerdirectives v1.3.0 h1:Ew5y5CtcAAQeTVKUVFrE7EwHMrTO6BggtEj8BZSjZ3A=
|
||||||
4d63.com/gocheckcompilerdirectives v1.4.0/go.mod h1:9ZOAiMOjqC/nRwci2fcUXVHUNLG/cH6r6rhUh+jTFtQ=
|
4d63.com/gocheckcompilerdirectives v1.3.0/go.mod h1:ofsJ4zx2QAuIP/NO/NAh1ig6R1Fb18/GI7RVMwz7kAY=
|
||||||
4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU=
|
4d63.com/gochecknoglobals v0.2.2 h1:H1vdnwnMaZdQW/N+NrkT1SZMTBmcwHe9Vq8lJcYYTtU=
|
||||||
4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0=
|
4d63.com/gochecknoglobals v0.2.2/go.mod h1:lLxwTQjL5eIesRbvnzIP3jZtG140FnTdz+AlMa+ogt0=
|
||||||
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
|
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4=
|
||||||
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
|
||||||
charm.land/lipgloss/v2 v2.0.6 h1:EaGKeuA8FvF+v2BT5VmZd2LoYLaMZJXA5n34th8nCIQ=
|
|
||||||
charm.land/lipgloss/v2 v2.0.6/go.mod h1:ipDDJNSGa1hlwDtSfW1s2/xR8Vdhbut4PXh2zEKZd0Q=
|
|
||||||
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
|
cloud.google.com/go v0.123.0 h1:2NAUJwPR47q+E35uaJeYoNhuNEM9kM8SjgRgdeOJUSE=
|
||||||
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
|
cloud.google.com/go v0.123.0/go.mod h1:xBoMV08QcqUGuPW65Qfm1o9Y4zKZBpGS+7bImXLTAZU=
|
||||||
cloud.google.com/go/auth v0.21.0 h1:g/QwYfYb2Ai6HH8oomAOyBaIHLbscZ4+T/F/f5JZHkE=
|
cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM=
|
||||||
cloud.google.com/go/auth v0.21.0/go.mod h1:M9o2Oz+YI2jAfxewJgb1vyI3vceHF+eohmxyzmrl+9s=
|
cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M=
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||||
|
|
@ -26,25 +24,23 @@ codeberg.org/polyfloyd/go-errorlint v1.9.0 h1:VkdEEmA1VBpH6ecQoMR4LdphVI3fA4RrCh
|
||||||
codeberg.org/polyfloyd/go-errorlint v1.9.0/go.mod h1:GPRRu2LzVijNn4YkrZYJfatQIdS+TrcK8rL5Xs24qw8=
|
codeberg.org/polyfloyd/go-errorlint v1.9.0/go.mod h1:GPRRu2LzVijNn4YkrZYJfatQIdS+TrcK8rL5Xs24qw8=
|
||||||
dev.gaijin.team/go/exhaustruct/v4 v4.0.0 h1:873r7aNneqoBB3IaFIzhvt2RFYTuHgmMjoKfwODoI1Y=
|
dev.gaijin.team/go/exhaustruct/v4 v4.0.0 h1:873r7aNneqoBB3IaFIzhvt2RFYTuHgmMjoKfwODoI1Y=
|
||||||
dev.gaijin.team/go/exhaustruct/v4 v4.0.0/go.mod h1:aZ/k2o4Y05aMJtiux15x8iXaumE88YdiB0Ai4fXOzPI=
|
dev.gaijin.team/go/exhaustruct/v4 v4.0.0/go.mod h1:aZ/k2o4Y05aMJtiux15x8iXaumE88YdiB0Ai4fXOzPI=
|
||||||
dev.gaijin.team/go/exhaustruct/v5 v5.0.3 h1:yOeA7DNjlT8y4yfmN6nWWYYggA13N523YAj9/TXbuTM=
|
dev.gaijin.team/go/golib v0.6.0 h1:v6nnznFTs4bppib/NyU1PQxobwDHwCXXl15P7DV5Zgo=
|
||||||
dev.gaijin.team/go/exhaustruct/v5 v5.0.3/go.mod h1:KwtBsX8nHHH1YxhxkpiBq6bfsmw5WnazWpNvJPHgY9Y=
|
dev.gaijin.team/go/golib v0.6.0/go.mod h1:uY1mShx8Z/aNHWDyAkZTkX+uCi5PdX7KsG1eDQa2AVE=
|
||||||
dev.gaijin.team/go/golib v0.8.1 h1:JYju4x9BSo+QD/AYeHULVDcvEhiFg8wOi6pT0IaZF5E=
|
|
||||||
dev.gaijin.team/go/golib v0.8.1/go.mod h1:c5fu7t1RSGMxSQgcUYO1sODbzsYnOCXJLmHeNG1Eb+0=
|
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||||
github.com/4meepo/tagalign v1.4.3 h1:Bnu7jGWwbfpAie2vyl63Zup5KuRv21olsPIha53BJr8=
|
github.com/4meepo/tagalign v1.4.3 h1:Bnu7jGWwbfpAie2vyl63Zup5KuRv21olsPIha53BJr8=
|
||||||
github.com/4meepo/tagalign v1.4.3/go.mod h1:00WwRjiuSbrRJnSVeGWPLp2epS5Q/l4UEy0apLLS37c=
|
github.com/4meepo/tagalign v1.4.3/go.mod h1:00WwRjiuSbrRJnSVeGWPLp2epS5Q/l4UEy0apLLS37c=
|
||||||
github.com/Abirdcfly/dupword v0.1.8 h1:SrhcUuGsROBuChFxHALRYzyyPODWn9zwghmzPvD9Cd8=
|
github.com/Abirdcfly/dupword v0.1.7 h1:2j8sInznrje4I0CMisSL6ipEBkeJUJAmK1/lfoNGWrQ=
|
||||||
github.com/Abirdcfly/dupword v0.1.8/go.mod h1:XZrhVnI7YGpsTiWZANSQaBJ4QpL/Tq5vIEdKJJAs9WI=
|
github.com/Abirdcfly/dupword v0.1.7/go.mod h1:K0DkBeOebJ4VyOICFdppB23Q0YMOgVafM0zYW0n9lF4=
|
||||||
github.com/AdminBenni/iota-mixing v1.0.0 h1:Os6lpjG2dp/AE5fYBPAA1zfa2qMdCAWwPMCgpwKq7wo=
|
github.com/AdminBenni/iota-mixing v1.0.0 h1:Os6lpjG2dp/AE5fYBPAA1zfa2qMdCAWwPMCgpwKq7wo=
|
||||||
github.com/AdminBenni/iota-mixing v1.0.0/go.mod h1:i4+tpAaB+qMVIV9OK3m4/DAynOd5bQFaOu+2AhtBCNY=
|
github.com/AdminBenni/iota-mixing v1.0.0/go.mod h1:i4+tpAaB+qMVIV9OK3m4/DAynOd5bQFaOu+2AhtBCNY=
|
||||||
github.com/AlwxSin/noinlineerr v1.0.6 h1:KAvuxunTe9QxvqrFB7nZTdb/7Wzas4AvifslTnG0Ld8=
|
github.com/AlwxSin/noinlineerr v1.0.5 h1:RUjt63wk1AYWTXtVXbSqemlbVTb23JOSRiNsshj7TbY=
|
||||||
github.com/AlwxSin/noinlineerr v1.0.6/go.mod h1:+QgkkoYrMH7RHvcdxdlI7vYYEdgeoFOVjU9sUhw/rQc=
|
github.com/AlwxSin/noinlineerr v1.0.5/go.mod h1:+QgkkoYrMH7RHvcdxdlI7vYYEdgeoFOVjU9sUhw/rQc=
|
||||||
github.com/Antonboom/errname v1.1.2 h1:dxwONZJua3VB8Xh/VaCjqAcqF645sWWv7xj26zy7tdQ=
|
github.com/Antonboom/errname v1.1.1 h1:bllB7mlIbTVzO9jmSWVWLjxTEbGBVQ1Ff/ClQgtPw9Q=
|
||||||
github.com/Antonboom/errname v1.1.2/go.mod h1:YeZIpgLMxT+SNkruGgYkLhzq/9vs3fsolTZegKaKDZI=
|
github.com/Antonboom/errname v1.1.1/go.mod h1:gjhe24xoxXp0ScLtHzjiXp0Exi1RFLKJb0bVBtWKCWQ=
|
||||||
github.com/Antonboom/nilnil v1.1.2 h1:aNlFuJhaEseXe4fHO3xbjXlSeEiQVYa2lEkWD2s2hAY=
|
github.com/Antonboom/nilnil v1.1.1 h1:9Mdr6BYd8WHCDngQnNVV0b554xyisFioEKi30sksufQ=
|
||||||
github.com/Antonboom/nilnil v1.1.2/go.mod h1:0ynwvphOLmAuMwTNDyBnDZmSwZoDpcFXmUHmzoHH2WA=
|
github.com/Antonboom/nilnil v1.1.1/go.mod h1:yCyAmSw3doopbOWhJlVci+HuyNRuHJKIv6V2oYQa8II=
|
||||||
github.com/Antonboom/testifylint v1.6.4 h1:gs9fUEy+egzxkEbq9P4cpcMB6/G0DYdMeiFS87UiqmQ=
|
github.com/Antonboom/testifylint v1.6.4 h1:gs9fUEy+egzxkEbq9P4cpcMB6/G0DYdMeiFS87UiqmQ=
|
||||||
github.com/Antonboom/testifylint v1.6.4/go.mod h1:YO33FROXX2OoUfwjz8g+gUxQXio5i9qpVy7nXGbxDD4=
|
github.com/Antonboom/testifylint v1.6.4/go.mod h1:YO33FROXX2OoUfwjz8g+gUxQXio5i9qpVy7nXGbxDD4=
|
||||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28=
|
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28=
|
||||||
|
|
@ -66,8 +62,6 @@ github.com/AzureAD/microsoft-authentication-library-for-go v1.7.0/go.mod h1:HKpQ
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
github.com/ClickHouse/clickhouse-go-linter v1.2.1 h1:zGEKIyd5YL08ieWG/LOUmlau2DxbxPVOfAeo+4Jz3ck=
|
|
||||||
github.com/ClickHouse/clickhouse-go-linter v1.2.1/go.mod h1:pLorS7ffPTfuUV9M0SJgfHA/h/WQPQUk2FWG9x74cQ4=
|
|
||||||
github.com/CycloneDX/cyclonedx-go v0.11.0 h1:GokP8FiRC+foiuwWhSSLpSD5H4hSWtGnR3wo7apkBFI=
|
github.com/CycloneDX/cyclonedx-go v0.11.0 h1:GokP8FiRC+foiuwWhSSLpSD5H4hSWtGnR3wo7apkBFI=
|
||||||
github.com/CycloneDX/cyclonedx-go v0.11.0/go.mod h1:vUvbCXQsEm48OI6oOlanxstwNByXjCZ2wuleUlwGEO8=
|
github.com/CycloneDX/cyclonedx-go v0.11.0/go.mod h1:vUvbCXQsEm48OI6oOlanxstwNByXjCZ2wuleUlwGEO8=
|
||||||
github.com/Djarvur/go-err113 v0.1.1 h1:eHfopDqXRwAi+YmCUas75ZE0+hoBHJ2GQNLYRSxao4g=
|
github.com/Djarvur/go-err113 v0.1.1 h1:eHfopDqXRwAi+YmCUas75ZE0+hoBHJ2GQNLYRSxao4g=
|
||||||
|
|
@ -80,10 +74,10 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapp
|
||||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
|
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0/go.mod h1:Mf6O40IAyB9zR/1J8nGDDPirZQQPbYJni8Yisy7NTMc=
|
||||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||||
github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE=
|
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
|
||||||
github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
|
||||||
github.com/MirrexOne/unqueryvet v1.5.4 h1:38QOxShO7JmMWT+eCdDMbcUgGCOeJphVkzzRgyLJgsQ=
|
github.com/MirrexOne/unqueryvet v1.5.3 h1:LpT3rsH+IY3cQddWF9bg4C7jsbASdGnrOSofY8IPEiw=
|
||||||
github.com/MirrexOne/unqueryvet v1.5.4/go.mod h1:fs9Zq6eh1LRIhsDIsxf9PONVUjYdFHdtkHIgZdJnyPU=
|
github.com/MirrexOne/unqueryvet v1.5.3/go.mod h1:fs9Zq6eh1LRIhsDIsxf9PONVUjYdFHdtkHIgZdJnyPU=
|
||||||
github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4=
|
github.com/OpenPeeDeeP/depguard/v2 v2.2.1 h1:vckeWVESWp6Qog7UZSARNqfu/cZqvki8zsuj3piCMx4=
|
||||||
github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo=
|
github.com/OpenPeeDeeP/depguard/v2 v2.2.1/go.mod h1:q4DKzC4UcVaAvcfd41CZh0PWpGgzrVxUYBlgKNGquUo=
|
||||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||||
|
|
@ -93,16 +87,16 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdko
|
||||||
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
||||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||||
github.com/alecthomas/chroma/v2 v2.27.0 h1:FodwmyOBgJULFYmDqibcp9pvfDLWdtPRh9v/r5BXYZs=
|
github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ/Vk/iY=
|
||||||
github.com/alecthomas/chroma/v2 v2.27.0/go.mod h1:NjJ3ciIgrqBNeIkWZ4e46nseoLDslxU1LmfCoL+wcY8=
|
github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
|
||||||
github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU=
|
github.com/alecthomas/go-check-sumtype v0.3.1 h1:u9aUvbGINJxLVXiFvHUlPEaD7VDULsrxJb4Aq31NLkU=
|
||||||
github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E=
|
github.com/alecthomas/go-check-sumtype v0.3.1/go.mod h1:A8TSiN3UPRw3laIgWEUOHHLPa6/r9MtoigdlP5h3K/E=
|
||||||
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
|
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
|
||||||
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||||
github.com/alexkohler/nakedret/v2 v2.0.6 h1:ME3Qef1/KIKr3kWX3nti3hhgNxw6aqN5pZmQiFSsuzQ=
|
github.com/alexkohler/nakedret/v2 v2.0.6 h1:ME3Qef1/KIKr3kWX3nti3hhgNxw6aqN5pZmQiFSsuzQ=
|
||||||
github.com/alexkohler/nakedret/v2 v2.0.6/go.mod h1:l3RKju/IzOMQHmsEvXwkqMDzHHvurNQfAgE1eVmT40Q=
|
github.com/alexkohler/nakedret/v2 v2.0.6/go.mod h1:l3RKju/IzOMQHmsEvXwkqMDzHHvurNQfAgE1eVmT40Q=
|
||||||
github.com/alexkohler/prealloc v1.1.0 h1:cKGRBqlXw5iyQGLYhrXrDlcHxugXpTq4tQ5c91wkf8M=
|
github.com/alexkohler/prealloc v1.0.2 h1:MPo8cIkGkZytq7WNH9UHv3DIX1mPz1RatPXnZb0zHWQ=
|
||||||
github.com/alexkohler/prealloc v1.1.0/go.mod h1:fT39Jge3bQrfA7nPMDngUfvUbQGQeJyGQnR+913SCig=
|
github.com/alexkohler/prealloc v1.0.2/go.mod h1:fT39Jge3bQrfA7nPMDngUfvUbQGQeJyGQnR+913SCig=
|
||||||
github.com/alfatraining/structtag v1.0.0 h1:2qmcUqNcCoyVJ0up879K614L9PazjBSFruTB0GOFjCc=
|
github.com/alfatraining/structtag v1.0.0 h1:2qmcUqNcCoyVJ0up879K614L9PazjBSFruTB0GOFjCc=
|
||||||
github.com/alfatraining/structtag v1.0.0/go.mod h1:p3Xi5SwzTi+Ryj64DqjLWz7XurHxbGsq6y3ubePJPus=
|
github.com/alfatraining/structtag v1.0.0/go.mod h1:p3Xi5SwzTi+Ryj64DqjLWz7XurHxbGsq6y3ubePJPus=
|
||||||
github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw=
|
github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw=
|
||||||
|
|
@ -113,10 +107,10 @@ github.com/anchore/go-struct-converter v0.1.0 h1:2rDRssAl6mgKBSLNiVCMADgZRhoqtw9
|
||||||
github.com/anchore/go-struct-converter v0.1.0/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA=
|
github.com/anchore/go-struct-converter v0.1.0/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA=
|
||||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
||||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
||||||
github.com/ashanbrown/forbidigo/v2 v2.3.1 h1:KAZijvQ7zeIBKbhikT4jCm0TLYXC4u78bTiLh/8JROI=
|
github.com/ashanbrown/forbidigo/v2 v2.3.0 h1:OZZDOchCgsX5gvToVtEBoV2UWbFfI6RKQTir2UZzSxo=
|
||||||
github.com/ashanbrown/forbidigo/v2 v2.3.1/go.mod h1:2QDkLTzU6TV937eFROamXrW92M3paehdae4HCDCOZCM=
|
github.com/ashanbrown/forbidigo/v2 v2.3.0/go.mod h1:5p6VmsG5/1xx3E785W9fouMxIOkvY2rRV9nMdWadd6c=
|
||||||
github.com/ashanbrown/makezero/v2 v2.2.1 h1:A7uU8dgB1PA9aelTxHMfHIQ8Qev8AB3JLxJUBUsejqM=
|
github.com/ashanbrown/makezero/v2 v2.1.0 h1:snuKYMbqosNokUKm+R6/+vOPs8yVAi46La7Ck6QYSaE=
|
||||||
github.com/ashanbrown/makezero/v2 v2.2.1/go.mod h1:aEGT/9q3S8DHeE57C88z2a6xydvgx8J5hgXIGWgo0MY=
|
github.com/ashanbrown/makezero/v2 v2.1.0/go.mod h1:aEGT/9q3S8DHeE57C88z2a6xydvgx8J5hgXIGWgo0MY=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.41.9 h1:/rYeyO2+HrMztAmxAq9++XJtFMqSIpSsNA0yDGALYq4=
|
github.com/aws/aws-sdk-go-v2 v1.41.9 h1:/rYeyO2+HrMztAmxAq9++XJtFMqSIpSsNA0yDGALYq4=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.41.9/go.mod h1:+HsoOEX80qAVUitj1A2DhCNTjmb3edVyuDypb6LNEeo=
|
github.com/aws/aws-sdk-go-v2 v1.41.9/go.mod h1:+HsoOEX80qAVUitj1A2DhCNTjmb3edVyuDypb6LNEeo=
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.11 h1:h5+3VT69KUBK24grGuuA5saDJTj2IIjLb9au668Fo5I=
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.11 h1:h5+3VT69KUBK24grGuuA5saDJTj2IIjLb9au668Fo5I=
|
||||||
|
|
@ -155,6 +149,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.42.3 h1:ErklX/7uhSbkAAeyQD/Y1OoQ9hO3
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.42.3/go.mod h1:ULe4HCzfKPiR6R3HEurE3b1upEkuk8AkMrOKtaOxKO8=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.42.3/go.mod h1:ULe4HCzfKPiR6R3HEurE3b1upEkuk8AkMrOKtaOxKO8=
|
||||||
github.com/aws/smithy-go v1.26.0 h1:9ouqbi+NyKP7fV3Te7UElCwdAb6Y8uk7LGwPE5tVe/s=
|
github.com/aws/smithy-go v1.26.0 h1:9ouqbi+NyKP7fV3Te7UElCwdAb6Y8uk7LGwPE5tVe/s=
|
||||||
github.com/aws/smithy-go v1.26.0/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
github.com/aws/smithy-go v1.26.0/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w=
|
github.com/bkielbasa/cyclop v1.2.3 h1:faIVMIGDIANuGPWH031CZJTi2ymOQBULs9H21HSMa5w=
|
||||||
|
|
@ -164,18 +160,18 @@ github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkAp
|
||||||
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
||||||
github.com/bombsimon/wsl/v4 v4.7.0 h1:1Ilm9JBPRczjyUs6hvOPKvd7VL1Q++PL8M0SXBDf+jQ=
|
github.com/bombsimon/wsl/v4 v4.7.0 h1:1Ilm9JBPRczjyUs6hvOPKvd7VL1Q++PL8M0SXBDf+jQ=
|
||||||
github.com/bombsimon/wsl/v4 v4.7.0/go.mod h1:uV/+6BkffuzSAVYD+yGyld1AChO7/EuLrCF/8xTiapg=
|
github.com/bombsimon/wsl/v4 v4.7.0/go.mod h1:uV/+6BkffuzSAVYD+yGyld1AChO7/EuLrCF/8xTiapg=
|
||||||
github.com/bombsimon/wsl/v5 v5.9.0 h1:WCrgZ7RQnZO5oEwbVTlYgBdU3wL294kR1BSWV8vTfsU=
|
github.com/bombsimon/wsl/v5 v5.6.0 h1:4z+/sBqC5vUmSp1O0mS+czxwH9+LKXtCWtHH9rZGQL8=
|
||||||
github.com/bombsimon/wsl/v5 v5.9.0/go.mod h1:kjo4HiAV5FDkHC8/uzJq9mBffEEd6WT/nvN7DoMovDM=
|
github.com/bombsimon/wsl/v5 v5.6.0/go.mod h1:Uqt2EfrMj2NV8UGoN1f1Y3m0NpUVCsUdrNCdet+8LvU=
|
||||||
github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M=
|
github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M=
|
||||||
github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0=
|
github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0=
|
||||||
github.com/breml/bidichk v0.3.3 h1:WSM67ztRusf1sMoqH6/c4OBCUlRVTKq+CbSeo0R17sE=
|
github.com/breml/bidichk v0.3.3 h1:WSM67ztRusf1sMoqH6/c4OBCUlRVTKq+CbSeo0R17sE=
|
||||||
github.com/breml/bidichk v0.3.3/go.mod h1:ISbsut8OnjB367j5NseXEGGgO/th206dVa427kR8YTE=
|
github.com/breml/bidichk v0.3.3/go.mod h1:ISbsut8OnjB367j5NseXEGGgO/th206dVa427kR8YTE=
|
||||||
github.com/breml/errchkjson v0.4.1 h1:keFSS8D7A2T0haP9kzZTi7o26r7kE3vymjZNeNDRDwg=
|
github.com/breml/errchkjson v0.4.1 h1:keFSS8D7A2T0haP9kzZTi7o26r7kE3vymjZNeNDRDwg=
|
||||||
github.com/breml/errchkjson v0.4.1/go.mod h1:a23OvR6Qvcl7DG/Z4o0el6BRAjKnaReoPQFciAl9U3s=
|
github.com/breml/errchkjson v0.4.1/go.mod h1:a23OvR6Qvcl7DG/Z4o0el6BRAjKnaReoPQFciAl9U3s=
|
||||||
github.com/butuzov/ireturn v0.4.1 h1:vWb3NO4t77iku/sjCQ/2pHTQeOmxEhjIriJqRLg1Y+I=
|
github.com/butuzov/ireturn v0.4.0 h1:+s76bF/PfeKEdbG8b54aCocxXmi0wvYdOVsWxVO7n8E=
|
||||||
github.com/butuzov/ireturn v0.4.1/go.mod h1:q+DXKzTDV5guNuXLnIab9fKXizTn2miZHLhxH7V/GB4=
|
github.com/butuzov/ireturn v0.4.0/go.mod h1:ghI0FrCmap8pDWZwfPisFD1vEc56VKH4NpQUxDHta70=
|
||||||
github.com/butuzov/mirror v1.3.3 h1:v0RsWBhfFc1RQqE/f3sHpSttKDtodFn0gFmtYyD4/hA=
|
github.com/butuzov/mirror v1.3.0 h1:HdWCXzmwlQHdVhwvsfBb2Au0r3HyINry3bDWLYXiKoc=
|
||||||
github.com/butuzov/mirror v1.3.3/go.mod h1:h9BzzwYnTiHO0GzgvaTqIg7VSsOUhdIv51cHFFBmX1w=
|
github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI=
|
||||||
github.com/catenacyber/perfsprint v0.10.1 h1:u7Riei30bk46XsG8nknMhKLXG9BcXz3+3tl/WpKm0PQ=
|
github.com/catenacyber/perfsprint v0.10.1 h1:u7Riei30bk46XsG8nknMhKLXG9BcXz3+3tl/WpKm0PQ=
|
||||||
github.com/catenacyber/perfsprint v0.10.1/go.mod h1:DJTGsi/Zufpuus6XPGJyKOTMELe347o6akPvWG9Zcsc=
|
github.com/catenacyber/perfsprint v0.10.1/go.mod h1:DJTGsi/Zufpuus6XPGJyKOTMELe347o6akPvWG9Zcsc=
|
||||||
github.com/ccojocar/zxcvbn-go v1.0.4 h1:FWnCIRMXPj43ukfX000kvBZvV6raSxakYr1nzyNrUcc=
|
github.com/ccojocar/zxcvbn-go v1.0.4 h1:FWnCIRMXPj43ukfX000kvBZvV6raSxakYr1nzyNrUcc=
|
||||||
|
|
@ -186,24 +182,18 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/charithe/durationcheck v0.0.11 h1:g1/EX1eIiKS57NTWsYtHDZ/APfeXKhye1DidBcABctk=
|
github.com/charithe/durationcheck v0.0.11 h1:g1/EX1eIiKS57NTWsYtHDZ/APfeXKhye1DidBcABctk=
|
||||||
github.com/charithe/durationcheck v0.0.11/go.mod h1:x5iZaixRNl8ctbM+3B2RrPG5t856TxRyVQEnbIEM2X4=
|
github.com/charithe/durationcheck v0.0.11/go.mod h1:x5iZaixRNl8ctbM+3B2RrPG5t856TxRyVQEnbIEM2X4=
|
||||||
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
|
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
||||||
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
|
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
||||||
github.com/charmbracelet/ultraviolet v0.0.0-20260811164956-006e29f97886 h1:rdnVWKgJpTVXKuKuJyxDJ+NFJdUaUqGvyGy61OcvlbA=
|
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||||
github.com/charmbracelet/ultraviolet v0.0.0-20260811164956-006e29f97886/go.mod h1:nAw0d9PhFp1qdzi2xhQU5YOu5sVpDIHWlaW2Uz/bCro=
|
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||||
github.com/charmbracelet/x/ansi v0.11.8 h1:JMFwp0CgDC2+jcOB162HH5k7I3FVbgFSMMYg7dSPBQQ=
|
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
|
||||||
github.com/charmbracelet/x/ansi v0.11.8/go.mod h1:ZNN+3mXny/516oTQPLMPIBeSINvNJJQ8uQXDgbeJxY0=
|
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
||||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
|
||||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||||
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
|
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||||
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
|
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||||
github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM=
|
|
||||||
github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=
|
|
||||||
github.com/ckaznocha/intrange v0.3.1 h1:j1onQyXvHUsPWujDH6WIjhyH26gkRt/txNlV7LspvJs=
|
github.com/ckaznocha/intrange v0.3.1 h1:j1onQyXvHUsPWujDH6WIjhyH26gkRt/txNlV7LspvJs=
|
||||||
github.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nWh/s6SHIJJk=
|
github.com/ckaznocha/intrange v0.3.1/go.mod h1:QVepyz1AkUoFQkpEqksSYpNpUo3c5W7nWh/s6SHIJJk=
|
||||||
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
|
|
||||||
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
|
|
||||||
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
|
||||||
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
|
||||||
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
|
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
|
||||||
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
|
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2/go.mod h1:qwXFYgsP6T7XnJtbKlf1HP8AjxZZyzxMmc+Lq5GjlU4=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
|
|
@ -219,15 +209,12 @@ github.com/dave/dst v0.27.3/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEy
|
||||||
github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo=
|
github.com/dave/jennifer v1.7.1 h1:B4jJJDHelWcDhlRQxWeo0Npa/pYKBLrirAQoTN45txo=
|
||||||
github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
|
github.com/dave/jennifer v1.7.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8=
|
github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42t4429eC9k8=
|
||||||
github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY=
|
github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY=
|
||||||
github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8=
|
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||||
github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/dlclark/regexp2/v2 v2.2.1 h1:mf4KkFUj0gJuarK8P+LgiS+Lit7m9N1yAwEfPbee7R0=
|
|
||||||
github.com/dlclark/regexp2/v2 v2.2.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU=
|
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/ecosyste-ms/ecosystems-go v0.4.0 h1:5A+zF+XWT8sYYYjlc2/tI1SmiDGzbHLyT9CapVc5dGA=
|
github.com/ecosyste-ms/ecosystems-go v0.4.0 h1:5A+zF+XWT8sYYYjlc2/tI1SmiDGzbHLyT9CapVc5dGA=
|
||||||
|
|
@ -241,22 +228,22 @@ github.com/ettle/strcase v0.2.0 h1:fGNiVF21fHXpX1niBgk0aROov1LagYsOwV/xqKDKR/Q=
|
||||||
github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A=
|
github.com/ettle/strcase v0.2.0/go.mod h1:DajmHElDSaX76ITe3/VHVyMin4LWSJN5Z909Wp+ED1A=
|
||||||
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw=
|
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw=
|
||||||
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA=
|
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA=
|
||||||
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||||
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||||
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
|
github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4=
|
||||||
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
|
github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94=
|
||||||
github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc=
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE=
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/firefart/nonamedreturns v1.0.8 h1:iB32Dl17zJl1zlVEj/WlUWgx0HiRyQ85OUw1WHa4/II=
|
github.com/firefart/nonamedreturns v1.0.6 h1:vmiBcKV/3EqKY3ZiPxCINmpS431OcE1S47AQUwhrg8E=
|
||||||
github.com/firefart/nonamedreturns v1.0.8/go.mod h1:vxFNvm5AfP/8rgAKFzYmnqx0yp1HjrYsErZ9pHPTznA=
|
github.com/firefart/nonamedreturns v1.0.6/go.mod h1:R8NisJnSIpvPWheCq0mNRXJok6D8h7fagJTF8EMEwCo=
|
||||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||||
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
|
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
|
||||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
|
github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo=
|
||||||
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
|
github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA=
|
||||||
github.com/ghostiam/protogetter v0.3.21 h1:EeWTGvL/Eyosp653hiWb6Byx4b69iJC4/E+za7vQHoI=
|
github.com/ghostiam/protogetter v0.3.20 h1:oW7OPFit2FxZOpmMRPP9FffU4uUpfeE/rEdE1f+MzD0=
|
||||||
github.com/ghostiam/protogetter v0.3.21/go.mod h1:iAKSpyoHwYzay+OpjoWgwzRtPFthEfuUvmlomTThck0=
|
github.com/ghostiam/protogetter v0.3.20/go.mod h1:FjIu5Yfs6FT391m+Fjp3fbAYJ6rkL/J6ySpZBfnODuI=
|
||||||
github.com/git-pkgs/archives v0.5.1 h1:qwu/vsoerQZF1iysRtfcxpy1KIUSJJSpXJ5JNxzNoQw=
|
github.com/git-pkgs/archives v0.5.1 h1:qwu/vsoerQZF1iysRtfcxpy1KIUSJJSpXJ5JNxzNoQw=
|
||||||
github.com/git-pkgs/archives v0.5.1/go.mod h1:AKpkxnts49R9uAt1mL2ULYcHrmYujCDVu24IsFvW9so=
|
github.com/git-pkgs/archives v0.5.1/go.mod h1:AKpkxnts49R9uAt1mL2ULYcHrmYujCDVu24IsFvW9so=
|
||||||
github.com/git-pkgs/cooldown v0.1.1 h1:9OqqzCB8gANz/y44SmqGD0Jp8Qtu81D1sCbKl6Ehg7w=
|
github.com/git-pkgs/cooldown v0.1.1 h1:9OqqzCB8gANz/y44SmqGD0Jp8Qtu81D1sCbKl6Ehg7w=
|
||||||
|
|
@ -273,8 +260,8 @@ 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/pom v0.1.5/go.mod h1:ufdMBe1lKzqOeP9IUb9NPZ458xKV8E8NvuyBMxOfwIk=
|
||||||
github.com/git-pkgs/purl v0.1.17 h1:oRSd8tqllTLl74Wa4WnuqU500hXd9OdUnImOEswQUVE=
|
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/purl v0.1.17/go.mod h1:7u7ora8tQdrkS7Auclr5v8dCJdjN4ej6AbrvYZi2b7k=
|
||||||
github.com/git-pkgs/registries v0.8.1 h1:Yf2FFdARQ1HcdtZfWBYa5OZFwZHzhFYStiz7qbTDDUU=
|
github.com/git-pkgs/registries v0.7.0 h1:+LbOOMHbvjmXGfsi88hcGH+SfTXYsXA3UY5KYI5mB7s=
|
||||||
github.com/git-pkgs/registries v0.8.1/go.mod h1:5dc3V7rOhAI5755L/bDtjtYV4D5XV4J/4ZtyIXSEs0U=
|
github.com/git-pkgs/registries v0.7.0/go.mod h1:VCD4q+ZW0fInopzseg9rAmBEL553R2JQe60UHXtv26w=
|
||||||
github.com/git-pkgs/spdx v0.3.1 h1:58JPY5X9pYpXvnzzZIgehItlBykeOOw52pNc4OBcS+c=
|
github.com/git-pkgs/spdx v0.3.1 h1:58JPY5X9pYpXvnzzZIgehItlBykeOOw52pNc4OBcS+c=
|
||||||
github.com/git-pkgs/spdx v0.3.1/go.mod h1:cqRoZcvl530s/W+oGNvwjt4ODN8T1W6D/20MUZEFdto=
|
github.com/git-pkgs/spdx v0.3.1/go.mod h1:cqRoZcvl530s/W+oGNvwjt4ODN8T1W6D/20MUZEFdto=
|
||||||
github.com/git-pkgs/vers v0.3.1 h1:jy/ht2wIRJI5zQrccm6GTeYr+hGFwe2z8LV1HOr4Wco=
|
github.com/git-pkgs/vers v0.3.1 h1:jy/ht2wIRJI5zQrccm6GTeYr+hGFwe2z8LV1HOr4Wco=
|
||||||
|
|
@ -285,8 +272,8 @@ github.com/github/go-spdx/v2 v2.7.0 h1:GzfXx4wFdlilARxmFRXW/mgUy3A4vSqZocCMFV6XF
|
||||||
github.com/github/go-spdx/v2 v2.7.0/go.mod h1:Ftc45YYG1WzpzwEPKRVm9Jv8vDqOrN4gWoCkK+bHer0=
|
github.com/github/go-spdx/v2 v2.7.0/go.mod h1:Ftc45YYG1WzpzwEPKRVm9Jv8vDqOrN4gWoCkK+bHer0=
|
||||||
github.com/go-chi/chi/v5 v5.3.1 h1:3j4HZLGZQ3JpMCrPJF/Jl3mYJfWLKBfNJ6quurUGCf8=
|
github.com/go-chi/chi/v5 v5.3.1 h1:3j4HZLGZQ3JpMCrPJF/Jl3mYJfWLKBfNJ6quurUGCf8=
|
||||||
github.com/go-chi/chi/v5 v5.3.1/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
|
github.com/go-chi/chi/v5 v5.3.1/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
|
||||||
github.com/go-critic/go-critic v0.14.4 h1:dSX4C3pWSeuMVxvQh6yG8U0ReSf3YOmKi4nwX5q7n/8=
|
github.com/go-critic/go-critic v0.14.3 h1:5R1qH2iFeo4I/RJU8vTezdqs08Egi4u5p6vOESA0pog=
|
||||||
github.com/go-critic/go-critic v0.14.4/go.mod h1:xwntfW6SYAd7h1OqDzmN6hBX/JxsEKl5up/Y2bsxgVQ=
|
github.com/go-critic/go-critic v0.14.3/go.mod h1:xwntfW6SYAd7h1OqDzmN6hBX/JxsEKl5up/Y2bsxgVQ=
|
||||||
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA=
|
||||||
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
|
@ -304,8 +291,8 @@ github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7
|
||||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||||
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
|
||||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||||
github.com/go-quicktest/qt v1.102.0 h1:HSQxCeh5YZH3EL3W39ixjtyaEhcWSXQHtHnMBzSs474=
|
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
|
||||||
github.com/go-quicktest/qt v1.102.0/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI=
|
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
|
||||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||||
|
|
@ -346,14 +333,14 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/golangci/asciicheck v0.5.0 h1:jczN/BorERZwK8oiFBOGvlGPknhvq0bjnysTj4nUfo0=
|
github.com/golangci/asciicheck v0.5.0 h1:jczN/BorERZwK8oiFBOGvlGPknhvq0bjnysTj4nUfo0=
|
||||||
github.com/golangci/asciicheck v0.5.0/go.mod h1:5RMNAInbNFw2krqN6ibBxN/zfRFa9S6tA1nPdM0l8qQ=
|
github.com/golangci/asciicheck v0.5.0/go.mod h1:5RMNAInbNFw2krqN6ibBxN/zfRFa9S6tA1nPdM0l8qQ=
|
||||||
github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202 h1:CbTB8KpqnViI6lIXxp03Oclc4VFHi3K4BWC1TacsZ+A=
|
github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 h1:WUvBfQL6EW/40l6OmeSBYQJNSif4O11+bmWEz+C7FYw=
|
||||||
github.com/golangci/dupl v0.0.0-20260401084720-c99c5cf5c202/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E=
|
github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32/go.mod h1:NUw9Zr2Sy7+HxzdjIULge71wI6yEg1lWQr7Evcu8K0E=
|
||||||
github.com/golangci/go-printf-func-name v0.1.1 h1:hIYTFJqAGp1iwoIfsNTpoq1xZAarogrvjO9AfiW3B4U=
|
github.com/golangci/go-printf-func-name v0.1.1 h1:hIYTFJqAGp1iwoIfsNTpoq1xZAarogrvjO9AfiW3B4U=
|
||||||
github.com/golangci/go-printf-func-name v0.1.1/go.mod h1:Es64MpWEZbh0UBtTAICOZiB+miW53w/K9Or/4QogJss=
|
github.com/golangci/go-printf-func-name v0.1.1/go.mod h1:Es64MpWEZbh0UBtTAICOZiB+miW53w/K9Or/4QogJss=
|
||||||
github.com/golangci/gofmt v0.0.0-20260820135601-e84e05053792 h1:WL8YKrt3UbOBqSRU7GpP5BTtQTMWtVtj+mfPijgZeIg=
|
github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d h1:viFft9sS/dxoYY0aiOTsLKO2aZQAPT4nlQCsimGcSGE=
|
||||||
github.com/golangci/gofmt v0.0.0-20260820135601-e84e05053792/go.mod h1:te5hX0dW4C5r6YbXs+6ysNr8Q5UTmdIqGbb+mlFiYmA=
|
github.com/golangci/gofmt v0.0.0-20250106114630-d62b90e6713d/go.mod h1:ivJ9QDg0XucIkmwhzCDsqcnxxlDStoTl89jDMIoNxKY=
|
||||||
github.com/golangci/golangci-lint/v2 v2.13.1 h1:RuM4OcluM4xFQcGuRE6R7jA33pqxK/W1EsBxpugdZjg=
|
github.com/golangci/golangci-lint/v2 v2.10.1 h1:flhw5Px6ojbLyEFzXvJn5B2HEdkkRlkhE1SnmCbQBiE=
|
||||||
github.com/golangci/golangci-lint/v2 v2.13.1/go.mod h1:HwX7mDzqHbcSxlhrTygjX1GJbAfQ3sJAqOx41qQlhDE=
|
github.com/golangci/golangci-lint/v2 v2.10.1/go.mod h1:dBsrOk6zj0vDhlTv+IiJGqkDokR24IVTS7W3EVfPTQY=
|
||||||
github.com/golangci/golines v0.15.0 h1:Qnph25g8Y1c5fdo1X7GaRDGgnMHgnxh4Gk4VfPTtRx0=
|
github.com/golangci/golines v0.15.0 h1:Qnph25g8Y1c5fdo1X7GaRDGgnMHgnxh4Gk4VfPTtRx0=
|
||||||
github.com/golangci/golines v0.15.0/go.mod h1:AZjXd23tbHMpowhtnGlj9KCNsysj72aeZVVHnVcZx10=
|
github.com/golangci/golines v0.15.0/go.mod h1:AZjXd23tbHMpowhtnGlj9KCNsysj72aeZVVHnVcZx10=
|
||||||
github.com/golangci/misspell v0.8.0 h1:qvxQhiE2/5z+BVRo1kwYA8yGz+lOlu5Jfvtx2b04Jbg=
|
github.com/golangci/misspell v0.8.0 h1:qvxQhiE2/5z+BVRo1kwYA8yGz+lOlu5Jfvtx2b04Jbg=
|
||||||
|
|
@ -362,8 +349,6 @@ github.com/golangci/plugin-module-register v0.1.2 h1:e5WM6PO6NIAEcij3B053CohVp3H
|
||||||
github.com/golangci/plugin-module-register v0.1.2/go.mod h1:1+QGTsKBvAIvPvoY/os+G5eoqxWn70HYDm2uvUyGuVw=
|
github.com/golangci/plugin-module-register v0.1.2/go.mod h1:1+QGTsKBvAIvPvoY/os+G5eoqxWn70HYDm2uvUyGuVw=
|
||||||
github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s=
|
github.com/golangci/revgrep v0.8.0 h1:EZBctwbVd0aMeRnNUsFogoyayvKHyxlV3CdUA46FX2s=
|
||||||
github.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k=
|
github.com/golangci/revgrep v0.8.0/go.mod h1:U4R/s9dlXZsg8uJmaR1GrloUr14D7qDl8gi2iPXJH8k=
|
||||||
github.com/golangci/rowserrcheck v0.0.0-20260419091836-c5f79b8a11ba h1:lqtcnSMDuuJdu/LrKWi5RJzpSNLOJXYe/nzQutTI5kg=
|
|
||||||
github.com/golangci/rowserrcheck v0.0.0-20260419091836-c5f79b8a11ba/go.mod h1:sCBNcpRmhJCtbFGz49+IM3ETTFf7QdJ30AeYCd43NKk=
|
|
||||||
github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e h1:ai0EfmVYE2bRA5htgAG9r7s3tHsfjIhN98WshBTJ9jM=
|
github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e h1:ai0EfmVYE2bRA5htgAG9r7s3tHsfjIhN98WshBTJ9jM=
|
||||||
github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e/go.mod h1:Vrn4B5oR9qRwM+f54koyeH3yzphlecwERs0el27Fr/s=
|
github.com/golangci/swaggoswag v0.0.0-20250504205917-77f2aca3143e/go.mod h1:Vrn4B5oR9qRwM+f54koyeH3yzphlecwERs0el27Fr/s=
|
||||||
github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e h1:gD6P7NEo7Eqtt0ssnqSJNNndxe69DOQ24A5h7+i3KpM=
|
github.com/golangci/unconvert v0.0.0-20250410112200-a129a6e6413e h1:gD6P7NEo7Eqtt0ssnqSJNNndxe69DOQ24A5h7+i3KpM=
|
||||||
|
|
@ -387,10 +372,10 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4=
|
github.com/google/wire v0.7.0 h1:JxUKI6+CVBgCO2WToKy/nQk0sS+amI9z9EjVmdaocj4=
|
||||||
github.com/google/wire v0.7.0/go.mod h1:n6YbUQD9cPKTnHXEBN2DXlOp/mVADhVErcMFb0v3J18=
|
github.com/google/wire v0.7.0/go.mod h1:n6YbUQD9cPKTnHXEBN2DXlOp/mVADhVErcMFb0v3J18=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.18 h1:hvVi34VucdrV1IIsiWuqYM8kutw/92MxNEFxCJZEh0k=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.14 h1:yh8ncqsbUY4shRD5dA6RlzjJaT4hi3kII+zYw8wmLb8=
|
||||||
github.com/googleapis/enterprise-certificate-proxy v0.3.18/go.mod h1:rSEsBUemEBZEexP2y6jPp16LUmUbjmSbcPMQizR0o4k=
|
github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
|
||||||
github.com/googleapis/gax-go/v2 v2.23.0 h1:Tchl7qkvE7Ip3y+ztvNufYFvkfqTe7NfLTYGIdJRLuE=
|
github.com/googleapis/gax-go/v2 v2.19.0 h1:fYQaUOiGwll0cGj7jmHT/0nPlcrZDFPrZRhTsoCr8hE=
|
||||||
github.com/googleapis/gax-go/v2 v2.23.0/go.mod h1:rBQKOVJCdb8IFEzg+FCwlt1LP/xMDGuqUXhUG+XMXEg=
|
github.com/googleapis/gax-go/v2 v2.19.0/go.mod h1:w2ROXVdfGEVFXzmlciUU4EdjHgWvB5h2n6x/8XSTTJA=
|
||||||
github.com/gordonklaus/ineffassign v0.2.0 h1:Uths4KnmwxNJNzq87fwQQDDnbNb7De00VOk9Nu0TySs=
|
github.com/gordonklaus/ineffassign v0.2.0 h1:Uths4KnmwxNJNzq87fwQQDDnbNb7De00VOk9Nu0TySs=
|
||||||
github.com/gordonklaus/ineffassign v0.2.0/go.mod h1:TIpymnagPSexySzs7F9FnO1XFTy8IT3a59vmZp5Y9Lw=
|
github.com/gordonklaus/ineffassign v0.2.0/go.mod h1:TIpymnagPSexySzs7F9FnO1XFTy8IT3a59vmZp5Y9Lw=
|
||||||
github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk=
|
github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk=
|
||||||
|
|
@ -410,8 +395,8 @@ github.com/hashicorp/go-immutable-radix/v2 v2.1.0/go.mod h1:hgdqLXA4f6NIjRVisM1T
|
||||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA=
|
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4=
|
||||||
github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
|
|
@ -420,8 +405,10 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq
|
||||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/jgautheron/goconst v1.11.0 h1:KgN90z5qXt5f0Uzf3cWXev3hfMMFUyNeKdpkSBRvLDk=
|
github.com/jgautheron/goconst v1.8.2 h1:y0XF7X8CikZ93fSNT6WBTb/NElBu9IjaY7CCYQrCMX4=
|
||||||
github.com/jgautheron/goconst v1.11.0/go.mod h1:0p+wv1lFOiUr0IlNNT1nrm6+8DB8u2sU6KHGzFRXHDc=
|
github.com/jgautheron/goconst v1.8.2/go.mod h1:A0oxgBCHy55NQn6sYpO7UdnA9p+h7cPtoOZUmvNIako=
|
||||||
|
github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs=
|
||||||
|
github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c=
|
||||||
github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8=
|
github.com/jjti/go-spancheck v0.6.5 h1:lmi7pKxa37oKYIMScialXUK6hP3iY5F1gu+mLBPgYB8=
|
||||||
github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU=
|
github.com/jjti/go-spancheck v0.6.5/go.mod h1:aEogkeatBrbYsyW6y5TgDfihCulDYciL1B7rG2vSsrU=
|
||||||
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||||
|
|
@ -435,8 +422,8 @@ github.com/karamaru-alpha/copyloopvar v1.2.2 h1:yfNQvP9YaGQR7VaWLYcfZUlRP2eo2vhE
|
||||||
github.com/karamaru-alpha/copyloopvar v1.2.2/go.mod h1:oY4rGZqZ879JkJMtX3RRkcXRkmUvH0x35ykgaKgsgJY=
|
github.com/karamaru-alpha/copyloopvar v1.2.2/go.mod h1:oY4rGZqZ879JkJMtX3RRkcXRkmUvH0x35ykgaKgsgJY=
|
||||||
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
|
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
|
||||||
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
|
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
|
||||||
github.com/kisielk/errcheck v1.20.0 h1:9rwHBNKzd4wkDWcROy3DvFGNqEPlkxBg305rvk7HabI=
|
github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M=
|
||||||
github.com/kisielk/errcheck v1.20.0/go.mod h1:O+f80MKNwX8Oor2jwgpeQ9An7uJm+hRSgT+h22knRJU=
|
github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8=
|
||||||
github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE=
|
github.com/kkHAIKE/contextcheck v1.1.6 h1:7HIyRcnyzxL9Lz06NGhiKvenXq7Zw6Q0UQu/ttjfJCE=
|
||||||
github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg=
|
github.com/kkHAIKE/contextcheck v1.1.6/go.mod h1:3dDbMRNBFaq8HFXWC1JyvDSPm43CmE6IuHam8Wr0rkg=
|
||||||
github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
|
github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
|
||||||
|
|
@ -458,8 +445,8 @@ github.com/lasiar/canonicalheader v1.1.2 h1:vZ5uqwvDbyJCnMhmFYimgMZnJMjwljN5VGY0
|
||||||
github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI=
|
github.com/lasiar/canonicalheader v1.1.2/go.mod h1:qJCeLFS0G/QlLQ506T+Fk/fWMa2VmBUiEI2cuMK4djI=
|
||||||
github.com/ldez/exptostd v0.4.5 h1:kv2ZGUVI6VwRfp/+bcQ6Nbx0ghFWcGIKInkG/oFn1aQ=
|
github.com/ldez/exptostd v0.4.5 h1:kv2ZGUVI6VwRfp/+bcQ6Nbx0ghFWcGIKInkG/oFn1aQ=
|
||||||
github.com/ldez/exptostd v0.4.5/go.mod h1:QRjHRMXJrCTIm9WxVNH6VW7oN7KrGSht69bIRwvdFsM=
|
github.com/ldez/exptostd v0.4.5/go.mod h1:QRjHRMXJrCTIm9WxVNH6VW7oN7KrGSht69bIRwvdFsM=
|
||||||
github.com/ldez/gomoddirectives v0.9.0 h1:2YV/EX7nVlWL4jySusYTzBKHuE3D2fgcRsQuMa3yIoo=
|
github.com/ldez/gomoddirectives v0.8.0 h1:JqIuTtgvFC2RdH1s357vrE23WJF2cpDCPFgA/TWDGpk=
|
||||||
github.com/ldez/gomoddirectives v0.9.0/go.mod h1:DdZzfm9MdXCjn2/UGYXCFfo+tzrp2Ib4iD2Q0kIJkwE=
|
github.com/ldez/gomoddirectives v0.8.0/go.mod h1:jutzamvZR4XYJLr0d5Honycp4Gy6GEg2mS9+2YX3F1Q=
|
||||||
github.com/ldez/grignotin v0.10.1 h1:keYi9rYsgbvqAZGI1liek5c+jv9UUjbvdj3Tbn5fn4o=
|
github.com/ldez/grignotin v0.10.1 h1:keYi9rYsgbvqAZGI1liek5c+jv9UUjbvdj3Tbn5fn4o=
|
||||||
github.com/ldez/grignotin v0.10.1/go.mod h1:UlDbXFCARrXbWGNGP3S5vsysNXAPhnSuBufpTEbwOas=
|
github.com/ldez/grignotin v0.10.1/go.mod h1:UlDbXFCARrXbWGNGP3S5vsysNXAPhnSuBufpTEbwOas=
|
||||||
github.com/ldez/structtags v0.6.1 h1:bUooFLbXx41tW8SvkfwfFkkjPYvFFs59AAMgVg6DUBk=
|
github.com/ldez/structtags v0.6.1 h1:bUooFLbXx41tW8SvkfwfFkkjPYvFFs59AAMgVg6DUBk=
|
||||||
|
|
@ -473,8 +460,8 @@ github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFB
|
||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
|
github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ=
|
||||||
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||||
github.com/lucasb-eyer/go-colorful v1.4.1 h1:1EO+WB73+EH8EVbzlrG3KLAfEypQWVHIBqlTf+2hNss=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/lucasb-eyer/go-colorful v1.4.1/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddBCpE=
|
github.com/macabu/inamedparam v0.2.0 h1:VyPYpOc10nkhI2qeNUdh3Zket4fcZjEWe35poddBCpE=
|
||||||
github.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U=
|
github.com/macabu/inamedparam v0.2.0/go.mod h1:+Pee9/YfGe5LJ62pYXqB89lJ+0k5bsR8Wgz/C0Zlq3U=
|
||||||
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||||
|
|
@ -486,8 +473,8 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
github.com/manuelarte/embeddedstructfieldcheck v0.4.0 h1:3mAIyaGRtjK6EO9E73JlXLtiy7ha80b2ZVGyacxgfww=
|
github.com/manuelarte/embeddedstructfieldcheck v0.4.0 h1:3mAIyaGRtjK6EO9E73JlXLtiy7ha80b2ZVGyacxgfww=
|
||||||
github.com/manuelarte/embeddedstructfieldcheck v0.4.0/go.mod h1:z8dFSyXqp+fC6NLDSljRJeNQJJDWnY7RoWFzV3PC6UM=
|
github.com/manuelarte/embeddedstructfieldcheck v0.4.0/go.mod h1:z8dFSyXqp+fC6NLDSljRJeNQJJDWnY7RoWFzV3PC6UM=
|
||||||
github.com/manuelarte/funcorder v0.6.0 h1:0hBngc4fa1IgNiI65A7sFGkMvoMCc878RjqB5V7rWP0=
|
github.com/manuelarte/funcorder v0.5.0 h1:llMuHXXbg7tD0i/LNw8vGnkDTHFpTnWqKPI85Rknc+8=
|
||||||
github.com/manuelarte/funcorder v0.6.0/go.mod h1:id3NDhXdQBmeqXH7eVC6Z89xS6JxvZ8kF9xUxpArU/g=
|
github.com/manuelarte/funcorder v0.5.0/go.mod h1:Yt3CiUQthSBMBxjShjdXMexmzpP8YGvGLjrxJNkO2hA=
|
||||||
github.com/maratori/testableexamples v1.0.1 h1:HfOQXs+XgfeRBJ+Wz0XfH+FHnoY9TVqL6Fcevpzy4q8=
|
github.com/maratori/testableexamples v1.0.1 h1:HfOQXs+XgfeRBJ+Wz0XfH+FHnoY9TVqL6Fcevpzy4q8=
|
||||||
github.com/maratori/testableexamples v1.0.1/go.mod h1:XE2F/nQs7B9N08JgyRmdGjYVGqxWwClLPCGSQhXQSrQ=
|
github.com/maratori/testableexamples v1.0.1/go.mod h1:XE2F/nQs7B9N08JgyRmdGjYVGqxWwClLPCGSQhXQSrQ=
|
||||||
github.com/maratori/testpackage v1.1.2 h1:ffDSh+AgqluCLMXhM19f/cpvQAKygKAJXFl9aUjmbqs=
|
github.com/maratori/testpackage v1.1.2 h1:ffDSh+AgqluCLMXhM19f/cpvQAKygKAJXFl9aUjmbqs=
|
||||||
|
|
@ -496,24 +483,24 @@ github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4=
|
||||||
github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs=
|
github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs=
|
||||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||||
github.com/mattn/go-colorable v0.1.15 h1:+u9SLTRGnXv73cEsnsmoZBom+dMU88B2M0aDcWy0/jY=
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
github.com/mattn/go-colorable v0.1.15/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
|
github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
|
||||||
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
|
github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
|
||||||
github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU=
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/mgechev/revive v1.15.0 h1:vJ0HzSBzfNyPbHKolgiFjHxLek9KUijhqh42yGoqZ8Q=
|
github.com/mgechev/revive v1.14.0 h1:CC2Ulb3kV7JFYt+izwORoS3VT/+Plb8BvslI/l1yZsc=
|
||||||
github.com/mgechev/revive v1.15.0/go.mod h1:LlAKO3QQe9OJ0pVZzI2GPa8CbXGZ/9lNpCGvK4T/a8A=
|
github.com/mgechev/revive v1.14.0/go.mod h1:MvnujelCZBZCaoDv5B3foPo6WWgULSSFxvfxp7GsPfo=
|
||||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI=
|
github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI=
|
||||||
github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U=
|
github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U=
|
||||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U=
|
github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U=
|
||||||
|
|
@ -525,16 +512,16 @@ github.com/nishanths/exhaustive v0.12.0 h1:vIY9sALmw6T/yxiASewa4TQcFsVYZQQRUQJhK
|
||||||
github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs=
|
github.com/nishanths/exhaustive v0.12.0/go.mod h1:mEZ95wPIZW+x8kC4TgC+9YCUgiST7ecevsVDTgc2obs=
|
||||||
github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk=
|
github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk=
|
||||||
github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c=
|
github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c=
|
||||||
github.com/nunnatsa/ginkgolinter v0.24.0 h1:Mp0EagluLFP98JatP6nqp/gGEoljNG97uf9AcxcBVy8=
|
github.com/nunnatsa/ginkgolinter v0.23.0 h1:x3o4DGYOWbBMP/VdNQKgSj+25aJKx2Pe6lHr8gBcgf8=
|
||||||
github.com/nunnatsa/ginkgolinter v0.24.0/go.mod h1:2ZMRuzX6+3XXyY6UZOwb6n+MCocVGbkIsDBC4vuWz5c=
|
github.com/nunnatsa/ginkgolinter v0.23.0/go.mod h1:9qN1+0akwXEccwV1CAcCDfcoBlWXHB+ML9884pL4SZ4=
|
||||||
github.com/oapi-codegen/nullable v1.2.0 h1:VflFkDW980KhBPiFF7nWSyjg+r4Obqj8lXipV0UkP5w=
|
github.com/oapi-codegen/nullable v1.2.0 h1:VflFkDW980KhBPiFF7nWSyjg+r4Obqj8lXipV0UkP5w=
|
||||||
github.com/oapi-codegen/nullable v1.2.0/go.mod h1:KUZ3vUzkmEKY90ksAmit2+5juDIhIZhfDl+0PwOQlFY=
|
github.com/oapi-codegen/nullable v1.2.0/go.mod h1:KUZ3vUzkmEKY90ksAmit2+5juDIhIZhfDl+0PwOQlFY=
|
||||||
github.com/oapi-codegen/runtime v1.6.0 h1:7Xx+GlueD6nRuyKoCPzL434Jfi3BetbiJOrzCHp/VPU=
|
github.com/oapi-codegen/runtime v1.6.0 h1:7Xx+GlueD6nRuyKoCPzL434Jfi3BetbiJOrzCHp/VPU=
|
||||||
github.com/oapi-codegen/runtime v1.6.0/go.mod h1:GwV7hC2hviaMzj+ITfHVRESK5J2W/GefVwIND/bMGvU=
|
github.com/oapi-codegen/runtime v1.6.0/go.mod h1:GwV7hC2hviaMzj+ITfHVRESK5J2W/GefVwIND/bMGvU=
|
||||||
github.com/onsi/ginkgo/v2 v2.32.0 h1:Hw7s2pVrQo/8Yz5N77qdnpHaoc+c6cC9WIV1Jce+J6E=
|
github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI=
|
||||||
github.com/onsi/ginkgo/v2 v2.32.0/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44=
|
github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
|
||||||
github.com/onsi/gomega v1.42.1 h1:iN1rCUX+44NZ1Dc97MPoeFYbFR0vh8zxoxMFwKdyZ6I=
|
github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
|
||||||
github.com/onsi/gomega v1.42.1/go.mod h1:REff/hsDsodHoKlWsP2mAPhu1+5/6hVYNf9rIEBpeSg=
|
github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=
|
||||||
github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
|
github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw=
|
||||||
github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
|
github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU=
|
||||||
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
|
github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w=
|
||||||
|
|
@ -548,17 +535,16 @@ github.com/pandatix/go-cvss v0.6.2 h1:TFiHlzUkT67s6UkelHmK6s1INKVUG7nlKYiWWDTITG
|
||||||
github.com/pandatix/go-cvss v0.6.2/go.mod h1:jDXYlQBZrc8nvrMUVVvTG8PhmuShOnKrxP53nOFkt8Q=
|
github.com/pandatix/go-cvss v0.6.2/go.mod h1:jDXYlQBZrc8nvrMUVVvTG8PhmuShOnKrxP53nOFkt8Q=
|
||||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY=
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
github.com/peterbourgon/g2s v0.0.0-20170223122336-d4e7ad98afea h1:sKwxy1H95npauwu8vtF95vG/syrL0p8fSZo/XlDg5gk=
|
github.com/peterbourgon/g2s v0.0.0-20170223122336-d4e7ad98afea h1:sKwxy1H95npauwu8vtF95vG/syrL0p8fSZo/XlDg5gk=
|
||||||
github.com/peterbourgon/g2s v0.0.0-20170223122336-d4e7ad98afea/go.mod h1:1VcHEd3ro4QMoHfiNl/j7Jkln9+KQuorp0PItHMJYNg=
|
github.com/peterbourgon/g2s v0.0.0-20170223122336-d4e7ad98afea/go.mod h1:1VcHEd3ro4QMoHfiNl/j7Jkln9+KQuorp0PItHMJYNg=
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
|
||||||
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
|
||||||
github.com/prometheus/client_golang v1.24.1 h1:JnJkREXzWxUdCuPFpIWZiPispT9xVV59uiuyR2bPlnU=
|
github.com/prometheus/client_golang v1.24.1 h1:JnJkREXzWxUdCuPFpIWZiPispT9xVV59uiuyR2bPlnU=
|
||||||
github.com/prometheus/client_golang v1.24.1/go.mod h1:F+oSRECHg4sse5ucfYpYDeIv/hu68Zo0uoHKetWnzcE=
|
github.com/prometheus/client_golang v1.24.1/go.mod h1:F+oSRECHg4sse5ucfYpYDeIv/hu68Zo0uoHKetWnzcE=
|
||||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||||
|
|
@ -577,14 +563,15 @@ github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 h1:TCg2WBOl
|
||||||
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0=
|
github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0=
|
||||||
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs=
|
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs=
|
||||||
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ=
|
github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ=
|
||||||
github.com/raeperd/recvcheck v0.3.0 h1:PM+XYvyxIj3bo+kobJfFTdTuU3Lmfu96mKDbyHDbRt8=
|
github.com/raeperd/recvcheck v0.2.0 h1:GnU+NsbiCqdC2XX5+vMZzP+jAJC5fht7rcVTAhX74UI=
|
||||||
github.com/raeperd/recvcheck v0.3.0/go.mod h1:PZNwG+HztFYMH2ZPq0Hu3QgkV2yiA6VrtNz9c1fXWJo=
|
github.com/raeperd/recvcheck v0.2.0/go.mod h1:n04eYkwIR0JbgD73wT8wL4JjPC3wm0nFtzBnWNocnYU=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 h1:18kd+8ZUlt/ARXhljq+14TwAoKa61q6dX8jtwOf6DH8=
|
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 h1:18kd+8ZUlt/ARXhljq+14TwAoKa61q6dX8jtwOf6DH8=
|
||||||
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA=
|
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529/go.mod h1:qe5TWALJ8/a1Lqznoc5BDHpYX/8HU60Hm2AwRmqzxqA=
|
||||||
github.com/rubyist/circuitbreaker v2.2.1+incompatible h1:KUKd/pV8Geg77+8LNDwdow6rVCAYOp8+kHUyFvL6Mhk=
|
github.com/rubyist/circuitbreaker v2.2.1+incompatible h1:KUKd/pV8Geg77+8LNDwdow6rVCAYOp8+kHUyFvL6Mhk=
|
||||||
|
|
@ -594,31 +581,31 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb7Elr+g=
|
github.com/ryancurrah/gomodguard v1.4.1 h1:eWC8eUMNZ/wM/PWuZBv7JxxqT5fiIKSIyTvjb7Elr+g=
|
||||||
github.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I=
|
github.com/ryancurrah/gomodguard v1.4.1/go.mod h1:qnMJwV1hX9m+YJseXEBhd2s90+1Xn6x9dLz11ualI1I=
|
||||||
github.com/ryancurrah/gomodguard/v2 v2.1.3 h1:E7sz3PJwE9Ba1reVxSpF6XLCPJZ74Kfw/LabTNM4GIA=
|
github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU=
|
||||||
github.com/ryancurrah/gomodguard/v2 v2.1.3/go.mod h1:CQicdLGatWMxLX53JzoBjYlsNZhHbmLv2AVa0s2aivU=
|
github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ=
|
||||||
github.com/ryanrolds/sqlclosecheck v0.6.0 h1:pEyL9okISdg1F1SEpJNlrEotkTGerv5BMk7U4AG0eVg=
|
|
||||||
github.com/ryanrolds/sqlclosecheck v0.6.0/go.mod h1:xyX16hsDaCMXHrMJ3JMzGf5OpDfHTOTTQrT7HOFUmeU=
|
|
||||||
github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0=
|
github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0=
|
||||||
github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4=
|
github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4=
|
||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 h1:1EYB5IzjZawrrnELUi78f9fPu57HuXjmddZPjrls/28=
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
|
||||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
||||||
github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw=
|
github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw=
|
||||||
github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ=
|
github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ=
|
||||||
github.com/sashamelentyev/usestdlibvars v1.29.0 h1:8J0MoRrw4/NAXtjQqTHrbW9NN+3iMf7Knkq057v4XOQ=
|
github.com/sashamelentyev/usestdlibvars v1.29.0 h1:8J0MoRrw4/NAXtjQqTHrbW9NN+3iMf7Knkq057v4XOQ=
|
||||||
github.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8=
|
github.com/sashamelentyev/usestdlibvars v1.29.0/go.mod h1:8PpnjHMk5VdeWlVb4wCdrB8PNbLqZ3wBZTZWkrpZZL8=
|
||||||
github.com/securego/gosec/v2 v2.28.0 h1:ZsSdiDb0AtTpLFVol5z91gbMei9ZiLEPG/pZjZujp7c=
|
github.com/securego/gosec/v2 v2.23.0 h1:h4TtF64qFzvnkqvsHC/knT7YC5fqyOCItlVR8+ptEBo=
|
||||||
github.com/securego/gosec/v2 v2.28.0/go.mod h1:lb4/9AHe+lJy/kjWmWRWWsEipvbwGKuxf+tY1Pmjdnk=
|
github.com/securego/gosec/v2 v2.23.0/go.mod h1:qRHEgXLFuYUDkI2T7W7NJAmOkxVhkR0x9xyHOIcMNZ0=
|
||||||
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
||||||
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
|
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||||
|
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/sirupsen/logrus v1.10.1 h1:xi4336Zh11WpU14fXR6I67V3yaTPQYwRx2WEtHbRg4Q=
|
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||||
github.com/sirupsen/logrus v1.10.1/go.mod h1:vsQHnG7xzNsxk3NrwboUiWPnIC3dmbjcGPykD7+tiHk=
|
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||||
github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE=
|
github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE=
|
||||||
github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4=
|
github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4=
|
||||||
github.com/sonatard/noctx v0.5.1 h1:wklWg9c9ZYugOAk7qG4yP4PBrlQsmSLPTvW1K4PRQMs=
|
github.com/sonatard/noctx v0.4.0 h1:7MC/5Gg4SQ4lhLYR6mvOP6mQVSxCrdyiExo7atBs27o=
|
||||||
github.com/sonatard/noctx v0.5.1/go.mod h1:64XdbzFb18XL4LporKXp8poqZtPKbCrqQ402CV+kJas=
|
github.com/sonatard/noctx v0.4.0/go.mod h1:64XdbzFb18XL4LporKXp8poqZtPKbCrqQ402CV+kJas=
|
||||||
github.com/sourcegraph/go-diff v0.8.0 h1:ipIyu4cTsLbIrln4l0qtHA3r0a7gyK4ntKjtQytHhvY=
|
github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0=
|
||||||
github.com/sourcegraph/go-diff v0.8.0/go.mod h1:hWlcO7Al+UZStZAP8rBumHpCK5ZHQ5BXsMls8p4+F5E=
|
github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs=
|
||||||
github.com/spdx/tools-golang v0.5.7 h1:+sWcKGnhwp3vLdMqPcLdA6QK679vd86cK9hQWH3AwCg=
|
github.com/spdx/tools-golang v0.5.7 h1:+sWcKGnhwp3vLdMqPcLdA6QK679vd86cK9hQWH3AwCg=
|
||||||
github.com/spdx/tools-golang v0.5.7/go.mod h1:jg7w0LOpoNAw6OxKEzCoqPC2GCTj45LyTlVmXubDsYw=
|
github.com/spdx/tools-golang v0.5.7/go.mod h1:jg7w0LOpoNAw6OxKEzCoqPC2GCTj45LyTlVmXubDsYw=
|
||||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||||
|
|
@ -643,14 +630,14 @@ github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRk
|
||||||
github.com/stbenjam/no-sprintf-host-port v0.3.1 h1:AyX7+dxI4IdLBPtDbsGAyqiTSLpCP9hWRrXQDU4Cm/g=
|
github.com/stbenjam/no-sprintf-host-port v0.3.1 h1:AyX7+dxI4IdLBPtDbsGAyqiTSLpCP9hWRrXQDU4Cm/g=
|
||||||
github.com/stbenjam/no-sprintf-host-port v0.3.1/go.mod h1:ODbZesTCHMVKthBHskvUUexdcNHAQRXk9NpSsL8p/HQ=
|
github.com/stbenjam/no-sprintf-host-port v0.3.1/go.mod h1:ODbZesTCHMVKthBHskvUUexdcNHAQRXk9NpSsL8p/HQ=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
|
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
|
||||||
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||||
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
|
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
|
||||||
|
|
@ -661,10 +648,10 @@ github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpR
|
||||||
github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY=
|
github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY=
|
||||||
github.com/terminalstatic/go-xsd-validate v0.1.6 h1:TenYeQ3eY631qNi1/cTmLH/s2slHPRKTTHT+XSHkepo=
|
github.com/terminalstatic/go-xsd-validate v0.1.6 h1:TenYeQ3eY631qNi1/cTmLH/s2slHPRKTTHT+XSHkepo=
|
||||||
github.com/terminalstatic/go-xsd-validate v0.1.6/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw=
|
github.com/terminalstatic/go-xsd-validate v0.1.6/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw=
|
||||||
github.com/tetafro/godot v1.5.6 h1:IEkrFCwXaYHlOn4mGzGS3F3dkP6m9t0jpwqBFPIkKiA=
|
github.com/tetafro/godot v1.5.4 h1:u1ww+gqpRLiIA16yF2PV1CV1n/X3zhyezbNXC3E14Sg=
|
||||||
github.com/tetafro/godot v1.5.6/go.mod h1:eOkMrVQurDui411nBY2FA05EYH01r14LuWY/NrVDVcU=
|
github.com/tetafro/godot v1.5.4/go.mod h1:eOkMrVQurDui411nBY2FA05EYH01r14LuWY/NrVDVcU=
|
||||||
github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4 h1:SiHe5XLTn9sFWJ5pBwJ5FN/4j34q9ZlOAD//kMoMYp0=
|
github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67 h1:9LPGD+jzxMlnk5r6+hJnar67cgpDIz/iyD+rfl5r2Vk=
|
||||||
github.com/timakin/bodyclose v0.0.0-20260129054331-73d1f95b84b4/go.mod h1:sDHLK7rb/59v/ZxZ7KtymgcoxuUMxjXq8gtu9VMOK8M=
|
github.com/timakin/bodyclose v0.0.0-20241222091800-1db5c5ca4d67/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460=
|
||||||
github.com/timonwong/loggercheck v0.11.0 h1:jdaMpYBl+Uq9mWPXv1r8jc5fC3gyXx4/WGwTnnNKn4M=
|
github.com/timonwong/loggercheck v0.11.0 h1:jdaMpYBl+Uq9mWPXv1r8jc5fC3gyXx4/WGwTnnNKn4M=
|
||||||
github.com/timonwong/loggercheck v0.11.0/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8=
|
github.com/timonwong/loggercheck v0.11.0/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8=
|
||||||
github.com/tomarrell/wrapcheck/v2 v2.12.0 h1:H/qQ1aNWz/eeIhxKAFvkfIA+N7YDvq6TWVFL27Of9is=
|
github.com/tomarrell/wrapcheck/v2 v2.12.0 h1:H/qQ1aNWz/eeIhxKAFvkfIA+N7YDvq6TWVFL27Of9is=
|
||||||
|
|
@ -679,10 +666,10 @@ github.com/ultraware/whitespace v0.2.0 h1:TYowo2m9Nfj1baEQBjuHzvMRbp19i+RCcRYrSW
|
||||||
github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8=
|
github.com/ultraware/whitespace v0.2.0/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8=
|
||||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||||
github.com/uudashr/gocognit v1.2.1 h1:CSJynt5txTnORn/DkhiB4mZjwPuifyASC8/6Q0I/QS4=
|
github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYRA=
|
||||||
github.com/uudashr/gocognit v1.2.1/go.mod h1:acaubQc6xYlXFEMb9nWX2dYBzJ/bIjEkc1zzvyIZg5Q=
|
github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU=
|
||||||
github.com/uudashr/iface v1.5.0 h1:PgdMt4uAettGG8K/Kbamc4B9FABgUgnS3TLbl6fnjEk=
|
github.com/uudashr/iface v1.4.1 h1:J16Xl1wyNX9ofhpHmQ9h9gk5rnv2A6lX/2+APLTo0zU=
|
||||||
github.com/uudashr/iface v1.5.0/go.mod h1:pbeBPlbuU2qkNDn0mmfrxP2X+wjPMIQAy+r1MBXSXtg=
|
github.com/uudashr/iface v1.4.1/go.mod h1:pbeBPlbuU2qkNDn0mmfrxP2X+wjPMIQAy+r1MBXSXtg=
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
|
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||||
|
|
@ -702,6 +689,7 @@ github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo=
|
gitlab.com/bosi/decorder v0.4.2 h1:qbQaV3zgwnBZ4zPMhGLW4KZe7A7NwxEhJx39R3shffo=
|
||||||
|
|
@ -710,20 +698,20 @@ go-simpler.org/assert v0.9.0 h1:PfpmcSvL7yAnWyChSjOz6Sp6m9j5lyK8Ok9pEL31YkQ=
|
||||||
go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28=
|
go-simpler.org/assert v0.9.0/go.mod h1:74Eqh5eI6vCK6Y5l3PI8ZYFXG4Sa+tkr70OIPJAUr28=
|
||||||
go-simpler.org/musttag v0.14.0 h1:XGySZATqQYSEV3/YTy+iX+aofbZZllJaqwFWs+RTtSo=
|
go-simpler.org/musttag v0.14.0 h1:XGySZATqQYSEV3/YTy+iX+aofbZZllJaqwFWs+RTtSo=
|
||||||
go-simpler.org/musttag v0.14.0/go.mod h1:uP8EymctQjJ4Z1kUnjX0u2l60WfUdQxCwSNKzE1JEOE=
|
go-simpler.org/musttag v0.14.0/go.mod h1:uP8EymctQjJ4Z1kUnjX0u2l60WfUdQxCwSNKzE1JEOE=
|
||||||
go-simpler.org/sloglint v0.12.0 h1:UzWDlLWNE5FLqsvyq3tWYHuQMbqrervOhT8qPl4Mmw4=
|
go-simpler.org/sloglint v0.11.1 h1:xRbPepLT/MHPTCA6TS/wNfZrDzkGvCCqUv4Bdwc3H7s=
|
||||||
go-simpler.org/sloglint v0.12.0/go.mod h1:jBjjC2bm8rYrs88oTRlFX497kWjJsyZWYoNaXkGRI6I=
|
go-simpler.org/sloglint v0.11.1/go.mod h1:2PowwiCOK8mjiF+0KGifVOT8ZsCNiFzvfyJeJOIt8MQ=
|
||||||
go.augendre.info/arangolint v0.4.0 h1:xSCZjRoS93nXazBSg5d0OGCi9APPLNMmmLrC995tR50=
|
go.augendre.info/arangolint v0.4.0 h1:xSCZjRoS93nXazBSg5d0OGCi9APPLNMmmLrC995tR50=
|
||||||
go.augendre.info/arangolint v0.4.0/go.mod h1:l+f/b4plABuFISuKnTGD4RioXiCCgghv2xqst/xOvAA=
|
go.augendre.info/arangolint v0.4.0/go.mod h1:l+f/b4plABuFISuKnTGD4RioXiCCgghv2xqst/xOvAA=
|
||||||
go.augendre.info/fatcontext v0.10.0 h1:HhFopmivh8U1+AU7f0kuwUeg2eiIns7YsGQOMHwSJ90=
|
go.augendre.info/fatcontext v0.9.0 h1:Gt5jGD4Zcj8CDMVzjOJITlSb9cEch54hjRRlN3qDojE=
|
||||||
go.augendre.info/fatcontext v0.10.0/go.mod h1:pqpGvA9GlrXy+aXkp8L2dKz12Zp4g2FhzcAtwToU+2w=
|
go.augendre.info/fatcontext v0.9.0/go.mod h1:L94brOAT1OOUNue6ph/2HnwxoNlds9aXDF2FcUntbNw=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
go.opentelemetry.io/contrib/detectors/gcp v1.43.0 h1:62yY3dT7/ShwOxzA0RsKRgshBmfElKI4d/Myu2OxDFU=
|
go.opentelemetry.io/contrib/detectors/gcp v1.43.0 h1:62yY3dT7/ShwOxzA0RsKRgshBmfElKI4d/Myu2OxDFU=
|
||||||
go.opentelemetry.io/contrib/detectors/gcp v1.43.0/go.mod h1:RyaZMFY7yi1kAs45S6mbFGz8O8rqB0dTY14uzvG4LCs=
|
go.opentelemetry.io/contrib/detectors/gcp v1.43.0/go.mod h1:RyaZMFY7yi1kAs45S6mbFGz8O8rqB0dTY14uzvG4LCs=
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg=
|
||||||
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
|
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
|
||||||
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
|
go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
|
||||||
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
|
go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
|
||||||
|
|
@ -744,43 +732,51 @@ go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
|
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
|
||||||
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
|
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
|
|
||||||
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
|
|
||||||
gocloud.dev v0.46.0 h1:niIuZwSjMtBx8K+ITB2s5kZullB13PGOS2ZoQPZxQ4Q=
|
gocloud.dev v0.46.0 h1:niIuZwSjMtBx8K+ITB2s5kZullB13PGOS2ZoQPZxQ4Q=
|
||||||
gocloud.dev v0.46.0/go.mod h1:ACQe+2qO+hEO+pdcvvsM+RB63r8TyGD1W3ESCLFyzvM=
|
gocloud.dev v0.46.0/go.mod h1:ACQe+2qO+hEO+pdcvvsM+RB63r8TyGD1W3ESCLFyzvM=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
|
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
|
||||||
|
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
|
||||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
|
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
|
||||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
|
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
|
||||||
golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||||
golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
|
||||||
golang.org/x/exp/typeparams v0.0.0-20260811152304-ee035b5b010f h1:+lI8cDJ4uceLipg2f1ODay7fEuLkk0BIHXd6PB8icxo=
|
golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358 h1:qWFG1Dj7TBjOjOvhEOkmyGPVoquqUKnIU0lEVLp8xyk=
|
||||||
golang.org/x/exp/typeparams v0.0.0-20260811152304-ee035b5b010f/go.mod h1:PqrXSW65cXDZH0k4IeUbhmg/bcAZDbzNz3byBpKCsXo=
|
golang.org/x/exp/typeparams v0.0.0-20260209203927-2842357ff358/go.mod h1:4Mzdyp/6jzw9auFDJ3OMF5qksa7UvPnzKqTVGcb04ms=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs=
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE=
|
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
||||||
|
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
|
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||||
|
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||||
|
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
|
||||||
|
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
|
||||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
|
@ -790,6 +786,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
|
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||||
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
|
@ -798,8 +796,10 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
|
@ -807,19 +807,27 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
|
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||||
|
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
|
||||||
|
golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
|
||||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
|
@ -828,11 +836,14 @@ golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWc
|
||||||
golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
|
||||||
golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU=
|
golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU=
|
||||||
golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU=
|
golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU=
|
||||||
|
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
|
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||||
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
|
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
|
||||||
|
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
|
||||||
|
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
|
||||||
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
|
golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM=
|
||||||
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
|
golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
|
||||||
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
|
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
|
||||||
|
|
@ -845,14 +856,14 @@ golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhS
|
||||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||||
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
|
||||||
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
|
||||||
google.golang.org/api v0.288.0 h1:glhO/J88obKP5I269W3hB73dvBKrjU56ZfmNlNXpgTU=
|
google.golang.org/api v0.272.0 h1:eLUQZGnAS3OHn31URRf9sAmRk3w2JjMx37d2k8AjJmA=
|
||||||
google.golang.org/api v0.288.0/go.mod h1:lM2kYRzYUCBY91P9h6VF1PYmvhxii3O5hji37qRvIcY=
|
google.golang.org/api v0.272.0/go.mod h1:wKjowi5LNJc5qarNvDCvNQBn3rVK8nSy6jg2SwRwzIA=
|
||||||
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=
|
google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5 h1:JNfk58HZ8lfmXbYK2vx/UvsqIL59TzByCxPIX4TDmsE=
|
||||||
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I=
|
google.golang.org/genproto v0.0.0-20260316180232-0b37fe3546d5/go.mod h1:x5julN69+ED4PcFk/XWayw35O0lf/nGa4aNgODCmNmw=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260630182238-925bb5da69e7 h1:jQ9p21COKWjP3VwuFrNRiiOTMh3mPpN45R7SLrH/HUU=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478 h1:yQugLulqltosq0B/f8l4w9VryjV+N/5gcW0jQ3N8Qec=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20260630182238-925bb5da69e7/go.mod h1:KqHwBx2upmfa1XSi1WuRvC+2VGCLtooKkfmyvRbUmqA=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 h1:qEHAMpSaUhtD0p3NbEEI83HwNGFxEwaSJ1G9PLnCBZE=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
|
||||||
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
|
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE=
|
||||||
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
|
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA=
|
||||||
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
|
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
|
||||||
|
|
@ -872,8 +883,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
honnef.co/go/tools v0.8.0 h1:UacpzPr7D6i5BAjTkA7sNVcx4kIbhAZcQ4zYtKiXx68=
|
honnef.co/go/tools v0.7.0 h1:w6WUp1VbkqPEgLz4rkBzH/CSU6HkoqNLp6GstyTx3lU=
|
||||||
honnef.co/go/tools v0.8.0/go.mod h1:XA+OnlRA9EDh/ukGvXMNSZNKGwFQJ+5dER0ioUkOxks=
|
honnef.co/go/tools v0.7.0/go.mod h1:pm29oPxeP3P82ISxZDgIYeOaf9ta6Pi0EWvCFoLG2vc=
|
||||||
modernc.org/cc/v4 v4.29.1 h1:MKgdCV3WykTSPqpVrnxdEDS0HEd2FHpKZDzxzU5LyeI=
|
modernc.org/cc/v4 v4.29.1 h1:MKgdCV3WykTSPqpVrnxdEDS0HEd2FHpKZDzxzU5LyeI=
|
||||||
modernc.org/cc/v4 v4.29.1/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
modernc.org/cc/v4 v4.29.1/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||||
modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU=
|
modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU=
|
||||||
|
|
@ -902,9 +913,9 @@ modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
mvdan.cc/gofumpt v0.11.0 h1:0H01XB95PnN2QgCSR9ELdZyTlJqNZ7181B0BTMh5VZc=
|
mvdan.cc/gofumpt v0.9.2 h1:zsEMWL8SVKGHNztrx6uZrXdp7AX8r421Vvp23sz7ik4=
|
||||||
mvdan.cc/gofumpt v0.11.0/go.mod h1:BeT5wCsOJt6J9zT2MZIOGszjUHzFkn1/l9g6xAzqsXo=
|
mvdan.cc/gofumpt v0.9.2/go.mod h1:iB7Hn+ai8lPvofHd9ZFGVg2GOr8sBUw1QUWjNbmIL/s=
|
||||||
mvdan.cc/unparam v0.0.0-20260818115549-3f964bcb5673 h1:dEE6li4OPIE54oojY2qaayFS1fSp17G14si0gXRxl0U=
|
mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15 h1:ssMzja7PDPJV8FStj7hq9IKiuiKhgz9ErWw+m68e7DI=
|
||||||
mvdan.cc/unparam v0.0.0-20260818115549-3f964bcb5673/go.mod h1:62roFV3D3nYOWIXv3PfGO4UYEKAotz2WgLywT87ONd8=
|
mvdan.cc/unparam v0.0.0-20251027182757-5beb8c8f8f15/go.mod h1:4M5MMXl2kW6fivUT6yRGpLLPNfuGtU2Z0cPvFquGDYU=
|
||||||
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
|
||||||
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,9 @@ import (
|
||||||
"gopkg.in/yaml.v3"
|
"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.
|
// Config holds all configuration for the proxy server.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
// Listen is the address to listen on (e.g., ":8080", "127.0.0.1:8080").
|
// Listen is the address to listen on (e.g., ":8080", "127.0.0.1:8080").
|
||||||
|
|
@ -313,6 +316,10 @@ type UpstreamConfig struct {
|
||||||
// Default: https://static.crates.io/crates
|
// Default: https://static.crates.io/crates
|
||||||
CargoDownload string `json:"cargo_download" yaml:"cargo_download"`
|
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.
|
// Debian is the upstream APT repository base URL.
|
||||||
// Example: http://archive.ubuntu.com/ubuntu would get Ubuntu.
|
// Example: http://archive.ubuntu.com/ubuntu would get Ubuntu.
|
||||||
// Default: http://deb.debian.org/debian
|
// Default: http://deb.debian.org/debian
|
||||||
|
|
@ -475,6 +482,7 @@ func Default() *Config {
|
||||||
GradlePluginPortal: "https://plugins.gradle.org/m2",
|
GradlePluginPortal: "https://plugins.gradle.org/m2",
|
||||||
Cargo: "https://index.crates.io",
|
Cargo: "https://index.crates.io",
|
||||||
CargoDownload: "https://static.crates.io/crates",
|
CargoDownload: "https://static.crates.io/crates",
|
||||||
|
Swift: DefaultSwiftUpstream,
|
||||||
Debian: "http://deb.debian.org/debian",
|
Debian: "http://deb.debian.org/debian",
|
||||||
},
|
},
|
||||||
Gradle: GradleConfig{
|
Gradle: GradleConfig{
|
||||||
|
|
@ -546,6 +554,7 @@ func setEnvBool(dst *bool, key string) {
|
||||||
// - PROXY_LOG_LEVEL
|
// - PROXY_LOG_LEVEL
|
||||||
// - PROXY_LOG_FORMAT
|
// - PROXY_LOG_FORMAT
|
||||||
// - PROXY_ACCESS_LOG_PATH
|
// - PROXY_ACCESS_LOG_PATH
|
||||||
|
// - PROXY_UPSTREAM_SWIFT
|
||||||
// - PROXY_HEALTH_STORAGE_PROBE_INTERVAL
|
// - PROXY_HEALTH_STORAGE_PROBE_INTERVAL
|
||||||
func (c *Config) LoadFromEnv() {
|
func (c *Config) LoadFromEnv() {
|
||||||
setEnvString(&c.Listen, "PROXY_LISTEN")
|
setEnvString(&c.Listen, "PROXY_LISTEN")
|
||||||
|
|
@ -565,6 +574,7 @@ func (c *Config) LoadFromEnv() {
|
||||||
setEnvString(&c.AccessLog.Path, "PROXY_ACCESS_LOG_PATH")
|
setEnvString(&c.AccessLog.Path, "PROXY_ACCESS_LOG_PATH")
|
||||||
setEnvString(&c.Upstream.Maven, "PROXY_UPSTREAM_MAVEN")
|
setEnvString(&c.Upstream.Maven, "PROXY_UPSTREAM_MAVEN")
|
||||||
setEnvString(&c.Upstream.GradlePluginPortal, "PROXY_UPSTREAM_GRADLE_PLUGIN_PORTAL")
|
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.Upstream.Debian, "PROXY_UPSTREAM_DEBIAN")
|
||||||
setEnvString(&c.Cooldown.Default, "PROXY_COOLDOWN_DEFAULT")
|
setEnvString(&c.Cooldown.Default, "PROXY_COOLDOWN_DEFAULT")
|
||||||
setEnvBool(&c.CacheMetadata, "PROXY_CACHE_METADATA")
|
setEnvBool(&c.CacheMetadata, "PROXY_CACHE_METADATA")
|
||||||
|
|
@ -928,7 +938,8 @@ func ParseSize(s string) (int64, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, s2 := range suffixes {
|
for _, s2 := range suffixes {
|
||||||
if numStr, ok := strings.CutSuffix(s, s2.suffix); ok {
|
if strings.HasSuffix(s, s2.suffix) {
|
||||||
|
numStr := strings.TrimSuffix(s, s2.suffix)
|
||||||
num, err := strconv.ParseFloat(numStr, 64)
|
num, err := strconv.ParseFloat(numStr, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, fmt.Errorf("invalid number %q", numStr)
|
return 0, fmt.Errorf("invalid number %q", numStr)
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,9 @@ func TestDefault(t *testing.T) {
|
||||||
if cfg.Upstream.GradlePluginPortal != "https://plugins.gradle.org/m2" {
|
if cfg.Upstream.GradlePluginPortal != "https://plugins.gradle.org/m2" {
|
||||||
t.Errorf("Upstream.GradlePluginPortal = %q, want %q", 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" {
|
if cfg.Upstream.Debian != "http://deb.debian.org/debian" {
|
||||||
t.Errorf("Upstream.Debian = %q, want %q", cfg.Upstream.Debian, "http://deb.debian.org/debian")
|
t.Errorf("Upstream.Debian = %q, want %q", cfg.Upstream.Debian, "http://deb.debian.org/debian")
|
||||||
}
|
}
|
||||||
|
|
@ -286,6 +289,7 @@ func TestLoadFromEnv(t *testing.T) {
|
||||||
t.Setenv("PROXY_ACCESS_LOG_PATH", "/tmp/proxy-access.jsonl")
|
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_MAVEN", "https://maven.example.com/repository/maven-public")
|
||||||
t.Setenv("PROXY_UPSTREAM_GRADLE_PLUGIN_PORTAL", "https://plugins.example.com/m2")
|
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_UPSTREAM_DEBIAN", "http://archive.ubuntu.com/ubuntu")
|
||||||
t.Setenv("PROXY_GRADLE_BUILD_CACHE_READ_ONLY", "true")
|
t.Setenv("PROXY_GRADLE_BUILD_CACHE_READ_ONLY", "true")
|
||||||
t.Setenv("PROXY_GRADLE_BUILD_CACHE_MAX_UPLOAD_SIZE", "32MB")
|
t.Setenv("PROXY_GRADLE_BUILD_CACHE_MAX_UPLOAD_SIZE", "32MB")
|
||||||
|
|
@ -319,6 +323,9 @@ func TestLoadFromEnv(t *testing.T) {
|
||||||
if cfg.Upstream.GradlePluginPortal != "https://plugins.example.com/m2" {
|
if cfg.Upstream.GradlePluginPortal != "https://plugins.example.com/m2" {
|
||||||
t.Errorf("Upstream.GradlePluginPortal = %q, want %q", 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" {
|
if cfg.Upstream.Debian != "http://archive.ubuntu.com/ubuntu" {
|
||||||
t.Errorf("Upstream.Debian = %q, want %q", cfg.Upstream.Debian, "http://archive.ubuntu.com/ubuntu")
|
t.Errorf("Upstream.Debian = %q, want %q", cfg.Upstream.Debian, "http://archive.ubuntu.com/ubuntu")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/git-pkgs/proxy/internal/packageurl"
|
||||||
"github.com/git-pkgs/purl"
|
"github.com/git-pkgs/purl"
|
||||||
"github.com/git-pkgs/registries"
|
"github.com/git-pkgs/registries"
|
||||||
_ "github.com/git-pkgs/registries/all" // Import all registry implementations
|
_ "github.com/git-pkgs/registries/all" // Import all registry implementations
|
||||||
|
|
@ -67,7 +68,10 @@ type VulnInfo struct {
|
||||||
|
|
||||||
// EnrichPackage fetches metadata for a package from registry APIs.
|
// EnrichPackage fetches metadata for a package from registry APIs.
|
||||||
func (s *Service) EnrichPackage(ctx context.Context, ecosystem, name string) (*PackageInfo, error) {
|
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)
|
pkg, err := registries.FetchPackageFromPURL(ctx, purlStr, s.regClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -102,7 +106,10 @@ func (s *Service) EnrichPackage(ctx context.Context, ecosystem, name string) (*P
|
||||||
|
|
||||||
// EnrichVersion fetches metadata for a specific package version.
|
// EnrichVersion fetches metadata for a specific package version.
|
||||||
func (s *Service) EnrichVersion(ctx context.Context, ecosystem, name, version string) (*VersionInfo, error) {
|
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)
|
ver, err := registries.FetchVersionFromPURL(ctx, purlStr, s.regClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -134,9 +141,14 @@ func (s *Service) EnrichVersion(ctx context.Context, ecosystem, name, version st
|
||||||
|
|
||||||
// BulkEnrichPackages fetches metadata for multiple packages in parallel.
|
// BulkEnrichPackages fetches metadata for multiple packages in parallel.
|
||||||
func (s *Service) BulkEnrichPackages(ctx context.Context, packages []struct{ Ecosystem, Name string }) map[string]*PackageInfo {
|
func (s *Service) BulkEnrichPackages(ctx context.Context, packages []struct{ Ecosystem, Name string }) map[string]*PackageInfo {
|
||||||
purls := make([]string, len(packages))
|
purls := make([]string, 0, len(packages))
|
||||||
for i, pkg := range packages {
|
for _, pkg := range packages {
|
||||||
purls[i] = purl.MakePURLString(pkg.Ecosystem, pkg.Name, "")
|
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)
|
pkgData := registries.BulkFetchPackages(ctx, purls, s.regClient)
|
||||||
|
|
@ -147,7 +159,10 @@ func (s *Service) BulkEnrichPackages(ctx context.Context, packages []struct{ Eco
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
p, _ := purl.Parse(purlStr)
|
p, err := purl.Parse(purlStr)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
info := &PackageInfo{
|
info := &PackageInfo{
|
||||||
Ecosystem: p.Type,
|
Ecosystem: p.Type,
|
||||||
Name: pkg.Name,
|
Name: pkg.Name,
|
||||||
|
|
@ -174,7 +189,10 @@ func (s *Service) BulkEnrichPackages(ctx context.Context, packages []struct{ Eco
|
||||||
|
|
||||||
// CheckVulnerabilities queries for vulnerabilities affecting a package version.
|
// CheckVulnerabilities queries for vulnerabilities affecting a package version.
|
||||||
func (s *Service) CheckVulnerabilities(ctx context.Context, ecosystem, name, version string) ([]VulnInfo, error) {
|
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)
|
vulnList, err := s.vulnSource.Query(ctx, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -201,6 +219,50 @@ func (s *Service) CheckVulnerabilities(ctx context.Context, ecosystem, name, ver
|
||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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, 0, len(packages))
|
||||||
|
supported := make([]int, 0, len(packages))
|
||||||
|
for i, pkg := range packages {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, vulnList := range vulnResults {
|
||||||
|
pkg := packages[supported[i]]
|
||||||
|
key := purls[i].String()
|
||||||
|
|
||||||
|
var infos []VulnInfo
|
||||||
|
for _, v := range vulnList {
|
||||||
|
info := VulnInfo{
|
||||||
|
ID: v.ID,
|
||||||
|
Summary: v.Summary,
|
||||||
|
Severity: v.SeverityLevel(),
|
||||||
|
CVSSScore: v.CVSSScore(),
|
||||||
|
FixedVersion: v.FixedVersion(pkg.Ecosystem, pkg.Name),
|
||||||
|
}
|
||||||
|
for _, ref := range v.References {
|
||||||
|
info.References = append(info.References, ref.URL)
|
||||||
|
}
|
||||||
|
infos = append(infos, info)
|
||||||
|
}
|
||||||
|
result[key] = infos
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
// IsOutdated checks if a version is older than the latest version.
|
// IsOutdated checks if a version is older than the latest version.
|
||||||
func (s *Service) IsOutdated(currentVersion, latestVersion string) bool {
|
func (s *Service) IsOutdated(currentVersion, latestVersion string) bool {
|
||||||
if latestVersion == "" || currentVersion == "" {
|
if latestVersion == "" || currentVersion == "" {
|
||||||
|
|
@ -211,7 +273,10 @@ func (s *Service) IsOutdated(currentVersion, latestVersion string) bool {
|
||||||
|
|
||||||
// GetLatestVersion fetches the latest version for a package.
|
// GetLatestVersion fetches the latest version for a package.
|
||||||
func (s *Service) GetLatestVersion(ctx context.Context, ecosystem, name string) (string, error) {
|
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)
|
latest, err := registries.FetchLatestVersionFromPURL(ctx, purlStr, s.regClient)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -251,6 +316,19 @@ func (s *Service) CategorizeLicense(license string) LicenseCategory {
|
||||||
return LicenseUnknown
|
return LicenseUnknown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NormalizeLicense normalizes a license string to SPDX format.
|
||||||
|
func (s *Service) NormalizeLicense(license string) string {
|
||||||
|
if license == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if normalized, err := spdx.NormalizeExpressionLax(license); err == nil {
|
||||||
|
return normalized
|
||||||
|
}
|
||||||
|
|
||||||
|
return license
|
||||||
|
}
|
||||||
|
|
||||||
// EnrichmentResult contains all enrichment data for a package version.
|
// EnrichmentResult contains all enrichment data for a package version.
|
||||||
type EnrichmentResult struct {
|
type EnrichmentResult struct {
|
||||||
Package *PackageInfo
|
Package *PackageInfo
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,40 @@
|
||||||
package enrichment
|
package enrichment
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/git-pkgs/purl"
|
||||||
|
"github.com/git-pkgs/vulns"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type recordingVulnerabilitySource struct {
|
||||||
|
purls []*purl.PURL
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *recordingVulnerabilitySource) Name() string {
|
||||||
|
return "recording"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *recordingVulnerabilitySource) Query(context.Context, *purl.PURL) ([]vulns.Vulnerability, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *recordingVulnerabilitySource) QueryBatch(_ context.Context, purls []*purl.PURL) ([][]vulns.Vulnerability, error) {
|
||||||
|
s.purls = purls
|
||||||
|
results := make([][]vulns.Vulnerability, len(purls))
|
||||||
|
for i := range results {
|
||||||
|
results[i] = []vulns.Vulnerability{{ID: "TEST-1"}}
|
||||||
|
}
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *recordingVulnerabilitySource) Get(context.Context, string) (*vulns.Vulnerability, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestNew(t *testing.T) {
|
func TestNew(t *testing.T) {
|
||||||
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
||||||
svc := New(logger)
|
svc := New(logger)
|
||||||
|
|
@ -23,6 +52,68 @@ func TestNew(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSwiftRegistryIdentitySkipsPURLDependentLookups(t *testing.T) {
|
||||||
|
svc := New(slog.New(slog.NewTextHandler(os.Stdout, nil)))
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
packageInfo, err := svc.EnrichPackage(ctx, "swift", "apple/example")
|
||||||
|
if err != nil || packageInfo != nil {
|
||||||
|
t.Errorf("EnrichPackage() = %#v, %v; want nil, nil", packageInfo, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
versionInfo, err := svc.EnrichVersion(ctx, "swift", "apple/example", "1.2.3")
|
||||||
|
if err != nil || versionInfo != nil {
|
||||||
|
t.Errorf("EnrichVersion() = %#v, %v; want nil, nil", versionInfo, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
vulnerabilities, err := svc.CheckVulnerabilities(ctx, "swift", "apple/example", "1.2.3")
|
||||||
|
if err != nil || vulnerabilities != nil {
|
||||||
|
t.Errorf("CheckVulnerabilities() = %#v, %v; want nil, nil", vulnerabilities, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
latest, err := svc.GetLatestVersion(ctx, "swift", "apple/example")
|
||||||
|
if err != nil || latest != "" {
|
||||||
|
t.Errorf("GetLatestVersion() = %q, %v; want empty string, nil", latest, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
packages := []struct{ Ecosystem, Name string }{{Ecosystem: "swift", Name: "apple/example"}}
|
||||||
|
if got := svc.BulkEnrichPackages(ctx, packages); len(got) != 0 {
|
||||||
|
t.Errorf("BulkEnrichPackages() = %#v, want empty result", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
versions := []struct{ Ecosystem, Name, Version string }{
|
||||||
|
{Ecosystem: "swift", Name: "apple/example", Version: "1.2.3"},
|
||||||
|
}
|
||||||
|
got, err := svc.BulkCheckVulnerabilities(ctx, versions)
|
||||||
|
if err != nil || len(got) != 0 {
|
||||||
|
t.Errorf("BulkCheckVulnerabilities() = %#v, %v; want empty result, nil", got, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBulkCheckVulnerabilitiesFiltersUnsupportedPackageIdentities(t *testing.T) {
|
||||||
|
source := &recordingVulnerabilitySource{}
|
||||||
|
svc := New(slog.New(slog.NewTextHandler(os.Stdout, nil)))
|
||||||
|
svc.vulnSource = source
|
||||||
|
packages := []struct{ Ecosystem, Name, Version string }{
|
||||||
|
{Ecosystem: "swift", Name: "apple/example", Version: "1.2.3"},
|
||||||
|
{Ecosystem: "npm", Name: "lodash", Version: "4.17.21"},
|
||||||
|
}
|
||||||
|
|
||||||
|
got, err := svc.BulkCheckVulnerabilities(context.Background(), packages)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("BulkCheckVulnerabilities() error = %v", err)
|
||||||
|
}
|
||||||
|
if len(source.purls) != 1 || source.purls[0].String() != "pkg:npm/lodash@4.17.21" {
|
||||||
|
t.Fatalf("queried PURLs = %#v, want only lodash", source.purls)
|
||||||
|
}
|
||||||
|
if len(got["pkg:npm/lodash@4.17.21"]) != 1 {
|
||||||
|
t.Errorf("result = %#v, want lodash vulnerability", got)
|
||||||
|
}
|
||||||
|
if _, exists := got[""]; exists {
|
||||||
|
t.Error("result contains an empty PURL key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestIsOutdated(t *testing.T) {
|
func TestIsOutdated(t *testing.T) {
|
||||||
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
||||||
svc := New(logger)
|
svc := New(logger)
|
||||||
|
|
@ -74,3 +165,25 @@ func TestCategorizeLicense(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNormalizeLicense(t *testing.T) {
|
||||||
|
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
||||||
|
svc := New(logger)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"MIT", "MIT"},
|
||||||
|
{"Apache 2", "Apache-2.0"},
|
||||||
|
{"Apache-2.0", "Apache-2.0"},
|
||||||
|
{"", ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
result := svc.NormalizeLicense(tc.input)
|
||||||
|
if result != tc.expected {
|
||||||
|
t.Errorf("NormalizeLicense(%q) = %q, want %q", tc.input, result, tc.expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"github.com/git-pkgs/cooldown"
|
"github.com/git-pkgs/cooldown"
|
||||||
"github.com/git-pkgs/proxy/internal/database"
|
"github.com/git-pkgs/proxy/internal/database"
|
||||||
"github.com/git-pkgs/proxy/internal/metrics"
|
"github.com/git-pkgs/proxy/internal/metrics"
|
||||||
|
"github.com/git-pkgs/proxy/internal/packageurl"
|
||||||
"github.com/git-pkgs/proxy/internal/storage"
|
"github.com/git-pkgs/proxy/internal/storage"
|
||||||
"github.com/git-pkgs/purl"
|
"github.com/git-pkgs/purl"
|
||||||
"github.com/git-pkgs/registries/fetch"
|
"github.com/git-pkgs/registries/fetch"
|
||||||
|
|
@ -61,7 +62,18 @@ var artifactCopyBufferPool = sync.Pool{ //nolint:gochecknoglobals // shared acro
|
||||||
// canonicalPackagePURL returns a versionless PURL in canonical form so cooldown
|
// canonicalPackagePURL returns a versionless PURL in canonical form so cooldown
|
||||||
// lookups match keys produced by config.CooldownConfig.NormalizedPackages.
|
// lookups match keys produced by config.CooldownConfig.NormalizedPackages.
|
||||||
func canonicalPackagePURL(ecosystem, name string) string {
|
func canonicalPackagePURL(ecosystem, name string) string {
|
||||||
return purl.MakePURLString(ecosystem, name, "")
|
return packageurl.MakeString(ecosystem, name, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
var errUnsupportedPackageIdentity = errors.New("package identity cannot be represented as a PURL")
|
||||||
|
|
||||||
|
func packagePURLStrings(ecosystem, name, version string) (string, string, error) {
|
||||||
|
packagePURL := packageurl.MakeString(ecosystem, name, "")
|
||||||
|
versionPURL := packageurl.MakeString(ecosystem, name, version)
|
||||||
|
if packagePURL == "" || versionPURL == "" {
|
||||||
|
return "", "", fmt.Errorf("%w: %s %q", errUnsupportedPackageIdentity, ecosystem, name)
|
||||||
|
}
|
||||||
|
return packagePURL, versionPURL, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentTypeJSON = "application/json"
|
const contentTypeJSON = "application/json"
|
||||||
|
|
@ -140,27 +152,32 @@ type CacheResult struct {
|
||||||
ContentType string
|
ContentType string
|
||||||
Hash string
|
Hash string
|
||||||
Cached bool
|
Cached bool
|
||||||
|
storagePath string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOrFetchArtifact retrieves an artifact from cache or fetches from upstream.
|
// GetOrFetchArtifact retrieves an artifact from cache or fetches from upstream.
|
||||||
func (p *Proxy) GetOrFetchArtifact(ctx context.Context, ecosystem, name, version, filename string) (*CacheResult, error) {
|
func (p *Proxy) GetOrFetchArtifact(ctx context.Context, ecosystem, name, version, filename string) (*CacheResult, error) {
|
||||||
if cached, err := p.GetCachedArtifact(ctx, ecosystem, name, version, filename); err != nil {
|
pkgPURL, versionPURL, err := packagePURLStrings(ecosystem, name, version)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if cached, err := p.checkCache(ctx, pkgPURL, versionPURL, filename); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if cached != nil {
|
} else if cached != nil {
|
||||||
return cached, nil
|
return cached, nil
|
||||||
}
|
}
|
||||||
metrics.RecordCacheMiss(ecosystem)
|
metrics.RecordCacheMiss(ecosystem)
|
||||||
|
|
||||||
pkgPURL := purl.MakePURLString(ecosystem, name, "")
|
|
||||||
versionPURL := purl.MakePURLString(ecosystem, name, version)
|
|
||||||
return p.fetchAndCache(ctx, ecosystem, name, version, filename, pkgPURL, versionPURL)
|
return p.fetchAndCache(ctx, ecosystem, name, version, filename, pkgPURL, versionPURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCachedArtifact retrieves an artifact from cache without contacting an upstream.
|
// GetCachedArtifact retrieves an artifact from cache without contacting an upstream.
|
||||||
// It returns nil when no usable cache entry exists.
|
// It returns nil when no usable cache entry exists.
|
||||||
func (p *Proxy) GetCachedArtifact(ctx context.Context, ecosystem, name, version, filename string) (*CacheResult, error) {
|
func (p *Proxy) GetCachedArtifact(ctx context.Context, ecosystem, name, version, filename string) (*CacheResult, error) {
|
||||||
pkgPURL := purl.MakePURLString(ecosystem, name, "")
|
pkgPURL, versionPURL, err := packagePURLStrings(ecosystem, name, version)
|
||||||
versionPURL := purl.MakePURLString(ecosystem, name, version)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return p.checkCache(ctx, pkgPURL, versionPURL, filename)
|
return p.checkCache(ctx, pkgPURL, versionPURL, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -170,8 +187,10 @@ func (p *Proxy) ClearCachedArtifact(ctx context.Context, ecosystem, name, versio
|
||||||
if p.DB == nil || p.Storage == nil {
|
if p.DB == nil || p.Storage == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
pkgPURL := purl.MakePURLString(ecosystem, name, "")
|
pkgPURL, versionPURL, err := packagePURLStrings(ecosystem, name, version)
|
||||||
versionPURL := purl.MakePURLString(ecosystem, name, version)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
cached, err := p.DB.GetCachedArtifact(pkgPURL, versionPURL, filename)
|
cached, err := p.DB.GetCachedArtifact(pkgPURL, versionPURL, filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("looking up cached artifact: %w", err)
|
return fmt.Errorf("looking up cached artifact: %w", err)
|
||||||
|
|
@ -205,6 +224,7 @@ func (p *Proxy) checkCache(ctx context.Context, pkgPURL, versionPURL, filename s
|
||||||
ContentType: artifact.ContentType.String,
|
ContentType: artifact.ContentType.String,
|
||||||
Hash: artifact.ContentHash.String,
|
Hash: artifact.ContentHash.String,
|
||||||
Cached: true,
|
Cached: true,
|
||||||
|
storagePath: artifact.StoragePath,
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.DirectServe {
|
if p.DirectServe {
|
||||||
|
|
@ -864,19 +884,55 @@ func (p *Proxy) GetOrFetchArtifactFromURL(ctx context.Context, ecosystem, name,
|
||||||
// GetOrFetchArtifactFromURLWithHeaders retrieves an artifact from cache or fetches from a URL
|
// GetOrFetchArtifactFromURLWithHeaders retrieves an artifact from cache or fetches from a URL
|
||||||
// with additional request-specific HTTP headers.
|
// with additional request-specific HTTP headers.
|
||||||
func (p *Proxy) GetOrFetchArtifactFromURLWithHeaders(ctx context.Context, ecosystem, name, version, filename, downloadURL string, headers http.Header) (*CacheResult, error) {
|
func (p *Proxy) GetOrFetchArtifactFromURLWithHeaders(ctx context.Context, ecosystem, name, version, filename, downloadURL string, headers http.Header) (*CacheResult, error) {
|
||||||
if cached, err := p.GetCachedArtifact(ctx, ecosystem, name, version, filename); err != nil {
|
return p.getOrFetchArtifactFromURL(ctx, ecosystem, name, version, filename, downloadURL, headers, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) getOrFetchArtifactFromURL(ctx context.Context, ecosystem, name, version, filename, downloadURL string, headers http.Header, upstreamHash string) (*CacheResult, error) {
|
||||||
|
pkgPURL, versionPURL, err := packagePURLStrings(ecosystem, name, version)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return p.getOrFetchArtifactFromURLWithCachePURLs(
|
||||||
|
ctx, ecosystem, name, version, filename, pkgPURL, versionPURL, downloadURL, headers, upstreamHash,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) getOrFetchArtifactFromURLWithCachePURLs(ctx context.Context, ecosystem, name, version, filename, pkgPURL, versionPURL, downloadURL string, headers http.Header, upstreamHash string) (*CacheResult, error) {
|
||||||
|
if cached, err := p.getCachedArtifactWithUpstreamHash(ctx, pkgPURL, versionPURL, filename, upstreamHash); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if cached != nil {
|
} else if cached != nil {
|
||||||
return cached, nil
|
return cached, nil
|
||||||
}
|
}
|
||||||
metrics.RecordCacheMiss(ecosystem)
|
metrics.RecordCacheMiss(ecosystem)
|
||||||
|
|
||||||
pkgPURL := purl.MakePURLString(ecosystem, name, "")
|
return p.fetchAndCacheFromURL(ctx, ecosystem, name, version, filename, pkgPURL, versionPURL, downloadURL, headers, upstreamHash)
|
||||||
versionPURL := purl.MakePURLString(ecosystem, name, version)
|
|
||||||
return p.fetchAndCacheFromURL(ctx, ecosystem, name, version, filename, pkgPURL, versionPURL, downloadURL, headers)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Proxy) fetchAndCacheFromURL(ctx context.Context, ecosystem, name, version, filename, pkgPURL, versionPURL, downloadURL string, headers http.Header) (*CacheResult, error) {
|
// getCachedArtifactWithUpstreamHash returns a cached artifact whose recorded
|
||||||
|
// content hash matches the checksum the upstream currently declares for it.
|
||||||
|
// This detects an upstream re-publishing under the same version, which the
|
||||||
|
// stream integrity check in checkCache cannot: that check only verifies the
|
||||||
|
// stored blob against the hash recorded when it was cached. On mismatch the
|
||||||
|
// stale entry is discarded and nil is returned so the caller re-fetches.
|
||||||
|
func (p *Proxy) getCachedArtifactWithUpstreamHash(ctx context.Context, pkgPURL, versionPURL, filename, upstreamHash string) (*CacheResult, error) {
|
||||||
|
cached, err := p.checkCache(ctx, pkgPURL, versionPURL, filename)
|
||||||
|
if err != nil || cached == nil {
|
||||||
|
return cached, err
|
||||||
|
}
|
||||||
|
if artifactHashMatches(cached.Hash, upstreamHash) {
|
||||||
|
return cached, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if cached.Reader != nil {
|
||||||
|
_ = cached.Reader.Close()
|
||||||
|
}
|
||||||
|
p.Logger.Warn("cached artifact hash disagrees with upstream metadata, discarding",
|
||||||
|
"purl", versionPURL, "filename", filename, "cached", cached.Hash, "upstream", upstreamHash)
|
||||||
|
p.discardCachedArtifact(ctx, versionPURL, filename, cached.storagePath)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) fetchAndCacheFromURL(ctx context.Context, ecosystem, name, version, filename, pkgPURL, versionPURL, downloadURL string, headers http.Header, upstreamHash string) (*CacheResult, error) {
|
||||||
p.Logger.Info("fetching from upstream",
|
p.Logger.Info("fetching from upstream",
|
||||||
"ecosystem", ecosystem, "name", name, "version", version, "url", downloadURL)
|
"ecosystem", ecosystem, "name", name, "version", version, "url", downloadURL)
|
||||||
|
|
||||||
|
|
@ -894,6 +950,12 @@ func (p *Proxy) fetchAndCacheFromURL(ctx context.Context, ecosystem, name, versi
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("storing artifact: %w", err)
|
return nil, fmt.Errorf("storing artifact: %w", err)
|
||||||
}
|
}
|
||||||
|
if !artifactHashMatches(hash, upstreamHash) {
|
||||||
|
if err := p.Storage.Delete(ctx, storagePath); err != nil {
|
||||||
|
p.Logger.Warn("failed to discard artifact with mismatched checksum", "path", storagePath, "error", err)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("artifact checksum mismatch: upstream declared %s, got %s", upstreamHash, hash)
|
||||||
|
}
|
||||||
|
|
||||||
if err := p.updateCacheDB(ecosystem, name, filename, pkgPURL, versionPURL, downloadURL, storagePath, hash, size, artifact.ContentType); err != nil {
|
if err := p.updateCacheDB(ecosystem, name, filename, pkgPURL, versionPURL, downloadURL, storagePath, hash, size, artifact.ContentType); err != nil {
|
||||||
p.Logger.Warn("failed to update cache database", "error", err)
|
p.Logger.Warn("failed to update cache database", "error", err)
|
||||||
|
|
@ -912,3 +974,18 @@ func (p *Proxy) fetchAndCacheFromURL(ctx context.Context, ecosystem, name, versi
|
||||||
Cached: false,
|
Cached: false,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func artifactHashMatches(got, expected string) bool {
|
||||||
|
return expected == "" || strings.EqualFold(got, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Proxy) discardCachedArtifact(ctx context.Context, versionPURL, filename, storagePath string) {
|
||||||
|
if storagePath != "" {
|
||||||
|
if err := p.Storage.Delete(ctx, storagePath); err != nil {
|
||||||
|
p.Logger.Warn("failed to discard cached artifact", "path", storagePath, "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := p.DB.ClearArtifactCache(versionPURL, filename); err != nil {
|
||||||
|
p.Logger.Warn("failed to clear artifact cache record", "purl", versionPURL, "filename", filename, "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,15 +105,17 @@ type mockFetcher struct {
|
||||||
fetchErrByURL map[string]error
|
fetchErrByURL map[string]error
|
||||||
fetchCalled bool
|
fetchCalled bool
|
||||||
fetchedURL string
|
fetchedURL string
|
||||||
|
fetchedHeader http.Header
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *mockFetcher) Fetch(ctx context.Context, url string) (*fetch.Artifact, error) {
|
func (f *mockFetcher) Fetch(ctx context.Context, url string) (*fetch.Artifact, error) {
|
||||||
return f.FetchWithHeaders(ctx, url, nil)
|
return f.FetchWithHeaders(ctx, url, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *mockFetcher) FetchWithHeaders(_ context.Context, url string, _ http.Header) (*fetch.Artifact, error) {
|
func (f *mockFetcher) FetchWithHeaders(_ context.Context, url string, headers http.Header) (*fetch.Artifact, error) {
|
||||||
f.fetchCalled = true
|
f.fetchCalled = true
|
||||||
f.fetchedURL = url
|
f.fetchedURL = url
|
||||||
|
f.fetchedHeader = headers.Clone()
|
||||||
if f.fetchErrByURL != nil {
|
if f.fetchErrByURL != nil {
|
||||||
if err, ok := f.fetchErrByURL[url]; ok {
|
if err, ok := f.fetchErrByURL[url]; ok {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -403,6 +405,28 @@ func TestGetOrFetchArtifactFromURL_CacheMiss_StorageMissing(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestArtifactCacheRejectsUnsupportedPackageIdentity(t *testing.T) {
|
||||||
|
proxy, _, _, fetcher := setupTestProxy(t)
|
||||||
|
|
||||||
|
_, err := proxy.GetCachedArtifact(
|
||||||
|
context.Background(), "swift", "apple/example", "1.2.3", "example-1.2.3.zip",
|
||||||
|
)
|
||||||
|
if !errors.Is(err, errUnsupportedPackageIdentity) {
|
||||||
|
t.Fatalf("GetCachedArtifact() error = %v, want unsupported package identity", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = proxy.GetOrFetchArtifactFromURL(
|
||||||
|
context.Background(), "swift", "apple/example", "1.2.3", "example-1.2.3.zip",
|
||||||
|
"https://registry.example/apple/example/1.2.3.zip",
|
||||||
|
)
|
||||||
|
if !errors.Is(err, errUnsupportedPackageIdentity) {
|
||||||
|
t.Fatalf("GetOrFetchArtifactFromURL() error = %v, want unsupported package identity", err)
|
||||||
|
}
|
||||||
|
if fetcher.fetchCalled {
|
||||||
|
t.Error("unsupported package identity reached the artifact fetcher")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetOrFetchArtifact_DirectServe_Redirect(t *testing.T) {
|
func TestGetOrFetchArtifact_DirectServe_Redirect(t *testing.T) {
|
||||||
proxy, db, store, fetcher := setupTestProxy(t)
|
proxy, db, store, fetcher := setupTestProxy(t)
|
||||||
seedPackage(t, db, store, "npm", "lodash", "4.17.21", "lodash-4.17.21.tgz", "cached content")
|
seedPackage(t, db, store, "npm", "lodash", "4.17.21", "lodash-4.17.21.tgz", "cached content")
|
||||||
|
|
|
||||||
645
internal/handler/swift.go
Normal file
645
internal/handler/swift.go
Normal file
|
|
@ -0,0 +1,645 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/git-pkgs/proxy/internal/config"
|
||||||
|
"github.com/git-pkgs/proxy/internal/packageurl"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
swiftAcceptJSON = "application/vnd.swift.registry.v1+json"
|
||||||
|
swiftAcceptManifest = "application/vnd.swift.registry.v1+swift"
|
||||||
|
swiftAcceptArchive = "application/vnd.swift.registry.v1+zip"
|
||||||
|
swiftContentVersion = "1"
|
||||||
|
swiftMaxScopeLength = 39
|
||||||
|
swiftMaxNameLength = 100
|
||||||
|
)
|
||||||
|
|
||||||
|
// SwiftHandler handles the read-only Swift Package Registry v1 protocol.
|
||||||
|
type SwiftHandler struct {
|
||||||
|
proxy *Proxy
|
||||||
|
upstreamURL string
|
||||||
|
proxyURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSwiftHandler creates a Swift Package Registry protocol handler.
|
||||||
|
func NewSwiftHandler(proxy *Proxy, proxyURL, upstreamURL string) *SwiftHandler {
|
||||||
|
if strings.TrimSpace(upstreamURL) == "" {
|
||||||
|
upstreamURL = config.DefaultSwiftUpstream
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SwiftHandler{
|
||||||
|
proxy: proxy,
|
||||||
|
upstreamURL: strings.TrimSuffix(upstreamURL, "/"),
|
||||||
|
proxyURL: strings.TrimSuffix(proxyURL, "/"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Routes returns the HTTP handler for Swift registry requests.
|
||||||
|
func (h *SwiftHandler) Routes() http.Handler {
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("GET /identifiers", h.handleIdentifiers)
|
||||||
|
mux.HandleFunc("GET /{scope}/{name}/{version}/Package.swift", h.handleManifest)
|
||||||
|
mux.HandleFunc("GET /{scope}/{name}/{version}", h.handleRelease)
|
||||||
|
mux.HandleFunc("PUT /{scope}/{name}/{version}", h.handlePublishingUnsupported)
|
||||||
|
mux.HandleFunc("GET /{scope}/{name}", h.handlePackageReleases)
|
||||||
|
return mux
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SwiftHandler) handlePackageReleases(w http.ResponseWriter, r *http.Request) {
|
||||||
|
scope := r.PathValue("scope")
|
||||||
|
name := strings.TrimSuffix(r.PathValue("name"), ".json")
|
||||||
|
if !validSwiftScope(scope) || !validSwiftPackageName(name) {
|
||||||
|
writeSwiftProblem(w, http.StatusBadRequest, "invalid package identifier")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scope, name = canonicalSwiftPackage(scope, name)
|
||||||
|
|
||||||
|
upstreamURL := h.buildUpstreamURL(scope, name, "", "", r.URL.RawQuery)
|
||||||
|
body, contentType, responseHeaders, err := h.fetchMetadataWithHeaders(
|
||||||
|
r.Context(), upstreamURL, requestAccept(r, swiftAcceptJSON),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
h.writeMetadataError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rewritten, err := h.rewriteReleaseURLs(scope, name, body)
|
||||||
|
if err != nil {
|
||||||
|
h.proxy.Logger.Warn("failed to rewrite Swift release URLs", "error", err)
|
||||||
|
rewritten = body
|
||||||
|
}
|
||||||
|
for _, link := range responseHeaders.Values("Link") {
|
||||||
|
w.Header().Add("Link", h.rewriteLinkHeader(link, upstreamURL))
|
||||||
|
}
|
||||||
|
writeSwiftMetadata(w, r, rewritten, contentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SwiftHandler) handleRelease(w http.ResponseWriter, r *http.Request) {
|
||||||
|
scope := r.PathValue("scope")
|
||||||
|
name := r.PathValue("name")
|
||||||
|
version := r.PathValue("version")
|
||||||
|
if strings.HasSuffix(version, ".zip") {
|
||||||
|
h.handleSourceArchive(w, r, scope, name, strings.TrimSuffix(version, ".zip"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
version = strings.TrimSuffix(version, ".json")
|
||||||
|
if !validSwiftPackageReference(scope, name, version) {
|
||||||
|
writeSwiftProblem(w, http.StatusBadRequest, "invalid package release")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scope, name = canonicalSwiftPackage(scope, name)
|
||||||
|
|
||||||
|
upstreamURL := h.buildUpstreamURL(scope, name, version, "", r.URL.RawQuery)
|
||||||
|
body, contentType, err := h.proxy.FetchOrCacheMetadata(
|
||||||
|
r.Context(), "swift", swiftReleaseCacheKey(scope, name, version), upstreamURL, requestAccept(r, swiftAcceptJSON),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
h.writeMetadataError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeSwiftMetadata(w, r, body, contentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SwiftHandler) handleManifest(w http.ResponseWriter, r *http.Request) {
|
||||||
|
scope := r.PathValue("scope")
|
||||||
|
name := r.PathValue("name")
|
||||||
|
version := r.PathValue("version")
|
||||||
|
if !validSwiftPackageReference(scope, name, version) {
|
||||||
|
writeSwiftProblem(w, http.StatusBadRequest, "invalid package release")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scope, name = canonicalSwiftPackage(scope, name)
|
||||||
|
|
||||||
|
upstreamURL := h.buildUpstreamURL(scope, name, version, "Package.swift", r.URL.RawQuery)
|
||||||
|
h.proxySwiftResource(w, r, upstreamURL, swiftAcceptManifest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SwiftHandler) handleIdentifiers(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Query().Get("url") == "" {
|
||||||
|
writeSwiftProblem(w, http.StatusBadRequest, "url query parameter is required")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
upstreamURL := h.upstreamURL + "/identifiers?" + r.URL.RawQuery
|
||||||
|
cacheKey := swiftMetadataCacheKey("identifiers", r.URL.RawQuery)
|
||||||
|
body, contentType, err := h.proxy.FetchOrCacheMetadata(
|
||||||
|
r.Context(), "swift", cacheKey, upstreamURL, requestAccept(r, swiftAcceptJSON),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
h.writeMetadataError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeSwiftMetadata(w, r, body, contentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SwiftHandler) handlePublishingUnsupported(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
w.Header().Set("Allow", "GET, HEAD")
|
||||||
|
writeSwiftProblem(w, http.StatusMethodNotAllowed, "publishing isn't supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SwiftHandler) handleSourceArchive(w http.ResponseWriter, r *http.Request, scope, name, version string) {
|
||||||
|
if !validSwiftPackageReference(scope, name, version) {
|
||||||
|
writeSwiftProblem(w, http.StatusBadRequest, "invalid package release")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
scope, name = canonicalSwiftPackage(scope, name)
|
||||||
|
|
||||||
|
packageName := scope + "/" + name
|
||||||
|
filename := fmt.Sprintf("%s-%s.zip", name, version)
|
||||||
|
upstreamURL := h.buildUpstreamURL(scope, name, version+".zip", "", r.URL.RawQuery)
|
||||||
|
packagePURL, versionPURL := packageurl.MakeCacheStrings("swift", packageName, version)
|
||||||
|
if packagePURL == "" || versionPURL == "" {
|
||||||
|
h.writeArtifactError(w, fmt.Errorf("%w: swift %q", errUnsupportedPackageIdentity, packageName))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
archiveInfo, infoErr := h.fetchArchiveInfo(r.Context(), scope, name, version)
|
||||||
|
if infoErr != nil {
|
||||||
|
h.writeArtifactError(w, fmt.Errorf("fetching release metadata: %w", infoErr))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Method == http.MethodHead {
|
||||||
|
h.handleSourceArchiveHead(w, r, name, version, filename, packagePURL, versionPURL, upstreamURL, archiveInfo)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := make(http.Header)
|
||||||
|
headers.Set("Accept", requestAccept(r, swiftAcceptArchive))
|
||||||
|
result, err := h.proxy.getOrFetchArtifactFromURLWithCachePURLs(
|
||||||
|
r.Context(), "swift", packageName, version, filename, packagePURL, versionPURL,
|
||||||
|
upstreamURL, headers, archiveInfo.checksum,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
h.writeArtifactError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result.ContentType = "application/zip"
|
||||||
|
setSwiftArchiveHeaders(w.Header(), name, version, result.Hash, archiveInfo)
|
||||||
|
serveArtifact(w, r.Method, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SwiftHandler) handleSourceArchiveHead(
|
||||||
|
w http.ResponseWriter,
|
||||||
|
r *http.Request,
|
||||||
|
name, version, filename, packagePURL, versionPURL, upstreamURL string,
|
||||||
|
archiveInfo swiftArchiveInfo,
|
||||||
|
) {
|
||||||
|
result, err := h.proxy.getCachedArtifactWithUpstreamHash(
|
||||||
|
r.Context(), packagePURL, versionPURL, filename, archiveInfo.checksum,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
h.writeArtifactError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if result != nil {
|
||||||
|
result.ContentType = "application/zip"
|
||||||
|
setSwiftArchiveHeaders(w.Header(), name, version, result.Hash, archiveInfo)
|
||||||
|
serveArtifact(w, r.Method, result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
size, err := h.probeSourceArchive(r.Context(), upstreamURL, requestAccept(r, swiftAcceptArchive))
|
||||||
|
if err != nil {
|
||||||
|
h.writeArtifactError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setSwiftArchiveHeaders(w.Header(), name, version, "", archiveInfo)
|
||||||
|
w.Header().Set("Content-Type", "application/zip")
|
||||||
|
if size >= 0 {
|
||||||
|
w.Header().Set("Content-Length", strconv.FormatInt(size, 10))
|
||||||
|
}
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SwiftHandler) probeSourceArchive(ctx context.Context, upstreamURL, accept string) (int64, error) {
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, upstreamURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("creating upstream archive request: %w", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Accept", accept)
|
||||||
|
req.Header.Set("Range", "bytes=0-0")
|
||||||
|
h.proxy.applyUpstreamAuth(req)
|
||||||
|
|
||||||
|
resp, err := h.proxy.HTTPClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("requesting upstream archive: %w", err)
|
||||||
|
}
|
||||||
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
|
||||||
|
if resp.StatusCode == http.StatusNotFound {
|
||||||
|
return 0, ErrUpstreamNotFound
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusPartialContent {
|
||||||
|
return 0, fmt.Errorf("upstream archive returned %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode == http.StatusPartialContent {
|
||||||
|
_, total, found := strings.Cut(resp.Header.Get("Content-Range"), "/")
|
||||||
|
if !found || total == "*" {
|
||||||
|
return -1, nil
|
||||||
|
}
|
||||||
|
if parsed, parseErr := strconv.ParseInt(total, 10, 64); parseErr == nil {
|
||||||
|
return parsed, nil
|
||||||
|
}
|
||||||
|
return -1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
size := int64(-1)
|
||||||
|
if contentLength := resp.Header.Get("Content-Length"); contentLength != "" {
|
||||||
|
if parsed, parseErr := strconv.ParseInt(contentLength, 10, 64); parseErr == nil {
|
||||||
|
size = parsed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return size, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SwiftHandler) fetchMetadataWithHeaders(
|
||||||
|
ctx context.Context,
|
||||||
|
upstreamURL, accept string,
|
||||||
|
) ([]byte, string, http.Header, error) {
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, upstreamURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", nil, fmt.Errorf("creating upstream metadata request: %w", err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Accept", accept)
|
||||||
|
h.proxy.applyUpstreamAuth(req)
|
||||||
|
|
||||||
|
resp, err := h.proxy.HTTPClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", nil, fmt.Errorf("requesting upstream metadata: %w", err)
|
||||||
|
}
|
||||||
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
|
||||||
|
if resp.StatusCode == http.StatusNotFound {
|
||||||
|
return nil, "", nil, ErrUpstreamNotFound
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, "", nil, fmt.Errorf("upstream metadata returned %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := h.proxy.ReadMetadata(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", nil, fmt.Errorf("reading upstream metadata: %w", err)
|
||||||
|
}
|
||||||
|
contentType := resp.Header.Get("Content-Type")
|
||||||
|
if contentType == "" {
|
||||||
|
contentType = contentTypeJSON
|
||||||
|
}
|
||||||
|
return body, contentType, resp.Header.Clone(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type swiftReleaseMetadata struct {
|
||||||
|
Resources []struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Checksum string `json:"checksum"`
|
||||||
|
Signing *struct {
|
||||||
|
Signature string `json:"signatureBase64Encoded"`
|
||||||
|
Format string `json:"signatureFormat"`
|
||||||
|
} `json:"signing"`
|
||||||
|
} `json:"resources"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type swiftArchiveInfo struct {
|
||||||
|
checksum string
|
||||||
|
signature string
|
||||||
|
signatureFormat string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SwiftHandler) fetchArchiveInfo(ctx context.Context, scope, name, version string) (swiftArchiveInfo, error) {
|
||||||
|
upstreamURL := h.buildUpstreamURL(scope, name, version, "", "")
|
||||||
|
body, _, err := h.proxy.FetchOrCacheMetadata(
|
||||||
|
ctx, "swift", swiftReleaseCacheKey(scope, name, version), upstreamURL, swiftAcceptJSON,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return swiftArchiveInfo{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var metadata swiftReleaseMetadata
|
||||||
|
if err := json.Unmarshal(body, &metadata); err != nil {
|
||||||
|
return swiftArchiveInfo{}, fmt.Errorf("parsing release metadata: %w", err)
|
||||||
|
}
|
||||||
|
for _, resource := range metadata.Resources {
|
||||||
|
if resource.Name != "source-archive" || resource.Type != "application/zip" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
checksum, err := normalizeSwiftChecksum(resource.Checksum)
|
||||||
|
if err != nil {
|
||||||
|
return swiftArchiveInfo{}, err
|
||||||
|
}
|
||||||
|
info := swiftArchiveInfo{checksum: checksum}
|
||||||
|
if resource.Signing != nil {
|
||||||
|
if resource.Signing.Signature == "" || resource.Signing.Format == "" {
|
||||||
|
return swiftArchiveInfo{}, errors.New("source archive signing metadata is incomplete")
|
||||||
|
}
|
||||||
|
info.signature = resource.Signing.Signature
|
||||||
|
info.signatureFormat = resource.Signing.Format
|
||||||
|
}
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return swiftArchiveInfo{}, errors.New("source archive is missing from release metadata")
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeSwiftChecksum(checksum string) (string, error) {
|
||||||
|
digest, err := hex.DecodeString(checksum)
|
||||||
|
if err != nil || len(digest) != sha256.Size {
|
||||||
|
return "", errors.New("source archive checksum is not a SHA-256 digest")
|
||||||
|
}
|
||||||
|
return hex.EncodeToString(digest), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSwiftArchiveHeaders(header http.Header, name, version, contentHash string, info swiftArchiveInfo) {
|
||||||
|
header.Set("Cache-Control", "public, immutable")
|
||||||
|
header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s-%s.zip"`, name, version))
|
||||||
|
header.Set("Content-Version", swiftContentVersion)
|
||||||
|
|
||||||
|
checksum := info.checksum
|
||||||
|
if checksum == "" {
|
||||||
|
checksum = contentHash
|
||||||
|
}
|
||||||
|
if digest := swiftDigestHeader(checksum); digest != "" {
|
||||||
|
header.Set("Digest", digest)
|
||||||
|
}
|
||||||
|
if info.signature != "" && info.signatureFormat != "" {
|
||||||
|
header.Set("X-Swift-Package-Signature", info.signature)
|
||||||
|
header.Set("X-Swift-Package-Signature-Format", info.signatureFormat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func swiftDigestHeader(checksum string) string {
|
||||||
|
digest, err := hex.DecodeString(checksum)
|
||||||
|
if err != nil || len(digest) != sha256.Size {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return "sha-256=" + base64.StdEncoding.EncodeToString(digest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SwiftHandler) proxySwiftResource(w http.ResponseWriter, r *http.Request, upstreamURL, defaultAccept string) {
|
||||||
|
req, err := http.NewRequestWithContext(r.Context(), r.Method, upstreamURL, nil)
|
||||||
|
if err != nil {
|
||||||
|
writeSwiftProblem(w, http.StatusInternalServerError, "failed to create upstream request")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Header.Set("Accept", requestAccept(r, defaultAccept))
|
||||||
|
for _, name := range []string{"If-Modified-Since", "If-None-Match"} {
|
||||||
|
if value := r.Header.Get(name); value != "" {
|
||||||
|
req.Header.Set(name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
h.proxy.applyUpstreamAuth(req)
|
||||||
|
|
||||||
|
resp, err := h.proxy.HTTPClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
writeSwiftProblem(w, http.StatusBadGateway, "upstream request failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
|
||||||
|
copySwiftResponseHeaders(w.Header(), resp.Header)
|
||||||
|
if location := resp.Header.Get("Location"); location != "" {
|
||||||
|
w.Header().Set("Location", h.rewriteRegistryURL(location, upstreamURL))
|
||||||
|
}
|
||||||
|
for _, link := range resp.Header.Values("Link") {
|
||||||
|
w.Header().Add("Link", h.rewriteLinkHeader(link, upstreamURL))
|
||||||
|
}
|
||||||
|
if w.Header().Get("Content-Version") == "" {
|
||||||
|
w.Header().Set("Content-Version", swiftContentVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
w.WriteHeader(resp.StatusCode)
|
||||||
|
if r.Method != http.MethodHead {
|
||||||
|
_, _ = io.Copy(w, resp.Body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func copySwiftResponseHeaders(dst, src http.Header) {
|
||||||
|
for _, name := range []string{
|
||||||
|
"Cache-Control", "Content-Disposition", "Content-Language", "Content-Length",
|
||||||
|
"Content-Type", "Content-Version", "Digest", "ETag", "Last-Modified",
|
||||||
|
"Retry-After", "Vary", "Warning", "X-Swift-Package-Signature",
|
||||||
|
"X-Swift-Package-Signature-Format",
|
||||||
|
} {
|
||||||
|
for _, value := range src.Values(name) {
|
||||||
|
dst.Add(name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SwiftHandler) rewriteLinkHeader(value, upstreamRequestURL string) string {
|
||||||
|
var result strings.Builder
|
||||||
|
for len(value) > 0 {
|
||||||
|
start := strings.IndexByte(value, '<')
|
||||||
|
if start < 0 {
|
||||||
|
result.WriteString(value)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
endOffset := strings.IndexByte(value[start+1:], '>')
|
||||||
|
if endOffset < 0 {
|
||||||
|
result.WriteString(value)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
end := start + 1 + endOffset
|
||||||
|
result.WriteString(value[:start+1])
|
||||||
|
result.WriteString(h.rewriteRegistryURL(value[start+1:end], upstreamRequestURL))
|
||||||
|
result.WriteByte('>')
|
||||||
|
value = value[end+1:]
|
||||||
|
}
|
||||||
|
return result.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SwiftHandler) rewriteRegistryURL(rawURL, upstreamRequestURL string) string {
|
||||||
|
base, err := url.Parse(h.upstreamURL)
|
||||||
|
if err != nil {
|
||||||
|
return rawURL
|
||||||
|
}
|
||||||
|
requestURL, err := url.Parse(upstreamRequestURL)
|
||||||
|
if err != nil {
|
||||||
|
return rawURL
|
||||||
|
}
|
||||||
|
reference, err := url.Parse(rawURL)
|
||||||
|
if err != nil {
|
||||||
|
return rawURL
|
||||||
|
}
|
||||||
|
absolute := requestURL.ResolveReference(reference)
|
||||||
|
if !strings.EqualFold(absolute.Scheme, base.Scheme) || !strings.EqualFold(absolute.Host, base.Host) {
|
||||||
|
return rawURL
|
||||||
|
}
|
||||||
|
|
||||||
|
basePath := strings.TrimSuffix(base.EscapedPath(), "/")
|
||||||
|
absolutePath := absolute.EscapedPath()
|
||||||
|
if absolutePath != basePath && !strings.HasPrefix(absolutePath, basePath+"/") {
|
||||||
|
return rawURL
|
||||||
|
}
|
||||||
|
suffix := strings.TrimPrefix(absolutePath, basePath)
|
||||||
|
rewritten := h.proxyURL + "/swift" + suffix
|
||||||
|
if absolute.RawQuery != "" {
|
||||||
|
rewritten += "?" + absolute.RawQuery
|
||||||
|
}
|
||||||
|
if absolute.Fragment != "" {
|
||||||
|
rewritten += "#" + absolute.Fragment
|
||||||
|
}
|
||||||
|
return rewritten
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SwiftHandler) rewriteReleaseURLs(scope, name string, body []byte) ([]byte, error) {
|
||||||
|
var metadata map[string]any
|
||||||
|
if err := json.Unmarshal(body, &metadata); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
releases, ok := metadata["releases"].(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
return body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for version, value := range releases {
|
||||||
|
release, ok := value.(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, hasURL := release["url"]; !hasURL {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
release["url"] = fmt.Sprintf(
|
||||||
|
"%s/swift/%s/%s/%s",
|
||||||
|
h.proxyURL,
|
||||||
|
url.PathEscape(scope),
|
||||||
|
url.PathEscape(name),
|
||||||
|
url.PathEscape(version),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return json.Marshal(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SwiftHandler) buildUpstreamURL(scope, name, version, resource, rawQuery string) string {
|
||||||
|
parts := []string{h.upstreamURL, url.PathEscape(scope), url.PathEscape(name)}
|
||||||
|
if version != "" {
|
||||||
|
parts = append(parts, url.PathEscape(version))
|
||||||
|
}
|
||||||
|
if resource != "" {
|
||||||
|
parts = append(parts, resource)
|
||||||
|
}
|
||||||
|
result := strings.Join(parts, "/")
|
||||||
|
if rawQuery != "" {
|
||||||
|
result += "?" + rawQuery
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func swiftMetadataCacheKey(parts ...string) string {
|
||||||
|
joined := strings.Join(parts, "\x00")
|
||||||
|
digest := sha256.Sum256([]byte(joined))
|
||||||
|
return hex.EncodeToString(digest[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func swiftReleaseCacheKey(scope, name, version string) string {
|
||||||
|
return swiftMetadataCacheKey("release", scope, name, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
func requestAccept(r *http.Request, fallback string) string {
|
||||||
|
if accept := r.Header.Get("Accept"); accept != "" {
|
||||||
|
return accept
|
||||||
|
}
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeSwiftMetadata(w http.ResponseWriter, r *http.Request, body []byte, contentType string) {
|
||||||
|
if contentType == "" {
|
||||||
|
contentType = "application/json"
|
||||||
|
}
|
||||||
|
digest := sha256.Sum256(body)
|
||||||
|
etag := fmt.Sprintf(`"%x"`, digest)
|
||||||
|
w.Header().Set("Content-Type", contentType)
|
||||||
|
w.Header().Set("Content-Version", swiftContentVersion)
|
||||||
|
w.Header().Set("ETag", etag)
|
||||||
|
if r.Header.Get("If-None-Match") == etag {
|
||||||
|
w.WriteHeader(http.StatusNotModified)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Length", strconv.Itoa(len(body)))
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
if r.Method != http.MethodHead {
|
||||||
|
_, _ = w.Write(body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SwiftHandler) writeMetadataError(w http.ResponseWriter, err error) {
|
||||||
|
if errors.Is(err, ErrUpstreamNotFound) {
|
||||||
|
writeSwiftProblem(w, http.StatusNotFound, "not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.proxy.Logger.Error("Swift metadata request failed", "error", err)
|
||||||
|
writeSwiftProblem(w, http.StatusBadGateway, "upstream request failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *SwiftHandler) writeArtifactError(w http.ResponseWriter, err error) {
|
||||||
|
if errors.Is(err, ErrUpstreamNotFound) {
|
||||||
|
writeSwiftProblem(w, http.StatusNotFound, "release not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.proxy.Logger.Error("Swift archive request failed", "error", err)
|
||||||
|
writeSwiftProblem(w, http.StatusBadGateway, "failed to fetch package")
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeSwiftProblem(w http.ResponseWriter, status int, detail string) {
|
||||||
|
w.Header().Set("Content-Type", "application/problem+json")
|
||||||
|
w.Header().Set("Content-Version", swiftContentVersion)
|
||||||
|
w.WriteHeader(status)
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]string{"detail": detail})
|
||||||
|
}
|
||||||
|
|
||||||
|
func validSwiftPackageReference(scope, name, version string) bool {
|
||||||
|
return validSwiftScope(scope) && validSwiftPackageName(name) && version != "" && version != "." && version != ".." && !strings.ContainsAny(version, "/\\")
|
||||||
|
}
|
||||||
|
|
||||||
|
func canonicalSwiftPackage(scope, name string) (string, string) {
|
||||||
|
return strings.ToLower(scope), strings.ToLower(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func validSwiftScope(scope string) bool {
|
||||||
|
return validSwiftIdentifier(scope, swiftMaxScopeLength, "-")
|
||||||
|
}
|
||||||
|
|
||||||
|
func validSwiftPackageName(name string) bool {
|
||||||
|
return validSwiftIdentifier(name, swiftMaxNameLength, "-_")
|
||||||
|
}
|
||||||
|
|
||||||
|
func validSwiftIdentifier(value string, maxLength int, separators string) bool {
|
||||||
|
if value == "" || len(value) > maxLength {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
previousSeparator := false
|
||||||
|
for i := 0; i < len(value); i++ {
|
||||||
|
character := value[i]
|
||||||
|
separator := strings.ContainsRune(separators, rune(character))
|
||||||
|
if separator {
|
||||||
|
if i == 0 || i == len(value)-1 || previousSeparator {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
previousSeparator = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (character < 'a' || character > 'z') &&
|
||||||
|
(character < 'A' || character > 'Z') &&
|
||||||
|
(character < '0' || character > '9') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
previousSeparator = false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
534
internal/handler/swift_test.go
Normal file
534
internal/handler/swift_test.go
Normal file
|
|
@ -0,0 +1,534 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/git-pkgs/proxy/internal/packageurl"
|
||||||
|
"github.com/git-pkgs/registries/fetch"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSwiftPackageReleasesRewritesRegistryURLs(t *testing.T) {
|
||||||
|
var gotAccept string
|
||||||
|
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path != "/registry/apple/swift-argument-parser" {
|
||||||
|
t.Errorf("upstream path = %q", r.URL.Path)
|
||||||
|
}
|
||||||
|
gotAccept = r.Header.Get("Accept")
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
w.Header().Set("Content-Version", "1")
|
||||||
|
w.Header().Add("Link", `</registry/apple/swift-argument-parser?page=2>; rel="next"`)
|
||||||
|
_, _ = io.WriteString(w, `{"releases":{"1.2.0":{"url":"/registry/apple/swift-argument-parser/1.2.0"},"1.1.0":{}}}`)
|
||||||
|
}))
|
||||||
|
defer upstream.Close()
|
||||||
|
|
||||||
|
proxy, _, _, _ := setupTestProxy(t)
|
||||||
|
handler := NewSwiftHandler(proxy, "https://proxy.example", upstream.URL+"/registry").Routes()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/APPLE/SWIFT-ARGUMENT-PARSER", nil)
|
||||||
|
req.Header.Set("Accept", swiftAcceptJSON)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d, want 200; body: %s", w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
if gotAccept != swiftAcceptJSON {
|
||||||
|
t.Errorf("upstream Accept = %q, want %q", gotAccept, swiftAcceptJSON)
|
||||||
|
}
|
||||||
|
if got := w.Header().Get("Content-Version"); got != "1" {
|
||||||
|
t.Errorf("Content-Version = %q, want 1", got)
|
||||||
|
}
|
||||||
|
if got := w.Header().Get("Link"); got != `<https://proxy.example/swift/apple/swift-argument-parser?page=2>; rel="next"` {
|
||||||
|
t.Errorf("Link = %q", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body struct {
|
||||||
|
Releases map[string]struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
} `json:"releases"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(w.Body).Decode(&body); err != nil {
|
||||||
|
t.Fatalf("decoding response: %v", err)
|
||||||
|
}
|
||||||
|
if got := body.Releases["1.2.0"].URL; got != "https://proxy.example/swift/apple/swift-argument-parser/1.2.0" {
|
||||||
|
t.Errorf("release URL = %q", got)
|
||||||
|
}
|
||||||
|
if got := body.Releases["1.1.0"].URL; got != "" {
|
||||||
|
t.Errorf("release without upstream URL gained URL %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSwiftReleaseMetadataSupportsJSONExtensionAndHead(t *testing.T) {
|
||||||
|
var requestMethods []string
|
||||||
|
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
requestMethods = append(requestMethods, r.Method)
|
||||||
|
if r.URL.Path != "/registry/apple/example/1.2.3" {
|
||||||
|
t.Errorf("upstream path = %q", r.URL.Path)
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = io.WriteString(w, `{"id":"apple.example","version":"1.2.3","resources":[]}`)
|
||||||
|
}))
|
||||||
|
defer upstream.Close()
|
||||||
|
|
||||||
|
proxy, _, _, _ := setupTestProxy(t)
|
||||||
|
handler := NewSwiftHandler(proxy, "https://proxy.example", upstream.URL+"/registry").Routes()
|
||||||
|
|
||||||
|
for _, method := range []string{http.MethodGet, http.MethodHead} {
|
||||||
|
req := httptest.NewRequest(method, "/APPLE/EXAMPLE/1.2.3.json", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(w, req)
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatalf("%s status = %d, want 200", method, w.Code)
|
||||||
|
}
|
||||||
|
if method == http.MethodHead && w.Body.Len() != 0 {
|
||||||
|
t.Errorf("HEAD response body length = %d, want 0", w.Body.Len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(requestMethods) != 2 || requestMethods[0] != http.MethodGet || requestMethods[1] != http.MethodGet {
|
||||||
|
t.Errorf("upstream methods = %v, want metadata GETs", requestMethods)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSwiftManifestProxiesQueryAndRewritesLinks(t *testing.T) {
|
||||||
|
var upstream *httptest.Server
|
||||||
|
var gotAccept string
|
||||||
|
upstream = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodGet {
|
||||||
|
t.Errorf("upstream method = %s, want GET", r.Method)
|
||||||
|
}
|
||||||
|
if r.URL.Path != "/registry/apple/example/1.2.3/Package.swift" {
|
||||||
|
t.Errorf("upstream path = %q", r.URL.Path)
|
||||||
|
}
|
||||||
|
if got := r.URL.Query().Get("swift-version"); got != "5.9" {
|
||||||
|
t.Errorf("swift-version = %q, want 5.9", got)
|
||||||
|
}
|
||||||
|
gotAccept = r.Header.Get("Accept")
|
||||||
|
w.Header().Set("Content-Type", "text/x-swift")
|
||||||
|
w.Header().Add("Link", fmt.Sprintf(`<%s/registry/apple/example/1.2.3/Package.swift?swift-version=5.8>; rel="alternate"; filename="Package@swift-5.8.swift"`, upstream.URL))
|
||||||
|
w.Header().Add("Link", `<https://github.com/apple/example>; rel="canonical"`)
|
||||||
|
_, _ = io.WriteString(w, "// swift-tools-version: 5.9\n")
|
||||||
|
}))
|
||||||
|
defer upstream.Close()
|
||||||
|
|
||||||
|
proxy, _, _, _ := setupTestProxy(t)
|
||||||
|
handler := NewSwiftHandler(proxy, "https://proxy.example", upstream.URL+"/registry").Routes()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/APPLE/EXAMPLE/1.2.3/Package.swift?swift-version=5.9", nil)
|
||||||
|
req.Header.Set("Accept", swiftAcceptManifest)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d, want 200", w.Code)
|
||||||
|
}
|
||||||
|
if gotAccept != swiftAcceptManifest {
|
||||||
|
t.Errorf("upstream Accept = %q, want %q", gotAccept, swiftAcceptManifest)
|
||||||
|
}
|
||||||
|
links := strings.Join(w.Header().Values("Link"), ",")
|
||||||
|
if !strings.Contains(links, "https://proxy.example/swift/apple/example/1.2.3/Package.swift?swift-version=5.8") {
|
||||||
|
t.Errorf("internal manifest Link was not rewritten: %q", links)
|
||||||
|
}
|
||||||
|
if !strings.Contains(links, "https://github.com/apple/example") {
|
||||||
|
t.Errorf("external canonical Link was changed: %q", links)
|
||||||
|
}
|
||||||
|
if got := w.Header().Get("Content-Version"); got != "1" {
|
||||||
|
t.Errorf("Content-Version = %q, want 1", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSwiftSourceArchiveCachesAndPreservesSecurityMetadata(t *testing.T) {
|
||||||
|
archive := []byte("swift source archive")
|
||||||
|
checksumBytes := sha256.Sum256(archive)
|
||||||
|
checksum := hex.EncodeToString(checksumBytes[:])
|
||||||
|
signature := base64.StdEncoding.EncodeToString([]byte("signature"))
|
||||||
|
|
||||||
|
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path != "/registry/apple/example/1.2.3" {
|
||||||
|
t.Errorf("metadata path = %q", r.URL.Path)
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = fmt.Fprintf(w, `{"id":"apple.example","version":"1.2.3","resources":[{"name":"source-archive","type":"application/zip","checksum":%q,"signing":{"signatureBase64Encoded":%q,"signatureFormat":"cms-1.0.0"}}]}`, checksum, signature)
|
||||||
|
}))
|
||||||
|
defer upstream.Close()
|
||||||
|
|
||||||
|
proxy, db, _, fetcher := setupTestProxy(t)
|
||||||
|
fetcher.artifact = &fetch.Artifact{
|
||||||
|
Body: io.NopCloser(strings.NewReader(string(archive))),
|
||||||
|
Size: int64(len(archive)),
|
||||||
|
ContentType: "application/zip",
|
||||||
|
}
|
||||||
|
handler := NewSwiftHandler(proxy, "https://proxy.example", upstream.URL+"/registry").Routes()
|
||||||
|
|
||||||
|
requestArchive := func(method string) *httptest.ResponseRecorder {
|
||||||
|
req := httptest.NewRequest(method, "/apple/example/1.2.3.zip", nil)
|
||||||
|
req.Header.Set("Accept", swiftAcceptArchive)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(w, req)
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
w := requestArchive(http.MethodGet)
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d, want 200; body: %s", w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
if got := w.Body.Bytes(); string(got) != string(archive) {
|
||||||
|
t.Errorf("archive body = %q", got)
|
||||||
|
}
|
||||||
|
if !fetcher.fetchCalled {
|
||||||
|
t.Fatal("archive fetcher was not called")
|
||||||
|
}
|
||||||
|
if got := fetcher.fetchedURL; got != upstream.URL+"/registry/apple/example/1.2.3.zip" {
|
||||||
|
t.Errorf("fetched URL = %q", got)
|
||||||
|
}
|
||||||
|
if got := fetcher.fetchedHeader.Get("Accept"); got != swiftAcceptArchive {
|
||||||
|
t.Errorf("archive Accept = %q, want %q", got, swiftAcceptArchive)
|
||||||
|
}
|
||||||
|
if got := w.Header().Get("Digest"); got != "sha-256="+base64.StdEncoding.EncodeToString(checksumBytes[:]) {
|
||||||
|
t.Errorf("Digest = %q", got)
|
||||||
|
}
|
||||||
|
if got := w.Header().Get("X-Swift-Package-Signature"); got != signature {
|
||||||
|
t.Errorf("signature = %q", got)
|
||||||
|
}
|
||||||
|
if got := w.Header().Get("X-Swift-Package-Signature-Format"); got != "cms-1.0.0" {
|
||||||
|
t.Errorf("signature format = %q", got)
|
||||||
|
}
|
||||||
|
if got := w.Header().Get("Content-Disposition"); got != `attachment; filename="example-1.2.3.zip"` {
|
||||||
|
t.Errorf("Content-Disposition = %q", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
packagePURL, versionPURL := packageurl.MakeCacheStrings("swift", "apple/example", "1.2.3")
|
||||||
|
if strings.HasPrefix(packagePURL, "pkg:swift/") {
|
||||||
|
t.Fatalf("registry identity produced source PURL %q", packagePURL)
|
||||||
|
}
|
||||||
|
versionRecord, err := db.GetVersionByPURL(versionPURL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("cached Swift version %q not found: %v", versionPURL, err)
|
||||||
|
}
|
||||||
|
if versionRecord == nil {
|
||||||
|
t.Fatalf("cached Swift version %q not found", versionPURL)
|
||||||
|
}
|
||||||
|
if versionRecord.PackagePURL != packagePURL {
|
||||||
|
t.Errorf("cached package PURL = %q, want %q", versionRecord.PackagePURL, packagePURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
fetcher.fetchCalled = false
|
||||||
|
w = requestArchive(http.MethodHead)
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatalf("HEAD status = %d, want 200", w.Code)
|
||||||
|
}
|
||||||
|
if w.Body.Len() != 0 {
|
||||||
|
t.Errorf("HEAD body length = %d, want 0", w.Body.Len())
|
||||||
|
}
|
||||||
|
if got := w.Header().Get("Content-Length"); got != fmt.Sprint(len(archive)) {
|
||||||
|
t.Errorf("HEAD Content-Length = %q", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
w = requestArchive(http.MethodGet)
|
||||||
|
if w.Code != http.StatusOK || w.Body.String() != string(archive) {
|
||||||
|
t.Fatalf("cached response = %d %q", w.Code, w.Body.Bytes())
|
||||||
|
}
|
||||||
|
if fetcher.fetchCalled {
|
||||||
|
t.Error("cached archive contacted artifact upstream")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSwiftSourceArchiveRejectsChecksumMismatch(t *testing.T) {
|
||||||
|
archive := []byte("unexpected archive")
|
||||||
|
expectedChecksum := sha256.Sum256([]byte("expected archive"))
|
||||||
|
|
||||||
|
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = fmt.Fprintf(w, `{"id":"apple.example","version":"1.2.3","resources":[{"name":"source-archive","type":"application/zip","checksum":%q}]}`, hex.EncodeToString(expectedChecksum[:]))
|
||||||
|
}))
|
||||||
|
defer upstream.Close()
|
||||||
|
|
||||||
|
proxy, db, store, fetcher := setupTestProxy(t)
|
||||||
|
fetcher.artifact = &fetch.Artifact{
|
||||||
|
Body: io.NopCloser(strings.NewReader(string(archive))),
|
||||||
|
Size: int64(len(archive)),
|
||||||
|
ContentType: "application/zip",
|
||||||
|
}
|
||||||
|
handler := NewSwiftHandler(proxy, "https://proxy.example", upstream.URL).Routes()
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/apple/example/1.2.3.zip", nil))
|
||||||
|
|
||||||
|
if w.Code != http.StatusBadGateway {
|
||||||
|
t.Fatalf("status = %d, want 502; body: %s", w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
if len(store.files) != 0 {
|
||||||
|
t.Errorf("mismatched archive remained in storage: %v", store.files)
|
||||||
|
}
|
||||||
|
packagePURL, versionPURL := packageurl.MakeCacheStrings("swift", "apple/example", "1.2.3")
|
||||||
|
cached, err := db.GetCachedArtifact(packagePURL, versionPURL, "example-1.2.3.zip")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("checking cache: %v", err)
|
||||||
|
}
|
||||||
|
if cached != nil {
|
||||||
|
t.Error("mismatched archive gained a cache record")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSwiftSourceArchiveCanonicalizesPackageIdentity(t *testing.T) {
|
||||||
|
archive := []byte("swift source archive")
|
||||||
|
checksumBytes := sha256.Sum256(archive)
|
||||||
|
checksum := hex.EncodeToString(checksumBytes[:])
|
||||||
|
var metadataPaths []string
|
||||||
|
|
||||||
|
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
metadataPaths = append(metadataPaths, r.URL.Path)
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = fmt.Fprintf(w, `{"id":"apple.example","version":"1.2.3","resources":[{"name":"source-archive","type":"application/zip","checksum":%q}]}`, checksum)
|
||||||
|
}))
|
||||||
|
defer upstream.Close()
|
||||||
|
|
||||||
|
proxy, db, store, fetcher := setupTestProxy(t)
|
||||||
|
handler := NewSwiftHandler(proxy, "https://proxy.example", upstream.URL).Routes()
|
||||||
|
requestArchive := func(path string) {
|
||||||
|
fetcher.artifact = &fetch.Artifact{
|
||||||
|
Body: io.NopCloser(strings.NewReader(string(archive))),
|
||||||
|
Size: int64(len(archive)),
|
||||||
|
ContentType: "application/zip",
|
||||||
|
}
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(w, httptest.NewRequest(http.MethodGet, path, nil))
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatalf("GET %s status = %d, want 200; body: %s", path, w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
requestArchive("/apple/example/1.2.3.zip")
|
||||||
|
requestArchive("/APPLE/EXAMPLE/1.2.3.zip")
|
||||||
|
|
||||||
|
if len(store.files) != 1 {
|
||||||
|
t.Errorf("cached files = %d, want 1", len(store.files))
|
||||||
|
}
|
||||||
|
for _, path := range metadataPaths {
|
||||||
|
if path != "/apple/example/1.2.3" {
|
||||||
|
t.Errorf("metadata path = %q, want canonical lowercase path", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canonicalPURL, _ := packageurl.MakeCacheStrings("swift", "apple/example", "1.2.3")
|
||||||
|
canonical, err := db.GetPackageByPURL(canonicalPURL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("getting canonical package: %v", err)
|
||||||
|
}
|
||||||
|
if canonical == nil {
|
||||||
|
t.Fatalf("canonical package %q not found", canonicalPURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
nonCanonicalPURL, _ := packageurl.MakeCacheStrings("swift", "APPLE/EXAMPLE", "1.2.3")
|
||||||
|
if nonCanonicalPURL != canonicalPURL {
|
||||||
|
t.Errorf("uppercase cache PURL = %q, want %q", nonCanonicalPURL, canonicalPURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSwiftSourceArchiveHeadDiscardsCachedChecksumMismatch(t *testing.T) {
|
||||||
|
archive := []byte("cached archive")
|
||||||
|
upstreamChecksum := sha256.Sum256([]byte("upstream archive"))
|
||||||
|
|
||||||
|
var probed bool
|
||||||
|
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if strings.HasSuffix(r.URL.Path, ".zip") {
|
||||||
|
probed = true
|
||||||
|
w.Header().Set("Content-Range", "bytes 0-0/456")
|
||||||
|
w.WriteHeader(http.StatusPartialContent)
|
||||||
|
_, _ = w.Write([]byte("x"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = fmt.Fprintf(w, `{"id":"apple.example","version":"1.2.3","resources":[{"name":"source-archive","type":"application/zip","checksum":%q}]}`, hex.EncodeToString(upstreamChecksum[:]))
|
||||||
|
}))
|
||||||
|
defer upstream.Close()
|
||||||
|
|
||||||
|
proxy, db, store, fetcher := setupTestProxy(t)
|
||||||
|
fetcher.artifact = &fetch.Artifact{
|
||||||
|
Body: io.NopCloser(strings.NewReader(string(archive))),
|
||||||
|
Size: int64(len(archive)),
|
||||||
|
ContentType: "application/zip",
|
||||||
|
}
|
||||||
|
packagePURL, versionPURL := packageurl.MakeCacheStrings("swift", "apple/example", "1.2.3")
|
||||||
|
cached, err := proxy.getOrFetchArtifactFromURLWithCachePURLs(
|
||||||
|
context.Background(), "swift", "apple/example", "1.2.3", "example-1.2.3.zip",
|
||||||
|
packagePURL, versionPURL, upstream.URL+"/apple/example/1.2.3.zip", nil, "",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("seeding cache: %v", err)
|
||||||
|
}
|
||||||
|
_ = cached.Reader.Close()
|
||||||
|
|
||||||
|
handler := NewSwiftHandler(proxy, "https://proxy.example", upstream.URL).Routes()
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(w, httptest.NewRequest(http.MethodHead, "/apple/example/1.2.3.zip", nil))
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d, want 200; body: %s", w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
if !probed {
|
||||||
|
t.Error("stale cache entry was not replaced by an upstream probe")
|
||||||
|
}
|
||||||
|
if got := w.Header().Get("Content-Length"); got != "456" {
|
||||||
|
t.Errorf("Content-Length = %q, want 456 from upstream probe", got)
|
||||||
|
}
|
||||||
|
if len(store.files) != 0 {
|
||||||
|
t.Errorf("mismatched cached archive remained in storage: %v", store.files)
|
||||||
|
}
|
||||||
|
if rec, _ := db.GetCachedArtifact(packagePURL, versionPURL, "example-1.2.3.zip"); rec != nil {
|
||||||
|
t.Error("mismatched cache record was not cleared")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSwiftSourceArchiveRequiresReleaseMetadata(t *testing.T) {
|
||||||
|
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
http.Error(w, "unavailable", http.StatusServiceUnavailable)
|
||||||
|
}))
|
||||||
|
defer upstream.Close()
|
||||||
|
|
||||||
|
proxy, _, store, fetcher := setupTestProxy(t)
|
||||||
|
fetcher.artifact = &fetch.Artifact{
|
||||||
|
Body: io.NopCloser(strings.NewReader("signed archive")),
|
||||||
|
ContentType: "application/zip",
|
||||||
|
}
|
||||||
|
handler := NewSwiftHandler(proxy, "https://proxy.example", upstream.URL).Routes()
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/apple/example/1.2.3.zip", nil))
|
||||||
|
|
||||||
|
if w.Code != http.StatusBadGateway {
|
||||||
|
t.Fatalf("status = %d, want 502; body: %s", w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
if fetcher.fetchCalled {
|
||||||
|
t.Error("archive was fetched without release security metadata")
|
||||||
|
}
|
||||||
|
if len(store.files) != 0 {
|
||||||
|
t.Errorf("archive was cached without release security metadata: %v", store.files)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSwiftSourceArchiveColdHeadUsesRangeGetAcrossRedirect(t *testing.T) {
|
||||||
|
checksum := strings.Repeat("a", sha256.Size*2)
|
||||||
|
var archiveAccept string
|
||||||
|
var archiveMethod string
|
||||||
|
var archiveRange string
|
||||||
|
download := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
archiveMethod = r.Method
|
||||||
|
archiveRange = r.Header.Get("Range")
|
||||||
|
w.Header().Set("Content-Range", "bytes 0-0/123")
|
||||||
|
w.WriteHeader(http.StatusPartialContent)
|
||||||
|
_, _ = w.Write([]byte("x"))
|
||||||
|
}))
|
||||||
|
defer download.Close()
|
||||||
|
|
||||||
|
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.URL.Path {
|
||||||
|
case "/apple/example/1.2.3":
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = fmt.Fprintf(w, `{"id":"apple.example","version":"1.2.3","resources":[{"name":"source-archive","type":"application/zip","checksum":%q}]}`, checksum)
|
||||||
|
case "/apple/example/1.2.3.zip":
|
||||||
|
archiveAccept = r.Header.Get("Accept")
|
||||||
|
http.Redirect(w, r, download.URL, http.StatusSeeOther)
|
||||||
|
default:
|
||||||
|
http.NotFound(w, r)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer upstream.Close()
|
||||||
|
|
||||||
|
proxy, _, _, _ := setupTestProxy(t)
|
||||||
|
proxy.HTTPClient = upstream.Client()
|
||||||
|
handler := NewSwiftHandler(proxy, "https://proxy.example", upstream.URL).Routes()
|
||||||
|
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(w, httptest.NewRequest(http.MethodHead, "/apple/example/1.2.3.zip", nil))
|
||||||
|
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d, want 200; body: %s", w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
if archiveMethod != http.MethodGet {
|
||||||
|
t.Errorf("download method = %q, want GET", archiveMethod)
|
||||||
|
}
|
||||||
|
if archiveRange != "bytes=0-0" {
|
||||||
|
t.Errorf("download Range = %q, want bytes=0-0", archiveRange)
|
||||||
|
}
|
||||||
|
if archiveAccept != swiftAcceptArchive {
|
||||||
|
t.Errorf("upstream Accept = %q, want %q", archiveAccept, swiftAcceptArchive)
|
||||||
|
}
|
||||||
|
if got := w.Header().Get("Content-Length"); got != "123" {
|
||||||
|
t.Errorf("Content-Length = %q, want 123", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSwiftIdentifiersAndPublishingUnsupported(t *testing.T) {
|
||||||
|
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path != "/registry/identifiers" {
|
||||||
|
t.Errorf("upstream path = %q", r.URL.Path)
|
||||||
|
}
|
||||||
|
if got := r.URL.Query().Get("url"); got != "https://github.com/apple/example" {
|
||||||
|
t.Errorf("lookup URL = %q", got)
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = io.WriteString(w, `{"identifiers":["apple.example"]}`)
|
||||||
|
}))
|
||||||
|
defer upstream.Close()
|
||||||
|
|
||||||
|
proxy, _, _, _ := setupTestProxy(t)
|
||||||
|
handler := NewSwiftHandler(proxy, "https://proxy.example", upstream.URL+"/registry").Routes()
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/identifiers?url=https%3A%2F%2Fgithub.com%2Fapple%2Fexample", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(w, req)
|
||||||
|
if w.Code != http.StatusOK || !strings.Contains(w.Body.String(), "apple.example") {
|
||||||
|
t.Fatalf("identifier response = %d %q", w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
req = httptest.NewRequest(http.MethodGet, "/identifiers", nil)
|
||||||
|
w = httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(w, req)
|
||||||
|
if w.Code != http.StatusBadRequest {
|
||||||
|
t.Errorf("missing URL status = %d, want 400", w.Code)
|
||||||
|
}
|
||||||
|
|
||||||
|
req = httptest.NewRequest(http.MethodPut, "/apple/example/1.2.3", strings.NewReader("ignored"))
|
||||||
|
w = httptest.NewRecorder()
|
||||||
|
handler.ServeHTTP(w, req)
|
||||||
|
if w.Code != http.StatusMethodNotAllowed {
|
||||||
|
t.Errorf("publish status = %d, want 405", w.Code)
|
||||||
|
}
|
||||||
|
if got := w.Header().Get("Allow"); got != "GET, HEAD" {
|
||||||
|
t.Errorf("Allow = %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSwiftIdentifierValidation(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
value string
|
||||||
|
valid func(string) bool
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{"scope", "apple", validSwiftScope, true},
|
||||||
|
{"scope hyphen", "swift-server", validSwiftScope, true},
|
||||||
|
{"scope underscore", "swift_server", validSwiftScope, false},
|
||||||
|
{"scope repeated separator", "swift--server", validSwiftScope, false},
|
||||||
|
{"package", "swift-argument_parser", validSwiftPackageName, true},
|
||||||
|
{"package repeated separators", "swift-_argument", validSwiftPackageName, false},
|
||||||
|
{"package trailing separator", "example-", validSwiftPackageName, false},
|
||||||
|
{"package non-ASCII", "café", validSwiftPackageName, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
if got := test.valid(test.value); got != test.want {
|
||||||
|
t.Errorf("validation of %q = %v, want %v", test.value, got, test.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
16
internal/mirror/registry.go
Normal file
16
internal/mirror/registry.go
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
package mirror
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegistrySource enumerates all packages in a registry for full mirroring.
|
||||||
|
// Registry enumeration is not yet implemented for any ecosystem.
|
||||||
|
type RegistrySource struct {
|
||||||
|
Ecosystem string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *RegistrySource) Enumerate(_ context.Context, _ func(PackageVersion) error) error {
|
||||||
|
return fmt.Errorf("registry enumeration is not yet implemented for ecosystem %q", s.Ecosystem)
|
||||||
|
}
|
||||||
46
internal/mirror/registry_test.go
Normal file
46
internal/mirror/registry_test.go
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
package mirror
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRegistrySourceUnsupported(t *testing.T) {
|
||||||
|
source := &RegistrySource{Ecosystem: "golang"}
|
||||||
|
err := source.Enumerate(context.Background(), func(pv PackageVersion) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for unsupported ecosystem")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegistrySourceNPMNotImplemented(t *testing.T) {
|
||||||
|
source := &RegistrySource{Ecosystem: "npm"}
|
||||||
|
err := source.Enumerate(context.Background(), func(pv PackageVersion) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected not-implemented error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegistrySourcePyPINotImplemented(t *testing.T) {
|
||||||
|
source := &RegistrySource{Ecosystem: "pypi"}
|
||||||
|
err := source.Enumerate(context.Background(), func(pv PackageVersion) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected not-implemented error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegistrySourceCargoNotImplemented(t *testing.T) {
|
||||||
|
source := &RegistrySource{Ecosystem: "cargo"}
|
||||||
|
err := source.Enumerate(context.Background(), func(pv PackageVersion) error {
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected not-implemented error")
|
||||||
|
}
|
||||||
|
}
|
||||||
62
internal/packageurl/packageurl.go
Normal file
62
internal/packageurl/packageurl.go
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
// Package packageurl builds package URLs from ecosystem-native package names.
|
||||||
|
package packageurl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/git-pkgs/purl"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Make constructs a package URL from an ecosystem-native package name.
|
||||||
|
func Make(ecosystem, name, version string) *purl.PURL {
|
||||||
|
return purl.MakePURL(ecosystem, name, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeString constructs a package URL string. It returns an empty string when
|
||||||
|
// the package identity cannot be represented as a PURL.
|
||||||
|
func MakeString(ecosystem, name, version string) string {
|
||||||
|
return purl.MakePURLString(ecosystem, name, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithVersionString returns a package PURL with its version replaced. It
|
||||||
|
// returns an empty string when packagePURL is invalid.
|
||||||
|
func WithVersionString(packagePURL, version string) string {
|
||||||
|
pkg, err := purl.Parse(packagePURL)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return pkg.WithVersion(version).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeCacheStrings returns package and version PURLs suitable for artifact
|
||||||
|
// cache records. Swift registry identities use an explicit generic PURL until
|
||||||
|
// their source repository has been resolved. The result is independent of the
|
||||||
|
// configured upstream so cache entries survive an upstream.swift change,
|
||||||
|
// matching every other ecosystem.
|
||||||
|
func MakeCacheStrings(ecosystem, name, version string) (packagePURL, versionPURL string) {
|
||||||
|
if pkg := Make(ecosystem, name, ""); pkg != nil {
|
||||||
|
return pkg.String(), pkg.WithVersion(version).String()
|
||||||
|
}
|
||||||
|
if purl.NormalizeEcosystem(ecosystem) != "swift" {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
identity, ok := swiftRegistryIdentity(name)
|
||||||
|
if !ok {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg := purl.New("generic", "swift-registry", identity, "", nil)
|
||||||
|
return pkg.String(), pkg.WithVersion(version).String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func swiftRegistryIdentity(name string) (string, bool) {
|
||||||
|
scope, packageName, found := strings.Cut(name, "/")
|
||||||
|
if !found {
|
||||||
|
scope, packageName, found = strings.Cut(name, ".")
|
||||||
|
}
|
||||||
|
if !found || scope == "" || packageName == "" || strings.ContainsAny(packageName, "/.") {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return strings.ToLower(scope) + "." + strings.ToLower(packageName), true
|
||||||
|
}
|
||||||
75
internal/packageurl/packageurl_test.go
Normal file
75
internal/packageurl/packageurl_test.go
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
package packageurl
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestMakeSwiftRegistryIdentityUnsupported(t *testing.T) {
|
||||||
|
identities := []string{"apple.swift-argument-parser", "apple/swift-argument-parser"}
|
||||||
|
for _, identity := range identities {
|
||||||
|
t.Run(identity, func(t *testing.T) {
|
||||||
|
if got := Make("swift", identity, "1.8.2"); got != nil {
|
||||||
|
t.Errorf("Make() = %q, want nil", got.String())
|
||||||
|
}
|
||||||
|
if got := MakeString("swift", identity, "1.8.2"); got != "" {
|
||||||
|
t.Errorf("MakeString() = %q, want empty string", got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakeStringSwiftSourceCoordinate(t *testing.T) {
|
||||||
|
got := MakeString("swift", "github.com/apple/swift-package-manager", "1.7.0")
|
||||||
|
want := "pkg:swift/github.com/apple/swift-package-manager@1.7.0"
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("MakeString() = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithVersionStringPreservesQualifiers(t *testing.T) {
|
||||||
|
packagePURL := "pkg:generic/swift-registry/apple.example?repository_url=https:%2F%2Fold.example%2Fswift"
|
||||||
|
got := WithVersionString(packagePURL, "1.2.3")
|
||||||
|
want := "pkg:generic/swift-registry/apple.example@1.2.3?repository_url=https:%2F%2Fold.example%2Fswift"
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("WithVersionString() = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := WithVersionString("not a purl", "1.2.3"); got != "" {
|
||||||
|
t.Errorf("WithVersionString() = %q for invalid PURL, want empty string", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakeCacheStringsSwiftRegistryIdentity(t *testing.T) {
|
||||||
|
packagePURL, versionPURL := MakeCacheStrings("swift", "APPLE/EXAMPLE", "1.2.3")
|
||||||
|
|
||||||
|
wantPackage := "pkg:generic/swift-registry/apple.example"
|
||||||
|
if packagePURL != wantPackage {
|
||||||
|
t.Errorf("package PURL = %q, want %q", packagePURL, wantPackage)
|
||||||
|
}
|
||||||
|
wantVersion := "pkg:generic/swift-registry/apple.example@1.2.3"
|
||||||
|
if versionPURL != wantVersion {
|
||||||
|
t.Errorf("version PURL = %q, want %q", versionPURL, wantVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
dottedPackage, dottedVersion := MakeCacheStrings("swift", "apple.example", "1.2.3")
|
||||||
|
if dottedPackage != packagePURL || dottedVersion != versionPURL {
|
||||||
|
t.Errorf("dotted identity cache PURLs = %q, %q; want %q, %q", dottedPackage, dottedVersion, packagePURL, versionPURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakeCacheStringsUsesSourcePURLWhenAvailable(t *testing.T) {
|
||||||
|
packagePURL, versionPURL := MakeCacheStrings("swift", "github.com/apple/swift-package-manager", "1.7.0")
|
||||||
|
|
||||||
|
if packagePURL != "pkg:swift/github.com/apple/swift-package-manager" {
|
||||||
|
t.Errorf("package PURL = %q", packagePURL)
|
||||||
|
}
|
||||||
|
if versionPURL != "pkg:swift/github.com/apple/swift-package-manager@1.7.0" {
|
||||||
|
t.Errorf("version PURL = %q", versionPURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakeStringDelegatesOtherEcosystems(t *testing.T) {
|
||||||
|
got := MakeString("npm", "@babel/core", "7.23.0")
|
||||||
|
want := "pkg:npm/%40babel/core@7.23.0"
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("MakeString() = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,7 +14,6 @@ import (
|
||||||
"github.com/git-pkgs/magic"
|
"github.com/git-pkgs/magic"
|
||||||
"github.com/git-pkgs/proxy/internal/database"
|
"github.com/git-pkgs/proxy/internal/database"
|
||||||
"github.com/git-pkgs/proxy/internal/handler"
|
"github.com/git-pkgs/proxy/internal/handler"
|
||||||
"github.com/git-pkgs/purl"
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -226,7 +225,7 @@ func (s *Server) browseList(w http.ResponseWriter, r *http.Request, ecosystem, n
|
||||||
dirPath := r.URL.Query().Get("path")
|
dirPath := r.URL.Query().Get("path")
|
||||||
|
|
||||||
// Get the artifact for this version
|
// Get the artifact for this version
|
||||||
versionPURL := purl.MakePURLString(ecosystem, name, version)
|
versionPURL := s.cachedVersionPURL(ecosystem, name, version)
|
||||||
artifacts, err := s.db.GetArtifactsByVersionPURL(versionPURL)
|
artifacts, err := s.db.GetArtifactsByVersionPURL(versionPURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
notFound(w, "version not found")
|
notFound(w, "version not found")
|
||||||
|
|
@ -313,7 +312,7 @@ func (s *Server) browseFile(w http.ResponseWriter, r *http.Request, ecosystem, n
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the artifact for this version
|
// Get the artifact for this version
|
||||||
versionPURL := purl.MakePURLString(ecosystem, name, version)
|
versionPURL := s.cachedVersionPURL(ecosystem, name, version)
|
||||||
artifacts, err := s.db.GetArtifactsByVersionPURL(versionPURL)
|
artifacts, err := s.db.GetArtifactsByVersionPURL(versionPURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
notFound(w, "version not found")
|
notFound(w, "version not found")
|
||||||
|
|
@ -534,8 +533,8 @@ type BrowseSourceData struct {
|
||||||
// @Router /ui/api/compare/{ecosystem}/{name}/{fromVersion}/{toVersion} [get]
|
// @Router /ui/api/compare/{ecosystem}/{name}/{fromVersion}/{toVersion} [get]
|
||||||
func (s *Server) compareDiff(w http.ResponseWriter, r *http.Request, ecosystem, name, fromVersion, toVersion string) {
|
func (s *Server) compareDiff(w http.ResponseWriter, r *http.Request, ecosystem, name, fromVersion, toVersion string) {
|
||||||
// Get artifacts for both versions
|
// Get artifacts for both versions
|
||||||
fromPURL := purl.MakePURLString(ecosystem, name, fromVersion)
|
fromPURL := s.cachedVersionPURL(ecosystem, name, fromVersion)
|
||||||
toPURL := purl.MakePURLString(ecosystem, name, toVersion)
|
toPURL := s.cachedVersionPURL(ecosystem, name, toVersion)
|
||||||
|
|
||||||
fromArtifacts, err := s.db.GetArtifactsByVersionPURL(fromPURL)
|
fromArtifacts, err := s.db.GetArtifactsByVersionPURL(fromPURL)
|
||||||
if err != nil || len(fromArtifacts) == 0 {
|
if err != nil || len(fromArtifacts) == 0 {
|
||||||
|
|
|
||||||
|
|
@ -430,10 +430,6 @@ func TestHandleBrowseSourcePage(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(body, "proxy test-version (test-commit)") {
|
|
||||||
t.Error("browse source footer should contain proxy build information, not the package version")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that the escapeHTML function is present for XSS protection
|
// Check that the escapeHTML function is present for XSS protection
|
||||||
if !strings.Contains(body, "function escapeHTML(str)") {
|
if !strings.Contains(body, "function escapeHTML(str)") {
|
||||||
t.Error("browse source page missing escapeHTML function for XSS protection")
|
t.Error("browse source page missing escapeHTML function for XSS protection")
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/git-pkgs/proxy/internal/database"
|
"github.com/git-pkgs/proxy/internal/database"
|
||||||
)
|
)
|
||||||
|
|
@ -140,6 +141,7 @@ func supportedEcosystems() []string {
|
||||||
"pub",
|
"pub",
|
||||||
"pypi",
|
"pypi",
|
||||||
"rpm",
|
"rpm",
|
||||||
|
"swift",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -184,6 +186,8 @@ func ecosystemBadgeClasses(ecosystem string) string {
|
||||||
return base + " bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300"
|
return base + " bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-300"
|
||||||
case "julia":
|
case "julia":
|
||||||
return base + " bg-emerald-100 text-emerald-700 dark:bg-emerald-900/50 dark:text-emerald-300"
|
return base + " bg-emerald-100 text-emerald-700 dark:bg-emerald-900/50 dark:text-emerald-300"
|
||||||
|
case "swift":
|
||||||
|
return base + " bg-orange-100 text-orange-700 dark:bg-orange-900/50 dark:text-orange-300"
|
||||||
case "oci":
|
case "oci":
|
||||||
return base + " bg-sky-100 text-sky-700 dark:bg-sky-900/50 dark:text-sky-300"
|
return base + " bg-sky-100 text-sky-700 dark:bg-sky-900/50 dark:text-sky-300"
|
||||||
case "deb":
|
case "deb":
|
||||||
|
|
@ -196,6 +200,11 @@ func ecosystemBadgeClasses(ecosystem string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRegistryConfigs(baseURL string) []RegistryConfig {
|
func getRegistryConfigs(baseURL string) []RegistryConfig {
|
||||||
|
swiftInsecureFlag := ""
|
||||||
|
if strings.HasPrefix(strings.ToLower(baseURL), "http://") {
|
||||||
|
swiftInsecureFlag = "--allow-insecure-http "
|
||||||
|
}
|
||||||
|
|
||||||
return []RegistryConfig{
|
return []RegistryConfig{
|
||||||
{
|
{
|
||||||
ID: "npm",
|
ID: "npm",
|
||||||
|
|
@ -396,6 +405,15 @@ local({
|
||||||
<p class="config-note">Or inside a running session:</p>
|
<p class="config-note">Or inside a running session:</p>
|
||||||
<pre><code>ENV["JULIA_PKG_SERVER"] = "` + baseURL + `/julia"
|
<pre><code>ENV["JULIA_PKG_SERVER"] = "` + baseURL + `/julia"
|
||||||
using Pkg; Pkg.update()</code></pre>`),
|
using Pkg; Pkg.update()</code></pre>`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "swift",
|
||||||
|
Name: "Swift Package Registry",
|
||||||
|
Language: "Swift",
|
||||||
|
Endpoint: "/swift/",
|
||||||
|
Instructions: template.HTML(`<p class="config-note">Configure SwiftPM to use the proxy for this project:</p>
|
||||||
|
<pre><code>swift package-registry set ` + swiftInsecureFlag + baseURL + `/swift</code></pre>
|
||||||
|
<p class="config-note">Use scoped package identifiers in Package.swift, for example <code>apple.swift-argument-parser</code>.</p>`),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "oci",
|
ID: "oci",
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import (
|
||||||
"github.com/git-pkgs/proxy/internal/storage"
|
"github.com/git-pkgs/proxy/internal/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupEvictionTest(t *testing.T) (*database.DB, *storage.Blob) {
|
func setupEvictionTest(t *testing.T) (*database.DB, *storage.Filesystem) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
tempDir := t.TempDir()
|
tempDir := t.TempDir()
|
||||||
|
|
@ -27,7 +27,7 @@ func setupEvictionTest(t *testing.T) (*database.DB, *storage.Blob) {
|
||||||
t.Fatalf("failed to create database: %v", err)
|
t.Fatalf("failed to create database: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
store, err := storage.OpenBucket(context.Background(), "file://"+storagePath)
|
store, err := storage.NewFilesystem(storagePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = db.Close()
|
_ = db.Close()
|
||||||
t.Fatalf("failed to create storage: %v", err)
|
t.Fatalf("failed to create storage: %v", err)
|
||||||
|
|
@ -243,7 +243,7 @@ func TestStartEvictionLoop_UnlimitedSkips(t *testing.T) {
|
||||||
}
|
}
|
||||||
defer func() { _ = db.Close() }()
|
defer func() { _ = db.Close() }()
|
||||||
|
|
||||||
store, err := storage.OpenBucket(context.Background(), "file://"+storagePath)
|
store, err := storage.NewFilesystem(storagePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create storage: %v", err)
|
t.Fatalf("failed to create storage: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -280,7 +280,7 @@ func defaultTestConfig(storagePath, dbPath string) *config.Config {
|
||||||
return &config.Config{
|
return &config.Config{
|
||||||
Listen: ":8080",
|
Listen: ":8080",
|
||||||
BaseURL: "http://localhost:8080",
|
BaseURL: "http://localhost:8080",
|
||||||
Storage: config.StorageConfig{URL: "file://" + storagePath, MaxSize: ""},
|
Storage: config.StorageConfig{Path: storagePath, MaxSize: ""},
|
||||||
Database: config.DatabaseConfig{
|
Database: config.DatabaseConfig{
|
||||||
Driver: "sqlite",
|
Driver: "sqlite",
|
||||||
Path: dbPath,
|
Path: dbPath,
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ type fakeStorage struct {
|
||||||
// Failure injection.
|
// Failure injection.
|
||||||
storeErr error
|
storeErr error
|
||||||
openErr error
|
openErr error
|
||||||
readErr error // returned by the io.ReadCloser.Read after partial bytes
|
readErr error // returned by the io.ReadCloser.Read after partial bytes
|
||||||
deleteErr error
|
deleteErr error
|
||||||
|
|
||||||
// Misbehavior knobs.
|
// Misbehavior knobs.
|
||||||
|
|
@ -132,8 +132,8 @@ func (f *fakeStorage) SignedURL(ctx context.Context, path string, expiry time.Du
|
||||||
return "", storage.ErrSignedURLUnsupported
|
return "", storage.ErrSignedURLUnsupported
|
||||||
}
|
}
|
||||||
func (f *fakeStorage) UsedSpace(ctx context.Context) (int64, error) { return 0, nil }
|
func (f *fakeStorage) UsedSpace(ctx context.Context) (int64, error) { return 0, nil }
|
||||||
func (f *fakeStorage) URL() string { return "fake://" }
|
func (f *fakeStorage) URL() string { return "fake://" }
|
||||||
func (f *fakeStorage) Close() error { return nil }
|
func (f *fakeStorage) Close() error { return nil }
|
||||||
|
|
||||||
// --- Tests follow. First test: happy path ---
|
// --- Tests follow. First test: happy path ---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,24 +2,17 @@ package server
|
||||||
|
|
||||||
import "net/http"
|
import "net/http"
|
||||||
|
|
||||||
// BuildInfo identifies the running proxy binary.
|
// Layout carries per-request fields consumed by the shared base template
|
||||||
type BuildInfo struct {
|
// (canonical URL, og:url). It is embedded in every page data struct so that
|
||||||
Version string
|
// templates can reference {{.UIBaseURL}} and {{.CanonicalPath}} alongside the
|
||||||
Commit string
|
// page's own fields.
|
||||||
}
|
|
||||||
|
|
||||||
// Layout carries shared fields consumed by the base template. It is embedded
|
|
||||||
// in every page data struct so templates can access canonical URL and build
|
|
||||||
// information alongside the page's own fields.
|
|
||||||
type Layout struct {
|
type Layout struct {
|
||||||
BuildInfo BuildInfo
|
|
||||||
UIBaseURL string
|
UIBaseURL string
|
||||||
CanonicalPath string
|
CanonicalPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) layoutFor(r *http.Request) Layout {
|
func (s *Server) layoutFor(r *http.Request) Layout {
|
||||||
return Layout{
|
return Layout{
|
||||||
BuildInfo: s.buildInfo,
|
|
||||||
UIBaseURL: s.cfg.UIBaseURL,
|
UIBaseURL: s.cfg.UIBaseURL,
|
||||||
CanonicalPath: r.URL.Path,
|
CanonicalPath: r.URL.Path,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -92,3 +92,17 @@ func requestEcosystem(path string) string {
|
||||||
return "other"
|
return "other"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ActiveRequestsMiddleware tracks the number of active requests using Prometheus metrics.
|
||||||
|
func ActiveRequestsMiddleware(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Don't track metrics endpoint itself
|
||||||
|
if r.URL.Path == "/metrics" {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implemented in server.go where metrics package is imported
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,36 @@ func TestGetRequestID(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestActiveRequestsMiddleware(t *testing.T) {
|
||||||
|
handler := ActiveRequestsMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}))
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/test", nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handler.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Errorf("expected status 200, got %d", rec.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActiveRequestsMiddleware_SkipsMetricsEndpoint(t *testing.T) {
|
||||||
|
handler := ActiveRequestsMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}))
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/metrics", nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handler.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Errorf("expected status 200, got %d", rec.Code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoggerMiddleware(t *testing.T) {
|
func TestLoggerMiddleware(t *testing.T) {
|
||||||
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||||
s := &Server{logger: logger}
|
s := &Server{logger: logger}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
// - /conda/* - Conda/Anaconda protocol
|
// - /conda/* - Conda/Anaconda protocol
|
||||||
// - /cran/* - CRAN (R) protocol
|
// - /cran/* - CRAN (R) protocol
|
||||||
// - /julia/* - Julia Pkg server protocol
|
// - /julia/* - Julia Pkg server protocol
|
||||||
|
// - /swift/* - Swift Package Registry protocol
|
||||||
// - /v2/* - OCI/Docker container registry protocol
|
// - /v2/* - OCI/Docker container registry protocol
|
||||||
// - /debian/* - Debian/APT repository protocol
|
// - /debian/* - Debian/APT repository protocol
|
||||||
// - /rpm/* - RPM/Yum repository protocol
|
// - /rpm/* - RPM/Yum repository protocol
|
||||||
|
|
@ -69,8 +70,8 @@ import (
|
||||||
upstreamhttp "github.com/git-pkgs/proxy/internal/httpclient"
|
upstreamhttp "github.com/git-pkgs/proxy/internal/httpclient"
|
||||||
"github.com/git-pkgs/proxy/internal/metrics"
|
"github.com/git-pkgs/proxy/internal/metrics"
|
||||||
"github.com/git-pkgs/proxy/internal/mirror"
|
"github.com/git-pkgs/proxy/internal/mirror"
|
||||||
|
"github.com/git-pkgs/proxy/internal/packageurl"
|
||||||
"github.com/git-pkgs/proxy/internal/storage"
|
"github.com/git-pkgs/proxy/internal/storage"
|
||||||
"github.com/git-pkgs/purl"
|
|
||||||
"github.com/git-pkgs/registries/fetch"
|
"github.com/git-pkgs/registries/fetch"
|
||||||
"github.com/git-pkgs/registries/safehttp"
|
"github.com/git-pkgs/registries/safehttp"
|
||||||
"github.com/git-pkgs/spdx"
|
"github.com/git-pkgs/spdx"
|
||||||
|
|
@ -92,7 +93,6 @@ type Server struct {
|
||||||
db *database.DB
|
db *database.DB
|
||||||
storage storage.Storage
|
storage storage.Storage
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
buildInfo BuildInfo
|
|
||||||
http *http.Server
|
http *http.Server
|
||||||
templates *Templates
|
templates *Templates
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
|
|
@ -101,7 +101,7 @@ type Server struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Server with the given configuration.
|
// New creates a new Server with the given configuration.
|
||||||
func New(cfg *config.Config, logger *slog.Logger, buildInfo BuildInfo) (*Server, error) {
|
func New(cfg *config.Config, logger *slog.Logger) (*Server, error) {
|
||||||
var activityLog *accesslog.Logger
|
var activityLog *accesslog.Logger
|
||||||
if cfg.AccessLog.Path != "" {
|
if cfg.AccessLog.Path != "" {
|
||||||
var err error
|
var err error
|
||||||
|
|
@ -170,7 +170,6 @@ func New(cfg *config.Config, logger *slog.Logger, buildInfo BuildInfo) (*Server,
|
||||||
db: db,
|
db: db,
|
||||||
storage: store,
|
storage: store,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
buildInfo: buildInfo,
|
|
||||||
templates: &Templates{},
|
templates: &Templates{},
|
||||||
healthCache: hc,
|
healthCache: hc,
|
||||||
accessLog: activityLog,
|
accessLog: activityLog,
|
||||||
|
|
@ -261,6 +260,7 @@ func (s *Server) Start() error {
|
||||||
condaHandler := handler.NewCondaHandler(proxy, s.cfg.BaseURL)
|
condaHandler := handler.NewCondaHandler(proxy, s.cfg.BaseURL)
|
||||||
cranHandler := handler.NewCRANHandler(proxy, s.cfg.BaseURL)
|
cranHandler := handler.NewCRANHandler(proxy, s.cfg.BaseURL)
|
||||||
juliaHandler := handler.NewJuliaHandler(proxy, s.cfg.BaseURL)
|
juliaHandler := handler.NewJuliaHandler(proxy, s.cfg.BaseURL)
|
||||||
|
swiftHandler := handler.NewSwiftHandler(proxy, s.cfg.BaseURL, s.cfg.Upstream.Swift)
|
||||||
containerHandler := handler.NewContainerHandler(proxy, s.cfg.BaseURL, s.cfg.Upstream.OCI)
|
containerHandler := handler.NewContainerHandler(proxy, s.cfg.BaseURL, s.cfg.Upstream.OCI)
|
||||||
helmHandler := handler.NewHelmHandler(proxy, s.cfg.BaseURL, s.cfg.Upstream.Helm)
|
helmHandler := handler.NewHelmHandler(proxy, s.cfg.BaseURL, s.cfg.Upstream.Helm)
|
||||||
debianHandler := handler.NewDebianHandler(proxy, s.cfg.BaseURL, s.cfg.Upstream.Debian)
|
debianHandler := handler.NewDebianHandler(proxy, s.cfg.BaseURL, s.cfg.Upstream.Debian)
|
||||||
|
|
@ -281,6 +281,7 @@ func (s *Server) Start() error {
|
||||||
r.Mount("/conda", http.StripPrefix("/conda", condaHandler.Routes()))
|
r.Mount("/conda", http.StripPrefix("/conda", condaHandler.Routes()))
|
||||||
r.Mount("/cran", http.StripPrefix("/cran", cranHandler.Routes()))
|
r.Mount("/cran", http.StripPrefix("/cran", cranHandler.Routes()))
|
||||||
r.Mount("/julia", http.StripPrefix("/julia", juliaHandler.Routes()))
|
r.Mount("/julia", http.StripPrefix("/julia", juliaHandler.Routes()))
|
||||||
|
r.Mount("/swift", http.StripPrefix("/swift", swiftHandler.Routes()))
|
||||||
r.Mount("/v2", http.StripPrefix("/v2", containerHandler.Routes()))
|
r.Mount("/v2", http.StripPrefix("/v2", containerHandler.Routes()))
|
||||||
r.Mount("/helm", http.StripPrefix("/helm", helmHandler.Routes()))
|
r.Mount("/helm", http.StripPrefix("/helm", helmHandler.Routes()))
|
||||||
r.Mount("/debian", http.StripPrefix("/debian", debianHandler.Routes()))
|
r.Mount("/debian", http.StripPrefix("/debian", debianHandler.Routes()))
|
||||||
|
|
@ -815,7 +816,7 @@ func (s *Server) showVersion(w http.ResponseWriter, r *http.Request, ecosystem,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
versionPURL := purl.MakePURLString(ecosystem, name, version)
|
versionPURL := packageurl.WithVersionString(pkg.PURL, version)
|
||||||
ver, err := s.db.GetVersionByPURL(versionPURL)
|
ver, err := s.db.GetVersionByPURL(versionPURL)
|
||||||
if err != nil || ver == nil {
|
if err != nil || ver == nil {
|
||||||
s.logger.Error("failed to get version", "error", err)
|
s.logger.Error("failed to get version", "error", err)
|
||||||
|
|
@ -857,6 +858,14 @@ func (s *Server) showVersion(w http.ResponseWriter, r *http.Request, ecosystem,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) cachedVersionPURL(ecosystem, name, version string) string {
|
||||||
|
pkg, err := s.db.GetPackageByEcosystemName(ecosystem, name)
|
||||||
|
if err != nil || pkg == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return packageurl.WithVersionString(pkg.PURL, version)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) showBrowseSource(w http.ResponseWriter, r *http.Request, ecosystem, name, version string) {
|
func (s *Server) showBrowseSource(w http.ResponseWriter, r *http.Request, ecosystem, name, version string) {
|
||||||
data := BrowseSourceData{
|
data := BrowseSourceData{
|
||||||
Layout: s.layoutFor(r),
|
Layout: s.layoutFor(r),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
@ -52,7 +51,7 @@ func newTestServer(t *testing.T) *testServer {
|
||||||
t.Fatalf("failed to create database: %v", err)
|
t.Fatalf("failed to create database: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
store, err := storage.OpenBucket(context.Background(), "file://"+storagePath)
|
store, err := storage.NewFilesystem(storagePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = db.Close()
|
_ = db.Close()
|
||||||
_ = os.RemoveAll(tempDir)
|
_ = os.RemoveAll(tempDir)
|
||||||
|
|
@ -66,7 +65,7 @@ func newTestServer(t *testing.T) *testServer {
|
||||||
|
|
||||||
cfg := &config.Config{
|
cfg := &config.Config{
|
||||||
BaseURL: "http://localhost:8080",
|
BaseURL: "http://localhost:8080",
|
||||||
Storage: config.StorageConfig{URL: "file://" + storagePath},
|
Storage: config.StorageConfig{Path: storagePath},
|
||||||
Database: config.DatabaseConfig{Path: dbPath},
|
Database: config.DatabaseConfig{Path: dbPath},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,6 +83,7 @@ func newTestServer(t *testing.T) *testServer {
|
||||||
goHandler := handler.NewGoHandler(proxy, cfg.BaseURL)
|
goHandler := handler.NewGoHandler(proxy, cfg.BaseURL)
|
||||||
pypiHandler := handler.NewPyPIHandler(proxy, cfg.BaseURL)
|
pypiHandler := handler.NewPyPIHandler(proxy, cfg.BaseURL)
|
||||||
gradleHandler := handler.NewGradleBuildCacheHandler(proxy)
|
gradleHandler := handler.NewGradleBuildCacheHandler(proxy)
|
||||||
|
swiftHandler := handler.NewSwiftHandler(proxy, cfg.BaseURL, cfg.Upstream.Swift)
|
||||||
|
|
||||||
r.Mount("/npm", http.StripPrefix("/npm", npmHandler.Routes()))
|
r.Mount("/npm", http.StripPrefix("/npm", npmHandler.Routes()))
|
||||||
r.Mount("/cargo", http.StripPrefix("/cargo", cargoHandler.Routes()))
|
r.Mount("/cargo", http.StripPrefix("/cargo", cargoHandler.Routes()))
|
||||||
|
|
@ -91,6 +91,7 @@ func newTestServer(t *testing.T) *testServer {
|
||||||
r.Mount("/go", http.StripPrefix("/go", goHandler.Routes()))
|
r.Mount("/go", http.StripPrefix("/go", goHandler.Routes()))
|
||||||
r.Mount("/pypi", http.StripPrefix("/pypi", pypiHandler.Routes()))
|
r.Mount("/pypi", http.StripPrefix("/pypi", pypiHandler.Routes()))
|
||||||
r.Mount("/gradle", http.StripPrefix("/gradle", gradleHandler.Routes()))
|
r.Mount("/gradle", http.StripPrefix("/gradle", gradleHandler.Routes()))
|
||||||
|
r.Mount("/swift", http.StripPrefix("/swift", swiftHandler.Routes()))
|
||||||
|
|
||||||
hc, err := newHealthCache(store, "30s", logger)
|
hc, err := newHealthCache(store, "30s", logger)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -105,7 +106,6 @@ func newTestServer(t *testing.T) *testServer {
|
||||||
db: db,
|
db: db,
|
||||||
storage: store,
|
storage: store,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
buildInfo: BuildInfo{Version: "test-version", Commit: "test-commit"},
|
|
||||||
templates: &Templates{},
|
templates: &Templates{},
|
||||||
healthCache: hc,
|
healthCache: hc,
|
||||||
}
|
}
|
||||||
|
|
@ -315,9 +315,6 @@ func TestDashboard(t *testing.T) {
|
||||||
if !strings.Contains(body, "Cached Artifacts") {
|
if !strings.Contains(body, "Cached Artifacts") {
|
||||||
t.Error("dashboard should contain stats")
|
t.Error("dashboard should contain stats")
|
||||||
}
|
}
|
||||||
if !strings.Contains(body, "proxy test-version (test-commit)") {
|
|
||||||
t.Error("dashboard footer should contain build information")
|
|
||||||
}
|
|
||||||
if !strings.Contains(body, "Popular Packages") {
|
if !strings.Contains(body, "Popular Packages") {
|
||||||
t.Error("dashboard should contain popular packages section")
|
t.Error("dashboard should contain popular packages section")
|
||||||
}
|
}
|
||||||
|
|
@ -333,11 +330,53 @@ func TestDashboard(t *testing.T) {
|
||||||
if !strings.Contains(body, ">debian<") {
|
if !strings.Contains(body, ">debian<") {
|
||||||
t.Error("dashboard should show debian in supported ecosystems")
|
t.Error("dashboard should show debian in supported ecosystems")
|
||||||
}
|
}
|
||||||
|
if !strings.Contains(body, ">swift<") {
|
||||||
|
t.Error("dashboard should show swift in supported ecosystems")
|
||||||
|
}
|
||||||
if !strings.Contains(body, "/openapi.json") {
|
if !strings.Contains(body, "/openapi.json") {
|
||||||
t.Error("page should link to the OpenAPI JSON spec")
|
t.Error("page should link to the OpenAPI JSON spec")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSwiftHandlerMounted(t *testing.T) {
|
||||||
|
ts := newTestServer(t)
|
||||||
|
defer ts.close()
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodPut, "/swift/apple/example/1.2.3", strings.NewReader("ignored"))
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
ts.handler.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
if w.Code != http.StatusMethodNotAllowed {
|
||||||
|
t.Fatalf("status = %d, want 405; body: %s", w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSwiftCachedVersionPURLUsesStoredPackagePURL(t *testing.T) {
|
||||||
|
ts := newTestServer(t)
|
||||||
|
defer ts.close()
|
||||||
|
|
||||||
|
packagePURL := "pkg:generic/swift-registry/apple.example?repository_url=https:%2F%2Fold.example%2Fswift"
|
||||||
|
if err := ts.db.UpsertPackage(&database.Package{
|
||||||
|
PURL: packagePURL,
|
||||||
|
Ecosystem: "swift",
|
||||||
|
Name: "apple/example",
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("failed to upsert package: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &Server{
|
||||||
|
cfg: &config.Config{
|
||||||
|
Upstream: config.UpstreamConfig{Swift: "https://new.example/swift"},
|
||||||
|
},
|
||||||
|
db: ts.db,
|
||||||
|
}
|
||||||
|
got := s.cachedVersionPURL("swift", "apple/example", "1.2.3")
|
||||||
|
want := "pkg:generic/swift-registry/apple.example@1.2.3?repository_url=https:%2F%2Fold.example%2Fswift"
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("cachedVersionPURL() = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func min(a, b int) int {
|
func min(a, b int) int {
|
||||||
if a < b {
|
if a < b {
|
||||||
return a
|
return a
|
||||||
|
|
@ -603,9 +642,6 @@ func TestVersionShowWithHitCount(t *testing.T) {
|
||||||
if !strings.Contains(body, "42 cache hits") {
|
if !strings.Contains(body, "42 cache hits") {
|
||||||
t.Error("expected page to show hit count")
|
t.Error("expected page to show hit count")
|
||||||
}
|
}
|
||||||
if !strings.Contains(body, "proxy test-version (test-commit)") {
|
|
||||||
t.Error("version show footer should contain proxy build information, not the package version")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSearchWithNullValues(t *testing.T) {
|
func TestSearchWithNullValues(t *testing.T) {
|
||||||
|
|
@ -1335,14 +1371,10 @@ func TestNewServer_StorageConnectivityCheck(t *testing.T) {
|
||||||
|
|
||||||
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||||
|
|
||||||
buildInfo := BuildInfo{Version: "test-version", Commit: "test-commit"}
|
srv, err := New(cfg, logger)
|
||||||
srv, err := New(cfg, logger, buildInfo)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("New() failed: %v", err)
|
t.Fatalf("New() failed: %v", err)
|
||||||
}
|
}
|
||||||
if srv.buildInfo != buildInfo {
|
|
||||||
t.Errorf("build info = %#v, want %#v", srv.buildInfo, buildInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
// On Windows, OpenBucket normalises to file:///C:/path; on Unix the
|
// On Windows, OpenBucket normalises to file:///C:/path; on Unix the
|
||||||
// absolute path already starts with /, so file:// + /path == file:///path.
|
// absolute path already starts with /, so file:// + /path == file:///path.
|
||||||
|
|
@ -1360,13 +1392,13 @@ func TestNewServer_InvalidAccessLogFailsBeforeDatabaseInit(t *testing.T) {
|
||||||
tempDir := t.TempDir()
|
tempDir := t.TempDir()
|
||||||
dbPath := filepath.Join(tempDir, "test.db")
|
dbPath := filepath.Join(tempDir, "test.db")
|
||||||
cfg := &config.Config{
|
cfg := &config.Config{
|
||||||
Storage: config.StorageConfig{URL: "file://" + filepath.Join(tempDir, "artifacts")},
|
Storage: config.StorageConfig{Path: filepath.Join(tempDir, "artifacts")},
|
||||||
Database: config.DatabaseConfig{Path: dbPath},
|
Database: config.DatabaseConfig{Path: dbPath},
|
||||||
AccessLog: config.AccessLogConfig{Path: filepath.Join(tempDir, "missing", "access.jsonl")},
|
AccessLog: config.AccessLogConfig{Path: filepath.Join(tempDir, "missing", "access.jsonl")},
|
||||||
}
|
}
|
||||||
|
|
||||||
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||||
if _, err := New(cfg, logger, BuildInfo{}); err == nil {
|
if _, err := New(cfg, logger); err == nil {
|
||||||
t.Fatal("New() succeeded with invalid access log path")
|
t.Fatal("New() succeeded with invalid access log path")
|
||||||
} else if !strings.Contains(err.Error(), "initializing access log") {
|
} else if !strings.Contains(err.Error(), "initializing access log") {
|
||||||
t.Fatalf("New() error = %v, want access log initialization error", err)
|
t.Fatalf("New() error = %v, want access log initialization error", err)
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,6 @@
|
||||||
<i data-lucide="github" class="w-4 h-4"></i><span>github.com/git-pkgs/proxy</span>
|
<i data-lucide="github" class="w-4 h-4"></i><span>github.com/git-pkgs/proxy</span>
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
{{if .BuildInfo.Version}}
|
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-500 mt-2">
|
|
||||||
proxy {{.BuildInfo.Version}}{{if .BuildInfo.Commit}} ({{.BuildInfo.Commit}}){{end}}
|
|
||||||
</p>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-3">Resources</h3>
|
<h3 class="text-sm font-semibold text-gray-900 dark:text-gray-100 mb-3">Resources</h3>
|
||||||
|
|
|
||||||
|
|
@ -186,64 +186,6 @@ func TestRenderEmitsCanonicalAndOG(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFooterUsesBuildInfoWhenPageDefinesVersion(t *testing.T) {
|
|
||||||
templates := &Templates{}
|
|
||||||
buildInfo := BuildInfo{Version: "proxy-build-1.2.3", Commit: "abc123def"}
|
|
||||||
wantFooter := "proxy proxy-build-1.2.3 (abc123def)"
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
page string
|
|
||||||
data any
|
|
||||||
shadow string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "version show page",
|
|
||||||
page: "version_show",
|
|
||||||
data: VersionShowData{
|
|
||||||
Layout: Layout{BuildInfo: buildInfo},
|
|
||||||
Package: &database.Package{
|
|
||||||
PURL: "pkg:npm/lodash",
|
|
||||||
Ecosystem: "npm",
|
|
||||||
Name: "lodash",
|
|
||||||
},
|
|
||||||
Version: &database.Version{
|
|
||||||
PURL: "pkg:npm/lodash@9.9.9",
|
|
||||||
PackagePURL: "pkg:npm/lodash",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
shadow: "9.9.9",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "browse source page",
|
|
||||||
page: "browse_source",
|
|
||||||
data: BrowseSourceData{
|
|
||||||
Layout: Layout{BuildInfo: buildInfo},
|
|
||||||
Ecosystem: "npm",
|
|
||||||
PackageName: "lodash",
|
|
||||||
Version: "9.9.9",
|
|
||||||
},
|
|
||||||
shadow: "9.9.9",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
w := httptest.NewRecorder()
|
|
||||||
if err := templates.Render(w, tt.page, tt.data); err != nil {
|
|
||||||
t.Fatalf("Render(%q) failed: %v", tt.page, err)
|
|
||||||
}
|
|
||||||
body := w.Body.String()
|
|
||||||
if !strings.Contains(body, wantFooter) {
|
|
||||||
t.Errorf("footer missing build info %q", wantFooter)
|
|
||||||
}
|
|
||||||
if strings.Contains(body, "proxy "+tt.shadow) {
|
|
||||||
t.Errorf("footer used page Version %q instead of BuildInfo", tt.shadow)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRenderOmitsCanonicalWhenUIBaseURLUnset(t *testing.T) {
|
func TestRenderOmitsCanonicalWhenUIBaseURLUnset(t *testing.T) {
|
||||||
templates := &Templates{}
|
templates := &Templates{}
|
||||||
|
|
||||||
|
|
@ -519,6 +461,20 @@ func TestEcosystemBadgeLabel(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSwiftRegistryInstructionsAllowLocalHTTP(t *testing.T) {
|
||||||
|
registries := getRegistryConfigs("http://localhost:8080")
|
||||||
|
for _, registry := range registries {
|
||||||
|
if registry.ID != "swift" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(registry.Instructions), "--allow-insecure-http") {
|
||||||
|
t.Error("Swift HTTP instructions do not allow the insecure local registry")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Fatal("Swift registry instructions not found")
|
||||||
|
}
|
||||||
|
|
||||||
func TestEcosystemBadgeClasses(t *testing.T) {
|
func TestEcosystemBadgeClasses(t *testing.T) {
|
||||||
// Every supported ecosystem should return a non-empty class string
|
// Every supported ecosystem should return a non-empty class string
|
||||||
ecosystems := supportedEcosystems()
|
ecosystems := supportedEcosystems()
|
||||||
|
|
|
||||||
277
internal/storage/filesystem.go
Normal file
277
internal/storage/filesystem.go
Normal file
|
|
@ -0,0 +1,277 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
fsys "io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Filesystem implements Storage using the local filesystem.
|
||||||
|
type Filesystem struct {
|
||||||
|
root string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFilesystem creates a new filesystem storage rooted at the given directory.
|
||||||
|
// The directory will be created if it does not exist.
|
||||||
|
func NewFilesystem(root string) (*Filesystem, error) {
|
||||||
|
absRoot, err := filepath.Abs(root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("resolving root path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(absRoot, dirPermissions); err != nil {
|
||||||
|
return nil, fmt.Errorf("creating root directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Filesystem{root: absRoot}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *Filesystem) fullPath(path string) (string, error) {
|
||||||
|
localPath, err := cleanStoragePath(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return filepath.Join(fs.root, localPath), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *Filesystem) prefixPath(prefix string) (string, error) {
|
||||||
|
if prefix == "" {
|
||||||
|
return fs.root, nil
|
||||||
|
}
|
||||||
|
return fs.fullPath(prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanStoragePath(path string) (string, error) {
|
||||||
|
if path == "." || strings.Contains(path, `\`) || !filepath.IsLocal(path) {
|
||||||
|
return "", fmt.Errorf("%w: invalid storage path", ErrNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
localPath, err := filepath.Localize(path)
|
||||||
|
if err != nil || localPath == "." || !filepath.IsLocal(localPath) {
|
||||||
|
return "", fmt.Errorf("%w: invalid storage path", ErrNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
return localPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *Filesystem) Store(ctx context.Context, path string, r io.Reader) (int64, string, error) {
|
||||||
|
fullPath, err := fs.fullPath(path)
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
dir := filepath.Dir(fullPath)
|
||||||
|
if err := os.MkdirAll(dir, dirPermissions); err != nil {
|
||||||
|
return 0, "", fmt.Errorf("creating directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to temp file first for atomic operation
|
||||||
|
tmpFile, err := os.CreateTemp(dir, ".tmp-*")
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", fmt.Errorf("creating temp file: %w", err)
|
||||||
|
}
|
||||||
|
tmpPath := tmpFile.Name()
|
||||||
|
|
||||||
|
// Clean up temp file on error
|
||||||
|
success := false
|
||||||
|
defer func() {
|
||||||
|
if !success {
|
||||||
|
_ = tmpFile.Close()
|
||||||
|
_ = os.Remove(tmpPath)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Write content and compute hash
|
||||||
|
h := sha256.New()
|
||||||
|
w := io.MultiWriter(tmpFile, h)
|
||||||
|
|
||||||
|
size, err := io.Copy(w, r)
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", fmt.Errorf("writing content: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := tmpFile.Close(); err != nil {
|
||||||
|
return 0, "", fmt.Errorf("closing temp file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Atomic rename
|
||||||
|
if err := os.Rename(tmpPath, fullPath); err != nil {
|
||||||
|
return 0, "", fmt.Errorf("renaming temp file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
success = true
|
||||||
|
hash := hex.EncodeToString(h.Sum(nil))
|
||||||
|
return size, hash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *Filesystem) Open(ctx context.Context, path string) (io.ReadCloser, error) {
|
||||||
|
fullPath, err := fs.fullPath(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Open(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("opening file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *Filesystem) Exists(ctx context.Context, path string) (bool, error) {
|
||||||
|
fullPath, err := fs.fullPath(path)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = os.Stat(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return false, fmt.Errorf("checking file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *Filesystem) Delete(ctx context.Context, path string) error {
|
||||||
|
fullPath, err := fs.fullPath(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Remove(fullPath)
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("removing file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to clean up empty parent directories
|
||||||
|
dir := filepath.Dir(fullPath)
|
||||||
|
for dir != fs.root {
|
||||||
|
if err := os.Remove(dir); err != nil {
|
||||||
|
break // Directory not empty or other error
|
||||||
|
}
|
||||||
|
dir = filepath.Dir(dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *Filesystem) SignedURL(_ context.Context, _ string, _ time.Duration) (string, error) {
|
||||||
|
return "", ErrSignedURLUnsupported
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *Filesystem) Size(ctx context.Context, path string) (int64, error) {
|
||||||
|
fullPath, err := fs.fullPath(path)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := os.Stat(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return 0, ErrNotFound
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("stat file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return info.Size(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *Filesystem) UsedSpace(ctx context.Context) (int64, error) {
|
||||||
|
var total int64
|
||||||
|
|
||||||
|
err := filepath.Walk(fs.root, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !info.IsDir() {
|
||||||
|
total += info.Size()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("walking directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return total, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListPrefix returns object metadata for paths under a prefix.
|
||||||
|
func (fs *Filesystem) ListPrefix(ctx context.Context, prefix string) ([]ObjectInfo, error) {
|
||||||
|
searchRoot, err := fs.prefixPath(prefix)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(searchRoot); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return []ObjectInfo{}, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("stat prefix: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
objects := make([]ObjectInfo, 0)
|
||||||
|
err = filepath.WalkDir(searchRoot, func(path string, entry fsys.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if entry.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := entry.Info()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
relPath, err := filepath.Rel(fs.root, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
objects = append(objects, ObjectInfo{
|
||||||
|
Path: filepath.ToSlash(relPath),
|
||||||
|
Size: info.Size(),
|
||||||
|
ModTime: info.ModTime(),
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("walking prefix: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return objects, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Root returns the root directory of the storage.
|
||||||
|
func (fs *Filesystem) Root() string {
|
||||||
|
return fs.root
|
||||||
|
}
|
||||||
|
|
||||||
|
// FullPath returns the full filesystem path for a storage path.
|
||||||
|
// Useful for serving files directly or debugging.
|
||||||
|
// Returns an error if the resulting path would escape the storage root.
|
||||||
|
func (fs *Filesystem) FullPath(path string) (string, error) {
|
||||||
|
return fs.fullPath(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *Filesystem) URL() string {
|
||||||
|
return "file://" + filepath.ToSlash(fs.root)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *Filesystem) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
331
internal/storage/filesystem_test.go
Normal file
331
internal/storage/filesystem_test.go
Normal file
|
|
@ -0,0 +1,331 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewFilesystem(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
root := filepath.Join(dir, "cache")
|
||||||
|
|
||||||
|
fs, err := NewFilesystem(root)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewFilesystem failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(root); err != nil {
|
||||||
|
t.Errorf("root directory not created: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fs.Root() != root {
|
||||||
|
t.Errorf("Root() = %q, want %q", fs.Root(), root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilesystemStore(t *testing.T) {
|
||||||
|
fs := createTestFilesystem(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
content := "test content for storage"
|
||||||
|
|
||||||
|
size, hash, err := fs.Store(ctx, "npm/lodash/4.17.21/lodash.tgz", strings.NewReader(content))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Store failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if size != int64(len(content)) {
|
||||||
|
t.Errorf("size = %d, want %d", size, len(content))
|
||||||
|
}
|
||||||
|
|
||||||
|
h := sha256.Sum256([]byte(content))
|
||||||
|
wantHash := hex.EncodeToString(h[:])
|
||||||
|
if hash != wantHash {
|
||||||
|
t.Errorf("hash = %s, want %s", hash, wantHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify file exists on disk
|
||||||
|
fullPath, err := fs.FullPath("npm/lodash/4.17.21/lodash.tgz")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("FullPath failed: %v", err)
|
||||||
|
}
|
||||||
|
data, err := os.ReadFile(fullPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("reading stored file: %v", err)
|
||||||
|
}
|
||||||
|
if string(data) != content {
|
||||||
|
t.Errorf("stored content = %q, want %q", string(data), content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilesystemStoreAtomic(t *testing.T) {
|
||||||
|
fs := createTestFilesystem(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Store initial content
|
||||||
|
_, _, err := fs.Store(ctx, "test/file.txt", strings.NewReader("initial"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("initial Store failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overwrite with new content
|
||||||
|
_, _, err = fs.Store(ctx, "test/file.txt", strings.NewReader("updated"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("update Store failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify updated content
|
||||||
|
r, err := fs.Open(ctx, "test/file.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Open failed: %v", err)
|
||||||
|
}
|
||||||
|
defer func() { _ = r.Close() }()
|
||||||
|
|
||||||
|
data, _ := io.ReadAll(r)
|
||||||
|
if string(data) != "updated" {
|
||||||
|
t.Errorf("content = %q, want %q", string(data), "updated")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilesystemOpen(t *testing.T) {
|
||||||
|
fs := createTestFilesystem(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
content := "readable content"
|
||||||
|
|
||||||
|
_, _, _ = fs.Store(ctx, "test/read.txt", strings.NewReader(content))
|
||||||
|
|
||||||
|
r, err := fs.Open(ctx, "test/read.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Open failed: %v", err)
|
||||||
|
}
|
||||||
|
defer func() { _ = r.Close() }()
|
||||||
|
|
||||||
|
data, err := io.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadAll failed: %v", err)
|
||||||
|
}
|
||||||
|
if string(data) != content {
|
||||||
|
t.Errorf("content = %q, want %q", string(data), content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilesystemOpenNotFound(t *testing.T) {
|
||||||
|
fs := createTestFilesystem(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
_, err := fs.Open(ctx, "does/not/exist.txt")
|
||||||
|
if !errors.Is(err, ErrNotFound) {
|
||||||
|
t.Errorf("Open non-existent = %v, want ErrNotFound", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilesystemExists(t *testing.T) {
|
||||||
|
fs := createTestFilesystem(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
exists, err := fs.Exists(ctx, "test/exists.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Exists failed: %v", err)
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
t.Error("Exists returned true for non-existent file")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, _ = fs.Store(ctx, "test/exists.txt", strings.NewReader("content"))
|
||||||
|
|
||||||
|
exists, err = fs.Exists(ctx, "test/exists.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Exists after store failed: %v", err)
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
t.Error("Exists returned false for existing file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilesystemDelete(t *testing.T) {
|
||||||
|
fs := createTestFilesystem(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
_, _, _ = fs.Store(ctx, "test/delete/nested/file.txt", strings.NewReader("content"))
|
||||||
|
|
||||||
|
err := fs.Delete(ctx, "test/delete/nested/file.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Delete failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
exists, _ := fs.Exists(ctx, "test/delete/nested/file.txt")
|
||||||
|
if exists {
|
||||||
|
t.Error("file still exists after delete")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty parent directories should be cleaned up
|
||||||
|
nestedDir, err := fs.FullPath("test/delete/nested")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("FullPath failed: %v", err)
|
||||||
|
}
|
||||||
|
if _, err := os.Stat(nestedDir); !os.IsNotExist(err) {
|
||||||
|
t.Error("empty nested directory not cleaned up")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilesystemDeleteNotFound(t *testing.T) {
|
||||||
|
fs := createTestFilesystem(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Delete non-existent file should not error
|
||||||
|
err := fs.Delete(ctx, "does/not/exist.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Delete non-existent = %v, want nil", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilesystemSize(t *testing.T) {
|
||||||
|
fs := createTestFilesystem(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
content := "size test content"
|
||||||
|
|
||||||
|
_, _, _ = fs.Store(ctx, "test/size.txt", strings.NewReader(content))
|
||||||
|
|
||||||
|
size, err := fs.Size(ctx, "test/size.txt")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Size failed: %v", err)
|
||||||
|
}
|
||||||
|
if size != int64(len(content)) {
|
||||||
|
t.Errorf("Size = %d, want %d", size, len(content))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilesystemSizeNotFound(t *testing.T) {
|
||||||
|
fs := createTestFilesystem(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
_, err := fs.Size(ctx, "does/not/exist.txt")
|
||||||
|
if !errors.Is(err, ErrNotFound) {
|
||||||
|
t.Errorf("Size non-existent = %v, want ErrNotFound", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilesystemUsedSpace(t *testing.T) {
|
||||||
|
fs := createTestFilesystem(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Empty storage
|
||||||
|
used, err := fs.UsedSpace(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("UsedSpace failed: %v", err)
|
||||||
|
}
|
||||||
|
if used != 0 {
|
||||||
|
t.Errorf("UsedSpace empty = %d, want 0", used)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add some files
|
||||||
|
_, _, _ = fs.Store(ctx, "a.txt", strings.NewReader("aaaa")) // 4 bytes
|
||||||
|
_, _, _ = fs.Store(ctx, "b.txt", strings.NewReader("bbbbbb")) // 6 bytes
|
||||||
|
_, _, _ = fs.Store(ctx, "c/d.txt", strings.NewReader("ccccc")) // 5 bytes
|
||||||
|
|
||||||
|
used, err = fs.UsedSpace(ctx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("UsedSpace failed: %v", err)
|
||||||
|
}
|
||||||
|
if used != 15 {
|
||||||
|
t.Errorf("UsedSpace = %d, want 15", used)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilesystemLargeFile(t *testing.T) {
|
||||||
|
assertLargeFileRoundTrip(t, createTestFilesystem(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilesystemRejectsInvalidPaths(t *testing.T) {
|
||||||
|
tmp := t.TempDir()
|
||||||
|
fs, err := NewFilesystem(tmp)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, p := range []string{
|
||||||
|
"",
|
||||||
|
".",
|
||||||
|
"../etc/passwd",
|
||||||
|
"../../etc/passwd",
|
||||||
|
"a/../../etc/passwd",
|
||||||
|
"/etc/passwd",
|
||||||
|
"test//file.txt",
|
||||||
|
"test/./file.txt",
|
||||||
|
"test/../file.txt",
|
||||||
|
`test\..\file.txt`,
|
||||||
|
} {
|
||||||
|
name := p
|
||||||
|
if name == "" {
|
||||||
|
name = "empty"
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
if _, err := fs.FullPath(p); !errors.Is(err, ErrNotFound) {
|
||||||
|
t.Errorf("FullPath(%q) = %v, want ErrNotFound", p, err)
|
||||||
|
}
|
||||||
|
if _, err := fs.Open(ctx, p); err == nil {
|
||||||
|
t.Errorf("Open(%q) should reject invalid path", p)
|
||||||
|
}
|
||||||
|
if _, _, err := fs.Store(ctx, p, strings.NewReader("x")); err == nil {
|
||||||
|
t.Errorf("Store(%q) should reject invalid path", p)
|
||||||
|
}
|
||||||
|
if _, err := fs.Exists(ctx, p); err == nil {
|
||||||
|
t.Errorf("Exists(%q) should reject invalid path", p)
|
||||||
|
}
|
||||||
|
if err := fs.Delete(ctx, p); err == nil {
|
||||||
|
t.Errorf("Delete(%q) should reject invalid path", p)
|
||||||
|
}
|
||||||
|
if _, err := fs.Size(ctx, p); err == nil {
|
||||||
|
t.Errorf("Size(%q) should reject invalid path", p)
|
||||||
|
}
|
||||||
|
if _, err := fs.ListPrefix(ctx, p); p != "" && err == nil {
|
||||||
|
t.Errorf("ListPrefix(%q) should reject invalid path", p)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilesystemListPrefixAllowsEmptyPrefix(t *testing.T) {
|
||||||
|
fs := createTestFilesystem(t)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
_, _, _ = fs.Store(ctx, "a.txt", strings.NewReader("aaaa"))
|
||||||
|
_, _, _ = fs.Store(ctx, "c/d.txt", strings.NewReader("ccccc"))
|
||||||
|
|
||||||
|
objects, err := fs.ListPrefix(ctx, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ListPrefix empty prefix failed: %v", err)
|
||||||
|
}
|
||||||
|
if len(objects) != 2 {
|
||||||
|
t.Fatalf("ListPrefix empty prefix returned %d objects, want 2", len(objects))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilesystemSignedURLUnsupported(t *testing.T) {
|
||||||
|
fs := createTestFilesystem(t)
|
||||||
|
|
||||||
|
_, err := fs.SignedURL(context.Background(), "test/file.txt", time.Minute)
|
||||||
|
if !errors.Is(err, ErrSignedURLUnsupported) {
|
||||||
|
t.Errorf("SignedURL = %v, want ErrSignedURLUnsupported", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTestFilesystem(t *testing.T) *Filesystem {
|
||||||
|
t.Helper()
|
||||||
|
dir := t.TempDir()
|
||||||
|
|
||||||
|
fs, err := NewFilesystem(dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewFilesystem failed: %v", err)
|
||||||
|
}
|
||||||
|
return fs
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,8 @@ package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -78,3 +80,42 @@ func ArtifactPath(ecosystem, namespace, name, version, filename string) string {
|
||||||
}
|
}
|
||||||
return ecosystem + "/" + name + "/" + version + "/" + filename
|
return ecosystem + "/" + name + "/" + version + "/" + filename
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HashingReader wraps a reader and computes SHA256 hash as content is read.
|
||||||
|
type HashingReader struct {
|
||||||
|
r io.Reader
|
||||||
|
hash []byte
|
||||||
|
h interface{ Sum([]byte) []byte }
|
||||||
|
size int64
|
||||||
|
done bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHashingReader(r io.Reader) *HashingReader {
|
||||||
|
h := sha256.New()
|
||||||
|
return &HashingReader{
|
||||||
|
r: io.TeeReader(r, h),
|
||||||
|
h: h,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hr *HashingReader) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = hr.r.Read(p)
|
||||||
|
hr.size += int64(n)
|
||||||
|
if err == io.EOF {
|
||||||
|
hr.done = true
|
||||||
|
hr.hash = hr.h.Sum(nil)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hr *HashingReader) Sum() string {
|
||||||
|
if !hr.done {
|
||||||
|
hr.hash = hr.h.Sum(nil)
|
||||||
|
hr.done = true
|
||||||
|
}
|
||||||
|
return hex.EncodeToString(hr.hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hr *HashingReader) Size() int64 {
|
||||||
|
return hr.size
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -34,6 +35,30 @@ func TestArtifactPath(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHashingReader(t *testing.T) {
|
||||||
|
content := "hello world"
|
||||||
|
r := NewHashingReader(strings.NewReader(content))
|
||||||
|
|
||||||
|
data, err := io.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ReadAll failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(data) != content {
|
||||||
|
t.Errorf("got content %q, want %q", string(data), content)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Size() != int64(len(content)) {
|
||||||
|
t.Errorf("got size %d, want %d", r.Size(), len(content))
|
||||||
|
}
|
||||||
|
|
||||||
|
h := sha256.Sum256([]byte(content))
|
||||||
|
wantHash := hex.EncodeToString(h[:])
|
||||||
|
if r.Sum() != wantHash {
|
||||||
|
t.Errorf("got hash %s, want %s", r.Sum(), wantHash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// assertLargeFileRoundTrip stores a 1MB file in the given storage, verifies size and
|
// assertLargeFileRoundTrip stores a 1MB file in the given storage, verifies size and
|
||||||
// hash, then reads it back and confirms the content matches.
|
// hash, then reads it back and confirms the content matches.
|
||||||
func assertLargeFileRoundTrip(t *testing.T, s Storage) {
|
func assertLargeFileRoundTrip(t *testing.T, s Storage) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue