1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-06-25 14:51:16 +02:00
nix/tests/nixos/github-flakes.nix
Eelco Dolstra c98026e982 Lazy trees v2
This adds a setting 'lazy-trees' that causes flake inputs to be
"mounted" as virtual filesystems on top of /nix/store as random
"virtual" store paths. Only when the store path is actually used as a
dependency of a store derivation do we materialize ("devirtualize")
the input by copying it to its content-addressed location in the
store.

String contexts determine when devirtualization happens. One wrinkle
is that there are cases where we had store paths without proper
contexts, in particular when the user does `toString <path>` (where
<path> is a source tree in the Nix store) and passes the result to a
derivation. This usage was always broken, since it can result in
derivations that lack correct references. But to ensure that we don't
change evaluation results, we introduce a new type of context that
results in devirtualization but not in store references. We also now
print a warning about this.
2025-05-19 16:45:53 +02:00

250 lines
8.6 KiB
Nix

{
lib,
config,
nixpkgs,
...
}:
let
pkgs = config.nodes.client.nixpkgs.pkgs;
# Generate a fake root CA and a fake api.github.com / github.com / channels.nixos.org certificate.
cert = pkgs.runCommand "cert" { nativeBuildInputs = [ pkgs.openssl ]; } ''
mkdir -p $out
openssl genrsa -out ca.key 2048
openssl req -new -x509 -days 36500 -key ca.key \
-subj "/C=NL/ST=Denial/L=Springfield/O=Dis/CN=Root CA" -out $out/ca.crt
openssl req -newkey rsa:2048 -nodes -keyout $out/server.key \
-subj "/C=CN/ST=Denial/L=Springfield/O=Dis/CN=github.com" -out server.csr
openssl x509 -req -extfile <(printf "subjectAltName=DNS:api.github.com,DNS:github.com,DNS:channels.nixos.org") \
-days 36500 -in server.csr -CA $out/ca.crt -CAkey ca.key -CAcreateserial -out $out/server.crt
'';
registry = pkgs.writeTextFile {
name = "registry";
text = ''
{
"flakes": [
{
"from": {
"type": "indirect",
"id": "nixpkgs"
},
"to": {
"type": "github",
"owner": "NixOS",
"repo": "nixpkgs"
}
},
{
"from": {
"type": "indirect",
"id": "private-flake"
},
"to": {
"type": "github",
"owner": "fancy-enterprise",
"repo": "private-flake"
}
}
],
"version": 2
}
'';
destination = "/flake-registry.json";
};
private-flake-rev = "9f1dd0df5b54a7dc75b618034482ed42ce34383d";
private-flake-api = pkgs.runCommand "private-flake" { } ''
mkdir -p $out/{commits,tarball}
# Setup https://docs.github.com/en/rest/commits/commits#get-a-commit
echo '{"sha": "${private-flake-rev}", "commit": {"tree": {"sha": "ffffffffffffffffffffffffffffffffffffffff"}}}' > $out/commits/HEAD
# Setup tarball download via API
dir=private-flake
mkdir $dir
echo '{ outputs = {...}: {}; }' > $dir/flake.nix
tar cfz $out/tarball/${private-flake-rev} $dir --hard-dereference
'';
nixpkgs-api = pkgs.runCommand "nixpkgs-flake" { } ''
mkdir -p $out/commits
# Setup https://docs.github.com/en/rest/commits/commits#get-a-commit
echo '{"sha": "${nixpkgs.rev}", "commit": {"tree": {"sha": "ffffffffffffffffffffffffffffffffffffffff"}}}' > $out/commits/HEAD
'';
archive = pkgs.runCommand "nixpkgs-flake" { } ''
mkdir -p $out/archive
dir=NixOS-nixpkgs-${nixpkgs.shortRev}
cp -prd ${nixpkgs} $dir
# Set the correct timestamp in the tarball.
find $dir -print0 | xargs -0 touch -h -t ${builtins.substring 0 12 nixpkgs.lastModifiedDate}.${
builtins.substring 12 2 nixpkgs.lastModifiedDate
} --
tar cfz $out/archive/${nixpkgs.rev}.tar.gz $dir --hard-dereference
'';
in
{
name = "github-flakes";
nodes = {
github =
{ config, pkgs, ... }:
{
networking.firewall.allowedTCPPorts = [
80
443
];
services.httpd.enable = true;
services.httpd.adminAddr = "foo@example.org";
services.httpd.extraConfig = ''
ErrorLog syslog:local6
'';
services.httpd.virtualHosts."channels.nixos.org" = {
forceSSL = true;
sslServerKey = "${cert}/server.key";
sslServerCert = "${cert}/server.crt";
servedDirs = [
{
urlPath = "/";
dir = registry;
}
];
};
services.httpd.virtualHosts."api.github.com" = {
forceSSL = true;
sslServerKey = "${cert}/server.key";
sslServerCert = "${cert}/server.crt";
servedDirs = [
{
urlPath = "/repos/NixOS/nixpkgs";
dir = nixpkgs-api;
}
{
urlPath = "/repos/fancy-enterprise/private-flake";
dir = private-flake-api;
}
];
};
services.httpd.virtualHosts."github.com" = {
forceSSL = true;
sslServerKey = "${cert}/server.key";
sslServerCert = "${cert}/server.crt";
servedDirs = [
{
urlPath = "/NixOS/nixpkgs";
dir = archive;
}
];
};
};
client =
{
config,
lib,
pkgs,
nodes,
...
}:
{
virtualisation.writableStore = true;
virtualisation.diskSize = 2048;
virtualisation.additionalPaths = [
pkgs.hello
pkgs.fuse
];
virtualisation.memorySize = 4096;
nix.settings.substituters = lib.mkForce [ ];
nix.extraOptions = "experimental-features = nix-command flakes";
networking.hosts.${(builtins.head nodes.github.networking.interfaces.eth1.ipv4.addresses).address} =
[
"channels.nixos.org"
"api.github.com"
"github.com"
];
security.pki.certificateFiles = [ "${cert}/ca.crt" ];
};
};
testScript =
{ nodes }:
''
# fmt: off
import json
import time
start_all()
def cat_log():
github.succeed("cat /var/log/httpd/*.log >&2")
github.wait_for_unit("httpd.service")
github.wait_for_unit("network-addresses-eth1.service")
client.wait_for_unit("network-addresses-eth1.service")
client.succeed("curl -v https://github.com/ >&2")
out = client.succeed("nix registry list")
print(out)
assert "github:NixOS/nixpkgs" in out, "nixpkgs flake not found"
assert "github:fancy-enterprise/private-flake" in out, "private flake not found"
cat_log()
# If no github access token is provided, nix should use the public archive url...
out = client.succeed("nix flake metadata nixpkgs --json")
print(out)
info = json.loads(out)
assert info["revision"] == "${nixpkgs.rev}", f"revision mismatch: {info['revision']} != ${nixpkgs.rev}"
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 --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}"
assert info["fingerprint"]
cat_log()
# Fetching with the resolved URL should produce the same result.
info2 = json.loads(client.succeed(f"nix flake metadata {info['url']} --json --access-tokens github.com=ghp_000000000000000000000000000000000000 --tarball-ttl 0"))
print(info["fingerprint"], info2["fingerprint"])
assert info["fingerprint"] == info2["fingerprint"], "fingerprint mismatch"
client.succeed("nix registry pin nixpkgs")
client.succeed("nix flake metadata nixpkgs --tarball-ttl 0 >&2")
# Test fetchTree on a github URL.
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")
assert "will not fetch unlocked input" in out, "--no-trust-tarballs-from-git-forges did not fail with the expected error"
# Shut down the web server. The flake should be cached on the client.
github.succeed("systemctl stop httpd.service")
info = json.loads(client.succeed("nix flake metadata nixpkgs --json"))
date = time.strftime("%Y%m%d%H%M%S", time.gmtime(info['lastModified']))
assert date == "${nixpkgs.lastModifiedDate}", "time mismatch"
client.succeed("nix build nixpkgs#hello")
# The build shouldn't fail even with --tarball-ttl 0 (the server
# being down should not be a fatal error).
client.succeed("nix build nixpkgs#fuse --tarball-ttl 0")
'';
}