--- title: "v0.11 CRD Plural Migration" weight: 40000 --- # 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 `.`, 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. ```bash # 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: ```bash # 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:** ```yaml spec: syncPolicy: syncOptions: - PruneLast=true - SkipDryRunOnMissingResource=true ``` **Do NOT set:** ```yaml # These are PROHIBITED for the migration release # Replace=true # Force=true ``` #### Recommended Application topology 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: ```bash # 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: ```bash 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: ```bash # 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: ```bash # 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. ```bash # 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: ```bash # 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- -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: ```yaml 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 ```bash # 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 ```bash # 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: ```bash argocd app get kaniop --refresh ``` If using two Applications, refresh both.