diff options
-rw-r--r-- | cloudinit/config/schema.py | 93 | ||||
-rw-r--r-- | cloudinit/config/schemas/schema-cloud-config-v1.json | 86 | ||||
-rw-r--r-- | doc/rtd/reference/modules.rst | 15 | ||||
-rw-r--r-- | tests/unittests/config/test_cc_ca_certs.py | 9 | ||||
-rw-r--r-- | tests/unittests/config/test_cc_growpart.py | 7 | ||||
-rw-r--r-- | tests/unittests/config/test_cc_grub_dpkg.py | 12 | ||||
-rw-r--r-- | tests/unittests/config/test_cc_package_update_upgrade_install.py | 19 | ||||
-rw-r--r-- | tests/unittests/config/test_cc_power_state_change.py | 14 | ||||
-rw-r--r-- | tests/unittests/config/test_cc_scripts_vendor.py | 7 | ||||
-rw-r--r-- | tests/unittests/config/test_cc_set_passwords.py | 22 | ||||
-rw-r--r-- | tests/unittests/config/test_cc_update_etc_hosts.py | 7 | ||||
-rw-r--r-- | tests/unittests/config/test_cc_users_groups.py | 42 | ||||
-rw-r--r-- | tests/unittests/config/test_schema.py | 201 |
13 files changed, 374 insertions, 160 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 diff --git a/cloudinit/config/schemas/schema-cloud-config-v1.json b/cloudinit/config/schemas/schema-cloud-config-v1.json index d227e159..8bd7a80c 100644 --- a/cloudinit/config/schemas/schema-cloud-config-v1.json +++ b/cloudinit/config/schemas/schema-cloud-config-v1.json @@ -174,6 +174,7 @@ "label": "<group_name>", "description": "When providing an object for users.groups the ``<group_name>`` keys are the groups to add this user to", "deprecated": true, + "deprecated_version": "23.1", "type": [ "null" ], @@ -197,9 +198,11 @@ }, "lock-passwd": { "default": true, - "description": "Dropped after April 2027. Use ``lock_passwd``. Default: ``true``", "type": "boolean", - "deprecated": true + "description": "Default: ``true``", + "deprecated": true, + "deprecated_version": "22.3", + "deprecated_description": "Use ``lock_passwd`` instead." }, "lock_passwd": { "default": true, @@ -292,8 +295,9 @@ }, { "type": "boolean", - "deprecated": true, - "description": "The value ``false`` will be dropped after April 2027. Use ``null`` or no ``sudo`` key instead." + "changed": true, + "changed_version": "22.2", + "changed_description": "The value ``false`` is deprecated for this key, use ``null`` instead." } ] }, @@ -305,8 +309,9 @@ }, { "type": "string", - "description": "The use of ``string`` type will be dropped after April 2027. Use an ``integer`` instead.", - "deprecated": true + "changed": true, + "changed_description": "The use of ``string`` type is deprecated. Use an ``integer`` instead.", + "changed_version": "22.3" } ] } @@ -361,10 +366,11 @@ "additionalProperties": false, "properties": { "remove-defaults": { - "description": "Dropped after April 2027. Use ``remove_defaults``.", "type": "boolean", "default": false, - "deprecated": true + "deprecated": true, + "deprecated_version": "22.3", + "deprecated_description": "Use ``remove_defaults`` instead." }, "remove_defaults": { "description": "Remove default CA certificates if true. Default: false", @@ -920,7 +926,8 @@ }, { "deprecated": true, - "description": "Dropped after April 2027. Use ``ca_certs``." + "deprecated_version": "22.3", + "deprecated_description": "Use ``ca_certs`` instead." } ] } @@ -1294,8 +1301,9 @@ "enum": [ false ], - "description": "Specifying a boolean ``false`` value for this key is deprecated. Use ``off`` instead.", - "deprecated": true + "changed": true, + "changed_version": "22.3", + "changed_description": "Specifying a boolean ``false`` value for ``mode`` is deprecated. Use ``off`` instead." } ] }, @@ -1342,8 +1350,9 @@ }, { "type": "string", - "description": "Use a boolean value instead.", - "deprecated": true + "changed": true, + "changed_version": "22.3", + "changed_description": "Use a boolean value instead." } ] } @@ -1351,8 +1360,9 @@ }, "grub-dpkg": { "type": "object", - "description": "Use ``grub_dpkg`` instead", - "deprecated": true + "deprecated": true, + "deprecated_version": "22.2", + "deprecated_description": "Use ``grub_dpkg`` instead." } } }, @@ -1918,20 +1928,26 @@ "apt_update": { "type": "boolean", "default": false, - "description": "Dropped after April 2027. Use ``package_update``. Default: ``false``", - "deprecated": true + "description": "Default: ``false``.", + "deprecated": true, + "deprecated_version": "22.2", + "deprecated_description": "Use ``package_update`` instead." }, "apt_upgrade": { "type": "boolean", "default": false, - "description": "Dropped after April 2027. Use ``package_upgrade``. Default: ``false``", - "deprecated": true + "description": "Default: ``false``.", + "deprecated": true, + "deprecated_version": "22.2", + "deprecated_description": "Use ``package_upgrade`` instead." }, "apt_reboot_if_required": { "type": "boolean", "default": false, - "description": "Dropped after April 2027. Use ``package_reboot_if_required``. Default: ``false``", - "deprecated": true + "description": "Default: ``false``.", + "deprecated": true, + "deprecated_version": "22.2", + "deprecated_description": "Use ``package_reboot_if_required`` instead." } } }, @@ -2005,8 +2021,9 @@ { "type": "string", "pattern": "^\\+?[0-9]+$", - "deprecated": true, - "description": "Use of string for this value will be dropped after April 2027. Use ``now`` or integer type." + "changed": true, + "changed_version": "22.3", + "changed_description": "Use of type string for this value is deprecated. Use ``now`` or integer type." }, { "enum": [ @@ -2424,8 +2441,9 @@ }, { "type": "string", - "description": "Use of string for this value is DEPRECATED. Use a boolean value instead.", - "deprecated": true + "deprecated": true, + "deprecated_version": "22.3", + "deprecated_description": "Use of type string for this value is deprecated. Use a boolean instead." } ] }, @@ -2522,8 +2540,9 @@ }, { "type": "string", - "description": "Use of non-boolean values for this field is DEPRECATED and will result in an error in a future version of cloud-init.", - "deprecated": true + "changed": true, + "changed_version": "22.3", + "changed_description": "Use of non-boolean values for this field is deprecated." } ], "description": "Sets whether or not to accept password authentication. ``true`` will enable password auth. ``false`` will disable. Default is to leave the value unchanged. In order for this config to be applied, SSH may need to be restarted. On systemd systems, this restart will only happen if the SSH service has already been started. On non-systemd systems, a restart will be attempted regardless of the service state." @@ -2538,7 +2557,7 @@ "description": "Whether to expire all user passwords such that a password will need to be reset on the user's next login. Default: ``true``" }, "users": { - "description": "Replaces the deprecated ``list`` key. This key represents a list of existing users to set passwords for. Each item under users contains the following required keys: ``name`` and ``password`` or in the case of a randomly generated password, ``name`` and ``type``. The ``type`` key has a default value of ``hash``, and may alternatively be set to ``text`` or ``RANDOM``.", + "description": "This key represents a list of existing users to set passwords for. Each item under users contains the following required keys: ``name`` and ``password`` or in the case of a randomly generated password, ``name`` and ``type``. The ``type`` key has a default value of ``hash``, and may alternatively be set to ``text`` or ``RANDOM``.", "type": "array", "items": { "minItems": 1, @@ -2602,8 +2621,10 @@ } ], "minItems": 1, - "description": "List of ``username:password`` pairs. Each user will have the corresponding password set. A password can be randomly generated by specifying ``RANDOM`` or ``R`` as a user's password. A hashed password, created by a tool like ``mkpasswd``, can be specified. A regex (``r'\\$(1|2a|2y|5|6)(\\$.+){2}'``) is used to determine if a password value should be treated as a hash.\n\nUse of a multiline string for this field is DEPRECATED and will result in an error in a future version of cloud-init.", - "deprecated": true + "description": "List of ``username:password`` pairs. Each user will have the corresponding password set. A password can be randomly generated by specifying ``RANDOM`` or ``R`` as a user's password. A hashed password, created by a tool like ``mkpasswd``, can be specified. A regex (``r'\\$(1|2a|2y|5|6)(\\$.+){2}'``) is used to determine if a password value should be treated as a hash.", + "deprecated": true, + "deprecated_version": "22.2", + "deprecated_description": "Use ``users`` instead." } } }, @@ -2966,8 +2987,9 @@ "enum": [ "template" ], - "description": "Value ``template`` will be dropped after April 2027. Use ``true`` instead.", - "deprecated": true + "changed_description": "Use of ``template`` is deprecated, use ``true`` instead.", + "changed": true, + "changed_version": "22.3" } ] }, diff --git a/doc/rtd/reference/modules.rst b/doc/rtd/reference/modules.rst index dc3a6c59..e727f59b 100644 --- a/doc/rtd/reference/modules.rst +++ b/doc/rtd/reference/modules.rst @@ -3,6 +3,21 @@ Module reference **************** +Deprecation schedule and versions +--------------------------------- +Keys may be documented as ``deprecated``, ``new``, or ``changed``. +This allows cloud-init to evolve as requirements change, and to adopt +better practices without maintaining design decisions indefinitely. + +Keys that have been marked as deprecated or changed may be removed or +changed 5 years from the date of deprecation. For example, a key that is +deprecated in version ``22.1`` (which is the first release in 2022) is +scheduled to be removed in ``27.1`` (first release in 2027). Use of +deprecated keys may cause warnings in the logs. In the case that a +key's expected value changes, the key will be marked ``changed`` with a +date. A 5 year timeline may also be expected for changed keys. + + .. automodule:: cloudinit.config.cc_ansible .. automodule:: cloudinit.config.cc_apk_configure .. automodule:: cloudinit.config.cc_apt_configure diff --git a/tests/unittests/config/test_cc_ca_certs.py b/tests/unittests/config/test_cc_ca_certs.py index 5f5a5843..19e5d422 100644 --- a/tests/unittests/config/test_cc_ca_certs.py +++ b/tests/unittests/config/test_cc_ca_certs.py @@ -373,11 +373,10 @@ class TestCACertsSchema: # Valid, yet deprecated schemas ( {"ca-certs": {"remove-defaults": True}}, - "Cloud config schema deprecations: " - "ca-certs: DEPRECATED. Dropped after April 2027. " - "Use ``ca_certs``., " - "ca-certs.remove-defaults: DEPRECATED. " - "Dropped after April 2027. Use ``remove_defaults``.", + "Cloud config schema deprecations: ca-certs: " + "Deprecated in version 22.3. Use ``ca_certs`` instead.," + " ca-certs.remove-defaults: Deprecated in version 22.3" + ". Use ``remove_defaults`` instead.", ), # Invalid schemas ( diff --git a/tests/unittests/config/test_cc_growpart.py b/tests/unittests/config/test_cc_growpart.py index c808333b..13622332 100644 --- a/tests/unittests/config/test_cc_growpart.py +++ b/tests/unittests/config/test_cc_growpart.py @@ -599,9 +599,10 @@ class TestGrowpartSchema: pytest.raises( SchemaValidationError, match=( - "deprecations: growpart.mode: DEPRECATED. Specifying" - " a boolean ``false`` value for this key is" - " deprecated. Use ``off`` instead." + "Cloud config schema deprecations: " + "growpart.mode: Changed in version 22.3. " + "Specifying a boolean ``false`` value for " + "``mode`` is deprecated. Use ``off`` instead." ), ), ), diff --git a/tests/unittests/config/test_cc_grub_dpkg.py b/tests/unittests/config/test_cc_grub_dpkg.py index 0f9cc232..aa076d19 100644 --- a/tests/unittests/config/test_cc_grub_dpkg.py +++ b/tests/unittests/config/test_cc_grub_dpkg.py @@ -206,9 +206,10 @@ class TestGrubDpkgSchema: pytest.raises( SchemaValidationError, match=( - r"^Cloud config schema deprecations:" - r" grub_dpkg.grub-pc/install_devices_empty:" - r" DEPRECATED. Use a boolean value instead.$" + "Cloud config schema deprecations: " + "grub_dpkg.grub-pc/install_devices_empty: " + "Changed in version 22.3. Use a boolean value " + "instead." ), ), False, @@ -234,8 +235,9 @@ class TestGrubDpkgSchema: pytest.raises( SchemaValidationError, match=( - r"^Cloud config schema deprecations: grub-dpkg:" - r" DEPRECATED. Use ``grub_dpkg`` instead$" + "Cloud config schema deprecations: grub-dpkg:" + " Deprecated in version 22.2. Use " + "``grub_dpkg`` instead." ), ), False, diff --git a/tests/unittests/config/test_cc_package_update_upgrade_install.py b/tests/unittests/config/test_cc_package_update_upgrade_install.py index e8fce98f..07c5b932 100644 --- a/tests/unittests/config/test_cc_package_update_upgrade_install.py +++ b/tests/unittests/config/test_cc_package_update_upgrade_install.py @@ -21,25 +21,26 @@ class TestPackageUpdateUpgradeSchema: ( {"apt_update": False}, ( - "deprecations: apt_update: DEPRECATED." - " Dropped after April 2027. Use ``package_update``." - " Default: ``false``" + "Cloud config schema deprecations: apt_update: " + "Default: ``false``. Deprecated in version 22.2. " + "Use ``package_update`` instead." ), ), ( {"apt_upgrade": False}, ( - "deprecations: apt_upgrade: DEPRECATED." - " Dropped after April 2027. Use ``package_upgrade``." - " Default: ``false``" + "Cloud config schema deprecations: apt_upgrade: " + "Default: ``false``. Deprecated in version 22.2. " + "Use ``package_upgrade`` instead." ), ), ( {"apt_reboot_if_required": False}, ( - "deprecations: apt_reboot_if_required: DEPRECATED." - " Dropped after April 2027." - " Use ``package_reboot_if_required``. Default: ``false``" + "Cloud config schema deprecations: " + "apt_reboot_if_required: Default: ``false``. " + "Deprecated in version 22.2. Use " + "``package_reboot_if_required`` instead." ), ), ], diff --git a/tests/unittests/config/test_cc_power_state_change.py b/tests/unittests/config/test_cc_power_state_change.py index 81750f5b..fbdc06ef 100644 --- a/tests/unittests/config/test_cc_power_state_change.py +++ b/tests/unittests/config/test_cc_power_state_change.py @@ -180,18 +180,20 @@ class TestPowerStateChangeSchema: ( {"power_state": {"mode": "halt", "delay": "5"}}, ( - "power_state.delay: DEPRECATED:" - " Use of string for this value will be dropped after" - " April 2027. Use ``now`` or integer type." + "Cloud config schema deprecations: " + "power_state.delay: Changed in version 22.3. Use " + "of type string for this value is deprecated. Use " + "``now`` or integer type." ), ), ({"power_state": {"mode": "halt", "delay": "now"}}, None), ( {"power_state": {"mode": "halt", "delay": "+5"}}, ( - "power_state.delay: DEPRECATED:" - " Use of string for this value will be dropped after" - " April 2027. Use ``now`` or integer type." + "Cloud config schema deprecations: " + "power_state.delay: Changed in version 22.3. Use " + "of type string for this value is deprecated. Use " + "``now`` or integer type." ), ), ({"power_state": {"mode": "halt", "delay": "+"}}, ""), diff --git a/tests/unittests/config/test_cc_scripts_vendor.py b/tests/unittests/config/test_cc_scripts_vendor.py index 1dcd0573..9d3e90e0 100644 --- a/tests/unittests/config/test_cc_scripts_vendor.py +++ b/tests/unittests/config/test_cc_scripts_vendor.py @@ -19,9 +19,10 @@ class TestScriptsVendorSchema: pytest.raises( SchemaValidationError, match=( - "deprecations: vendor_data.enabled: DEPRECATED." - " Use of string for this value is DEPRECATED." - " Use a boolean value instead." + "Cloud config schema deprecations: " + "vendor_data.enabled: Deprecated in version " + "22.3. Use of type string for this value is " + "deprecated. Use a boolean instead." ), ), ), diff --git a/tests/unittests/config/test_cc_set_passwords.py b/tests/unittests/config/test_cc_set_passwords.py index 0553f781..a375c00b 100644 --- a/tests/unittests/config/test_cc_set_passwords.py +++ b/tests/unittests/config/test_cc_set_passwords.py @@ -565,10 +565,9 @@ class TestSetPasswordsSchema: pytest.raises( SchemaValidationError, match=( - "deprecations: ssh_pwauth: DEPRECATED. Use of" - " non-boolean values for this field is DEPRECATED and" - " will result in an error in a future version of" - " cloud-init." + "Cloud config schema deprecations: ssh_pwauth:" + " Changed in version 22.3. Use of non-boolean" + " values for this field is deprecated." ), ), ), @@ -577,16 +576,17 @@ class TestSetPasswordsSchema: pytest.raises( SchemaValidationError, match=( - "deprecations: ssh_pwauth: DEPRECATED. Use of" - " non-boolean values for this field is DEPRECATED and" - " will result in an error in a future version of" - " cloud-init." + "Cloud config schema deprecations: ssh_pwauth:" + " Changed in version 22.3. Use of non-boolean" + " values for this field is deprecated." ), ), ), ( {"chpasswd": {"list": "blah"}}, - pytest.raises(SchemaValidationError, match="DEPRECATED"), + pytest.raises( + SchemaValidationError, match="Deprecated in version" + ), ), # Valid combinations ( @@ -699,7 +699,9 @@ class TestSetPasswordsSchema: # Test regex ( {"chpasswd": {"list": ["user:pass"]}}, - pytest.raises(SchemaValidationError, match="DEPRECATED"), + pytest.raises( + SchemaValidationError, match="Deprecated in version" + ), ), # Test valid ({"password": "pass"}, does_not_raise()), diff --git a/tests/unittests/config/test_cc_update_etc_hosts.py b/tests/unittests/config/test_cc_update_etc_hosts.py index d48656f7..6ee6f197 100644 --- a/tests/unittests/config/test_cc_update_etc_hosts.py +++ b/tests/unittests/config/test_cc_update_etc_hosts.py @@ -87,9 +87,10 @@ class TestUpdateEtcHosts: pytest.raises( SchemaValidationError, match=( - "deprecations: manage_etc_hosts: DEPRECATED. Value" - " ``template`` will be dropped after April 2027." - " Use ``true`` instead" + "Cloud config schema deprecations: " + "manage_etc_hosts: Changed in version 22.3. " + "Use of ``template`` is deprecated, use " + "``true`` instead." ), ), ), diff --git a/tests/unittests/config/test_cc_users_groups.py b/tests/unittests/config/test_cc_users_groups.py index 6067f8e9..6a026a87 100644 --- a/tests/unittests/config/test_cc_users_groups.py +++ b/tests/unittests/config/test_cc_users_groups.py @@ -365,9 +365,10 @@ class TestUsersGroupsSchema: pytest.raises( SchemaValidationError, match=( - "users.0.lock-passwd: DEPRECATED." - " Dropped after April 2027. Use ``lock_passwd``." - " Default: ``true``" + "Cloud config schema deprecations: " + "users.0.lock-passwd: Default: ``true`` " + "Deprecated in version 22.3. Use " + "``lock_passwd`` instead." ), ), False, @@ -387,10 +388,14 @@ class TestUsersGroupsSchema: pytest.raises( SchemaValidationError, match=( - "Cloud config schema deprecations: users.0.groups.adm:" - " DEPRECATED. When providing an object for" - " users.groups the ``<group_name>`` keys are the" - " groups to add this user to," + "Cloud config schema deprecations: " + "users.0.groups.adm: When providing an object " + "for users.groups the ``<group_name>`` keys " + "are the groups to add this user to Deprecated" + " in version 23.1., users.0.groups.sudo: When " + "providing an object for users.groups the " + "``<group_name>`` keys are the groups to add " + "this user to Deprecated in version 23.1." ), ), False, @@ -440,10 +445,11 @@ class TestUsersGroupsSchema: pytest.raises( SchemaValidationError, match=( - "deprecations: user.groups.sbuild: DEPRECATED. " - "When providing an object for users.groups the " - "``<group_name>`` keys are the groups to add this " - "user to" + "Cloud config schema deprecations: " + "user.groups.sbuild: When providing an object " + "for users.groups the ``<group_name>`` keys " + "are the groups to add this user to Deprecated" + " in version 23.1." ), ), False, @@ -453,9 +459,10 @@ class TestUsersGroupsSchema: pytest.raises( SchemaValidationError, match=( - "deprecations: user.sudo: DEPRECATED. The value" - " ``false`` will be dropped after April 2027." - " Use ``null`` or no ``sudo`` key instead." + "Cloud config schema deprecations: user.sudo:" + " Changed in version 22.2. The value " + "``false`` is deprecated for this key, use " + "``null`` instead." ), ), False, @@ -470,9 +477,10 @@ class TestUsersGroupsSchema: pytest.raises( SchemaValidationError, match=( - "users.0.uid: DEPRECATED. The use of ``string`` type" - " will be dropped after April 2027. Use an ``integer``" - " instead." + "Cloud config schema deprecations: " + "users.0.uid: Changed in version 22.3. The " + "use of ``string`` type is deprecated. Use " + "an ``integer`` instead." ), ), False, diff --git a/tests/unittests/config/test_schema.py b/tests/unittests/config/test_schema.py index d70c73fd..d43af5cc 100644 --- a/tests/unittests/config/test_schema.py +++ b/tests/unittests/config/test_schema.py @@ -9,6 +9,7 @@ import logging import os import re import sys +import unittest from collections import namedtuple from copy import deepcopy from pathlib import Path @@ -146,6 +147,54 @@ class TestVersionedSchemas: ) +class TestCheckSchema(unittest.TestCase): + def test_schema_bools_have_dates(self): + """ensure that new/changed/deprecated keys have an associated + version key + """ + + def check_deprecation_keys(schema, search_key): + if search_key in schema: + assert f"{search_key}_version" in schema + for sub_item in schema.values(): + if isinstance(sub_item, dict): + check_deprecation_keys(sub_item, search_key) + return True + + # ensure that check_deprecation_keys works as expected + assert check_deprecation_keys( + {"changed": True, "changed_version": "22.3"}, "changed" + ) + assert check_deprecation_keys( + {"properties": {"deprecated": True, "deprecated_version": "22.3"}}, + "deprecated", + ) + assert check_deprecation_keys( + { + "properties": { + "properties": {"new": True, "new_version": "22.3"} + } + }, + "new", + ) + with self.assertRaises(AssertionError): + check_deprecation_keys({"changed": True}, "changed") + with self.assertRaises(AssertionError): + check_deprecation_keys( + {"properties": {"deprecated": True}}, "deprecated" + ) + with self.assertRaises(AssertionError): + check_deprecation_keys( + {"properties": {"properties": {"new": True}}}, "new" + ) + + # test the in-repo schema + schema = get_schema() + assert check_deprecation_keys(schema, "new") + assert check_deprecation_keys(schema, "changed") + assert check_deprecation_keys(schema, "deprecated") + + class TestGetSchema: def test_static_schema_file_is_valid(self, caplog): with caplog.at_level(logging.WARNING): @@ -402,13 +451,17 @@ class TestValidateCloudConfigSchema: "a-b": { "type": "string", "deprecated": True, + "deprecated_version": "22.1", + "new": True, + "new_version": "22.1", "description": "<desc>", }, "a_b": {"type": "string", "description": "noop"}, }, }, {"a-b": "asdf"}, - "Deprecated cloud-config provided:\na-b: DEPRECATED: <desc>", + "Deprecated cloud-config provided:\na-b: <desc> " + "Deprecated in version 22.1.", ), ( { @@ -420,6 +473,7 @@ class TestValidateCloudConfigSchema: { "type": "string", "deprecated": True, + "deprecated_version": "22.1", "description": "<desc>", }, ] @@ -427,7 +481,8 @@ class TestValidateCloudConfigSchema: }, }, {"x": "+5"}, - "Deprecated cloud-config provided:\nx: DEPRECATED: <desc>", + "Deprecated cloud-config provided:\nx: <desc> " + "Deprecated in version 22.1.", ), ( { @@ -438,6 +493,8 @@ class TestValidateCloudConfigSchema: {"type": "string", "description": "noop"}, { "deprecated": True, + "deprecated_version": "22.1", + "deprecated_description": "<dep desc>", "description": "<desc>", }, ] @@ -445,7 +502,8 @@ class TestValidateCloudConfigSchema: }, }, {"x": "5"}, - "Deprecated cloud-config provided:\nx: DEPRECATED: <desc>", + "Deprecated cloud-config provided:\nx: <desc> " + "Deprecated in version 22.1. <dep desc>", ), ( { @@ -457,6 +515,7 @@ class TestValidateCloudConfigSchema: { "type": "string", "deprecated": True, + "deprecated_version": "22.1", "description": "<desc>", }, ] @@ -464,7 +523,8 @@ class TestValidateCloudConfigSchema: }, }, {"x": "5"}, - "Deprecated cloud-config provided:\nx: DEPRECATED: <desc>", + "Deprecated cloud-config provided:\nx: <desc> " + "Deprecated in version 22.1.", ), ( { @@ -473,12 +533,14 @@ class TestValidateCloudConfigSchema: "x": { "type": "string", "deprecated": True, + "deprecated_version": "22.1", "description": "<desc>", }, }, }, {"x": "+5"}, - "Deprecated cloud-config provided:\nx: DEPRECATED: <desc>", + "Deprecated cloud-config provided:\nx: <desc> " + "Deprecated in version 22.1.", ), ( { @@ -500,6 +562,7 @@ class TestValidateCloudConfigSchema: "$defs": { "my_ref": { "deprecated": True, + "deprecated_version": "32.3", "description": "<desc>", } }, @@ -513,7 +576,8 @@ class TestValidateCloudConfigSchema: }, }, {"x": "+5"}, - "Deprecated cloud-config provided:\nx: DEPRECATED: <desc>", + "Deprecated cloud-config provided:\nx: <desc> " + "Deprecated in version 32.3.", ), ( { @@ -521,6 +585,7 @@ class TestValidateCloudConfigSchema: "$defs": { "my_ref": { "deprecated": True, + "deprecated_version": "27.2", } }, "properties": { @@ -536,7 +601,8 @@ class TestValidateCloudConfigSchema: }, }, {"x": "+5"}, - "Deprecated cloud-config provided:\nx: DEPRECATED.", + "Deprecated cloud-config provided:\nx: Deprecated in " + "version 27.2.", ), ( { @@ -545,12 +611,14 @@ class TestValidateCloudConfigSchema: "^.+$": { "minItems": 1, "deprecated": True, + "deprecated_version": "27.2", "description": "<desc>", } }, }, {"a-b": "asdf"}, - "Deprecated cloud-config provided:\na-b: DEPRECATED: <desc>", + "Deprecated cloud-config provided:\na-b: <desc> " + "Deprecated in version 27.2.", ), pytest.param( { @@ -559,11 +627,17 @@ class TestValidateCloudConfigSchema: "^.+$": { "minItems": 1, "deprecated": True, + "deprecated_version": "27.2", + "changed": True, + "changed_version": "22.2", + "changed_description": "Drop ballast.", } }, }, {"a-b": "asdf"}, - "Deprecated cloud-config provided:\na-b: DEPRECATED.", + "Deprecated cloud-config provided:\na-b: Deprecated " + "in version 27.2.\na-b: Changed in version 22.2. " + "Drop ballast.", id="deprecated_pattern_property_without_description", ), ], @@ -792,7 +866,7 @@ class TestSchemaDocMarkdown: **Supported distros:** debian, rhel **Config schema**: - **prop1:** (array of integer) prop-description. + **prop1:** (array of integer) prop-description **Examples**:: @@ -812,12 +886,12 @@ class TestSchemaDocMarkdown: "properties": { "prop1": { "type": "array", - "description": "prop-description", + "description": "prop-description.", "items": {"type": "string"}, }, "prop2": { "type": "boolean", - "description": "prop2-description", + "description": "prop2-description.", }, }, } @@ -892,7 +966,7 @@ class TestSchemaDocMarkdown: "patternProperties": { "^.+$": { "label": "<opaque_label>", - "description": "List of cool strings", + "description": "List of cool strings.", "type": "array", "items": {"type": "string"}, "minItems": 1, @@ -1046,7 +1120,7 @@ class TestSchemaDocMarkdown: "properties": { "prop1": { "type": "array", - "description": "prop-description", + "description": "prop-description.", "items": {"type": "integer"}, } }, @@ -1102,7 +1176,7 @@ class TestSchemaDocMarkdown: - option2 - option3 - The default value is option1. + The default value is option1 """ ) @@ -1213,7 +1287,9 @@ class TestSchemaDocMarkdown: } } }, - "**prop1:** (string/integer) DEPRECATED: <description>", + "**prop1:** (string/integer) <description>\n\n " + "*Deprecated in version <missing deprecated_version " + "key, please file a bug report>.*", ), ( { @@ -1223,10 +1299,40 @@ class TestSchemaDocMarkdown: "type": ["string", "integer"], "description": "<description>", "deprecated": True, + "deprecated_version": "2", + "changed": True, + "changed_version": "1", + "new": True, + "new_version": "1", }, }, }, - "**prop1:** (string/integer) DEPRECATED: <description>", + "**prop1:** (string/integer) <description>\n\n " + "*Deprecated in version 2.*\n\n *Changed in version" + " 1.*\n\n *New in version 1.*", + ), + ( + { + "$schema": "http://json-schema.org/draft-04/schema#", + "properties": { + "prop1": { + "type": ["string", "integer"], + "description": "<description>", + "deprecated": True, + "deprecated_version": "2", + "deprecated_description": "dep", + "changed": True, + "changed_version": "1", + "changed_description": "chg", + "new": True, + "new_version": "1", + "new_description": "new", + }, + }, + }, + "**prop1:** (string/integer) <description>\n\n " + "*Deprecated in version 2. dep*\n\n *Changed in " + "version 1. chg*\n\n *New in version 1. new*", ), ( { @@ -1244,7 +1350,9 @@ class TestSchemaDocMarkdown: } }, }, - "**prop1:** (string/integer) DEPRECATED: <description>", + "**prop1:** (string/integer) <description>\n\n " + "*Deprecated in version <missing deprecated_version " + "key, please file a bug report>.*", ), ( { @@ -1264,7 +1372,9 @@ class TestSchemaDocMarkdown: } }, }, - "**prop1:** (string/integer) DEPRECATED: <description>", + "**prop1:** (string/integer) <description>\n\n " + "*Deprecated in version <missing deprecated_version " + "key, please file a bug report>.*", ), ( { @@ -1275,14 +1385,17 @@ class TestSchemaDocMarkdown: "anyOf": [ { "type": ["string", "integer"], - "description": "<deprecated_description>", + "description": "<deprecated_description>.", "deprecated": True, }, ], }, }, }, - "**prop1:** (UNDEFINED) <description>. DEPRECATED: <deprecat", + "**prop1:** (UNDEFINED) <description>. " + "<deprecated_description>.\n\n *Deprecated in " + "version <missing deprecated_version key, please " + "file a bug report>.*", ), ( { @@ -1292,7 +1405,7 @@ class TestSchemaDocMarkdown: "anyOf": [ { "type": ["string", "integer"], - "description": "<deprecated_description>", + "description": "<deprecated_description>.", "deprecated": True, }, { @@ -1303,8 +1416,9 @@ class TestSchemaDocMarkdown: }, }, }, - "**prop1:** (number) <description>. DEPRECATED:" - " <deprecated_description>", + "**prop1:** (number) <deprecated_description>.\n\n" + " *Deprecated in version <missing " + "deprecated_version key, please file a bug report>.*", ), ( { @@ -1316,6 +1430,7 @@ class TestSchemaDocMarkdown: "type": ["string", "integer"], "description": "<deprecated_description>", "deprecated": True, + "deprecated_version": "22.1", }, { "type": "string", @@ -1326,8 +1441,9 @@ class TestSchemaDocMarkdown: }, }, }, - "**prop1:** (``none``/``unchanged``/``os``) <description>." - " DEPRECATED: <deprecated_description>.", + "**prop1:** (``none``/``unchanged``/``os``) " + "<description>. <deprecated_description>\n\n " + "*Deprecated in version 22.1.*", ), ( { @@ -1348,8 +1464,9 @@ class TestSchemaDocMarkdown: }, }, }, - "**prop1:** (string/integer/``none``/``unchanged``/``os``)" - " <description_1>. <description>_2.\n", + "**prop1:** (string/integer/``none``/" + "``unchanged``/``os``) <description_1>. " + "<description>_2\n", ), ( { @@ -1370,7 +1487,7 @@ class TestSchemaDocMarkdown: }, }, }, - "**prop1:** (array of object) <desc_1>.\n", + "**prop1:** (array of object) <desc_1>\n", ), ], ) @@ -1753,15 +1870,14 @@ class TestHandleSchemaArgs: #cloud-config packages: - htop - apt_update: true # D1 - apt_upgrade: true # D2 - apt_reboot_if_required: true # D3 + apt_update: true # D1 + apt_upgrade: true # D2 + apt_reboot_if_required: true # D3 # Deprecations: ------------- - # D1: DEPRECATED: Dropped after April 2027. Use ``package_update``. Default: ``false`` - # D2: DEPRECATED: Dropped after April 2027. Use ``package_upgrade``. Default: ``false`` - # D3: DEPRECATED: Dropped after April 2027. Use ``package_reboot_if_required``. Default: ``false`` - + # D1: Default: ``false``. Deprecated in version 22.2. Use ``package_update`` instead. + # D2: Default: ``false``. Deprecated in version 22.2. Use ``package_upgrade`` instead. + # D3: Default: ``false``. Deprecated in version 22.2. Use ``package_reboot_if_required`` instead. Valid cloud-config: {cfg_file} """ # noqa: E501 @@ -1772,9 +1888,11 @@ class TestHandleSchemaArgs: dedent( """\ Cloud config schema deprecations: \ -apt_reboot_if_required: DEPRECATED: Dropped after April 2027. Use ``package_reboot_if_required``. Default: ``false``, \ -apt_update: DEPRECATED: Dropped after April 2027. Use ``package_update``. Default: ``false``, \ -apt_upgrade: DEPRECATED: Dropped after April 2027. Use ``package_upgrade``. Default: ``false`` +apt_reboot_if_required: Default: ``false``. Deprecated in version 22.2.\ + Use ``package_reboot_if_required`` instead., apt_update: Default: \ +``false``. Deprecated in version 22.2. Use ``package_update`` instead.,\ + apt_upgrade: Default: ``false``. Deprecated in version 22.2. Use \ +``package_upgrade`` instead.\ Valid cloud-config: {cfg_file} """ # noqa: E501 ), @@ -1806,6 +1924,9 @@ apt_upgrade: DEPRECATED: Dropped after April 2027. Use ``package_upgrade``. Defa ) handle_schema_args("unused", args) out, err = capsys.readouterr() - assert expected_output.format(cfg_file=user_data_fn) == out + assert ( + expected_output.format(cfg_file=user_data_fn).split() + == out.split() + ) assert not err assert "deprec" not in caplog.text |