Skip to content

feat(gitops-api): F7 — higher-level KRM objects as first-class documents#203

Open
sunib wants to merge 2 commits into
mainfrom
feat/gitops-api-f7
Open

feat(gitops-api): F7 — higher-level KRM objects as first-class documents#203
sunib wants to merge 2 commits into
mainfrom
feat/gitops-api-f7

Conversation

@sunib

@sunib sunib commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements F7 — Higher-level KRM objects as first-class documents from the
gitops-api feature ladder (docs/design/gitops-api/README.md), the next
launch-critical item after F1/F4.

Higher-level control-plane custom resources — Flux HelmRelease, Argo CD
Application, KRO resources — already mirror and edit through the operator's
kind-agnostic pipeline exactly like core resources (no per-kind allowlist;
only a small deny list of noisy built-ins). F7 does not change product code;
it makes that guarantee load-bearing with a corpus + e2e so a future
special-case cannot silently regress it, and teaches "install an app = add a KRM
document" in user docs.

What's in it

  • Design docdocs/design/gitops-api/f7-higher-level-krm-documents.md
    (premise, why-prove-it, coverage split, what it deliberately doesn't do).
  • Unit corpushelmrelease.yaml, argocd-application.yaml,
    kro-podinfoapp.yaml added to internal/git/manifestedit/testdata/corpus/.
    They flow through the two existing globbed gates with no new test code:
    TestCorpusRoundTrip_ByteIdentical (re-encode byte-for-byte) and
    TestConvergence_Corpus (perturb-then-settle).
  • e2e pintest/e2e/helmrelease_mirror_edit_e2e_test.go
    (manager, f7-higher-level-krm): a Flux HelmRelease is mirrored to its
    canonical path <ns>/helm.toolkit.fluxcd.io/helmreleases/<name>.yaml, then a
    live spec.chart.spec.version bump round-trips in place, preserving a
    hand-authored comment. Uses the Flux CRDs already installed in the base e2e
    cluster; spec.suspend: true keeps the helm-controller inert so the object
    behaves like any inert document. (The generic-CRD case is already pinned by
    crd_lifecycle_e2e_test.go; this pins a real, named higher-level type.)
  • User docsdocs/installing-apps-as-krm.md: installing an app is adding a
    KRM document; the chart-inflation boundary (helmCharts:) stays refused.
  • README ladder — F7 marked implemented.

Validation

  • task lint — 0 issues.
  • task test — pass; unit coverage steady at baseline (no production code
    changed). New manifestedit corpus documents pass round-trip + convergence.
  • task test-e2e — the new f7-higher-level-krm spec passes (HelmRelease
    mirror + version-bump + comment-preservation, live), and 54 of 55 run specs
    pass. The one failure is a pre-existing, unrelated flake in
    Manager Unsupported Folder Refusal > refuses a hard-Kustomize path
    (the racy GitPathAccepted/refusal projection — refusal recorded on the data
    plane doesn't promptly re-enqueue the GitTarget, so the WatchRule
    GitTargetReady=False projection can lag past the 90s gate). It reproduces in
    isolation on this environment and is independent of this PR: no production Go
    is changed here
    (the only .go change is the new e2e test), so the deployed
    operator binary is behaviourally identical to main.

Note: this change is purely additive (docs + testdata corpus + one e2e spec +
two templates). It changes no operator code path.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added documentation for installing apps as KRM objects, including support for Flux HelmReleases, Argo CD Applications, and KRO resources.
    • Expanded design docs to reflect that higher-level KRM documents are now supported as first-class editable resources.
    • Added new examples and test coverage for mirroring and editing these resources while preserving comments and changes in Git.
  • Documentation

    • Updated the GitOps API overview and start-here links to point to the new guidance.

…cuments

Flux HelmRelease, Argo CD Application, and KRO resources already mirror and edit
through the kind-agnostic pipeline exactly like core resources (no per-kind
allowlist; only a small deny list of noisy built-ins). F7 makes that guarantee
load-bearing rather than assumed. No product code changes.

- manifestedit corpus: helmrelease.yaml, argocd-application.yaml,
  kro-podinfoapp.yaml flow through the existing globbed round-trip
  (byte-identical) and convergence (perturb-then-settle) gates.
- e2e (manager,f7-higher-level-krm): a Flux HelmRelease is mirrored to its
  canonical path and a live chart-version bump round-trips in place, preserving
  a hand-authored comment. Uses the Flux CRDs already in the base e2e cluster;
  spec.suspend keeps the helm-controller inert.
- docs: f7 design doc + user guide installing-apps-as-krm.md (install an app =
  add a KRM document; the chart-inflation boundary stays refused); README ladder
  marks F7 implemented.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@sunib, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 5 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d7d93c44-632d-4ee9-9143-ed356e7ef75e

📥 Commits

Reviewing files that changed from the base of the PR and between 2678e84 and bfa6e30.

📒 Files selected for processing (1)
  • docs/installing-apps-as-krm.md
📝 Walkthrough

Walkthrough

This PR adds design and user documentation for "F7: Higher-level KRM objects as first-class documents," updates the GitOps API README to mark F7 as implemented, adds three manifestedit test corpus fixtures (Argo CD, HelmRelease, KRO), and introduces a new e2e test with templates validating HelmRelease mirroring and in-place comment-preserving edits.

Changes

F7 Higher-Level KRM Documents

Layer / File(s) Summary
F7 design document and README updates
docs/design/gitops-api/f7-higher-level-krm-documents.md, docs/design/gitops-api/README.md
New design doc defines kind-agnostic pipeline behavior, scope, pinning strategy, exclusions, and test plan; README feature ladder marks F7 implemented and baseline section adds a bullet on higher-level KRM mirror/edit behavior.
User-facing installing-apps-as-krm documentation
docs/installing-apps-as-krm.md, docs/README.md
New user doc covers install/rollout workflows for core, Flux HelmRelease, Argo CD Application, and KRO resources, WatchRule configuration, and Git placement; docs/README.md links to it.
Manifestedit test corpus fixtures
internal/git/manifestedit/testdata/corpus/argocd-application.yaml, .../helmrelease.yaml, .../kro-podinfoapp.yaml
New YAML fixtures for Argo CD Application, Flux HelmRelease, and KRO PodInfoApp added to the round-trip/convergence test corpus.
HelmRelease mirror/edit e2e test and templates
test/e2e/helmrelease_mirror_edit_e2e_test.go, test/e2e/templates/manager/helmrelease.tmpl, test/e2e/templates/manager/watchrule-helmrelease.tmpl
New Ginkgo e2e test provisions a namespace, applies a HelmRelease and WatchRule, verifies mirroring without status, seeds a preserved comment, patches the chart version, and asserts in-place edit; adds a helper for committing/pushing with rebase retry, plus supporting templates.

Estimated code review effort: 2 (Simple) | ~15 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Test as E2E Test
  participant K8s as Kubernetes API
  participant Operator as Manager Operator
  participant Git as Gitea Repository

  Test->>K8s: Apply WatchRule for HelmReleases
  Test->>K8s: Apply HelmRelease (podinfo, version 6.5.0)
  Operator->>K8s: Watch HelmRelease
  Operator->>Git: Mirror HelmRelease to canonical path (no status)
  Test->>Git: Seed head comment into mirrored file and push
  Test->>K8s: Patch HelmRelease chart version
  Operator->>Git: Edit mirrored file in place, preserving comment
  Test->>Git: Assert new version present, comment preserved, old version removed
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description covers the main changes but omits most template sections and checkbox details. Reformat the description to match the template and add Type of Change, Testing, Checklist, Related Issues, Screenshots, and Additional Notes.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly names the feature and matches the main change set.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/gitops-api-f7

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/e2e/helmrelease_mirror_edit_e2e_test.go`:
- Around line 155-167: The fallback path in this test helper destroys the
checkout and then immediately tries to read the committed file again, which
makes the later os.ReadFile in the seeding block fail misleadingly. Update the
orphan-branch handling in the same helper that uses gitRun/mustGit so it does
not assume relPath still exists after checkout --orphan and rm -rf ., or bail
out early with a clear error before attempting to seed the comment. Keep the
file-read and write step only for the successful origin/main checkout path, or
make the fallback create the target file explicitly before reading it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ffb9bb2c-c77d-4b53-bc26-74abacfd24ea

📥 Commits

Reviewing files that changed from the base of the PR and between 97a9c87 and 2678e84.

📒 Files selected for processing (10)
  • docs/README.md
  • docs/design/gitops-api/README.md
  • docs/design/gitops-api/f7-higher-level-krm-documents.md
  • docs/installing-apps-as-krm.md
  • internal/git/manifestedit/testdata/corpus/argocd-application.yaml
  • internal/git/manifestedit/testdata/corpus/helmrelease.yaml
  • internal/git/manifestedit/testdata/corpus/kro-podinfoapp.yaml
  • test/e2e/helmrelease_mirror_edit_e2e_test.go
  • test/e2e/templates/manager/helmrelease.tmpl
  • test/e2e/templates/manager/watchrule-helmrelease.tmpl

Comment on lines +155 to +167
if _, err := gitRun(repo.CheckoutDir, "fetch", "origin", "main"); err == nil {
mustGit("checkout", "-B", "main", "origin/main")
mustGit("reset", "--hard", "origin/main")
} else {
mustGit("checkout", "--orphan", "main")
_, _ = gitRun(repo.CheckoutDir, "rm", "-rf", ".")
}

full := filepath.Join(repo.CheckoutDir, relPath)
content, readErr := os.ReadFile(full)
Expect(readErr).NotTo(HaveOccurred(), "committed file must exist before seeding a comment")
seeded := comment + "\n" + string(content)
Expect(os.WriteFile(full, []byte(seeded), 0o600)).To(Succeed())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fallback branch destroys the file it later needs.

If git fetch origin main fails, the else-branch does checkout --orphan main and rm -rf . — but line 164 then unconditionally reads the pre-existing committed file at relPath and asserts it exists ("committed file must exist before seeding a comment"). If this branch is ever hit (e.g. a transient fetch error, not just a genuinely-empty repo), the local checkout is wiped and the subsequent os.ReadFile fails, producing a confusing/misleading test failure instead of a clear one.

In practice this branch is likely unreachable here (the earlier Eventually block already confirms the file exists on origin/main), but the fallback logic is inconsistent with the function's precondition and worth tightening.

🐛 Proposed fix
-	if _, err := gitRun(repo.CheckoutDir, "fetch", "origin", "main"); err == nil {
-		mustGit("checkout", "-B", "main", "origin/main")
-		mustGit("reset", "--hard", "origin/main")
-	} else {
-		mustGit("checkout", "--orphan", "main")
-		_, _ = gitRun(repo.CheckoutDir, "rm", "-rf", ".")
-	}
+	fetchOut, fetchErr := gitRun(repo.CheckoutDir, "fetch", "origin", "main")
+	Expect(fetchErr).NotTo(HaveOccurred(), fmt.Sprintf("git fetch origin main: %s", fetchOut))
+	mustGit("checkout", "-B", "main", "origin/main")
+	mustGit("reset", "--hard", "origin/main")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if _, err := gitRun(repo.CheckoutDir, "fetch", "origin", "main"); err == nil {
mustGit("checkout", "-B", "main", "origin/main")
mustGit("reset", "--hard", "origin/main")
} else {
mustGit("checkout", "--orphan", "main")
_, _ = gitRun(repo.CheckoutDir, "rm", "-rf", ".")
}
full := filepath.Join(repo.CheckoutDir, relPath)
content, readErr := os.ReadFile(full)
Expect(readErr).NotTo(HaveOccurred(), "committed file must exist before seeding a comment")
seeded := comment + "\n" + string(content)
Expect(os.WriteFile(full, []byte(seeded), 0o600)).To(Succeed())
fetchOut, fetchErr := gitRun(repo.CheckoutDir, "fetch", "origin", "main")
Expect(fetchErr).NotTo(HaveOccurred(), fmt.Sprintf("git fetch origin main: %s", fetchOut))
mustGit("checkout", "-B", "main", "origin/main")
mustGit("reset", "--hard", "origin/main")
full := filepath.Join(repo.CheckoutDir, relPath)
content, readErr := os.ReadFile(full)
Expect(readErr).NotTo(HaveOccurred(), "committed file must exist before seeding a comment")
seeded := comment + "\n" + string(content)
Expect(os.WriteFile(full, []byte(seeded), 0o600)).To(Succeed())
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/e2e/helmrelease_mirror_edit_e2e_test.go` around lines 155 - 167, The
fallback path in this test helper destroys the checkout and then immediately
tries to read the committed file again, which makes the later os.ReadFile in the
seeding block fail misleadingly. Update the orphan-branch handling in the same
helper that uses gitRun/mustGit so it does not assume relPath still exists after
checkout --orphan and rm -rf ., or bail out early with a clear error before
attempting to seed the comment. Keep the file-read and write step only for the
successful origin/main checkout path, or make the fallback create the target
file explicitly before reading it.

The install-apps guide explained that the operator won't *render* charts, but
not the bigger configuration discipline: users must deliberately scope capture
to authored intent (HelmRelease/Application/KRO documents) and keep the objects
those controllers render (Deployments, Services, ConfigMaps, ReplicaSets, Pods)
out of Git.

Add a "capture intent, not the rendered output" section that explains why
mirroring rendered output breaks round-trippability (controller-owned objects
have no single writable destination), that the operator cannot tell an authored
Deployment from a rendered one (only a few purely-runtime kinds are excluded by
default), and how to draw the line: select intent kinds not workload kinds,
separate intent from runtime by namespace, and mind overlaps. Frame it as the
same principle as the chart-inflation boundary, applied at runtime.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant