python312Packages.millheater: 0.11.8 -> 0.12.0
[NixPkgs.git] / nixos / tests / forgejo.nix
blob5e6f19e56df05ebd94f38e2abcfb770be6e5dab6
1 { system ? builtins.currentSystem
2 , config ? { }
3 , pkgs ? import ../.. { inherit system config; }
4 , forgejoPackage ? pkgs.forgejo
5 }:
7 with import ../lib/testing-python.nix { inherit system pkgs; };
8 with pkgs.lib;
10 let
11   ## gpg --faked-system-time='20230301T010000!' --quick-generate-key snakeoil ed25519 sign
12   signingPrivateKey = ''
13     -----BEGIN PGP PRIVATE KEY BLOCK-----
15     lFgEY/6jkBYJKwYBBAHaRw8BAQdADXiZRV8RJUyC9g0LH04wLMaJL9WTc+szbMi7
16     5fw4yP8AAQCl8EwGfzSLm/P6fCBfA3I9znFb3MEHGCCJhJ6VtKYyRw7ktAhzbmFr
17     ZW9pbIiUBBMWCgA8FiEE+wUM6VW/NLtAdSixTWQt6LZ4x50FAmP+o5ACGwMFCQPC
18     ZwAECwkIBwQVCgkIBRYCAwEAAh4FAheAAAoJEE1kLei2eMedFTgBAKQs1oGFZrCI
19     TZP42hmBTKxGAI1wg7VSdDEWTZxut/2JAQDGgo2sa4VHMfj0aqYGxrIwfP2B7JHO
20     GCqGCRf9O/hzBA==
21     =9Uy3
22     -----END PGP PRIVATE KEY BLOCK-----
23   '';
24   signingPrivateKeyId = "4D642DE8B678C79D";
26   actionsWorkflowYaml = ''
27     run-name: dummy workflow
28     on:
29       push:
30     jobs:
31       cat:
32         runs-on: native
33         steps:
34           - uses: http://localhost:3000/test/checkout@main
35           - run: cat testfile
36   '';
37   # https://github.com/actions/checkout/releases
38   checkoutActionSource = pkgs.fetchFromGitHub {
39     owner = "actions";
40     repo = "checkout";
41     rev = "v4.1.1";
42     hash = "sha256-h2/UIp8IjPo3eE4Gzx52Fb7pcgG/Ww7u31w5fdKVMos=";
43   };
45   metricSecret = "fakesecret";
47   supportedDbTypes = [ "mysql" "postgres" "sqlite3" ];
48   makeForgejoTest = type: nameValuePair type (makeTest {
49     name = "forgejo-${type}";
50     meta.maintainers = with maintainers; [ bendlas emilylange ];
52     nodes = {
53       server = { config, pkgs, ... }: {
54         virtualisation.memorySize = 2047;
55         services.forgejo = {
56           enable = true;
57           package = forgejoPackage;
58           database = { inherit type; };
59           settings.service.DISABLE_REGISTRATION = true;
60           settings."repository.signing".SIGNING_KEY = signingPrivateKeyId;
61           settings.actions.ENABLED = true;
62           settings.repository = {
63             ENABLE_PUSH_CREATE_USER = true;
64             DEFAULT_PUSH_CREATE_PRIVATE = false;
65           };
66           settings.metrics.ENABLED = true;
67           secrets.metrics.TOKEN = pkgs.writeText "metrics_secret" metricSecret;
68         };
69         environment.systemPackages = [ config.services.forgejo.package pkgs.gnupg pkgs.jq pkgs.file pkgs.htmlq ];
70         services.openssh.enable = true;
72         specialisation.runner = {
73           inheritParentConfig = true;
74           configuration.services.gitea-actions-runner = {
75             package = pkgs.forgejo-runner;
76             instances."test" = {
77               enable = true;
78               name = "ci";
79               url = "http://localhost:3000";
80               labels = [
81                 # type ":host" does not depend on docker/podman/lxc
82                 "native:host"
83               ];
84               tokenFile = "/var/lib/forgejo/runner_token";
85             };
86           };
87         };
88         specialisation.dump = {
89           inheritParentConfig = true;
90           configuration.services.forgejo.dump = {
91             enable = true;
92             type = "tar.zst";
93             file = "dump.tar.zst";
94           };
95         };
96       };
97       client = { ... }: {
98         programs.git = {
99           enable = true;
100           config = {
101             user.email = "test@localhost";
102             user.name = "test";
103             init.defaultBranch = "main";
104           };
105         };
106         programs.ssh.extraConfig = ''
107           Host *
108             StrictHostKeyChecking no
109             IdentityFile ~/.ssh/privk
110         '';
111       };
112     };
114     testScript = { nodes, ... }:
115       let
116         inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
117         serverSystem = nodes.server.system.build.toplevel;
118         dumpFile = with nodes.server.specialisation.dump.configuration.services.forgejo.dump; "${backupDir}/${file}";
119         remoteUri = "forgejo@server:test/repo";
120         remoteUriCheckoutAction = "forgejo@server:test/checkout";
121       in
122       ''
123         import json
125         start_all()
127         client.succeed("mkdir -p ~/.ssh")
128         client.succeed("(umask 0077; cat ${snakeOilPrivateKey} > ~/.ssh/privk)")
130         client.succeed("mkdir /tmp/repo")
131         client.succeed("git -C /tmp/repo init")
132         client.succeed("echo 'hello world' > /tmp/repo/testfile")
133         client.succeed("git -C /tmp/repo add .")
134         client.succeed("git -C /tmp/repo commit -m 'Initial import'")
135         client.succeed("git -C /tmp/repo remote add origin ${remoteUri}")
137         server.wait_for_unit("forgejo.service")
138         server.wait_for_open_port(3000)
139         server.wait_for_open_port(22)
140         server.succeed("curl --fail http://localhost:3000/")
142         server.succeed(
143             "su -l forgejo -c 'gpg --homedir /var/lib/forgejo/data/home/.gnupg "
144             + "--import ${toString (pkgs.writeText "forgejo.key" signingPrivateKey)}'"
145         )
147         assert "BEGIN PGP PUBLIC KEY BLOCK" in server.succeed("curl http://localhost:3000/api/v1/signing-key.gpg")
149         api_version = json.loads(server.succeed("curl http://localhost:3000/api/forgejo/v1/version")).get("version")
150         assert "development" != api_version and "${forgejoPackage.version}+gitea-" in api_version, (
151             "/api/forgejo/v1/version should not return 'development' "
152             + f"but should contain a forgejo+gitea compatibility version string. Got '{api_version}' instead."
153         )
155         server.succeed(
156             "curl --fail http://localhost:3000/user/sign_up | grep 'Registration is disabled. "
157             + "Please contact your site administrator.'"
158         )
159         server.succeed(
160             "su -l forgejo -c 'GITEA_WORK_DIR=/var/lib/forgejo gitea admin user create "
161             + "--username test --password totallysafe --email test@localhost --must-change-password=false'"
162         )
164         api_token = server.succeed(
165             "curl --fail -X POST http://test:totallysafe@localhost:3000/api/v1/users/test/tokens "
166             + "-H 'Accept: application/json' -H 'Content-Type: application/json' -d "
167             + "'{\"name\":\"token\",\"scopes\":[\"all\"]}' | jq '.sha1' | xargs echo -n"
168         )
170         server.succeed(
171             "curl --fail -X POST http://localhost:3000/api/v1/user/repos "
172             + "-H 'Accept: application/json' -H 'Content-Type: application/json' "
173             + f"-H 'Authorization: token {api_token}'"
174             + ' -d \'{"auto_init":false, "description":"string", "license":"mit", "name":"repo", "private":false}\'''
175         )
177         server.succeed(
178             "curl --fail -X POST http://localhost:3000/api/v1/user/keys "
179             + "-H 'Accept: application/json' -H 'Content-Type: application/json' "
180             + f"-H 'Authorization: token {api_token}'"
181             + ' -d \'{"key":"${snakeOilPublicKey}","read_only":true,"title":"SSH"}\'''
182         )
184         client.succeed("git -C /tmp/repo push origin main")
186         client.succeed("git clone ${remoteUri} /tmp/repo-clone")
187         print(client.succeed("ls -lash /tmp/repo-clone"))
188         assert "hello world" == client.succeed("cat /tmp/repo-clone/testfile").strip()
190         with subtest("Testing git protocol version=2 over ssh"):
191             git_protocol = client.succeed("GIT_TRACE2_EVENT=true GIT_TRACE2_EVENT_NESTING=3 git -C /tmp/repo-clone fetch |& grep negotiated-version")
192             version = json.loads(git_protocol).get("value")
193             assert version == "2", f"git did not negotiate protocol version 2, but version {version} instead."
195         server.wait_until_succeeds(
196             'test "$(curl http://localhost:3000/api/v1/repos/test/repo/commits '
197             + '-H "Accept: application/json" | jq length)" = "1"',
198             timeout=10
199         )
201         with subtest("Testing /metrics endpoint with token from cfg.secrets"):
202             server.fail("curl --fail http://localhost:3000/metrics")
203             server.succeed('curl --fail http://localhost:3000/metrics -H "Authorization: Bearer ${metricSecret}"')
205         with subtest("Testing runner registration and action workflow"):
206             server.succeed(
207                 "su -l forgejo -c 'GITEA_WORK_DIR=/var/lib/forgejo gitea actions generate-runner-token' | sed 's/^/TOKEN=/' | tee /var/lib/forgejo/runner_token"
208             )
209             server.succeed("${serverSystem}/specialisation/runner/bin/switch-to-configuration test")
210             server.wait_for_unit("gitea-runner-test.service")
211             server.succeed("journalctl -o cat -u gitea-runner-test.service | grep -q 'Runner registered successfully'")
213             # enable actions feature for this repository, defaults to disabled
214             server.succeed(
215                 "curl --fail -X PATCH http://localhost:3000/api/v1/repos/test/repo "
216                 + "-H 'Accept: application/json' -H 'Content-Type: application/json' "
217                 + f"-H 'Authorization: token {api_token}'"
218                 + ' -d \'{"has_actions":true}\'''
219             )
221             # mirror "actions/checkout" action
222             client.succeed("cp -R ${checkoutActionSource}/ /tmp/checkout")
223             client.succeed("git -C /tmp/checkout init")
224             client.succeed("git -C /tmp/checkout add .")
225             client.succeed("git -C /tmp/checkout commit -m 'Initial import'")
226             client.succeed("git -C /tmp/checkout remote add origin ${remoteUriCheckoutAction}")
227             client.succeed("git -C /tmp/checkout push origin main")
229             # push workflow to initial repo
230             client.succeed("mkdir -p /tmp/repo/.forgejo/workflows")
231             client.succeed("cp ${pkgs.writeText "dummy-workflow.yml" actionsWorkflowYaml} /tmp/repo/.forgejo/workflows/")
232             client.succeed("git -C /tmp/repo add .")
233             client.succeed("git -C /tmp/repo commit -m 'Add dummy workflow'")
234             client.succeed("git -C /tmp/repo push origin main")
236             def poll_workflow_action_status(_) -> bool:
237                 output = server.succeed(
238                     "curl --fail http://localhost:3000/test/repo/actions | "
239                     + 'htmlq ".flex-item-leading span" --attribute "data-tooltip-content"'
240                 ).strip()
242                 # values taken from https://codeberg.org/forgejo/forgejo/src/commit/af47c583b4fb3190fa4c4c414500f9941cc02389/options/locale/locale_en-US.ini#L3649-L3661
243                 if output in [ "Failure", "Canceled", "Skipped", "Blocked" ]:
244                     raise Exception(f"Workflow status is '{output}', which we consider failed.")
245                     server.log(f"Command returned '{output}', which we consider failed.")
247                 elif output in [ "Unknown", "Waiting", "Running", "" ]:
248                     server.log(f"Workflow status is '{output}'. Waiting some more...")
249                     return False
251                 elif output in [ "Success" ]:
252                     return True
254                 raise Exception(f"Workflow status is '{output}', which we don't know. Value mappings likely need updating.")
256             with server.nested("Waiting for the workflow run to be successful"):
257                 retry(poll_workflow_action_status)
259         with subtest("Testing backup service"):
260             server.succeed("${serverSystem}/specialisation/dump/bin/switch-to-configuration test")
261             server.systemctl("start forgejo-dump")
262             assert "Zstandard compressed data" in server.succeed("file ${dumpFile}")
263             server.copy_from_vm("${dumpFile}")
264       '';
265   });
268 listToAttrs (map makeForgejoTest supportedDbTypes)