Ejemplo n.º 1
0
    def _get_test_dir_absolute(self, test_type, schema_id):
        """Get absolute path of directory in which schema unit tests exist.

        Args:
            test_type (str): Test type. One of "valid" or "invalid"
            schema_id (str): Schema ID for which to get test dir absolute path

        Returns:
            str: Full path of test directory.
        """
        if test_type not in ["valid", "invalid"]:
            raise ValueError(
                f"Test type parameter was {test_type}. Must be one of 'valid' or 'invalid'"
            )

        if not self.schemas.get(schema_id, None):
            raise ValueError(f"Could not find schema ID {schema_id}")

        root = os.path.abspath(os.getcwd())
        short_schema_id = schema_id.split(
            "/")[1] if "/" in schema_id else schema_id
        test_dir = os.path.join(root, self.test_directory, short_schema_id,
                                test_type)

        if not os.path.exists(test_dir):
            error(
                f"Tried to search {test_dir} for {test_type} data, but the path does not exist."
            )
            sys.exit(1)

        return test_dir
Ejemplo n.º 2
0
def validate(show_pass, show_checks, strict):  # noqa D205
    """Validates instance files against defined schema.

    \f

    Args:
        show_pass (bool): show successful schema validations
        show_checks (bool): show schemas which will be validated against each instance file
        strict (bool): Forces a stricter schema check that warns about unexpected additional properties
    """
    config.load()

    # ---------------------------------------------------------------------
    # Load Schema(s) from disk
    # ---------------------------------------------------------------------
    smgr = SchemaManager(config=config.SETTINGS)

    if not smgr.schemas:
        error("No schemas were loaded")
        sys.exit(1)

    # ---------------------------------------------------------------------
    # Load Instances
    # ---------------------------------------------------------------------
    ifm = InstanceFileManager(config=config.SETTINGS)

    if not ifm.instances:
        error("No instance files were found to validate")
        sys.exit(1)

    if show_checks:
        ifm.print_schema_mapping()
        sys.exit(0)

    error_exists = False
    for instance in ifm.instances:
        for result in instance.validate(smgr, strict):

            result.instance_type = "FILE"
            result.instance_name = instance.filename
            result.instance_location = instance.path

            if not result.passed():
                error_exists = True
                result.print()

            elif result.passed() and show_pass:
                result.print()

    if not error_exists:
        print(colored("ALL SCHEMA VALIDATION CHECKS PASSED", "green"))
    else:
        sys.exit(1)
Ejemplo n.º 3
0
def schema(check, generate_invalid, list_schemas, schema_id,
           dump_schemas):  # noqa: D417,D301,D205
    """Manage your schemas.

    \f

    Args:
        check (bool): Validates that all schemas are valid (spec and unit tests)
        generate_invalid (bool): Generates expected invalid data from a given schema
        list_schemas (bool): List all available schemas
        schema_id (str): Name of schema to evaluate
        dump_schemas (bool): Dump all schema data or a single schema if schema_id is provided
    """
    if not check and not generate_invalid and not list_schemas and not schema_id and not dump_schemas:
        error(
            "The 'schema' command requires one or more arguments. You can run the command 'schema-enforcer schema --help' to see the arguments available."
        )
        sys.exit(1)

    config.load()

    # ---------------------------------------------------------------------
    # Load Schema(s) from disk
    # ---------------------------------------------------------------------
    smgr = SchemaManager(config=config.SETTINGS)

    if not smgr.schemas:
        error("No schemas were loaded")
        sys.exit(1)

    if list_schemas:
        smgr.print_schemas_list()
        sys.exit(0)

    if dump_schemas:
        smgr.dump_schema(schema_id)
        sys.exit(0)

    if generate_invalid:
        if not schema_id:
            sys.exit(
                "Please indicate the schema you'd like to generate invalid data for using the --schema-id flag"
            )
        smgr.generate_invalid_tests_expected(schema_id=schema_id)
        sys.exit(0)

    if check:
        smgr.test_schemas()
        sys.exit(0)
Ejemplo n.º 4
0
    def _ensure_results_invalid(results, data_file):
        """Ensures each result is schema valid in a list of results data structures.

        Args:
            results(dict): List of Dicts of results. Each result dict must include a 'result' key of 'PASS' or 'FAIL'
            data_file (str): Data file which should be schema invalid

        Raises:
            error: Raises an error and calls sys.exit(1) if one of the results objects is schema valid.
        """
        results_pass_or_fail = [result["result"] for result in results]

        if "PASS" in results_pass_or_fail:
            error(
                f"{data_file} is schema valid, but should be schema invalid as it defines an invalid test"
            )
            sys.exit(1)
Ejemplo n.º 5
0
def ansible(inventory, limit, show_pass, show_checks):  # pylint: disable=too-many-branches,too-many-locals,too-many-locals  # noqa: D417,D301
    """Validate the hostvars for all hosts within an Ansible inventory.

    The hostvars are dynamically rendered based on groups to which each host belongs.
    For each host, if a variable `schema_enforcer_schema_ids` is defined, it will be used
    to determine which schemas should be use to validate each key. If this variable is
    not defined, the hostvars top level keys will be automatically mapped to a schema
    definition's top level properties to automatically infer which schema should be used
    to validate which hostvar.
    \f

    Args:
        inventory (string): The name of the file used to construct an ansible inventory.
        limit (string, None): Name of a host to limit the execution to.
        show_pass (bool): Shows validation checks that pass. Defaults to False.
        show_checks (bool): Shows the schema ids each host will be evaluated against.

    Example:
        $ cd examples/ansible
        $ ls -ls
        total 8
        drwxr-xr-x  5 damien  staff   160B Jul 25 16:37 group_vars
        drwxr-xr-x  4 damien  staff   128B Jul 25 16:37 host_vars
        -rw-r--r--  1 damien  staff    69B Jul 25 16:37 inventory.ini
        drwxr-xr-x  4 damien  staff   128B Jul 25 16:37 schema
        $ schema-enforcer ansible -i inventory.ini
        Found 4 hosts in the inventory
        FAIL | [ERROR] False is not of type 'string' [HOST] spine1 [PROPERTY] dns_servers:0:address
        FAIL | [ERROR] False is not of type 'string' [HOST] spine2 [PROPERTY] dns_servers:0:address
        $ schema-enforcer ansible -i inventory.ini -h leaf1
        Found 4 hosts in the inventory
        ALL SCHEMA VALIDATION CHECKS PASSED
        $ schema-enforcer ansible -i inventory.ini -h spine1 --show-pass
        Found 4 hosts in the inventory
        FAIL | [ERROR] False is not of type 'string' [HOST] spine1 [PROPERTY] dns_servers:0:address
        PASS | [HOST] spine1 [SCHEMA ID] schemas/interfaces
    """
    # Ansible is currently always installed by schema-enforcer. This was added in the interest of making ansible an
    # optional dependency. We decided to make two separate packages installable via PyPi, one with ansible, one without.
    # This has been left in the code until such a time as we implement the change to two packages so code will not need
    # to be re-written/
    try:
        from schema_enforcer.ansible_inventory import AnsibleInventory  # pylint: disable=import-outside-toplevel
    except ModuleNotFoundError:
        error(
            "ansible package not found, you can run the command 'pip install schema-enforcer[ansible]' to install the latest schema-enforcer sanctioned version."
        )
        sys.exit(1)

    if inventory:
        config.load(config_data={"ansible_inventory": inventory})
    else:
        config.load()

    # ---------------------------------------------------------------------
    # Load Schema(s) from disk
    # ---------------------------------------------------------------------
    smgr = SchemaManager(config=config.SETTINGS)

    if not smgr.schemas:
        error("No schemas were loaded")
        sys.exit(1)

    # ---------------------------------------------------------------------
    # Load Ansible Inventory file
    #  - generate hostvar for all devices in the inventory
    #  - Validate Each key in the hostvar individually against the schemas defined in the var jsonschema_mapping
    # ---------------------------------------------------------------------
    inv = AnsibleInventory(inventory=config.SETTINGS.ansible_inventory)
    hosts = inv.get_hosts_containing()
    print(f"Found {len(hosts)} hosts in the inventory")

    if show_checks:
        inv.print_schema_mapping(hosts, limit, smgr)
        sys.exit(0)

    error_exists = False

    for host in hosts:
        if limit and host.name != limit:
            continue

        # Acquire Host Variables
        hostvars = inv.get_clean_host_vars(host)

        # Acquire validation settings for the given host
        schema_validation_settings = inv.get_schema_validation_settings(host)
        declared_schema_ids = schema_validation_settings["declared_schema_ids"]
        strict = schema_validation_settings["strict"]
        automap = schema_validation_settings["automap"]

        # Validate declared schemas exist
        smgr.validate_schemas_exist(declared_schema_ids)

        # Acquire schemas applicable to the given host
        applicable_schemas = inv.get_applicable_schemas(
            hostvars, smgr, declared_schema_ids, automap)
        # import pdb; pdb.set_trace()
        for schema_obj in applicable_schemas.values():
            # Combine host attributes into a single data structure matching to properties defined at the top level of the schema definition
            if not strict:
                data = dict()
                for var in schema_obj.top_level_properties:
                    data.update({var: hostvars.get(var)})

            # If the schema_enforcer_strict bool is set, hostvars should match a single schema exactly.
            # Thus, we want to pass the entirety of the cleaned host vars into the validate method rather
            # than creating a data structure with only the top level vars defined by the schema.
            else:
                data = hostvars

            # Validate host vars against schema
            for result in schema_obj.validate(data=data, strict=strict):

                result.instance_type = "HOST"
                result.instance_hostname = host.name

                if not result.passed():
                    error_exists = True
                    result.print()

                elif result.passed() and show_pass:
                    result.print()

    if not error_exists:
        print(colored("ALL SCHEMA VALIDATION CHECKS PASSED", "green"))
    else:
        sys.exit(1)