From 3751c06fe199f22249c4fbbe01382641ee87687b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 6 Feb 2025 22:08:48 +0100 Subject: [PATCH 1/6] coerceToStorePath(): Improve error message --- src/libexpr/eval.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 19ca1a359..38dd7425b 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -2441,7 +2441,7 @@ StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, NixStringCon auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned(); if (auto storePath = store->maybeParseStorePath(path)) return *storePath; - error("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow(); + error("cannoet coerce '%s' to a store path because it's not in the Nix store", path).withTrace(pos, errorCtx).debugThrow(); } From f24ff056cb36c3ceb887722c44db64b705371dae Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 6 Feb 2025 22:39:01 +0100 Subject: [PATCH 2/6] Make `nix flake metadata|update|lock` lazy These don't need to evaluate anything (except for the flake metadata in flake.nix) so we can make these commands operate on lazy trees without risk of any semantic change in the evaluator. However, `nix flake metadata` now no longer prints the store path, which is a breaking change (but unavoidable if we want lazy trees). --- src/libflake/flake/flake.cc | 38 +++++++++++++++++++------------ src/libflake/flake/flake.hh | 11 ++++++++- src/nix/flake.cc | 10 +++----- tests/functional/flakes/flakes.sh | 1 - 4 files changed, 37 insertions(+), 23 deletions(-) diff --git a/src/libflake/flake/flake.cc b/src/libflake/flake/flake.cc index 717848ee1..90945f949 100644 --- a/src/libflake/flake/flake.cc +++ b/src/libflake/flake/flake.cc @@ -397,7 +397,8 @@ static Flake getFlake( const FlakeRef & originalRef, bool useRegistries, FlakeCache & flakeCache, - const InputAttrPath & lockRootAttrPath) + const InputAttrPath & lockRootAttrPath, + bool forceLazy) { // Fetch a lazy tree first. auto [accessor, resolvedRef, lockedRef] = fetchOrSubstituteTree( @@ -419,17 +420,22 @@ static Flake getFlake( lockedRef = lockedRef2; } - // Copy the tree to the store. - auto storePath = copyInputToStore(state, lockedRef.input, originalRef.input, accessor); - // Re-parse flake.nix from the store. - return readFlake(state, originalRef, resolvedRef, lockedRef, state.rootPath(state.store->toRealPath(storePath)), lockRootAttrPath); + return readFlake( + state, originalRef, resolvedRef, lockedRef, + forceLazy && lockedRef.input.isLocked() + ? SourcePath(accessor) + : // Copy the tree to the store. + state.rootPath( + state.store->toRealPath( + copyInputToStore(state, lockedRef.input, originalRef.input, accessor))), + lockRootAttrPath); } -Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool useRegistries) +Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool useRegistries, bool forceLazy) { FlakeCache flakeCache; - return getFlake(state, originalRef, useRegistries, flakeCache, {}); + return getFlake(state, originalRef, useRegistries, flakeCache, {}, forceLazy); } static LockFile readLockFile( @@ -455,7 +461,7 @@ LockedFlake lockFlake( auto useRegistries = lockFlags.useRegistries.value_or(settings.useRegistries); - auto flake = getFlake(state, topRef, useRegistries, flakeCache, {}); + auto flake = getFlake(state, topRef, useRegistries, flakeCache, {}, lockFlags.forceLazy); if (lockFlags.applyNixConfig) { flake.config.apply(settings); @@ -630,7 +636,7 @@ LockedFlake lockFlake( if (auto resolvedPath = resolveRelativePath()) { return readFlake(state, *input.ref, *input.ref, *input.ref, *resolvedPath, inputAttrPath); } else { - return getFlake(state, *input.ref, useRegistries, flakeCache, inputAttrPath); + return getFlake(state, *input.ref, useRegistries, flakeCache, inputAttrPath, lockFlags.forceLazy); } }; @@ -781,10 +787,14 @@ LockedFlake lockFlake( auto [accessor, resolvedRef, lockedRef] = fetchOrSubstituteTree( state, *input.ref, useRegistries, flakeCache); - // FIXME: allow input to be lazy. - auto storePath = copyInputToStore(state, lockedRef.input, input.ref->input, accessor); - - return {state.rootPath(state.store->toRealPath(storePath)), lockedRef}; + return { + lockFlags.forceLazy && lockedRef.input.isLocked() + ? SourcePath(accessor) + : state.rootPath( + state.store->toRealPath( + copyInputToStore(state, lockedRef.input, input.ref->input, accessor))), + lockedRef + }; } }(); @@ -894,7 +904,7 @@ LockedFlake lockFlake( repo, so we should re-read it. FIXME: we could also just clear the 'rev' field... */ auto prevLockedRef = flake.lockedRef; - flake = getFlake(state, topRef, useRegistries); + flake = getFlake(state, topRef, useRegistries, lockFlags.forceLazy); if (lockFlags.commitLockFile && flake.lockedRef.input.getRev() && diff --git a/src/libflake/flake/flake.hh b/src/libflake/flake/flake.hh index 8d9b9a698..3696fd110 100644 --- a/src/libflake/flake/flake.hh +++ b/src/libflake/flake/flake.hh @@ -123,7 +123,11 @@ struct Flake } }; -Flake getFlake(EvalState & state, const FlakeRef & flakeRef, bool useRegistries); +Flake getFlake( + EvalState & state, + const FlakeRef & flakeRef, + bool useRegistries, + bool forceLazy = false); /** * Fingerprint of a locked flake; used as a cache key. @@ -221,6 +225,11 @@ struct LockFlags * for those inputs will be ignored. */ std::set inputUpdates; + + /** + * If set, do not copy the flake to the Nix store. + */ + bool forceLazy = false; }; LockedFlake lockFlake( diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 6f220b495..1cc13bf59 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -133,6 +133,7 @@ public: lockFlags.recreateLockFile = updateAll; lockFlags.writeLockFile = true; lockFlags.applyNixConfig = true; + lockFlags.forceLazy = true; lockFlake(); } @@ -165,6 +166,7 @@ struct CmdFlakeLock : FlakeCommand lockFlags.writeLockFile = true; lockFlags.failOnUnlocked = true; lockFlags.applyNixConfig = true; + lockFlags.forceLazy = true; lockFlake(); } @@ -211,12 +213,10 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON void run(nix::ref store) override { + lockFlags.forceLazy = true; auto lockedFlake = lockFlake(); auto & flake = lockedFlake.flake; - // Currently, all flakes are in the Nix store via the rootFS accessor. - auto storePath = store->printStorePath(sourcePathToStorePath(store, flake.path).first); - if (json) { nlohmann::json j; if (flake.description) @@ -237,7 +237,6 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON j["revCount"] = *revCount; if (auto lastModified = flake.lockedRef.input.getLastModified()) j["lastModified"] = *lastModified; - j["path"] = storePath; j["locks"] = lockedFlake.lockFile.toJSON().first; if (auto fingerprint = lockedFlake.getFingerprint(store, fetchSettings)) j["fingerprint"] = fingerprint->to_string(HashFormat::Base16, false); @@ -254,9 +253,6 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON logger->cout( ANSI_BOLD "Description:" ANSI_NORMAL " %s", *flake.description); - logger->cout( - ANSI_BOLD "Path:" ANSI_NORMAL " %s", - storePath); if (auto rev = flake.lockedRef.input.getRev()) logger->cout( ANSI_BOLD "Revision:" ANSI_NORMAL " %s", diff --git a/tests/functional/flakes/flakes.sh b/tests/functional/flakes/flakes.sh index d8c9f254d..8936afa22 100755 --- a/tests/functional/flakes/flakes.sh +++ b/tests/functional/flakes/flakes.sh @@ -69,7 +69,6 @@ nix flake metadata "$flake1Dir" | grepQuiet 'URL:.*flake1.*' # Test 'nix flake metadata --json'. json=$(nix flake metadata flake1 --json | jq .) [[ $(echo "$json" | jq -r .description) = 'Bla bla' ]] -[[ -d $(echo "$json" | jq -r .path) ]] [[ $(echo "$json" | jq -r .lastModified) = $(git -C "$flake1Dir" log -n1 --format=%ct) ]] hash1=$(echo "$json" | jq -r .revision) [[ -n $(echo "$json" | jq -r .fingerprint) ]] From 9e6b89c92c00c67ada5ea6f15b48b8f6c69b002b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 7 Feb 2025 14:58:22 +0100 Subject: [PATCH 3/6] lockFlake(): Always compute a NAR hash for inputs For the top-level flake, we don't need a NAR hash. But for inputs, we do. Also, add a test for the lazy behaviour of `nix flake metadata|lock`. --- src/libflake/flake/flake.cc | 51 ++++++++++++-------- src/libflake/flake/flake.hh | 13 ++++- src/libstore/store-api.cc | 8 ++- src/nix/flake.cc | 16 ++++-- tests/functional/flakes/flakes.sh | 2 +- tests/functional/flakes/follow-paths.sh | 9 ++-- tests/functional/flakes/unlocked-override.sh | 2 +- 7 files changed, 69 insertions(+), 32 deletions(-) diff --git a/src/libflake/flake/flake.cc b/src/libflake/flake/flake.cc index 90945f949..a0ba404cd 100644 --- a/src/libflake/flake/flake.cc +++ b/src/libflake/flake/flake.cc @@ -100,6 +100,20 @@ static StorePath copyInputToStore( return storePath; } +static SourcePath maybeCopyInputToStore( + EvalState & state, + fetchers::Input & input, + const fetchers::Input & originalInput, + ref accessor, + CopyMode copyMode) +{ + return copyMode == CopyMode::Lazy || (copyMode == CopyMode::RequireLockable && (input.isLocked() || input.getNarHash())) + ? SourcePath(accessor) + : state.rootPath( + state.store->toRealPath( + copyInputToStore(state, input, originalInput, accessor))); +} + static void forceTrivialValue(EvalState & state, Value & value, const PosIdx pos) { if (value.isThunk() && value.isTrivial()) @@ -398,7 +412,7 @@ static Flake getFlake( bool useRegistries, FlakeCache & flakeCache, const InputAttrPath & lockRootAttrPath, - bool forceLazy) + CopyMode copyMode) { // Fetch a lazy tree first. auto [accessor, resolvedRef, lockedRef] = fetchOrSubstituteTree( @@ -423,19 +437,14 @@ static Flake getFlake( // Re-parse flake.nix from the store. return readFlake( state, originalRef, resolvedRef, lockedRef, - forceLazy && lockedRef.input.isLocked() - ? SourcePath(accessor) - : // Copy the tree to the store. - state.rootPath( - state.store->toRealPath( - copyInputToStore(state, lockedRef.input, originalRef.input, accessor))), + maybeCopyInputToStore(state, lockedRef.input, originalRef.input, accessor, copyMode), lockRootAttrPath); } -Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool useRegistries, bool forceLazy) +Flake getFlake(EvalState & state, const FlakeRef & originalRef, bool useRegistries, CopyMode copyMode) { FlakeCache flakeCache; - return getFlake(state, originalRef, useRegistries, flakeCache, {}, forceLazy); + return getFlake(state, originalRef, useRegistries, flakeCache, {}, copyMode); } static LockFile readLockFile( @@ -461,7 +470,7 @@ LockedFlake lockFlake( auto useRegistries = lockFlags.useRegistries.value_or(settings.useRegistries); - auto flake = getFlake(state, topRef, useRegistries, flakeCache, {}, lockFlags.forceLazy); + auto flake = getFlake(state, topRef, useRegistries, flakeCache, {}, lockFlags.copyMode); if (lockFlags.applyNixConfig) { flake.config.apply(settings); @@ -506,6 +515,13 @@ LockedFlake lockFlake( explicitCliOverrides.insert(i.first); } + /* For locking of inputs, we require at least a NAR + hash. I.e. we can't be fully lazy. */ + auto inputCopyMode = + lockFlags.copyMode == CopyMode::Lazy + ? CopyMode::RequireLockable + : lockFlags.copyMode; + LockFile newLockFile; std::vector parents; @@ -633,11 +649,10 @@ LockedFlake lockFlake( flakerefs relative to the parent flake. */ auto getInputFlake = [&]() { - if (auto resolvedPath = resolveRelativePath()) { + if (auto resolvedPath = resolveRelativePath()) return readFlake(state, *input.ref, *input.ref, *input.ref, *resolvedPath, inputAttrPath); - } else { - return getFlake(state, *input.ref, useRegistries, flakeCache, inputAttrPath, lockFlags.forceLazy); - } + else + return getFlake(state, *input.ref, useRegistries, flakeCache, inputAttrPath, inputCopyMode); }; /* Do we have an entry in the existing lock file? @@ -788,11 +803,7 @@ LockedFlake lockFlake( state, *input.ref, useRegistries, flakeCache); return { - lockFlags.forceLazy && lockedRef.input.isLocked() - ? SourcePath(accessor) - : state.rootPath( - state.store->toRealPath( - copyInputToStore(state, lockedRef.input, input.ref->input, accessor))), + maybeCopyInputToStore(state, lockedRef.input, input.ref->input, accessor, inputCopyMode), lockedRef }; } @@ -904,7 +915,7 @@ LockedFlake lockFlake( repo, so we should re-read it. FIXME: we could also just clear the 'rev' field... */ auto prevLockedRef = flake.lockedRef; - flake = getFlake(state, topRef, useRegistries, lockFlags.forceLazy); + flake = getFlake(state, topRef, useRegistries, lockFlags.copyMode); if (lockFlags.commitLockFile && flake.lockedRef.input.getRev() && diff --git a/src/libflake/flake/flake.hh b/src/libflake/flake/flake.hh index 3696fd110..93bd18188 100644 --- a/src/libflake/flake/flake.hh +++ b/src/libflake/flake/flake.hh @@ -123,11 +123,20 @@ struct Flake } }; +enum struct CopyMode { + //! Copy the input to the store. + RequireStorePath, + //! Ensure that the input is locked or has a NAR hash. + RequireLockable, + //! Just return a lazy source accessor. + Lazy, +}; + Flake getFlake( EvalState & state, const FlakeRef & flakeRef, bool useRegistries, - bool forceLazy = false); + CopyMode copyMode = CopyMode::RequireStorePath); /** * Fingerprint of a locked flake; used as a cache key. @@ -229,7 +238,7 @@ struct LockFlags /** * If set, do not copy the flake to the Nix store. */ - bool forceLazy = false; + CopyMode copyMode = CopyMode::RequireStorePath; }; LockedFlake lockFlake( diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc index 236622eae..25acdefc8 100644 --- a/src/libstore/store-api.cc +++ b/src/libstore/store-api.cc @@ -214,8 +214,12 @@ StorePath Store::addToStore( auto sink = sourceToSink([&](Source & source) { LengthSource lengthSource(source); storePath = addToStoreFromDump(lengthSource, name, fsm, method, hashAlgo, references, repair); - if (settings.warnLargePathThreshold && lengthSource.total >= settings.warnLargePathThreshold) - warn("copied large path '%s' to the store (%s)", path, renderSize(lengthSource.total)); + if (settings.warnLargePathThreshold && lengthSource.total >= settings.warnLargePathThreshold) { + static bool failOnLargePath = getEnv("_NIX_TEST_FAIL_ON_LARGE_PATH").value_or("") == "1"; + if (failOnLargePath) + throw Error("won't copy large path '%s' to the store (%d)", path, renderSize(lengthSource.total)); + warn("copied large path '%s' to the store (%d)", path, renderSize(lengthSource.total)); + } }); dumpPath(path, *sink, fsm, filter); sink->finish(); diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 1cc13bf59..37df51f37 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -133,7 +133,7 @@ public: lockFlags.recreateLockFile = updateAll; lockFlags.writeLockFile = true; lockFlags.applyNixConfig = true; - lockFlags.forceLazy = true; + lockFlags.copyMode = CopyMode::Lazy; lockFlake(); } @@ -166,7 +166,7 @@ struct CmdFlakeLock : FlakeCommand lockFlags.writeLockFile = true; lockFlags.failOnUnlocked = true; lockFlags.applyNixConfig = true; - lockFlags.forceLazy = true; + lockFlags.copyMode = CopyMode::Lazy; lockFlake(); } @@ -213,10 +213,14 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON void run(nix::ref store) override { - lockFlags.forceLazy = true; + lockFlags.copyMode = CopyMode::Lazy; auto lockedFlake = lockFlake(); auto & flake = lockedFlake.flake; + std::optional storePath; + if (flake.lockedRef.input.getNarHash()) + storePath = flake.lockedRef.input.computeStorePath(*store); + if (json) { nlohmann::json j; if (flake.description) @@ -237,6 +241,8 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON j["revCount"] = *revCount; if (auto lastModified = flake.lockedRef.input.getLastModified()) j["lastModified"] = *lastModified; + if (storePath) + j["path"] = store->printStorePath(*storePath); j["locks"] = lockedFlake.lockFile.toJSON().first; if (auto fingerprint = lockedFlake.getFingerprint(store, fetchSettings)) j["fingerprint"] = fingerprint->to_string(HashFormat::Base16, false); @@ -253,6 +259,10 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON logger->cout( ANSI_BOLD "Description:" ANSI_NORMAL " %s", *flake.description); + if (storePath) + logger->cout( + ANSI_BOLD "Path:" ANSI_NORMAL " %s", + store->printStorePath(*storePath)); if (auto rev = flake.lockedRef.input.getRev()) logger->cout( ANSI_BOLD "Revision:" ANSI_NORMAL " %s", diff --git a/tests/functional/flakes/flakes.sh b/tests/functional/flakes/flakes.sh index 8936afa22..f55d3a04d 100755 --- a/tests/functional/flakes/flakes.sh +++ b/tests/functional/flakes/flakes.sh @@ -75,7 +75,7 @@ hash1=$(echo "$json" | jq -r .revision) echo foo > "$flake1Dir/foo" git -C "$flake1Dir" add $flake1Dir/foo -[[ $(nix flake metadata flake1 --json --refresh | jq -r .dirtyRevision) == "$hash1-dirty" ]] +[[ $(_NIX_TEST_FAIL_ON_LARGE_PATH=1 nix flake metadata flake1 --json --refresh --warn-large-path-threshold 1 | jq -r .dirtyRevision) == "$hash1-dirty" ]] [[ "$(nix flake metadata flake1 --json | jq -r .fingerprint)" != null ]] echo -n '# foo' >> "$flake1Dir/flake.nix" diff --git a/tests/functional/flakes/follow-paths.sh b/tests/functional/flakes/follow-paths.sh index a71d4c6d7..c654e0650 100755 --- a/tests/functional/flakes/follow-paths.sh +++ b/tests/functional/flakes/follow-paths.sh @@ -118,20 +118,23 @@ nix flake lock $flakeFollowsA jq -r -c '.nodes | keys | .[]' $flakeFollowsA/flake.lock | grep "^foobar$" # Check that path: inputs cannot escape from their root. +# FIXME: this test is wonky because with lazy trees, ../flakeB at the root is equivalent to /flakeB and not an error. cat > $flakeFollowsA/flake.nix <&1 | grep '/flakeB.*is forbidden in pure evaluation mode' -expect 1 nix flake lock --impure $flakeFollowsA 2>&1 | grep '/flakeB.*does not exist' +expect 1 nix eval $flakeFollowsA#x 2>&1 | grep '/flakeB.*is forbidden in pure evaluation mode' +expect 1 nix eval --impure $flakeFollowsA#x 2>&1 | grep '/flakeB.*does not exist' # Test relative non-flake inputs. cat > $flakeFollowsA/flake.nix < "$flake1Dir"/x.nix expectStderr 1 nix flake lock "$flake2Dir" --override-input flake1 "$TEST_ROOT/flake1" | grepQuiet "Will not write lock file.*because it has an unlocked input" -nix flake lock "$flake2Dir" --override-input flake1 "$TEST_ROOT/flake1" --allow-dirty-locks +_NIX_TEST_FAIL_ON_LARGE_PATH=1 nix flake lock "$flake2Dir" --override-input flake1 "$TEST_ROOT/flake1" --allow-dirty-locks --warn-large-path-threshold 1 # Using a lock file with a dirty lock does not require --allow-dirty-locks, but should print a warning. expectStderr 0 nix eval "$flake2Dir#x" | From 2890a2e25da3645f1979d2a35eb88239e8ca9630 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 7 Feb 2025 17:26:29 +0100 Subject: [PATCH 4/6] Typo --- src/libexpr/eval.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 38dd7425b..92dd8edab 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -2441,7 +2441,7 @@ StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, NixStringCon auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned(); if (auto storePath = store->maybeParseStorePath(path)) return *storePath; - error("cannoet coerce '%s' to a store path because it's not in the Nix store", path).withTrace(pos, errorCtx).debugThrow(); + error("cannot coerce '%s' to a store path because it does not denote a subpath of the Nix store", path).withTrace(pos, errorCtx).debugThrow(); } From 343218413648af3070e472e5f01e6574ea20e16f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 10 Feb 2025 19:38:47 +0100 Subject: [PATCH 5/6] Compute NAR hash for Git archive flakes if --no-trust-tarballs-from-git-forges --- src/libfetchers/github.cc | 7 +++++++ tests/nixos/github-flakes.nix | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libfetchers/github.cc b/src/libfetchers/github.cc index ec469df7c..347cc70eb 100644 --- a/src/libfetchers/github.cc +++ b/src/libfetchers/github.cc @@ -299,6 +299,13 @@ struct GitArchiveInputScheme : InputScheme false, "«" + input.to_string() + "»"); + if (!input.settings->trustTarballsFromGitForges) + // FIXME: computing the NAR hash here is wasteful if + // copyInputToStore() is just going to hash/copy it as + // well. + input.attrs.insert_or_assign("narHash", + accessor->hashPath(CanonPath::root).to_string(HashFormat::SRI, true)); + return {accessor, input}; } diff --git a/tests/nixos/github-flakes.nix b/tests/nixos/github-flakes.nix index dcba464a3..c6b3db96c 100644 --- a/tests/nixos/github-flakes.nix +++ b/tests/nixos/github-flakes.nix @@ -205,7 +205,7 @@ in cat_log() # ... otherwise it should use the API - out = client.succeed("nix flake metadata private-flake --json --access-tokens github.com=ghp_000000000000000000000000000000000000 --tarball-ttl 0") + out = client.succeed("nix flake metadata private-flake --json --access-tokens github.com=ghp_000000000000000000000000000000000000 --tarball-ttl 0 --no-trust-tarballs-from-git-forges") print(out) info = json.loads(out) assert info["revision"] == "${private-flake-rev}", f"revision mismatch: {info['revision']} != ${private-flake-rev}" From 307ce9bc1d8c58a947fc4c8f9c3369c64f5a2d4b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 10 Feb 2025 19:55:24 +0100 Subject: [PATCH 6/6] Add NAR hash mismatch test --- tests/nixos/github-flakes.nix | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/nixos/github-flakes.nix b/tests/nixos/github-flakes.nix index c6b3db96c..8175e807c 100644 --- a/tests/nixos/github-flakes.nix +++ b/tests/nixos/github-flakes.nix @@ -224,6 +224,10 @@ in hash = client.succeed(f"nix eval --no-trust-tarballs-from-git-forges --raw --expr '(fetchTree {info['url']}).narHash'") assert hash == info['locked']['narHash'] + # Fetching with an incorrect NAR hash should fail. + out = client.fail(f"nix eval --no-trust-tarballs-from-git-forges --raw --expr '(fetchTree \"github:fancy-enterprise/private-flake/{info['revision']}?narHash=sha256-HsrRFZYg69qaVe/wDyWBYLeS6ca7ACEJg2Z%2BGpEFw4A%3D\").narHash' 2>&1") + assert "NAR hash mismatch in input" in out, "NAR hash check did not fail with the expected error" + # Fetching without a narHash should succeed if trust-github is set and fail otherwise. client.succeed(f"nix eval --raw --expr 'builtins.fetchTree github:github:fancy-enterprise/private-flake/{info['revision']}'") out = client.fail(f"nix eval --no-trust-tarballs-from-git-forges --raw --expr 'builtins.fetchTree github:github:fancy-enterprise/private-flake/{info['revision']}' 2>&1")