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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions charts/cozystack/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -245,10 +245,28 @@ extraSchedulerArgs: {}
# (e.g. ghcr.io) or point at a private pull-through cache. To emit NO
# registry config at all, null the key out (`registryMirrors:` with no
# value) — a per-node override merges deeply, so `{}` keeps the default
# docker.io mirror rather than clearing it. Example:
# docker.io mirror rather than clearing it.
#
# Each mirror also accepts two optional per-mirror settings:
# overridePath — use the endpoint URL exactly as written, without
# appending /v2 to its path. REQUIRED for pull-through caches that
# carry the registry name in the path and already include /v2 in the
# URL (Harbor proxy cache, e.g.
# https://mirror.example.com/v2/proxy-docker.io): without
# overridePath containerd appends /v2 again and the cache answers
# with its HTML portal — pulls fail with "unexpected media type
# text/html". On the multidoc schema it is stamped onto every
# endpoint of the mirror (the typed document has no mirror-level
# form); on the legacy schema it is a sibling of endpoints. Requires
# Talos v1.3+; an older pinned talosVersion contract fails fast.
# skipFallback — do not fall back to the upstream registry when no
# mirror serves the image. Requires Talos v1.9+; on an older pinned
# talosVersion contract the render fails fast (an UNPINNED contract
# is trusted as current and skips that guard).
# Example:
# registryMirrors:
# docker.io: { endpoints: [https://registry.example.com/v2/docker.io] }
# ghcr.io: { endpoints: [https://registry.example.com/v2/ghcr.io] }
# docker.io: { endpoints: [https://mirror.example.com/v2/proxy-docker.io], overridePath: true }
# ghcr.io: { endpoints: [https://mirror.example.com/v2/proxy-ghcr.io], overridePath: true, skipFallback: true }
registryMirrors:
docker.io:
endpoints:
Expand Down
10 changes: 8 additions & 2 deletions charts/generic/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,15 @@ extraSchedulerArgs: {}

# Container registry mirrors, keyed by registry host. The generic preset
# ships no default (cozystack mirrors docker.io through gcr); this key
# exists so a values file stays portable between the two presets. Example:
# exists so a values file stays portable between the two presets. Each
# mirror also accepts optional per-mirror `overridePath` (use the
# endpoint URL as written, without appending /v2 — required by Harbor
# proxy-cache endpoints such as
# https://registry.example.com/v2/proxy-docker.io; Talos v1.3+) and
# `skipFallback` (never fall back to the upstream registry; Talos v1.9+).
# Older pinned templateOptions.talosVersion contracts fail fast. Example:
# registryMirrors:
# docker.io: { endpoints: [https://registry.example.com/v2/docker.io] }
# docker.io: { endpoints: [https://registry.example.com/v2/proxy-docker.io], overridePath: true }
registryMirrors: {}

# TLS posture per registry ENDPOINT host — the hostname of the endpoint
Expand Down
40 changes: 39 additions & 1 deletion charts/talm/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,8 @@ busPath: {{ .spec.busPath }}
route, matching what the discovery path emits. */ -}}
{{- /* Shared registry knobs, used by every preset so a values file stays
portable between them. registryMirrors is keyed by registry host and
maps to endpoint URLs; registryTLS is keyed by the ENDPOINT host and
maps to endpoint URLs plus the optional per-mirror overridePath /
skipFallback settings; registryTLS is keyed by the ENDPOINT host and
carries the TLS posture for it. Guards here rather than per preset,
so the two copies cannot drift. */ -}}
{{- define "talm.guard.registries" }}
Expand All @@ -325,10 +326,28 @@ busPath: {{ .spec.busPath }}
{{- fail (printf "talm: registryMirrors.%s endpoints must be a list (got %s) — a bare URL is the missing-dash typo. Example: %q: { endpoints: [https://mirror.example.com] }." $name (kindOf $cfg.endpoints) $name) }}
{{- end }}
{{- range $cfg.endpoints }}
{{- if kindIs "map" . }}
{{- fail (printf "talm: registryMirrors.%s endpoints entries are plain URL strings, not mappings (got %s). Per-mirror settings go next to endpoints: %q: { endpoints: [https://mirror.example.com], overridePath: true }." $name (kindOf .) $name) }}
{{- end }}
{{- if not (or (hasPrefix "http://" (. | toString)) (hasPrefix "https://" (. | toString))) }}
{{- fail (printf "talm: registryMirrors.%s endpoint %q has no scheme. Talos rejects it with \"unsupported scheme\"; write the full URL, e.g. https://%s." $name (. | toString) (. | toString)) }}
{{- end }}
{{- end }}
{{- if and (not (kindIs "invalid" $cfg.overridePath)) (not (kindIs "bool" $cfg.overridePath)) }}
{{- fail (printf "talm: registryMirrors.%s overridePath must be true or false (got %s). It tells containerd not to append /v2 to the endpoint path — required by pull-through caches that put the registry name in the path (Harbor proxy cache, e.g. https://mirror.example.com/v2/proxy-docker.io)." $name (kindOf $cfg.overridePath)) }}
{{- end }}
{{- if and (not (kindIs "invalid" $cfg.skipFallback)) (not (kindIs "bool" $cfg.skipFallback)) }}
{{- fail (printf "talm: registryMirrors.%s skipFallback must be true or false (got %s). It decides whether a pull falls back to the upstream registry when no mirror serves it, so an ambiguous YAML scalar is refused." $name (kindOf $cfg.skipFallback)) }}
{{- end }}
{{- /* overridePath and skipFallback are unknown keys to Talos older than
v1.3 and v1.9 respectively, and the strict node-side decode rejects
the whole config for them. */}}
{{- if and (not (kindIs "invalid" $cfg.overridePath)) $.TalosVersion (semverCompare "<1.3.0-0" $.TalosVersion) }}
{{- fail (printf "talm: registryMirrors.%s sets overridePath, which Talos only understands from v1.3 on; templateOptions.talosVersion pins %s. Drop the key or raise the contract." $name $.TalosVersion) }}
{{- end }}
{{- if and (not (kindIs "invalid" $cfg.skipFallback)) $.TalosVersion (semverCompare "<1.9.0-0" $.TalosVersion) }}
{{- fail (printf "talm: registryMirrors.%s sets skipFallback, which Talos only understands from v1.9 on; templateOptions.talosVersion pins %s. Drop the key or raise the contract." $name $.TalosVersion) }}
{{- end }}
{{- end }}
{{- range $host, $cfg := .Values.registryTLS }}
{{- if eq ($host | toString) "*" }}
Expand Down Expand Up @@ -399,6 +418,17 @@ name: {{ $name | toYaml }}
endpoints:
{{- range $cfg.endpoints }}
- url: {{ . }}
{{- /* RegistryMirrorConfig carries overridePath per endpoint; the values
knob is per-mirror (mirroring the legacy schema, which has no
per-endpoint form), so it is stamped onto every endpoint of the
mirror. All endpoints of one mirror share the same path layout in
practice, so the granularity loss is not observable. */}}
{{- if not (kindIs "invalid" $cfg.overridePath) }}
overridePath: {{ $cfg.overridePath }}
{{- end }}
{{- end }}
{{- if not (kindIs "invalid" $cfg.skipFallback) }}
skipFallback: {{ $cfg.skipFallback }}
{{- end }}
{{- end }}
{{- range $host, $cfg := .Values.registryTLS }}
Expand Down Expand Up @@ -437,6 +467,14 @@ insecureSkipVerify: {{ $cfg.insecureSkipVerify }}
{{- range $cfg.endpoints }}
- {{ . }}
{{- end }}
{{- /* The legacy schema carries both settings as per-mirror siblings of
endpoints — the exact shape the values knob mirrors. */}}
{{- if not (kindIs "invalid" $cfg.overridePath) }}
overridePath: {{ $cfg.overridePath }}
{{- end }}
{{- if not (kindIs "invalid" $cfg.skipFallback) }}
skipFallback: {{ $cfg.skipFallback }}
{{- end }}
{{- end }}
{{- end }}
{{- with .Values.registryTLS }}
Expand Down
15 changes: 15 additions & 0 deletions docs/manual-test-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,21 @@ Expected (multidoc): one `RegistryMirrorConfig` document per host. The legacy sc

Regression anchor: `TestContract_Machine_RegistryMirrors_CustomMirror_Cozystack`, `TestContract_Machine_Registries_DockerMirror_LegacyCozystack` (default), `TestContract_Machine_NoRegistriesOnGeneric`.

A Harbor-style pull-through cache puts the registry name in the pull path and already carries `/v2` in the endpoint URL, so containerd must not append `/v2` again — that is the per-mirror `overridePath` setting (Talos documents it in the pull-through-cache guide). `skipFallback` pins "never fall back to the upstream registry":

```bash
# values.yaml:
# registryMirrors:
# docker.io: { endpoints: [https://mirror.example.com/v2/proxy-docker.io], overridePath: true, skipFallback: true }
talm template -f nodes/controlplane-0.yaml | yq 'select(.kind == "RegistryMirrorConfig")'
# legacy schema: re-render with templateOptions.talosVersion pinned to v1.11
talm template -f nodes/controlplane-0.yaml | yq '.machine.registries.mirrors'
```

Expected (multidoc): every endpoint of the mirror carries `overridePath: true`, and the document carries `skipFallback: true`. Expected (legacy): both keys sit as siblings of `endpoints` under the mirror name. With neither key set the output is byte-identical to before the knob existed. A non-bool value fails the render fast, as do `overridePath` on a pinned pre-v1.3 contract and `skipFallback` on a pinned pre-v1.9 contract (the keys do not exist there). A mapping endpoint (`{url: ..., overridePath: ...}` — the shape the DOCUMENT uses) is refused with a message pointing at the per-mirror keys, because the legacy schema has no per-endpoint form to render it into.

Regression anchor: `TestContract_Machine_RegistryMirrors_OverridePath_Multidoc_Cozystack`, `TestContract_Machine_RegistryMirrors_OverridePath_Legacy_Cozystack`, `TestContract_Machine_RegistryMirrors_OverridePath_Unset_Omitted_Cozystack`, `TestContract_Machine_RegistryMirrors_OverridePath_FalseIsEmitted_Cozystack`, `TestContract_Machine_RegistryMirrors_SkipFallback_FalseIsEmitted_Cozystack`, `TestContract_Machine_RegistryMirrors_NonBoolOverridePath_Fails_Cozystack`, `TestContract_Machine_RegistryMirrors_NonBoolSkipFallback_Fails_Cozystack`, `TestContract_Machine_RegistryMirrors_MapEndpoint_Fails_Cozystack`, `TestContract_Machine_RegistryMirrors_OverridePath_Pre13Contract_Fails_Cozystack`, `TestContract_Machine_RegistryMirrors_OverridePath_GuardPassingSide_Cozystack`, `TestContract_Machine_RegistryMirrors_SkipFallback_Pre19Contract_Fails_Cozystack`, `TestContract_Machine_RegistryMirrors_SkipFallback_GuardPassingSide_Cozystack`.

A self-signed pull-through mirror also needs its TLS posture stated. `registryTLS` is keyed by the endpoint HOST, not the mirror name:

```bash
Expand Down
Loading
Loading