yeke.io · docs
GitOps repo reflection
Manage the YAML counterpart of console changes in your repo. Review cluster and repo changes before approval, then use drift reports to track differences.
What it is for
Keep the next reconcile from undoing an urgent fix.
If a cluster change is not reflected in the repo, the GitOps controller restores the declared state on its next reconcile.
YEKE also updates the YAML counterpart of an approved change. The cluster’s repo binding determines whether it proceeds directly or through a proposal branch.
- The apply always happens, and is never rolled back. Reflection runs after the apply. If the repo side fails, the operation is not cancelled — undoing urgent work because a secondary system failed would be the wrong priority.
- The repo side is reported separately. The operation carries its own state: reflected, no counterpart found, write failed, or not applicable. That state sits on the operation card and in the audit trail — it turns a silent loss into a loud one.
- The commit goes to the tracked branch. Directly, to the branch the reconciler watches. The reason is one sentence: only the content of that branch fixes the failure, and an unmerged proposal does not stop a reconcile.
- If the branch is protected, the job stays half done and says so. When the push is refused, reflection falls back to a proposal branch and the state is not "reflected". The wording is not softened: the change was applied to the cluster, and until you merge, the next reconcile may undo it.
Drift is the second layer: every operation reflection could not locate or could not write stands out as a visible difference in the next drift reading. Reflection prevents; drift catches what got away.
Connecting a repo
Six steps, and the middle one happens outside YEKE.
- 1 · Administration → GitOps repositories. The page opens for the admin role only; GitOps mode is a licensed item.
- 2 · Register the repo. Three fields: the remote address, the tracked branch and the scope path. Details in the table below.
- 3 · YEKE generates a deploy key. The private half stays in core and appears on
no screen and in no download. The public half is always visible and copyable; the
yeke-gitopscomment at the end of the line is there so you can recognise the key on your provider's screen. - 4 · Add the public key to your provider with WRITE permission. This step happens outside YEKE and it is the one most easily skipped. What you are looking for is a repository-level deploy key and the write checkbox on that record; the menu names and their placement differ per provider. A read-only key still connects, but it cannot commit.
- 5 · Test the connection. This is the only gesture that measures whether the setup actually works; the next section says what it measures.
- 6 · Bind the cluster to the repo. From GitOps binding in the cluster row's action menu: pick the repo and, if you want, narrow it by scope path, namespace and kind. The binding changes that cluster's write custody, and the cluster card states it as a line.
Fields of the record
| Field | What goes in | Note |
|---|---|---|
| Remote address | ssh://[email protected]/ornek/gitops.git |
SSH only. HTTPS and git:// are refused. It cannot be edited after
registration: to change it you connect a new repo and remove the old one — changing
the remote would leave "which provider holds this key" unanswered. |
| Tracked branch | uretim |
If you declare none, the remote HEAD is used and the card says so. That the branch exists on the remote is not verified; a wrong name shows up as a failure at the first reflection. |
| Scope path | clusters/uretim |
Whether it matches anything in the repo is not verified at registration time. A wrong scope does not look like an error — it looks like "no counterpart in the repo". The first real signal comes from the connection test. |
Host identity
On the first connection the host key the server presents is pinned. YEKE does not verify it: you compare the fingerprint against the value your provider publishes. The card does not hide this; it says it in the body.
A pinned host identity does not mean your key was accepted. Those are two separate measurements and the screen does not fold them into one green.
If the host key changes, obtain the new fingerprint from your provider or server administrator and enter it manually. YEKE saves it only if it matches the key received from the server.
Narrowing a binding
For namespace and kind, two states are deliberately distinct: no narrowing and an empty list. An empty list means "no namespace is in scope", and the dialog says so before you save.
Narrowing the scope path to a subdirectory does not break the identity of a declaration.
kustomization.yaml files that sit above your scope are still read for the
identity chain — even when a transformer such as namespace: lives in a parent
directory, the real identity of the declaration is resolved correctly. Those files do not
leak into the corpus: only the identity resolver sees them, and neither the file list that
goes to the model nor the render tree does.
What "Test connection" measures
Three questions, one button, and the result is kept in the record.
- Is the connection established. Can the remote be reached, and is the host identity pinned.
- Does the key carry WRITE permission. Does the provider accept the key, and can that key write. A read-only key shows up separately on this line — it does not pass as "connected".
- Does the scope path match anything. The number of files under the scope comes back; zero files is a warning.
| State | What it means |
|---|---|
| Not tested | Nothing has been measured. The card does not say "ready"; nothing is drawn green. |
| Accepted | Measured: the provider accepted the key and write permission was observed. |
| Rejected | The provider does not accept this key. The public key may never have been added, or added to the wrong record. |
| Host identity mismatch | The identity the server presents differs from the pinned one. Nothing is written. |
| Unreachable | The server could not be reached; no measurement was made. |
Untested and failed connections have separate states. Run the connection test to find missing settings before your first operation.
The same distinction holds for the file count: on a rejected test the scope's file count comes back empty, not zero. "I looked and it was empty" is one thing; "I could not look" is another.
Seeing what will happen before you approve
The approval card states both sides: what changes in the cluster, and what changes in the repo.
- Which file, which lines, which branch. The repo diff is part of the card and enters the summary of the plan you approve — what you approved cannot change quietly afterwards.
- In a templated layout the value is driven, not the template. For a helm or kustomize layout the card shows which input value will change; the template itself is left alone. Verification is not left to a claim either: if the object produced after the edit does not match the object that was applied, the proposal is refused.
- If no counterpart is found, that is information, not a blocker. The operation is still applied, and the card says no counterpart was found in the repo and that you may need to update it by hand. "There is no counterpart" and "there was one but I could not write it" are two different states.
Finding the counterpart is mechanical first: the declarations under the scope are read and matched by object identity (apiVersion, kind, namespace, name) — the file name need not have anything to do with the object name. Only when the layout is templated, or the repo's convention cannot be read mechanically, is the model consulted, and what goes to it is file paths: your manifest bodies do not, and the model's text never lands in a file. The model's output is a proposal, and code tests it by running it.
When the repo is not consulted
Two cases: the object cannot have a counterpart in the repo, or you do not want one. Neither shows up on the card as "no counterpart found".
- An object owned by a controller is outside the scope. Delete a pod and the repo is never consulted: no clone, no checkbox, no render. The test is not the kind name but the shape of the object — the pod was created by a ReplicaSet and that by a Deployment, and the declaration in the repo is the Deployment's. The card says exactly that: not "I could not look" and not "it is not in the repo", but there is nothing to look for.
- You can untick "reflect into the repo". The product default is on — reflection is what the product promises, and turning it off is a deliberate departure. Per repo you can turn that default off in the GitOps repo settings: on clusters bound to that repo the box comes unticked, the plan is built without consulting the repo (faster), and ticking the box rebuilds the plan with the repo counterpart. The card says whether the box is off by the repo default or by your choice, and the record carries it. For experimental work you untick it: the operation is applied to the cluster and nothing lands in the repo.
- The box comes before approval and is part of the plan you approve. Its state enters the plan summary, so the record says "I did not want it", not "I forgot".
- With it off, the repo half is never computed. No clone is taken, no render runs, the model is not called. There is a measurable difference too: on a real server the same plan was built in 51 ms instead of 1307 ms.
- While the box is off, no conflicting-declaration warning is raised. With the repo default off, that warning is silent for that repo by default. A declared limit; your decision stays in the plan summary and in the audit trail.
New files and deletions need their own approval
Updating an existing declaration is automatic. Adding a file and removing a declaration are not.
| Verb | Repo counterpart | Default |
|---|---|---|
| Update declaration exists |
The file is edited in place — at any depth, including a new field or an array item. | Automatic |
| New object no declaration |
A new file, following the repo's own convention; in a kustomize layout the
resources: entry is added too. |
Checkbox, off |
| Delete | The declaration is removed; in a kustomize layout the resources:
entry is dropped. |
Checkbox, off |
- The tick is part of the plan you approve. The state of the checkbox enters the plan summary: the scope you approved cannot grow quietly afterwards.
- Convention decides where a new file goes; the operation itself decides what is in it. The file naming pattern and directory layout are read from the repo; if they cannot be read, only a path is proposed. The proposal is accepted only if a build verifies it — if the render does not actually produce the object, the proposal is refused and the state stays "no counterpart".
- For deletion the test is the build as well: if the render still produces the object after the edit, nothing is written to the repo.
- The last object in an overlay is asked separately. If the object you are deleting is the last one its kustomization produces, a second checkbox appears on the card: whether the overlay should be removed entirely. Its default is off and it is separate from the first — removing an overlay is a different decision from deleting an object and cannot hide under a single approval. If ticked, the reference in the parent kustomization is dropped too; if that cannot be done, the removal is refused, because a half removal breaks the build of that whole scope.
- Declarations left pointing at it are counted, not edited. If other declarations
in the repo refer to the object you are deleting — a route that targets it, a workload
that uses its claim — the card lists them before you approve. None of them are
touched, and that is a deliberate decision: there is more than one correct way to fix
a dangling reference, and YEKE does not choose for you. A target in another namespace is
written as
namespace/name; when a declaration's effective namespace cannot be resolved from the repo, the card says so rather than showing it as resolved.
Your formatting is preserved
Bytes that are not touched stay the same; formatting is read from the file, not assumed.
- Your comments, indentation and flow style stay put. Indent width, sequence indentation and the quoting of a scalar are read from the file's own convention. Nothing outside the changed lines is reformatted — in a direct-commit model that noise would show up on the tracked branch, not in a proposal.
- Kustomize housekeeping. When a declaration is removed, its counterpart in
kustomization.yamlgoes with it:resources,patches,replacementsand generator entries included. - An entry that cannot be judged is left alone. If it cannot be established that an entry targets only the deleted object, it stays as it is and is shown to you.
- Where it cannot write correctly, it does not write. If the path to edit runs through an anchor/alias or sits under a tagged node, no edit is produced at all: reflection carries the "could not write" state, the operation does not stop, the trace remains. The reason is plain — resolving it would mean changing a second place you never approved.
What the drift report reads
For Kustomize, the drift report compares build output rather than raw YAML, accounting for patches, namePrefix and namespace transformations.
If a scope cannot be rendered the report does not say "clean"; it is entirely unknown and says why. Helm charts are not read in the drift report yet.
The report reads the binding's own scope: if you gave the cluster binding a narrow scope, the verdict comes from that scope; if the binding has no scope, the repo record's is used. The report carries the scope it looked at, so the screen does not assume it. The namespace and kind narrowings are deliberately not applied to the report — applying them would erase an observed divergence and turn the summary green without cause.
Limits
Unsupported cases and known limitations.
- Because an unparseable file is skipped, a reference sitting in it cannot be scanned either: a declaration inside that file that is left pointing at a deleted object is not listed on the card. The card names the file it could not read, so this is not silent; but that file's contents have not been measured.
- Adding a NEW object to a Helm chart is not supported. Updating the value of an object the chart produces works; producing a new template plus a values entry is reported as "not applicable". Writing a template moves the work into an area that cannot be verified.
- The template itself is never edited. YEKE writes to the value, not to the template.
- In a mixed-verb plan the checkbox covers the whole card. If one plan contains both an update and a deletion and the box is unticked, the updates are not reflected either. A plan is a single intent; landing half of it would force the card to say which half went in. Objects that fall outside the scope — a pod owned by a controller, say — do not count towards this.
- YEKE does not trigger the reconciler. It does not force a sync, does not read Argo CD / Flux objects and does not show sync status. Delivery stays your tool's job.
- A repo with bindings cannot be deleted. You remove the cluster bindings first, and the rule lives on the server — it holds even if you call the API directly. When you delete the repo record, the public key at your provider stays where it is; removing that is yours to do.