#!/usr/bin/env python3 # SPDX-License-Identifier: Apache-2.0 """ A tool for validating entity manager configurations. """ import argparse import json import os import re import sys import jsonschema.validators DEFAULT_SCHEMA_FILENAME = "global.json" def remove_c_comments(string): # first group captures quoted strings (double or single) # second group captures comments (//single-line or /* multi-line */) pattern = r"(\".*?(?<!\\)\"|\'.*?(?<!\\)\')|(/\*.*?\*/|//[^\r\n]*$)" regex = re.compile(pattern, re.MULTILINE | re.DOTALL) def _replacer(match): if match.group(2) is not None: return "" else: return match.group(1) return regex.sub(_replacer, string) def main(): parser = argparse.ArgumentParser( description="Entity manager configuration validator", ) parser.add_argument( "-s", "--schema", help=( "Use the specified schema file instead of the default " "(__file__/../../schemas/global.json)" ), ) parser.add_argument( "-c", "--config", action="append", help=( "Validate the specified configuration files (can be " "specified more than once) instead of the default " "(__file__/../../configurations/**.json)" ), ) parser.add_argument( "-e", "--expected-fails", help=( "A file with a list of configurations to ignore should " "they fail to validate" ), ) parser.add_argument( "-k", "--continue", action="store_true", help="keep validating after a failure", ) parser.add_argument( "-v", "--verbose", action="store_true", help="be noisy" ) args = parser.parse_args() schema_file = args.schema if schema_file is None: try: source_dir = os.path.realpath(__file__).split(os.sep)[:-2] schema_file = os.sep + os.path.join( *source_dir, "schemas", DEFAULT_SCHEMA_FILENAME ) except Exception: sys.stderr.write( "Could not guess location of {}\n".format( DEFAULT_SCHEMA_FILENAME ) ) sys.exit(2) schema = {} try: with open(schema_file) as fd: schema = json.load(fd) except FileNotFoundError: sys.stderr.write( "Could not read schema file '{}'\n".format(schema_file) ) sys.exit(2) config_files = args.config or [] if len(config_files) == 0: try: source_dir = os.path.realpath(__file__).split(os.sep)[:-2] configs_dir = os.sep + os.path.join(*source_dir, "configurations") data = os.walk(configs_dir) for root, _, files in data: for f in files: if f.endswith(".json"): config_files.append(os.path.join(root, f)) except Exception: sys.stderr.write("Could not guess location of configurations\n") sys.exit(2) configs = [] for config_file in config_files: try: with open(config_file) as fd: configs.append(json.loads(remove_c_comments(fd.read()))) except FileNotFoundError: sys.stderr.write( "Could not parse config file '{}'\n".format(config_file) ) sys.exit(2) expected_fails = [] if args.expected_fails: try: with open(args.expected_fails) as fd: for line in fd: expected_fails.append(line.strip()) except Exception: sys.stderr.write( "Could not read expected fails file '{}'\n".format( args.expected_fails ) ) sys.exit(2) base_uri = "file://{}/".format( os.path.split(os.path.realpath(schema_file))[0] ) resolver = jsonschema.RefResolver(base_uri, schema) validator = jsonschema.Draft7Validator(schema, resolver=resolver) results = { "invalid": [], "unexpected_pass": [], } for config_file, config in zip(config_files, configs): name = os.path.split(config_file)[1] expect_fail = name in expected_fails try: validator.validate(config) if expect_fail: results["unexpected_pass"].append(name) if not getattr(args, "continue"): break except jsonschema.exceptions.ValidationError as e: if not expect_fail: results["invalid"].append(name) if args.verbose: print(e) if expect_fail or getattr(args, "continue"): continue break exit_status = 0 if len(results["invalid"]) + len(results["unexpected_pass"]): exit_status = 1 unexpected_pass_suffix = " **" show_suffix_explanation = False print("results:") for f in config_files: if any([x in f for x in results["unexpected_pass"]]): show_suffix_explanation = True print(" '{}' passed!{}".format(f, unexpected_pass_suffix)) if any([x in f for x in results["invalid"]]): print(" '{}' failed!".format(f)) if show_suffix_explanation: print("\n** configuration expected to fail") sys.exit(exit_status) if __name__ == "__main__": main()