diff --git a/.github/workflows/tag-format.yml b/.github/workflows/tag-format.yml new file mode 100644 index 0000000..10ba123 --- /dev/null +++ b/.github/workflows/tag-format.yml @@ -0,0 +1,19 @@ +# Enforce one spelling for release tags. The grammar lives in +# EduIDE/.github so the repos cannot drift apart on it. +# +# git tag vX.Y.Z +# image tag X.Y.Z +# chart version X.Y.Z + +name: Tag format + +on: + push: + tags: ["**"] + +permissions: + contents: read + +jobs: + check: + uses: EduIDE/.github/.github/workflows/check-tag-format.yml@main diff --git a/scripts/check-agents-md.sh b/scripts/check-agents-md.sh index 2aa8353..734b84b 100755 --- a/scripts/check-agents-md.sh +++ b/scripts/check-agents-md.sh @@ -1,13 +1,22 @@ #!/usr/bin/env bash -# Flag paths referenced by AGENTS.md that no longer exist. +# Check AGENTS.md's factual claims about paths, in both directions. # -# Both pre-existing AGENTS.md files in this org had rotted into fiction. One -# named a CI job that had been deleted and a package.json path that does not -# exist; the other described a landing page removed months earlier. Nothing -# checked them, so nothing noticed. +# Every AGENTS.md in this org had rotted into fiction. One named a CI job that +# had been deleted and a package.json path that does not exist; another +# described a landing page removed months earlier. Nothing checked them, so +# nothing noticed. +# +# A path in backticks must exist - unless the sentence containing it says it +# does not, in which case it must NOT exist. That second direction matters: +# these docs deliberately name dead paths so nobody mistakes them for live code, +# and if someone later creates one the doc has quietly become wrong again. +# +# Checking is per REFERENCE, not per path: a doc that says a file is gone in one +# sentence and tells you to edit it in another is wrong, and evaluating the path +# once would let the negated sentence excuse the live one. # # Only repo-relative, extension-bearing paths in backticks are checked. Prose is -# not validated, and this is a lint rather than a proof. +# not validated; this is a lint, not a proof. set -uo pipefail @@ -15,21 +24,52 @@ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" DOC="$ROOT/AGENTS.md" [[ -f "$DOC" ]] || { echo "no AGENTS.md here"; exit 0; } -missing=0 -while read -r p; do - [[ "$p" == */* ]] || continue # must look like a path - [[ "$p" == *.* ]] || continue # and carry an extension +# A sentence asserting absence. Deliberately narrow: "is dead" and "retired" +# describe something that exists and does not work, which is a different claim. +NEGATED='does not exist|do not exist|no longer exists|no longer exist|was removed|were removed|has no root' + +interesting() { + local p="$1" + [[ "$p" == */* ]] || return 1 # must look like a path + [[ "$p" == *.* ]] || return 1 # and carry an extension case "$p" in - http*|*ghcr.io*|*github.com*|oci://*|*.tum.de*) continue ;; + http*|*ghcr.io*|*github.com*|oci://*|*.tum.de*|*.io/*|*@*) return 1 ;; esac - if [[ ! -e "$ROOT/$p" ]]; then - echo " missing: $p" - missing=1 - fi -done < <(grep -oE '`[A-Za-z0-9_./-]+`' "$DOC" | tr -d '`' | sort -u) - -if [[ $missing -ne 0 ]]; then - echo "AGENTS.md references paths that do not exist. Fix the doc or the path." + return 0 +} + +failed=0; checked=0 +# One line at a time, so each reference is judged in its own sentence. +while IFS=: read -r lineno line; do + while IFS= read -r p; do + interesting "$p" || continue + + # A parent-directory reference resolves against whatever happens to sit + # beside the checkout, so it passes locally and fails in CI - or worse, the + # reverse. Cross-repo paths belong in prose, not in backticks. + case "$p" in + ../*|*/../*) + echo " line $lineno: leaves the repository: $p" + echo " cross-repo paths cannot be checked; name the file without a path" + failed=1 + continue ;; + esac + + checked=$((checked + 1)) + if grep -qiE "$NEGATED" <<<"$line"; then + if [[ -e "$ROOT/$p" ]]; then + echo " line $lineno: says this does not exist, but it does: $p" + failed=1 + fi + elif [[ ! -e "$ROOT/$p" ]]; then + echo " line $lineno: missing: $p" + failed=1 + fi + done < <(grep -oE '`[A-Za-z0-9_./-]+`' <<<"$line" | tr -d '`') +done < <(grep -n '`' "$DOC") + +if [[ $failed -ne 0 ]]; then + echo "AGENTS.md disagrees with the repository. Fix the doc or the path." exit 1 fi -echo "AGENTS.md: every referenced path exists" +echo "AGENTS.md: $checked path references all check out" diff --git a/scripts/render-envs.sh b/scripts/render-envs.sh index bcc509f..0c4b1c7 100755 --- a/scripts/render-envs.sh +++ b/scripts/render-envs.sh @@ -102,10 +102,29 @@ mkdir -p "$OUT" mask() { sed -E \ -e 's/^([[:space:]]*redis-password:).*/\1 /' \ + -e 's/^([[:space:]]*prometheusPassword:).*/\1 /' \ -e 's/^([[:space:]]*minInstances:).*/\1 /' \ -e 's/^([[:space:]]*maxInstances:).*/\1 /' } +# Rendering the same input twice must produce the same output. This is what +# actually keeps the mask list honest: prometheusPassword was a second +# randAlphaNum in the same subchart as redis-password, was never masked, and put +# a spurious secret change in every single render diff - which is how a diff +# stops being read. +assert_deterministic() { + local chart="$1" name="$2"; shift 2 + local a b + a="$(helm template determinism-check "$chart" "$@" 2>/dev/null | mask)" + b="$(helm template determinism-check "$chart" "$@" 2>/dev/null | mask)" + if [[ "$a" != "$b" ]]; then + echo "RENDER IS NONDETERMINISTIC for ${name}:" >&2 + diff <(printf '%s' "$a") <(printf '%s' "$b") | grep '^[<>]' | head -6 | sed 's/^/ /' >&2 + echo " A template gained a lookup or a random value. Add it to mask() above." >&2 + return 1 + fi +} + rendered=0 for dir in "$DEPLOY"/$LAYOUT/*/; do env_name="$(basename "$dir")" @@ -130,6 +149,8 @@ for dir in "$DEPLOY"/$LAYOUT/*/; do ns="$(yq -r '.hosts.configuration.landing // "default"' "$values")" fi + assert_deterministic "$CHARTS_DIR/$TENANT_CHART" "$env_name" "${extra[@]}" -f "$values" --namespace "$ns" || exit 1 + if ! helm template theia-cloud "$CHARTS_DIR/$TENANT_CHART" \ "${extra[@]}" -f "$values" --namespace "$ns" 2> "$OUT/$env_name.err" | mask > "$OUT/$env_name.yaml"; then echo "RENDER FAILED for $env_name:" >&2