diff options
Diffstat (limited to 'cloudinit/config/schema.py')
-rw-r--r-- | cloudinit/config/schema.py | 93 |
1 files changed, 66 insertions, 27 deletions
diff --git a/cloudinit/config/schema.py b/cloudinit/config/schema.py index dcaf13af..b0b5fccf 100644 --- a/cloudinit/config/schema.py +++ b/cloudinit/config/schema.py @@ -1,6 +1,5 @@ # This file is part of cloud-init. See LICENSE file for license information. """schema.py: Set of module functions for processing cloud-config schema.""" - import argparse import json import logging @@ -11,6 +10,7 @@ import textwrap from collections import defaultdict from collections.abc import Iterable from copy import deepcopy +from functools import partial from itertools import chain from typing import TYPE_CHECKING, List, NamedTuple, Optional, Type, Union, cast @@ -162,17 +162,47 @@ def is_schema_byte_string(checker, instance): ) or isinstance(instance, (bytes,)) -def _add_deprecation_msg(description: Optional[str] = None) -> str: - if description: - return f"{DEPRECATED_PREFIX}{description}" - return DEPRECATED_PREFIX.replace(":", ".").strip() +def _add_deprecated_changed_or_new_msg( + config: dict, annotate=False, filter_key=None +) -> str: + """combine description with new/changed/deprecated message + + deprecated/changed/new keys require a _version key (this is verified + in a unittest), a _description key is optional + """ + + def format_message(key: str): + if not config.get(f"{key}"): + return "" + key_description = config.get(f"{key}_description", "") + v = config.get( + f"{key}_version", + f"<missing {key}_version key, please file a bug report>", + ) + msg = f"{key.capitalize()} in version {v}. {key_description}" + if annotate: + return f" {msg}" + # italicised RST - no whitespace between astrisk and text + return f"\n\n*{msg.strip()}*" -def _validator_deprecated( + # define print order + filter_keys = ( + filter_key if filter_key else ["deprecated", "changed", "new"] + ) + + # build a deprecation/new/changed string + changed_new_deprecated = "".join(map(format_message, filter_keys)) + description = config.get("description", "") + return f"{description}{changed_new_deprecated}".rstrip() + + +def _validator( _validator, deprecated: bool, _instance, schema: dict, + filter_key: str, error_type: Type[Exception] = SchemaDeprecationError, ): """Jsonschema validator for `deprecated` items. @@ -181,11 +211,16 @@ def _validator_deprecated( otherwise the instance is consider faulty. """ if deprecated: - description = schema.get("description") - msg = _add_deprecation_msg(description) + msg = _add_deprecated_changed_or_new_msg( + schema, annotate=True, filter_key=[filter_key] + ) yield error_type(msg) +_validator_deprecated = partial(_validator, filter_key="deprecated") +_validator_changed = partial(_validator, filter_key="changed") + + def _anyOf( validator, anyOf, @@ -314,6 +349,7 @@ def get_jsonschema_validator(): # Add deprecation handling validators = dict(Draft4Validator.VALIDATORS) validators[DEPRECATED_KEY] = _validator_deprecated + validators["changed"] = _validator_changed validators["oneOf"] = _oneOf validators["anyOf"] = _anyOf @@ -843,32 +879,35 @@ def _get_property_description(prop_config: dict) -> str: Order and deprecated property description after active descriptions. Add a trailing stop "." to any description not ending with ":". """ - prop_descr = prop_config.get("description", "") + + def assign_descriptions( + config: dict, descriptions: list, deprecated_descriptions: list + ): + if any( + map( + config.get, + ("deprecated_version", "changed_version", "new_version"), + ) + ): + deprecated_descriptions.append( + _add_deprecated_changed_or_new_msg(config) + ) + elif config.get("description"): + descriptions.append(_add_deprecated_changed_or_new_msg(config)) + oneOf = prop_config.get("oneOf", {}) anyOf = prop_config.get("anyOf", {}) - descriptions = [] - deprecated_descriptions = [] - if prop_descr: - prop_descr = prop_descr.rstrip(".") - if not prop_config.get(DEPRECATED_KEY): - descriptions.append(prop_descr) - else: - deprecated_descriptions.append(_add_deprecation_msg(prop_descr)) + descriptions: list = [] + deprecated_descriptions: list = [] + + assign_descriptions(prop_config, descriptions, deprecated_descriptions) for sub_item in chain(oneOf, anyOf): - if not sub_item.get("description"): - continue - if not sub_item.get(DEPRECATED_KEY): - descriptions.append(sub_item["description"].rstrip(".")) - else: - deprecated_descriptions.append( - f"{DEPRECATED_PREFIX}{sub_item['description'].rstrip('.')}" - ) + assign_descriptions(sub_item, descriptions, deprecated_descriptions) + # order deprecated descrs last description = ". ".join(chain(descriptions, deprecated_descriptions)) if description: description = f" {description}" - if description[-1] != ":": - description += "." return description |