Skip to content

Commit bc753f6

Browse files
feat(platform): make unprotect strips no-delete label for teardown
Address review feedback from @lexfrei on packages/core/platform/templates/ deletion-protection.yaml: with this PR every protected object — CRDs, ClusterIssuers, cozy-system and tenant-root Namespaces, cozystack-version ConfigMap, cozystack-packages Repository, tenant-root HelmRelease, LinstorCluster, plus the VAP and Binding themselves — carries the no-delete label. `helm uninstall cozy-platform` issues DELETE on each, and DELETE is denied for every one of them, so the chart effectively cannot be uninstalled without manual `kubectl label` against each object. Disaster recovery and dev-cluster reset both regress as a result. Add packages/core/platform/hack/unprotect.sh: enumerates the guarded set locally (per-kind, not via a single all-resources sweep — some kinds may not exist on every install, e.g. linstorcluster on a non-LINSTOR cluster, and kubectl errors on unknown kinds). For cluster-scoped kinds, lists objects with the label and labels them off one at a time. For namespaced kinds, lists (namespace, name) pairs via jsonpath and labels each in the right namespace. Idempotent: a label that is already absent is a no-op. Wire `make unprotect` in packages/core/platform/Makefile so the operator runs it before `helm uninstall`. The Makefile target carries a comment noting this is teardown / disaster-recovery only — after it runs, the cluster has no guardrail until the next `helm upgrade` re-stamps the label. Verified with a stub kubectl in PATH that the script issues the right get/label calls for both cluster-scoped (e.g. `kubectl label customresourcedefinition/packages.cozystack.io platform.cozystack.io/ no-delete-`) and namespaced kinds (e.g. `kubectl label --namespace tenant-root helmrelease tenant-root platform.cozystack.io/no-delete-`). The template-header reference at templates/deletion-protection.yaml that forward-links to `make unprotect` was added in the preceding L3 commit; the recipe it points at now exists. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
1 parent c1ac16e commit bc753f6

2 files changed

Lines changed: 78 additions & 0 deletions

File tree

packages/core/platform/Makefile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ diff:
1717
test:
1818
helm unittest .
1919

20+
# Strip the platform.cozystack.io/no-delete label from every guarded object so
21+
# `helm uninstall cozy-platform` (and ad-hoc deletes of protected resources)
22+
# is no longer denied by the deletion-protection VAP. After this runs, the
23+
# cluster has no guardrail until the next `helm upgrade` re-stamps the label.
24+
# Intended for teardown / disaster-recovery, not routine use.
25+
unprotect:
26+
hack/unprotect.sh
27+
2028
image: image-migrations
2129

2230
image-migrations:
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#!/bin/sh
2+
# Strip the platform.cozystack.io/no-delete=true label from every object that
3+
# carries it, so `helm uninstall cozy-platform` (and ad-hoc `kubectl delete`
4+
# on a protected resource) is no longer denied by the deletion-protection
5+
# ValidatingAdmissionPolicy.
6+
#
7+
# Intended for chart teardown and disaster-recovery — NOT a routine operation.
8+
# After this runs, the cluster has no guardrail until the next `helm upgrade`
9+
# re-stamps the label.
10+
#
11+
# Idempotent: a label that is already absent is a no-op.
12+
#
13+
# The set of guarded kinds is enumerated locally rather than discovered
14+
# dynamically because some of the kinds may not exist in every cluster
15+
# (e.g. linstorcluster on a non-LINSTOR install) and kubectl returns an
16+
# error for unknown resource types. The per-kind get is best-effort.
17+
18+
set -eu
19+
20+
LABEL="platform.cozystack.io/no-delete"
21+
22+
# Cluster-scoped guarded kinds. CRDs and Namespaces sit here, plus the
23+
# VAP/Binding pair (which is also self-labeled) and LinstorCluster.
24+
CLUSTER_KINDS="
25+
customresourcedefinition
26+
clusterissuer
27+
namespace
28+
validatingadmissionpolicy
29+
validatingadmissionpolicybinding
30+
linstorcluster
31+
"
32+
33+
# Namespaced guarded kinds. ConfigMap, Repository/OCIRepository, HelmRelease.
34+
# Use --all-namespaces with -o jsonpath so we get back (ns, name) pairs and
35+
# can pass each to a per-object `kubectl label`.
36+
NS_KINDS="
37+
configmap
38+
ocirepository
39+
helmrelease
40+
"
41+
42+
echo "Removing $LABEL label from all guarded objects..."
43+
44+
for kind in $CLUSTER_KINDS; do
45+
names=$(kubectl get "$kind" --selector="$LABEL=true" --output=name 2>/dev/null || true)
46+
if [ -n "$names" ]; then
47+
# One kubectl per resource — `kubectl label` takes the label op as the
48+
# last argument, so xargs's trailing-args model would put it in the
49+
# wrong position. The protected set is small (<20 objects) so the
50+
# extra round-trips are negligible.
51+
printf '%s\n' "$names" | while read -r ref; do
52+
[ -z "$ref" ] && continue
53+
kubectl label "$ref" "$LABEL-"
54+
done
55+
fi
56+
done
57+
58+
for kind in $NS_KINDS; do
59+
pairs=$(kubectl get "$kind" --all-namespaces --selector="$LABEL=true" \
60+
--output=jsonpath='{range .items[*]}{.metadata.namespace}{" "}{.metadata.name}{"\n"}{end}' \
61+
2>/dev/null || true)
62+
if [ -n "$pairs" ]; then
63+
printf '%s\n' "$pairs" | while read -r ns name; do
64+
[ -z "$name" ] && continue
65+
kubectl label --namespace "$ns" "$kind" "$name" "$LABEL-"
66+
done
67+
fi
68+
done
69+
70+
echo "Done. helm uninstall is now unblocked."

0 commit comments

Comments
 (0)