[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-81751":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":14,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":16,"stars7d":17,"stars30d":18,"stars90d":15,"forks30d":15,"starsTrendScore":19,"compositeScore":20,"rankGlobal":10,"rankLanguage":10,"license":21,"archived":22,"fork":22,"defaultBranch":23,"hasWiki":22,"hasPages":22,"topics":24,"createdAt":10,"pushedAt":10,"updatedAt":25,"readmeContent":26,"aiSummary":27,"trendingCount":15,"starSnapshotCount":15,"syncStatus":28,"lastSyncTime":29,"discoverSource":30},81751,"flate","home-operations\u002Fflate","home-operations","A Flux resource inflator ⇄","",null,"Go",43,6,1,0,8,14,16,24,2.54,"GNU Affero General Public License v3.0",false,"main",[],"2026-06-12 02:04:19","# flate\n\n> Render and diff Flux GitOps repositories fully offline — one static binary, no cluster, no `kubectl`, no shellouts.\n\n[![Tests](https:\u002F\u002Fgithub.com\u002Fhome-operations\u002Fflate\u002Factions\u002Fworkflows\u002Ftests.yaml\u002Fbadge.svg)](https:\u002F\u002Fgithub.com\u002Fhome-operations\u002Fflate\u002Factions\u002Fworkflows\u002Ftests.yaml)\n[![Build](https:\u002F\u002Fgithub.com\u002Fhome-operations\u002Fflate\u002Factions\u002Fworkflows\u002Fbuild.yaml\u002Fbadge.svg)](https:\u002F\u002Fgithub.com\u002Fhome-operations\u002Fflate\u002Factions\u002Fworkflows\u002Fbuild.yaml)\n[![Lint](https:\u002F\u002Fgithub.com\u002Fhome-operations\u002Fflate\u002Factions\u002Fworkflows\u002Flint.yaml\u002Fbadge.svg)](https:\u002F\u002Fgithub.com\u002Fhome-operations\u002Fflate\u002Factions\u002Fworkflows\u002Flint.yaml)\n[![Release](https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Fv\u002Frelease\u002Fhome-operations\u002Fflate)](https:\u002F\u002Fgithub.com\u002Fhome-operations\u002Fflate\u002Freleases)\n[![License](https:\u002F\u002Fimg.shields.io\u002Fgithub\u002Flicense\u002Fhome-operations\u002Fflate)](LICENSE)\n\nflate is a Go rewrite of [flux-local](https:\u002F\u002Fgithub.com\u002Fallenporter\u002Fflux-local). Helm, kustomize, go-git, and oras-go are linked as native libraries, so a `kind` cluster plus a stack of CLIs (`helm`, `kustomize`, `flux`, `kubectl`) collapse into one binary that runs in CI in seconds, not minutes. Changed-only mode reconciles just the subtree a PR touches, dropping single-file diffs to tens of milliseconds on real home-ops repos.\n\n## Install\n\n```bash\nbrew install --cask home-operations\u002Ftap\u002Fflate\ngo install github.com\u002Fhome-operations\u002Fflate\u002Fcmd\u002Fflate@latest\ndocker pull ghcr.io\u002Fhome-operations\u002Fflate:latest\n```\n\n…or in a GitHub Actions workflow:\n\n```yaml\n- uses: home-operations\u002Fflate\u002Faction@main\n```\n\n## Use\n\n```bash\nflate get ks       --path .\u002Fkubernetes\nflate build hr     --path .\u002Fkubernetes plex\nflate diff ks      --path .\u002Fkubernetes --path-orig ..\u002Fbaseline\u002Fkubernetes\nflate diff all     --path .\u002Fkubernetes --path-orig ..\u002Fbaseline\u002Fkubernetes\nflate diff images  --path .\u002Fkubernetes --path-orig ..\u002Fbaseline\u002Fkubernetes -o json\nflate test all     --path .\u002Fkubernetes\n```\n\nThe `[name]` positional on `get ks\u002Fhr` and `build`\u002F`diff`\u002F`test ks\u002Fhr` is matched against the resource's bare name (`metadata.name`), not `namespace\u002Fname`. Use `-n \u002F --namespace` to scope.\n\nEvery reconcile-running command takes `--path \u003Cdir>` (default `.`); `--path-orig \u003Cdir>` switches into changed-only mode. `flate \u003Cverb> --help` lists every flag. `get`, `build`, `diff`, and `test` all run the same offline reconcile pipeline before producing output, so referenced Git, OCI, Helm, Bucket, or remote kustomize sources must be reachable. Source caches respect Flux intervals: immutable pins reuse cache; mutable refs refresh when their interval expires.\n\n| Verb | Targets | Notes |\n|---|---|---|\n| `get` | `ks`, `hr`, `images`, `all` | List or summarize. `-o table` \u002F `yaml` \u002F `json` \u002F `name`. |\n| `build` | `ks`, `hr`, `all` | Render Kustomizations and HelmReleases to YAML or JSON. |\n| `diff` | `ks`, `hr`, `images`, `all` | Path-keyed diff against `--path-orig`, rendered via [dyff](https:\u002F\u002Fgithub.com\u002Fhomeport\u002Fdyff) in `--output github` mode. K8s-aware: list entries match by identifier (container name, env-var name), so a reorder shows as `⇆ order changed` instead of a wall of phantom value churn. |\n| `test` | `ks`, `hr`, `all` | Pytest-style `PASS` \u002F `FAIL` \u002F `SKIPPED` per resource. Non-zero exit on any failure. |\n\n`get ks` and `get hr` accept `-l\u002F--selector key=value` for label filtering. `diff` accepts `--strip-attr \u003Ckey>` (repeatable) to drop annotation\u002Flabel keys before comparison; the default set covers chart-bump noise (`helm.sh\u002Fchart`, `checksum\u002Fconfig`, `app.kubernetes.io\u002Fversion`, `chart`). Helm template flags are available on every reconcile-running subcommand because flate reconciles the full graph before filtering output. Reconcile-running subcommands accept `--allow-missing-secrets` to soft-skip source auth Secrets and omit generated HR `valuesFrom` refs that only exist in the live cluster — see [Behaviors](#behaviors).\n\n**Default output filters.** `--skip-secrets` and `--skip-crds` both default to `true` — `build` and `diff` strip rendered `Secret` and `CustomResourceDefinition` objects from manifest output. Pass `--skip-secrets=false` \u002F `--skip-crds=false` to include them; `--skip-kinds \u003Ckind>` (repeatable) drops additional kinds. These are output-stream filters, distinct from `--allow-missing-secrets`, which gates source auth and generated HR values Secret readiness.\n\n## Changed-only mode\n\n`--path-orig` flips every command into change-aware reconcile. flate diffs the two paths, walks ownership backwards (longest matching Flux KS `spec.path`, including `spec.components`), and reconciles only the touched subtree plus its content dependencies.\n\nIn the keep-set: direct file edits, chart sources, KS `sourceRef`, HR `valuesFrom`, kustomize components (touching a shared component re-renders every consumer).\n\nOut: `dependsOn` (reconcile-ordering, not content — skipped resources still get marked `Ready` so downstream waits unblock) and meta-Kustomizations that don't claim the deeper file.\n\n```bash\ngit worktree add ..\u002Fbaseline main\nflate diff ks --path .\u002Fkubernetes --path-orig ..\u002Fbaseline\u002Fkubernetes\n```\n\n`--path` can point at a narrow Flux entry like `.\u002Fkubernetes\u002Fflux\u002Fcluster`; flate iteratively follows each loaded KS's `spec.path` to discover the rest of the tree.\n\n## Source kinds and auth\n\n| Kind | Status | Auth (`spec.secretRef`) |\n|---|---|---|\n| `GitRepository` | full | HTTPS: `username` + `password` or `bearerToken`. SSH: `identity` (+ optional `password`, `known_hosts`). |\n| `OCIRepository` | full | `.dockerconfigjson`. Falls back to `--registry-config`, then `~\u002F.docker\u002Fconfig.json`. |\n| `HelmRepository` | full | HTTP basic: `username` + `password`; OCI flavor routes through the OCI puller. |\n| `HelmChart` | full | Inline (`HR.spec.chart`) and standalone CRD. |\n| `Bucket` | `generic` only | `accesskey` + `secretkey`. `aws`\u002F`gcp`\u002F`azure` fail loud — use static creds. |\n| `ExternalArtifact` | `file:\u002F\u002F` only | `status.artifact.url` must be a local path. |\n\nPLACEHOLDER-wiped values (the always-on wipe of cleartext Secret data) are treated as missing — auth fails with a clear \"missing username\u002Fpassword\" instead of attempting auth with the placeholder string. See [Behaviors](#behaviors) for `--allow-missing-secrets`, which soft-skips affected sources end-to-end.\n\n## Behaviors\n\n**SOPS** — `spec.decryption` is not implemented. Encrypted Secret values get wiped to `..PLACEHOLDER_\u003Ckey>..`, same as cleartext values under the always-on wipe. Downstream `postBuild.substituteFrom` lookups resolve to the placeholder string rather than failing.\n\n**`spec.suspend`** — honored on every reconcilable CR. Suspended resources mark `Ready \u002F \"suspended\"` and produce no rendered output.\n\n**`--allow-missing-secrets`** — off by default. When set, a source whose auth `secretRef` is missing or PLACEHOLDER-wiped marks `Ready \u002F \"skipped: …\"` instead of `Failed`, and consumers (KS `sourceRef`, HR `chartRef`) propagate the skip so `flate test` reports SKIPPED rather than a cascade of FAILED. HelmRelease `valuesFrom` Secret\u002FConfigMap refs that cannot materialize offline are omitted so the release can render with the remaining values. Verify, cert, and proxy `secretRef`s still fail loud, since silently dropping verification or TLS material is a security downgrade.\n\n**`spec.dependsOn[].readyExpr` (CEL)** — evaluated against `self` and `dep` projections, matching upstream kustomize- and helm-controller binding:\n\n```yaml\ndependsOn:\n- name: infra-controllers\n  readyExpr: |\n    dep.status.conditions.exists(c, c.type == \"Healthy\" && c.status == \"True\")\n```\n\n**Substitution opt-out** — the `kustomize.toolkit.fluxcd.io\u002Fsubstitute: disabled` label or annotation is honored per-resource, matching kustomize-controller. Used for ConfigMaps embedding bash array expansions envsubst can't parse.\n\n**Signature verification** — `OCIRepository` uses cosign keyed mode (`spec.verify.secretRef` with PEM keys) verified through stdlib crypto, no sigstore dep tree. `GitRepository` uses PGP via `spec.verify.{mode,secretRef}`. Cosign keyless and `notation` are not supported (see [Limits](#limits)).\n\n## Library use\n\n`pkg\u002Forchestrator` is the embed entry point.\n\n```go\nimport (\n    \"context\"\n    \"github.com\u002Fhome-operations\u002Fflate\u002Fpkg\u002Forchestrator\"\n)\n\no, _ := orchestrator.New(orchestrator.Config{Path: \"\u002Fpath\u002Fto\u002Fcluster\"})\nres, err := o.Render(context.Background())\n\u002F\u002F res is non-nil even when err != nil — partial output stays usable.\nfor id, docs := range res.Manifests {\n    \u002F\u002F rendered YAML docs for the KS \u002F HR with this id\n}\nfor id, info := range res.Failed {\n    \u002F\u002F structured failure list keyed by NamedResource\n}\n```\n\nOther entry points worth knowing:\n\n- `Orchestrator.WithFetcher(kind, f)` — swap any source fetcher (in-memory fakes for tests, custom kinds).\n- `Store.OnObject` \u002F `OnStatus` \u002F `OnArtifact` — typed listeners; payloads are pre-cast.\n- `helm.Prepare(hr, lookup, provider)` then `helmClient.TemplateDocs(...)` — render one HelmRelease without the orchestrator. `lookup` is a `manifest.HelmChartLookup` (`func(ns, name string) *HelmChartSource`). `kustomize.Prepare(ks, provider)` is the symmetric helper for Kustomizations.\n- `discovery.Run(ctx, Config{Path, Store, WipeSecrets})` — load phase as a standalone unit.\n- `change.Filter.Add(id)` — extend the changed-only-mode keep set at runtime when a custom controller emits a child that wasn't visible at filter-build time. Call BEFORE `Store.AddObject(child)` so the synchronous listener sees the extended set.\n- `Store.Mutate[T]` — clone-then-AddObject helper encoding the immutability contract. See [`pkg\u002Fmanifest\u002Fdoc.go`](pkg\u002Fmanifest\u002Fdoc.go) for the full rule.\n\n## Architecture\n\n```\ndiscovery → Store ⇄ events ⇄ controllers (source · kustomization · helmrelease)\n```\n\nPipeline: bootstrap-source seed → loader pre-pass excluding `configMapGenerator`\u002F`secretGenerator` data files → file walk → `spec.path` + ResourceSet fixed-point expansion → bootstrap-source aliasing for unresolved `GitRepository` refs → namespace inheritance → parent index → `dependsOn` cycle preflight → change-filter → controllers fire → render → render-time keep-set extension for emitted children → orphan demotion → output.\n\nThe Store is the single source of truth. Every stored manifest is immutable; mutation routes through `Store.Mutate[T]` (clone, mutate, AddObject). Helm chart loads coalesce through a per-path keylock — N parallel reconciles of the same chart issue exactly one parse.\n\n## Limits\n\nflate is rendering-only.\n\n- **No SOPS decryption.** Values wiped; pre-decrypt if you need them in the diff.\n- **No cosign keyless.** Keyed verification works end-to-end; keyless logs and renders unverified (no offline trust roots).\n- **No notation.** Fails loud.\n- **No cloud workload identity.** `spec.serviceAccountName` is a no-op; use static creds in a Secret.\n- **No `healthChecks`.** flate tracks resource readiness, not status conditions of rendered objects.\n- **`ResourceSetInputProvider`: `Static` only.** Dynamic providers (GitHub, GitLab, OCIArtifactTag, ExternalService) need network access and contribute zero inputs.\n- **Diff output isn't a unified-diff patch.** `flate diff` emits dyff path-keyed syntax (`@@ \u003Cpath> @@`); GitHub's diff lexer renders it natively but it won't apply with `patch` \u002F `git apply` — use the rendered output of `flate build` if you need a literal patch.\n\n## Development\n\n```bash\ngo build .\u002Fcmd\u002Fflate\ngo test .\u002F...\ngo test -race .\u002F...\ngolangci-lint run .\u002F...\n```\n\nTool versions pin via [mise](https:\u002F\u002Fmise.jdx.dev). Testdata lives in [`testdata\u002F`](testdata\u002F); [`test\u002Fe2e`](test\u002Fe2e\u002F) runs the cobra command tree in-process — no fork\u002Fexec, no freshly built binary.\n\n## License\n\nAGPL-3.0. flate borrows behavior and test fixtures from [flux-local](https:\u002F\u002Fgithub.com\u002Fallenporter\u002Fflux-local) (Apache-2.0).\n","Flate 是一个用于渲染和比较 Flux GitOps 仓库的工具，完全支持离线操作。它通过将 Helm、kustomize、go-git 和 oras-go 作为本地库链接，将多个 CLI 工具的功能整合到一个单一的二进制文件中，从而无需集群或 `kubectl` 等外部依赖即可运行。flate 支持快速 CI 流程，并且在处理 PR 变更时仅对受影响的子树进行操作，大大提升了效率。此项目适用于需要高效离线管理 Kubernetes 资源定义的场景，如个人运维环境或自动化测试流程中，能够显著减少构建时间和资源消耗。",2,"2026-06-11 04:06:14","CREATED_QUERY"]