Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
at release-19.03 249 lines 8.8 kB view raw
1#!/usr/bin/env nix-shell 2#! nix-shell -i python3 -p bundix common-updater-scripts nix nix-prefetch-git python3 python3Packages.requests python3Packages.lxml python3Packages.click python3Packages.click-log vgo2nix 3 4import click 5import click_log 6import os 7import re 8import logging 9import subprocess 10import json 11import pathlib 12from distutils.version import LooseVersion 13from typing import Iterable 14 15import requests 16from xml.etree import ElementTree 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, repo: str): 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 + "/tags?format=atom", stream=True) 34 35 tree = ElementTree.fromstring(r.content) 36 versions = [e.text for e in tree.findall('{http://www.w3.org/2005/Atom}entry/{http://www.w3.org/2005/Atom}title')] 37 # filter out versions not matching version_regex 38 versions = list(filter(self.version_regex.match, versions)) 39 40 # sort, but ignore v and -ee for sorting comparisons 41 versions.sort(key=lambda x: LooseVersion(x.replace("v", "").replace("-ee", "")), reverse=True) 42 return versions 43 44 def get_git_hash(self, rev: str): 45 out = subprocess.check_output(['nix-prefetch-git', self.url, rev]) 46 j = json.loads(out) 47 return j['sha256'] 48 49 def get_deb_url(self, flavour: str, version: str, arch: str = 'amd64') -> str: 50 """ 51 gitlab builds debian packages, which we currently need as we don't build the frontend on our own 52 this returns the url of a given flavour, version and arch 53 :param flavour: 'ce' or 'ee' 54 :param version: a version, without 'v' prefix and '-ee' suffix 55 :param arch: amd64 56 :return: url of the debian package 57 """ 58 if self.owner != "gitlab-org" or self.repo not in ['gitlab-ce', 'gitlab-ee']: 59 raise Exception(f"don't know how to get deb_url for {self.url}") 60 return f"https://packages.gitlab.com/gitlab/gitlab-{flavour}/packages" + \ 61 f"/debian/stretch/gitlab-{flavour}_{version}-{flavour}.0_{arch}.deb/download.deb" 62 63 def get_deb_hash(self, flavour: str, version: str) -> str: 64 out = subprocess.check_output(['nix-prefetch-url', self.get_deb_url(flavour, version)]) 65 return out.decode('utf-8').strip() 66 67 @staticmethod 68 def rev2version(tag: str) -> str: 69 """ 70 normalize a tag to a version number. 71 This obviously isn't very smart if we don't pass something that looks like a tag 72 :param tag: the tag to normalize 73 :return: a normalized version number 74 """ 75 # strip v prefix 76 version = re.sub(r"^v", '', tag) 77 # strip -ee suffix 78 return re.sub(r"-ee$", '', version) 79 80 def get_file(self, filepath, rev): 81 """ 82 returns file contents at a given rev 83 :param filepath: the path to the file, relative to the repo root 84 :param rev: the rev to fetch at 85 :return: 86 """ 87 return requests.get(self.url + f"/raw/{rev}/{filepath}").text 88 89 def get_data(self, rev, flavour): 90 version = self.rev2version(rev) 91 92 passthru = {v: self.get_file(v, rev).strip() for v in ['GITALY_SERVER_VERSION', 'GITLAB_PAGES_VERSION', 93 'GITLAB_SHELL_VERSION', 'GITLAB_WORKHORSE_VERSION']} 94 return dict(version=self.rev2version(rev), 95 repo_hash=self.get_git_hash(rev), 96 deb_hash=self.get_deb_hash(flavour, version), 97 deb_url=self.get_deb_url(flavour, version), 98 owner=self.owner, 99 repo=self.repo, 100 rev=rev, 101 passthru=passthru) 102 103 104def _flavour2gitlabrepo(flavour: str): 105 if flavour not in ['ce', 'ee']: 106 raise Exception(f"unknown gitlab flavour: {flavour}, needs to be ce or ee") 107 108 owner = 'gitlab-org' 109 repo = 'gitlab-' + flavour 110 111 return GitLabRepo(owner, repo) 112 113 114def _update_data_json(filename: str, repo: GitLabRepo, rev: str, flavour: str): 115 flavour_data = repo.get_data(rev, flavour) 116 117 if not os.path.exists(filename): 118 with open(filename, 'w') as f: 119 json.dump({flavour: flavour_data}, f, indent=2) 120 else: 121 with open(filename, 'r+') as f: 122 data = json.load(f) 123 data[flavour] = flavour_data 124 f.seek(0) 125 f.truncate() 126 json.dump(data, f, indent=2) 127 128 129def _get_data_json(): 130 data_file_path = pathlib.Path(__file__).parent / 'data.json' 131 with open(data_file_path, 'r') as f: 132 return json.load(f) 133 134 135def _call_update_source_version(pkg, version): 136 """calls update-source-version from nixpkgs root dir""" 137 nixpkgs_path = pathlib.Path(__file__).parent / '../../../../' 138 return subprocess.check_output(['update-source-version', pkg, version], cwd=nixpkgs_path) 139 140 141@click_log.simple_verbosity_option(logger) 142@click.group() 143def cli(): 144 pass 145 146 147@cli.command('update-data') 148@click.option('--rev', default='latest', help='The rev to use, \'latest\' points to the latest (stable) tag') 149@click.argument('flavour') 150def update_data(rev: str, flavour: str): 151 """Update data.nix for a selected flavour""" 152 r = _flavour2gitlabrepo(flavour) 153 154 if rev == 'latest': 155 # filter out pre and re releases 156 rev = next(filter(lambda x: not ('rc' in x or x.endswith('pre')), r.tags)) 157 logger.debug(f"Using rev {rev}") 158 159 version = r.rev2version(rev) 160 logger.debug(f"Using version {version}") 161 162 data_file_path = pathlib.Path(__file__).parent / 'data.json' 163 164 _update_data_json(filename=data_file_path.as_posix(), 165 repo=r, 166 rev=rev, 167 flavour=flavour) 168 169 170@cli.command('update-rubyenv') 171@click.argument('flavour') 172def update_rubyenv(flavour): 173 """Update rubyEnv-${flavour}""" 174 if flavour not in ['ce', 'ee']: 175 raise Exception(f"unknown gitlab flavour: {flavour}, needs to be ce or ee") 176 177 r = _flavour2gitlabrepo(flavour) 178 rubyenv_dir = pathlib.Path(__file__).parent / f"rubyEnv-{flavour}" 179 180 # load rev from data.json 181 data = _get_data_json() 182 rev = data[flavour]['rev'] 183 184 for fn in ['Gemfile.lock', 'Gemfile']: 185 with open(rubyenv_dir / fn, 'w') as f: 186 f.write(r.get_file(fn, rev)) 187 188 subprocess.check_output(['bundix'], cwd=rubyenv_dir) 189 190 191@cli.command('update-gitaly') 192def update_gitaly(): 193 """Update gitaly""" 194 data = _get_data_json() 195 gitaly_server_version = data['ce']['passthru']['GITALY_SERVER_VERSION'] 196 r = GitLabRepo('gitlab-org', 'gitaly') 197 gitaly_dir = pathlib.Path(__file__).parent / 'gitaly' 198 199 for fn in ['Gemfile.lock', 'Gemfile']: 200 with open(gitaly_dir / fn, 'w') as f: 201 f.write(r.get_file(f"ruby/{fn}", f"v{gitaly_server_version}")) 202 203 for fn in ['go.mod', 'go.sum']: 204 with open(gitaly_dir / fn, 'w') as f: 205 f.write(r.get_file(fn, f"v{gitaly_server_version}")) 206 207 subprocess.check_output(['bundix'], cwd=gitaly_dir) 208 subprocess.check_output(['vgo2nix'], cwd=gitaly_dir) 209 210 for fn in ['go.mod', 'go.sum']: 211 os.unlink(gitaly_dir / fn) 212 # currently broken, as `gitaly.meta.position` returns 213 # pkgs/development/go-modules/generic/default.nix 214 # so update-source-version doesn't know where to update hashes 215 # _call_update_source_version('gitaly', gitaly_server_version) 216 gitaly_hash = r.get_git_hash(f"v{gitaly_server_version}") 217 click.echo(f"Please update gitaly/default.nix to version {gitaly_server_version} and hash {gitaly_hash}") 218 219 220@cli.command('update-gitlab-shell') 221def update_gitlab_shell(): 222 """Update gitlab-shell""" 223 data = _get_data_json() 224 gitlab_shell_version = data['ce']['passthru']['GITLAB_SHELL_VERSION'] 225 _call_update_source_version('gitlab-shell', gitlab_shell_version) 226 227 228@cli.command('update-gitlab-workhorse') 229def update_gitlab_workhorse(): 230 """Update gitlab-shell""" 231 data = _get_data_json() 232 gitlab_workhorse_version = data['ce']['passthru']['GITLAB_WORKHORSE_VERSION'] 233 _call_update_source_version('gitlab-workhorse', gitlab_workhorse_version) 234 235 236@cli.command('update-all') 237@click.pass_context 238def update_all(ctx): 239 """Update gitlab ce and ee data.nix and rubyenvs to the latest stable release""" 240 for flavour in ['ce', 'ee']: 241 ctx.invoke(update_data, rev='latest', flavour=flavour) 242 ctx.invoke(update_rubyenv, flavour=flavour) 243 ctx.invoke(update_gitaly) 244 ctx.invoke(update_gitlab_shell) 245 ctx.invoke(update_gitlab_workhorse) 246 247 248if __name__ == '__main__': 249 cli()