From 06a25c1c458eefcb5196636a9c6a043fc4e46eb5 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Fri, 28 Nov 2025 20:07:26 +0100 Subject: [PATCH] [kubernetes] Add version management system with automated version updates Signed-off-by: Andrei Kvapil --- .github/workflows/pre-commit.yml | 2 +- packages/apps/kubernetes/.helmignore | 1 + packages/apps/kubernetes/Makefile | 5 +- packages/apps/kubernetes/README.md | 2 +- packages/apps/kubernetes/files/versions.yaml | 10 +- .../apps/kubernetes/hack/update-versions.sh | 260 ++++++++++++++++++ packages/apps/kubernetes/values.schema.json | 16 +- packages/apps/kubernetes/values.yaml | 12 +- .../cozyrds/kubernetes.yaml | 2 +- 9 files changed, 292 insertions(+), 18 deletions(-) create mode 100755 packages/apps/kubernetes/hack/update-versions.sh diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 685d41fe2..ef1ce088d 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -28,7 +28,7 @@ jobs: - name: Install generate run: | - curl -sSL https://github.com/cozystack/cozyvalues-gen/releases/download/v1.0.5/cozyvalues-gen-linux-amd64.tar.gz | tar -xzvf- -C /usr/local/bin/ cozyvalues-gen + curl -sSL https://github.com/cozystack/cozyvalues-gen/releases/download/v1.0.6/cozyvalues-gen-linux-amd64.tar.gz | tar -xzvf- -C /usr/local/bin/ cozyvalues-gen - name: Run pre-commit hooks run: | diff --git a/packages/apps/kubernetes/.helmignore b/packages/apps/kubernetes/.helmignore index 1ea0ae84f..3de7d4a58 100644 --- a/packages/apps/kubernetes/.helmignore +++ b/packages/apps/kubernetes/.helmignore @@ -1,3 +1,4 @@ .helmignore /logos /Makefile +/hack diff --git a/packages/apps/kubernetes/Makefile b/packages/apps/kubernetes/Makefile index 799a19bc0..ae6dd757f 100644 --- a/packages/apps/kubernetes/Makefile +++ b/packages/apps/kubernetes/Makefile @@ -6,9 +6,12 @@ include ../../../scripts/package.mk generate: cozyvalues-gen -v values.yaml -s values.schema.json -r README.md - yq -o=json -i '.properties.version.enum = (load("files/versions.yaml") | keys)' values.schema.json ../../../hack/update-crd.sh +update: + hack/update-versions.sh + make generate + image: image-ubuntu-container-disk image-kubevirt-cloud-provider image-kubevirt-csi-driver image-cluster-autoscaler image-ubuntu-container-disk: diff --git a/packages/apps/kubernetes/README.md b/packages/apps/kubernetes/README.md index 4bc344a2f..8bff49aa1 100644 --- a/packages/apps/kubernetes/README.md +++ b/packages/apps/kubernetes/README.md @@ -104,7 +104,7 @@ See the reference for components utilized in this service: | `nodeGroups[name].resources.memory` | Memory (RAM) available. | `quantity` | `""` | | `nodeGroups[name].gpus` | List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM). | `[]object` | `[]` | | `nodeGroups[name].gpus[i].name` | Name of GPU, such as "nvidia.com/AD102GL_L40S". | `string` | `""` | -| `version` | Kubernetes version (vMAJOR.MINOR). Supported: 1.28–1.33. | `string` | `v1.33` | +| `version` | Kubernetes major.minor version to deploy | `string` | `v1.33` | | `host` | External hostname for Kubernetes cluster. Defaults to `.` if empty. | `string` | `""` | diff --git a/packages/apps/kubernetes/files/versions.yaml b/packages/apps/kubernetes/files/versions.yaml index f92367512..51a36034c 100644 --- a/packages/apps/kubernetes/files/versions.yaml +++ b/packages/apps/kubernetes/files/versions.yaml @@ -1,6 +1,6 @@ -"v1.28": "v1.28.15" -"v1.29": "v1.29.15" -"v1.30": "v1.30.14" -"v1.31": "v1.31.10" -"v1.32": "v1.32.6" "v1.33": "v1.33.0" +"v1.32": "v1.32.10" +"v1.31": "v1.31.14" +"v1.30": "v1.30.14" +"v1.29": "v1.29.15" +"v1.28": "v1.28.15" diff --git a/packages/apps/kubernetes/hack/update-versions.sh b/packages/apps/kubernetes/hack/update-versions.sh new file mode 100755 index 000000000..85c962c53 --- /dev/null +++ b/packages/apps/kubernetes/hack/update-versions.sh @@ -0,0 +1,260 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +KUBERNETES_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" +VALUES_FILE="${KUBERNETES_DIR}/values.yaml" +VERSIONS_FILE="${KUBERNETES_DIR}/files/versions.yaml" +MAKEFILE="${KUBERNETES_DIR}/Makefile" +KAMAJI_DOCKERFILE="${KUBERNETES_DIR}/../../system/kamaji/images/kamaji/Dockerfile" + +# Check if skopeo is installed +if ! command -v skopeo &> /dev/null; then + echo "Error: skopeo is not installed. Please install skopeo and try again." >&2 + exit 1 +fi + +# Check if jq is installed +if ! command -v jq &> /dev/null; then + echo "Error: jq is not installed. Please install jq and try again." >&2 + exit 1 +fi + +# Get kamaji version from Dockerfile +echo "Reading kamaji version from Dockerfile..." +if [ ! -f "$KAMAJI_DOCKERFILE" ]; then + echo "Error: Kamaji Dockerfile not found at $KAMAJI_DOCKERFILE" >&2 + exit 1 +fi + +KAMAJI_VERSION=$(grep "^ARG VERSION=" "$KAMAJI_DOCKERFILE" | cut -d= -f2 | tr -d '"') +if [ -z "$KAMAJI_VERSION" ]; then + echo "Error: Could not extract kamaji version from Dockerfile" >&2 + exit 1 +fi + +echo "Kamaji version: $KAMAJI_VERSION" + +# Get Kubernetes version from kamaji repository +echo "Fetching Kubernetes version from kamaji repository..." +KUBERNETES_VERSION_FROM_KAMAJI=$(curl -sSL "https://raw.githubusercontent.com/clastix/kamaji/${KAMAJI_VERSION}/internal/upgrade/kubeadm_version.go" | grep "KubeadmVersion" | sed -E 's/.*KubeadmVersion = "([^"]+)".*/\1/') + +if [ -z "$KUBERNETES_VERSION_FROM_KAMAJI" ]; then + echo "Error: Could not fetch Kubernetes version from kamaji repository" >&2 + exit 1 +fi + +echo "Kubernetes version from kamaji: $KUBERNETES_VERSION_FROM_KAMAJI" + +# Extract major.minor version (e.g., "1.33" from "v1.33.0") +KUBERNETES_MAJOR_MINOR=$(echo "$KUBERNETES_VERSION_FROM_KAMAJI" | sed -E 's/v([0-9]+)\.([0-9]+)\.[0-9]+/\1.\2/') +KUBERNETES_MAJOR=$(echo "$KUBERNETES_MAJOR_MINOR" | cut -d. -f1) +KUBERNETES_MINOR=$(echo "$KUBERNETES_MAJOR_MINOR" | cut -d. -f2) + +echo "Kubernetes major.minor: $KUBERNETES_MAJOR_MINOR" + +# Get available image tags +echo "Fetching available image tags from registry..." +AVAILABLE_TAGS=$(skopeo list-tags docker://registry.k8s.io/kube-apiserver | jq -r '.Tags[] | select(test("^v[0-9]+\\.[0-9]+\\.[0-9]+$"))' | sort -V) + +if [ -z "$AVAILABLE_TAGS" ]; then + echo "Error: Could not fetch available image tags" >&2 + exit 1 +fi + +# Filter out versions higher than KUBERNETES_VERSION_FROM_KAMAJI +echo "Filtering versions above ${KUBERNETES_VERSION_FROM_KAMAJI}..." +FILTERED_TAGS=$(echo "$AVAILABLE_TAGS" | while read tag; do + if [ -n "$tag" ]; then + # Compare tag with KUBERNETES_VERSION_FROM_KAMAJI using version sort + # Include tag if it's less than or equal to KUBERNETES_VERSION_FROM_KAMAJI + if [ "$(printf '%s\n%s\n' "$tag" "$KUBERNETES_VERSION_FROM_KAMAJI" | sort -V | head -1)" = "$tag" ] || [ "$tag" = "$KUBERNETES_VERSION_FROM_KAMAJI" ]; then + echo "$tag" + fi + fi +done) + +if [ -z "$FILTERED_TAGS" ]; then + echo "Error: No versions found after filtering" >&2 + exit 1 +fi + +AVAILABLE_TAGS="$FILTERED_TAGS" +echo "Filtered to $(echo "$AVAILABLE_TAGS" | wc -l | tr -d ' ') versions" + +# Find the latest patch version for the supported major.minor version +echo "Finding latest patch version for ${KUBERNETES_MAJOR_MINOR}..." +SUPPORTED_PATCH_TAGS=$(echo "$AVAILABLE_TAGS" | grep "^v${KUBERNETES_MAJOR}\\.${KUBERNETES_MINOR}\\.") +if [ -z "$SUPPORTED_PATCH_TAGS" ]; then + echo "Error: Could not find any patch versions for ${KUBERNETES_MAJOR_MINOR}" >&2 + exit 1 +fi +KUBERNETES_VERSION=$(echo "$SUPPORTED_PATCH_TAGS" | tail -n1) +echo "Using latest patch version: $KUBERNETES_VERSION" + +# Build versions map: major.minor -> latest patch version +# First, collect all unique major.minor versions from available tags +echo "Collecting all available major.minor versions..." +ALL_MAJOR_MINOR_VERSIONS=$(echo "$AVAILABLE_TAGS" | sed -E 's/v([0-9]+)\.([0-9]+)\.[0-9]+/v\1.\2/' | sort -V -u) + +# Find the position of the supported version in the sorted list +SUPPORTED_MAJOR_MINOR="v${KUBERNETES_MAJOR}.${KUBERNETES_MINOR}" +echo "Looking for supported version: $SUPPORTED_MAJOR_MINOR" + +# Get all versions that are <= supported version +# Create a temporary file for filtering +TEMP_VERSIONS=$(mktemp) + +echo "$ALL_MAJOR_MINOR_VERSIONS" | while read version; do + # Compare versions using sort -V (version sort) + # If version <= supported, include it + if [ "$(printf '%s\n%s\n' "$version" "$SUPPORTED_MAJOR_MINOR" | sort -V | head -1)" = "$version" ] || [ "$version" = "$SUPPORTED_MAJOR_MINOR" ]; then + echo "$version" + fi +done > "$TEMP_VERSIONS" + +# Get the supported version and 5 previous versions (total 6 versions) +# First, find the position of supported version +SUPPORTED_POS=$(grep -n "^${SUPPORTED_MAJOR_MINOR}$" "$TEMP_VERSIONS" | cut -d: -f1) + +if [ -z "$SUPPORTED_POS" ]; then + echo "Error: Supported version $SUPPORTED_MAJOR_MINOR not found in available versions" >&2 + exit 1 +fi + +# Calculate start position (5 versions before supported, or from beginning if less than 5 available) +TOTAL_LINES=$(wc -l < "$TEMP_VERSIONS" | tr -d ' ') +START_POS=$((SUPPORTED_POS - 5)) +if [ $START_POS -lt 1 ]; then + START_POS=1 +fi + +# Extract versions from START_POS to SUPPORTED_POS (inclusive) +CANDIDATE_VERSIONS=$(sed -n "${START_POS},${SUPPORTED_POS}p" "$TEMP_VERSIONS") + +if [ -z "$CANDIDATE_VERSIONS" ]; then + echo "Error: Could not find supported version $SUPPORTED_MAJOR_MINOR in available versions" >&2 + exit 1 +fi + +declare -A VERSION_MAP +VERSIONS=() + +# Process each candidate version +for major_minor_key in $CANDIDATE_VERSIONS; do + # Extract major and minor for matching + major=$(echo "$major_minor_key" | sed -E 's/v([0-9]+)\.([0-9]+)/\1/') + minor=$(echo "$major_minor_key" | sed -E 's/v([0-9]+)\.([0-9]+)/\2/') + + # Find all tags that match this major.minor version + matching_tags=$(echo "$AVAILABLE_TAGS" | grep "^v${major}\\.${minor}\\.") + + if [ -n "$matching_tags" ]; then + # Get the latest patch version for this major.minor version + latest_tag=$(echo "$matching_tags" | tail -n1) + + VERSION_MAP["${major_minor_key}"]="${latest_tag}" + VERSIONS+=("${major_minor_key}") + echo "Found version: ${major_minor_key} -> ${latest_tag}" + fi +done + +if [ ${#VERSIONS[@]} -eq 0 ]; then + echo "Error: No matching versions found" >&2 + exit 1 +fi + +# Sort versions in descending order (newest first) +IFS=$'\n' VERSIONS=($(printf '%s\n' "${VERSIONS[@]}" | sort -V -r)) +unset IFS + +echo "Versions to add: ${VERSIONS[*]}" + +# Create/update versions.yaml file +echo "Updating $VERSIONS_FILE..." +{ + for ver in "${VERSIONS[@]}"; do + echo "\"${ver}\": \"${VERSION_MAP[$ver]}\"" + done +} > "$VERSIONS_FILE" + +echo "Successfully updated $VERSIONS_FILE" + +# Update values.yaml - enum with major.minor versions only +TEMP_FILE=$(mktemp) +trap "rm -f $TEMP_FILE $TEMP_VERSIONS" EXIT + +# Build new version section +NEW_VERSION_SECTION="## @enum {string} Version" +for ver in "${VERSIONS[@]}"; do + NEW_VERSION_SECTION="${NEW_VERSION_SECTION} +## @value $ver" +done +NEW_VERSION_SECTION="${NEW_VERSION_SECTION} + +## @param {Version} version - Kubernetes major.minor version to deploy +version: \"${VERSIONS[0]}\"" + +# Check if version section already exists +if grep -q "^## @enum {string} Version" "$VALUES_FILE"; then + # Version section exists, update it using awk + echo "Updating existing version section in $VALUES_FILE..." + + # Use awk to replace the section from "## @enum {string} Version" to "version: " (inclusive) + # Delete the old section and insert the new one + awk -v new_section="$NEW_VERSION_SECTION" ' + /^## @enum {string} Version/ { + in_section = 1 + print new_section + next + } + in_section && /^version: / { + in_section = 0 + next + } + in_section { + next + } + { print } + ' "$VALUES_FILE" > "$TEMP_FILE.tmp" + mv "$TEMP_FILE.tmp" "$VALUES_FILE" +else + # Version section doesn't exist, insert it before Application-specific parameters section + echo "Inserting new version section in $VALUES_FILE..." + + # Use awk to insert before "## @section Application-specific parameters" + awk -v new_section="$NEW_VERSION_SECTION" ' + /^## @section Application-specific parameters/ { + print new_section + print "" + } + { print } + ' "$VALUES_FILE" > "$TEMP_FILE.tmp" + mv "$TEMP_FILE.tmp" "$VALUES_FILE" +fi + +echo "Successfully updated $VALUES_FILE with versions: ${VERSIONS[*]}" + +# Update KUBERNETES_VERSION in Makefile +# Extract major.minor from KUBERNETES_VERSION (e.g., "v1.33" from "v1.33.4") +KUBERNETES_MAJOR_MINOR_FOR_MAKEFILE=$(echo "$KUBERNETES_VERSION" | sed -E 's/v([0-9]+)\.([0-9]+)\.[0-9]+/v\1.\2/') + +if grep -q "^KUBERNETES_VERSION" "$MAKEFILE"; then + # Update existing KUBERNETES_VERSION line using awk + echo "Updating KUBERNETES_VERSION in $MAKEFILE..." + awk -v new_version="${KUBERNETES_MAJOR_MINOR_FOR_MAKEFILE}" ' + /^KUBERNETES_VERSION = / { + print "KUBERNETES_VERSION = " new_version + next + } + { print } + ' "$MAKEFILE" > "$TEMP_FILE.tmp" + mv "$TEMP_FILE.tmp" "$MAKEFILE" + echo "Successfully updated KUBERNETES_VERSION in $MAKEFILE to ${KUBERNETES_MAJOR_MINOR_FOR_MAKEFILE}" +else + echo "Warning: KUBERNETES_VERSION not found in $MAKEFILE" >&2 +fi + diff --git a/packages/apps/kubernetes/values.schema.json b/packages/apps/kubernetes/values.schema.json index 6092d35e2..9ec0c4169 100644 --- a/packages/apps/kubernetes/values.schema.json +++ b/packages/apps/kubernetes/values.schema.json @@ -492,7 +492,7 @@ } }, "host": { - "description": "External hostname for Kubernetes cluster. Defaults to `.` if empty.", + "description": "External hostname for Kubernetes cluster. Defaults to `\u003ccluster-name\u003e.\u003ctenant-host\u003e` if empty.", "type": "string", "default": "" }, @@ -615,17 +615,17 @@ "default": "replicated" }, "version": { - "description": "Kubernetes version (vMAJOR.MINOR). Supported: 1.28–1.33.", + "description": "Kubernetes major.minor version to deploy", "type": "string", "default": "v1.33", "enum": [ - "v1.28", - "v1.29", - "v1.30", - "v1.31", + "v1.33", "v1.32", - "v1.33" + "v1.31", + "v1.30", + "v1.29", + "v1.28" ] } } -} +} \ No newline at end of file diff --git a/packages/apps/kubernetes/values.yaml b/packages/apps/kubernetes/values.yaml index 312e06341..567d2e025 100644 --- a/packages/apps/kubernetes/values.yaml +++ b/packages/apps/kubernetes/values.yaml @@ -46,9 +46,19 @@ nodeGroups: resources: {} gpus: [] -## @param {string} version - Kubernetes version (vMAJOR.MINOR). Supported: 1.28–1.33. +## +## @enum {string} Version +## @value v1.33 +## @value v1.32 +## @value v1.31 +## @value v1.30 +## @value v1.29 +## @value v1.28 + +## @param {Version} version - Kubernetes major.minor version to deploy version: "v1.33" + ## @param {string} host - External hostname for Kubernetes cluster. Defaults to `.` if empty. host: "" diff --git a/packages/system/cozystack-resource-definitions/cozyrds/kubernetes.yaml b/packages/system/cozystack-resource-definitions/cozyrds/kubernetes.yaml index 86e8694db..05ed110f9 100644 --- a/packages/system/cozystack-resource-definitions/cozyrds/kubernetes.yaml +++ b/packages/system/cozystack-resource-definitions/cozyrds/kubernetes.yaml @@ -8,7 +8,7 @@ spec: singular: kubernetes plural: kuberneteses openAPISchema: |- - {"title":"Chart Values","type":"object","properties":{"addons":{"description":"Cluster addons configuration.","type":"object","default":{},"required":["certManager","cilium","coredns","fluxcd","gatewayAPI","gpuOperator","ingressNginx","monitoringAgents","velero","verticalPodAutoscaler"],"properties":{"certManager":{"description":"Cert-manager addon.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable cert-manager.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"cilium":{"description":"Cilium CNI plugin.","type":"object","default":{},"required":["valuesOverride"],"properties":{"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"coredns":{"description":"CoreDNS addon.","type":"object","default":{},"required":["valuesOverride"],"properties":{"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"fluxcd":{"description":"FluxCD GitOps operator.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable FluxCD.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"gatewayAPI":{"description":"Gateway API addon.","type":"object","default":{},"required":["enabled"],"properties":{"enabled":{"description":"Enable Gateway API.","type":"boolean","default":false}}},"gpuOperator":{"description":"NVIDIA GPU Operator.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable GPU Operator.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"ingressNginx":{"description":"Ingress-NGINX controller.","type":"object","default":{},"required":["enabled","exposeMethod","valuesOverride"],"properties":{"enabled":{"description":"Enable the controller (requires nodes labeled `ingress-nginx`).","type":"boolean","default":false},"exposeMethod":{"description":"Method to expose the controller. Allowed values: `Proxied`, `LoadBalancer`.","type":"string","default":"Proxied"},"hosts":{"description":"Domains routed to this tenant cluster when `exposeMethod` is `Proxied`.","type":"array","default":[],"items":{"type":"string"}},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"monitoringAgents":{"description":"Monitoring agents.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable monitoring agents.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"velero":{"description":"Velero backup/restore addon.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable Velero.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"verticalPodAutoscaler":{"description":"Vertical Pod Autoscaler.","type":"object","default":{},"required":["valuesOverride"],"properties":{"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}}}},"controlPlane":{"description":"Kubernetes control-plane configuration.","type":"object","default":{},"required":["apiServer","controllerManager","konnectivity","replicas","scheduler"],"properties":{"apiServer":{"description":"API Server configuration.","type":"object","default":{},"required":["resources","resourcesPreset"],"properties":{"resources":{"description":"CPU and memory resources for API Server.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory (RAM) available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"resourcesPreset":{"description":"Preset if `resources` omitted.","type":"string","default":"medium","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"controllerManager":{"description":"Controller Manager configuration.","type":"object","default":{},"required":["resources","resourcesPreset"],"properties":{"resources":{"description":"CPU and memory resources for Controller Manager.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory (RAM) available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"resourcesPreset":{"description":"Preset if `resources` omitted.","type":"string","default":"micro","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"konnectivity":{"description":"Konnectivity configuration.","type":"object","default":{},"required":["server"],"properties":{"server":{"description":"Konnectivity Server configuration.","type":"object","default":{},"required":["resources","resourcesPreset"],"properties":{"resources":{"description":"CPU and memory resources for Konnectivity.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory (RAM) available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"resourcesPreset":{"description":"Preset if `resources` omitted.","type":"string","default":"micro","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}}}},"replicas":{"description":"Number of control-plane replicas.","type":"integer","default":2},"scheduler":{"description":"Scheduler configuration.","type":"object","default":{},"required":["resources","resourcesPreset"],"properties":{"resources":{"description":"CPU and memory resources for Scheduler.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory (RAM) available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"resourcesPreset":{"description":"Preset if `resources` omitted.","type":"string","default":"micro","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}}}},"host":{"description":"External hostname for Kubernetes cluster. Defaults to `.` if empty.","type":"string","default":""},"nodeGroups":{"description":"Worker nodes configuration map.","type":"object","default":{"md0":{"ephemeralStorage":"20Gi","gpus":[],"instanceType":"u1.medium","maxReplicas":10,"minReplicas":0,"resources":{},"roles":["ingress-nginx"]}},"additionalProperties":{"type":"object","required":["ephemeralStorage","instanceType","maxReplicas","minReplicas","resources"],"properties":{"ephemeralStorage":{"description":"Ephemeral storage size.","default":"20Gi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"gpus":{"description":"List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM).","type":"array","items":{"type":"object","required":["name"],"properties":{"name":{"description":"Name of GPU, such as \"nvidia.com/AD102GL_L40S\".","type":"string"}}}},"instanceType":{"description":"Virtual machine instance type.","type":"string","default":"u1.medium"},"maxReplicas":{"description":"Maximum number of replicas.","type":"integer","default":10},"minReplicas":{"description":"Minimum number of replicas.","type":"integer","default":0},"resources":{"description":"CPU and memory resources for each worker node.","type":"object","properties":{"cpu":{"description":"CPU available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory (RAM) available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"roles":{"description":"List of node roles.","type":"array","items":{"type":"string"}}}}},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":"replicated"},"version":{"description":"Kubernetes version (vMAJOR.MINOR). Supported: 1.28–1.33.","type":"string","default":"v1.33","enum":["v1.28","v1.29","v1.30","v1.31","v1.32","v1.33"]}}} + {"title":"Chart Values","type":"object","properties":{"addons":{"description":"Cluster addons configuration.","type":"object","default":{},"required":["certManager","cilium","coredns","fluxcd","gatewayAPI","gpuOperator","ingressNginx","monitoringAgents","velero","verticalPodAutoscaler"],"properties":{"certManager":{"description":"Cert-manager addon.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable cert-manager.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"cilium":{"description":"Cilium CNI plugin.","type":"object","default":{},"required":["valuesOverride"],"properties":{"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"coredns":{"description":"CoreDNS addon.","type":"object","default":{},"required":["valuesOverride"],"properties":{"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"fluxcd":{"description":"FluxCD GitOps operator.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable FluxCD.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"gatewayAPI":{"description":"Gateway API addon.","type":"object","default":{},"required":["enabled"],"properties":{"enabled":{"description":"Enable Gateway API.","type":"boolean","default":false}}},"gpuOperator":{"description":"NVIDIA GPU Operator.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable GPU Operator.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"ingressNginx":{"description":"Ingress-NGINX controller.","type":"object","default":{},"required":["enabled","exposeMethod","valuesOverride"],"properties":{"enabled":{"description":"Enable the controller (requires nodes labeled `ingress-nginx`).","type":"boolean","default":false},"exposeMethod":{"description":"Method to expose the controller. Allowed values: `Proxied`, `LoadBalancer`.","type":"string","default":"Proxied"},"hosts":{"description":"Domains routed to this tenant cluster when `exposeMethod` is `Proxied`.","type":"array","default":[],"items":{"type":"string"}},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"monitoringAgents":{"description":"Monitoring agents.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable monitoring agents.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"velero":{"description":"Velero backup/restore addon.","type":"object","default":{},"required":["enabled","valuesOverride"],"properties":{"enabled":{"description":"Enable Velero.","type":"boolean","default":false},"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}},"verticalPodAutoscaler":{"description":"Vertical Pod Autoscaler.","type":"object","default":{},"required":["valuesOverride"],"properties":{"valuesOverride":{"description":"Custom Helm values overrides.","type":"object","default":{},"x-kubernetes-preserve-unknown-fields":true}}}}},"controlPlane":{"description":"Kubernetes control-plane configuration.","type":"object","default":{},"required":["apiServer","controllerManager","konnectivity","replicas","scheduler"],"properties":{"apiServer":{"description":"API Server configuration.","type":"object","default":{},"required":["resources","resourcesPreset"],"properties":{"resources":{"description":"CPU and memory resources for API Server.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory (RAM) available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"resourcesPreset":{"description":"Preset if `resources` omitted.","type":"string","default":"medium","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"controllerManager":{"description":"Controller Manager configuration.","type":"object","default":{},"required":["resources","resourcesPreset"],"properties":{"resources":{"description":"CPU and memory resources for Controller Manager.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory (RAM) available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"resourcesPreset":{"description":"Preset if `resources` omitted.","type":"string","default":"micro","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}},"konnectivity":{"description":"Konnectivity configuration.","type":"object","default":{},"required":["server"],"properties":{"server":{"description":"Konnectivity Server configuration.","type":"object","default":{},"required":["resources","resourcesPreset"],"properties":{"resources":{"description":"CPU and memory resources for Konnectivity.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory (RAM) available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"resourcesPreset":{"description":"Preset if `resources` omitted.","type":"string","default":"micro","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}}}},"replicas":{"description":"Number of control-plane replicas.","type":"integer","default":2},"scheduler":{"description":"Scheduler configuration.","type":"object","default":{},"required":["resources","resourcesPreset"],"properties":{"resources":{"description":"CPU and memory resources for Scheduler.","type":"object","default":{},"properties":{"cpu":{"description":"CPU available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory (RAM) available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"resourcesPreset":{"description":"Preset if `resources` omitted.","type":"string","default":"micro","enum":["nano","micro","small","medium","large","xlarge","2xlarge"]}}}}},"host":{"description":"External hostname for Kubernetes cluster. Defaults to `.` if empty.","type":"string","default":""},"nodeGroups":{"description":"Worker nodes configuration map.","type":"object","default":{"md0":{"ephemeralStorage":"20Gi","gpus":[],"instanceType":"u1.medium","maxReplicas":10,"minReplicas":0,"resources":{},"roles":["ingress-nginx"]}},"additionalProperties":{"type":"object","required":["ephemeralStorage","instanceType","maxReplicas","minReplicas","resources"],"properties":{"ephemeralStorage":{"description":"Ephemeral storage size.","default":"20Gi","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"gpus":{"description":"List of GPUs to attach (NVIDIA driver requires at least 4 GiB RAM).","type":"array","items":{"type":"object","required":["name"],"properties":{"name":{"description":"Name of GPU, such as \"nvidia.com/AD102GL_L40S\".","type":"string"}}}},"instanceType":{"description":"Virtual machine instance type.","type":"string","default":"u1.medium"},"maxReplicas":{"description":"Maximum number of replicas.","type":"integer","default":10},"minReplicas":{"description":"Minimum number of replicas.","type":"integer","default":0},"resources":{"description":"CPU and memory resources for each worker node.","type":"object","properties":{"cpu":{"description":"CPU available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true},"memory":{"description":"Memory (RAM) available.","pattern":"^(\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\\+|-)?(([0-9]+(\\.[0-9]*)?)|(\\.[0-9]+))))?$","anyOf":[{"type":"integer"},{"type":"string"}],"x-kubernetes-int-or-string":true}}},"roles":{"description":"List of node roles.","type":"array","items":{"type":"string"}}}}},"storageClass":{"description":"StorageClass used to store the data.","type":"string","default":"replicated"},"version":{"description":"Kubernetes major.minor version to deploy","type":"string","default":"v1.33","enum":["v1.33","v1.32","v1.31","v1.30","v1.29","v1.28"]}}} release: prefix: kubernetes- labels: