CGOA — GitOps Certified Associate
Deleted, yet still here: a GitOps pruning experiment
Goal
You distinguish, on a real Argo CD and k3s, the intent to delete in Git, per-object protection, approval, and the rejection of an empty target.
Why it matters
OutOfSync may be the result of a protection working, and Succeeded may not be the result of the current commit. You do not touch the production LabHub, external repositories, user data, or PVCs. You use only the cgoa-prune-safety Namespace on your personal VM and two dedicated Applications. The actual deletion target is a ConfigMap holding a learning string. Do not delete the Namespace, the Applications, or the shared controller. Do not change global security settings or permissions. It is a 55-minute lab. If needed, extend the time before the session expires and keep your observations. When it ends, the VM and files are reclaimed.
Prepared environment and helper
/srv/cgoa-prune-safety is the working Git, and /srv/bare/cgoa-prune-safety.git is the remote repository inside the VM. The Application cgoa-prune-safety reads the app directory, and cgoa-prune-safety-empty reads the empty directory. Compare the actual SHA instead of the name main. The observation files are in /root/cgoa-prune. The key=value in the tasks is explanatory notation. In JSON files, use quotes for keys and strings as in the format examples, and write booleans as true/false. python3 /opt/fixtures/cgoa_prune_lab.py observe reads the current Git, Application, and ConfigMaps. complete N of the same helper reviews your input and, after a bounded change, saves the actual observation. Steps 2, 3, 4, and 5 are Git changes, step 6 is the deletion approval, step 7 is a policy change after reviewing the empty target, and step 8 is restoring the default policy and Git. In step 7, preview-empty removes only the last target from Git to observe the protection first, and allowing deletion needs a separate answer and complete 7. Review the changes the helper made yourself with git show. An existing unfinished Git change or a partial answer is not overwritten, and the helper stops. solve N is the same as the solution view and writes only the current answer that is missing. prepare N prepares only the earlier steps that are missing and does not create the current answer. grade N only reads. Re-running a completed step does not repeat the deletion or the creation of observations.
Steps
- Read git -C /srv/cgoa-prune-safety rev-parse HEAD and baseline.json. In identity.json, write revision=the actual commit SHA and namespace=cgoa-prune-safety as strings, and run complete 1. In observation-1.json, check the source.path of the two Applications and the ownership and UIDs of the five ConfigMaps.
- Check configmaps.normal.uid in baseline.json. In normal.json, write remove=normal and uid=the UID string you checked, and run complete 2. The helper removes only normal from the app directory in Git. With git show and observation-2.json, check that only normal is gone and it is Synced/Succeeded, and that the other UIDs are unchanged.
- In retention.json, write the strings remove=retained, option=Prune=false, and expected_sync=OutOfSync, and run complete 3. Even if retained is gone from Git, the actual UID is kept. In operationState.syncResult.resources of observation-3.json, find PruneSkipped and explain why Succeeded and OutOfSync are present together.
- In restore.json, write the string fix_source=git and the boolean same_object=true, and run complete 4. After the Git definition is back, check that retained has the same UID and is Synced. Deleting or recreating the live object is not preservation in this task.
- In pending.json, write the strings remove=confirmed and expected_operation=Running and the boolean approved=false, and run complete 5. In observation-5.json, check that the approval-waiting message and the requiresPruning target are the single confirmed object. Do not conclude that Running is a controller failure.
- Read apps.app.uid and configmaps.confirmed.uid from observation-5.json. In approval.json, write the boolean approve=true and app_uid and target_uid as the corresponding UID strings, and run complete 6. The helper checks that it is still the object and scope you reviewed, and then records the approval time on the Application. Check that, with the Git commit unchanged, confirmed is deleted and it becomes Synced/Succeeded.
- Run preview-empty first. It changes the empty directory's target to a valid empty List but does not allow deletion. Read whether snapshot.revision in guard-7.json and apps.empty.status.operationState.syncResult.revision differ, it is a SyncError, and the disposable UID was preserved. In empty.json, write the booleans allow_empty=true and previous_success_is_current=false, revision=the current SHA you reviewed, and target_uid=the disposable UID string, and run complete 7. Check that on the same Git, only the policy changes and only disposable is deleted.
- In decision.json, write the booleans restore_git=true, allow_empty=false, same_object=false, and backup_verified=false, and run complete 8. It restores the default protection to false and puts the Git definition back. In observation-8.json, check that disposable has a new UID and retained and anchor have the baseline UIDs. Report declaration restoration and data backup verification separately.
Cautions and interpretation
retained and anchor keep the baseline UID and data to the end. disposable must have a new UID after deletion and restoration. If the UID of the target to approve and the current scope differ, it stops. Do not forget that the approval annotation is per Application. An empty target is not a read failure but a valid List with items=[]. Check not only the rejection message but also the current revision, the policy, and the preserved objects together. The hash in the completion record detects accidental overwriting of a file. It is not a security guarantee that prevents every forgery by root on the same VM. If a pending journal remains, do not guess whether the change was completed and delete again. Keep the material and start over in a new lab. Grading has a budget of 60 seconds, and preparing earlier steps has a budget of 90 seconds. Waiting for convergence happens only in complete. Argo CD sync options · Automated sync and empty targets.
Reading the baseline of Git and object lifetimes
Read git -C /srv/cgoa-prune-safety rev-parse HEAD and baseline.json. In identity.json, write revision=the actual commit SHA and namespace=cgoa-prune-safety as strings, and run complete 1. In observation-1.json, check the source.path of the two Applications and the ownership and UIDs of the five ConfigMaps.
Read the commit SHA instead of the branch name, and the UID along with the object name.
Confirming the actual deletion of an unprotected object
Check configmaps.normal.uid in baseline.json. In normal.json, write remove=normal and uid=the UID string you checked, and run complete 2. The helper removes only normal from the app directory in Git. With git show and observation-2.json, check that only normal is gone and it is Synced/Succeeded, and that the other UIDs are unchanged.
Compare the Git diff and the current ConfigMap list together.
Telling a success that skipped deletion from a difference
In retention.json, write the strings remove=retained, option=Prune=false, and expected_sync=OutOfSync, and run complete 3. Even if retained is gone from Git, the actual UID is kept. In operationState.syncResult.resources of observation-3.json, find PruneSkipped and explain why Succeeded and OutOfSync are present together.
An operation succeeding and matching the desired state are different questions.
Preserving the same object through a Git restoration
In restore.json, write the string fix_source=git and the boolean same_object=true, and run complete 4. After the Git definition is back, check that retained has the same UID and is Synced. Deleting or recreating the live object is not preservation in this task.
Even with the same name, if the UID changes, it is not the same object preserved.
Checking the wait for approval and the deletion target
In pending.json, write the strings remove=confirmed and expected_operation=Running and the boolean approved=false, and run complete 5. In observation-5.json, check that the approval-waiting message and the requiresPruning target are the single confirmed object. Do not conclude that Running is a controller failure.
Read operationState.message and the requiresPruning list.
Approving only for the UID you reviewed
Read apps.app.uid and configmaps.confirmed.uid from observation-5.json. In approval.json, write the boolean approve=true and app_uid and target_uid as the corresponding UID strings, and run complete 6. The helper checks that it is still the object and scope you reviewed, and then records the approval time on the Application. Check that, with the Git commit unchanged, confirmed is deleted and it becomes Synced/Succeeded.
The approval annotation attaches to the Application, so check the entire scope scheduled for deletion.
Observing the empty-target rejection, then allowing it explicitly
Run preview-empty first. It changes the empty directory's target to a valid empty List but does not allow deletion. Read whether snapshot.revision in guard-7.json and apps.empty.status.operationState.syncResult.revision differ, it is a SyncError, and the disposable UID was preserved. In empty.json, write the booleans allow_empty=true and previous_success_is_current=false, revision=the current SHA you reviewed, and target_uid=the disposable UID string, and run complete 7. Check that on the same Git, only the policy changes and only disposable is deleted.
Check which Git commit the last operation's success belongs to.
Restoring the default protection and Git, and reporting the limits
In decision.json, write the booleans restore_git=true, allow_empty=false, same_object=false, and backup_verified=false, and run complete 8. It restores the default protection to false and puts the Git definition back. In observation-8.json, check that disposable has a new UID and retained and anchor have the baseline UIDs. Report declaration restoration and data backup verification separately.
Recreating a ConfigMap is not a test of volume or database recovery.