PRD: Everything-is-an-app — git-driven app/values layout; drop curated per-app schemas
Nobody has claimed this yet.
- Dominant language
- Go Template
- Stars
- 2.3k
- Forks
- 186
- Avg merge
- 3d 11h
- Merged PRs (30d)
- 66
Description
# Everything-is-an-app: drop curated per-app schemas, move to a fully git-driven app/values layout
> Refined after a design grilling session. This supersedes the original PRD. It keeps the original goal (retire curated per-app JSON Schema + gotmpl translation, validate against vendor chart interfaces) and layers on a fully git-driven "everything is an app" file layout.
## Problem Statement
This proposal ensures full audibility of the platform (including configuration changes made by platform operator). End user is enabled to overwrite any configuration parameter with enough context over the managed helm chart values.
apl-core maintains a hand-curated JSON Schema for every core app inside `values-schema.yaml` (~7000 lines), plus a hand-written `.gotmpl` translation file per app under `values//` that reshapes curated fields into the shape the vendor Helm chart expects. Every one of the ~58 vendored charts requires this duplicated effort, then a hand-written `values-changes.yaml` migration entry whenever the curated shape changes.
This is expensive, lags vendor chart capability, and pushes users who need an uncurated field onto `_rawValues`, which bypasses validation. Because of the abstraction layer, users cannot see what the platform manages or how to override it.
Additionally, ArgoCD `Application` CRs are currently built in memory and server-side-applied (`src/cmd/apply-as-apps.ts`), with chart values embedded inline in `spec.source.helm.values`. This means platform state is not fully declarative in git, and app values, enable flags, and rendered artifacts are entangled.
## Solution (refined)
Make the entire core-app surface **git-driven** and **vendor-shaped**:
- **Everything is an app.** Every core-app `Application` CR becomes a file in the values repo under `env/manifests/namespaces/argocd/applications/-.yaml`, reconciled by a standing root **app-of-apps**. The operator's job shrinks to "write files + commit"; server-side apply is retained only for the bootstrap root app during initial install.
- **App identity = `(namespace, releaseName)`.** Vendor-shaped values live at `env/values///{values.custom.yaml, values.managed.yaml}`. Multi-chart apps (e.g. istio's 5 charts) each get their own release directory. `Application` CR name/filename keep today's `-` convention (`getAppName`) for a smooth upgrade.
- **Enable flags consolidated.** A single `env/settings/apps.yaml` (plain `apps:` map, no `AplApp` envelope) holds `enabled` and nested sub-flags per app. This replaces per-app `settings.yaml`. `env/apps/` is removed entirely.
- **`-artifacts` releases become plain manifests.** The operator renders each `-artifacts` release and explodes it into one-object-per-file under `env/manifests/namespaces///.yaml` (namespaced) or `env/manifests/global//.yaml` (cluster-scoped), reconciled by the existing `gitops-ns-` / `gitops-global` apps. These releases lose their own `Application` CRs.
- **Vendor-shaped values, split into custom + managed.** `values.custom.yaml` is user-edited (raw editor), validated against the vendor chart's `values.schema.json` when one exists. `values.managed.yaml` is platform-computed (cross-cutting/derived values). ArgoCD multi-source `valueFiles` lists `values.managed.yaml` then `values.custom.yaml` (custom wins on overlap).
Team apps are out of scope and unaffected.
## Key Design Decisions
### 1. Delivery model — git-driven app-of-apps
- `Application` CRs are files in `env/manifests/namespaces/argocd/applications/`, reconciled by a root app-of-apps.
- The root app-of-apps is **server-side-applied during initial install** so ArgoCD knows to sync from the values repo. Everything is git-driven thereafter.
- The operator writes CR files + value files + artifact manifests and commits, instead of server-side-applying per-app CRs.
### 2. App identity & values layout
- Identity `(namespace, releaseName)` is used everywhere: values path, settings key context, `Application` name/filename.
- `env/values///values.custom.yaml` + `env/values///values.managed.yaml`.
- `Application` CR: multi-source — one source for the chart (as today), one `ref` source to the values repo, `helm.valueFiles: [ .../values.managed.yaml, .../values.custom.yaml ]` in that order.
- File/CR name = `-` (unchanged convention).
### 3. Consolidated `env/settings/apps.yaml`
- Single file, plain `apps:` map, `AplApp` envelope dropped.
- **Full nested current shape kept** (e.g. `istio.ambient`, `istio.egressGateway.enabled`) — not a strict single `enabled`.
- `enabled`/sub-flags are **per-app** and continue to gate multiple releases; every existing `installed: {{ $a | get ".enabled" }}` expression keeps working unchanged.
- Non-app gating conditions (`alerts.receivers`, cluster provider, etc.) stay in their existing settings files.
- `values-schema.yaml` shrinks to validating only this `apps.` enable surface.
### 4. `-artifacts` rendering & placement
- Operator renders `-artifacts` releases during reconcile using the **unchanged** `charts/raw` + `values//-raw.gotmpl` source, splits the multi-doc stream, writes one Kubernetes object per file, and commits before the gitops app syncs.
- Path: `` = **lowercased singular** (`clusterissuer`, `externalsecret`, `certificate`); `` = `metadata.name`.
- Namespaced → `env/manifests/namespaces///.yaml`
- Cluster-scoped → `env/manifests/global//.yaml`
- `-artifacts` releases lose their own `Application` CR (removes ~23 apps) and ride the existing `gitops-ns-` / `gitops-global` apps.
- The Phase-1 bootstrap subset (`ExternalSecret/custom-ca`, `ClusterIssuer/custom-ca` when `customRootCA` is set) is still applied directly during initial install per ADR 2026-08-20; the full set lands in git for ArgoCD.
### 5. Values pipeline & managed computation
- **`values.managed.yaml` is rendered by gotmpl** (not TypeScript). The per-app `values//.gotmpl` is retargeted to emit **only** the platform cross-cutting/derived vendor-shaped keys; the curated user-field passthrough is dropped (users write `values.custom.yaml` directly). The operator runs `helm/helmfile write-values` then commits `values.managed.yaml` each reconcile.
- **helmfile.d release definitions stay** as the release registry (name/namespace/chart/`installed:` gate); the operator enumerates releases via `helmfile list`.
- ArgoCD renders charts from `valueFiles` for steady state. helmfile direct-apply is retained only for the **explicit Phase-1 bootstrap set** (apps that must be up before ArgoCD can reconcile — e.g. argocd, sealed-secrets, istio/CNI, custom-ca subset).
- `values//.gotmpl` deleted; `values//-raw.gotmpl` kept (feeds §4).
### 6. Managed vs custom boundary
- **Managed** (platform-computed, `values.managed.yaml`): values driven by `otomi.useORCS` (OCI Registry Cache Service / proxied registry across all apps), `otomi.nodeSelector`, `otomi.version` (image tags), `otomi.globalPullSecret`, `cluster.domainSuffix`, `cluster.name`, plus OIDC/cross-cutting wiring.
- **Managed** (platform-computed, `values.managed.yaml`): are update only on either version upgrade higher order setting update (app enabled, useORCS, etc).
- `cluster.domainSuffix`, `cluster.name`, plus OIDC/cross-cutting wiring.
- **Custom** (user, `values.custom.yaml`): 3rd-party integration settings and any user override.
### 7. Secrets (secret-free value files)
- `values.custom.yaml` and `values.managed.yaml` are **secret-free by contract**.
- Secret material continues to be projected at the Kubernetes layer via SealedSecrets/ExternalSecrets (the `-artifacts` mechanism) — unchanged.
- Where the platform must wire a secret (e.g. OIDC client secret), `values.managed.yaml` emits a **reference** (`existingSecret`/`valueFrom`), never a literal.
### 8. Read-write seam (`FileMap`)
- App **chart** values (`custom`/`managed`) live **outside** the `FileMap`/`.Values` merge — plain vendor files, not `AplKind` envelopes. They're read-written by a separate raw-file path: console raw editor + apl-api write-time vendor-schema validation for `custom.yaml`; operator gotmpl render for `managed.yaml`; ArgoCD consumes via `valueFiles`.
- The merged `.Values` tree (`cluster.*`, `otomi.*`, `_derived.*`, `apps..enabled`) still exists and **feeds** the managed gotmpl render.
- `AplApp`'s `FileMap` entry collapses into a **single-file `platformSettings` map**: `$.apps` → `env/settings/apps.yaml` (mirroring `AplCluster`), `loadToSpec: true`. Only enable flags load into `.Values.apps.*`.
### 9. Migration — two tracks
- **(a) Offline `otomi migrate` (kept)** handles platform **settings** file moves via the existing `values-changes.yaml` mechanism. `otomi migrate`/`values-changes.yaml` is **not** retired — it remains for platform settings.
- **(b) Runtime upgrade script (operator, cluster-aware)** does the app-values diff extraction:
1. Read the existing `Application`'s effective values from the cluster (inline `spec.source.helm.values`, fallback `helm get values`).
2. Render the new `values.managed.yaml`.
3. `custom = deepDiff(existing, managed)` — a key lands in custom iff it is absent from or differs from managed (minimal custom.yaml).
4. Write `custom` + `managed`, commit, and let the multi-source `Application` (same `-` name) supersede the inline one as an update, not a replace.
- This avoids hand-authored per-app curated→vendor mappings and auto-handles multi-release splitting, because the deployed cluster already holds vendor-shaped effective values.
## Testing Decisions
Assert observable behavior at boundaries a caller depends on (files written, `Application` CR shape, validation accept/reject), not internal value computation.
1. **Core-app reconcile / CR-shape function** (extending today's untested `getArgocdCoreAppManifest` / `createArgocdAppManifest` in `src/cmd/apply-as-apps.ts`): follow the existing DI `deps = {...}` pattern (as `addGitOpsApps`) and the pure-function assertion style of `getArgocdGitopsManifest`. Assert: value files written before the CR file is committed (bootstrap ordering); multi-source `valueFiles` list `managed` then `custom`; reconcile reads `env/settings/apps.yaml` `enabled` to decide whether to act. No real git/K8s — everything crosses injected `deps`.
2. **apl-api server-side validation** (`validateValues` in `src/cmd/validate-values.ts`, currently untested): pure function — chart name + `values.custom.yaml` in, accept/reject out. Use a real vendor `values.schema.json` fixture (e.g. cert-manager) and a schema-less chart to cover both paths.
3. **`-artifacts` splitter**: pure function — rendered multi-doc YAML in, `{ path → single-object YAML }` out; assert kind-folder casing and namespaced-vs-global bucketing.
4. Extend `src/common/repo.ts`'s `FileMap` self-consistency test (`describe('File map constraints', ...)`) for the new `env/settings/apps.yaml` single-file map and the raw value-file paths.
Out of seam (manual/exploratory): console raw-editor UX, CI schema-diff comment, and the runtime diff-migration's per-cluster behavior.
## Out of Scope
- Team apps and their configuration mechanism.
- Any change to secret handling beyond making value files secret-free (SealedSecrets/ESO unchanged).
- Authoring/vendoring `values.schema.json` for charts that don't ship one (no gate → errors surface at render/sync).
- Retiring `otomi migrate`/`values-changes.yaml` for platform settings (explicitly kept).
## Further Notes
- Reverses `adr/2021-10-18-defaults-and-derived.md` for `values.managed.yaml` specifically (values written back to the repo). Worth an ADR note/update.
- Only ~10 of ~58 charts ship a `values.schema.json` today; the rest get the new layout with no validation gate.
- `apps.yaml` (console catalog metadata) and `values-schema.yaml`'s `apps.properties` list undercount the ~58-directory scope; reconciling those registries is an implementation detail.
---
## Sub-issues (vertical slices)
Delivered as independent tracer-bullet issues (each cuts end-to-end: settings → values → Application CR → app-of-apps → validation/ArgoCD). All labeled `ready-for-agent`. Pilot = `cert-manager` (always-installed, has a vendor schema, has artifacts). Runtime migration is the final step.
- [ ] #3571 — Pilot `cert-manager` end-to-end (app-of-apps + `env/settings/apps.yaml` + `env/values///`) — _no blockers_ — §1, §2, §3, §5, §8
- [ ] #3572 — Validate `values.custom.yaml` against vendor `values.schema.json` (apl-api + console) — _blocked by #3571_ — §8
- [ ] #3573 — Render `cert-manager` `-artifacts` into git manifests (one object per file) — _blocked by #3571_ — §4
- [ ] #3574 — Non-blocking CI comment on vendor `values.schema.json` drift — _no blockers_
- [ ] #3575 — Cutover: Ingress/DNS/TLS (istio ×5, kubernetes-gateways, external-dns) — _blocked by #3571, #3572, #3573_ — shares `domainSuffix`/`entrypoint`/TLS
- [ ] #3576 — Cutover: SSO/OIDC (keycloak, oauth2-proxy(+redis), argocd, otomi-console) — _blocked by #3571, #3572, #3573_ — shares `oidc.*`
- [ ] #3577 — Cutover: Storage & DB (harbor, gitea, loki, kubeflow-pipelines) — _blocked by #3571, #3572, #3573_ — shares `obj.*` + `databases.*`
- [ ] #3578 — Cutover: Monitoring (prometheus, grafana, promtail, alertmanager, otel) — _blocked by #3571, #3572, #3573_ — shares `alerts.*`
- [ ] #3579 — Cutover: standalone (universal-only) + delete `env/apps/` + secrets audit — _blocked by #3571, #3572, #3573_ — §6, §7
- [ ] #3580 — Migrate existing clusters: offline `otomi migrate` settings + runtime diff-extraction (**final step**) — _blocked by #3575, #3576, #3577, #3578, #3579_ — §9
Dependency order: #3571 → (#3572, #3573) → cutover batches #3575–#3579 → #3580. #3574 is independent.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the proposed tests and existing entry points in src/cmd/apply-as-apps.ts, especially getArgocdCoreAppManifest, createArgocdAppManifest, and the injected-dependency patterns. Then read validateValues in src/cmd/validate-values.ts and the FileMap tests in src/common/repo.ts. Done means the specified file layout, Application valueFiles ordering, vendor-schema validation, artifact splitting, and apps.yaml mapping are covered by boundary tests.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- git, helm, kubernetes, typescript
- Domain
- devops, infrastructure
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100