Release NixOS 23.11
[NixPkgs.git] / nixos / tests / gitlab.nix
blob88cd774f815a5ed628bac9b76c4ad4cc058eb30b
1 # This test runs gitlab and performs the following tests:
2 # - Creating users
3 # - Pushing commits
4 #   - over the API
5 #   - over SSH
6 # - Creating Merge Requests and merging them
7 # - Opening and closing issues.
8 # - Downloading repository archives as tar.gz and tar.bz2
9 # Run with
10 # [nixpkgs]$ nix-build -A nixosTests.gitlab
12 { pkgs, lib, ... }:
14 let
15   inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
16   initialRootPassword = "notproduction";
17   rootProjectId = "2";
19   aliceUsername = "alice";
20   aliceUserId = "2";
21   alicePassword = "R5twyCgU0uXC71wT9BBTCqLs6HFZ7h3L";
22   aliceProjectId = "1";
23   aliceProjectName = "test-alice";
25   bobUsername = "bob";
26   bobUserId = "3";
27   bobPassword = "XwkkBbl2SiIwabQzgcoaTbhsotijEEtF";
28   bobProjectId = "2";
29 in {
30   name = "gitlab";
31   meta.maintainers = with lib.maintainers; [ globin yayayayaka ];
33   nodes = {
34     gitlab = { ... }: {
35       imports = [ common/user-account.nix ];
37       virtualisation.memorySize = if pkgs.stdenv.is64bit then 4096 else 2047;
38       virtualisation.cores = 4;
39       virtualisation.useNixStoreImage = true;
40       virtualisation.writableStore = false;
42       systemd.services.gitlab.serviceConfig.Restart = lib.mkForce "no";
43       systemd.services.gitlab-workhorse.serviceConfig.Restart = lib.mkForce "no";
44       systemd.services.gitaly.serviceConfig.Restart = lib.mkForce "no";
45       systemd.services.gitlab-sidekiq.serviceConfig.Restart = lib.mkForce "no";
47       services.nginx = {
48         enable = true;
49         recommendedProxySettings = true;
50         virtualHosts = {
51           localhost = {
52             locations."/".proxyPass = "http://unix:/run/gitlab/gitlab-workhorse.socket";
53           };
54         };
55       };
57       services.openssh.enable = true;
59       services.dovecot2 = {
60         enable = true;
61         enableImap = true;
62       };
64       systemd.services.gitlab-backup.environment.BACKUP = "dump";
66       services.gitlab = {
67         enable = true;
68         databasePasswordFile = pkgs.writeText "dbPassword" "xo0daiF4";
69         initialRootPasswordFile = pkgs.writeText "rootPassword" initialRootPassword;
70         smtp.enable = true;
71         pages = {
72           enable = true;
73           settings.pages-domain = "localhost";
74         };
75         extraConfig = {
76           incoming_email = {
77             enabled = true;
78             mailbox = "inbox";
79             address = "alice@localhost";
80             user = "alice";
81             password = "foobar";
82             host = "localhost";
83             port = 143;
84           };
85         };
86         secrets = {
87           secretFile = pkgs.writeText "secret" "Aig5zaic";
88           otpFile = pkgs.writeText "otpsecret" "Riew9mue";
89           dbFile = pkgs.writeText "dbsecret" "we2quaeZ";
90           jwsFile = pkgs.runCommand "oidcKeyBase" {} "${pkgs.openssl}/bin/openssl genrsa 2048 > $out";
91         };
92       };
93     };
94   };
96   testScript = { nodes, ... }:
97     let
98       auth = pkgs.writeText "auth.json" (builtins.toJSON {
99         grant_type = "password";
100         username = "root";
101         password = initialRootPassword;
102       });
104       createUserAlice = pkgs.writeText "create-user-alice.json" (builtins.toJSON rec {
105         username = aliceUsername;
106         name = username;
107         email = "alice@localhost";
108         password = alicePassword;
109         skip_confirmation = true;
110       });
112       createUserBob = pkgs.writeText "create-user-bob.json" (builtins.toJSON rec {
113         username = bobUsername;
114         name = username;
115         email = "bob@localhost";
116         password = bobPassword;
117         skip_confirmation = true;
118       });
120       aliceAuth = pkgs.writeText "alice-auth.json" (builtins.toJSON {
121         grant_type = "password";
122         username = aliceUsername;
123         password = alicePassword;
124       });
126       bobAuth = pkgs.writeText "bob-auth.json" (builtins.toJSON {
127         grant_type = "password";
128         username = bobUsername;
129         password = bobPassword;
130       });
132       aliceAddSSHKey = pkgs.writeText "alice-add-ssh-key.json" (builtins.toJSON {
133         id = aliceUserId;
134         title = "snakeoil@nixos";
135         key = snakeOilPublicKey;
136       });
138       createProjectAlice = pkgs.writeText "create-project-alice.json" (builtins.toJSON {
139         name = aliceProjectName;
140         visibility = "public";
141       });
143       putFile = pkgs.writeText "put-file.json" (builtins.toJSON {
144         branch = "master";
145         author_email = "author@example.com";
146         author_name = "Firstname Lastname";
147         content = "some content";
148         commit_message = "create a new file";
149       });
151       mergeRequest = pkgs.writeText "merge-request.json" (builtins.toJSON {
152         id = bobProjectId;
153         target_project_id = aliceProjectId;
154         source_branch = "master";
155         target_branch = "master";
156         title = "Add some other file";
157       });
159       newIssue = pkgs.writeText "new-issue.json" (builtins.toJSON {
160         title = "useful issue title";
161       });
163       closeIssue = pkgs.writeText "close-issue.json" (builtins.toJSON {
164         issue_iid = 1;
165         state_event = "close";
166       });
168       # Wait for all GitLab services to be fully started.
169       waitForServices = ''
170         gitlab.wait_for_unit("gitaly.service")
171         gitlab.wait_for_unit("gitlab-workhorse.service")
172         gitlab.wait_for_unit("gitlab-mailroom.service")
173         gitlab.wait_for_unit("gitlab.service")
174         gitlab.wait_for_unit("gitlab-pages.service")
175         gitlab.wait_for_unit("gitlab-sidekiq.service")
176         gitlab.wait_for_file("${nodes.gitlab.services.gitlab.statePath}/tmp/sockets/gitlab.socket")
177         gitlab.wait_until_succeeds("curl -sSf http://gitlab/users/sign_in")
178       '';
180       # The actual test of GitLab. Only push data to GitLab if
181       # `doSetup` is is true.
182       test = doSetup: ''
183         GIT_SSH_COMMAND = "ssh -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null"
185         gitlab.succeed(
186             "curl -isSf http://gitlab | grep -i location | grep http://gitlab/users/sign_in"
187         )
188         gitlab.succeed(
189             "${pkgs.sudo}/bin/sudo -u gitlab -H gitlab-rake gitlab:check 1>&2"
190         )
191         gitlab.succeed(
192             "echo \"Authorization: Bearer $(curl -X POST -H 'Content-Type: application/json' -d @${auth} http://gitlab/oauth/token | ${pkgs.jq}/bin/jq -r '.access_token')\" >/tmp/headers"
193         )
194       '' + lib.optionalString doSetup ''
195         with subtest("Create user Alice"):
196             gitlab.succeed(
197                 """[ "$(curl -o /dev/null -w '%{http_code}' -X POST -H 'Content-Type: application/json' -H @/tmp/headers -d @${createUserAlice} http://gitlab/api/v4/users)" = "201" ]"""
198             )
199             gitlab.succeed(
200                 "echo \"Authorization: Bearer $(curl -X POST -H 'Content-Type: application/json' -d @${aliceAuth} http://gitlab/oauth/token | ${pkgs.jq}/bin/jq -r '.access_token')\" >/tmp/headers-alice"
201             )
203         with subtest("Create user Bob"):
204             gitlab.succeed(
205                 """ [ "$(curl -o /dev/null -w '%{http_code}' -X POST -H 'Content-Type: application/json' -H @/tmp/headers -d @${createUserBob} http://gitlab/api/v4/users)" = "201" ]"""
206             )
207             gitlab.succeed(
208                 "echo \"Authorization: Bearer $(curl -X POST -H 'Content-Type: application/json' -d @${bobAuth} http://gitlab/oauth/token | ${pkgs.jq}/bin/jq -r '.access_token')\" >/tmp/headers-bob"
209             )
211         with subtest("Setup Git and SSH for Alice"):
212             gitlab.succeed("git config --global user.name Alice")
213             gitlab.succeed("git config --global user.email alice@nixos.invalid")
214             gitlab.succeed("mkdir -m 700 /root/.ssh")
215             gitlab.succeed("cat ${snakeOilPrivateKey} > /root/.ssh/id_ecdsa")
216             gitlab.succeed("chmod 600 /root/.ssh/id_ecdsa")
217             gitlab.succeed(
218                 """
219                 [ "$(curl \
220                     -o /dev/null \
221                     -w '%{http_code}' \
222                     -X POST \
223                     -H 'Content-Type: application/json' \
224                     -H @/tmp/headers-alice -d @${aliceAddSSHKey} \
225                     http://gitlab/api/v4/user/keys)" = "201" ]
226                 """
227             )
229         with subtest("Create a new repository"):
230             # Alice creates a new repository
231             gitlab.succeed(
232                 """
233                 [ "$(curl \
234                     -o /dev/null \
235                     -w '%{http_code}' \
236                     -X POST \
237                     -H 'Content-Type: application/json' \
238                     -H @/tmp/headers-alice \
239                     -d @${createProjectAlice} \
240                     http://gitlab/api/v4/projects)" = "201" ]
241                 """
242             )
244             # Alice commits an initial commit
245             gitlab.succeed(
246                 """
247                 [ "$(curl \
248                     -o /dev/null \
249                     -w '%{http_code}' \
250                     -X POST \
251                     -H 'Content-Type: application/json' \
252                     -H @/tmp/headers-alice \
253                     -d @${putFile} \
254                     http://gitlab/api/v4/projects/${aliceProjectId}/repository/files/some-file.txt)" = "201" ]"""
255             )
257         with subtest("git clone over HTTP"):
258             gitlab.succeed(
259                 """git clone http://gitlab/alice/${aliceProjectName}.git clone-via-http""",
260                 timeout=15
261             )
263         with subtest("Push a commit via SSH"):
264             gitlab.succeed(
265                 f"""GIT_SSH_COMMAND="{GIT_SSH_COMMAND}" git clone gitlab@gitlab:alice/${aliceProjectName}.git""",
266                 timeout=15
267             )
268             gitlab.succeed(
269                 """echo "a commit sent over ssh" > ${aliceProjectName}/ssh.txt"""
270             )
271             gitlab.succeed(
272                 """
273                 cd ${aliceProjectName} || exit 1
274                 git add .
275                 """
276             )
277             gitlab.succeed(
278                 """
279                 cd ${aliceProjectName} || exit 1
280                 git commit -m "Add a commit to be sent over ssh"
281                 """
282             )
283             gitlab.succeed(
284                 f"""
285                 cd ${aliceProjectName} || exit 1
286                 GIT_SSH_COMMAND="{GIT_SSH_COMMAND}" git push --set-upstream origin master
287                 """,
288                 timeout=15
289             )
291         with subtest("Fork a project"):
292             # Bob forks Alice's project
293             gitlab.succeed(
294                 """
295                 [ "$(curl \
296                     -o /dev/null \
297                     -w '%{http_code}' \
298                     -X POST \
299                     -H 'Content-Type: application/json' \
300                     -H @/tmp/headers-bob \
301                     http://gitlab/api/v4/projects/${aliceProjectId}/fork)" = "201" ]
302                 """
303             )
305             # Bob creates a commit
306             gitlab.wait_until_succeeds(
307                 """
308                 [ "$(curl \
309                     -o /dev/null \
310                     -w '%{http_code}' \
311                     -X POST \
312                     -H 'Content-Type: application/json' \
313                     -H @/tmp/headers-bob \
314                     -d @${putFile} \
315                     http://gitlab/api/v4/projects/${bobProjectId}/repository/files/some-other-file.txt)" = "201" ]
316                 """
317             )
319         with subtest("Create a Merge Request"):
320             # Bob opens a merge request against Alice's repository
321             gitlab.wait_until_succeeds(
322                 """
323                 [ "$(curl \
324                     -o /dev/null \
325                     -w '%{http_code}' \
326                     -X POST \
327                     -H 'Content-Type: application/json' \
328                     -H @/tmp/headers-bob \
329                     -d @${mergeRequest} \
330                     http://gitlab/api/v4/projects/${bobProjectId}/merge_requests)" = "201" ]
331                 """
332             )
334             # Alice merges the MR
335             gitlab.wait_until_succeeds(
336                 """
337                 [ "$(curl \
338                     -o /dev/null \
339                     -w '%{http_code}' \
340                     -X PUT \
341                     -H 'Content-Type: application/json' \
342                     -H @/tmp/headers-alice \
343                     -d @${mergeRequest} \
344                     http://gitlab/api/v4/projects/${aliceProjectId}/merge_requests/1/merge)" = "200" ]
345                 """
346             )
348         with subtest("Create an Issue"):
349             # Bob opens an issue on Alice's repository
350             gitlab.succeed(
351                 """[ "$(curl \
352                     -o /dev/null \
353                     -w '%{http_code}' \
354                     -X POST \
355                     -H 'Content-Type: application/json' \
356                     -H @/tmp/headers-bob \
357                     -d @${newIssue} \
358                     http://gitlab/api/v4/projects/${aliceProjectId}/issues)" = "201" ]
359                 """
360             )
362             # Alice closes the issue
363             gitlab.wait_until_succeeds(
364                 """
365                 [ "$(curl \
366                     -o /dev/null \
367                     -w '%{http_code}' \
368                     -X PUT \
369                     -H 'Content-Type: application/json' \
370                     -H @/tmp/headers-alice -d @${closeIssue} http://gitlab/api/v4/projects/${aliceProjectId}/issues/1)" = "200" ]
371                 """
372             )
373       '' + ''
374         with subtest("Download archive.tar.gz"):
375             gitlab.succeed(
376                 """
377                 [ "$(curl \
378                     -o /dev/null \
379                     -w '%{http_code}' \
380                     -H @/tmp/headers-alice \
381                     http://gitlab/api/v4/projects/${aliceProjectId}/repository/archive.tar.gz)" = "200" ]
382                 """
383             )
384             gitlab.succeed(
385                 """
386                 curl \
387                     -H @/tmp/headers-alice \
388                     http://gitlab/api/v4/projects/${aliceProjectId}/repository/archive.tar.gz > /tmp/archive.tar.gz
389                 """
390             )
391             gitlab.succeed("test -s /tmp/archive.tar.gz")
393         with subtest("Download archive.tar.bz2"):
394             gitlab.succeed(
395                 """
396                 [ "$(curl \
397                     -o /dev/null \
398                     -w '%{http_code}' \
399                     -H @/tmp/headers-alice \
400                     http://gitlab/api/v4/projects/${aliceProjectId}/repository/archive.tar.bz2)" = "200" ]
401                 """
402             )
403             gitlab.succeed(
404                 """
405                 curl \
406                     -H @/tmp/headers-alice \
407                     http://gitlab/api/v4/projects/${aliceProjectId}/repository/archive.tar.bz2 > /tmp/archive.tar.bz2
408                 """
409             )
410             gitlab.succeed("test -s /tmp/archive.tar.bz2")
411       '';
413   in ''
414       gitlab.start()
415     ''
416     + waitForServices
417     + test true
418     + ''
419       gitlab.systemctl("start gitlab-backup.service")
420       gitlab.wait_for_unit("gitlab-backup.service")
421       gitlab.wait_for_file("${nodes.gitlab.services.gitlab.statePath}/backup/dump_gitlab_backup.tar")
422       gitlab.systemctl("stop postgresql.service gitlab.target")
423       gitlab.succeed(
424           "find ${nodes.gitlab.services.gitlab.statePath} -mindepth 1 -maxdepth 1 -not -name backup -execdir rm -r {} +"
425       )
426       gitlab.succeed("systemd-tmpfiles --create")
427       gitlab.succeed("rm -rf ${nodes.gitlab.services.postgresql.dataDir}")
428       gitlab.systemctl("start gitlab-config.service gitaly.service gitlab-postgresql.service")
429       gitlab.wait_for_file("${nodes.gitlab.services.gitlab.statePath}/tmp/sockets/gitaly.socket")
430       gitlab.succeed(
431           "sudo -u gitlab -H gitlab-rake gitlab:backup:restore RAILS_ENV=production BACKUP=dump force=yes"
432       )
433       gitlab.systemctl("start gitlab.target")
434     ''
435     + waitForServices
436     + test false;