1#!/usr/bin/env nix-shell
2#! nix-shell -I nixpkgs=../../../.. -i python3 -p bundix bundler nix-update nix nix-universal-prefetch python3 python3Packages.requests python3Packages.click python3Packages.click-log prefetch-yarn-deps
3
4import click
5import click_log
6import os
7import re
8import logging
9import subprocess
10import json
11import pathlib
12import tempfile
13from distutils.version import LooseVersion
14from typing import Iterable
15
16import requests
17
18logger = logging.getLogger(__name__)
19
20
21class GitLabRepo:
22 version_regex = re.compile(r"^v\d+\.\d+\.\d+(\-rc\d+)?(\-ee)?")
23 def __init__(self, owner: str = 'gitlab-org', repo: str = 'gitlab'):
24 self.owner = owner
25 self.repo = repo
26
27 @property
28 def url(self):
29 return f"https://gitlab.com/{self.owner}/{self.repo}"
30
31 @property
32 def tags(self) -> Iterable[str]:
33 r = requests.get(self.url + "/refs?sort=updated_desc&ref=master").json()
34 tags = r.get("Tags", [])
35
36 # filter out versions not matching version_regex
37 versions = list(filter(self.version_regex.match, tags))
38
39 # sort, but ignore v and -ee for sorting comparisons
40 versions.sort(key=lambda x: LooseVersion(x.replace("v", "").replace("-ee", "")), reverse=True)
41 return versions
42
43 def get_git_hash(self, rev: str):
44 return subprocess.check_output(['nix-universal-prefetch', 'fetchFromGitLab', '--owner', self.owner, '--repo', self.repo, '--rev', rev]).decode('utf-8').strip()
45
46 def get_yarn_hash(self, rev: str):
47 with tempfile.TemporaryDirectory() as tmp_dir:
48 with open(tmp_dir + '/yarn.lock', 'w') as f:
49 f.write(self.get_file('yarn.lock', rev))
50 return subprocess.check_output(['prefetch-yarn-deps', tmp_dir + '/yarn.lock']).decode('utf-8').strip()
51
52 @staticmethod
53 def rev2version(tag: str) -> str:
54 """
55 normalize a tag to a version number.
56 This obviously isn't very smart if we don't pass something that looks like a tag
57 :param tag: the tag to normalize
58 :return: a normalized version number
59 """
60 # strip v prefix
61 version = re.sub(r"^v", '', tag)
62 # strip -ee suffix
63 return re.sub(r"-ee$", '', version)
64
65 def get_file(self, filepath, rev):
66 """
67 returns file contents at a given rev
68 :param filepath: the path to the file, relative to the repo root
69 :param rev: the rev to fetch at
70 :return:
71 """
72 return requests.get(self.url + f"/raw/{rev}/{filepath}").text
73
74 def get_data(self, rev):
75 version = self.rev2version(rev)
76
77 passthru = {v: self.get_file(v, rev).strip() for v in ['GITALY_SERVER_VERSION', 'GITLAB_PAGES_VERSION',
78 'GITLAB_SHELL_VERSION']}
79
80 passthru["GITLAB_WORKHORSE_VERSION"] = version
81
82 return dict(version=self.rev2version(rev),
83 repo_hash=self.get_git_hash(rev),
84 yarn_hash=self.get_yarn_hash(rev),
85 owner=self.owner,
86 repo=self.repo,
87 rev=rev,
88 passthru=passthru)
89
90
91def _get_data_json():
92 data_file_path = pathlib.Path(__file__).parent / 'data.json'
93 with open(data_file_path, 'r') as f:
94 return json.load(f)
95
96
97def _call_nix_update(pkg, version):
98 """calls nix-update from nixpkgs root dir"""
99 nixpkgs_path = pathlib.Path(__file__).parent / '../../../../'
100 return subprocess.check_output(['nix-update', pkg, '--version', version], cwd=nixpkgs_path)
101
102
103@click_log.simple_verbosity_option(logger)
104@click.group()
105def cli():
106 pass
107
108
109@cli.command('update-data')
110@click.option('--rev', default='latest', help='The rev to use (vX.Y.Z-ee), or \'latest\'')
111def update_data(rev: str):
112 """Update data.nix"""
113 repo = GitLabRepo()
114
115 if rev == 'latest':
116 # filter out pre and re releases
117 rev = next(filter(lambda x: not ('rc' in x or x.endswith('pre')), repo.tags))
118 logger.debug(f"Using rev {rev}")
119
120 version = repo.rev2version(rev)
121 logger.debug(f"Using version {version}")
122
123 data_file_path = pathlib.Path(__file__).parent / 'data.json'
124
125 data = repo.get_data(rev)
126
127 with open(data_file_path.as_posix(), 'w') as f:
128 json.dump(data, f, indent=2)
129 f.write("\n")
130
131
132@cli.command('update-rubyenv')
133def update_rubyenv():
134 """Update rubyEnv"""
135 repo = GitLabRepo()
136 rubyenv_dir = pathlib.Path(__file__).parent / f"rubyEnv"
137
138 # load rev from data.json
139 data = _get_data_json()
140 rev = data['rev']
141
142 with open(rubyenv_dir / 'Gemfile.lock', 'w') as f:
143 f.write(repo.get_file('Gemfile.lock', rev))
144 with open(rubyenv_dir / 'Gemfile', 'w') as f:
145 original = repo.get_file('Gemfile', rev)
146 original += "\ngem 'sd_notify'\n"
147 f.write(re.sub(r".*mail-smtp_pool.*", "", original))
148
149 subprocess.check_output(['bundle', 'lock'], cwd=rubyenv_dir)
150 subprocess.check_output(['bundix'], cwd=rubyenv_dir)
151
152
153@cli.command('update-gitaly')
154def update_gitaly():
155 """Update gitaly"""
156 data = _get_data_json()
157 gitaly_server_version = data['passthru']['GITALY_SERVER_VERSION']
158 repo = GitLabRepo(repo='gitaly')
159 gitaly_dir = pathlib.Path(__file__).parent / 'gitaly'
160
161 for fn in ['Gemfile.lock', 'Gemfile']:
162 with open(gitaly_dir / fn, 'w') as f:
163 f.write(repo.get_file(f"ruby/{fn}", f"v{gitaly_server_version}"))
164
165 subprocess.check_output(['bundle', 'lock'], cwd=gitaly_dir)
166 subprocess.check_output(['bundix'], cwd=gitaly_dir)
167
168 _call_nix_update('gitaly', gitaly_server_version)
169
170
171@cli.command('update-gitlab-shell')
172def update_gitlab_shell():
173 """Update gitlab-shell"""
174 data = _get_data_json()
175 gitlab_shell_version = data['passthru']['GITLAB_SHELL_VERSION']
176 _call_nix_update('gitlab-shell', gitlab_shell_version)
177
178
179@cli.command('update-gitlab-workhorse')
180def update_gitlab_workhorse():
181 """Update gitlab-workhorse"""
182 data = _get_data_json()
183 gitlab_workhorse_version = data['passthru']['GITLAB_WORKHORSE_VERSION']
184 _call_nix_update('gitlab-workhorse', gitlab_workhorse_version)
185
186
187@cli.command('update-all')
188@click.option('--rev', default='latest', help='The rev to use (vX.Y.Z-ee), or \'latest\'')
189@click.pass_context
190def update_all(ctx, rev: str):
191 """Update all gitlab components to the latest stable release"""
192 ctx.invoke(update_data, rev=rev)
193 ctx.invoke(update_rubyenv)
194 ctx.invoke(update_gitaly)
195 ctx.invoke(update_gitlab_shell)
196 ctx.invoke(update_gitlab_workhorse)
197
198
199if __name__ == '__main__':
200 cli()