You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

137 lines
3.9 KiB
Python

#! /usr/bin/env python3
#
# SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
# SPDX-License-Identifier: BSD-2-Clause
#
usage = """
Validates a Calamares config file -- YAML syntax -- against a schema.
The schema is also written in YAML syntax, but the schema itself
is JSON-schema. This is possible because all JSON is YAML, and most
YAML is JSON. The limited subset of YAML that Calamares uses is
JSON-representable, anyway.
Usage:
configvalidator.py <schema> <file> ...
configvalidator.py -m <module>
configvalidator.py -x
Exits with value 0 on success, otherwise:
1 on missing dependencies
2 on invalid command-line arguments
3 on missing files
4 if files have invalid syntax
5 if files fail to validate
Use -x as only command-line argument to check the imports only.
Use -m <module> as shorthand for standard paths in src/modules/<module>/
"""
# The schemata originally lived outside the Calamares repository,
# without documented tooling. By putting them in the repository
# with the example files and explicit tooling, there's a better
# chance of them catching problems and acting as documentation.
dependencies = """
Dependencies for this tool are: py-yaml and py-jsonschema.
https://pyyaml.org/
https://github.com/Julian/jsonschema
Simple installation is `pip install pyyaml jsonschema`
"""
ERR_IMPORT, ERR_USAGE, ERR_FILE_NOT_FOUND, ERR_SYNTAX, ERR_INVALID = range(1,6)
### DEPENDENCIES
#
#
try:
from jsonschema import validate, SchemaError, ValidationError
from jsonschema import draft7_format_checker
from yaml import safe_load, YAMLError
except ImportError as e:
print(e)
print(dependencies)
exit(ERR_IMPORT)
from os.path import exists
import sys
### INPUT VALIDATION
#
#
if len(sys.argv) < 3:
# Special-case: called with -x to just test the imports
if len(sys.argv) == 2 and sys.argv[1] == "-x":
exit(0)
print(usage)
exit(ERR_USAGE)
if len(sys.argv) == 3 and sys.argv[1] == "-m":
module = sys.argv[2]
schema_file_name = f"src/modules/{module}/{module}.schema.yaml"
config_file_names = [ f"src/modules/{module}/{module}.conf" ]
else:
schema_file_name = sys.argv[1]
config_file_names = sys.argv[2:]
if not exists(schema_file_name):
print(usage)
print("\nSchema file '{}' does not exist.".format(schema_file_name))
exit(ERR_FILE_NOT_FOUND)
for f in config_file_names:
if not exists(f):
print(usage)
print("\nYAML file '{}' does not exist.".format(f))
exit(ERR_FILE_NOT_FOUND)
### FILES SYNTAX CHECK
#
#
with open(schema_file_name, "r") as data:
try:
schema = safe_load(data)
except YAMLError as e:
print("Schema error: {} {}.".format(e.problem, e.problem_mark))
print("\nSchema file '{}' is invalid YAML.".format(schema_file_name))
exit(ERR_SYNTAX)
try:
validate(instance={}, schema=schema)
# While developing the schemata, get full exceptions from schema failure
except SchemaError as e:
print(e)
print("\nSchema file '{}' is invalid JSON-Schema.".format(schema_file_name))
exit(ERR_INVALID)
except ValidationError:
# Just means that empty isn't valid, but the Schema itself is
pass
configs = []
for f in config_file_names:
config = None
with open(f, "r") as data:
try:
config = safe_load(data)
except YAMLError as e:
print("YAML error: {} {}.".format(e.problem, e.problem_mark))
print("\nYAML file '{}' is invalid.".format(f))
exit(ERR_SYNTAX)
if config is None:
print("YAML file '{}' is empty.".format(f))
configs.append(config)
assert len(configs) == len(config_file_names), "Not all configurations loaded."
### SCHEMA VALIDATION
#
#
for c, f in zip(configs, config_file_names):
try:
validate(instance=c, schema=schema)
except ValidationError as e:
print(e)
print("\nConfig file '{}' does not validate in schema.".format(f))
exit(ERR_INVALID)