blob: 88786d9fc3ab2775d8bf70014cc3cd758c28fb58 [file] [log] [blame]
#!/usr/bin/env python
"""
Those tests checks the following requirements for the `projects.yaml` file:
- Its syntax is valid
- Each project definition should consist of the following mandatory parts:
* project
* description
and could contain the following optional parts:
* acl-config
* upstream
No other parts are possible.
- All the projects listed in the `projects.yaml` file
must be sorted alphabetically.
"""
import logging
import os
import sys
import git
import jsonschema
import yaml
logging.basicConfig(level=logging.INFO)
# Only lower case letters (a-z), digits (0-9), plus (+) and minus (-)
# and periods (.).
# They must be at least two characters long and must start with an
# alphanumeric character.
# https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Source
# Additionally allow using underline symbol (required by openstack projects)
_PREFIX_PATTERN = '\A([a-z]([a-z]|\d|-)+/)*'
_NAMES_PATTERN = '([a-zA-Z]|\d)([a-zA-Z]|\d|[+-_.])+\Z'
PROJECT_NAME_PATTERN = _PREFIX_PATTERN + _NAMES_PATTERN
PROJECT_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "array",
"items": {
"type": "object",
"additionalProperties": False,
"properties": {
"project": {
"type": "string",
"pattern": PROJECT_NAME_PATTERN
},
"description": {
"type": "string"
},
"upstream": {
"type": "string"
},
"acl-config": {
"type": "string"
}
},
"required": ["project", "description"]
}
}
def parse_yaml_file(file_path):
try:
data = yaml.safe_load(open(file_path))
if data is None:
logging.error("File {0} is empty".format(file_path))
sys.exit(1)
return data
except yaml.YAMLError as exc:
msg = "File {0} could not be parsed: {1}".format(file_path, exc)
logging.error(msg)
sys.exit(1)
def validate_data_by_schema(data, file_path):
try:
jsonschema.validate(data, PROJECT_SCHEMA)
except jsonschema.exceptions.ValidationError as exc:
raise ValueError(_make_error_message(exc, file_path))
def _make_error_message(exc, file_path):
value_path = []
if exc.absolute_path:
value_path.extend(exc.absolute_path)
error_msg = "File '{0}', {1}".format(file_path, exc.message)
if value_path:
value_path = ' -> '.join(map(str, value_path))
error_msg = '{0}, value path {1}'.format(error_msg, value_path)
return error_msg
def check_duplicate_projects(data):
projects_items = []
for item in data:
if item['project'] not in projects_items:
projects_items.append(item['project'])
else:
msg = "Project '{0}' is duplicated".format(item['project'])
raise ValueError(msg)
def check_alphabetical_order(data):
for i in range(len(data) - 1):
if not data[i]['project'] < data[i + 1]['project']:
msg = ("Alphabetical order violation: project '{0}' must be "
"placed after '{1}'".format(data[i]['project'],
data[i + 1]['project']))
raise ValueError(msg)
def check_acls_config_path(data):
valid = True
for item in data:
acl_config_path = item.get('acl-config')
if not acl_config_path:
continue
# Allow to skip acls/ prefix in acl-config
if acl_config_path[:4] != 'acls':
acl_config_path = os.path.join('acls', acl_config_path)
config_path = os.path.join(os.path.abspath(os.curdir),
acl_config_path)
if not os.path.isfile(config_path):
logging.error("Config file for project '{0}' is not found "
"at {1}.".format(item.get('project'), config_path))
valid = False
if not valid:
sys.exit(1)
def check_upstream_clonable(data):
clonable = True
for item in data:
upstream_repo = item.get('upstream')
if not upstream_repo:
continue
logging.info("Checking availability: {0}".format(upstream_repo))
try:
g = git.cmd.Git()
g.ls_remote(upstream_repo)
except git.exc.GitCommandError as e:
err_msg = ("Unable to clone '{0}':"
"{1}".format(upstream_repo, str(e)))
logging.error(err_msg)
clonable = False
if not clonable:
sys.exit(1)
def run_checks(file_to_check):
data = parse_yaml_file(file_to_check)
validate_data_by_schema(data, file_to_check)
check_duplicate_projects(data)
check_alphabetical_order(data)
check_acls_config_path(data)
#check_upstream_clonable(data)
if __name__ == '__main__':
if len(sys.argv) < 2:
sys.stderr.write("Usage: {0} path/to/projects.yaml"
"\n".format(sys.argv[0]))
sys.exit(1)
run_checks(sys.argv[1])