Ctrl K
ComunidadGuías…

hack/check-worker-image-catalog-defaults.bats

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env bats
# -----------------------------------------------------------------------------
# Ties the kubernetes-nodes chart's default Talos image to the kubernetes-worker-image
# catalog's default golden. The two live in independently edited files:
#
#   packages/apps/kubernetes-nodes/values.yaml      talos.{schematicID,version}
#   packages/system/kubernetes-worker-image/values.yaml   images[], storageClass
#
# A worker pool that opts in with `osImage.builtin: {}` resolves the golden it
# clones from the POOL chart's talos defaults, then requires the catalog to hold a
# matching (schematicID, version) — packages/apps/kubernetes-nodes/templates/
# nodegroup.yaml fails the whole render when it does not. So a one-sided Talos bump
# silently breaks every fresh install using osImage.builtin, and nothing catches it
# until a ~95-minute e2e run does.
#
# The StorageClass is the same trap from the other direction: CDI cannot
# CSI-clone across StorageClasses (it would silently fall back to a host-assisted
# copy over the pod network), so the render also rejects a group whose
# storageClass differs from its golden's. Bumping one file's default alone brings
# that guard down on the default path.
#
# Both checks are cheap and exact, which is the point — they turn a slow, remote
# e2e failure into a local one-second unit test.
#
# Compatible with both `bats` directly and the in-repo cozytest.sh runner, which
# runs each @test in a fresh subshell with `set -u` and does not honor bats
# setup()/teardown().

load test_helper

REPO_ROOT="$(cd "$(dirname "${BATS_TEST_FILENAME:-$0}")/.." && pwd)"
APP_VALUES="$REPO_ROOT/packages/apps/kubernetes-nodes/values.yaml"
CATALOG_VALUES="$REPO_ROOT/packages/system/kubernetes-worker-image/values.yaml"

@test "worker image catalog holds a golden for the pool chart's default Talos (schematicID, version)" {
  sid=$(yq '.talos.schematicID' "$APP_VALUES")
  ver=$(yq '.talos.version' "$APP_VALUES")
  [ -n "$sid" ] && [ "$sid" != "null" ]
  [ -n "$ver" ] && [ "$ver" != "null" ]

  # Any entry may match — the catalog is a list of flavors, not an ordered pair.
  if ! yq '.images[] | .schematicID + " " + .version' "$CATALOG_VALUES" \
       | grep -qxF "$sid $ver"; then
    echo "packages/apps/kubernetes-nodes/values.yaml defaults to Talos:" >&2
    echo "  schematicID: $sid" >&2
    echo "  version:     $ver" >&2
    echo "but the kubernetes-worker-image catalog has no matching images[] entry:" >&2
    yq '.images[] | "  - " + .schematicID + " " + .version' "$CATALOG_VALUES" >&2
    echo "Every worker pool using 'osImage.builtin: {}' would fail to render." >&2
    echo "Add the pair to the catalog, or bump both files together." >&2
    return 1
  fi
}

@test "worker image catalog golden shares the pool chart's default StorageClass" {
  sid=$(yq '.talos.schematicID' "$APP_VALUES")
  ver=$(yq '.talos.version' "$APP_VALUES")
  app_sc=$(yq '.storageClass' "$APP_VALUES")

  # The golden's effective class is its own override when set, else the catalog
  # default — mirroring how the pool chart resolves its class.
  entry_sc=$(SID="$sid" VER="$ver" yq \
    '.images[] | select(.schematicID == strenv(SID) and .version == strenv(VER)) | .storageClass' \
    "$CATALOG_VALUES")
  if [ -z "$entry_sc" ] || [ "$entry_sc" = "null" ]; then
    entry_sc=$(yq '.storageClass' "$CATALOG_VALUES")
  fi

  if [ "$app_sc" != "$entry_sc" ]; then
    echo "StorageClass mismatch on the default osImage.builtin path:" >&2
    echo "  packages/apps/kubernetes-nodes/values.yaml storageClass:  $app_sc" >&2
    echo "  kubernetes-worker-image golden effective storageClass:    $entry_sc" >&2
    echo "CDI cannot CSI-clone across StorageClasses, so the tenant render" >&2
    echo "rejects this outright. Keep the two defaults in step." >&2
    return 1
  fi
}

# -----------------------------------------------------------------------------
# Second family of default-drift guards on the same chart: the Talos <-> Kubernetes
# support matrix. The render guard keyed only on `talos.version`, so a pool could
# reach a different Talos release through `osImage.builtin.version` /
# `osImage.factory.version` and boot a kubelet outside its support window with no
# render failure. The table now lives once in templates/_helpers.tpl and both
# paths consult it.
#
# The symmetry is structural, not observable from a render: the matrix ships one
# row (`v1.13`) whose Kubernetes list is a superset of `values.schema.json`'s
# `version` enum, so no schema-valid pairing can make either call site fail today.
# A helm-unittest case would have to put an out-of-enum `version` in, and
# helm-unittest validates the schema, so it cannot. These two guards cover what a
# render cannot: that both call sites exist, and that the table still means
# something for the defaults the chart ships.

NODES_TEMPLATE="$REPO_ROOT/packages/apps/kubernetes-nodes/templates/nodegroup.yaml"
NODES_SCHEMA="$REPO_ROOT/packages/apps/kubernetes-nodes/values.schema.json"

@test "the support matrix is consulted for the image-resolved Talos version, not only talos.version" {
  calls=$(grep -c 'include "kubernetes-nodes.assertTalosSupportsKubernetes"' "$NODES_TEMPLATE" || true)
  if [ "$calls" -lt 2 ]; then
    echo "nodegroup.yaml calls the Talos support-matrix guard $calls time(s); it needs one for talos.version and one for the version osImage.builtin/osImage.factory resolves to" >&2
    echo "without the second call a pool keeps a supported talos.version and still boots an unsupported Talos minor through its image override, with no render failure" >&2
    return 1
  fi
  # And the second call must be fed the RESOLVED version. Passing talos.version
  # twice would satisfy the count above while checking nothing new.
  if ! grep -q '"talosVersion" \$reqVersion' "$NODES_TEMPLATE"; then
    echo "the second support-matrix call does not pass \$reqVersion, so it re-checks the value the first call already checked" >&2
    return 1
  fi
}

@test "the support matrix covers every Kubernetes version the schema offers, for the default Talos" {
  ver=$(yq '.talos.version' "$APP_VALUES")
  minor=$(printf '%s\n' "$ver" | grep -oE '^v[0-9]+\.[0-9]+')
  [ -n "$minor" ]

  helpers="$REPO_ROOT/packages/apps/kubernetes-nodes/templates/_helpers.tpl"
  # The row is a Helm dict literal: `"v1.13" (list "v1.31" "v1.32" ...)`.
  row=$(grep -oE "\"${minor}\" \(list [^)]*\)" "$helpers" || true)
  if [ -z "$row" ]; then
    echo "the Talos support matrix in $helpers has no row for ${minor}, the default talos.version ($ver)" >&2
    echo "an unlisted Talos minor passes the guard unchecked, so the guard means nothing on the shipped defaults" >&2
    return 1
  fi

  # Every version the schema lets an operator pick must be in that row, or the
  # guard rejects a combination the API advertises as valid. Capture the enum
  # first and refuse an empty read: iterating straight over the command
  # substitution cannot tell "no versions" from "that query found nothing", so a
  # renamed or relocated property would run the loop zero times and report
  # success -- the drift this test exists to catch would be the change that
  # disarms it.
  enum=$(yq -r '.properties.version.enum[]' "$NODES_SCHEMA" || true)
  if [ -z "$enum" ]; then
    echo "could not read .properties.version.enum from $NODES_SCHEMA" >&2
    echo "the enum was renamed, moved, or the file relocated; this guard cannot check the matrix without it" >&2
    return 1
  fi
  for k in $enum; do
    case "$row" in
      *"\"$k\""*) ;;
      *)
        echo "values.schema.json offers version $k, but the Talos ${minor} matrix row does not list it:" >&2
        echo "  $row" >&2
        echo "either the row is stale or the enum gained a version Talos ${minor} does not support" >&2
        return 1 ;;
    esac
  done
}

# -----------------------------------------------------------------------------
# Third guard, and the premise the first one rests on. The catalog is keyed off
# the kubernetes-nodes chart's `talos` defaults, but the parent kubernetes chart
# carries its own copy of the same block since the Phase 2 split, and its own
# values.yaml says to keep the two in sync. Nothing enforced that: the pool
# chart's `make update` copies files/versions.yaml and images/kubectl.tag from
# the parent and not the talos block, so a one-sided bump leaves the parent's
# support-matrix guard reasoning about one Talos release while every worker boots
# another — and the catalog check above would go on passing, because it only ever
# reads one of the two files.

PARENT_VALUES="$REPO_ROOT/packages/apps/kubernetes/values.yaml"

@test "the parent kubernetes chart and kubernetes-nodes agree on their Talos defaults" {
  for key in version schematicID imageFactoryURL installerRepository registryMirrors; do
    parent=$(yq ".talos.${key}" "$PARENT_VALUES")
    pool=$(yq ".talos.${key}" "$APP_VALUES")
    if [ "$parent" != "$pool" ]; then
      echo "talos.${key} differs between the two charts:" >&2
      echo "  packages/apps/kubernetes/values.yaml:       $parent" >&2
      echo "  packages/apps/kubernetes-nodes/values.yaml: $pool" >&2
      echo "both are declared 'keep in sync' and a worker pool follows the second while the parent's support-matrix guard reads the first" >&2
      return 1
    fi
  done
}