예제 #1
0
def main(ylib: str = None,
         path: str = None,
         scope: ValidationScope = ValidationScope.all,
         ctype: ContentType = ContentType.config,
         set_id: bool = False,
         tree: bool = False,
         no_types: bool = False,
         digest: bool = False,
         validate: str = None) -> int:
    """Entry-point for a validation script.

    Args:
        ylib: Name of the file with YANG library
        path: Colon-separated list of directories to search  for YANG modules.
        scope: Validation scope (syntax, semantics or all).
        ctype: Content type of the data instance (config, nonconfig or all)
        set_id: If `True`, print module set id.
        tree: If `True`, print schema tree.
        no_types: If `True`, don't print types in schema tree.
        digest: If `True`, print schema digest.
        validate: Name of file to validate against the schema.

    Returns:
        Numeric return code (0=no error, 2=YANG error, 1=other)
    """
    if ylib is None:
        parser = argparse.ArgumentParser(
            prog="yangson",
            description="Validate JSON data against a YANG data model.")
        parser.add_argument(
            "-V",
            "--version",
            action="version",
            version=
            f"%(prog)s {pkg_resources.get_distribution('yangson').version}")
        parser.add_argument(
            "ylib",
            metavar="YLIB",
            help=("name of the file with description of the data model"
                  " in JSON-encoded YANG library format [RFC 7895]"))
        parser.add_argument(
            "-p",
            "--path",
            help=("colon-separated list of directories to search"
                  " for YANG modules"))
        grp = parser.add_mutually_exclusive_group()
        grp.add_argument("-i",
                         "--id",
                         action="store_true",
                         help="print module set id")
        grp.add_argument("-t",
                         "--tree",
                         action="store_true",
                         help="print schema tree as ASCII art")
        grp.add_argument("-d",
                         "--digest",
                         action="store_true",
                         help="print schema digest in JSON format")
        grp.add_argument(
            "-v",
            "--validate",
            metavar="INST",
            help="name of the file with JSON-encoded instance data")
        parser.add_argument("-s",
                            "--scope",
                            choices=["syntax", "semantics", "all"],
                            default="all",
                            help="validation scope (default: %(default)s)")
        parser.add_argument(
            "-c",
            "--ctype",
            type=str,
            choices=["config", "nonconfig", "all"],
            default="config",
            help="content type of the data instance (default: %(default)s)")
        parser.add_argument("-n",
                            "--no-types",
                            action="store_true",
                            help="suppress type info in tree output")
        args = parser.parse_args()
        ylib: str = args.ylib
        path: Optional[str] = args.path
        scope = ValidationScope[args.scope]
        ctype = ContentType[args.ctype]
        set_id: bool = args.id
        tree: bool = args.tree
        no_types = args.no_types
        digest: bool = args.digest
        validate: str = args.validate
    try:
        with open(ylib, encoding="utf-8") as infile:
            yl = infile.read()
    except (FileNotFoundError, PermissionError,
            json.decoder.JSONDecodeError) as e:
        print("YANG library:", str(e), file=sys.stderr)
        return 1
    sp = path if path else os.environ.get("YANG_MODPATH", ".")
    try:
        dm = DataModel(yl, tuple(sp.split(":")))
    except BadYangLibraryData as e:
        print("Invalid YANG library:", str(e), file=sys.stderr)
        return 2
    except FeaturePrerequisiteError as e:
        print("Unsupported pre-requisite feature:", str(e), file=sys.stderr)
        return 2
    except MultipleImplementedRevisions as e:
        print("Multiple implemented revisions:", str(e), file=sys.stderr)
        return 2
    except ModuleNotFound as e:
        print("Module not found:", str(e), file=sys.stderr)
        return 2
    except ModuleNotRegistered as e:
        print("Module not registered:", str(e), file=sys.stderr)
        return 2
    if set_id:
        print(dm.module_set_id())
        return 0
    if tree:
        print(dm.ascii_tree(no_types))
        return 0
    if digest:
        print(dm.schema_digest())
        return 0
    if not validate:
        return 0
    try:
        with open(validate, encoding="utf-8") as infile:
            itxt = json.load(infile)
    except (FileNotFoundError, PermissionError,
            json.decoder.JSONDecodeError) as e:
        print("Instance data:", str(e), file=sys.stderr)
        return 1
    try:
        i = dm.from_raw(itxt)
    except RawMemberError as e:
        print("Illegal object member:", str(e), file=sys.stderr)
        return 3
    except RawTypeError as e:
        print("Invalid type:", str(e), file=sys.stderr)
        return 3
    try:
        i.validate(scope, ctype)
    except SchemaError as e:
        print("Schema error:", str(e), file=sys.stderr)
        return 3
    except SemanticError as e:
        print("Semantic error:", str(e), file=sys.stderr)
        return 3
    return 0
예제 #2
0
def validate_yangson(instance_data,
                     yang_mod_dir,
                     yang_mod_lib=None,
                     validation_scope="all",
                     content_type="all",
                     to_xml=False,
                     metadata=True):
    """
    Validate instance_data for compliance with YANG modules at
    yang_mod_dir directory.

    Args:
        instance_data (dictionary or list): parsing results to validate
        yang_mod_dir (str): OS path to directory with YANG modules
        yang_mod_lib (str): optional, OS path to file with JSON-encoded YANG library data [RFC7895]
        content_type (str): optional, content type
            as per https://yangson.labs.nic.cz/enumerations.html
            supported - all, config, nonconfig
        validation_scope (str): optional, validation scope
            as per https://yangson.labs.nic.cz/enumerations.html
            supported - all, semantics, syntax
        to_xml (bool): default is False, converts results to XML if True
        metadata (bool): default is True, return data with validation results

    Returns:

        Dictionary of if metadata is True::
            {
                "result": instance_data or to_xml results,
                "exception": {},
                "valid": {},
                "comment": ""
            }

        If metadata is False returns results as is on successful validation or False otherwise

        If metadata is False but to_xml is True, return parsing results converted to XML string
    """
    if to_xml:
        from xml.etree import cElementTree as ET
    ret = {
        "result": [] if to_xml else instance_data,
        "exception": {},
        "valid": {},
        "comment": "",
    }

    if not HAS_LIBS:
        ret["comment"] = "Failed to import yangson library, make sure it is installed."
        ret["exception"] = {0: "ImportError"}
        ret["valid"] = {0: False}
    output_tag_load = _ttp_["output_object"].tag_load

    # load yang_modules_library and instantiate DataModel object
    try:
        if output_tag_load and isinstance(output_tag_load, str):
            yang_modules_library = output_tag_load
        elif yang_mod_lib:
            with open(yang_mod_lib, "r") as f:
                yang_modules_library = f.read()
        else:
            yang_modules_library = _make_library(yang_mod_dir)
        dm = DataModel(yltxt=yang_modules_library, mod_path=[yang_mod_dir])
    except:
        ret["exception"] = traceback.format_exc()
        ret["success"] = False
        ret["comment"] = "Failed to instantiate DataModel, check YANG library and path to YANG modules."
        if not metadata:
            return False
        else:
            return ret

    # decide on scopes and content
    if validation_scope == "all":
        scope = enumerations.ValidationScope.all
    elif validation_scope == "semantics":
        scope = enumerations.ValidationScope.semantics
    elif validation_scope == "syntax":
        scope = enumerations.ValidationScope.syntax
    if content_type == "all":
        ctype = enumerations.ContentType.all
    elif content_type == "config":
        ctype = enumerations.ContentType.config
    elif content_type == "nonconfig":
        ctype = enumerations.ContentType.nonconfig

    # run validation of data
    if isinstance(instance_data, list):
        for index, item in enumerate(instance_data):
            try:
                inst = dm.from_raw(item)
                inst.validate(scope=scope, ctype=ctype)
                ret["valid"][index] = True
                if to_xml:
                    ret["result"].append(
                        ET.tostring(inst.to_xml(), encoding="unicode"))
            except:
                if not metadata:
                    return False
                ret["exception"][index] = traceback.format_exc()
                ret["valid"][index] = False
    elif isinstance(instance_data, dict):
        try:
            inst = dm.from_raw(instance_data)
            inst.validate(scope=scope, ctype=ctype)
            ret["valid"] = True
            if to_xml:
                ret["result"] = ET.tostring(inst.to_xml(), encoding="unicode")
        except:
            if not metadata:
                return False
            ret["exception"] = traceback.format_exc()
            ret["valid"] = False

    # return results
    if not metadata:
        return ret["result"]
    else:
        return ret