notes: 2.3.0 -> 2.3.1 (#352950)
[NixPkgs.git] / nixos / tests / radicle.nix
blobb68cb7d716c233c6c4063d759cd8dad3a67fa076
1 # This test runs the radicle-node and radicle-httpd services on a seed host,
2 # and verifies that an alice peer can host a repository on the seed,
3 # and that a bob peer can send alice a patch via the seed.
5 { pkgs, ... }:
7 let
8   # The Node ID depends on nodes.seed.services.radicle.privateKeyFile
9   seed-nid = "z6Mkg52RcwDrPKRzzHaYgBkHH3Gi5p4694fvPstVE9HTyMB6";
10   seed-ssh-keys = import ./ssh-keys.nix pkgs;
11   seed-tls-certs = import common/acme/server/snakeoil-certs.nix;
13   commonHostConfig = { nodes, config, pkgs, ... }: {
14     environment.systemPackages = [
15       config.services.radicle.package
16       pkgs.curl
17       pkgs.gitMinimal
18       pkgs.jq
19     ];
20     environment.etc."gitconfig".text = ''
21       [init]
22         defaultBranch = main
23       [user]
24         email = root@${config.networking.hostName}
25         name = ${config.networking.hostName}
26     '';
27     networking = {
28       extraHosts = ''
29         ${nodes.seed.networking.primaryIPAddress} ${nodes.seed.services.radicle.httpd.nginx.serverName}
30       '';
31     };
32     security.pki.certificateFiles = [
33       seed-tls-certs.ca.cert
34     ];
35   };
37   radicleConfig = { nodes, ... }: alias:
38     pkgs.writeText "config.json" (builtins.toJSON {
39       preferredSeeds = [
40         "${seed-nid}@seed:${toString nodes.seed.services.radicle.node.listenPort}"
41       ];
42       node = {
43         inherit alias;
44         relay = "never";
45         seedingPolicy = {
46           default = "block";
47         };
48       };
49     });
53   name = "radicle";
55   meta = with pkgs.lib.maintainers; {
56     maintainers = [
57       julm
58       lorenzleutgeb
59     ];
60   };
62   nodes = {
63     seed = { pkgs, config, ... }: {
64       imports = [ commonHostConfig ];
66       services.radicle = {
67         enable = true;
68         privateKeyFile = seed-ssh-keys.snakeOilEd25519PrivateKey;
69         publicKey = seed-ssh-keys.snakeOilEd25519PublicKey;
70         node = {
71           openFirewall = true;
72         };
73         httpd = {
74           enable = true;
75           nginx = {
76             serverName = seed-tls-certs.domain;
77             addSSL = true;
78             sslCertificate = seed-tls-certs.${seed-tls-certs.domain}.cert;
79             sslCertificateKey = seed-tls-certs.${seed-tls-certs.domain}.key;
80           };
81         };
82         settings = {
83           preferredSeeds = [];
84           node = {
85             relay = "always";
86             seedingPolicy = {
87               default = "allow";
88               scope = "all";
89             };
90           };
91         };
92       };
94       services.nginx = {
95         enable = true;
96       };
98       networking.firewall.allowedTCPPorts = [ 443 ];
99     };
101     alice = {
102       imports = [ commonHostConfig ];
103     };
105     bob = {
106       imports = [ commonHostConfig ];
107     };
108   };
110   testScript = { nodes, ... }@args: ''
111     start_all()
113     with subtest("seed can run radicle-node"):
114       # The threshold and/or hardening may have to be changed with new features/checks
115       print(seed.succeed("systemd-analyze security radicle-node.service --threshold=10 --no-pager"))
116       seed.wait_for_unit("radicle-node.service")
117       seed.wait_for_open_port(${toString nodes.seed.services.radicle.node.listenPort})
119     with subtest("seed can run radicle-httpd"):
120       # The threshold and/or hardening may have to be changed with new features/checks
121       print(seed.succeed("systemd-analyze security radicle-httpd.service --threshold=10 --no-pager"))
122       seed.wait_for_unit("radicle-httpd.service")
123       seed.wait_for_open_port(${toString nodes.seed.services.radicle.httpd.listenPort})
124       seed.wait_for_open_port(443)
125       assert alice.succeed("curl -sS 'https://${nodes.seed.services.radicle.httpd.nginx.serverName}/api/v1' | jq -r .nid") == "${seed-nid}\n"
126       assert bob.succeed("curl -sS 'https://${nodes.seed.services.radicle.httpd.nginx.serverName}/api/v1' | jq -r .nid") == "${seed-nid}\n"
128     with subtest("alice can create a Node ID"):
129       alice.succeed("rad auth --alias alice --stdin </dev/null")
130       alice.copy_from_host("${radicleConfig args "alice"}", "/root/.radicle/config.json")
131     with subtest("alice can run a node"):
132       alice.succeed("rad node start")
133     with subtest("alice can create a Git repository"):
134       alice.succeed(
135         "mkdir /tmp/repo",
136         "git -C /tmp/repo init",
137         "echo hello world > /tmp/repo/testfile",
138         "git -C /tmp/repo add .",
139         "git -C /tmp/repo commit -m init"
140       )
141     with subtest("alice can create a Repository ID"):
142       alice.succeed(
143         "cd /tmp/repo && rad init --name repo --description descr --default-branch main --public"
144       )
145     alice_repo_rid=alice.succeed("cd /tmp/repo && rad inspect --rid").rstrip("\n")
146     with subtest("alice can send a repository to the seed"):
147       alice.succeed(f"rad sync --seed ${seed-nid} {alice_repo_rid}")
149     with subtest(f"seed can receive the repository {alice_repo_rid}"):
150       seed.wait_until_succeeds("test 1 = \"$(rad-system stats | jq .local.repos)\"")
152     with subtest("bob can create a Node ID"):
153       bob.succeed("rad auth --alias bob --stdin </dev/null")
154       bob.copy_from_host("${radicleConfig args "bob"}", "/root/.radicle/config.json")
155       bob.succeed("rad node start")
156     with subtest("bob can clone alice's repository from the seed"):
157       bob.succeed(f"rad clone {alice_repo_rid} /tmp/repo")
158       assert bob.succeed("cat /tmp/repo/testfile") == "hello world\n"
160     with subtest("bob can clone alice's repository from the seed through the HTTP gateway"):
161       bob.succeed(f"git clone https://${nodes.seed.services.radicle.httpd.nginx.serverName}/{alice_repo_rid[4:]}.git /tmp/repo-http")
162       assert bob.succeed("cat /tmp/repo-http/testfile") == "hello world\n"
164     with subtest("alice can push the main branch to the rad remote"):
165       alice.succeed(
166         "echo hello bob > /tmp/repo/testfile",
167         "git -C /tmp/repo add .",
168         "git -C /tmp/repo commit -m 'hello to bob'",
169         "git -C /tmp/repo push rad main"
170       )
171     with subtest("bob can sync bob's repository from the seed"):
172       bob.succeed(
173         "cd /tmp/repo && rad sync --seed ${seed-nid}",
174         "cd /tmp/repo && git pull"
175       )
176       assert bob.succeed("cat /tmp/repo/testfile") == "hello bob\n"
178     with subtest("bob can push a patch"):
179       bob.succeed(
180         "echo hello alice > /tmp/repo/testfile",
181         "git -C /tmp/repo checkout -b for-alice",
182         "git -C /tmp/repo add .",
183         "git -C /tmp/repo commit -m 'hello to alice'",
184         "git -C /tmp/repo push -o patch.message='hello for alice' rad HEAD:refs/patches"
185       )
187     bob_repo_patch1_pid=bob.succeed("cd /tmp/repo && git branch --remotes | sed -ne 's:^ *rad/patches/::'p").rstrip("\n")
188     with subtest("alice can receive the patch"):
189       alice.wait_until_succeeds("test 1 = \"$(rad stats | jq .local.patches)\"")
190       alice.succeed(
191         f"cd /tmp/repo && rad patch show {bob_repo_patch1_pid} | grep 'opened by bob'",
192         f"cd /tmp/repo && rad patch checkout {bob_repo_patch1_pid}"
193       )
194       assert alice.succeed("cat /tmp/repo/testfile") == "hello alice\n"
195     with subtest("alice can comment the patch"):
196       alice.succeed(
197         f"cd /tmp/repo && rad patch comment {bob_repo_patch1_pid} -m thank-you"
198       )
199     with subtest("alice can merge the patch"):
200       alice.succeed(
201         "git -C /tmp/repo checkout main",
202         f"git -C /tmp/repo merge patch/{bob_repo_patch1_pid[:7]}",
203         "git -C /tmp/repo push rad main",
204         "cd /tmp/repo && rad patch list | grep -qxF 'Nothing to show.'"
205       )
206   '';