feat(presentation): pluggable GitOps backend (ArgoCD + minimal Flux)#104
feat(presentation): pluggable GitOps backend (ArgoCD + minimal Flux)#104sini wants to merge 3 commits into
Conversation
|
I provided only a minimal impl as a POC for framing discussion. |
|
I've been sitting and thinking about this today and I think I'm for the idea of supporting a pluggable "presentation" backend as long as the original ArgoCD experience doesn't suffer (apart from moving the options a bit, but the aliases help). I have used Flux a bit at $work but never liked it as much as ArgoCD, how do you propose the environmentPackage layout should be? Won't there need to be a similar app-of-apps setup? I think the consensus in the Flux community is that the rendered manifest pattern like nixidy uses (commit manifests to git) is an anti-pattern. I think they're more in favour of pushing manifests in OCI artifacts. I think it would make sense to have the standard flux backend be as close to the argocd one (but make it make sense for flux usage) but also offer a user provided backend if the user wants. Maybe someone could implement a new activation script that builds an OCI artifact and pushes it instead. Maybe What do you think? |
|
Yeah; that's in line with my thinking. The flux impl in this draft isn't intended to ship; just show what the restructure would look like for ArgoCD. ArgoCD of course remains the first class flagship, we just re-architect the backend model a bit to support (ideally, any) alternative gitops toolchains and more closely match a MVC-style rendering pattern for future enhancements. :) |
Restructure modules/build.nix into modules/build/ as a functional-core / imperative-shell pipeline. No public API change; all module and lib tests green. - layout.nix: pure core — each application -> [FileSpec] (one per output file), exposed as the internal `config.build.layout` seam so file-layout policy is plain data that tests assert directly (no derivation built). - render.nix: FileSpec -> shell fragment (the yq/cp realization). - emit-environment.nix: environmentPackage / activationPackage / bootstrapPackage / extrasPackage, each a fold over the seam. - apply.nix: declarativePackage (apply script) + the activation post-process stream; shares chainOf/resolveCommand across both paths. - default.nix: the module — options.build.*, the assertion, and wiring. One intentional behavior change: the per-app postProcess collision assertion is strengthened from "no two post-processed objects share a file" to "a post-processed object's group is single-object". This closes a latent divergence where the switch (per-object) and apply (group-head) paths could post-process different sets in a multi-document file — the case the old message already called "undefined". apply == activation now holds by construction; the previously-undefined case becomes an explicit build error, and every case the tests exercise is unaffected. New tests: layout-filespec.nix asserts the seam directly; layout-postprocess-multidoc-assert.nix covers the strengthened assertion.
79dde2b to
9ce529d
Compare
|
Thanks, that lines up, and digging into the module system turned up a constraint that I think points at a cleaner shape than the type I floated. Why But your
Registration is unconditional and free (present is lazy, only called for the selected key). ArgoCD stays exactly as first-class as today: same options, the On Flux + the app-of-apps question. Agreed a real Flux backend wants its own bootstrap shape (root Kustomization / GitRepository, not an app-of-apps Application); that's just what its On OCI artifacts, I'd scope that out of this change, deliberately. "Commit rendered manifests" vs "build and push an OCI artifact" is a delivery concern, orthogonal to presentation (what controller objects describe the apps). Pure Full design writeup here: https://gist.github.com/sini/d2a1b7876977e01cfb0712ee8d89dcfe I'll push the registry version onto #104. Does the |
Replace the enum + mkIf-guarded presentation dispatch with a registry of
pure backend records. nixidy.presentation.backends is an attrsOf record
{ perAppOptions; typeImports; present; bootstrapFile }; nixidy.presentation.backend
is a str key selecting one, with an unknown-key assertion. modules/presentation/default.nix
resolves the selected record and wires its four contributions; synthesis is a
pure present :: ctx -> attrsOf appConfig (no module config access, no per-backend
guards). ArgoCD stays first-class (options relocated behind .argocd.* with
value-forwarding aliases; default-backend output byte-identical); a minimal Flux
backend proves the seam. A user backend is a record assigned from ordinary config,
no nixidy edit required.
…ckend Adds: dispatch unknown-backend assertion; direct unit tests of each backend's pure present function on a fixture ctx; an end-to-end user-provided backend proving third-party extensibility with isolation from argocd/flux; plus the argocd option-alias, flux synthesis, and bootstrap-manifest coverage.
9ce529d to
d7f0306
Compare
Stage three of the "pure functional core / imperative shell" direction (#102 is the file
layoutseam; the generators split madecompilepure). This makes presentation a pure function and opens it to pluggable, user-provided GitOps backends.Design writeup: https://gist.github.com/sini/d2a1b7876977e01cfb0712ee8d89dcfe
What changes
The enum +
mkIf (backend == "x")-guarded dispatch becomes a registry of pure backend records:nixidy.presentation.backends : attrsOf { perAppOptions ? []; typeImports ? []; present ? (ctx: {}); bootstrapFile ? null; }. Built-ins registerargocd/flux; a user adds an entry.nixidy.presentation.backend : str. Selects by key, with an unknown-key assertion.modules/presentation/default.nixresolves the selected record and wires its four contributions. Synthesis is a purepresent :: ctx -> attrsOf appConfig(no moduleconfigaccess, no per-backend guards); everymkIf (backend == ...)collapses into onebackends.${backend}lookup.presentbeing a function that is called (not a module that is imported) is what lets a user backend work without touching nixidy: it sidesteps the "a module'simportscan't readconfig" wall. (Context in the thread above.)ArgoCD stays first-class
Options relocated behind
nixidy.presentation.argocd.*; the oldnixidy.appOfApps.*/nixidy.defaults.*paths value-forward viamkRenamedOptionModulealiases, so existing configs are unchanged (a pre-existing app-of-apps test that sets the old paths still passes end-to-end). Default-backend rendered output is byte-identical.Flux backend (minimal, illustrative)
A minimal Flux backend proves the seam is not ArgoCD-shaped. It is intentionally a proof, not production (no HelmRelease/OCI parity;
bootstrapManifest.enableis not wired for it). A real Flux backend and the OCI-artifact delivery story are deliberately scoped out: that is a delivery axis, orthogonal to presentation, and a clean follow-up.User backends
A backend is a record assigned from ordinary config.
tests/presentation-user-backend.nixregisters acustombackend entirely in user config, selects it, renders end-to-end, and asserts isolation from argocd/flux.Tests
Unknown-backend dispatch assertion; direct unit tests of each backend's pure
presenton a fixturectx; the user-backend extensibility proof; plus argocd alias, flux synthesis, and bootstrap-manifest coverage.moduleTests56/56,libTests13/13,staticCheckclean.Commits
The first commit is #102 (the D4
build/layout seam) and drops out once #102 lands; review the twofeat(presentation)/test(presentation)commits.