Exemple #1
0
def get_environment_pair(heat_template):
    """Returns a yaml/env pair given a yaml file"""
    base_dir, filename = os.path.split(heat_template)
    basename = os.path.splitext(filename)[0]
    env_template = os.path.join(base_dir, "{}.env".format(basename))
    if os.path.exists(env_template):
        with open(heat_template, "r") as fh:
            yyml = yaml.load(fh)
        with open(env_template, "r") as fh:
            eyml = yaml.load(fh)

        environment_pair = {"name": basename, "yyml": yyml, "eyml": eyml}
        return environment_pair

    return None
def test_get_file_only_reference_local_files(yaml_file):
    """
    Make sure that all references to get_file only try to access local files
    and only assume a flat directory structure
    """
    is_url = re.compile(r"(?:http|https|file|ftp|ftps)://.+")
    base_dir, filename = path.split(yaml_file)

    with open(yaml_file) as fh:
        yml = yaml.load(fh)

    # skip if parameters are not defined
    if "resources" not in yml:
        pytest.skip("No resources specified in the heat template")

    get_files = find_all_get_file_in_yml(yml["resources"])

    invalid_files = []
    for get_file in get_files:
        if is_url.match(get_file):
            pytest.skip("external get_file detected")
            continue
        if get_file not in listdir(base_dir):
            invalid_files.append(get_file)
            continue

    assert not set(
        invalid_files), "Non-local files detected in get_file {}".format(
            invalid_files)
Exemple #3
0
def test_volume_templates_contains_cinder_or_resource_group(volume_template):
    """
    Check that all templates marked as volume templates are
    in fact volume templates
    """
    acceptable_resources = []
    dirname = os.path.dirname(volume_template)
    list_of_files = get_list_of_nested_files(volume_template, dirname)

    list_of_files.append(volume_template)

    for file in list_of_files:
        with open(file) as fh:
            yml = yaml.load(fh)
        resources = yml.get("resources") or {}
        for k, v in resources.items():
            if not isinstance(v, dict):
                continue
            if "type" not in v:
                continue
            if v["type"] in ["OS::Cinder::Volume", "OS::Heat::ResourceGroup"]:
                acceptable_resources.append(k)

    assert acceptable_resources, (
        "No OS::Cinder::Volume or OS::Heat::ResourceGroup resources "
        "found in volume module")
def test_environment_context(yaml_file):
    """
    A VNF's Heat Orchestration Template's OS::Nova::Server Resource
    **MUST**
    contain the metadata map value parameter 'environment_context'.

    A VNF's Heat Orchestration Template's OS::Nova::Server Resource
    metadata map value parameter 'environment_context' **MUST**
    be declared as type: 'string'.
    """
    with open(yaml_file) as fh:
        yml = yaml.load(fh)

    if "parameters" not in yml:
        pytest.skip("No parameters specified in the heat template")
    if "resources" not in yml:
        pytest.skip("No resources specified in the heat template")

    for resource, v in yml["resources"].items():
        if (not isinstance(v, dict) or v.get("type") != "OS::Nova::Server"
                or "properties" not in v):
            continue
        metadata = v["properties"].get("metadata")
        if not isinstance(metadata, dict):
            continue
        error = validate_metadata(metadata, yml["parameters"])
        if error:
            assert False, '%s resource "%s" %s' % (yaml_file, resource, error)
def test_unique_resources_across_all_yaml_files(yaml_files):
    """
    Check that all instance names are unique
    across all yaml files.
    """
    resources_ids = collections.defaultdict(set)
    for yaml_file in yaml_files:
        with open(yaml_file) as fh:
            yml = yaml.load(fh)
        if "resources" not in yml:
            continue
        for resource_id in yml["resources"]:
            resources_ids[resource_id].add(os.path.split(yaml_file)[1])

    dup_ids = {
        r_id: files
        for r_id, files in resources_ids.items() if len(files) > 1
    }

    msg = "The following resource IDs are duplicated in one or more files: "
    errors = [
        "ID ({}) appears in {}.".format(r_id, ", ".join(files))
        for r_id, files in dup_ids.items()
    ]
    msg += ", ".join(errors)
    assert not dup_ids, msg
def test_network_resource_id_format(yaml_file):
    """
    Make sure all network resource ids use the allowed naming
    convention
    """
    RE_INTERNAL_NETWORK_RID = re.compile(  # match pattern
        r"int_(?P<network_role>.+)_network$")

    with open(yaml_file) as fh:
        yml = yaml.load(fh)

    # skip if resources are not defined
    if "resources" not in yml:
        pytest.skip("No resources specified in the heat template")

    invalid_networks = []
    for k, v in yml["resources"].items():
        if not isinstance(v, dict):
            continue
        if "properties" not in v:
            continue
        if property_uses_get_resource(v, "network"):
            continue
        if v.get("type") not in NETWORK_RESOURCE_TYPES:
            continue
        match = RE_INTERNAL_NETWORK_RID.match(k)
        if not match:
            invalid_networks.append(k)

    assert not set(invalid_networks), (
        "Heat templates must only create internal networks "
        "and follow format int_{{network-role}}_network"
        "{}".format(", ".join(invalid_networks)))
def test_heat_template_parameters_contain_required_fields(yaml_file):
    """
    Check that all parameters in the environment
    file have the required fields
    """
    required_keys = {"type", "description"}

    with open(yaml_file) as fh:
        yml = yaml.load(fh)

    # skip if parameters are not defined
    if "parameters" not in yml:
        pytest.skip("No parameters specified in the heat template")

    invalid_params = defaultdict(list)
    for param, param_attrs in yml["parameters"].items():
        if not isinstance(param_attrs, dict):
            continue
        for key in required_keys:
            if key not in param_attrs:
                invalid_params[param].append(key)

    msg = [
        "Parameter {} is missing required attribute(s): {}".format(
            k, ", ".join(v)) for k, v in invalid_params.items()
    ]
    msg = ". ".join(msg)
    assert not invalid_params, msg
Exemple #8
0
def test_servers_have_required_metadata(yaml_file):
    """
    Check all defined nova server instances have the required metadata:
    vnf_id, vf_module_id, and vnf_name
    """
    with open(yaml_file) as fh:
        yml = yaml.load(fh)

    if "resources" not in yml:
        pytest.skip("No resources specified in the heat template")

    required_metadata = {"vnf_id", "vf_module_id", "vnf_name"}

    errors = []
    for k, v in yml["resources"].items():
        if v.get("type") != "OS::Nova::Server":
            continue
        if "properties" not in v:
            continue
        if "metadata" not in v["properties"]:
            continue

        metadata = set(v.get("properties", {}).get("metadata", {}).keys())
        missing_metadata = required_metadata.difference(metadata)
        if missing_metadata:
            msg_template = ("OS::Nova::Server {} is missing the following " +
                            "metadata properties: {}")
            errors.append(msg_template.format(k, missing_metadata))

    assert not errors, "\n".join(errors)
Exemple #9
0
def test_vm_type_consistent_on_nova_servers(yaml_file):
    """
    Make sure all nova servers have properly formatted properties
    for their name, image and flavor
    """
    with open(yaml_file) as fh:
        yml = yaml.load(fh)

    # skip if resources are not defined
    if "resources" not in yml:
        pytest.skip("No resources specified in the heat template")

    invalid_nova_servers = []
    for k, v in yml["resources"].items():
        if not isinstance(v, dict):
            continue
        if v.get("type") != "OS::Nova::Server":
            continue
        if "properties" not in v:
            continue

        vm_types = get_vm_types_for_resource(v)
        if len(vm_types) != 1:
            invalid_nova_servers.append(k)

    assert not set(
        invalid_nova_servers
    ), "vm_types not consistant on the following resources: {}".format(
        ",".join(invalid_nova_servers))
Exemple #10
0
def test_vm_type_assignments_on_nova_servers_only_use_get_param(yaml_file):
    """
    Make sure all nova servers only use get_param for their properties
    """
    with open(yaml_file) as fh:
        yml = yaml.load(fh)

    # skip if resources are not defined
    if "resources" not in yml:
        pytest.skip("No resources specified in the heat template")

    key_values = ["name", "flavor", "image"]
    invalid_nova_servers = set()

    for k, v in yml["resources"].items():
        if not isinstance(v, dict):
            continue
        if "properties" not in v:
            continue
        if "type" not in v:
            continue
        if v["type"] != "OS::Nova::Server":
            continue

        for k2, v2 in v["properties"].items():
            if k2 in key_values:
                if not isinstance(v2, dict):
                    invalid_nova_servers.add(k)
                elif "get_param" not in v2:
                    invalid_nova_servers.add(k)
    msg = (
        "These OS::Nova::Server resources do not derive one or more of "
        + "their {} properties via get_param: {}"
    ).format(", ".join(key_values), ", ".join(invalid_nova_servers))
    assert not invalid_nova_servers, msg
Exemple #11
0
def check_nova_parameter_format(prop, yaml_file):

    formats = {
        "string": {
            "name": re.compile(r"(.+?)_name_\d+$"),
            "flavor": re.compile(r"(.+?)_flavor_name$"),
            "image": re.compile(r"(.+?)_image_name$"),
        },
        "comma_delimited_list": {
            "name": re.compile(r"(.+?)_names$")
        },
    }

    with open(yaml_file) as fh:
        yml = yaml.load(fh)

    # skip if resources are not defined
    if "resources" not in yml:
        pytest.skip("No resources specified in the heat template")

    # skip if resources are not defined
    if "parameters" not in yml:
        pytest.skip("No parameters specified in the heat template")

    invalid_parameters = []

    for k, v in yml["resources"].items():
        if not isinstance(v, dict):
            continue
        if v.get("type") != "OS::Nova::Server":
            continue

        prop_val = v.get("properties", {}).get(prop, {})
        prop_param = prop_val.get("get_param", "") if isinstance(
            prop_val, dict) else ""

        if not prop_param:
            pytest.skip("{} doesn't have property {}".format(k, prop))
        elif isinstance(prop_param, list):
            prop_param = prop_param[0]

        template_param_type = yml.get("parameters", {}).get(prop_param,
                                                            {}).get("type")

        if not template_param_type:
            pytest.skip(
                "could not determine param type for {}".format(prop_param))

        format_match = formats.get(template_param_type, {}).get(prop)

        if not format_match or not format_match.match(prop_param):
            msg = ("Invalid parameter format ({}) on Resource ID ({}) property"
                   " ({})").format(prop_param, k, prop)
            invalid_parameters.append(msg)

    assert not set(invalid_parameters), ", ".join(invalid_parameters)
def parametrize_heat_volume_pair(metafunc):
    """
    Define a list of pairs of parsed yaml from the a heat and volume
    template
    """
    pairs = []
    if metafunc.config.getoption("self_test"):
        sub_dirs = ["pass", "fail"]
        volume_files = list_template_dir(
            metafunc, [".yaml", ".yml"], True, "volume", sub_dirs
        )
        yaml_files = list_template_dir(metafunc, [".yaml", ".yml"], True, "", sub_dirs)
    else:
        volume_files = list_template_dir(metafunc, [".yaml", ".yml"], True, "volume")
        yaml_files = list_template_dir(metafunc, [".yaml", ".yml"], True)

    pattern = re.compile(r"\_volume$")
    for vfilename in volume_files:
        basename = pattern.sub("", path.splitext(vfilename)[0])
        if basename + ".yml" in yaml_files:
            yfilename = basename + ".yml"
        else:
            yfilename = basename + ".yaml"

        try:
            with open(vfilename) as fh:
                vyml = yaml.load(fh)
            with open(yfilename) as fh:
                yyml = yaml.load(fh)

            if "fail" in vfilename:
                pairs.append(
                    pytest.mark.xfail(
                        {"name": basename, "yyml": yyml, "vyml": vyml}, strict=True
                    )
                )
            else:
                pairs.append({"name": basename, "yyml": yyml, "vyml": vyml})

        except yaml.YAMLError as e:
            print(e)  # pylint: disable=superfluous-parens

    metafunc.parametrize("heat_volume_pair", pairs)
Exemple #13
0
def load_yaml(yaml_file):
    """
    Load the YAML file at the given path.  If the file has previously been
    loaded, then a cached version will be returned.

    :param yaml_file: path to the YAML file
    :return: data structure loaded from the YAML file
    """
    with open(yaml_file) as fh:
        return yaml.load(fh)
def test_environment_file_contains_required_sections(env_file):
    """
    Check that all environments files only have the allowed sections
    """
    required_keys = ["parameters"]

    with open(env_file) as fh:
        yml = yaml.load(fh)
    missing_keys = [v for v in required_keys if v not in yml]
    assert not missing_keys, "%s missing %s" % (env_file, missing_keys)
def parametrize_environment_pair(metafunc, template_type=""):
    """
    Define a list of pairs of parsed yaml from the heat templates and
    environment files
    """
    pairs = []
    if metafunc.config.getoption("self_test"):
        sub_dirs = ["pass", "fail"]
        env_files = list_template_dir(metafunc, [".env"], True, template_type, sub_dirs)
        yaml_files = list_template_dir(
            metafunc, [".yaml", ".yml"], True, template_type, sub_dirs
        )
    else:
        env_files = list_template_dir(metafunc, [".env"], True, template_type)
        yaml_files = list_template_dir(metafunc, [".yaml", ".yml"], True, template_type)

    for filename in env_files:
        basename = path.splitext(filename)[0]
        if basename + ".yml" in yaml_files:
            yfilename = basename + ".yml"
        else:
            yfilename = basename + ".yaml"

        try:
            with open(filename) as fh:
                eyml = yaml.load(fh)
            with open(yfilename) as fh:
                yyml = yaml.load(fh)

            if "fail" in filename:
                pairs.append(
                    pytest.mark.xfail(
                        {"name": basename, "yyml": yyml, "eyml": eyml}, strict=True
                    )
                )
            else:
                pairs.append({"name": basename, "yyml": yyml, "eyml": eyml})

        except yaml.YAMLError as e:
            print(e)  # pylint: disable=superfluous-parens

    metafunc.parametrize("environment_pair", pairs)
Exemple #16
0
def test_env_no_resource_registry(env_files):
    """
    A VNF's Heat Orchestration template's Environment File's
    **MUST NOT** contain the "resource_registry:" section.
    """
    for filename in env_files:
        with open(filename) as fi:
            yml = yaml.load(fi)
        assert "resource_registry" not in yml, (
            '%s contains "resource_registry"' % filename
        )
Exemple #17
0
def check_parameters_no_constraints(yaml_file, parameter):

    with open(yaml_file) as fh:
        yml = yaml.load(fh)

    param = yml.get("parameters", {}).get(parameter)
    if not param:
        pytest.skip("Parameter {} not defined in parameters section".format(parameter))

    assert (
        "constraints" not in param
    ), "Found constraints defined for parameter: {}".format(parameter)
def test_heat_template_structure_contains_resources(heat_template):
    """
    Check that all heat templates have the required sections
    """
    required_key_values = ["resources"]

    with open(heat_template) as fh:
        yml = yaml.load(fh)
    assert all([
        k in yml for k in required_key_values
    ]), "{} doesn't contain the {} section, but it is required".format(
        heat_template, required_key_values[0])
def test_volume_outputs_consumed(template_dir, volume_template):
    """
    Check that all outputs in a volume template is consumed
    by the corresponding heat template
    """
    pair_module = VolumePairModule(volume_template)
    if not pair_module.exists:
        pytest.skip("No pair module found for volume template")
    with open(volume_template, "r") as f:
        volume = yaml.load(f)
    with open(pair_module.get_module_path(), "r") as f:
        pair = yaml.load(f)
    outputs = set(volume.get("outputs", {}).keys())
    parameters = set(pair.get("parameters", {}).keys())
    missing_output_parameters = outputs.difference(parameters)
    assert not missing_output_parameters, (
        "The output parameters ({}) in {} were not all "
        "used by the expected module {}".format(
            ",".join(missing_output_parameters), volume_template, pair_module))

    # Now make sure that none of the output parameters appear in any other
    # template
    template_files = set(glob.glob("*.yaml")).union(glob.glob(".yml"))
    errors = {}
    for template_path in template_files:
        if template_path in (pair_module, volume_template):
            continue  # Skip these files since we already checked this pair
        with open(template_path, "r") as f:
            template = yaml.load(f)
        parameters = set(template.get("parameters", {}).keys())
        misused_outputs = outputs.intersection(parameters)
        if misused_outputs:
            errors[template_path] = misused_outputs
    message = ", ".join("{} ({})".format(path, ", ".join(params))
                        for path, params in errors.items())
    assert not errors, (
        "Volume output parameters detected in unexpected modules: " + message)
Exemple #20
0
def run_check_resource_parameter(yaml_file,
                                 prop,
                                 DESIRED,
                                 resource_type,
                                 check_resource=True,
                                 **kwargs):
    filepath, filename = os.path.split(yaml_file)
    environment_pair = get_environment_pair(yaml_file)

    if not environment_pair:
        # this is a nested file

        if not check_resource:
            # dont check env for nested files
            # This will be tested separately for parent template
            pytest.skip("This test doesn't apply to nested files")

        environment_pair = find_environment_file(yaml_file)
        if environment_pair:
            with open(yaml_file, "r") as f:
                yml = yaml.load(f)
            environment_pair["yyml"] = yml
        else:
            pytest.skip(
                "unable to determine environment file for nested yaml file")

    if check_resource:
        invalid_parameters = check_resource_parameter(environment_pair, prop,
                                                      DESIRED, resource_type,
                                                      **kwargs)
    else:
        invalid_parameters = check_param_in_env_file(environment_pair, prop,
                                                     DESIRED)

    if kwargs.get("resource_type_inverse"):
        resource_type = "non-{}".format(resource_type)

    params = (": {}".format(", ".join(invalid_parameters)) if isinstance(
        invalid_parameters, Iterable) else "")

    assert not invalid_parameters, ("{} {} parameters in template {}{}"
                                    " found in {} environment file{}".format(
                                        resource_type,
                                        prop,
                                        filename,
                                        " not" if DESIRED else "",
                                        environment_pair.get("name"),
                                        params,
                                    ))
def check_server_parameter_name(heat_template, parameter, parameter_name):
    """
    Check each OS::Nova::Server metadata property
    uses the same parameter name w/ get_param
    """

    with open(heat_template) as fh:
        yml = yaml.load(fh)

    # skip if resources are not defined
    if "resources" not in yml:
        pytest.skip("No resources specified in the heat template")

    invalid_parameters = []

    for k1, v1 in yml["resources"].items():
        if not isinstance(v1, dict):
            continue
        if "type" not in v1:
            continue

        if v1["type"] != "OS::Nova::Server":
            continue

        metadata = v1.get("properties", {}).get("metadata", {}).get(parameter)

        if not metadata or not isinstance(metadata, dict):
            continue

        get_param = metadata.get("get_param")

        if not get_param:
            continue

        if get_param != parameter_name:
            invalid_parameters.append(
                {
                    "resource": k1,
                    "metadata property": parameter_name,
                    "get_param": get_param,
                }
            )

    assert not invalid_parameters, (
        "metadata property {} must use get_param and "
        "the parameter name must be {}: {}".format(
            parameter, parameter_name, invalid_parameters
        )
    )
def get_all_vm_types(yaml_files):
    """
    Get all vm_types for a list of yaml files
    """
    vm_types = []
    for yaml_file in yaml_files:
        with open(yaml_file, "r") as f:
            yml = yaml.load(f)

        if "resources" not in yml:
            continue

        vm_types.extend(get_vm_types(yml["resources"]))

    return set(vm_types)
 def load(self, filepath):
     """Load the Heat template given a filepath.
     """
     self.filepath = filepath
     self.basename = os.path.basename(self.filepath)
     self.dirname = os.path.dirname(self.filepath)
     with open(self.filepath) as fi:
         self.yml = yaml.load(fi)
     self.heat_template_version = self.yml.get("heat_template_version",
                                               None)
     self.description = self.yml.get("description", "")
     self.parameter_groups = self.yml.get("parameter_groups") or {}
     self.parameters = self.yml.get("parameters") or {}
     self.resources = self.yml.get("resources") or {}
     self.outputs = self.yml.get("outputs") or {}
     self.conditions = self.yml.get("conditions") or {}
def test_alphanumeric_resource_ids_only(yaml_file):
    valid_format = re.compile(r"^[\w-]+$")

    with open(yaml_file) as fh:
        yml = yaml.load(fh)

    if "resources" not in yml:
        pytest.skip("No resources specified in the heat template")

    invalid_resource_ids = [
        k for k in yml["resources"].keys() if not valid_format.match(k)
    ]

    msg = "Invalid character(s) detected in the following resource IDs: " + ", ".join(
        invalid_resource_ids)
    assert not set(invalid_resource_ids), msg
Exemple #25
0
def test_no_vf_module_index_in_cinder(volume_template):
    """
    vf_module_index is prohibited in volume templates
    """

    with open(volume_template) as fh:
        yml = yaml.load(fh)

    if "parameters" not in yml:
        pytest.skip("No parameters specified in the heat template")

    parameters = yml.get("parameters")
    if parameters and isinstance(parameters, dict):
        assert ("vf_module_index" not in parameters
                ), "{} must not use vf_module_index as a parameter".format(
                    volume_template)
def test_network_has_subnet(yaml_file):
    """
    if creating internal network, make sure there is a
    corresponding subnet that references it
    """

    with open(yaml_file) as fh:
        yml = yaml.load(fh)

    # skip if resources are not defined
    if "resources" not in yml:
        pytest.skip("No resources specified in the heat template")

    networks = []

    for k, v in yml["resources"].items():
        if not isinstance(v, dict):
            continue
        if "properties" not in v:
            continue
        # need to check if contrail networks also require subnet
        # and it is defined the same as neutron networks
        # if v.get("type") not in NETWORK_RESOURCE_TYPES:
        if v.get("type") not in ["OS::Neutron::Net"]:
            continue
        networks.append(k)

    for k, v in yml["resources"].items():
        if not isinstance(v, dict):
            continue
        if "properties" not in v:
            continue
        if v.get("type") != "OS::Neutron::Subnet":
            continue
        network_prop = v.get("properties", {}).get("network",
                                                   {}).get("get_resource")

        if not network_prop:
            continue
        x = 0
        for network in networks:
            if network == network_prop:
                networks.pop(x)
                break
            x += 1

    assert not networks, "Networks detected without subnet {}".format(networks)
def test_06_heat_template_resource_section_has_resources(heat_template):

    found_resource = False

    with open(heat_template) as fh:
        yml = yaml.load(fh)

    resources = yml.get("resources")
    if resources:
        for k1, v1 in yml["resources"].items():
            if not isinstance(v1, dict):
                continue

            found_resource = True
            break

    assert found_resource, "Heat templates must contain at least one resource"
def test_parameter_names(yaml_file):
    """
    A VNF's Heat Orchestration Template's parameter name
    (i.e., <param name>) **MUST** contain only alphanumeric
    characters and underscores ('_').
    """
    with open(yaml_file) as fh:
        yml = yaml.load(fh)

    # skip if parameters are not defined
    if "parameters" not in yml:
        pytest.skip("No parameters specified in the heat template")

    for key in yml["parameters"]:
        assert RE_VALID_PARAMETER_NAME.match(
            key), '%s parameter "%s" not alphanumeric or underscore' % (
                yaml_file, key)
def test_neutron_port_network_param_is_string(yaml_file):
    """
    Make sure all network properties use the allowed naming
    conventions
    """
    with open(yaml_file) as fh:
        yml = yaml.load(fh)

    # skip if resources are not defined
    if "resources" not in yml:
        pytest.skip("No resources specified in the heat template")

    # skip if parameters are not defined
    if "parameters" not in yml:
        pytest.skip("No parameters specified in the heat template")

    invalid_ports = []
    for k, v in yml["resources"].items():
        if not isinstance(v, dict):
            continue
        if "properties" not in v:
            continue
        if property_uses_get_resource(v, "network"):
            continue
        if v.get("type") != "OS::Neutron::Port":
            continue

        prop = v.get("properties", {}).get("network", {})
        network_param = prop.get("get_param", "") if isinstance(prop,
                                                                dict) else ""
        if not network_param:
            continue

        param = yml.get("parameters").get(network_param)
        if not param:
            continue

        param_type = param.get("type")
        if not param_type:
            continue

        if param_type != "string":
            invalid_ports.append({"port": k, "param": network_param})

    assert not invalid_ports, "network parameter must be defined as string {} ".format(
        invalid_ports)
def test_parameter_type(yaml_file):
    """A VNF's Heat Orchestration Template's parameter type **MUST**
    be one of the following values:
    """
    types = ["string", "number", "json", "comma_delimited_list", "boolean"]
    with open(yaml_file) as fh:
        yml = yaml.load(fh)
    for key, param in yml.get("parameters", {}).items():
        assert isinstance(
            param, dict), "%s parameter %s is not dict" % (yaml_file, key)
        if "type" not in param:
            continue
        typ = param["type"]
        assert typ in types, '%s parameter %s has invalid type "%s"' % (
            yaml_file,
            key,
            typ,
        )