summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README16
-rwxr-xr-xscripts/validate-lorries176
2 files changed, 192 insertions, 0 deletions
diff --git a/README b/README
index 97edebf..e7d568e 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/scripts/validate-lorries b/scripts/validate-lorries
new file mode 100755
index 0000000..0d1d880
--- /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()