Add two Nix/Flake examples (with their note files)

This commit is contained in:
Hassan Abedi 2026-04-30 15:42:12 +02:00
parent e5906d9163
commit 1353687dc2
10 changed files with 454 additions and 0 deletions

20
39-flake-apps/README.md Normal file
View File

@ -0,0 +1,20 @@
# 39-flake-apps
This example shows dedicated `apps.<system>` 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
```

27
39-flake-apps/flake.lock generated Normal file
View File

@ -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
}

112
39-flake-apps/flake.nix Normal file
View File

@ -0,0 +1,112 @@
{
# Exposes `apps.<system>` 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"
'';
};
}

19
40-path-inputs/README.md Normal file
View File

@ -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
```

View File

@ -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"
];
}
];
};
};
}

39
40-path-inputs/flake.lock generated Normal file
View File

@ -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
}

65
40-path-inputs/flake.nix Normal file
View File

@ -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"
'';
};
}

View File

@ -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.<system>` outputs, and
- `40-path-inputs/` covers a local `path:` input.
Non-flake sources (a repo without `flake.nix`):
```nix

62
notes/042-flake-apps.md Normal file
View File

@ -0,0 +1,62 @@
# Flake Apps
This note covers `39-flake-apps/`, which exposes runnable `apps.<system>` 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.<system>.report` and `packages.<system>.promote` build the scripts, and
- `apps.<system>.default` and `apps.<system>.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
```

View File

@ -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
```