#!/usr/bin/env nix-shell #! nix-shell -I nixpkgs=../../../.. -i python3 -p bundix bundler nix-update nix nix-universal-prefetch python3 python3Packages.requests python3Packages.click python3Packages.click-log python3Packages.packaging prefetch-yarn-deps import click import click_log import os import re import logging import subprocess import json import pathlib import tempfile from packaging.version import Version from typing import Iterable import requests # Always keep this in sync with the GitLaab version you're updating to. # If you see any errors about vendored dependencies during an update, check the Gemfile. VENDORED_GEMS = [ "bundler-checksum", "devise-pbkdf2-encryptable", "omniauth-azure-oauth2", "omniauth-cas3", "omniauth-gitlab", "omniauth-google-oauth2", "omniauth_crowd", "omniauth-salesforce", "mail-smtp_pool", "microsoft_graph_mailer", "ipynbdiff", "error_tracking_open_api", ] logger = logging.getLogger(__name__) class GitLabRepo: version_regex = re.compile(r"^v\d+\.\d+\.\d+(\-rc\d+)?(\-ee)?") def __init__(self, owner: str = 'gitlab-org', repo: str = 'gitlab'): self.owner = owner self.repo = repo @property def url(self): return f"https://gitlab.com/{self.owner}/{self.repo}" @property def tags(self) -> Iterable[str]: r = requests.get(self.url + "/refs?sort=updated_desc&ref=master").json() tags = r.get("Tags", []) # filter out versions not matching version_regex versions = list(filter(self.version_regex.match, tags)) # sort, but ignore v and -ee for sorting comparisons versions.sort(key=lambda x: Version(x.replace("v", "").replace("-ee", "")), reverse=True) return versions def get_git_hash(self, rev: str): return subprocess.check_output(['nix-universal-prefetch', 'fetchFromGitLab', '--owner', self.owner, '--repo', self.repo, '--rev', rev]).decode('utf-8').strip() def get_yarn_hash(self, rev: str): with tempfile.TemporaryDirectory() as tmp_dir: with open(tmp_dir + '/yarn.lock', 'w') as f: f.write(self.get_file('yarn.lock', rev)) return subprocess.check_output(['prefetch-yarn-deps', tmp_dir + '/yarn.lock']).decode('utf-8').strip() @staticmethod def rev2version(tag: str) -> str: """ normalize a tag to a version number. This obviously isn't very smart if we don't pass something that looks like a tag :param tag: the tag to normalize :return: a normalized version number """ # strip v prefix version = re.sub(r"^v", '', tag) # strip -ee suffix return re.sub(r"-ee$", '', version) def get_file(self, filepath, rev): """ returns file contents at a given rev :param filepath: the path to the file, relative to the repo root :param rev: the rev to fetch at :return: """ return requests.get(self.url + f"/raw/{rev}/{filepath}").text def get_data(self, rev): version = self.rev2version(rev) passthru = {v: self.get_file(v, rev).strip() for v in ['GITALY_SERVER_VERSION', 'GITLAB_PAGES_VERSION', 'GITLAB_SHELL_VERSION']} passthru["GITLAB_WORKHORSE_VERSION"] = version return dict(version=self.rev2version(rev), repo_hash=self.get_git_hash(rev), yarn_hash=self.get_yarn_hash(rev), owner=self.owner, repo=self.repo, rev=rev, passthru=passthru, vendored_gems=VENDORED_GEMS) def _get_data_json(): data_file_path = pathlib.Path(__file__).parent / 'data.json' with open(data_file_path, 'r') as f: return json.load(f) def _call_nix_update(pkg, version): """calls nix-update from nixpkgs root dir""" nixpkgs_path = pathlib.Path(__file__).parent / '../../../../' return subprocess.check_output(['nix-update', pkg, '--version', version], cwd=nixpkgs_path) @click_log.simple_verbosity_option(logger) @click.group() def cli(): pass @cli.command('update-data') @click.option('--rev', default='latest', help='The rev to use (vX.Y.Z-ee), or \'latest\'') def update_data(rev: str): """Update data.nix""" repo = GitLabRepo() if rev == 'latest': # filter out pre and re releases rev = next(filter(lambda x: not ('rc' in x or x.endswith('pre')), repo.tags)) logger.debug(f"Using rev {rev}") version = repo.rev2version(rev) logger.debug(f"Using version {version}") data_file_path = pathlib.Path(__file__).parent / 'data.json' data = repo.get_data(rev) with open(data_file_path.as_posix(), 'w') as f: json.dump(data, f, indent=2) f.write("\n") @cli.command('update-rubyenv') def update_rubyenv(): """Update rubyEnv""" repo = GitLabRepo() rubyenv_dir = pathlib.Path(__file__).parent / f"rubyEnv" # load rev from data.json data = _get_data_json() rev = data['rev'] gemfile = repo.get_file('Gemfile', rev) gemfile_lock = repo.get_file('Gemfile.lock', rev) if "pg (1.4.1)" in gemfile_lock: gemfile_lock = gemfile_lock.replace("pg (1.4.1)", "pg (1.4.3)") else: logger.info("Looks like pg was updated! Please remove update-pg.patch, as this will cause a build failure") with open(rubyenv_dir / 'Gemfile', 'w') as f: f.write(re.sub(f'.*({"|".join(VENDORED_GEMS)}).*', "", gemfile)) with open(rubyenv_dir / 'Gemfile.lock', 'w') as f: f.write(gemfile_lock) subprocess.check_output(['bundle', 'lock'], cwd=rubyenv_dir) subprocess.check_output(['bundix'], cwd=rubyenv_dir) with open(rubyenv_dir / 'Gemfile', 'w') as f: for gem in VENDORED_GEMS: gemfile = gemfile.replace(f'path: \'vendor/gems/{gem}\'', f'path: \'{gem}\'') f.write(gemfile) with open(rubyenv_dir / 'Gemfile.lock', 'w') as f: for gem in VENDORED_GEMS: gemfile_lock = gemfile_lock.replace(f'remote: vendor/gems/{gem}', f'remote: {gem}') f.write(gemfile_lock) @cli.command('update-gitaly') def update_gitaly(): """Update gitaly""" data = _get_data_json() gitaly_server_version = data['passthru']['GITALY_SERVER_VERSION'] repo = GitLabRepo(repo='gitaly') gitaly_dir = pathlib.Path(__file__).parent / 'gitaly' for fn in ['Gemfile.lock', 'Gemfile']: with open(gitaly_dir / fn, 'w') as f: f.write(repo.get_file(f"ruby/{fn}", f"v{gitaly_server_version}")) subprocess.check_output(['bundle', 'lock'], cwd=gitaly_dir) subprocess.check_output(['bundix'], cwd=gitaly_dir) _call_nix_update('gitaly', gitaly_server_version) @cli.command('update-gitlab-shell') def update_gitlab_shell(): """Update gitlab-shell""" data = _get_data_json() gitlab_shell_version = data['passthru']['GITLAB_SHELL_VERSION'] _call_nix_update('gitlab-shell', gitlab_shell_version) @cli.command('update-gitlab-workhorse') def update_gitlab_workhorse(): """Update gitlab-workhorse""" data = _get_data_json() gitlab_workhorse_version = data['passthru']['GITLAB_WORKHORSE_VERSION'] _call_nix_update('gitlab-workhorse', gitlab_workhorse_version) @cli.command('update-all') @click.option('--rev', default='latest', help='The rev to use (vX.Y.Z-ee), or \'latest\'') @click.pass_context def update_all(ctx, rev: str): """Update all gitlab components to the latest stable release""" ctx.invoke(update_data, rev=rev) ctx.invoke(update_rubyenv) ctx.invoke(update_gitaly) ctx.invoke(update_gitlab_shell) ctx.invoke(update_gitlab_workhorse) if __name__ == '__main__': cli()