The pre-delete unpin hook's existence check swallowed every kubectl get
failure (`... >/dev/null 2>&1 || return 0`), four lines below the
comment declaring "Failures are NOT swallowed". kubectl exits 1 for
NotFound and for a transient apiserver error alike, so a timeout during
uninstall read as "object absent": the unpin was skipped, the Job
succeeded, and Helm then skipped the still-keep-annotated
MachineDeployment -- leaking the adopted pool's worker VMs into the
tenant namespace, running and unmanaged, after the pool was deleted.
The KMT-listing path was already hardened for exactly this class; the
existence check was the remaining instance.
Classify by stderr: NotFound (native pool, or already gone) stays a
no-op; any other failure fails the Job, bounded by backoffLimit and
activeDeadlineSeconds, with logs retained. The existence check and the
annotation read fold into a single request.
The embedded script's behavior cannot be asserted from the manifest, so
a new bats suite renders the chart, extracts the Job's script, and
executes it against a fake kubectl: NotFound no-ops and succeeds, a
transient named-get error fails the Job (red against the previous
script, which printed "unpin done" and exited 0), keep-annotated
objects are unpinned with the 6-hex hash anchor respected, and an
unpinned existing object is left untouched.
Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>