ci: cite YAML by anchor instead of line number, and guard it (REN-183)
Comments that pointed at other YAML as `file.yaml:N` went stale on any edit above the cited line; three drift fixes had been merged by hand (REN-181). The cited block now carries `# anchor: <name>` and the comment cites `path.yaml#<name>`. - Sweep: all 32 line-number citations in YAML comments converted. Several were already stale at head and now point at the right block (see PR). Vendored files (Flux gotk-components, IMC CRD) get no anchor; those two citations name the object/schema path instead. - Guard: scripts/ci/yaml-citations.sh in ci-manifests-test.yml; fails on a line-number citation or a path#name whose file/anchor is gone. Self-test in ci-scripts-test.yml. - CONTRIBUTING.md: the convention in three lines. Comment-only under kubernetes/: every file parses to identical objects before and after, so Flux applies nothing and no ConfigMap data changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
2608c264d3
commit
21a7583523
@@ -36,6 +36,7 @@ when:
|
||||
- "kubernetes/**"
|
||||
- ".woodpecker/**"
|
||||
- "scripts/ci/promtool-rules.sh"
|
||||
- "scripts/ci/yaml-citations.sh"
|
||||
|
||||
# THE THIRD DISCOVERY CHECK WENT TO A SCRIPT. kustomize-build and shellcheck
|
||||
# both hand-roll the same shape — `find`, a floor guard against empty discovery,
|
||||
@@ -50,10 +51,10 @@ when:
|
||||
# ci-scripts-test.yml. A FOURTH inline copy should go the same way; do not add
|
||||
# one here.
|
||||
#
|
||||
# Every step carries `depends_on: []` so all five run in PARALLEL and every
|
||||
# Every step carries `depends_on: []` so all six run in PARALLEL and every
|
||||
# failure shows in one run. Woodpecker's default is sequential, which would stop
|
||||
# at the first failure and make a contributor fix-and-repush to discover the
|
||||
# next — and these five are independent, cheap, offline checks with no ordering
|
||||
# next — and these six are independent, cheap, offline checks with no ordering
|
||||
# between them. Keep it on every step: `depends_on` is per-step, so dropping it
|
||||
# from one silently re-serialises that step behind the others.
|
||||
steps:
|
||||
@@ -320,3 +321,22 @@ steps:
|
||||
image: prom/prometheus:v3.15.0
|
||||
commands:
|
||||
- sh scripts/ci/promtool-rules.sh
|
||||
|
||||
# Comments cite other YAML blocks by anchor name, never by line number
|
||||
# (REN-183; the exact syntax is in CONTRIBUTING.md, not repeated here because
|
||||
# an example written in this comment would itself be checked). Line numbers drift on any edit
|
||||
# above them and nothing noticed: three drift fixes were merged by hand
|
||||
# before this step existed. This fails on a new line-number citation and on
|
||||
# a citation whose file or anchor marker does not exist.
|
||||
# Existence only — it never judges whether the anchored block still says
|
||||
# what the citing comment claims, so ordinary edits cannot turn it red.
|
||||
#
|
||||
# It scans every YAML file in the checkout, but this pipeline only runs on
|
||||
# the paths in `when:` above, so an edit confined to ansible/ or esphome/ is
|
||||
# checked on the next PR that does touch kubernetes/ or .woodpecker/.
|
||||
# Self-test: scripts/ci/test-yaml-citations.sh, in ci-scripts-test.yml.
|
||||
- name: yaml-citations
|
||||
depends_on: [] # see note above `steps:`
|
||||
image: gitea.renner.uno/gitea_admin/ci-k8s:2026-09-08
|
||||
commands:
|
||||
- sh scripts/ci/yaml-citations.sh
|
||||
|
||||
@@ -23,14 +23,14 @@
|
||||
# only runs when someone remembers to touch the right directory is not a guard.
|
||||
#
|
||||
# Only in homelab (scripts/ci/ lives here); the file is not deployed to the
|
||||
# mirrored repos, so it does not run on their PRs. Both tests are offline (no
|
||||
# mirrored repos, so it does not run on their PRs. All tests here are offline (no
|
||||
# token, no network, no cluster) — the ci-claude image just provides sh + jq, and
|
||||
# it is already pulled by pr-review.yml on the same event, so the runner has it
|
||||
# cached.
|
||||
when:
|
||||
- event: pull_request
|
||||
|
||||
# `depends_on: []` on both so they run in parallel and one failure does not hide
|
||||
# `depends_on: []` on every step so they run in parallel and one failure does not hide
|
||||
# the other. Same reasoning as the note above `steps:` in ci-manifests-test.yml.
|
||||
steps:
|
||||
- name: model-selection-tests
|
||||
@@ -44,3 +44,9 @@ steps:
|
||||
image: gitea.renner.uno/gitea_admin/ci-claude:2.1.280
|
||||
commands:
|
||||
- sh scripts/ci/test-promtool-rules.sh
|
||||
|
||||
- name: yaml-citations-guard-tests
|
||||
depends_on: []
|
||||
image: gitea.renner.uno/gitea_admin/ci-claude:2.1.280
|
||||
commands:
|
||||
- sh scripts/ci/test-yaml-citations.sh
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# Contributing
|
||||
|
||||
## Citing other YAML from a comment
|
||||
|
||||
- Mark the block you cite with a comment line `# anchor: <name>` (kebab-case, unique in that file). If the block is inside a `|` block scalar, put the marker just above the key, never inside the scalar.
|
||||
- Cite it as `path.yaml#<name>`, with the path relative to the citing file's directory or to the repo root. Never cite `path.yaml:<line>`, because line numbers go stale on any edit above them.
|
||||
- `scripts/ci/yaml-citations.sh` enforces this in `ci-manifests-test.yml`: it fails on line-number citations and on citations whose file or anchor does not exist.
|
||||
@@ -40,12 +40,13 @@ metadata:
|
||||
# fixed in place because the sentence lives inside the `config.yaml: |` block
|
||||
# scalar below: any edit to it changes ConfigMap DATA, and
|
||||
# `configmap.reloader.stakater.com/reload: gatus-config`
|
||||
# (gatus/deployment.yaml:13) would then roll the gatus Deployment — a
|
||||
# (deployment.yaml#reloader) would then roll the gatus Deployment — a
|
||||
# production restart for a comment. This header is outside `data:`, so it is
|
||||
# inert; the served config is byte-identical.
|
||||
#
|
||||
# * infrastructure/network-policies/paperless-ns.yaml:34-36 ALREADY admits
|
||||
# the whole monitoring namespace on every port, and monitoring is where
|
||||
# * ../../infrastructure/network-policies/paperless-ns.yaml#monitoring-admit
|
||||
# ALREADY admits the whole monitoring namespace on every port, and
|
||||
# monitoring is where
|
||||
# blackbox-exporter runs. Cilium unions the allow rules of every CNP
|
||||
# selecting an endpoint, so no extra policy is required.
|
||||
# * The in-cluster paperless probe therefore exists, added with no change
|
||||
@@ -60,6 +61,7 @@ metadata:
|
||||
# The Web Service 11 entry below stays a reachability-only check on purpose.
|
||||
# The app-health half of the pair is the blackbox probe named above.
|
||||
data:
|
||||
# anchor: gatus-endpoints
|
||||
config.yaml: |
|
||||
storage:
|
||||
type: sqlite
|
||||
|
||||
@@ -6,6 +6,7 @@ metadata:
|
||||
labels:
|
||||
app: gatus
|
||||
annotations:
|
||||
# anchor: reloader
|
||||
# Gatus reads its config once at startup and never re-reads the file, so
|
||||
# without this a ConfigMap change reconciles cleanly and then does nothing:
|
||||
# git and the running checks drift apart silently, with no failed resource
|
||||
|
||||
@@ -93,10 +93,11 @@ spec:
|
||||
# are the first targets here that leave the monitoring namespace.
|
||||
#
|
||||
# Both sit entirely behind an Authentik outpost — navidrome.renner.uno
|
||||
# on ak-outpost-navidrome-gate (navidrome/ingress.yaml:21),
|
||||
# lidarr.renner.uno on ak-outpost-lidarr-gate
|
||||
# (navidrome/lidarr.yaml:218) — so an EXTERNAL probe of either name
|
||||
# measures the gate, not the app, and stays green while the app behind
|
||||
# on ak-outpost-navidrome-gate
|
||||
# (../navidrome/ingress.yaml#navidrome-gate), lidarr.renner.uno on
|
||||
# ak-outpost-lidarr-gate (../navidrome/lidarr.yaml#lidarr-gate) — so an
|
||||
# EXTERNAL probe of either name measures the gate, not the app, and
|
||||
# stays green while the app behind
|
||||
# a live gate is dead. That external signal is still wanted, and REN-2
|
||||
# adds it from UptimeRobot; these are the other half of the pair.
|
||||
#
|
||||
@@ -124,7 +125,7 @@ spec:
|
||||
# infrastructure/network-policies/monitoring-ns.yaml
|
||||
# (default-deny-ingress) and monitoring-alloy-syslog.yaml
|
||||
# (allow-syslog-world-ingress). blackbox-exporter runs in that same
|
||||
# namespace (apps/monitoring/helmrelease-blackbox-exporter.yaml:5).
|
||||
# namespace (../monitoring/helmrelease-blackbox-exporter.yaml#namespace).
|
||||
# Re-verified at 2b71dda1 across all 635 yaml files in the repo — 73
|
||||
# Cilium objects, 38 carrying an egress or egressDeny block, none of
|
||||
# them in the monitoring namespace, and the single
|
||||
@@ -142,9 +143,10 @@ spec:
|
||||
# gatus checks, so status.renner.uno is unaffected and no workload is
|
||||
# restarted. What breaks is the signal, and it breaks misleadingly.
|
||||
# All three targets start failing BlackboxProbeFailing
|
||||
# (prometheusrule-blackbox.yaml:34 — severity warning, for: 10m); none
|
||||
# of the three is in that rule's exclusion list. BlackboxProbesAbsent
|
||||
# does NOT fire, because the other 17 targets keep producing
|
||||
# (prometheusrule-blackbox.yaml#blackbox-probe-failing — severity
|
||||
# warning, for: 10m); none of the three is in that rule's exclusion
|
||||
# list. BlackboxProbesAbsent does NOT fire, because the other 17
|
||||
# targets keep producing
|
||||
# probe_success — so it reads as three apps going down, not as a
|
||||
# policy change. WHO NOTICES: warning routes to the default `email`
|
||||
# receiver, admin@renner.uno (helmrelease-prometheus.yaml alertmanager
|
||||
@@ -156,16 +158,17 @@ spec:
|
||||
# `flux reconcile kustomization monitoring-rules --with-source`.
|
||||
#
|
||||
# /ping on both: unauthenticated, and already the pods' own kubelet
|
||||
# probe path (navidrome/deployment.yaml:80-93,
|
||||
# navidrome/lidarr.yaml:114-127). For lidarr that it is ungated is
|
||||
# proven independently of the gate — lidarr.yaml:29 curls /ping in a
|
||||
# probe path (../navidrome/deployment.yaml#kubelet-probes,
|
||||
# ../navidrome/lidarr.yaml#kubelet-probes). For lidarr that it is
|
||||
# ungated is proven independently of the gate — its reconcile script
|
||||
# (../navidrome/lidarr.yaml#reconcile-script) curls /ping in a
|
||||
# loop BEFORE it has read the API key out of config.xml.
|
||||
#
|
||||
# STATUS CODE, unproven for all three of the new targets: module
|
||||
# http_2xx follows redirects and passes only on a FINAL 2xx, while
|
||||
# every piece of repo evidence behind these paths establishes only
|
||||
# `< 400` — a kubelet httpGet accepts 200-399, and lidarr.yaml:29's
|
||||
# `curl -f` without -L exits 0 on a 302. That evidence proves the
|
||||
# `< 400` — a kubelet httpGet accepts 200-399, and that reconcile
|
||||
# script's `curl -f` without -L exits 0 on a 302. That evidence proves the
|
||||
# paths are UNGATED, not that they answer 200. Check
|
||||
# probe_http_status_code on the first scrapes; a target that settles
|
||||
# on a 3xx gets REVERTED, not widened to accept 3xx.
|
||||
@@ -207,10 +210,12 @@ spec:
|
||||
# BLAST RADIUS paragraph above.
|
||||
- http://navidrome.navidrome.svc.cluster.local:80/ping
|
||||
- http://lidarr.navidrome.svc.cluster.local:80/ping
|
||||
# anchor: paperless-app-health
|
||||
# APP-HEALTH probe for paperless. Same rule as the block above
|
||||
# (monitoring-vantage-point-rule); the blind spot it closes is the one
|
||||
# gatus already documents at apps/gatus/configmap.yaml:205-214 —
|
||||
# through the paperless-gate outpost EVERY path, /api/ included,
|
||||
# gatus already documents in its Web Service 11 comment
|
||||
# (../gatus/configmap.yaml#gatus-endpoints) — through the
|
||||
# paperless-gate outpost EVERY path, /api/ included,
|
||||
# answers 302 to an unauthenticated caller, so no external probe of
|
||||
# paperless.renner.uno can tell a live app from a live gate. That
|
||||
# external signal is still wanted, and REN-2 adds it from UptimeRobot;
|
||||
@@ -218,10 +223,12 @@ spec:
|
||||
#
|
||||
# That gatus comment says the in-cluster probe "needs a
|
||||
# CiliumNetworkPolicy first". That sentence is simply WRONG, not
|
||||
# merely inapplicable: paperless-ns.yaml:34-36 already admits the
|
||||
# whole monitoring namespace, which is where blackbox-exporter runs,
|
||||
# merely inapplicable:
|
||||
# ../../infrastructure/network-policies/paperless-ns.yaml#monitoring-admit
|
||||
# already admits the whole monitoring namespace, which is where
|
||||
# blackbox-exporter runs,
|
||||
# and Cilium unions the allow rules of every CNP selecting an
|
||||
# endpoint (the mechanism apps/paperless/network-policy.yaml:22-26
|
||||
# endpoint (the mechanism ../paperless/network-policy.yaml#cnp-union
|
||||
# relies on). The EGRESS paragraph in the block above applies here
|
||||
# identically. Nothing in the paperless namespace is edited by this
|
||||
# change.
|
||||
@@ -229,19 +236,22 @@ spec:
|
||||
# The stale sentence is deliberately left in place in
|
||||
# apps/gatus/configmap.yaml: it sits INSIDE the `config.yaml: |`
|
||||
# block scalar, so editing it changes ConfigMap data and
|
||||
# `configmap.reloader.stakater.com/reload` (gatus/deployment.yaml:13)
|
||||
# rolls the gatus Deployment. The correction is written at the top of
|
||||
# `configmap.reloader.stakater.com/reload`
|
||||
# (../gatus/deployment.yaml#reloader) rolls the gatus Deployment. The
|
||||
# correction is written at the top of
|
||||
# that file instead, outside `data:`, where it costs no restart.
|
||||
#
|
||||
# /accounts/login/ because paperless-ngx has no unauthenticated health
|
||||
# endpoint at all. It is the app's own established "the webserver
|
||||
# answers" signal — the same path its startup, liveness and readiness
|
||||
# probes use (paperless.yaml:252-272) — and it renders a Django view
|
||||
# probes use (../paperless/paperless.yaml#http-probes) — and it
|
||||
# renders a Django view
|
||||
# rather than a static file. It does NOT cover the consumer/celery
|
||||
# workers: see process-liveness-vs-protocol-liveness.
|
||||
#
|
||||
# The cluster-local Host header is accepted only because
|
||||
# PAPERLESS_ALLOWED_HOSTS is "*" (paperless.yaml:27). Narrow that and
|
||||
# PAPERLESS_ALLOWED_HOSTS is "*"
|
||||
# (../paperless/paperless.yaml#allowed-hosts). Narrow that and
|
||||
# Django answers 400 here and this probe goes permanently red.
|
||||
#
|
||||
# This is the target most likely to fail the STATUS CODE check in the
|
||||
@@ -335,10 +345,12 @@ spec:
|
||||
# WHY A SEPARATE jobName, and why these must NEVER be folded into the
|
||||
# blackbox-http Probe above: after PR 2b these targets fail permanently and BY
|
||||
# DESIGN. BlackboxProbeFailing and BlackboxProbesAbsent
|
||||
# (prometheusrule-blackbox.yaml:35,50) are both scoped to job="blackbox-http",
|
||||
# so a distinct job is outside both and stays silent — and the hand-maintained
|
||||
# instance!~ exclusion regex at :35, whose fragility that file warns about, does
|
||||
# not have to grow a fifth entry. Adding these to blackbox-http instead would
|
||||
# (prometheusrule-blackbox.yaml#blackbox-probe-failing and
|
||||
# prometheusrule-blackbox.yaml#blackbox-probes-absent) are both scoped to
|
||||
# job="blackbox-http", so a distinct job is outside both and stays silent — and
|
||||
# the hand-maintained instance!~ exclusion regex in the first of them, whose
|
||||
# fragility that file warns about, does not have to grow a fifth entry. Adding
|
||||
# these to blackbox-http instead would
|
||||
# page the on-call forever.
|
||||
#
|
||||
# The inverted alert that gives these targets teeth — CNPNegativeControlReachable,
|
||||
@@ -377,12 +389,14 @@ spec:
|
||||
# exporter's 9187). Neither that rule nor paperless-allow-blackbox
|
||||
# exists yet at this commit — that is the point, and why this target
|
||||
# must read 1 now. Service is CNPG-generated from Cluster `pprl-pg`
|
||||
# (paperless/cluster.yaml:4) and is the name paperless itself dials
|
||||
# (paperless.yaml:46).
|
||||
# (../paperless/cluster.yaml#pprl-pg) and is the name paperless itself
|
||||
# dials (../paperless/paperless.yaml#db-host).
|
||||
- pprl-pg-rw.paperless.svc.cluster.local:5432
|
||||
# Three witnesses that the whole-namespace `monitoring` admit is gone
|
||||
# for workloads no new rule selects. Ports from the Services themselves:
|
||||
# redis.yaml:76, tika.yaml:57, gotenberg.yaml:63.
|
||||
# ../paperless/redis.yaml#service-port,
|
||||
# ../paperless/tika.yaml#service-port,
|
||||
# ../paperless/gotenberg.yaml#service-port.
|
||||
- redis.paperless.svc.cluster.local:6379
|
||||
- tika.paperless.svc.cluster.local:9998
|
||||
- gotenberg.paperless.svc.cluster.local:3000
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
#
|
||||
# NO POLICY CHANGE IS NEEDED TO MAKE THESE WORK TODAY. Ingress: the paperless
|
||||
# baseline still admits the whole `monitoring` namespace on every port
|
||||
# (infrastructure/network-policies/paperless-ns.yaml:34-36) — that admission is
|
||||
# (../../infrastructure/network-policies/paperless-ns.yaml#monitoring-admit) —
|
||||
# that admission is
|
||||
# exactly what REN-28 removes. Egress: the `monitoring` namespace is in no
|
||||
# egress enforcement at all; its only two Cilium policies, monitoring-ns.yaml
|
||||
# and monitoring-alloy-syslog.yaml, are ingress-only. Same dependency the three
|
||||
@@ -88,7 +89,8 @@ spec:
|
||||
# The test as written in §7 says "a paperless POD port other than
|
||||
# 8000"; there is no such port. The paperless pod's only other listener
|
||||
# is the bridge-tls-proxy socat, bound to 127.0.0.1:11143
|
||||
# (apps/paperless/paperless.yaml:289), so every other port on that pod
|
||||
# (../paperless/paperless.yaml#bridge-tls-proxy), so every other port
|
||||
# on that pod
|
||||
# is closed today and answers RST — there is no "reachable before" to
|
||||
# record and the port half of paperless-allow-blackbox (:8000) cannot
|
||||
# be tested at all. gotenberg is the half that CAN be: it is one of the
|
||||
|
||||
@@ -31,6 +31,7 @@ spec:
|
||||
# target means. A target whose loss deserves a critical page gets its
|
||||
# own rule — and lands in the list above. That is what keeps the
|
||||
# exclusion list from being arbitrary.
|
||||
# anchor: blackbox-probe-failing
|
||||
- alert: BlackboxProbeFailing
|
||||
expr: probe_success{job="blackbox-http", instance!~"https://fsn1\\.your-objectstorage\\.com|https://netbird\\.renner\\.uno|http://proxy-[12]\\.renner\\.uno:9000/health|https://ci\\.renner\\.uno|https://openbao\\.renner\\.uno/v1/sys/health.*"} == 0
|
||||
for: 10m
|
||||
@@ -46,6 +47,7 @@ spec:
|
||||
# The check that the checks are running. Every rule above reads
|
||||
# probe_success; if the series stop being produced at all, none of them
|
||||
# can fire and the silence looks exactly like health.
|
||||
# anchor: blackbox-probes-absent
|
||||
- alert: BlackboxProbesAbsent
|
||||
expr: absent(probe_success{job="blackbox-http"})
|
||||
for: 15m
|
||||
|
||||
@@ -2,6 +2,7 @@ apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: blackbox-exporter
|
||||
# anchor: namespace
|
||||
namespace: monitoring
|
||||
spec:
|
||||
interval: 30m
|
||||
|
||||
@@ -83,6 +83,7 @@ spec:
|
||||
- name: music
|
||||
mountPath: /music
|
||||
readOnly: true
|
||||
# anchor: kubelet-probes
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /ping
|
||||
|
||||
@@ -16,6 +16,7 @@ spec:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
# anchor: navidrome-gate
|
||||
backend:
|
||||
service:
|
||||
name: ak-outpost-navidrome-gate
|
||||
|
||||
@@ -23,6 +23,7 @@ metadata:
|
||||
name: lidarr-reconcile
|
||||
namespace: navidrome
|
||||
data:
|
||||
# anchor: reconcile-script
|
||||
reconcile.sh: |
|
||||
set -eu
|
||||
API=http://localhost:8686/api/v1
|
||||
@@ -111,6 +112,7 @@ spec:
|
||||
mountPath: /config
|
||||
- name: music
|
||||
mountPath: /music
|
||||
# anchor: kubelet-probes
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /ping
|
||||
@@ -213,6 +215,7 @@ spec:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
# anchor: lidarr-gate
|
||||
backend:
|
||||
service:
|
||||
name: ak-outpost-lidarr-gate
|
||||
|
||||
@@ -58,7 +58,8 @@
|
||||
# aggregation did not already cover them. It does, so none is added:
|
||||
# `flux-view-flux-system` carries `rbac.authorization.k8s.io/aggregate-to-view:
|
||||
# "true"` and lists get/list/watch on `*` across all six API groups —
|
||||
# `../../clusters/homelab/flux-system/gotk-components.yaml:232-254`. The
|
||||
# the `flux-view-flux-system` ClusterRole in
|
||||
# `../../clusters/homelab/flux-system/gotk-components.yaml`. The
|
||||
# aggregation controller merges those rules into `view` itself, so this one
|
||||
# binding picks up kustomizations, helmreleases, gitrepositories, imagepolicies,
|
||||
# imagerepositories and imageupdateautomations. Read from git, not from a live
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
# anchor: pprl-pg
|
||||
name: pprl-pg
|
||||
namespace: paperless
|
||||
annotations:
|
||||
|
||||
@@ -59,6 +59,7 @@ metadata:
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/name: gotenberg
|
||||
# anchor: service-port
|
||||
ports:
|
||||
- port: 3000
|
||||
targetPort: http
|
||||
|
||||
@@ -11,14 +11,19 @@
|
||||
# the allow rules of every CNP selecting an endpoint (the same mechanism
|
||||
# described below). The effective ingress set is this outpost PLUS, on
|
||||
# every port:
|
||||
# - the paperless namespace (paperless-ns.yaml:9-11)
|
||||
# - the database namespace (paperless-ns.yaml:31-33)
|
||||
# - the monitoring namespace (paperless-ns.yaml:34-36)
|
||||
# - host / remote-node (paperless-ns.yaml:37-39)
|
||||
# - the paperless namespace
|
||||
# ../../infrastructure/network-policies/paperless-ns.yaml#paperless-admit
|
||||
# - the database namespace
|
||||
# ../../infrastructure/network-policies/paperless-ns.yaml#database-admit
|
||||
# - the monitoring namespace
|
||||
# ../../infrastructure/network-policies/paperless-ns.yaml#monitoring-admit
|
||||
# - host / remote-node
|
||||
# ../../infrastructure/network-policies/paperless-ns.yaml#host-admit
|
||||
# So any pod in database or monitoring can reach this HTTP listener
|
||||
# directly and set X-Authentik-Username to any value. Do not read this
|
||||
# rule as the whole boundary. Narrowing is tracked in REN-28.
|
||||
#
|
||||
# anchor: cnp-union
|
||||
# This is a CiliumNetworkPolicy (not a standard NetworkPolicy) so it
|
||||
# composes additively with the cluster's baseline CNP
|
||||
# `default-deny-ingress` for the paperless namespace — Cilium's allow
|
||||
|
||||
@@ -24,6 +24,7 @@ data:
|
||||
PAPERLESS_ACCOUNT_DEFAULT_GROUPS: "Familie"
|
||||
PAPERLESS_URL: "https://paperless.renner.uno"
|
||||
PAPERLESS_CSRF_TRUSTED_ORIGINS: "https://paperless.renner.uno,https://docs.renner.uno"
|
||||
# anchor: allowed-hosts
|
||||
PAPERLESS_ALLOWED_HOSTS: "*"
|
||||
PAPERLESS_TIME_ZONE: "Europe/Berlin"
|
||||
PAPERLESS_OCR_LANGUAGE: "deu+eng"
|
||||
@@ -43,6 +44,7 @@ data:
|
||||
PAPERLESS_CONSUMER_POLLING_INTERVAL: "10"
|
||||
PAPERLESS_REDIS: "redis://redis:6379"
|
||||
PAPERLESS_DBENGINE: "postgresql"
|
||||
# anchor: db-host
|
||||
PAPERLESS_DBHOST: "pprl-pg-rw"
|
||||
PAPERLESS_DBNAME: "paperless"
|
||||
PAPERLESS_TIKA_ENABLED: "1"
|
||||
@@ -249,6 +251,7 @@ spec:
|
||||
# answers, giving that boot 30 * 20s = 10 minutes. Without it the
|
||||
# 120s liveness delay plus three 30s periods kills the pod 210s in,
|
||||
# part-way through a migration, and the next boot starts over.
|
||||
# anchor: http-probes
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /accounts/login/
|
||||
@@ -282,6 +285,7 @@ spec:
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
add: ["CHOWN", "DAC_OVERRIDE", "FOWNER", "SETGID", "SETUID"]
|
||||
# anchor: bridge-tls-proxy
|
||||
- name: bridge-tls-proxy
|
||||
image: alpine/socat:1.8.0.3
|
||||
args:
|
||||
|
||||
@@ -72,6 +72,7 @@ metadata:
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/name: redis
|
||||
# anchor: service-port
|
||||
ports:
|
||||
- port: 6379
|
||||
targetPort: redis
|
||||
|
||||
@@ -53,6 +53,7 @@ metadata:
|
||||
spec:
|
||||
selector:
|
||||
app.kubernetes.io/name: tika
|
||||
# anchor: service-port
|
||||
ports:
|
||||
- port: 9998
|
||||
targetPort: http
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
# raised — no further PR needed. Until then this object logs one refused POST
|
||||
# every 300s and nothing else: nothing pages, nothing degrades, and
|
||||
# `audiobooks.renner.uno` stays covered by its 60s Gatus check
|
||||
# (apps/gatus/configmap.yaml:163) throughout.
|
||||
# (Web Service 8 in ../gatus/configmap.yaml#gatus-endpoints) throughout.
|
||||
#
|
||||
# REVERT: revert this commit. Flux prunes the object again and, because the
|
||||
# monitor does not exist to begin with, nothing in UptimeRobot changes.
|
||||
@@ -72,7 +72,8 @@
|
||||
# namespace, and the reason is structural rather than a permissions gap:
|
||||
#
|
||||
# * `IngressURLSource` has exactly one field, `name` — there is no namespace
|
||||
# field on it (crd-endpointmonitors.yaml:443-459).
|
||||
# field on it (crd-endpointmonitors.yaml, schema path
|
||||
# `spec.urlFrom.ingressRef`).
|
||||
# * `discoverURLFromRefs` resolves that name against
|
||||
# `ingressMonitor.Namespace` — the EndpointMonitor's OWN namespace
|
||||
# (v2.2.13 pkg/kube/util/url.go:71 → :38-40).
|
||||
@@ -240,7 +241,7 @@ metadata:
|
||||
spec:
|
||||
# `https://www.renner.uno` and not `/wp-admin` or `/wp-login.php`: those two
|
||||
# paths are served by a SECOND Ingress that sits behind the Authentik outpost
|
||||
# (apps/wordpress/ingress.yaml:50,57). `/` is the ungated one.
|
||||
# (../wordpress/ingress.yaml#gated-paths). `/` is the ungated one.
|
||||
url: https://www.renner.uno/
|
||||
uptimeRobotConfig:
|
||||
# `http`, not `keyword`. The survey recommends a keyword check here
|
||||
@@ -273,7 +274,7 @@ spec:
|
||||
# explicitly ignored when `spec.url` is set (pkg/kube/util/url.go:31-33).
|
||||
# `/healthcheck` is ungated, so the check reaches application health rather
|
||||
# than mere reachability — it is the same endpoint the Gatus check uses
|
||||
# (apps/gatus/configmap.yaml:163).
|
||||
# (Web Service 8 in ../gatus/configmap.yaml#gatus-endpoints).
|
||||
url: https://audiobooks.renner.uno/healthcheck
|
||||
uptimeRobotConfig:
|
||||
monitorType: http
|
||||
|
||||
@@ -75,6 +75,7 @@ spec:
|
||||
name: ak-outpost-wordpress-gate
|
||||
port:
|
||||
number: 9000
|
||||
# anchor: gated-paths
|
||||
# Covers admin-ajax.php too. That is only safe because the gift
|
||||
# registry plugin -- the sole thing on this site that ever emitted
|
||||
# an ajaxurl -- is being dropped in the migration. Verified against
|
||||
|
||||
@@ -6,6 +6,7 @@ metadata:
|
||||
spec:
|
||||
endpointSelector: {}
|
||||
ingress:
|
||||
# anchor: paperless-admit
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
"k8s:io.kubernetes.pod.namespace": paperless
|
||||
@@ -25,15 +26,18 @@ spec:
|
||||
# because both are load-bearing — CNPG reaches the postgres cluster
|
||||
# from `database`, and the blackbox probe of
|
||||
# http://paperless.paperless.svc.cluster.local:80/accounts/login/
|
||||
# (kubernetes/apps/monitoring-rules/probes-blackbox.yaml:250) runs
|
||||
# from `monitoring`.
|
||||
# (kubernetes/apps/monitoring-rules/probes-blackbox.yaml#paperless-app-health)
|
||||
# runs from `monitoring`.
|
||||
# Scoping them to specific pods and ports is tracked in REN-28.
|
||||
# anchor: database-admit
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
"k8s:io.kubernetes.pod.namespace": database
|
||||
# anchor: monitoring-admit
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
"k8s:io.kubernetes.pod.namespace": monitoring
|
||||
# anchor: host-admit
|
||||
- fromEntities:
|
||||
- host
|
||||
- remote-node
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/bin/sh
|
||||
# Offline self-test for yaml-citations.sh: each case is a small fixture tree
|
||||
# that must FAIL (or, for happy-path, PASS). Run: sh scripts/ci/test-yaml-citations.sh
|
||||
set -eu
|
||||
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
SUT="$HERE/yaml-citations.sh"
|
||||
[ -f "$SUT" ] || { echo "FAIL: $SUT not found"; exit 1; }
|
||||
|
||||
TMP="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP"' EXIT INT TERM
|
||||
fails=0
|
||||
|
||||
# new_tree NAME — a fixture with one anchored target the cases can cite.
|
||||
new_tree() {
|
||||
d="$TMP/$1"; mkdir -p "$d/k/a" "$d/k/b"
|
||||
printf 'kind: X\n# anchor: target-block\nspec: {}\n' > "$d/k/a/target.yaml"
|
||||
echo "$d"
|
||||
}
|
||||
|
||||
# expect CASE want(pass|fail) DIR
|
||||
expect() {
|
||||
if YAML_CITATIONS_FLOOR=1 sh "$SUT" "$3" > "$TMP/$1.out" 2>&1; then got=pass; else got=fail; fi
|
||||
if [ "$got" = "$2" ]; then echo "ok $1 ($got)"
|
||||
else echo "FAIL $1: wanted $2, got $got"; sed 's/^/ /' "$TMP/$1.out"; fails=1; fi
|
||||
}
|
||||
|
||||
d=$(new_tree happy-path)
|
||||
printf '# see target.yaml#target-block and ../a/target.yaml#target-block\nx: 1\n' > "$d/k/a/same-dir.yaml"
|
||||
printf '# root-relative: k/a/target.yaml#target-block\nx: 1\n' > "$d/k/b/root.yaml"
|
||||
printf 'url: "http://h/p.yaml#frag" # a # inside a value is not a comment\n' > "$d/k/b/url.yaml"
|
||||
expect happy-path pass "$d"
|
||||
|
||||
d=$(new_tree line-number)
|
||||
printf '# see ../a/target.yaml:2\nx: 1\n' > "$d/k/b/c.yaml"
|
||||
expect line-number fail "$d"
|
||||
|
||||
d=$(new_tree line-range-yml)
|
||||
printf '# see ../a/target.yml:2-3\nx: 1\n' > "$d/k/b/c.yml"
|
||||
expect line-range-yml fail "$d"
|
||||
|
||||
d=$(new_tree missing-file)
|
||||
printf '# see ../a/gone.yaml#target-block\nx: 1\n' > "$d/k/b/c.yaml"
|
||||
expect missing-file fail "$d"
|
||||
|
||||
d=$(new_tree missing-anchor)
|
||||
printf '# see ../a/target.yaml#no-such-block\nx: 1\n' > "$d/k/b/c.yaml"
|
||||
expect missing-anchor fail "$d"
|
||||
|
||||
d=$(new_tree duplicate-anchor)
|
||||
printf '# anchor: target-block\n' >> "$d/k/a/target.yaml"
|
||||
expect duplicate-anchor fail "$d"
|
||||
|
||||
d=$(new_tree malformed-anchor)
|
||||
printf '# anchor: Not_Kebab\nx: 1\n' > "$d/k/b/c.yaml"
|
||||
expect malformed-anchor fail "$d"
|
||||
|
||||
d="$TMP/empty-discovery"; mkdir -p "$d"
|
||||
if sh "$SUT" "$d" > "$TMP/empty.out" 2>&1; then echo "FAIL empty-discovery: passed with no files"; fails=1
|
||||
else echo "ok empty-discovery (fail)"; fi
|
||||
|
||||
exit $fails
|
||||
@@ -0,0 +1,110 @@
|
||||
#!/bin/sh
|
||||
# Guard the citation convention in YAML comments (REN-183, see CONTRIBUTING.md).
|
||||
#
|
||||
# Run: sh scripts/ci/yaml-citations.sh [repo-root]
|
||||
# Needs: sh, find, awk, grep (BusyBox is fine). Offline.
|
||||
# Tests: scripts/ci/test-yaml-citations.sh
|
||||
#
|
||||
# A comment cites another block as `path.yaml#name`, and the cited block
|
||||
# carries a marker line `# anchor: name`. `path` is relative to the citing
|
||||
# file's directory or to the repo root. This fails when:
|
||||
#
|
||||
# * a comment cites by line number (`foo.yaml:12`). Line numbers go stale on
|
||||
# the first unrelated edit above them, silently; three drift fixes were
|
||||
# merged before this guard existed (REN-181).
|
||||
# * a `path#name` citation names a file or an anchor that does not exist.
|
||||
# * an anchor is not kebab-case, or is declared twice in one file.
|
||||
#
|
||||
# Existence only. It does not check that the anchored block still says what the
|
||||
# citing comment claims — that stays a reviewer's job, and it is why this guard
|
||||
# has no false positives from ordinary edits.
|
||||
set -eu
|
||||
|
||||
ROOT="${1:-.}"
|
||||
cd "$ROOT"
|
||||
|
||||
# Same loose-floor reasoning as ci-manifests-test.yml: catch discovery that
|
||||
# silently returns nothing, not normal churn. ~650 YAML files exist today.
|
||||
FLOOR="${YAML_CITATIONS_FLOOR:-50}"
|
||||
|
||||
TMP="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP"' EXIT INT TERM
|
||||
|
||||
# Not `find -printf` (BusyBox lacks it).
|
||||
find . -path ./.git -prune -o -type f \( -name '*.yaml' -o -name '*.yml' \) -print \
|
||||
| sed 's|^\./||' | sort > "$TMP/files"
|
||||
count=$(grep -c . "$TMP/files" || true)
|
||||
if [ "$count" -lt "$FLOOR" ]; then
|
||||
echo "FAIL: found only $count YAML files; floor is $FLOOR. Discovery is broken."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# One pass over every file. A YAML comment starts at a `#` that begins the line
|
||||
# or follows whitespace, so a `#` inside a URL or a quoted value is not one.
|
||||
# Emits tab-separated records:
|
||||
# L file line text a line-number citation
|
||||
# C file line path name a path#name citation
|
||||
# A file line name an anchor declaration
|
||||
# B file line text a malformed anchor declaration
|
||||
while IFS= read -r f; do
|
||||
awk -v f="$f" '
|
||||
{
|
||||
if (!match($0, /(^|[ \t])#/)) next
|
||||
c = substr($0, RSTART)
|
||||
if (c ~ /^[ \t]*# anchor:/) {
|
||||
if (c ~ /^[ \t]*# anchor: [a-z0-9]+(-[a-z0-9]+)*[ \t]*$/) {
|
||||
n = c; sub(/^[ \t]*# anchor: /, "", n); sub(/[ \t]*$/, "", n)
|
||||
print "A\t" f "\t" NR "\t" n
|
||||
} else {
|
||||
print "B\t" f "\t" NR "\t" c
|
||||
}
|
||||
next
|
||||
}
|
||||
if (c ~ /\.ya?ml:[0-9]/) print "L\t" f "\t" NR "\t" c
|
||||
while (match(c, /[A-Za-z0-9_.\/-]+\.ya?ml#[A-Za-z0-9_-]+/)) {
|
||||
m = substr(c, RSTART, RLENGTH)
|
||||
p = m; sub(/#.*/, "", p)
|
||||
n = m; sub(/.*#/, "", n)
|
||||
print "C\t" f "\t" NR "\t" p "\t" n
|
||||
c = substr(c, RSTART + RLENGTH)
|
||||
}
|
||||
}' "$f"
|
||||
done < "$TMP/files" > "$TMP/records"
|
||||
|
||||
fail=0
|
||||
while IFS="$(printf '\t')" read -r kind f line a b; do
|
||||
case "$kind" in
|
||||
L)
|
||||
echo "FAIL $f:$line cites by line number; use path.yaml#anchor instead"
|
||||
echo " $a"
|
||||
fail=1 ;;
|
||||
B)
|
||||
echo "FAIL $f:$line malformed anchor; want '# anchor: kebab-case-name'"
|
||||
fail=1 ;;
|
||||
C)
|
||||
dir=$(dirname "$f")
|
||||
if [ -f "$dir/$a" ]; then target="$dir/$a"
|
||||
elif [ -f "$a" ]; then target="$a"
|
||||
else
|
||||
echo "FAIL $f:$line cites $a#$b, but no such file (tried $dir/$a and ./$a)"
|
||||
fail=1; continue
|
||||
fi
|
||||
if ! grep -qE "^[[:space:]]*# anchor: $b[[:space:]]*\$" "$target"; then
|
||||
echo "FAIL $f:$line cites $a#$b, but $target has no '# anchor: $b'"
|
||||
fail=1
|
||||
fi ;;
|
||||
esac
|
||||
done < "$TMP/records"
|
||||
|
||||
# Duplicate anchors in one file make a citation ambiguous.
|
||||
awk -F '\t' '$1 == "A" { k = $2 "\t" $4; if (k in seen) print $2 ":" $3 " duplicate anchor " $4 " (first at line " seen[k] ")"; else seen[k] = $3 }' \
|
||||
"$TMP/records" > "$TMP/dups"
|
||||
if [ -s "$TMP/dups" ]; then
|
||||
sed 's/^/FAIL /' "$TMP/dups"
|
||||
fail=1
|
||||
fi
|
||||
|
||||
anchors=$(grep -c '^A' "$TMP/records" || true)
|
||||
cites=$(grep -c '^C' "$TMP/records" || true)
|
||||
[ "$fail" -eq 0 ] && echo "ok $count YAML files, $anchors anchors, $cites path#anchor citations, 0 line-number citations"
|
||||
exit $fail
|
||||
Reference in New Issue
Block a user