import datetime from yaml import load, SafeLoader from os import environ, makedirs, unlink from os.path import expandvars, isfile, isdir, dirname, join from re import search from shutil import move from glob import glob from tempfile import TemporaryDirectory from subprocess import run from dulwich.porcelain import init, clone, add, commit, push, pull from revprox import Authentication from mookdns import MookDnsSystem PORT = '3000' FORGEJO_USERNAME = 'git' FORGEJO_PORT = '2222' KEY_FILE = '/var/lib/risotto/srv/hosts/forgejo' # transition between gitea and forgejo GITEA_KEY_FILE = '/var/lib/risotto/srv/hosts/gitea' CONFIG_SSH = expandvars('$HOME/.ssh/config') CONFIG_GIT = expandvars('$HOME/.gitconfig') CONFIG_KNOWN_HOST = expandvars('$HOME/.ssh/known_hosts') AUTHENTICATION = None DATA = None def get_data(): global DATA if not DATA: conf_file = f'{environ["MACHINE_TEST_DIR"]}/forgejo.yml' with open(conf_file) as yaml: DATA = load(yaml, Loader=SafeLoader) return DATA def get_authentication(data): global AUTHENTICATION if not AUTHENTICATION: AUTHENTICATION = Authentication(data['auth_url'], data['auth_server'], data['revprox_ip'], data['username'], data['password'], # f'{data["username"]} - Tableau de bord - {data["forgejo_title"]}', f'{data["username"]} - Dashboard - {data["forgejo_title"]}', ) return AUTHENTICATION class SSHConfig: def __enter__(self): self.old_file = '{CONFIG_SSH}.old' if isfile(CONFIG_SSH) and not isfile(self.old_file): move(CONFIG_SSH, self.old_file) with open(CONFIG_SSH, 'w') as fh: fh.write(f"""Host * User forgejo PubkeyAcceptedKeyTypes +ssh-rsa StrictHostKeyChecking no IdentityFile {KEY_FILE} """) def __exit__(self, *args): if isfile(self.old_file): move(self.old_file, CONFIG_SSH) else: unlink(CONFIG_SSH) class GITConfig: def __enter__(self): self.old_file = '{CONFIG_GIT}.old' if isfile(CONFIG_GIT) and not isfile(self.old_file): move(CONFIG_GIT, self.old_file) with open(CONFIG_GIT, 'w') as fh: conf_file = f'{environ["MACHINE_TEST_DIR"]}/reverse-proxy.yml' with open(conf_file) as yaml: data = load(yaml, Loader=SafeLoader) path = join(environ["MACHINE_TEST_DIR"], data["ca_certificate"]) cert = glob(path) fh.write(f"""[http] sslCAInfo = {cert[0]} """) def __exit__(self, *args): if isfile(self.old_file): move(self.old_file, CONFIG_GIT) else: unlink(CONFIG_GIT) def get_info(authentication, url, with_uid=False, with_data_id=False, found_string=None ): pattern_csrf = r'name="_csrf" value="([a-zA-Z0-9\-\_=]+)"' ret = authentication.get(url) csrf = search(pattern_csrf, ret)[1] ret_data = [] if with_uid: pattern_uid = r'input type="hidden" id="uid" name="uid" value="(\d)+"' uid = search(pattern_uid, ret) if uid is None: ret_data.append(uid) else: ret_data.append(uid[1]) if with_data_id: pattern_uid = r'/user/settings/keys/delete?type=ssh" data-id="(\d)+"' uid = search(pattern_uid, ret) if uid is None: ret_data.append(uid) else: ret_data.append(uid[1]) if found_string: ret_data.append(found_string in ret) ret_data.append(csrf) if len(ret_data) == 1: return ret_data[0] return ret_data def add_ssh_key(authentication, data): # Send key to forgejo url = f'{data["base_url"]}user/settings/keys' is_already_key, csrf = get_info(authentication, url, found_string='test_key_risotto') if is_already_key: return # Gen SSH key if needed if not isfile(KEY_FILE): key_dir = dirname(KEY_FILE) if not isdir(key_dir): makedirs(key_dir) cmd = ['/usr/bin/ssh-keygen', '-t', 'rsa', '-N', '', '-f', KEY_FILE] run(cmd) with open(f'{KEY_FILE}.pub') as fh: key = fh.read() authentication.post(url, {'_csrf': csrf, 'title': 'test_key_risotto', 'content': key, 'type': 'ssh'}) def delete_ssh_key(authentication, data): url = f'{data["base_url"]}user/settings/keys' is_already_key, csrf = get_info(authentication, url, found_string='test_key_risotto') if is_already_key: uid, csrf = get_info(authentication, url, with_data_id=True) url = f'{data["base_url"]}user/settings/keys/delete?type=ssh' authentication.post(url, {'_csrf': csrf, 'id': uid}) is_already_key, csrf = get_info(authentication, url, found_string='test_key_risotto') def test_forgejo(): data = get_data() get_authentication(data) def test_forgejo_repos(): data = get_data() authentication = get_authentication(data) if 'FIRST_RUN' in environ: url = f'{data["base_url"]}repo/create' uid, csrf = get_info(authentication, url, with_uid=True) authentication.post(url, {'_csrf': csrf, 'uid': uid, 'repo_name': 'test_persistent'}) url = f'{data["base_url"]}api/v1/repos/search?sort=updated&order=desc&uid=1&team_id=0&q=&page=1&mode=' json = authentication.get(url, json=True) assert json['ok'] assert len(json['data']) == 1 username = data['username'].split('@', 1)[0] assert json['data'][0]['full_name'] == f'{username}/test_persistent' def test_forgejo_create_repo(): data = get_data() authentication = get_authentication(data) url = f'{data["base_url"]}repo/create' uid, csrf = get_info(authentication, url, with_uid=True) authentication.post(url, {'_csrf': csrf, 'uid': uid, 'repo_name': 'test', 'default_branch': 'main'}) url = f'{data["base_url"]}api/v1/repos/search?sort=updated&order=desc&uid=1&team_id=0&q=&page=1&mode=' json = authentication.get(url, json=True) assert json['ok'] assert len(json['data']) == 2 username = data['username'].split('@', 1)[0] assert {dat['full_name'] for dat in json['data']} == set([f'{username}/test_persistent', f'{username}/test']) def test_repo(): data = get_data() authentication = get_authentication(data) if 'FIRST_RUN' in environ: # delete_ssh_key(authentication, data) add_ssh_key(authentication, data) cmd = ['/usr/bin/ssh-keygen', '-f', CONFIG_KNOWN_HOST, '-R', data['git_url']] run(cmd) if not isfile(KEY_FILE): if isfile(GITEA_KEY_FILE): move(GITEA_KEY_FILE, KEY_FILE) move(GITEA_KEY_FILE + '.pub', KEY_FILE + '.pub') else: raise Exception(f'cannot find ssh key "{KEY_FILE}", do you run with FIRST_RUN?') with TemporaryDirectory() as tmpdirname: username = data['username'].split('@', 1)[0] dns = data['base_url'].split('/', 3)[2] ssh_url = f'ssh://{FORGEJO_USERNAME}@{dns}:{FORGEJO_PORT}/{username}/test.git' with SSHConfig(): with MookDnsSystem(dns, data['ip']): filename = join(tmpdirname, 'test.txt') with open(filename, 'w') as fh: fh.write('test') repo = init(tmpdirname) add(repo, filename) commit(repo, message=b'test commit') push(repo=repo, remote_location=ssh_url, refspecs='master', ) lst = list(repo.get_walker()) assert len(lst) == 1 assert lst[0].commit.message == b'test commit' def test_clone_http(): data = get_data() authentication = get_authentication(data) if 'FIRST_RUN' in environ: # delete_ssh_key(authentication, data) add_ssh_key(authentication, data) with TemporaryDirectory() as tmpdirname: username = data['username'].split('@', 1)[0] dns = data['base_url'].split('/', 3)[2] http_url = f'{data["base_url"]}{username}/test.git' with SSHConfig(): with MookDnsSystem(dns, data['revprox_ip']): try: repo = clone(http_url, tmpdirname) except: with GITConfig(): repo = clone(http_url, tmpdirname) lst = list(repo.get_walker()) assert len(lst) == 1 assert lst[0].commit.message == b'test commit' def test_forgejo_delete_repo(): repo_name = 'test' data = get_data() authentication = get_authentication(data) username = data['username'].split('@', 1)[0] url = f'{data["base_url"]}{username}/{repo_name}/settings' csrf = get_info(authentication, url) authentication.post(url, {'_csrf': csrf, 'action': 'delete', 'repo_name': repo_name}) url = f'{data["base_url"]}api/v1/repos/search?sort=updated&order=desc&uid=1&team_id=0&q=&page=1&mode=' json = authentication.get(url, json=True) assert json['ok'] assert len(json['data']) == 1 username = data['username'].split('@', 1)[0] assert json['data'][0]['full_name'] == f'{username}/test_persistent' def test_repo_persistent(): data = get_data() authentication = get_authentication(data) if 'FIRST_RUN' in environ: # delete_ssh_key(authentication, data) add_ssh_key(authentication, data) with TemporaryDirectory() as tmpdirname: username = data['username'].split('@', 1)[0] dns = data['base_url'].split('/', 3)[2] ssh_url = f'ssh://{FORGEJO_USERNAME}@{dns}:{FORGEJO_PORT}/{username}/test_persistent.git' with SSHConfig(): with MookDnsSystem(dns, data['ip']): filename = join(tmpdirname, 'test.txt') if 'FIRST_RUN' in environ: with open(filename, 'w') as fh: fh.write('test') repo = init(tmpdirname) add(repo, filename) commit(repo, message=b'test commit') push(repo=repo, remote_location=ssh_url, refspecs='master', ) else: repo = clone(ssh_url, tmpdirname) with open(filename, 'r') as fh: len_file = len(fh.readlines()) # get previous commit number lst = list(repo.get_walker()) len_before_commit = len(lst) assert len_before_commit == len_file # add a new line in file and commit with open(filename, 'a') as fh: fh.write('\ntest') with open(filename, 'r') as fh: len_line = len(fh.read().split('\n')) add(repo, filename) date = datetime.datetime.now() commit_message = f'test commit {date}'.encode() commit(repo, message=commit_message) push(repo=repo, remote_location=ssh_url, refspecs='master', ) # test if commit is added and last commit pull(repo=repo, remote_location=ssh_url, refspecs='master', ) lst = list(repo.get_walker()) len_after_commit = len(lst) assert len_after_commit == len_line assert len_before_commit + 1 == len_after_commit assert lst[0].commit.message == commit_message