1#!/usr/bin/env nix-shell
2#! nix-shell -I nixpkgs=../../../.. -i python3 -p bundix bundler nix-update nix python3 python3Packages.requests python3Packages.click python3Packages.click-log python3Packages.packaging prefetch-yarn-deps git
3
4import click
5import click_log
6import re
7import logging
8import subprocess
9import json
10import pathlib
11import tempfile
12from packaging.version import Version
13from typing import Iterable
14
15import requests
16
17NIXPKGS_PATH = pathlib.Path(__file__).parent / "../../../../"
18GITLAB_DIR = pathlib.Path(__file__).parent
19
20logger = logging.getLogger(__name__)
21click_log.basic_config(logger)
22
23
24class GitLabRepo:
25 version_regex = re.compile(r"^v\d+\.\d+\.\d+(\-rc\d+)?(\-ee)?(\-gitlab)?")
26
27 def __init__(self, owner: str = "gitlab-org", repo: str = "gitlab"):
28 self.owner = owner
29 self.repo = repo
30
31 @property
32 def url(self):
33 return f"https://gitlab.com/{self.owner}/{self.repo}"
34
35 @property
36 def tags(self) -> Iterable[str]:
37 """Returns a sorted list of repository tags"""
38 r = requests.get(self.url + "/refs?sort=updated_desc&ref=master").json()
39 tags = r.get("Tags", [])
40
41 # filter out versions not matching version_regex
42 versions = list(filter(self.version_regex.match, tags))
43
44 # sort, but ignore v, -ee and -gitlab for sorting comparisons
45 versions.sort(
46 key=lambda x: Version(
47 x.replace("v", "").replace("-ee", "").replace("-gitlab", "")
48 ),
49 reverse=True,
50 )
51 return versions
52 def get_git_hash(self, rev: str):
53 return (
54 subprocess.check_output(
55 [
56 "nix-prefetch-url",
57 "--unpack",
58 f"https://gitlab.com/{self.owner}/{self.repo}/-/archive/{rev}/{self.repo}-{rev}.tar.gz",
59 ]
60 )
61 .decode("utf-8")
62 .strip()
63 )
64
65 def get_yarn_hash(self, rev: str):
66 with tempfile.TemporaryDirectory() as tmp_dir:
67 with open(tmp_dir + "/yarn.lock", "w") as f:
68 f.write(self.get_file("yarn.lock", rev))
69 return (
70 subprocess.check_output(["prefetch-yarn-deps", tmp_dir + "/yarn.lock"])
71 .decode("utf-8")
72 .strip()
73 )
74
75 @staticmethod
76 def rev2version(tag: str) -> str:
77 """
78 normalize a tag to a version number.
79 This obviously isn't very smart if we don't pass something that looks like a tag
80 :param tag: the tag to normalize
81 :return: a normalized version number
82 """
83 # strip v prefix
84 version = re.sub(r"^v", "", tag)
85 # strip -ee and -gitlab suffixes
86 return re.sub(r"-(ee|gitlab)$", "", version)
87
88 def get_file(self, filepath, rev):
89 """
90 returns file contents at a given rev
91 :param filepath: the path to the file, relative to the repo root
92 :param rev: the rev to fetch at
93 :return:
94 """
95 return requests.get(self.url + f"/raw/{rev}/{filepath}").text
96
97 def get_data(self, rev):
98 version = self.rev2version(rev)
99
100 passthru = {
101 v: self.get_file(v, rev).strip()
102 for v in [
103 "GITALY_SERVER_VERSION",
104 "GITLAB_PAGES_VERSION",
105 "GITLAB_SHELL_VERSION",
106 "GITLAB_ELASTICSEARCH_INDEXER_VERSION",
107 ]
108 }
109 passthru["GITLAB_WORKHORSE_VERSION"] = version
110
111 return dict(
112 version=self.rev2version(rev),
113 repo_hash=self.get_git_hash(rev),
114 yarn_hash=self.get_yarn_hash(rev),
115 owner=self.owner,
116 repo=self.repo,
117 rev=rev,
118 passthru=passthru,
119 )
120
121
122def _get_data_json():
123 data_file_path = pathlib.Path(__file__).parent / "data.json"
124 with open(data_file_path, "r") as f:
125 return json.load(f)
126
127
128def _call_nix_update(pkg, version):
129 """calls nix-update from nixpkgs root dir"""
130 return subprocess.check_output(
131 ["nix-update", pkg, "--version", version], cwd=NIXPKGS_PATH
132 )
133
134
135@click_log.simple_verbosity_option(logger)
136@click.group()
137def cli():
138 pass
139
140
141@cli.command("update-data")
142@click.option("--rev", default="latest", help="The rev to use (vX.Y.Z-ee), or 'latest'")
143def update_data(rev: str):
144 """Update data.json"""
145 logger.info("Updating data.json")
146
147 repo = GitLabRepo()
148 if rev == "latest":
149 # filter out pre and rc releases
150 rev = next(filter(lambda x: not ("rc" in x or x.endswith("pre")), repo.tags))
151
152 data_file_path = pathlib.Path(__file__).parent / "data.json"
153
154 data = repo.get_data(rev)
155
156 with open(data_file_path.as_posix(), "w") as f:
157 json.dump(data, f, indent=2)
158 f.write("\n")
159
160
161@cli.command("update-rubyenv")
162def update_rubyenv():
163 """Update rubyEnv"""
164 logger.info("Updating gitlab")
165 repo = GitLabRepo()
166 rubyenv_dir = pathlib.Path(__file__).parent / "rubyEnv"
167
168 # load rev from data.json
169 data = _get_data_json()
170 rev = data["rev"]
171 version = data["version"]
172
173 for fn in ["Gemfile.lock", "Gemfile"]:
174 with open(rubyenv_dir / fn, "w") as f:
175 f.write(repo.get_file(fn, rev))
176
177 # update to 1.2.9 to include https://gitlab.com/gitlab-org/ruby/gems/prometheus-client-mmap/-/commit/5d77f3f3e048834250589b416c6b3d4bba65a570
178 subprocess.check_output(
179 ["sed", "-i", "s:'prometheus-client-mmap', '~> 1.2.8':'prometheus-client-mmap', '~> 1.2.9':g", "Gemfile"],
180 cwd=rubyenv_dir,
181 )
182
183 # Un-vendor sidekiq
184 #
185 # The sidekiq dependency was vendored to maintain compatibility with Redis 6.0 (as
186 # stated in this [comment]) but unfortunately, it seems to cause a crash in the
187 # application, as noted in this [upstream issue].
188 #
189 # We can safely swap out the dependency, as our Redis release in nixpkgs is >= 7.0.
190 #
191 # [comment]: https://gitlab.com/gitlab-org/gitlab/-/issues/468435#note_1979750600
192 # [upstream issue]: https://gitlab.com/gitlab-org/gitlab/-/issues/468435
193 subprocess.check_output(
194 ["sed", "-i", "s|gem 'sidekiq', path: 'vendor/gems/sidekiq', require: 'sidekiq'|gem 'sidekiq', '~> 7.3.9'|g", "Gemfile"],
195 cwd=rubyenv_dir,
196 )
197
198 # Fetch vendored dependencies temporarily in order to build the gemset.nix
199 subprocess.check_output(["mkdir", "-p", "vendor/gems", "gems"], cwd=rubyenv_dir)
200 subprocess.check_output(
201 [
202 "sh",
203 "-c",
204 f"curl -L https://gitlab.com/gitlab-org/gitlab/-/archive/v{version}-ee/gitlab-v{version}-ee.tar.bz2?path=vendor/gems | tar -xj --strip-components=3",
205 ],
206 cwd=f"{rubyenv_dir}/vendor/gems",
207 )
208 subprocess.check_output(
209 [
210 "sh",
211 "-c",
212 f"curl -L https://gitlab.com/gitlab-org/gitlab/-/archive/v{version}-ee/gitlab-v{version}-ee.tar.bz2?path=gems | tar -xj --strip-components=2",
213 ],
214 cwd=f"{rubyenv_dir}/gems",
215 )
216
217 # Undo our gemset.nix patches so that bundix runs through
218 subprocess.check_output(
219 ["sed", "-i", "-e", "s|\\${src}/||g", "gemset.nix"], cwd=rubyenv_dir
220 )
221 subprocess.check_output(
222 ["sed", "-i", "-e", "s|^src:[[:space:]]||g", "gemset.nix"], cwd=rubyenv_dir
223 )
224
225 subprocess.check_output(["bundle", "lock"], cwd=rubyenv_dir)
226 subprocess.check_output(["bundix"], cwd=rubyenv_dir)
227
228 subprocess.check_output(
229 [
230 "sed",
231 "-i",
232 "-e",
233 "1c\\src: {",
234 "-e",
235 's:path = \\(vendor/[^;]*\\);:path = "${src}/\\1";:g',
236 "-e",
237 's:path = \\(gems/[^;]*\\);:path = "${src}/\\1";:g',
238 "gemset.nix",
239 ],
240 cwd=rubyenv_dir,
241 )
242 subprocess.check_output(["rm", "-rf", "vendor", "gems"], cwd=rubyenv_dir)
243
244 # Reformat gemset.nix
245 subprocess.check_output(["nix-shell", "--run", "treefmt pkgs/by-name/gi/gitlab"], cwd=NIXPKGS_PATH)
246
247
248@cli.command("update-gitaly")
249def update_gitaly():
250 """Update gitaly"""
251 logger.info("Updating gitaly")
252 data = _get_data_json()
253 gitaly_server_version = data['passthru']['GITALY_SERVER_VERSION']
254 repo = GitLabRepo(repo="gitaly")
255 gitaly_dir = pathlib.Path(__file__).parent / 'gitaly'
256
257 makefile = repo.get_file("Makefile", f"v{gitaly_server_version}")
258 makefile += "\nprint-%:;@echo $($*)\n"
259
260 git_version = subprocess.run(["make", "-f", "-", "print-GIT_VERSION"], check=True, input=makefile, text=True, capture_output=True).stdout.strip()
261
262 _call_nix_update("gitaly", gitaly_server_version)
263 _call_nix_update("gitaly.git", git_version)
264
265
266@cli.command("update-gitlab-pages")
267def update_gitlab_pages():
268 """Update gitlab-pages"""
269 logger.info("Updating gitlab-pages")
270 data = _get_data_json()
271 gitlab_pages_version = data["passthru"]["GITLAB_PAGES_VERSION"]
272 _call_nix_update("gitlab-pages", gitlab_pages_version)
273
274
275def get_container_registry_version() -> str:
276 """Returns the version attribute of gitlab-container-registry"""
277 return subprocess.check_output(
278 [
279 "nix",
280 "--experimental-features",
281 "nix-command",
282 "eval",
283 "-f",
284 ".",
285 "--raw",
286 "gitlab-container-registry.version",
287 ],
288 cwd=NIXPKGS_PATH,
289 ).decode("utf-8")
290
291
292@cli.command("update-gitlab-shell")
293def update_gitlab_shell():
294 """Update gitlab-shell"""
295 logger.info("Updating gitlab-shell")
296 data = _get_data_json()
297 gitlab_shell_version = data["passthru"]["GITLAB_SHELL_VERSION"]
298 _call_nix_update("gitlab-shell", gitlab_shell_version)
299
300
301@cli.command("update-gitlab-workhorse")
302def update_gitlab_workhorse():
303 """Update gitlab-workhorse"""
304 logger.info("Updating gitlab-workhorse")
305 data = _get_data_json()
306 gitlab_workhorse_version = data["passthru"]["GITLAB_WORKHORSE_VERSION"]
307 _call_nix_update("gitlab-workhorse", gitlab_workhorse_version)
308
309
310@cli.command("update-gitlab-container-registry")
311@click.option("--rev", default="latest", help="The rev to use (vX.Y.Z-ee), or 'latest'")
312@click.option(
313 "--commit", is_flag=True, default=False, help="Commit the changes for you"
314)
315def update_gitlab_container_registry(rev: str, commit: bool):
316 """Update gitlab-container-registry"""
317 logger.info("Updading gitlab-container-registry")
318 repo = GitLabRepo(repo="container-registry")
319 old_container_registry_version = get_container_registry_version()
320
321 if rev == "latest":
322 rev = next(filter(lambda x: not ("rc" in x or x.endswith("pre")), repo.tags))
323
324 version = repo.rev2version(rev)
325 _call_nix_update("gitlab-container-registry", version)
326 if commit:
327 new_container_registry_version = get_container_registry_version()
328 commit_container_registry(
329 old_container_registry_version, new_container_registry_version
330 )
331
332
333@cli.command('update-gitlab-elasticsearch-indexer')
334def update_gitlab_elasticsearch_indexer():
335 """Update gitlab-elasticsearch-indexer"""
336 data = _get_data_json()
337 gitlab_elasticsearch_indexer_version = data['passthru']['GITLAB_ELASTICSEARCH_INDEXER_VERSION']
338 _call_nix_update('gitlab-elasticsearch-indexer', gitlab_elasticsearch_indexer_version)
339
340
341@cli.command("update-all")
342@click.option("--rev", default="latest", help="The rev to use (vX.Y.Z-ee), or 'latest'")
343@click.option(
344 "--commit", is_flag=True, default=False, help="Commit the changes for you"
345)
346@click.pass_context
347def update_all(ctx, rev: str, commit: bool):
348 """Update all gitlab components to the latest stable release"""
349 old_data_json = _get_data_json()
350 old_container_registry_version = get_container_registry_version()
351
352 ctx.invoke(update_data, rev=rev)
353
354 new_data_json = _get_data_json()
355
356 ctx.invoke(update_rubyenv)
357 ctx.invoke(update_gitaly)
358 ctx.invoke(update_gitlab_pages)
359 ctx.invoke(update_gitlab_shell)
360 ctx.invoke(update_gitlab_workhorse)
361 ctx.invoke(update_gitlab_elasticsearch_indexer)
362 if commit:
363 commit_gitlab(
364 old_data_json["version"], new_data_json["version"], new_data_json["rev"]
365 )
366
367 ctx.invoke(update_gitlab_container_registry)
368 if commit:
369 new_container_registry_version = get_container_registry_version()
370 commit_container_registry(
371 old_container_registry_version, new_container_registry_version
372 )
373
374
375def commit_gitlab(old_version: str, new_version: str, new_rev: str) -> None:
376 """Commits the gitlab changes for you"""
377 subprocess.run(
378 [
379 "git",
380 "add",
381 "pkgs/by-name/gi/gitlab",
382 "pkgs/by-name/gi/gitaly",
383 "pkgs/by-name/gi/gitlab-elasticsearch-indexer",
384 "pkgs/by-name/gi/gitlab-pages",
385 ],
386 cwd=NIXPKGS_PATH,
387 )
388 subprocess.run(
389 [
390 "git",
391 "commit",
392 "--message",
393 f"""gitlab: {old_version} -> {new_version}\n\nhttps://gitlab.com/gitlab-org/gitlab/-/blob/{new_rev}/CHANGELOG.md""",
394 ],
395 cwd=NIXPKGS_PATH,
396 )
397
398
399def commit_container_registry(old_version: str, new_version: str) -> None:
400 """Commits the gitlab-container-registry changes for you"""
401 subprocess.run(
402 [
403 "git",
404 "add",
405 "pkgs/by-name/gi/gitlab-container-registry"
406 ],
407 cwd=NIXPKGS_PATH,
408 )
409 subprocess.run(
410 [
411 "git",
412 "commit",
413 "--message",
414 f"gitlab-container-registry: {old_version} -> {new_version}\n\nhttps://gitlab.com/gitlab-org/container-registry/-/blob/v{new_version}-gitlab/CHANGELOG.md",
415 ],
416 cwd=NIXPKGS_PATH,
417 )
418
419
420if __name__ == "__main__":
421 cli()