fix(platform): migrate ephemeralStorage to diskSize via pre-upgrade hook

Add pre-upgrade migration 41 that walks all kuberneteses.apps.cozystack.io
Application CRs and renames nodeGroups[*].ephemeralStorage to diskSize,
preserving the user's value.

Without this migration, clusters upgraded after PR #2454 either fail to
reconcile (hard fail blocks all Flux operations) or silently lose the user's
disk size setting (default 20Gi replaces whatever was configured).

The migration is idempotent and best-effort: a failed patch is logged and
leaves the version stamp at 41 for retry on next upgrade.

Update the chart-side guard error message to direct operators to the
migration job logs when the field appears post-upgrade (regression detector).
Bump migrations.targetVersion 41 -> 42.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
(cherry picked from commit ed1bb53d2c)
This commit is contained in:
IvanHunters authored and Myasnikov Daniil committed 2026-05-21 17:37:38 +05:00
1 parent a5a4236aff
commit eee5eb9848
5 files changed
+119 -6

No files matched your search

+1 -1
View File
@@ -85,7 +85,7 @@ See the reference for components utilized in this service:
## Breaking Changes
- **`ephemeralStorage` renamed to `diskSize`**: The `nodeGroups[name].ephemeralStorage` field has been renamed to `nodeGroups[name].diskSize` to better reflect its purpose (persistent disk for kubelet and containerd data). There is no backward-compatibility fallback; users MUST update their configurations to use `diskSize` instead of `ephemeralStorage`. If `ephemeralStorage` is still present in values, Helm template rendering will fail with an error directing you to use `diskSize`. When upgrading the CRD directly (bypassing Helm), the unrecognized field is silently dropped and kubelet storage reverts to the default 20Gi. Existing VMs will be automatically rolling-updated via CAPI when the new values are applied. State persists across same-VM reboots (virt-launcher restart, guest reboot, node failure); VM replacement by CAPI (e.g. nodeGroup field change, MachineHealthCheck remediation) provisions a fresh PVC.
- **`ephemeralStorage` renamed to `diskSize`** (v1.4): The `nodeGroups[name].ephemeralStorage` field has been renamed to `nodeGroups[name].diskSize` to better reflect its purpose (persistent disk for kubelet and containerd data). Existing clusters are migrated transparently by platform migration 41 during the pre-upgrade hook — no manual action is required. Newly written values should use `diskSize`. Existing VMs will be automatically rolling-updated via CAPI when the new values are applied. State persists across same-VM reboots (virt-launcher restart, guest reboot, node failure); VM replacement by CAPI (e.g. nodeGroup field change, MachineHealthCheck remediation) provisions a fresh PVC.
## Parameters
@@ -39,7 +39,7 @@ spec:
cdi.kubevirt.io/storage.usePopulator: "false"
spec:
{{- if hasKey .group "ephemeralStorage" }}
{{- fail (printf "nodeGroup %q: ephemeralStorage is no longer supported. Rename it to diskSize. See README.md for migration instructions." .groupName) }}
{{- fail (printf "nodeGroup %q: ephemeralStorage is no longer supported and should have been automatically migrated to diskSize by platform migration 41. If you see this error after upgrading, the migration did not run — check the cozystack-migration-hook Job logs in cozy-system." .groupName) }}
{{- end }}
source:
blank: {}
@@ -351,7 +351,7 @@ tests:
nodeGroups.md0.ephemeralStorage: 50Gi
asserts:
- failedTemplate:
errorMessage: 'nodeGroup "md0": ephemeralStorage is no longer supported. Rename it to diskSize. See README.md for migration instructions.'
errorMessage: 'nodeGroup "md0": ephemeralStorage is no longer supported and should have been automatically migrated to diskSize by platform migration 41. If you see this error after upgrading, the migration did not run — check the cozystack-migration-hook Job logs in cozy-system.'
- it: fails when ephemeralStorage is set to empty string
release:
@@ -361,7 +361,7 @@ tests:
nodeGroups.md0.ephemeralStorage: ""
asserts:
- failedTemplate:
errorMessage: 'nodeGroup "md0": ephemeralStorage is no longer supported. Rename it to diskSize. See README.md for migration instructions.'
errorMessage: 'nodeGroup "md0": ephemeralStorage is no longer supported and should have been automatically migrated to diskSize by platform migration 41. If you see this error after upgrading, the migration did not run — check the cozystack-migration-hook Job logs in cozy-system.'
- it: fails when both ephemeralStorage and diskSize are set
release:
@@ -372,7 +372,7 @@ tests:
nodeGroups.md0.diskSize: 50Gi
asserts:
- failedTemplate:
errorMessage: 'nodeGroup "md0": ephemeralStorage is no longer supported. Rename it to diskSize. See README.md for migration instructions.'
errorMessage: 'nodeGroup "md0": ephemeralStorage is no longer supported and should have been automatically migrated to diskSize by platform migration 41. If you see this error after upgrading, the migration did not run — check the cozystack-migration-hook Job logs in cozy-system.'
###############################################
# KubeadmConfigTemplate — files block #
+113
View File
@@ -0,0 +1,113 @@
#!/bin/bash
# Migration 41 --> 42
# Rename nodeGroups[*].ephemeralStorage to nodeGroups[*].diskSize in every
# kuberneteses.apps.cozystack.io Application CR.
#
# PR #2454 renamed the field in the kubernetes app chart with no fallback.
# Without this migration, upgraded clusters either fail to reconcile (hard fail
# behavior) or silently lose their disk size (silent-drop behavior).
#
# Idempotent: re-running on already-migrated values is a no-op
# (has("ephemeralStorage") is false on the post-migration shape).
# Best-effort: a failed patch is logged and leaves the version stamp at 41 so
# the migration retries on the next platform upgrade.
set -euo pipefail
DRY_RUN="${MIGRATION_DRY_RUN:-0}"
FAILURES=0
PATCHED=0
SKIPPED=0
# Emit ephemeralStorage: null rather than dropping the key, because JSON merge
# patch only deletes a field when an explicit null is present in the patch
# document. A patch that simply omits a key leaves the existing key intact.
JQ_TRANSFORM='
walk(
if type == "object" and has("nodeGroups") and (.nodeGroups | type) == "object" then
.nodeGroups |= with_entries(
.value |= (
if type == "object" and has("ephemeralStorage") then
(if has("diskSize") and (.diskSize // "") != "" then .
else .diskSize = .ephemeralStorage end)
| .ephemeralStorage = null
else . end
)
)
else . end
)
'
patch_object() {
local kind="$1" ns="$2" name="$3" selector="$4"
local obj current new patch
obj=$(kubectl get "$kind" --namespace "$ns" "$name" --output json 2>/dev/null || true)
if [ -z "$obj" ]; then SKIPPED=$((SKIPPED + 1)); return 0; fi
current=$(printf '%s' "$obj" | jq -c "$selector // null")
if [ "$current" = "null" ]; then SKIPPED=$((SKIPPED + 1)); return 0; fi
new=$(printf '%s' "$current" | jq -c "$JQ_TRANSFORM")
if [ "$current" = "$new" ]; then SKIPPED=$((SKIPPED + 1)); return 0; fi
patch=$(jq --null-input --argjson val "$new" --arg path "${selector#.}" '
def to_path: split(".");
{} | setpath($path | to_path; $val)
')
if [ "$DRY_RUN" = "1" ]; then
echo "[dry-run] would patch $kind $ns/$name ($selector)"
diff <(printf '%s' "$current" | jq --sort-keys .) \
<(printf '%s' "$new" | jq --sort-keys .) || true
PATCHED=$((PATCHED + 1))
return 0
fi
local kubectl_err
if kubectl_err=$(printf '%s' "$patch" | kubectl patch "$kind" --namespace "$ns" "$name" \
--type merge --patch-file /dev/stdin 2>&1 >/dev/null); then
echo "Patched $kind $ns/$name ($selector)"
PATCHED=$((PATCHED + 1))
else
echo "WARN: failed to patch $kind $ns/$name ($selector): ${kubectl_err:-unknown error}" >&2
FAILURES=$((FAILURES + 1))
fi
}
list_objects() {
local kind="$1" out
if ! out=$(kubectl get "$kind" --all-namespaces \
--output 'jsonpath={range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\n"}{end}' \
2>&1); then
case "$out" in
*"the server doesn't have a resource type"*|*"no matches for kind"*|*"NotFound"*) return 0 ;;
esac
echo "ERROR: kubectl get $kind failed: $out" >&2
return 1
fi
printf '%s' "$out"
}
echo "==> Migrating kuberneteses.apps.cozystack.io"
items=$(list_objects kuberneteses.apps.cozystack.io) || exit 1
while IFS=$'\t' read -r ns name; do
[ -z "$ns" ] && continue
patch_object kuberneteses.apps.cozystack.io "$ns" "$name" .spec
done <<<"$items"
echo "==> Summary: patched=$PATCHED skipped=$SKIPPED failures=$FAILURES" | tee /dev/stderr
if [ "$DRY_RUN" = "1" ]; then
echo "Dry-run complete; version stamp not advanced"
exit 0
fi
if [ "$FAILURES" -gt 0 ]; then
echo "WARN: $FAILURES patch(es) failed; leaving CURRENT_VERSION at 41 for retry on next upgrade."
exit 0
fi
kubectl create configmap --namespace cozy-system cozystack-version \
--from-literal version=42 --dry-run=client --output yaml \
| kubectl apply --filename -
+1 -1
View File
@@ -6,7 +6,7 @@ sourceRef:
migrations:
enabled: false
image: ghcr.io/cozystack/cozystack/platform-migrations:v1.4.0-rc.2@sha256:17390197a9b11d76a8dd1b24c6d1512adb7d3226b28b5bcba8551b14b1ec4be0
targetVersion: 40
targetVersion: 42
# Bundle deployment configuration
bundles:
system: