From 1353687dc232921305a065192acff197bb4d0207 Mon Sep 17 00:00:00 2001 From: Hassan Abedi Date: Thu, 30 Apr 2026 15:42:12 +0200 Subject: [PATCH] Add two Nix/Flake examples (with their note files) --- 39-flake-apps/README.md | 20 ++++++ 39-flake-apps/flake.lock | 27 ++++++++ 39-flake-apps/flake.nix | 112 +++++++++++++++++++++++++++++++ 40-path-inputs/README.md | 19 ++++++ 40-path-inputs/catalog/flake.nix | 48 +++++++++++++ 40-path-inputs/flake.lock | 39 +++++++++++ 40-path-inputs/flake.nix | 65 ++++++++++++++++++ notes/003-flakes.md | 5 ++ notes/042-flake-apps.md | 62 +++++++++++++++++ notes/043-local-path-inputs.md | 57 ++++++++++++++++ 10 files changed, 454 insertions(+) create mode 100644 39-flake-apps/README.md create mode 100644 39-flake-apps/flake.lock create mode 100644 39-flake-apps/flake.nix create mode 100644 40-path-inputs/README.md create mode 100644 40-path-inputs/catalog/flake.nix create mode 100644 40-path-inputs/flake.lock create mode 100644 40-path-inputs/flake.nix create mode 100644 notes/042-flake-apps.md create mode 100644 notes/043-local-path-inputs.md diff --git a/39-flake-apps/README.md b/39-flake-apps/README.md new file mode 100644 index 0000000..fd341cb --- /dev/null +++ b/39-flake-apps/README.md @@ -0,0 +1,20 @@ +# 39-flake-apps + +This example shows dedicated `apps.` outputs. + +It includes: + +- one default app that prints a rollout report, +- one named app, `promote`, that prints the next rollout wave, +- package outputs that back those apps, and +- a check that runs both app programs. + +Useful commands: + +```bash +nix flake show +nix run +nix run .#promote +nix build .#report +nix flake check +``` diff --git a/39-flake-apps/flake.lock b/39-flake-apps/flake.lock new file mode 100644 index 0000000..dfdfdf9 --- /dev/null +++ b/39-flake-apps/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1776548001, + "narHash": "sha256-ZSK0NL4a1BwVbbTBoSnWgbJy9HeZFXLYQizjb2DPF24=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b12141ef619e0a9c1c84dc8c684040326f27cdcc", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/39-flake-apps/flake.nix b/39-flake-apps/flake.nix new file mode 100644 index 0000000..8fa04f1 --- /dev/null +++ b/39-flake-apps/flake.nix @@ -0,0 +1,112 @@ +{ + # Exposes `apps.` entries that point at small runnable programs. + description = "A minimal flake apps example"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + }; + + outputs = + { self, nixpkgs, ... }: + let + system = "x86_64-linux"; + pkgs = import nixpkgs { inherit system; }; + + releaseName = "search-index-2026-04"; + releasePlan = [ + { + stage = "staging"; + percentage = 10; + owner = "team-search"; + gates = [ + "smoke-tests" + "error-budget" + ]; + } + { + stage = "canary"; + percentage = 35; + owner = "team-search"; + gates = [ + "dashboard-review" + "support-readiness" + ]; + } + { + stage = "production"; + percentage = 100; + owner = "team-search"; + gates = [ + "canary-slo" + "stakeholder-signoff" + ]; + } + ]; + + renderWave = + wave: + "${wave.stage}: ${toString wave.percentage}% owned by ${wave.owner}; gates: ${builtins.concatStringsSep ", " wave.gates}"; + + reportText = builtins.concatStringsSep "\n" ( + [ + "release: ${releaseName}" + "waves:" + ] + ++ map renderWave releasePlan + ); + + nextWave = builtins.elemAt releasePlan 1; + + reportPackage = pkgs.writeShellApplication { + name = "show-rollout-report"; + text = '' + cat <<'EOF' + ${reportText} + EOF + ''; + }; + + promotePackage = pkgs.writeShellApplication { + name = "suggest-next-promotion"; + text = '' + echo "next wave: ${nextWave.stage} (${toString nextWave.percentage}%) after ${builtins.concatStringsSep " and " nextWave.gates}" + ''; + }; + in + { + packages.${system} = { + default = reportPackage; + report = reportPackage; + promote = promotePackage; + }; + + apps.${system} = { + default = { + type = "app"; + program = "${reportPackage}/bin/show-rollout-report"; + meta.description = "Print the rollout report for a release train."; + }; + + promote = { + type = "app"; + program = "${promotePackage}/bin/suggest-next-promotion"; + meta.description = "Print the next rollout wave that should be promoted."; + }; + }; + + checks.${system}.apps-run = pkgs.runCommand "flake-apps-run" { } '' + report="$(${self.apps.${system}.default.program})" + promote="$(${self.apps.${system}.promote.program})" + + printf '%s\n' "$report" | grep -q '^release: ${releaseName}$' + printf '%s\n' "$report" | grep -q '^production: 100% owned by team-search; gates: canary-slo, stakeholder-signoff$' + + if [ "$promote" != "next wave: canary (35%) after dashboard-review and support-readiness" ]; then + echo "unexpected promotion output: $promote" >&2 + exit 1 + fi + + echo ok > "$out" + ''; + }; +} diff --git a/40-path-inputs/README.md b/40-path-inputs/README.md new file mode 100644 index 0000000..fc9716c --- /dev/null +++ b/40-path-inputs/README.md @@ -0,0 +1,19 @@ +# 40-path-inputs + +This example shows a local `path:` input that points at another flake inside the example directory. + +It includes: + +- one nested flake in `catalog/`, +- pure release metadata exposed from that nested flake through `lib`, +- one package that renders the imported data, and +- a check that proves the parent flake is reading the child flake output. + +Useful commands: + +```bash +nix flake metadata +nix build +./result/bin/show-local-plan +nix flake check +``` diff --git a/40-path-inputs/catalog/flake.nix b/40-path-inputs/catalog/flake.nix new file mode 100644 index 0000000..f69e72e --- /dev/null +++ b/40-path-inputs/catalog/flake.nix @@ -0,0 +1,48 @@ +{ + # Exposes pure release metadata for the parent flake that imports this + # directory with `path:./catalog`. + description = "Local release catalog for the path input example"; + + outputs = _: { + lib = { + releaseName = "checkout-2026-05"; + + serviceOwners = { + api = "team-checkout"; + billing = "team-revenue"; + worker = "team-fulfillment"; + }; + + rolloutWaves = [ + { + name = "staging"; + services = [ + "api" + "worker" + ]; + approvals = [ "qa" ]; + } + { + name = "canary"; + services = [ "api" ]; + approvals = [ + "sre" + "support" + ]; + } + { + name = "production"; + services = [ + "api" + "billing" + "worker" + ]; + approvals = [ + "change-advisory" + "finance-signoff" + ]; + } + ]; + }; + }; +} diff --git a/40-path-inputs/flake.lock b/40-path-inputs/flake.lock new file mode 100644 index 0000000..d0a10d0 --- /dev/null +++ b/40-path-inputs/flake.lock @@ -0,0 +1,39 @@ +{ + "nodes": { + "catalog": { + "locked": { + "path": "./catalog", + "type": "path" + }, + "original": { + "path": "./catalog", + "type": "path" + }, + "parent": [] + }, + "nixpkgs": { + "locked": { + "lastModified": 1776548001, + "narHash": "sha256-ZSK0NL4a1BwVbbTBoSnWgbJy9HeZFXLYQizjb2DPF24=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "b12141ef619e0a9c1c84dc8c684040326f27cdcc", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "catalog": "catalog", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/40-path-inputs/flake.nix b/40-path-inputs/flake.nix new file mode 100644 index 0000000..c071154 --- /dev/null +++ b/40-path-inputs/flake.nix @@ -0,0 +1,65 @@ +{ + # Uses a local `path:` input to consume pure data from another flake. + description = "A minimal local path input example"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + catalog.url = "path:./catalog"; + }; + + outputs = + { + self, + nixpkgs, + catalog, + ... + }: + let + system = "x86_64-linux"; + pkgs = import nixpkgs { inherit system; }; + + inherit (catalog.lib) + releaseName + serviceOwners + rolloutWaves + ; + + renderWave = + wave: + "${wave.name}: ${builtins.concatStringsSep ", " wave.services} with approvals ${builtins.concatStringsSep ", " wave.approvals}"; + + ownerLines = pkgs.lib.mapAttrsToList (service: owner: "${service}: ${owner}") serviceOwners; + + planText = builtins.concatStringsSep "\n" ( + [ + "release: ${releaseName}" + "owners:" + ] + ++ ownerLines + ++ [ + "waves:" + ] + ++ map renderWave rolloutWaves + ); + in + { + packages.${system}.default = pkgs.writeShellApplication { + name = "show-local-plan"; + text = '' + cat <<'EOF' + ${planText} + EOF + ''; + }; + + checks.${system}.catalog-is-used = pkgs.runCommand "local-path-input-check" { } '' + plan="$(${pkgs.lib.getExe self.packages.${system}.default})" + + printf '%s\n' "$plan" | grep -q '^release: checkout-2026-05$' + printf '%s\n' "$plan" | grep -q '^billing: team-revenue$' + printf '%s\n' "$plan" | grep -q '^production: api, billing, worker with approvals change-advisory, finance-signoff$' + + echo ok > "$out" + ''; + }; +} diff --git a/notes/003-flakes.md b/notes/003-flakes.md index 575a30c..06b393e 100644 --- a/notes/003-flakes.md +++ b/notes/003-flakes.md @@ -48,6 +48,11 @@ tarball+https://…/src.tar.gz nixpkgs # registry alias (resolves via `nix registry`) ``` +This repository now has focused examples for two of those cases: + +- `39-flake-apps/` covers `apps.` outputs, and +- `40-path-inputs/` covers a local `path:` input. + Non-flake sources (a repo without `flake.nix`): ```nix diff --git a/notes/042-flake-apps.md b/notes/042-flake-apps.md new file mode 100644 index 0000000..8ee9c8f --- /dev/null +++ b/notes/042-flake-apps.md @@ -0,0 +1,62 @@ +# Flake Apps + +This note covers `39-flake-apps/`, which exposes runnable `apps.` outputs instead of relying only on packages. + +--- + +## 1. What an App Output Is + +An app output is a small attrset with a type and a program path: + +```nix +apps.${system}.default = { + type = "app"; + program = "${reportPackage}/bin/show-rollout-report"; +}; +``` + +`nix run` looks for this schema. The program must already exist in the Nix store, so apps usually point at a package output. + +--- + +## 2. Why This Example Still Defines Packages + +The runnable programs are built with `writeShellApplication`, then exposed in two ways: + +- `packages..report` and `packages..promote` build the scripts, and +- `apps..default` and `apps..promote` tell `nix run` which program to execute. + +That separation keeps the build logic in packages and the command-line entry points in apps. + +--- + +## 3. What the Example Demonstrates + +This example keeps the data a little richer than a one-line hello script: + +- one release train, +- three rollout waves, and +- one named app for the next promotion step. + +That makes it clear that app outputs are just pointers to real programs. They do not replace packages or change how the program is built. + +--- + +## 4. What the Check Verifies + +`nix flake check` does not run apps automatically, so the example adds a check that executes both app program paths and verifies their output. + +That is the pattern to remember: expose the app through `apps`, then add an explicit check if you want CI to prove the command works. + +--- + +## 5. Commands to Try + +```bash +cd 39-flake-apps + +nix flake show +nix run +nix run .#promote +nix flake check +``` diff --git a/notes/043-local-path-inputs.md b/notes/043-local-path-inputs.md new file mode 100644 index 0000000..8bfb553 --- /dev/null +++ b/notes/043-local-path-inputs.md @@ -0,0 +1,57 @@ +# Local Path Inputs + +This note covers `40-path-inputs/`, which uses `inputs.catalog.url = "path:./catalog";` to pull another local flake into the parent flake. + +--- + +## 1. What a Local Path Input Looks Like + +A local input is declared with a `path:` flake reference: + +```nix +inputs.catalog.url = "path:./catalog"; +``` + +The target directory must itself be a flake, so `catalog/` contains its own `flake.nix`. + +--- + +## 2. What the Child Flake Exposes + +The child flake in this example does not build packages. It exports pure data through `lib`: + +- one release name, +- one service-owner map, and +- one rollout plan. + +The parent flake reads those values and turns them into a runnable script. + +--- + +## 3. Why This Example Uses `lib` + +Using `lib` keeps the focus on the input boundary, not on packaging details in both flakes. + +That is the important part of `path:` inputs: once the child is a flake, its outputs are consumed the same way as any other flake input. + +--- + +## 4. The Git-Tracked File Rule Still Applies + +This is still a flake input, so Nix reads it from the flake source tree. Files inside `catalog/` must be tracked, or the parent flake will not see +them during pure evaluation. + +That rule is easy to miss when the child flake lives in the same repository. + +--- + +## 5. Commands to Try + +```bash +cd 40-path-inputs + +nix flake metadata +nix build +./result/bin/show-local-plan +nix flake check +```