No description
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Kiara Grouwstra 711e977f6b
All checks were successful
check / nix-unit (push) Successful in 11s
check / treefmt (push) Successful in 6m8s
flake: instantiate the entrypoint once per system (#6)
The flake wrapper applied the entrypoint once per `(attr, system)` pair. `import` memoizes the *file* value, not the *application*, so none of those applications shared any of the entrypoint's internal work -- each re-ran the whole `let`, including the `pkgs ? import sources.nixpkgs { ... }` default argument, i.e. a whole fresh nixpkgs.

Binding one instantiation per system in the `let` and reading every keyed output out of it collapses that.

Measured on `fediversity-ci`, `nix flake show --no-eval-cache`, warm store, second of two runs:

| | wall | user+sys | maxrss | nrThunks | gc.totalBytes |
|---|---|---|---|---|---|
| before | 1.99 s | 1.31 s | 223 MB | 565,116 | 168.7 MB |
| after | 1.50 s | 0.95 s | 132 MB | 261,485 | 76.0 MB |

Output is unchanged: `nix flake show --json --all-systems --no-eval-cache | jq -S .` is byte-identical before and after, and every `drvPath` is unmoved bar the `treefmt` check's, which hashes the project source and so moves with any edit to `flake.nix` itself.

This is the same defect that cost the fediversity monorepo far more (its entrypoints carry a whole deployment surface, not just a nixpkgs); the wrapper was copied from https://codeberg.org/kiara/poc-override-nix-deps, which is being fixed at the source too.

Reviewed-on: #6
2026-08-12 23:47:43 +02:00
.forgejo/workflows ci(update): add monthly npins-update workflow 2026-07-12 17:19:18 +02:00
md Cherry-pick three ideas from tofunix: error breadcrumbs, references, meta-arguments (#5) 2026-08-08 14:11:27 +02:00
npins npins: update nixpkgs 2026-08-05 16:57:54 +02:00
conversion-test.nix Cherry-pick three ideas from tofunix: error breadcrumbs, references, meta-arguments (#5) 2026-08-08 14:11:27 +02:00
conversion.nix Cherry-pick three ideas from tofunix: error breadcrumbs, references, meta-arguments (#5) 2026-08-08 14:11:27 +02:00
default.nix Expose helpers under lib; render docs via nix-docs-lib 2026-07-12 17:01:51 +02:00
flake.nix flake: instantiate the entrypoint once per system (#6) 2026-08-12 23:47:43 +02:00
lib.nix Initial import: Terraform<->Nix schema conversion library 2026-07-12 13:30:11 +02:00
LICENSE Add LICENSE (EUPL-1.2) to match the fediversity monorepo 2026-07-13 10:07:31 +02:00
README.md Cherry-pick three ideas from tofunix: error breadcrumbs, references, meta-arguments (#5) 2026-08-08 14:11:27 +02:00
treefmt.nix Initial import: Terraform<->Nix schema conversion library 2026-07-12 13:30:11 +02:00

nix-tf-schema

A small, self-contained library of pure Nix helpers that translate between OpenTofu/Terraform provider schemas (tofu providers schema -json) and Nix module types / tfvars JSON.

It has no domain logic -- nothing about any particular infrastructure, service, or deployment. Given a provider schema it can:

  • wrap Terraform attribute schemas into tfvars variable blocks (wrapTfType, wrapTfAttr, wrapTfAttrs, wrapTfProviderSchema, ...);
  • convert Terraform types and attributes into nixpkgs module types (fromTfTypes, fromTfVar, fromTfVars, fromTfProviderSchema, ...);
  • turn a resource schema into Nix options and ${var.*} references (resourceAttrsToOptions, resourceAttrsToVarRefs);
  • build cross-resource reference expressions for the hand-written parts of a module (ref, referenceType, refOr, unbrace);
  • type Terraform's own resource meta-arguments -- count, for_each, depends_on, provider, lifecycle (metaArgOptions, metaArgsToResourceBody);
  • assemble a complete Terraform module as JSON (mkTfModuleJson); and
  • extract provider schemas at build time by running tofu providers schema in a sandboxed derivation (extractProviderSchemas, an IFD).

Layout

  • conversion.nix -- the library ({ lib, pkgs, ... }: -> conversion attrset).
  • lib.nix -- two vendored helpers (cast, evalOption) it depends on.
  • conversion-test.nix -- the nix-unit suite (20 tests).
  • default.nix -- source of truth: builds a pkgs from the local nixpkgs pin, callPackages conversion.nix under lib, and exposes formatter + checks + packages.docs.
  • flake.nix -- thin wrapper adding the per-system layer over default.nix.
  • md/ -- the doc-site prose pages, rendered with the shared nix-docs-lib.

Consuming it

default.nix is callPackage-shaped and exposes the helpers under lib (so they do not clash with the flake-schema attrs), so a consumer that already has its own nixpkgs passes it in and eval uses that nixpkgs:

# in a repo that pins this via npins as `nix-tf-schema`
let
  sources = import ./npins;
  tfSchema = pkgs.callPackage "${sources.nix-tf-schema}" { };
in
tfSchema.lib.mkTfModuleJson { /* ... */ }

Pin it with npins:

npins add forgejo https://git.fediversity.eu fediversity nix-tf-schema --branch main

Documentation

The doc-commented API renders to a static site (via the shared nix-docs-lib ndg machinery):

nix build .#packages.x86_64-linux.docs

Known limitations

Three places where the translation is lossy. None of them is observable in what we generate today; each is recorded so the next person hitting one knows it is deliberate rather than an oversight.

  • null means unset, with no way to say "explicitly null". Optional attributes become nullOr T with default = null. Terraform needs no distinction here -- an argument set to null behaves exactly as if omitted, and the optional(...) type constraints we render carry no default, so the two coincide inside object types too. tofunix carries an unset sentinel because it bakes literals into the module JSON, where an unset option would emit a null key into blocks that reject one; our unset values are simply absent from the environment. It would start to matter if a code path rendered optional(T, default), or needed a key absent from a JSON payload rather than present-and-null.
  • int64 is typed as ints.s32. nixpkgs has no ints.s64, so the wider range is not enforced at eval time.
  • set is typed as listOf. Nix has no unordered collection type, so a set's ordering is not normalized and duplicates are not rejected.

Debugging

The schema walks tag every recursion step with builtins.addErrorContext, so a failure names the resource, attribute and block it was walking rather than surfacing as a bare attribute 'foo' missing. Every such breadcrumb is prefixed with [nix-tf-schema], which is what makes them greppable out of an otherwise enormous trace:

nix ... --show-trace 2> >(grep "… \[nix-tf-schema\]")

The contexts nest, so the surviving lines read back as the path into the schema, innermost first.

Development

# format
nix fmt

# run the unit tests (needs `nix` on PATH for the IFD test-extractProviderSchemas)
nix develop --command nix-unit ./conversion-test.nix

# sandbox-safe checks (treefmt)
nix flake check -L

The nix-unit suite is run by CI through the Forgejo workflow rather than as a nix flake check derivation, because test-extractProviderSchemas performs an import-from-derivation (tofu providers schema) that needs a recursive nix build the flake-check sandbox forbids.