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
11
12{ pkgs, lib, ... }:
13
14let
15 inherit (import ./ssh-keys.nix pkgs) snakeOilPrivateKey snakeOilPublicKey;
16 initialRootPassword = "notproduction";
17 rootProjectId = "2";
18
19 aliceUsername = "alice";
20 aliceUserId = "2";
21 alicePassword = "R5twyCgU0uXC71wT9BBTCqLs6HFZ7h3L";
22 aliceProjectId = "1";
23 aliceProjectName = "test-alice";
24
25 bobUsername = "bob";
26 bobUserId = "3";
27 bobPassword = "XwkkBbl2SiIwabQzgcoaTbhsotijEEtF";
28 bobProjectId = "2";
29in {
30 name = "gitlab";
31 meta.maintainers = with lib.maintainers; [ globin yayayayaka ];
32
33 nodes = {
34 gitlab = { ... }: {
35 imports = [ common/user-account.nix ];
36
37 virtualisation.memorySize = 6144;
38 virtualisation.cores = 4;
39 virtualisation.useNixStoreImage = true;
40 virtualisation.writableStore = false;
41
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";
46
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 };
56
57 services.openssh.enable = true;
58
59 services.dovecot2 = {
60 enable = true;
61 enableImap = true;
62 };
63
64 systemd.services.gitlab-backup.environment.BACKUP = "dump";
65
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 # reduce memory usage
94 sidekiq.concurrency = 1;
95 puma.workers = 2;
96 };
97 };
98 };
99
100 testScript = { nodes, ... }:
101 let
102 auth = pkgs.writeText "auth.json" (builtins.toJSON {
103 grant_type = "password";
104 username = "root";
105 password = initialRootPassword;
106 });
107
108 createUserAlice = pkgs.writeText "create-user-alice.json" (builtins.toJSON rec {
109 username = aliceUsername;
110 name = username;
111 email = "alice@localhost";
112 password = alicePassword;
113 skip_confirmation = true;
114 });
115
116 createUserBob = pkgs.writeText "create-user-bob.json" (builtins.toJSON rec {
117 username = bobUsername;
118 name = username;
119 email = "bob@localhost";
120 password = bobPassword;
121 skip_confirmation = true;
122 });
123
124 aliceAuth = pkgs.writeText "alice-auth.json" (builtins.toJSON {
125 grant_type = "password";
126 username = aliceUsername;
127 password = alicePassword;
128 });
129
130 bobAuth = pkgs.writeText "bob-auth.json" (builtins.toJSON {
131 grant_type = "password";
132 username = bobUsername;
133 password = bobPassword;
134 });
135
136 aliceAddSSHKey = pkgs.writeText "alice-add-ssh-key.json" (builtins.toJSON {
137 id = aliceUserId;
138 title = "snakeoil@nixos";
139 key = snakeOilPublicKey;
140 });
141
142 createProjectAlice = pkgs.writeText "create-project-alice.json" (builtins.toJSON {
143 name = aliceProjectName;
144 visibility = "public";
145 });
146
147 putFile = pkgs.writeText "put-file.json" (builtins.toJSON {
148 branch = "master";
149 author_email = "author@example.com";
150 author_name = "Firstname Lastname";
151 content = "some content";
152 commit_message = "create a new file";
153 });
154
155 mergeRequest = pkgs.writeText "merge-request.json" (builtins.toJSON {
156 id = bobProjectId;
157 target_project_id = aliceProjectId;
158 source_branch = "master";
159 target_branch = "master";
160 title = "Add some other file";
161 });
162
163 newIssue = pkgs.writeText "new-issue.json" (builtins.toJSON {
164 title = "useful issue title";
165 });
166
167 closeIssue = pkgs.writeText "close-issue.json" (builtins.toJSON {
168 issue_iid = 1;
169 state_event = "close";
170 });
171
172 # Wait for all GitLab services to be fully started.
173 waitForServices = ''
174 gitlab.wait_for_unit("gitaly.service")
175 gitlab.wait_for_unit("gitlab-workhorse.service")
176 gitlab.wait_for_unit("gitlab-mailroom.service")
177 gitlab.wait_for_unit("gitlab.service")
178 gitlab.wait_for_unit("gitlab-pages.service")
179 gitlab.wait_for_unit("gitlab-sidekiq.service")
180 gitlab.wait_for_file("${nodes.gitlab.services.gitlab.statePath}/tmp/sockets/gitlab.socket")
181 gitlab.wait_until_succeeds("curl -sSf http://gitlab/users/sign_in")
182 '';
183
184 # The actual test of GitLab. Only push data to GitLab if
185 # `doSetup` is is true.
186 test = doSetup: ''
187 GIT_SSH_COMMAND = "ssh -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null"
188
189 gitlab.succeed(
190 "curl -isSf http://gitlab | grep -i location | grep http://gitlab/users/sign_in"
191 )
192 gitlab.succeed(
193 "${pkgs.sudo}/bin/sudo -u gitlab -H gitlab-rake gitlab:check 1>&2"
194 )
195 gitlab.succeed(
196 "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"
197 )
198 '' + lib.optionalString doSetup ''
199 with subtest("Create user Alice"):
200 gitlab.succeed(
201 """[ "$(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" ]"""
202 )
203 gitlab.succeed(
204 "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"
205 )
206
207 with subtest("Create user Bob"):
208 gitlab.succeed(
209 """ [ "$(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" ]"""
210 )
211 gitlab.succeed(
212 "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"
213 )
214
215 with subtest("Setup Git and SSH for Alice"):
216 gitlab.succeed("git config --global user.name Alice")
217 gitlab.succeed("git config --global user.email alice@nixos.invalid")
218 gitlab.succeed("mkdir -m 700 /root/.ssh")
219 gitlab.succeed("cat ${snakeOilPrivateKey} > /root/.ssh/id_ecdsa")
220 gitlab.succeed("chmod 600 /root/.ssh/id_ecdsa")
221 gitlab.succeed(
222 """
223 [ "$(curl \
224 -o /dev/null \
225 -w '%{http_code}' \
226 -X POST \
227 -H 'Content-Type: application/json' \
228 -H @/tmp/headers-alice -d @${aliceAddSSHKey} \
229 http://gitlab/api/v4/user/keys)" = "201" ]
230 """
231 )
232
233 with subtest("Create a new repository"):
234 # Alice creates a new repository
235 gitlab.succeed(
236 """
237 [ "$(curl \
238 -o /dev/null \
239 -w '%{http_code}' \
240 -X POST \
241 -H 'Content-Type: application/json' \
242 -H @/tmp/headers-alice \
243 -d @${createProjectAlice} \
244 http://gitlab/api/v4/projects)" = "201" ]
245 """
246 )
247
248 # Alice commits an initial commit
249 gitlab.succeed(
250 """
251 [ "$(curl \
252 -o /dev/null \
253 -w '%{http_code}' \
254 -X POST \
255 -H 'Content-Type: application/json' \
256 -H @/tmp/headers-alice \
257 -d @${putFile} \
258 http://gitlab/api/v4/projects/${aliceProjectId}/repository/files/some-file.txt)" = "201" ]"""
259 )
260
261 with subtest("git clone over HTTP"):
262 gitlab.succeed(
263 """git clone http://gitlab/alice/${aliceProjectName}.git clone-via-http""",
264 timeout=15
265 )
266
267 with subtest("Push a commit via SSH"):
268 gitlab.succeed(
269 f"""GIT_SSH_COMMAND="{GIT_SSH_COMMAND}" git clone gitlab@gitlab:alice/${aliceProjectName}.git""",
270 timeout=15
271 )
272 gitlab.succeed(
273 """echo "a commit sent over ssh" > ${aliceProjectName}/ssh.txt"""
274 )
275 gitlab.succeed(
276 """
277 cd ${aliceProjectName} || exit 1
278 git add .
279 """
280 )
281 gitlab.succeed(
282 """
283 cd ${aliceProjectName} || exit 1
284 git commit -m "Add a commit to be sent over ssh"
285 """
286 )
287 gitlab.succeed(
288 f"""
289 cd ${aliceProjectName} || exit 1
290 GIT_SSH_COMMAND="{GIT_SSH_COMMAND}" git push --set-upstream origin master
291 """,
292 timeout=15
293 )
294
295 with subtest("Fork a project"):
296 # Bob forks Alice's project
297 gitlab.succeed(
298 """
299 [ "$(curl \
300 -o /dev/null \
301 -w '%{http_code}' \
302 -X POST \
303 -H 'Content-Type: application/json' \
304 -H @/tmp/headers-bob \
305 http://gitlab/api/v4/projects/${aliceProjectId}/fork)" = "201" ]
306 """
307 )
308
309 # Bob creates a commit
310 gitlab.wait_until_succeeds(
311 """
312 [ "$(curl \
313 -o /dev/null \
314 -w '%{http_code}' \
315 -X POST \
316 -H 'Content-Type: application/json' \
317 -H @/tmp/headers-bob \
318 -d @${putFile} \
319 http://gitlab/api/v4/projects/${bobProjectId}/repository/files/some-other-file.txt)" = "201" ]
320 """
321 )
322
323 with subtest("Create a Merge Request"):
324 # Bob opens a merge request against Alice's repository
325 gitlab.wait_until_succeeds(
326 """
327 [ "$(curl \
328 -o /dev/null \
329 -w '%{http_code}' \
330 -X POST \
331 -H 'Content-Type: application/json' \
332 -H @/tmp/headers-bob \
333 -d @${mergeRequest} \
334 http://gitlab/api/v4/projects/${bobProjectId}/merge_requests)" = "201" ]
335 """
336 )
337
338 # Alice merges the MR
339 gitlab.wait_until_succeeds(
340 """
341 [ "$(curl \
342 -o /dev/null \
343 -w '%{http_code}' \
344 -X PUT \
345 -H 'Content-Type: application/json' \
346 -H @/tmp/headers-alice \
347 -d @${mergeRequest} \
348 http://gitlab/api/v4/projects/${aliceProjectId}/merge_requests/1/merge)" = "200" ]
349 """
350 )
351
352 with subtest("Create an Issue"):
353 # Bob opens an issue on Alice's repository
354 gitlab.succeed(
355 """[ "$(curl \
356 -o /dev/null \
357 -w '%{http_code}' \
358 -X POST \
359 -H 'Content-Type: application/json' \
360 -H @/tmp/headers-bob \
361 -d @${newIssue} \
362 http://gitlab/api/v4/projects/${aliceProjectId}/issues)" = "201" ]
363 """
364 )
365
366 # Alice closes the issue
367 gitlab.wait_until_succeeds(
368 """
369 [ "$(curl \
370 -o /dev/null \
371 -w '%{http_code}' \
372 -X PUT \
373 -H 'Content-Type: application/json' \
374 -H @/tmp/headers-alice -d @${closeIssue} http://gitlab/api/v4/projects/${aliceProjectId}/issues/1)" = "200" ]
375 """
376 )
377 '' + ''
378 with subtest("Download archive.tar.gz"):
379 gitlab.succeed(
380 """
381 [ "$(curl \
382 -o /dev/null \
383 -w '%{http_code}' \
384 -H @/tmp/headers-alice \
385 http://gitlab/api/v4/projects/${aliceProjectId}/repository/archive.tar.gz)" = "200" ]
386 """
387 )
388 gitlab.succeed(
389 """
390 curl \
391 -H @/tmp/headers-alice \
392 http://gitlab/api/v4/projects/${aliceProjectId}/repository/archive.tar.gz > /tmp/archive.tar.gz
393 """
394 )
395 gitlab.succeed("test -s /tmp/archive.tar.gz")
396
397 with subtest("Download archive.tar.bz2"):
398 gitlab.succeed(
399 """
400 [ "$(curl \
401 -o /dev/null \
402 -w '%{http_code}' \
403 -H @/tmp/headers-alice \
404 http://gitlab/api/v4/projects/${aliceProjectId}/repository/archive.tar.bz2)" = "200" ]
405 """
406 )
407 gitlab.succeed(
408 """
409 curl \
410 -H @/tmp/headers-alice \
411 http://gitlab/api/v4/projects/${aliceProjectId}/repository/archive.tar.bz2 > /tmp/archive.tar.bz2
412 """
413 )
414 gitlab.succeed("test -s /tmp/archive.tar.bz2")
415 '';
416
417 in ''
418 gitlab.start()
419 ''
420 + waitForServices
421 + test true
422 + ''
423 gitlab.systemctl("start gitlab-backup.service")
424 gitlab.wait_for_unit("gitlab-backup.service")
425 gitlab.wait_for_file("${nodes.gitlab.services.gitlab.statePath}/backup/dump_gitlab_backup.tar")
426 gitlab.systemctl("stop postgresql.service gitlab-config.service gitlab.target")
427 gitlab.succeed(
428 "find ${nodes.gitlab.services.gitlab.statePath} -mindepth 1 -maxdepth 1 -not -name backup -execdir rm -r {} +"
429 )
430 gitlab.succeed("systemd-tmpfiles --create")
431 gitlab.succeed("rm -rf ${nodes.gitlab.services.postgresql.dataDir}")
432 gitlab.systemctl("start gitlab-config.service gitaly.service gitlab-postgresql.service")
433 gitlab.wait_for_file("${nodes.gitlab.services.gitlab.statePath}/tmp/sockets/gitaly.socket")
434 gitlab.succeed(
435 "sudo -u gitlab -H gitlab-rake gitlab:backup:restore RAILS_ENV=production BACKUP=dump force=yes"
436 )
437 gitlab.systemctl("start gitlab.target")
438 ''
439 + waitForServices
440 + test false;
441}