diff --git a/ci/configvalidator.py b/ci/configvalidator.py new file mode 100644 index 000000000..e3b8ac24e --- /dev/null +++ b/ci/configvalidator.py @@ -0,0 +1,110 @@ +#! /usr/bin/env python3 +# +# SPDX-FileCopyrightText: 2020 Adriaan de Groot +# SPDX-License-Identifier: BSD-2-Clause +# License-Filename: LICENSES/BSD2 +# +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 ... +""" + +# 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 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: + print(usage) + exit(ERR_USAGE) + +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) +except SchemaError as e: + print(e.message) + 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) + +### SCHEMA VALIDATION +# +# +# Here a ValidationError from jsonschema carries a lot of useful information, +# so just let it go. +for c in configs: + validate(instance=c, schema=schema)