diff --git a/packages/core/platform/images/migrations/migrations/29 b/packages/core/platform/images/migrations/migrations/29 new file mode 100755 index 000000000..557fad0e3 --- /dev/null +++ b/packages/core/platform/images/migrations/migrations/29 @@ -0,0 +1,691 @@ +#!/bin/bash +# Migration 29 --> 30 +# Convert virtual-machine HelmReleases to vm-disk + vm-instance. +# Discovers all VirtualMachine HelmReleases by label, migrates each instance. +# Idempotent: safe to re-run. + +set -euo pipefail + +OLD_PREFIX="virtual-machine" +NEW_DISK_PREFIX="vm-disk" +NEW_INSTANCE_PREFIX="vm-instance" +PROTECTION_WEBHOOK_NAME="protection-webhook" +PROTECTION_WEBHOOK_NS="protection-webhook" + +# ============================================================ +# Helper functions +# ============================================================ + +resource_exists() { + local ns="$1" type="$2" name="$3" + kubectl -n "$ns" get "$type" "$name" --no-headers 2>/dev/null | grep -q . +} + +delete_resource() { + local ns="$1" type="$2" name="$3" + if resource_exists "$ns" "$type" "$name"; then + echo " [DELETE] ${type}/${name}" + kubectl -n "$ns" delete "$type" "$name" --wait=false + else + echo " [SKIP] ${type}/${name} already gone" + fi +} + +clone_resource() { + local ns="$1" type="$2" old_name="$3" new_name="$4" old_instance="$5" new_instance="$6" + + if resource_exists "$ns" "$type" "$new_name"; then + echo " [SKIP] ${type}/${new_name} already exists" + return 0 + fi + if ! resource_exists "$ns" "$type" "$old_name"; then + echo " [SKIP] ${type}/${old_name} not found" + return 0 + fi + + echo " [CREATE] ${type}/${new_name} from ${old_name}" + kubectl -n "$ns" get "$type" "$old_name" -o json | \ + jq --arg new_name "$new_name" --arg old "$old_instance" --arg new "$new_instance" ' + .metadata.name = $new_name | + del(.metadata.uid, .metadata.resourceVersion, .metadata.creationTimestamp, + .metadata.generation, .metadata.managedFields, .metadata.selfLink, + .metadata.ownerReferences) | + del(.status) | + .metadata.labels = ((.metadata.labels // {}) | with_entries( + if (.value | type) == "string" then .value |= gsub($old; $new) else . end)) | + .metadata.annotations = ((.metadata.annotations // {}) | with_entries( + select(.key != "kubectl.kubernetes.io/last-applied-configuration") | + if (.value | type) == "string" then .value |= gsub($old; $new) else . end)) + ' | kubectl -n "$ns" apply -f - +} + +# ============================================================ +# STEP 1: Discover all VirtualMachine instances +# ============================================================ +echo "=== Discovering VirtualMachine HelmReleases ===" +INSTANCES=() +while IFS=/ read -r ns name; do + [ -z "$ns" ] && continue + instance="${name#${OLD_PREFIX}-}" + INSTANCES+=("${ns}/${instance}") + echo " Found: ${ns}/${name} (instance=${instance})" +done < <(kubectl get hr -A -l "apps.cozystack.io/application.kind=VirtualMachine" \ + -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}{"\n"}{end}' 2>/dev/null) + +if [ ${#INSTANCES[@]} -eq 0 ]; then + echo " No VirtualMachine HelmReleases found. Nothing to migrate." + kubectl create configmap -n cozy-system cozystack-version \ + --from-literal=version=30 --dry-run=client -o yaml | kubectl apply -f- + exit 0 +fi +echo " Total: ${#INSTANCES[@]} instance(s)" + +# ============================================================ +# STEP 2: Migrate each instance +# ============================================================ +ALL_PV_NAMES=() +ALL_PROTECTED_RESOURCES=() + +for entry in "${INSTANCES[@]}"; do + NAMESPACE="${entry%%/*}" + INSTANCE="${entry#*/}" + OLD_NAME="${OLD_PREFIX}-${INSTANCE}" + NEW_DISK_NAME="${NEW_DISK_PREFIX}-${INSTANCE}" + NEW_INSTANCE_NAME="${NEW_INSTANCE_PREFIX}-${INSTANCE}" + + echo "" + echo "======================================================================" + echo "=== Migrating: ${OLD_NAME} -> ${NEW_DISK_NAME} + ${NEW_INSTANCE_NAME} in ${NAMESPACE}" + echo "======================================================================" + + # --- 2a: Suspend old HelmRelease --- + echo " --- Suspend HelmRelease ---" + if resource_exists "$NAMESPACE" "hr" "$OLD_NAME"; then + suspended=$(kubectl -n "$NAMESPACE" get hr "$OLD_NAME" -o jsonpath='{.spec.suspend}' 2>/dev/null || echo "false") + if [ "$suspended" != "true" ]; then + echo " [SUSPEND] hr/${OLD_NAME}" + kubectl -n "$NAMESPACE" patch hr "$OLD_NAME" --type=merge -p '{"spec":{"suspend":true}}' + else + echo " [SKIP] hr/${OLD_NAME} already suspended" + fi + else + echo " [SKIP] hr/${OLD_NAME} not found" + fi + + # --- 2b: Extract values from HelmRelease --- + echo " --- Extract values ---" + VALUES_SECRET="" + VALUES_KEY="values.yaml" + if resource_exists "$NAMESPACE" "hr" "$OLD_NAME"; then + VALUES_SECRET=$(kubectl -n "$NAMESPACE" get hr "$OLD_NAME" -o json | \ + jq -r '.spec.valuesFrom // [] | map(select(.kind == "Secret" and (.name | test("cozystack-values") | not))) | .[0].name // ""') + if [ -n "$VALUES_SECRET" ]; then + VALUES_KEY=$(kubectl -n "$NAMESPACE" get hr "$OLD_NAME" -o json | \ + jq -r '.spec.valuesFrom // [] | map(select(.kind == "Secret" and (.name | test("cozystack-values") | not))) | .[0].valuesKey // "values.yaml"') + fi + fi + + # Extract values from secret + EXTERNAL="false" + EXTERNAL_METHOD="PortList" + EXTERNAL_PORTS='[22]' + INSTANCE_TYPE="u1.medium" + INSTANCE_PROFILE="ubuntu" + RUN_STRATEGY="Always" + SYSTEM_DISK_IMAGE="ubuntu" + SYSTEM_DISK_STORAGE="5Gi" + SYSTEM_DISK_STORAGE_CLASS="replicated" + SSH_KEYS="[]" + CLOUD_INIT="" + CLOUD_INIT_SEED="" + SUBNETS="[]" + GPUS="[]" + CPU_MODEL="" + RESOURCES="{}" + + if [ -n "$VALUES_SECRET" ] && resource_exists "$NAMESPACE" "secret" "$VALUES_SECRET"; then + echo " Reading values from secret: ${VALUES_SECRET}" + VALUES_YAML=$(kubectl -n "$NAMESPACE" get secret "$VALUES_SECRET" -o jsonpath="{.data.${VALUES_KEY}}" 2>/dev/null | base64 -d 2>/dev/null || echo "") + if [ -z "$VALUES_YAML" ]; then + VALUES_YAML=$(kubectl -n "$NAMESPACE" get secret "$VALUES_SECRET" -o jsonpath="{.stringData.${VALUES_KEY}}" 2>/dev/null || echo "") + fi + if [ -n "$VALUES_YAML" ]; then + EXTERNAL=$(echo "$VALUES_YAML" | yq -r '.external // false') + EXTERNAL_METHOD=$(echo "$VALUES_YAML" | yq -r '.externalMethod // "PortList"') + EXTERNAL_PORTS=$(echo "$VALUES_YAML" | yq -c '.externalPorts // [22]') + INSTANCE_TYPE=$(echo "$VALUES_YAML" | yq -r '.instanceType // "u1.medium"') + INSTANCE_PROFILE=$(echo "$VALUES_YAML" | yq -r '.instanceProfile // "ubuntu"') + RUN_STRATEGY=$(echo "$VALUES_YAML" | yq -r '.runStrategy // (if .running == false then "Halted" else "Always" end)') + SYSTEM_DISK_IMAGE=$(echo "$VALUES_YAML" | yq -r '.systemDisk.image // "ubuntu"') + SYSTEM_DISK_STORAGE=$(echo "$VALUES_YAML" | yq -r '.systemDisk.storage // "5Gi"') + SYSTEM_DISK_STORAGE_CLASS=$(echo "$VALUES_YAML" | yq -r '.systemDisk.storageClass // "replicated"') + SSH_KEYS=$(echo "$VALUES_YAML" | yq -c '.sshKeys // []') + CLOUD_INIT=$(echo "$VALUES_YAML" | yq -r '.cloudInit // ""') + CLOUD_INIT_SEED=$(echo "$VALUES_YAML" | yq -r '.cloudInitSeed // ""') + SUBNETS=$(echo "$VALUES_YAML" | yq -c '.subnets // []') + GPUS=$(echo "$VALUES_YAML" | yq -c '.gpus // []') + CPU_MODEL=$(echo "$VALUES_YAML" | yq -r '.cpuModel // ""') + RESOURCES=$(echo "$VALUES_YAML" | yq -c '.resources // {}') + fi + fi + + echo " external=${EXTERNAL}, instanceType=${INSTANCE_TYPE}, image=${SYSTEM_DISK_IMAGE}" + + # --- 2c: Save kube-ovn IP --- + echo " --- Save kube-ovn IP ---" + OVN_IP="" + OVN_MAC="" + OVN_SUBNET="ovn-default" + OVN_IP_NAME="${OLD_NAME}.${NAMESPACE}" + if kubectl get ip.kubeovn.io "$OVN_IP_NAME" --no-headers 2>/dev/null | grep -q .; then + OVN_IP=$(kubectl get ip.kubeovn.io "$OVN_IP_NAME" -o jsonpath='{.spec.ipAddress}') + OVN_MAC=$(kubectl get ip.kubeovn.io "$OVN_IP_NAME" -o jsonpath='{.spec.macAddress}') + OVN_SUBNET=$(kubectl get ip.kubeovn.io "$OVN_IP_NAME" -o jsonpath='{.spec.subnet}') + echo " kube-ovn IP: ${OVN_IP}, MAC: ${OVN_MAC}, subnet: ${OVN_SUBNET}" + else + echo " [SKIP] No kube-ovn IP resource found" + fi + + # --- 2d: Save LoadBalancer IP (if external=true) --- + echo " --- Save LoadBalancer IP ---" + LB_IP="" + if [ "$EXTERNAL" = "true" ] && resource_exists "$NAMESPACE" "svc" "$OLD_NAME"; then + LB_IP=$(kubectl -n "$NAMESPACE" get svc "$OLD_NAME" -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null || echo "") + if [ -n "$LB_IP" ]; then + echo " LoadBalancer IP: ${LB_IP}" + else + echo " [WARN] external=true but no LoadBalancer IP assigned" + fi + else + echo " [SKIP] Not external or no service" + fi + + # --- 2e: Switch PV reclaim policy to Retain --- + echo " --- Switch PV reclaim to Retain ---" + PV_NAME="" + if resource_exists "$NAMESPACE" "pvc" "$OLD_NAME"; then + PV_NAME=$(kubectl -n "$NAMESPACE" get pvc "$OLD_NAME" -o jsonpath='{.spec.volumeName}') + if [ -n "$PV_NAME" ]; then + ALL_PV_NAMES+=("$PV_NAME") + current_policy=$(kubectl get pv "$PV_NAME" -o jsonpath='{.spec.persistentVolumeReclaimPolicy}') + if [ "$current_policy" != "Retain" ]; then + echo " [PATCH] PV ${PV_NAME}: ${current_policy} -> Retain" + kubectl patch pv "$PV_NAME" -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}' + else + echo " [SKIP] PV ${PV_NAME} already Retain" + fi + fi + elif resource_exists "$NAMESPACE" "pvc" "$NEW_DISK_NAME"; then + PV_NAME=$(kubectl -n "$NAMESPACE" get pvc "$NEW_DISK_NAME" -o jsonpath='{.spec.volumeName}') + ALL_PV_NAMES+=("$PV_NAME") + echo " [SKIP] New PVC ${NEW_DISK_NAME} already exists, PV=${PV_NAME}" + else + echo " [WARN] No PVC found for ${OLD_NAME} or ${NEW_DISK_NAME}" + fi + + # --- 2f: Stop and delete the VirtualMachine --- + echo " --- Stop VirtualMachine ---" + if resource_exists "$NAMESPACE" "vm" "$OLD_NAME"; then + echo " [PATCH] Stop VM ${OLD_NAME}" + kubectl -n "$NAMESPACE" patch vm "$OLD_NAME" --type merge -p '{"spec":{"runStrategy":"Halted"}}' 2>/dev/null || \ + kubectl -n "$NAMESPACE" patch vm "$OLD_NAME" --type merge -p '{"spec":{"running":false}}' 2>/dev/null || true + + echo " Waiting for VMI to terminate..." + kubectl -n "$NAMESPACE" wait --for=delete vmi "$OLD_NAME" --timeout=120s 2>/dev/null || true + fi + + # --- 2g: Delete VirtualMachine CR (cascades to DataVolume and PVC) --- + echo " --- Delete VirtualMachine CR ---" + if resource_exists "$NAMESPACE" "vm" "$OLD_NAME"; then + echo " [DELETE] vm/${OLD_NAME}" + kubectl -n "$NAMESPACE" delete vm "$OLD_NAME" --wait=true --timeout=60s 2>/dev/null || true + fi + + echo " Waiting for old PVC to be deleted..." + for i in $(seq 1 30); do + if ! resource_exists "$NAMESPACE" "pvc" "$OLD_NAME"; then + echo " [OK] PVC ${OLD_NAME} deleted" + break + fi + sleep 2 + done + + # If PVC still exists (orphaned), delete it manually + if resource_exists "$NAMESPACE" "pvc" "$OLD_NAME"; then + echo " [DELETE] Orphaned pvc/${OLD_NAME}" + kubectl -n "$NAMESPACE" delete pvc "$OLD_NAME" --wait=false 2>/dev/null || true + fi + + # Delete orphaned DataVolume if still present + if resource_exists "$NAMESPACE" "dv" "$OLD_NAME"; then + echo " [DELETE] Orphaned dv/${OLD_NAME}" + kubectl -n "$NAMESPACE" delete dv "$OLD_NAME" --wait=false 2>/dev/null || true + fi + + # --- 2h: Create new PVC for vm-disk --- + echo " --- Create new PVC for vm-disk ---" + if [ -n "$PV_NAME" ]; then + if resource_exists "$NAMESPACE" "pvc" "$NEW_DISK_NAME"; then + new_phase=$(kubectl -n "$NAMESPACE" get pvc "$NEW_DISK_NAME" \ + -o jsonpath='{.status.phase}' 2>/dev/null || echo "unknown") + if [ "$new_phase" = "Bound" ]; then + echo " [SKIP] PVC ${NEW_DISK_NAME} already Bound" + fi + else + echo " [CREATE] PVC ${NEW_DISK_NAME} -> PV ${PV_NAME}" + cat < ${NEW_DISK_NAME}" + kubectl patch pv "$PV_NAME" --type=merge -p "{ + \"spec\":{\"claimRef\":{\"apiVersion\":\"v1\",\"kind\":\"PersistentVolumeClaim\", + \"name\":\"${NEW_DISK_NAME}\",\"namespace\":\"${NAMESPACE}\",\"uid\":\"${new_pvc_uid}\"}} + }" + + echo " Waiting for PVC ${NEW_DISK_NAME} to bind..." + kubectl -n "$NAMESPACE" wait pvc "$NEW_DISK_NAME" \ + --for=jsonpath='{.status.phase}'=Bound --timeout=60s + echo " [OK] PVC ${NEW_DISK_NAME} is Bound" + fi + fi + + # --- 2i: Clone Secrets --- + echo " --- Clone Secrets ---" + for secret in $(kubectl -n "$NAMESPACE" get secret -o name 2>/dev/null \ + | grep "secret/${OLD_NAME}" | grep -v "sh.helm.release" | grep -v "values"); do + old_secret_name="${secret#secret/}" + suffix="${old_secret_name#${OLD_NAME}}" + new_secret_name="${NEW_INSTANCE_NAME}${suffix}" + clone_resource "$NAMESPACE" "secret" "$old_secret_name" "$new_secret_name" "$OLD_NAME" "$NEW_INSTANCE_NAME" + done + + # --- 2j: Clone WorkloadMonitor --- + echo " --- Clone WorkloadMonitor ---" + clone_resource "$NAMESPACE" "workloadmonitor.cozystack.io" "$OLD_NAME" "$NEW_INSTANCE_NAME" "$OLD_NAME" "$NEW_INSTANCE_NAME" + + # --- 2k: Migrate kube-ovn IP --- + echo " --- Migrate kube-ovn IP ---" + NEW_OVN_IP_NAME="${NEW_INSTANCE_NAME}.${NAMESPACE}" + if [ -n "$OVN_IP" ]; then + if kubectl get ip.kubeovn.io "$NEW_OVN_IP_NAME" --no-headers 2>/dev/null | grep -q .; then + echo " [SKIP] ip.kubeovn.io/${NEW_OVN_IP_NAME} already exists" + else + echo " [CREATE] ip.kubeovn.io/${NEW_OVN_IP_NAME}" + cat </dev/null | grep -q .; then + echo " [DELETE] ip.kubeovn.io/${OVN_IP_NAME}" + kubectl delete ip.kubeovn.io "$OVN_IP_NAME" 2>/dev/null || true + fi + fi + + # --- 2l: Create vm-disk values secret --- + echo " --- Create vm-disk values secret ---" + DISK_VALUES_SECRET="${NEW_DISK_NAME}-values" + if ! resource_exists "$NAMESPACE" "secret" "$DISK_VALUES_SECRET"; then + echo " [CREATE] secret/${DISK_VALUES_SECRET}" + cat </dev/null \ + | grep "secret/${OLD_NAME}" | grep -v "sh.helm.release" | grep -v "values"); do + old_secret_name="${secret#secret/}" + delete_resource "$NAMESPACE" "secret" "$old_secret_name" + done + + delete_resource "$NAMESPACE" "workloadmonitor.cozystack.io" "$OLD_NAME" + + if resource_exists "$NAMESPACE" "hr" "$OLD_NAME"; then + kubectl -n "$NAMESPACE" patch hr "$OLD_NAME" --type=json \ + -p='[{"op":"remove","path":"/metadata/finalizers"}]' 2>/dev/null || true + delete_resource "$NAMESPACE" "hr" "$OLD_NAME" + fi + + echo " [DELETE] secrets with label owner=helm,name=${OLD_NAME}" + kubectl -n "$NAMESPACE" delete secret -l "owner=helm,name=${OLD_NAME}" 2>/dev/null || true + + # Delete old values secret + if [ -n "$VALUES_SECRET" ]; then + delete_resource "$NAMESPACE" "secret" "$VALUES_SECRET" + fi + + # Collect protected resources for batch deletion + if resource_exists "$NAMESPACE" "svc" "$OLD_NAME"; then + ALL_PROTECTED_RESOURCES+=("${NAMESPACE}:svc/${OLD_NAME}") + fi +done + +# ============================================================ +# STEP 3: Delete protected resources (Services) +# ============================================================ +echo "" +echo "--- Step 3: Delete protected resources ---" + +if [ ${#ALL_PROTECTED_RESOURCES[@]} -gt 0 ]; then + echo " --- Temporarily disabling protection-webhook ---" + + WEBHOOK_REPLICAS=$(kubectl -n "$PROTECTION_WEBHOOK_NS" get deploy "$PROTECTION_WEBHOOK_NAME" \ + -o jsonpath='{.spec.replicas}' 2>/dev/null || echo "1") + + echo " [SCALE] ${PROTECTION_WEBHOOK_NAME} -> 0 (was ${WEBHOOK_REPLICAS})" + kubectl -n "$PROTECTION_WEBHOOK_NS" scale deploy "$PROTECTION_WEBHOOK_NAME" --replicas=0 + + echo " [PATCH] Set failurePolicy=Ignore on ValidatingWebhookConfiguration/${PROTECTION_WEBHOOK_NAME}" + kubectl get validatingwebhookconfiguration "$PROTECTION_WEBHOOK_NAME" -o json | \ + jq '.webhooks[].failurePolicy = "Ignore"' | \ + kubectl apply -f - + + echo " Waiting for webhook pods to terminate..." + kubectl -n "$PROTECTION_WEBHOOK_NS" wait --for=delete pod \ + -l app.kubernetes.io/name=protection-webhook --timeout=60s 2>/dev/null || true + sleep 3 + + for entry in "${ALL_PROTECTED_RESOURCES[@]}"; do + ns="${entry%%:*}" + res="${entry#*:}" + echo " [DELETE] ${ns}/${res}" + kubectl -n "$ns" delete "$res" --wait=false 2>/dev/null || true + done + + echo " [PATCH] Set failurePolicy=Fail on ValidatingWebhookConfiguration/${PROTECTION_WEBHOOK_NAME}" + kubectl get validatingwebhookconfiguration "$PROTECTION_WEBHOOK_NAME" -o json | \ + jq '.webhooks[].failurePolicy = "Fail"' | \ + kubectl apply -f - + + echo " [SCALE] ${PROTECTION_WEBHOOK_NAME} -> ${WEBHOOK_REPLICAS}" + kubectl -n "$PROTECTION_WEBHOOK_NS" scale deploy "$PROTECTION_WEBHOOK_NAME" \ + --replicas="$WEBHOOK_REPLICAS" + echo " --- protection-webhook restored ---" +else + echo " [SKIP] No protected resources to delete" +fi + +# ============================================================ +# STEP 4: Restore PV reclaim policies +# ============================================================ +echo "" +echo "--- Step 4: Restore PV reclaim policies ---" +for pv_name in "${ALL_PV_NAMES[@]}"; do + if [ -n "$pv_name" ]; then + current_policy=$(kubectl get pv "$pv_name" \ + -o jsonpath='{.spec.persistentVolumeReclaimPolicy}' 2>/dev/null || echo "unknown") + if [ "$current_policy" = "Retain" ]; then + echo " [PATCH] PV ${pv_name}: Retain -> Delete" + kubectl patch pv "$pv_name" -p '{"spec":{"persistentVolumeReclaimPolicy":"Delete"}}' + else + echo " [SKIP] PV ${pv_name} already ${current_policy}" + fi + fi +done + +# ============================================================ +# STEP 5: Unsuspend vm-disk HelmReleases first +# ============================================================ +echo "" +echo "--- Step 5: Unsuspend vm-disk HelmReleases ---" +for entry in "${INSTANCES[@]}"; do + ns="${entry%%/*}" + instance="${entry#*/}" + disk_name="${NEW_DISK_PREFIX}-${instance}" + if resource_exists "$ns" "hr" "$disk_name"; then + suspended=$(kubectl -n "$ns" get hr "$disk_name" \ + -o jsonpath='{.spec.suspend}' 2>/dev/null || echo "false") + if [ "$suspended" = "true" ]; then + echo " [UNSUSPEND] ${ns}/hr/${disk_name}" + kubectl -n "$ns" patch hr "$disk_name" --type=merge -p '{"spec":{"suspend":false}}' + else + echo " [SKIP] ${ns}/hr/${disk_name} already not suspended" + fi + fi +done + +# Wait for disk HelmReleases to become ready +echo " Waiting for vm-disk HelmReleases to reconcile..." +for entry in "${INSTANCES[@]}"; do + ns="${entry%%/*}" + instance="${entry#*/}" + disk_name="${NEW_DISK_PREFIX}-${instance}" + for i in $(seq 1 30); do + ready=$(kubectl -n "$ns" get hr "$disk_name" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null || echo "") + if [ "$ready" = "True" ]; then + echo " [OK] ${ns}/hr/${disk_name} is Ready" + break + fi + sleep 5 + done +done + +# ============================================================ +# STEP 6: Unsuspend vm-instance HelmReleases +# ============================================================ +echo "" +echo "--- Step 6: Unsuspend vm-instance HelmReleases ---" +for entry in "${INSTANCES[@]}"; do + ns="${entry%%/*}" + instance="${entry#*/}" + instance_name="${NEW_INSTANCE_PREFIX}-${instance}" + if resource_exists "$ns" "hr" "$instance_name"; then + suspended=$(kubectl -n "$ns" get hr "$instance_name" \ + -o jsonpath='{.spec.suspend}' 2>/dev/null || echo "false") + if [ "$suspended" = "true" ]; then + echo " [UNSUSPEND] ${ns}/hr/${instance_name}" + kubectl -n "$ns" patch hr "$instance_name" --type=merge -p '{"spec":{"suspend":false}}' + else + echo " [SKIP] ${ns}/hr/${instance_name} already not suspended" + fi + fi +done + +echo "" +echo "=== Migration complete (${#INSTANCES[@]} instance(s)) ===" + +# Stamp version +kubectl create configmap -n cozy-system cozystack-version \ + --from-literal=version=30 --dry-run=client -o yaml | kubectl apply -f- diff --git a/packages/core/platform/values.yaml b/packages/core/platform/values.yaml index 9bb8e2b19..38452a172 100644 --- a/packages/core/platform/values.yaml +++ b/packages/core/platform/values.yaml @@ -6,7 +6,7 @@ sourceRef: migrations: enabled: false image: ghcr.io/cozystack/cozystack/platform-migrations:latest - targetVersion: 29 + targetVersion: 30 # Bundle deployment configuration bundles: system: