6e6481e09a
Parallel package-type PRs conflict because every type edits the same shared lines (README tables, provider.go, server.go imports, the package type list, UI maps, the e2e caching table). A new type now only adds files.
- move per-type README sections into docs/<type>/{remote,local,virtual}.md
- split provider.go capability and metadata types into cap_*.go and metadata_*.go
- import providers via internal/provider/all, one file per type
- declare each package type in package_type_<type>.go, registered via init()
- split the e2e caching cases and UI colours/snippets into per-type files
Reviewed-on: #150
Co-authored-by: unkin-agent <unkin-agent@unkin.net>
Co-committed-by: unkin-agent <unkin-agent@unkin.net>
101 lines
5.1 KiB
Markdown
101 lines
5.1 KiB
Markdown
# Caching and classification (all remotes)
|
|
|
|
Every proxied request on a remote is classified as **denied**, **immutable** or
|
|
**mutable**. The class picks the storage path and the freshness TTL.
|
|
|
|
## Fields
|
|
|
|
| Field | Meaning |
|
|
|---|---|
|
|
| `patterns` | Allowlist of regexes. Empty allows every path; otherwise a non-matching path is denied (403). |
|
|
| `blocklist` | Regexes; a match is denied (403). |
|
|
| `immutable_patterns` | Regexes forcing a path immutable. |
|
|
| `mutable_patterns` | Regexes forcing a path mutable. |
|
|
| `immutable_ttl` | Seconds an immutable path stays fresh. `0` = forever. |
|
|
| `mutable_ttl` | Seconds a mutable path stays fresh. `0` also means never expires, and the API stores `0` when the field is omitted. |
|
|
| `check_mutable` | Stores the upstream ETag and tries a conditional `HEAD` (`If-None-Match`) when a mutable path is fetched. The ETag expires with the `mutable_ttl` freshness key, so an expired mutable path is refetched in full, not revalidated. |
|
|
| `stale_on_error` | On an upstream network error, serve the stored copy instead of failing. |
|
|
| `quarantine_enabled` / `quarantine_days` | Stored on the remote but not enforced by the proxy yet. |
|
|
| `mirrorlist` / `mirror_strategy` | rpm/deb/alpine only. Upstream pool is `[base_url] + mirrorlist`; `round_robin` (default) rotates the starting mirror, `least_conn` starts at the one with the fewest in-flight requests. Network errors and 5xx fail over to the next mirror; other statuses return as-is. |
|
|
|
|
## Precedence
|
|
|
|
The first rule that matches wins:
|
|
|
|
1. `blocklist` matches: **denied**
|
|
2. `patterns` is non-empty and nothing matches: **denied**
|
|
3. `immutable_patterns` matches: **immutable**
|
|
4. `mutable_patterns` matches: **mutable**
|
|
5. the provider's built-in `Classify` says mutable: **mutable**
|
|
6. anything else: **immutable**
|
|
|
|
So `blocklist` beats the allowlist, and `immutable_patterns` beats
|
|
`mutable_patterns`. Either user list overrides the built-in rules. `GET` and
|
|
`HEAD` both apply this order.
|
|
|
|
## Matching rules
|
|
|
|
- Regexes are Go `regexp`, matched **unanchored** (`MatchString`). Add `^` or `$` yourself when you need them.
|
|
- They match the path after `/api/v1/remote/<name>/`, without a leading slash, e.g. `9/BaseOS/x86_64/os/repodata/repomd.xml`.
|
|
- The query string is not part of the path. It is dropped and not forwarded upstream.
|
|
- `create` and `update` reject an invalid regex in `patterns`, `blocklist`, `mutable_patterns`, `immutable_patterns` or `ban_tags` with a 400. Only invalid regexes already stored before that check are skipped at match time.
|
|
|
|
## Lifecycles
|
|
|
|
| | Immutable | Mutable |
|
|
|---|---|---|
|
|
| Stored as | content-addressed blob (sha256), streamed | per-path index object, buffered and rewritten by the provider |
|
|
| Fresh for | `immutable_ttl` (`0` = forever) | `mutable_ttl` |
|
|
| After expiry | refetched from upstream | refetched from upstream (the ETag expires with the TTL, so `check_mutable` does not revalidate it) |
|
|
| Upstream down | `stale_on_error` serves the stored copy and re-arms the TTL; an open circuit breaker always serves stale if a copy exists, else 503 | same |
|
|
|
|
Concurrent misses on one path share a fetch lock. Waiters poll the store for
|
|
up to 5s instead of all going upstream.
|
|
|
|
## Eviction
|
|
|
|
```sh
|
|
curl -X DELETE https://artifactapi.example.com/api/v2/remotes/<name>/objects/<path>
|
|
curl -X DELETE https://artifactapi.example.com/api/v2/remotes/<name>/objects/<dir>/*
|
|
```
|
|
|
|
- Evict drops the artifact row, the index object and the Redis TTL and ETag keys, so the next request goes upstream whatever the class or TTL.
|
|
- A single-path evict takes the fetch lock first, so an in-flight fetch can't re-cache the path. It returns 503 if the lock stays busy.
|
|
- A wildcard must end in `<dir>/*` and takes no lock.
|
|
- Returns 204, or 404 for an unknown remote.
|
|
|
|
## Examples
|
|
|
|
Bodies for `POST https://artifactapi.example.com/api/v2/remotes`.
|
|
|
|
GitHub release assets through a `generic` remote, allowlisted:
|
|
|
|
```json
|
|
{"name": "github", "package_type": "generic", "base_url": "https://github.com",
|
|
"immutable_ttl": 0, "mutable_ttl": 7200,
|
|
"patterns": ["^mikefarah/yq/releases/download/.*/yq_linux_amd64$"]}
|
|
```
|
|
|
|
rpm remote: `repodata/*` and `repomd.xml` are mutable by default, so only the TTLs are needed:
|
|
|
|
```json
|
|
{"name": "almalinux", "package_type": "rpm", "base_url": "https://repo.almalinux.org/almalinux",
|
|
"immutable_ttl": 0, "mutable_ttl": 3600, "check_mutable": true, "stale_on_error": true}
|
|
```
|
|
|
|
Force a moving path mutable, e.g. branch tarballs on `generic`:
|
|
|
|
```json
|
|
{"mutable_patterns": ["/archive/refs/heads/.*\\.tar\\.gz$"], "mutable_ttl": 600}
|
|
```
|
|
|
|
## Built-in classification per type
|
|
|
|
Each type's page lists its auto-detected mutable and immutable paths:
|
|
[alpine](../alpine/remote.md), [alpine github](../alpine/remote-github.md),
|
|
[cargo](../cargo/remote.md), [deb](../deb/remote.md), [deb github](../deb/remote-github.md),
|
|
[docker](../docker/remote.md), [generic](../generic/remote.md), [goproxy](../goproxy/remote.md),
|
|
[helm](../helm/remote.md), [npm](../npm/remote.md), [nuget](../nuget/remote.md),
|
|
[puppet](../puppet/remote.md), [pypi](../pypi/remote.md), [rpm](../rpm/remote.md),
|
|
[rpm github](../rpm/remote-github.md), [terraform](../terraform/remote.md).
|