#!/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()