aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-12 19:56:13 +0000
committertv <tv@krebsco.de>2026-11-13 03:24:17 +0100
commit9f824e06ee72fcb2cdacf9b90885f5cd0606a180 (patch)
tree8c98563391b20bd09aa651a8347e4bf77ae523fb /tests
parent470de08082a8d251c3854ceb6da9cef8bd43b74b (diff)
populate flake-input: fail on failures, reuse the store, leave out replaced inputs
Delivering flakes and flake inputs gets more robust: - With fetchOnTarget, the inputs get collected before the pipeline that links them, so populate fails if the target can't fetch one, instead of linking the flake without its inputs and succeeding. - What the lock file pins gets taken from the store if it's there: the store path is the one of a directory named source with the locked hash, which nix-store --print-fixed-path computes. builtins.fetchTree contacts where the input comes from even then, so redeploying unchanged sources fails without that if a server is unreachable. - With fetchOnTarget, a delivered input that is itself locked to a local path the target lacks fails with populate's error naming the input, like its inputs do. - Inputs that get drawn from other sources are left out, with all they pin: Nix archives an empty flake in their place. So nothing gets fetched, copied, and rooted that no evaluation uses, and a pin that is unreachable doesn't matter if it gets replaced anyway. - flake-input sources get the option requireInputs. By default, populating fails if the inputs that the input's own lock file pins can't be delivered, e.g. because the lock file lacks an input its flake.nix has. With false, populate warns and delivers the input with what Nix locks now, or else without its inputs. A remote flake that the target fetches entirely on its own collects its inputs like everything else does. The Nix function finding its subdirectory moves next to the jq programs. New edge cases cover each of these.
Diffstat (limited to 'tests')
-rwxr-xr-xtests/fixtures.sh102
-rw-r--r--tests/scenarios.nix118
2 files changed, 220 insertions, 0 deletions
diff --git a/tests/fixtures.sh b/tests/fixtures.sh
index 3839c1d..d9d8b2b 100755
--- a/tests/fixtures.sh
+++ b/tests/fixtures.sh
@@ -359,3 +359,105 @@ cat > /fixtures/patches/bad.patch <<'PATCH'
-nothing like this
+anything
PATCH
+
+# broken: a flake with an input that can't be fetched anymore.
+mkdir /fixtures/www/src/vanishing
+cat > /fixtures/www/src/vanishing/flake.nix <<'NIX'
+{ outputs = _: { who = "vanishing"; }; }
+NIX
+tarball vanishing
+mkdir /fixtures/broken
+cat > /fixtures/broken/flake.nix <<NIX
+{
+ inputs.vanishing.url = "$url/vanishing.tar.gz";
+ outputs = { vanishing, ... }: { msg = "broken:\${vanishing.who}"; };
+}
+NIX
+(cd /fixtures/broken && nix flake lock)
+
+# pinned, usepinned: a tarball flake whose own lock file pins an input that
+# can't be fetched anymore, and a flake that draws that input from its own
+# input instead, so nothing it evaluates needs the pin.
+mkdir /fixtures/www/src/pinned
+cat > /fixtures/www/src/pinned/flake.nix <<NIX
+{
+ inputs.inner.url = "$url/vanishing.tar.gz";
+ outputs = { inner, ... }: { greeting = "pinned+\${inner.who}"; };
+}
+NIX
+(cd /fixtures/www/src/pinned && nix flake lock)
+tarball pinned
+mkdir /fixtures/usepinned
+cat > /fixtures/usepinned/flake.nix <<NIX
+{
+ inputs.inner.url = "$url/inner.tar.gz";
+ inputs.dep.url = "$url/pinned.tar.gz";
+ inputs.dep.inputs.inner.follows = "inner";
+ outputs = { dep, ... }: { msg = "usepinned:\${dep.greeting}"; };
+}
+NIX
+(cd /fixtures/usepinned && nix flake lock)
+rm /fixtures/www/vanishing.tar.gz
+
+# stale, usestale: a tarball flake whose lock file lacks an input its
+# flake.nix has, and a flake with it as input, whose own lock file is
+# complete.
+mkdir /fixtures/www/src/stale
+cat > /fixtures/www/src/stale/flake.nix <<NIX
+{
+ inputs.inner.url = "$url/inner.tar.gz";
+ outputs = { inner, ... }: { greeting = "stale+\${inner.who}"; };
+}
+NIX
+(cd /fixtures/www/src/stale && nix flake lock)
+cat > /fixtures/www/src/stale/flake.nix <<NIX
+{
+ inputs.inner.url = "$url/inner.tar.gz";
+ inputs.extra.url = "$url/inner2.tar.gz";
+ outputs = { inner, extra, ... }: { greeting = "stale+\${inner.who}+\${extra.who}"; };
+}
+NIX
+tarball stale
+mkdir /fixtures/usestale
+cat > /fixtures/usestale/flake.nix <<NIX
+{
+ inputs.dep.url = "$url/stale.tar.gz";
+ outputs = { dep, ... }: { msg = "usestale:\${dep.greeting}"; };
+}
+NIX
+(cd /fixtures/usestale && nix flake lock)
+
+# offline: a flake with an input of its own, which the tests make
+# unreachable between deploys.
+mkdir /fixtures/www/src/gap
+cat > /fixtures/www/src/gap/flake.nix <<'NIX'
+{ outputs = _: { who = "gap"; }; }
+NIX
+tarball gap
+mkdir /fixtures/offline
+cat > /fixtures/offline/flake.nix <<NIX
+{
+ inputs.gap.url = "$url/gap.tar.gz";
+ outputs = { gap, ... }: { msg = "offline:\${gap.who}"; };
+}
+NIX
+(cd /fixtures/offline && nix flake lock)
+
+# c3: a flake with an input, to draw another flake's input from.
+mkdir /fixtures/c3
+cat > /fixtures/c3/flake.nix <<NIX
+{
+ inputs.z.url = "$url/inner.tar.gz";
+ outputs = { z, ... }: { who = "c3(\${z.who})"; };
+}
+NIX
+(cd /fixtures/c3 && nix flake lock)
+
+# filtered-parent, filtered-parent-link: like filtered, but the filter file
+# applies to a subdirectory, which is what gets synced.
+mkdir -p /fixtures/filtered-parent/sub
+printf -- '- garbage\n' > /fixtures/filtered-parent/.rsync-filter
+echo keep > /fixtures/filtered-parent/sub/keep
+echo garbage > /fixtures/filtered-parent/sub/garbage
+ln -s filtered-parent /fixtures/filtered-parent-link
+chown -h nobody /fixtures/filtered-parent-link
diff --git a/tests/scenarios.nix b/tests/scenarios.nix
index af072b9..7743482 100644
--- a/tests/scenarios.nix
+++ b/tests/scenarios.nix
@@ -1104,6 +1104,124 @@ assert !(krops.isLocalTarget (targets.remote "/"));
check = checkFlake { attr = "msg"; expected = "rel:lib"; };
};
})
+ # The target can't fetch an input of the flake. The deploying machine
+ # fetched it when locking, so its fetcher cache has to go.
+ (s "fetch-failure" {
+ prepareDeployer = ''
+ rm -fR /root/.cache/nix
+ '';
+ phases = [{
+ sources.x.flake = { url = "/fixtures/broken"; fetchOnTarget = true; };
+ expect.fail = "vanishing.tar.gz";
+ }];
+ })
+ # The delivered input itself is locked to a path the target lacks.
+ (s "gone-input" {
+ phases = [{
+ sources.x.flake-input = { url = "/fixtures/usegone"; name = "gone"; fetchOnTarget = true; };
+ expect.fail = "input gone refers to /fixtures/gone, which does not exist on the target";
+ }];
+ })
+ # An input that gets drawn from a source isn't needed on the target, with
+ # all it pins.
+ (modes "overridden-roots" ({ draw, mode, ... }: {
+ phases = twice {
+ sources = alt // {
+ x.flake-input = {
+ url = "/fixtures/top"; name = "dep"; inherit fetchOnTarget;
+ } // draw { inner.follows = "alt"; };
+ };
+ check = checkFlake {
+ attr = "greeting"; expected = "dep+alt";
+ generated = mode == "generated";
+ } + roots 0;
+ };
+ }))
+ # The input's own lock file pins something unreachable, which gets drawn
+ # from a source anyway.
+ (modes "own-lock-unreachable" ({ draw, mode, ... }: {
+ prepareDeployer = ''
+ rm -fR /root/.cache/nix
+ '';
+ phases = twice {
+ sources = alt // {
+ x.flake-input = {
+ url = "/fixtures/usepinned"; name = "dep"; inherit fetchOnTarget;
+ } // draw { inner.follows = "alt"; };
+ };
+ check = checkFlake {
+ attr = "greeting"; expected = "pinned+alt";
+ generated = mode == "generated";
+ };
+ };
+ }))
+ # The input's own lock file pins something unreachable, or lacks an input
+ # its flake.nix has. That fails, unless the input isn't required to come
+ # with its inputs.
+ (s "own-lock-broken" (let
+ x = url: requireInputs: {
+ x.flake-input = { inherit url; name = "dep"; inherit fetchOnTarget requireInputs; };
+ };
+ delivered = ''
+ test -e "$T"/x/flake.nix || fail "input not delivered"
+ '';
+ in {
+ prepareDeployer = ''
+ rm -fR /root/.cache/nix
+ '';
+ phases = [
+ {
+ sources = x "/fixtures/usepinned" true;
+ expect.fail = "error: source x: the inputs its own lock file pins can't be delivered. Set its requireInputs to false to deliver it anyway.";
+ }
+ {
+ sources = x "/fixtures/usepinned" false;
+ check = delivered + roots 0;
+ }
+ {
+ sources = x "/fixtures/usestale" true;
+ expect.fail = "error: source x: the inputs its own lock file pins can't be delivered. Set its requireInputs to false to deliver it anyway.";
+ }
+ {
+ sources = x "/fixtures/usestale" false;
+ check = delivered + roots 2;
+ }
+ {
+ sources = x "/fixtures/usestale" false;
+ check = delivered + roots 2;
+ }
+ ];
+ }))
+ # Redeploying an unchanged input while where it comes from is
+ # unreachable and the fetcher caches are gone: the store has it.
+ (s "offline-redeploy" (let
+ sources.x.flake-input = { url = "/fixtures/offline"; name = "gap"; inherit fetchOnTarget; };
+ check = checkFlake { attr = "who"; expected = "gap"; };
+ in {
+ prepareDeployer = ''
+ if test -e /fixtures/www/gap.tar.gz.off; then
+ mv /fixtures/www/gap.tar.gz.off /fixtures/www/gap.tar.gz
+ fi
+ '';
+ phases = [
+ {
+ inherit sources check;
+ mutate = ''
+ rm -fR /root/.cache/nix
+ '';
+ mutateDeployer = ''
+ rm -fR /root/.cache/nix
+ mv /fixtures/www/gap.tar.gz /fixtures/www/gap.tar.gz.off
+ '';
+ }
+ {
+ inherit sources check;
+ mutateDeployer = ''
+ mv /fixtures/www/gap.tar.gz.off /fixtures/www/gap.tar.gz
+ '';
+ }
+ ];
+ }))
(s "flake-to-file" {
phases = [
{