Upgrading to v0.11: KanidmPersonAccount CRD Plural Migration

This guide describes the upgrade procedure for moving from Kaniop v0.10.2 or older (legacy CRD plural kanidmpersonsaccounts) to v0.11.0+ (corrected CRD plural kanidmpersonaccounts).

If you are already on v0.10.3, the corrected CRD is already in place and the migration Job is a no-op. A standard helm upgrade or Argo CD sync is safe.

Which releases are affected

Release CRD plural Migration required?
v0.10.2 and older kanidmpersonsaccounts (legacy) Yes — must use migration Job
v0.10.3 kanidmpersonaccounts (corrected) No — migration is a no-op
v0.11.0+ kanidmpersonaccounts (corrected) Fresh install or upgrade

Why this migration is required

Releases v0.10.2 and older contained a typo in the CRD plural name:

  Legacy (v0.10.2 and older) Corrected (v0.10.3, v0.11.0+)
CRD name kanidmpersonsaccounts.kaniop.rs kanidmpersonaccounts.kaniop.rs
REST endpoint /apis/kaniop.rs/v1beta1/.../kanidmpersonsaccounts /apis/kaniop.rs/v1beta1/.../kanidmpersonaccounts
Kind KanidmPersonAccount KanidmPersonAccount (unchanged)
API group/version kaniop.rs/v1beta1 kaniop.rs/v1beta1 (unchanged)

Because the CRD’s metadata.name is derived from <plural>.<group>, changing the plural changes the identity of the CRD and its REST endpoint. A normal kubectl apply, patch, or replace cannot safely move existing custom resources to the new endpoint.

What changes and what is preserved

Preserved (safe)

  • Kanidm account identities: UUIDs, credentials, tokens, and all Kanidm-side data are not modified.
  • Person specifications: The spec field of every KanidmPersonAccount is preserved exactly.
  • Labels and annotations: All user-defined labels and annotations survive, including Argo CD tracking metadata (argocd.argoproj.io/tracking-id).
  • Owner references: If a person had an owner reference, it is preserved.

Changed (expected)

  • Kubernetes metadata.uid: Each person resource receives a new UID because the CRD is recreated. Any external system referencing the old UID must be updated.
  • metadata.resourceVersion: Reset by Kubernetes on recreation.
  • metadata.generation: Reset.
  • metadata.creationTimestamp: Set to the time of migration.
  • metadata.managedFields: Reset.

Prohibited actions

Do NOT perform any of the following during this upgrade. They will delete your Kanidm accounts and/or person resources:

  • kubectl replace --force on the CRD
  • kubectl delete crd kanidmpersonsaccounts.kaniop.rs manually
  • Argo CD sync option Replace=true or Force=true on the CRD
  • Manually deleting person resources before or during the migration
  • Selective/resource-filtered Argo CD sync (hooks do not run; see below)

Upgrade procedure

Helm users

The migration is fully automated through Helm hooks. A standard helm upgrade triggers:

  1. PreSync hook: The migration Job runs before any chart resources are applied.
  2. Migration Job: Stops the operator, backs up all person resources, removes the legacy finalizer, deletes the legacy CRD, creates the corrected CRD, and restores all person resources.
  3. PostSync hook: A verification Job confirms that all persons were adopted by the new operator before removing backup Secrets.
# Standard upgrade command - the migration runs automatically
helm upgrade --namespace kaniop --wait kaniop \
  oci://ghcr.io/pando85/helm-charts/kaniop \
  --version v0.11.0

After the upgrade, verify:

# Corrected CRD is established
kubectl get crd kanidmpersonaccounts.kaniop.rs
kubectl wait --for=condition=established crd/kanidmpersonaccounts.kaniop.rs --timeout=60s

# Legacy CRD is gone
kubectl get crd kanidmpersonsaccounts.kaniop.rs  # should return NotFound

# Operator is running
kubectl -n kaniop rollout status deploy/kaniop

# Person resources are present and Ready
kubectl get kanidmpersonaccounts.kaniop.rs -A

Argo CD users

The migration uses Helm hook annotations that Argo CD maps to its own sync phases:

  • helm.sh/hook: pre-install,pre-upgrade → Argo CD PreSync
  • helm.sh/hook: post-install,post-upgrade → Argo CD PostSync
  • helm.sh/hook-weight → Argo CD sync wave

Mandatory: full sync

Argo CD hooks do not run during selective sync. For the migration release, you must perform a full sync of the Kaniop Application.

Do NOT:

  • Sync only the Deployment
  • Sync only the CRD
  • Use a resource-filtered sync
  • Enable ApplyOutOfSyncOnly=true

Do:

spec:
  syncPolicy:
    syncOptions:
      - PruneLast=true
      - SkipDryRunOnMissingResource=true

Do NOT set:

# These are PROHIBITED for the migration release
# Replace=true
# Force=true

The safest configuration uses two Argo CD Applications:

  • Application A: Kaniop chart (CRDs, operator, webhook, migration Jobs)
  • Application B: KanidmPersonAccount and other Kaniop custom resources

During the migration:

  1. Sync Application A (full sync).
  2. Wait for the PostSync verification Job to complete (Application A reaches Healthy+Synced).
  3. Refresh and sync Application B.

If Application B has automated self-heal enabled, temporarily disable it until Application A reports Healthy.

Single Application topology

If CRDs and person resources are in the same Application:

  1. Ensure SkipDryRunOnMissingResource=true is enabled.
  2. Perform a full sync.
  3. Argo CD’s discovery cache may temporarily remember the legacy REST mapping. If the first sync fails, refresh the Application and sync again.

Verification

After the sync completes:

# Check Application status
kubectl -n argocd get application kaniop

# Verify corrected CRD
kubectl get crd kanidmpersonaccounts.kaniop.rs

# Verify persons are present
kubectl get kanidmpersonaccounts.kaniop.rs -A

# Check that Argo CD tracking annotations survived
kubectl get kanidmpersonaccounts.kaniop.rs -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.annotations.argocd\.argoproj\.io/tracking-id}{"\n"}{end}'

Migration safety mechanisms

The migration Job enforces these invariants:

  1. No Kanidm deletion: The operator is scaled to zero before any destructive operation. The migration binary never calls Kanidm’s delete API.
  2. Verified backup before destruction: Every person resource is backed up to a Kubernetes Secret. The backup count, checksums, and namespace/name uniqueness are verified before the legacy CRD is deleted.
  3. Only the known finalizer is removed: The Job removes only kanidmpersonsaccounts.kaniop.rs/finalizer. If any resource has an unknown finalizer, the migration aborts before the destructive boundary.
  4. Resumable: If the Job fails or is interrupted, it can be rerun. It inspects the live cluster state and a marker ConfigMap to resume from the last completed phase.
  5. Idempotent: Running the migration multiple times after success is a no-op.

Migration state and observability

The migration creates a marker ConfigMap in the operator namespace:

kubectl -n kaniop get configmap kaniop-person-crd-migration -o yaml

Key fields:

  • phase: Current migration phase (Discovered, BackedUp, OperatorStopped, FinalizersRemoved, LegacyCRDDeleted, CorrectedCRDCreated, ObjectsRestored, Verified, Completed)
  • sourceCount: Number of person resources found before migration
  • restoredCount: Number of person resources restored
  • legacyCRD: Name of the legacy CRD
  • correctedCRD: Name of the corrected CRD

Backup Secrets are labeled with kaniop.rs/migration=person-plural-v1 and are retained until the PostSync verification Job confirms all persons are adopted. If verification fails, backups remain for manual recovery.

Recovery

If the migration fails before the legacy CRD is deleted

The operator can be restarted and the migration rerun:

# Scale operator back up
kubectl -n kaniop scale deploy kaniop --replicas=1

# Check migration state
kubectl -n kaniop get configmap kaniop-person-crd-migration -o yaml

# Rerun the migration (idempotent)
helm upgrade --namespace kaniop --wait kaniop \
  oci://ghcr.io/pando85/helm-charts/kaniop \
  --version v0.11.0

If the migration fails after the legacy CRD is deleted

This is the forward-only boundary. Recovery must continue toward the corrected CRD:

# Check if the corrected CRD exists
kubectl get crd kanidmpersonaccounts.kaniop.rs

# If not, check backup secrets
kubectl -n kaniop get secret -l kaniop.rs/migration=person-plural-v1

# Rerun the migration to continue from the current phase
helm upgrade --namespace kaniop --wait kaniop \
  oci://ghcr.io/pando85/helm-charts/kaniop \
  --version v0.11.0

Manual recovery

If the Helm hooks or Argo CD sync are not available, the safest recovery path is to rerun the standard upgrade. The migration Job is idempotent and resumable — it inspects the live cluster state and the marker ConfigMap to continue from the last completed phase.

# Helm: rerun the upgrade
helm upgrade --namespace kaniop --wait kaniop \
  oci://ghcr.io/pando85/helm-charts/kaniop \
  --version v0.11.0

# Argo CD: trigger a full sync
argocd app sync kaniop

Backup Secrets (labeled kaniop.rs/migration=person-plural-v1) are retained until the PostSync verification Job confirms all persons are adopted. If verification fails, backups remain available for inspection.

Restoring from backups manually

If all else fails, backup Secrets contain the sanitized person specifications:

# List backups
kubectl -n kaniop get secret -l kaniop.rs/migration=person-plural-v1

# Extract a backup
kubectl -n kaniop get secret kaniop-person-backup-<hash> -o jsonpath='{.data.object\.json}' | base64 -d

# Apply to the corrected endpoint
kubectl apply -f restored-person.yaml

Disabling the migration

The migration can be disabled via values:

crdMigration:
  personAccountPlural:
    enabled: false

Warning: Disabling the migration on an affected upgrade (from v0.10.2 or older with existing KanidmPersonAccount resources) is unsupported unless you have completed the migration manually. The v0.11 operator watches the corrected CRD plural and will not reconcile resources under the legacy plural.

Fresh installations

Fresh installations of v0.11.0+ create the corrected CRD directly. The migration Job detects that neither the legacy CRD nor any backup exists and exits as a no-op. No special action is required.

Upgrading from v0.10.3

v0.10.3 already ships the corrected CRD (kanidmpersonaccounts.kaniop.rs). The migration Job detects that the corrected CRD is already established and the legacy CRD is absent, and exits as a no-op. A standard helm upgrade or Argo CD sync is safe.

Troubleshooting

Migration Job is stuck

# Check Job logs
kubectl -n kaniop logs -l app.kubernetes.io/component=crd-migration --all-containers=true

# Check migration marker phase
kubectl -n kaniop get configmap kaniop-person-crd-migration -o jsonpath='{.data.phase}'

# Check if operator is still running (should be scaled to 0 during migration)
kubectl -n kaniop get deploy kaniop

Person resources are not reconciled after migration

# Check that the new operator is running
kubectl -n kaniop rollout status deploy/kaniop

# Check operator logs for the person controller
kubectl -n kaniop logs deploy/kaniop | grep -i person

# Verify the corrected CRD is established
kubectl get crd kanidmpersonaccounts.kaniop.rs -o jsonpath='{.status.conditions[?(@.type=="Established")].status}'

Argo CD shows OutOfSync after migration

After a successful migration, Argo CD may show the Application as OutOfSync because the person resources have new UIDs. Refresh the Application:

argocd app get kaniop --refresh

If using two Applications, refresh both.