blob: 88786d9fc3ab2775d8bf70014cc3cd758c28fb58 [file] [log] [blame]
Sergey Otpuschennikovfc3a3832020-09-04 16:07:37 +04001#!/usr/bin/env python
2
3"""
4Those tests checks the following requirements for the `projects.yaml` file:
5 - Its syntax is valid
6 - Each project definition should consist of the following mandatory parts:
7 * project
8 * description
9 and could contain the following optional parts:
10 * acl-config
11 * upstream
12 No other parts are possible.
13 - All the projects listed in the `projects.yaml` file
14 must be sorted alphabetically.
15"""
16
17import logging
18import os
19import sys
20
21import git
22import jsonschema
23import yaml
24
25
26logging.basicConfig(level=logging.INFO)
27
28# Only lower case letters (a-z), digits (0-9), plus (+) and minus (-)
29# and periods (.).
30# They must be at least two characters long and must start with an
31# alphanumeric character.
32# https://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Source
33# Additionally allow using underline symbol (required by openstack projects)
34
35_PREFIX_PATTERN = '\A([a-z]([a-z]|\d|-)+/)*'
36_NAMES_PATTERN = '([a-zA-Z]|\d)([a-zA-Z]|\d|[+-_.])+\Z'
37PROJECT_NAME_PATTERN = _PREFIX_PATTERN + _NAMES_PATTERN
38
39PROJECT_SCHEMA = {
40 "$schema": "http://json-schema.org/draft-04/schema#",
41 "type": "array",
42 "items": {
43 "type": "object",
44 "additionalProperties": False,
45 "properties": {
46 "project": {
47 "type": "string",
48 "pattern": PROJECT_NAME_PATTERN
49 },
50 "description": {
51 "type": "string"
52 },
53 "upstream": {
54 "type": "string"
55 },
56
57 "acl-config": {
58 "type": "string"
59 }
60 },
61 "required": ["project", "description"]
62 }
63}
64
65
66def parse_yaml_file(file_path):
67 try:
68 data = yaml.safe_load(open(file_path))
69 if data is None:
70 logging.error("File {0} is empty".format(file_path))
71 sys.exit(1)
72 return data
73 except yaml.YAMLError as exc:
74 msg = "File {0} could not be parsed: {1}".format(file_path, exc)
75 logging.error(msg)
76 sys.exit(1)
77
78
79def validate_data_by_schema(data, file_path):
80 try:
81 jsonschema.validate(data, PROJECT_SCHEMA)
82 except jsonschema.exceptions.ValidationError as exc:
83 raise ValueError(_make_error_message(exc, file_path))
84
85
86def _make_error_message(exc, file_path):
87 value_path = []
88
89 if exc.absolute_path:
90 value_path.extend(exc.absolute_path)
91
92 error_msg = "File '{0}', {1}".format(file_path, exc.message)
93
94 if value_path:
95 value_path = ' -> '.join(map(str, value_path))
96 error_msg = '{0}, value path {1}'.format(error_msg, value_path)
97
98 return error_msg
99
100
101def check_duplicate_projects(data):
102 projects_items = []
103 for item in data:
104 if item['project'] not in projects_items:
105 projects_items.append(item['project'])
106 else:
107 msg = "Project '{0}' is duplicated".format(item['project'])
108 raise ValueError(msg)
109
110
111def check_alphabetical_order(data):
112 for i in range(len(data) - 1):
113 if not data[i]['project'] < data[i + 1]['project']:
114 msg = ("Alphabetical order violation: project '{0}' must be "
115 "placed after '{1}'".format(data[i]['project'],
116 data[i + 1]['project']))
117 raise ValueError(msg)
118
119
120def check_acls_config_path(data):
121 valid = True
122
123 for item in data:
124 acl_config_path = item.get('acl-config')
125 if not acl_config_path:
126 continue
127 # Allow to skip acls/ prefix in acl-config
128 if acl_config_path[:4] != 'acls':
129 acl_config_path = os.path.join('acls', acl_config_path)
130
131 config_path = os.path.join(os.path.abspath(os.curdir),
132 acl_config_path)
133 if not os.path.isfile(config_path):
134 logging.error("Config file for project '{0}' is not found "
135 "at {1}.".format(item.get('project'), config_path))
136 valid = False
137 if not valid:
138 sys.exit(1)
139
140
141def check_upstream_clonable(data):
142 clonable = True
143
144 for item in data:
145 upstream_repo = item.get('upstream')
146 if not upstream_repo:
147 continue
148 logging.info("Checking availability: {0}".format(upstream_repo))
149 try:
150 g = git.cmd.Git()
151 g.ls_remote(upstream_repo)
152 except git.exc.GitCommandError as e:
153 err_msg = ("Unable to clone '{0}':"
154 "{1}".format(upstream_repo, str(e)))
155 logging.error(err_msg)
156 clonable = False
157
158 if not clonable:
159 sys.exit(1)
160
161
162def run_checks(file_to_check):
163 data = parse_yaml_file(file_to_check)
164 validate_data_by_schema(data, file_to_check)
165 check_duplicate_projects(data)
166 check_alphabetical_order(data)
167 check_acls_config_path(data)
168 #check_upstream_clonable(data)
169
170
171if __name__ == '__main__':
172 if len(sys.argv) < 2:
173 sys.stderr.write("Usage: {0} path/to/projects.yaml"
174 "\n".format(sys.argv[0]))
175 sys.exit(1)
176 run_checks(sys.argv[1])