diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..6bb0e3d80cc43f8a5f104778a1008c61f16e00b7 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,9 @@ +image: debian:buster + +before_script: +- apt-get update -y +- apt-get install -y python3 python3-yaml + +run-validate: + script: + - scripts/validate-lorries diff --git a/README b/README index 97edebf6d5d5013c7d7a88b43b9a2932bb8aea5d..e7d568e27062b784ce2c5f85b966c2e863213431 100644 --- a/README +++ b/README @@ -16,3 +16,19 @@ add any additional configuration to this repository. Remember, the Lorry tool is not permitted to manage repositories inside your prefix which is baserock. +Validation +---------- + +Run `scripts/validate-lorries` to check the `.lorry` files. This will +report: + +* Invalid YAML/JSON syntax +* Wrong data types +* Duplicate repository names +* Missing required keys +* Unexpected keys +* Invalid values + +The syntax and duplicate name checks are applied even to disabled +`.lorry` files, because reusing a repository name for a different +upstream type generally won't work. diff --git a/open-source-lorries-disabled/boost.lorry b/open-source-lorries-disabled/boost.lorry deleted file mode 100644 index 2a0e4d2a95b9594b3b7551c83ebf1521b8373f9b..0000000000000000000000000000000000000000 --- a/open-source-lorries-disabled/boost.lorry +++ /dev/null @@ -1,7 +0,0 @@ -{ - "boost": { - "type": "svn", - "url": "http://svn.boost.org/svn/boost/", - "layout": "standard" - } -} diff --git a/open-source-lorries/libmicrohttpd.lorry b/open-source-lorries/libmicrohttpd.lorry deleted file mode 100644 index 45b847e4647f843d91a3f82af514b6dc51ebd3e7..0000000000000000000000000000000000000000 --- a/open-source-lorries/libmicrohttpd.lorry +++ /dev/null @@ -1,7 +0,0 @@ -{ - "coreutils": { - "type": "svn", - "url": "https://gnunet.org/svn/libmicrohttpd", - "layout": "standard" - } -} diff --git a/scripts/validate-lorries b/scripts/validate-lorries new file mode 100755 index 0000000000000000000000000000000000000000..0d1d880d5a3b9a3051039009bbc46ab09e00cb01 --- /dev/null +++ b/scripts/validate-lorries @@ -0,0 +1,176 @@ +#!/usr/bin/python3 + +import glob +import json +import sys +import yaml + + +def check_string(v): + if not isinstance(v, str): + return 'must be a string' + + +def check_bool(v): + if not isinstance(v, bool): + return 'must be a boolean' + + +def check_type_string(v): + err = check_string(v) + if err is not None: + return err + + if v not in ['bzr', 'cvs', 'git', 'gzip', 'hg', 'svn', 'tarball', 'zip']: + return '"%s" is not a recognised type' % v + + +def check_stringlist(v): + if not isinstance(v, list): + return 'must be a list' + + for elem in v: + if not isinstance(elem, str): + return 'must have strings as elements' + + +def check_stringdict(v): + if not isinstance(v, dict): + return 'must be a dictionary' + + for key, value in v.items(): + if not (isinstance(key, str) and isinstance(value, str)): + return 'must have strings as keys and value' + + +def check_svn_layout(v): + if v == 'standard': + return + + if not isinstance(v, dict): + return 'must be either "standard" or a dictionary' + + return check_stringdict(v) + + +def validate(filename, repo_filenames, strict=True): + is_ok = True + repo_name = None + + def diagnostic(level, msg): + if repo_name is None: + print('%s: %s: %s' % (level, filename, msg), + file=sys.stderr) + else: + print('%s: %s: %s: %s' % (level, filename, repo_name, msg), + file=sys.stderr) + + def error(msg): + nonlocal is_ok + is_ok = False + diagnostic('E', msg) + + def warning(msg): + diagnostic('W', msg) + + with open(filename) as f: + try: + try: + obj = yaml.safe_load(f) + except yaml.YAMLError: + f.seek(0) + obj = json.load(f) + except ValueError: + error('not valid YAML or JSON') + return is_ok + + if not isinstance(obj, dict): + error('must be a dictionary') + return is_ok + + for repo_name, upstream_def in obj.items(): + if repo_name in repo_filenames: + error('repository already defined in %s' + % repo_filenames[repo_name]) + else: + repo_filenames[repo_name] = filename + if not strict: + continue + + upstream_type = upstream_def.get('type') + + value_checkers = { + # Keys listed in Lorry's README + 'type': check_type_string, + 'url': check_string, + 'check-certificates': check_bool, + 'branches': check_stringdict, + 'layout': check_svn_layout, + 'module': check_string, + # Undocumented Lorry feature + 'refspecs': check_stringlist, + # Lorry Controller extension + 'description': check_string, + } + + required_keys = set(['type']) + optional_keys = set(['refspecs', 'description']) + if upstream_type != 'bzr': + required_keys.add('url') + else: + optional_keys.add('url') + optional_keys.add('branches') + if upstream_type in ['bzr', 'git', 'hg']: + optional_keys.add('check-certificates') + if upstream_type == 'svn': + required_keys.add('layout') + if upstream_type == 'cvs': + required_keys.add('module') + + for key in required_keys: + if key not in upstream_def: + error('missing "%s" key' % key) + + # For bzr, exactly one of url and branches keys is required + if upstream_type == 'bzr': + has_url = 'url' in upstream_def + has_branches = 'branches' in upstream_def + if has_url and has_branches: + error('has both "url" and "branches" keys') + elif not has_url and not has_branches: + error('missing both "url" and "branches" keys') + + for key, value in upstream_def.items(): + if key.startswith('x-products-'): + # Baserock Import extension + msg = check_stringlist(value) + else: + if key not in required_keys and key not in optional_keys: + warning('unexpected "%s" key' % key) + if key in value_checkers: + msg = value_checkers[key](value) + else: + msg = None + if msg: + error('%s: %s' % (key, msg)) + + return is_ok + + +def main(): + repo_filenames = {} + all_ok = True + + for filename in glob.glob('*-lorries/*.lorry'): + if not validate(filename, repo_filenames): + all_ok = False + + for filename in glob.glob('*-lorries-disabled/*.lorry'): + if not validate(filename, repo_filenames, strict=False): + all_ok = False + + sys.exit(0 if all_ok else 1) + + +if __name__ == '__main__': + main()