summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrett Holman <brett.holman@canonical.com>2023-02-24 08:00:44 -0700
committerGitHub <noreply@github.com>2023-02-24 08:00:44 -0700
commit6100fda632f63605981636c0900e1e0b8b354979 (patch)
treedab05196ac0cc299acd0c672053e3506c0ade213
parent46fcd03187d70f405c748f7a6cfdb02ecb8c6ee7 (diff)
downloadcloud-init-git-6100fda632f63605981636c0900e1e0b8b354979.tar.gz
log: Add standardized deprecation tooling (SC-1312) (#2026)
- Add deprecation log level - Add deprecation utilities for structured format and messaging - Update existing deprecation log sites, add deprecated versions
-rwxr-xr-xcloudinit/cmd/main.py10
-rw-r--r--cloudinit/config/cc_apt_configure.py20
-rw-r--r--cloudinit/config/cc_ca_certs.py14
-rw-r--r--cloudinit/config/cc_growpart.py7
-rw-r--r--cloudinit/config/cc_rsyslog.py6
-rw-r--r--cloudinit/config/cc_set_passwords.py22
-rw-r--r--cloudinit/config/cc_update_etc_hosts.py7
-rw-r--r--cloudinit/config/schema.py4
-rw-r--r--cloudinit/distros/__init__.py22
-rw-r--r--cloudinit/distros/ug_util.py15
-rw-r--r--cloudinit/log.py11
-rw-r--r--cloudinit/net/network_state.py9
-rw-r--r--cloudinit/netinfo.py8
-rw-r--r--cloudinit/stages.py9
-rw-r--r--cloudinit/util.py83
-rw-r--r--doc/rtd/reference/faq.rst15
-rw-r--r--tests/integration_tests/modules/test_combined.py12
-rw-r--r--tests/unittests/config/test_cc_ca_certs.py21
-rw-r--r--tests/unittests/config/test_schema.py6
-rw-r--r--tests/unittests/distros/test_create_users.py22
-rw-r--r--tests/unittests/net/test_network_state.py7
-rw-r--r--tests/unittests/test_log.py30
-rw-r--r--tests/unittests/test_net.py3
23 files changed, 267 insertions, 96 deletions
diff --git a/cloudinit/cmd/main.py b/cloudinit/cmd/main.py
index f28fda15..f16c59a7 100755
--- a/cloudinit/cmd/main.py
+++ b/cloudinit/cmd/main.py
@@ -219,11 +219,11 @@ def attempt_cmdline_url(path, network=True, cmdline=None) -> Tuple[int, str]:
is_cloud_cfg = False
if is_cloud_cfg:
if cmdline_name == "url":
- LOG.warning(
- "DEPRECATED: `url` kernel command line key is"
- " deprecated for providing cloud-config via URL."
- " Please use `cloud-config-url` kernel command line"
- " parameter instead"
+ util.deprecate(
+ deprecated="The kernel command line key `url`",
+ deprecated_version="22.3",
+ extra_message=" Please use `cloud-config-url` "
+ "kernel command line parameter instead",
)
else:
if cmdline_name == "cloud-config-url":
diff --git a/cloudinit/config/cc_apt_configure.py b/cloudinit/config/cc_apt_configure.py
index 98957f8d..17c2fb58 100644
--- a/cloudinit/config/cc_apt_configure.py
+++ b/cloudinit/config/cc_apt_configure.py
@@ -614,9 +614,10 @@ def add_apt_sources(
def convert_v1_to_v2_apt_format(srclist):
"""convert v1 apt format to v2 (dict in apt_sources)"""
srcdict = {}
- LOG.warning(
- "DEPRECATION: 'apt_sources' deprecated config key found."
- " Use 'apt' instead"
+ util.deprecate(
+ deprecated="Config key 'apt_sources'",
+ deprecated_version="22.1",
+ extra_message="Use 'apt' instead",
)
if isinstance(srclist, list):
LOG.debug("apt config: convert V1 to V2 format (source list to dict)")
@@ -692,18 +693,17 @@ def convert_v2_to_v3_apt_format(oldcfg):
# no old config, so no new one to be created
if not needtoconvert:
return oldcfg
- LOG.warning(
- "DEPRECATION apt: converted deprecated config V2 to V3 format for"
- " keys '%s'. Use updated config keys.",
- ", ".join(needtoconvert),
+ util.deprecate(
+ deprecated=f"The following config key(s): {needtoconvert}",
+ deprecated_version="22.1",
)
# if old AND new config are provided, prefer the new one (LP #1616831)
newaptcfg = oldcfg.get("apt", None)
if newaptcfg is not None:
- LOG.warning(
- "DEPRECATION: apt config: deprecated V1/2 and V3 format specified,"
- " preferring V3"
+ util.deprecate(
+ deprecated="Support for combined old and new apt module keys",
+ deprecated_version="22.1",
)
for oldkey in needtoconvert:
newkey = mapoldkeys[oldkey]
diff --git a/cloudinit/config/cc_ca_certs.py b/cloudinit/config/cc_ca_certs.py
index 51b8577c..b1c4a2bf 100644
--- a/cloudinit/config/cc_ca_certs.py
+++ b/cloudinit/config/cc_ca_certs.py
@@ -229,9 +229,10 @@ def handle(
@param args: Any module arguments from cloud.cfg
"""
if "ca-certs" in cfg:
- LOG.warning(
- "DEPRECATION: key 'ca-certs' is now deprecated. Use 'ca_certs'"
- " instead."
+ util.deprecate(
+ deprecated="Key 'ca-certs'",
+ deprecated_version="22.1",
+ extra_message="Use 'ca_certs' instead.",
)
elif "ca_certs" not in cfg:
LOG.debug(
@@ -251,9 +252,10 @@ def handle(
# If there is a remove_defaults option set to true, disable the system
# default trusted CA certs first.
if "remove-defaults" in ca_cert_cfg:
- LOG.warning(
- "DEPRECATION: key 'ca-certs.remove-defaults' is now deprecated."
- " Use 'ca_certs.remove_defaults' instead."
+ util.deprecate(
+ deprecated="Key 'remove-defaults'",
+ deprecated_version="22.1",
+ extra_message="Use 'remove_defaults' instead.",
)
if ca_cert_cfg.get(
"remove_defaults", ca_cert_cfg.get("remove-defaults", False)
diff --git a/cloudinit/config/cc_growpart.py b/cloudinit/config/cc_growpart.py
index f3acbe2a..2a496820 100644
--- a/cloudinit/config/cc_growpart.py
+++ b/cloudinit/config/cc_growpart.py
@@ -583,9 +583,10 @@ def handle(
mode = mycfg.get("mode", "auto")
if util.is_false(mode):
if mode != "off":
- log.warning(
- f"DEPRECATED: growpart mode '{mode}' is deprecated. "
- "Use 'off' instead."
+ util.deprecate(
+ deprecated="Growpart's 'mode' key with value '{mode}'",
+ deprecated_version="22.2",
+ extra_message="Use 'off' instead.",
)
log.debug("growpart disabled: mode=%s" % mode)
return
diff --git a/cloudinit/config/cc_rsyslog.py b/cloudinit/config/cc_rsyslog.py
index 9baaf094..47ade927 100644
--- a/cloudinit/config/cc_rsyslog.py
+++ b/cloudinit/config/cc_rsyslog.py
@@ -107,9 +107,9 @@ def load_config(cfg: dict) -> dict:
mycfg = cfg.get("rsyslog", {})
if isinstance(cfg.get("rsyslog"), list):
- LOG.warning(
- "DEPRECATION: This rsyslog list format is deprecated and will be "
- "removed in a future version of cloud-init. Use documented keys."
+ util.deprecate(
+ deprecated="The rsyslog key with value of type 'list'",
+ deprecated_version="22.2",
)
mycfg = {KEYNAME_CONFIGS: cfg.get("rsyslog")}
if KEYNAME_LEGACY_FILENAME in cfg:
diff --git a/cloudinit/config/cc_set_passwords.py b/cloudinit/config/cc_set_passwords.py
index 3a0b3f5b..bd27d533 100644
--- a/cloudinit/config/cc_set_passwords.py
+++ b/cloudinit/config/cc_set_passwords.py
@@ -133,10 +133,10 @@ def handle_ssh_pwauth(pw_auth, distro: Distro):
cfg_name = "PasswordAuthentication"
if isinstance(pw_auth, str):
- LOG.warning(
- "DEPRECATION: The 'ssh_pwauth' config key should be set to "
- "a boolean value. The string format is deprecated and will be "
- "removed in a future version of cloud-init."
+ util.deprecate(
+ deprecated="Using a string value for the 'ssh_pwauth' key",
+ deprecated_version="22.2",
+ extra_message="Use a boolean value with 'ssh_pwauth'.",
)
if util.is_true(pw_auth):
cfg_val = "yes"
@@ -192,17 +192,19 @@ def handle(
chfg = cfg["chpasswd"]
users_list = util.get_cfg_option_list(chfg, "users", default=[])
if "list" in chfg and chfg["list"]:
- log.warning(
- "DEPRECATION: key 'lists' is now deprecated. Use 'users'."
+ util.deprecate(
+ deprecated="Config key 'lists'",
+ deprecated_version="22.3",
+ extra_message="Use 'users' instead.",
)
if isinstance(chfg["list"], list):
log.debug("Handling input for chpasswd as list.")
plist = util.get_cfg_option_list(chfg, "list", plist)
else:
- log.warning(
- "DEPRECATION: The chpasswd multiline string format is "
- "deprecated and will be removed from a future version of "
- "cloud-init. Use the list format instead."
+ util.deprecate(
+ deprecated="The chpasswd multiline string",
+ deprecated_version="22.2",
+ extra_message="Use string type instead.",
)
log.debug("Handling input for chpasswd as multiline string.")
multiline = util.get_cfg_option_str(chfg, "list")
diff --git a/cloudinit/config/cc_update_etc_hosts.py b/cloudinit/config/cc_update_etc_hosts.py
index 7bb9dff0..695bc019 100644
--- a/cloudinit/config/cc_update_etc_hosts.py
+++ b/cloudinit/config/cc_update_etc_hosts.py
@@ -106,9 +106,10 @@ def handle(
if util.translate_bool(manage_hosts, addons=["template"]):
if manage_hosts == "template":
- log.warning(
- "DEPRECATED: please use manage_etc_hosts: true instead of"
- " 'template'"
+ util.deprecate(
+ deprecated="Value 'template' for key 'manage_etc_hosts'",
+ deprecated_version="22.2",
+ extra_message="Use 'true' instead.",
)
(hostname, fqdn, _) = util.get_hostname_fqdn(cfg, cloud)
if not hostname:
diff --git a/cloudinit/config/schema.py b/cloudinit/config/schema.py
index b0b5fccf..9bccdcec 100644
--- a/cloudinit/config/schema.py
+++ b/cloudinit/config/schema.py
@@ -476,7 +476,9 @@ def validate_cloudconfig_schema(
prefix="Deprecated cloud-config provided:\n",
separator="\n",
)
- LOG.warning(message)
+ # This warning doesn't fit the standardized util.deprecated() utility
+ # format, but it is a deprecation log, so log it directly.
+ LOG.deprecated(message) # type: ignore
if strict and (errors or deprecations):
raise SchemaValidationError(errors, deprecations)
if errors:
diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py
index 940b689e..40789297 100644
--- a/cloudinit/distros/__init__.py
+++ b/cloudinit/distros/__init__.py
@@ -549,12 +549,12 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
groups = groups.split(",")
if isinstance(groups, dict):
- LOG.warning(
- "DEPRECATED: The user %s has a 'groups' config value of"
- " type dict which is deprecated and will be removed in a"
- " future version of cloud-init. Use a comma-delimited"
- " string or array instead: group1,group2.",
- name,
+ util.deprecate(
+ deprecated=f"The user {name} has a 'groups' config value "
+ "of type dict",
+ deprecated_version="22.3",
+ extra_message="Use a comma-delimited string or "
+ "array instead: group1,group2.",
)
# remove any white spaces in group names, most likely
@@ -682,11 +682,11 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
if kwargs["sudo"]:
self.write_sudo_rules(name, kwargs["sudo"])
elif kwargs["sudo"] is False:
- LOG.warning(
- "DEPRECATED: The user %s has a 'sudo' config value of"
- " 'false' which will be dropped after April 2027."
- " Use 'null' instead.",
- name,
+ util.deprecate(
+ deprecated=f"The value of 'false' in user {name}'s "
+ "'sudo' config",
+ deprecated_version="22.3",
+ extra_message="Use 'null' instead.",
)
# Import SSH keys
diff --git a/cloudinit/distros/ug_util.py b/cloudinit/distros/ug_util.py
index e0a4d068..2697527d 100644
--- a/cloudinit/distros/ug_util.py
+++ b/cloudinit/distros/ug_util.py
@@ -174,9 +174,10 @@ def normalize_users_groups(cfg, distro):
# Translate it into a format that will be more useful going forward
if isinstance(old_user, str):
old_user = {"name": old_user}
- LOG.warning(
- "DEPRECATED: 'user' of type string is deprecated and will"
- " be removed in a future release. Use 'users' list instead."
+ util.deprecate(
+ deprecated="'user' of type string",
+ deprecated_version="22.2",
+ extra_message="Use 'users' list instead.",
)
elif not isinstance(old_user, dict):
LOG.warning(
@@ -206,10 +207,10 @@ def normalize_users_groups(cfg, distro):
base_users = cfg.get("users", [])
if isinstance(base_users, (dict, str)):
- LOG.warning(
- "DEPRECATED: 'users' of type %s is deprecated and will be removed"
- " in a future release. Use 'users' as a list.",
- type(base_users),
+ util.deprecate(
+ deprecated=f"'users' of type {type(base_users)}",
+ deprecated_version="22.2",
+ extra_message="Use 'users' as a list.",
)
elif not isinstance(base_users, (list)):
LOG.warning(
diff --git a/cloudinit/log.py b/cloudinit/log.py
index f40201bb..d9912a50 100644
--- a/cloudinit/log.py
+++ b/cloudinit/log.py
@@ -63,6 +63,16 @@ def flushLoggers(root):
flushLoggers(root.parent)
+def defineDeprecationLogger(lvl=35):
+ logging.addLevelName(lvl, "DEPRECATED")
+
+ def deprecated(self, message, *args, **kwargs):
+ if self.isEnabledFor(lvl):
+ self._log(lvl, message, args, **kwargs)
+
+ logging.Logger.deprecated = deprecated
+
+
def setupLogging(cfg=None):
# See if the config provides any logging conf...
if not cfg:
@@ -83,6 +93,7 @@ def setupLogging(cfg=None):
log_cfgs.append("\n".join(cfg_str))
else:
log_cfgs.append(str(a_cfg))
+ defineDeprecationLogger()
# See if any of them actually load...
am_tried = 0
diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py
index f88b1321..790398bc 100644
--- a/cloudinit/net/network_state.py
+++ b/cloudinit/net/network_state.py
@@ -86,10 +86,11 @@ NET_CONFIG_TO_V2: Dict[str, Dict[str, Any]] = {
def warn_deprecated_all_devices(dikt: dict) -> None:
"""Warn about deprecations of v2 properties for all devices"""
if "gateway4" in dikt or "gateway6" in dikt:
- LOG.warning(
- "DEPRECATED: The use of `gateway4` and `gateway6` is"
- " deprecated. For more info check out: "
- "https://cloudinit.readthedocs.io/en/latest/topics/network-config-format-v2.html" # noqa: E501
+ util.deprecate(
+ deprecated="The use of `gateway4` and `gateway6`",
+ deprecated_version="22.4",
+ extra_message="For more info check out: "
+ "https://cloudinit.readthedocs.io/en/latest/topics/network-config-format-v2.html", # noqa: E501
)
diff --git a/cloudinit/netinfo.py b/cloudinit/netinfo.py
index 5eeeb967..a3bca86e 100644
--- a/cloudinit/netinfo.py
+++ b/cloudinit/netinfo.py
@@ -94,11 +94,13 @@ def _netdev_info_iproute_json(ipaddr_json):
return devs
+@util.deprecate_call(
+ deprecated_version="22.1",
+ extra_message="Required by old iproute2 versions that don't "
+ "support ip json output. Consider upgrading to a more recent version.",
+)
def _netdev_info_iproute(ipaddr_out):
"""
- DEPRECATED: Only used on distros that don't support ip json output
- Use _netdev_info_iproute_json() when possible.
-
@param ipaddr_out: Output string from 'ip addr show' command.
@returns: A dict of device info keyed by network device name containing
diff --git a/cloudinit/stages.py b/cloudinit/stages.py
index 9494a0bf..a063e778 100644
--- a/cloudinit/stages.py
+++ b/cloudinit/stages.py
@@ -758,10 +758,11 @@ class Init:
return
if isinstance(enabled, str):
- LOG.debug(
- "Use of string '%s' for 'vendor_data:enabled' field "
- "is deprecated. Use boolean value instead",
- enabled,
+ util.deprecate(
+ deprecated=f"Use of string '{enabled}' for "
+ "'vendor_data:enabled' field",
+ deprecated_version="23.1",
+ extra_message="Use boolean value instead.",
)
LOG.debug(
diff --git a/cloudinit/util.py b/cloudinit/util.py
index 8ba3e2b6..aed332ec 100644
--- a/cloudinit/util.py
+++ b/cloudinit/util.py
@@ -11,6 +11,7 @@
import contextlib
import copy as obj_copy
import email
+import functools
import glob
import grp
import gzip
@@ -36,7 +37,7 @@ from collections import deque, namedtuple
from errno import EACCES, ENOENT
from functools import lru_cache, total_ordering
from pathlib import Path
-from typing import Callable, Deque, Dict, List, TypeVar
+from typing import Callable, Deque, Dict, List, Optional, TypeVar
from urllib import parse
from cloudinit import features, importer
@@ -3097,3 +3098,83 @@ class Version(namedtuple("Version", ["major", "minor", "patch", "rev"])):
if self.rev > other.rev:
return 1
return -1
+
+
+def deprecate(
+ *,
+ deprecated: str,
+ deprecated_version: str,
+ extra_message: Optional[str] = None,
+ schedule: int = 5,
+):
+ """Mark a "thing" as deprecated. Deduplicated deprecations are
+ logged.
+
+ @param deprecated: Noun to be deprecated. Write this as the start
+ of a sentence, with no period. Version and extra message will
+ be appended.
+ @param deprecated_version: The version in which the thing was
+ deprecated
+ @param extra_message: A remedy for the user's problem. A good
+ message will be actionable and specific (i.e., don't use a
+ generic "Use updated key." if the user used a deprecated key).
+ End the string with a period.
+ @param schedule: Manually set the deprecation schedule. Defaults to
+ 5 years. Leave a comment explaining your reason for deviation if
+ setting this value.
+
+ Note: uses keyword-only arguments to improve legibility
+ """
+ if not hasattr(deprecate, "_log"):
+ deprecate._log = set() # type: ignore
+ message = extra_message or ""
+ dedup = hash(deprecated + message + deprecated_version + str(schedule))
+ version = Version.from_str(deprecated_version)
+ version_removed = Version(version.major + schedule, version.minor)
+ if dedup not in deprecate._log: # type: ignore
+ deprecate._log.add(dedup) # type: ignore
+ deprecate_msg = (
+ f"{deprecated} is deprecated in "
+ f"{deprecated_version} and scheduled to be removed in "
+ f"{version_removed}. {message}"
+ ).rstrip()
+ if hasattr(LOG, "deprecated"):
+ LOG.deprecated(deprecate_msg)
+ else:
+ LOG.warning(deprecate_msg)
+
+
+def deprecate_call(
+ *, deprecated_version: str, extra_message: str, schedule: int = 5
+):
+ """Mark a "thing" as deprecated. Deduplicated deprecations are
+ logged.
+
+ @param deprecated_version: The version in which the thing was
+ deprecated
+ @param extra_message: A remedy for the user's problem. A good
+ message will be actionable and specific (i.e., don't use a
+ generic "Use updated key." if the user used a deprecated key).
+ End the string with a period.
+ @param schedule: Manually set the deprecation schedule. Defaults to
+ 5 years. Leave a comment explaining your reason for deviation if
+ setting this value.
+
+ Note: uses keyword-only arguments to improve legibility
+ """
+
+ def wrapper(func):
+ @functools.wraps(func)
+ def decorator(*args, **kwargs):
+ # don't log message multiple times
+ out = func(*args, **kwargs)
+ deprecate(
+ deprecated_version=deprecated_version,
+ deprecated=func.__name__,
+ extra_message=extra_message,
+ )
+ return out
+
+ return decorator
+
+ return wrapper
diff --git a/doc/rtd/reference/faq.rst b/doc/rtd/reference/faq.rst
index 87aade59..ba741741 100644
--- a/doc/rtd/reference/faq.rst
+++ b/doc/rtd/reference/faq.rst
@@ -235,6 +235,20 @@ to their respective support channels. For Subiquity autoinstaller that is via
IRC (``#ubuntu-server`` on Libera) or Discourse. For Juju support see their
`discourse page`_.
+
+Can I use cloud-init as a library?
+==================================
+Yes, in fact some projects `already do`_. However, ``cloud-init`` does not
+currently make any API guarantees to external consumers - current library
+users are projects that have close contact with ``cloud-init``, which is why
+this model currently works.
+
+It is worth mentioning for library users that ``cloud-init`` defines a custom
+log level. This log level, ``35``, is dedicated to logging info
+related to deprecation information. Users of ``cloud-init`` as a library
+may wish to ensure that this log level doesn't collide with external
+libraries that define their own custom log levels.
+
Where can I learn more?
=======================
@@ -279,6 +293,7 @@ Whitepapers:
.. _validate-yaml.py: https://github.com/canonical/cloud-init/blob/main/tools/validate-yaml.py
.. _Juju: https://ubuntu.com/blog/topics/juju
.. _discourse page: https://discourse.charmhub.io
+.. _already do: https://github.com/canonical/ubuntu-advantage-client/blob/9b46480b9e4b88e918bac5ced0d4b8edb3cbbeab/lib/auto_attach.py#L35
.. _cloud-init - The Good Parts: https://www.youtube.com/watch?v=2_m6EUo6VOI
.. _Utilising cloud-init on Microsoft Azure (Whitepaper): https://ubuntu.com/engage/azure-cloud-init-whitepaper
diff --git a/tests/integration_tests/modules/test_combined.py b/tests/integration_tests/modules/test_combined.py
index 647e8728..264e2383 100644
--- a/tests/integration_tests/modules/test_combined.py
+++ b/tests/integration_tests/modules/test_combined.py
@@ -29,6 +29,10 @@ from tests.integration_tests.util import (
USER_DATA = """\
#cloud-config
+users:
+- default
+- name: craig
+ sudo: false # make sure craig doesn't get elevated perms
apt:
primary:
- arches: [default]
@@ -113,6 +117,14 @@ class TestCombined:
assert re.search(expected, log)
+ def test_deprecated_message(self, class_client: IntegrationInstance):
+ """Check that deprecated key produces a log warning"""
+ client = class_client
+ log = client.read_from_file("/var/log/cloud-init.log")
+ assert "Deprecated cloud-config provided" in log
+ assert "The value of 'false' in user craig's 'sudo' config is " in log
+ assert 2 == log.count("DEPRECATE")
+
def test_ntp_with_apt(self, class_client: IntegrationInstance):
"""LP #1628337.
diff --git a/tests/unittests/config/test_cc_ca_certs.py b/tests/unittests/config/test_cc_ca_certs.py
index 6db17485..adc3609a 100644
--- a/tests/unittests/config/test_cc_ca_certs.py
+++ b/tests/unittests/config/test_cc_ca_certs.py
@@ -9,7 +9,9 @@ from unittest import mock
import pytest
-from cloudinit import distros, helpers, subp, util
+from cloudinit import distros, helpers
+from cloudinit import log as logger
+from cloudinit import subp, util
from cloudinit.config import cc_ca_certs
from cloudinit.config.schema import (
SchemaValidationError,
@@ -424,25 +426,23 @@ class TestCACertsSchema:
@mock.patch.object(cc_ca_certs, "update_ca_certs")
def test_deprecate_key_warnings(self, update_ca_certs, caplog):
"""Assert warnings are logged for deprecated keys."""
- log = logging.getLogger("CALogTest")
+ logger.setupLogging()
cloud = get_cloud("ubuntu")
cc_ca_certs.handle(
- "IGNORE", {"ca-certs": {"remove-defaults": False}}, cloud, log, []
+ "IGNORE", {"ca-certs": {"remove-defaults": False}}, cloud, None, []
)
expected_warnings = [
- "DEPRECATION: key 'ca-certs' is now deprecated. Use 'ca_certs'"
- " instead.",
- "DEPRECATION: key 'ca-certs.remove-defaults' is now deprecated."
- " Use 'ca_certs.remove_defaults' instead.",
+ "Key 'ca-certs' is deprecated in",
+ "Key 'remove-defaults' is deprecated in",
]
for warning in expected_warnings:
assert warning in caplog.text
+ assert "DEPRECAT" in caplog.text
assert 1 == update_ca_certs.call_count
@mock.patch.object(cc_ca_certs, "update_ca_certs")
def test_duplicate_keys(self, update_ca_certs, caplog):
"""Assert warnings are logged for deprecated keys."""
- log = logging.getLogger("CALogTest")
cloud = get_cloud("ubuntu")
cc_ca_certs.handle(
"IGNORE",
@@ -451,7 +451,7 @@ class TestCACertsSchema:
"ca_certs": {"remove_defaults": False},
},
cloud,
- log,
+ None,
[],
)
expected_warning = (
@@ -460,6 +460,3 @@ class TestCACertsSchema:
)
assert expected_warning in caplog.text
assert 1 == update_ca_certs.call_count
-
-
-# vi: ts=4 expandtab
diff --git a/tests/unittests/config/test_schema.py b/tests/unittests/config/test_schema.py
index d43af5cc..8276511d 100644
--- a/tests/unittests/config/test_schema.py
+++ b/tests/unittests/config/test_schema.py
@@ -19,7 +19,7 @@ from typing import List, Optional, Sequence, Set
import pytest
-from cloudinit import stages
+from cloudinit import log, stages
from cloudinit.config.schema import (
CLOUD_CONFIG_HEADER,
VERSIONED_USERDATA_SCHEMA_FILE,
@@ -55,6 +55,7 @@ from tests.unittests.helpers import (
from tests.unittests.util import FakeDataSource
M_PATH = "cloudinit.config.schema."
+DEPRECATED_LOG_LEVEL = 35
def get_schemas() -> dict:
@@ -645,6 +646,7 @@ class TestValidateCloudConfigSchema:
def test_validateconfig_logs_deprecations(
self, schema, config, expected_msg, log_deprecations, caplog
):
+ log.setupLogging()
validate_cloudconfig_schema(
config,
schema,
@@ -653,7 +655,7 @@ class TestValidateCloudConfigSchema:
)
if expected_msg is None:
return
- log_record = (M_PATH[:-1], logging.WARNING, expected_msg)
+ log_record = (M_PATH[:-1], DEPRECATED_LOG_LEVEL, expected_msg)
if log_deprecations:
assert log_record == caplog.record_tuples[-1]
else:
diff --git a/tests/unittests/distros/test_create_users.py b/tests/unittests/distros/test_create_users.py
index edc152e1..2a3d85b8 100644
--- a/tests/unittests/distros/test_create_users.py
+++ b/tests/unittests/distros/test_create_users.py
@@ -2,7 +2,7 @@
import re
-from cloudinit import distros, ssh_util
+from cloudinit import distros, log, ssh_util
from tests.unittests.helpers import CiTestCase, mock
from tests.unittests.util import abstract_to_concrete
@@ -140,6 +140,7 @@ class TestCreateUser(CiTestCase):
self, m_is_group, m_subp, m_is_snappy
):
"""users.groups supports a dict value, but emit deprecation log."""
+ log.setupLogging()
user = "foouser"
self.dist.create_user(user, groups={"group1": None, "group2": None})
expected = [
@@ -150,10 +151,15 @@ class TestCreateUser(CiTestCase):
]
self.assertEqual(m_subp.call_args_list, expected)
self.assertIn(
- "WARNING: DEPRECATED: The user foouser has a 'groups' config"
- " value of type dict which is deprecated and will be removed in a"
- " future version of cloud-init. Use a comma-delimited string or"
- " array instead: group1,group2.",
+ "DEPRECAT",
+ self.logs.getvalue(),
+ )
+ self.assertIn(
+ "The user foouser has a 'groups' config value of type dict",
+ self.logs.getvalue(),
+ )
+ self.assertIn(
+ "Use a comma-delimited",
self.logs.getvalue(),
)
@@ -182,9 +188,9 @@ class TestCreateUser(CiTestCase):
],
)
self.assertIn(
- "WARNING: DEPRECATED: The user foouser has a 'sudo' config value"
- " of 'false' which will be dropped after April 2027. Use 'null'"
- " instead.",
+ "DEPRECATED: The value of 'false' in user foouser's 'sudo' "
+ "config is deprecated in 22.3 and scheduled to be removed"
+ " in 27.3. Use 'null' instead.",
self.logs.getvalue(),
)
diff --git a/tests/unittests/net/test_network_state.py b/tests/unittests/net/test_network_state.py
index 57a4436f..0e1b27b4 100644
--- a/tests/unittests/net/test_network_state.py
+++ b/tests/unittests/net/test_network_state.py
@@ -4,7 +4,7 @@ from unittest import mock
import pytest
-from cloudinit import safeyaml
+from cloudinit import log, safeyaml, util
from cloudinit.net import network_state
from cloudinit.net.netplan import Renderer as NetplanRenderer
from cloudinit.net.renderers import NAME_TO_RENDERER
@@ -214,6 +214,9 @@ class TestNetworkStateParseConfigV2:
In netplan targets we perform a passthrough and the warning is not
needed.
"""
+ log.setupLogging()
+
+ util.deprecate._log = set() # type: ignore
ncfg = safeyaml.load(
cfg.format(
gateway4="gateway4: 10.54.0.1",
@@ -233,7 +236,7 @@ class TestNetworkStateParseConfigV2:
else:
count = 0 # No deprecation as we passthrough
assert count == caplog.text.count(
- "DEPRECATED: The use of `gateway4` and `gateway6` is"
+ "The use of `gateway4` and `gateway6`"
)
diff --git a/tests/unittests/test_log.py b/tests/unittests/test_log.py
index 87c69dbb..38791ef9 100644
--- a/tests/unittests/test_log.py
+++ b/tests/unittests/test_log.py
@@ -8,6 +8,7 @@ import logging
import time
from cloudinit import log as ci_logging
+from cloudinit import util
from cloudinit.analyze.dump import CLOUD_INIT_ASCTIME_FMT
from tests.unittests.helpers import CiTestCase
@@ -57,3 +58,32 @@ class TestCloudInitLogger(CiTestCase):
self.assertLess(parsed_dt, utc_after)
self.assertLess(utc_before, utc_after)
self.assertGreater(utc_after, parsed_dt)
+
+
+class TestDeprecatedLogs:
+ def test_deprecated_log_level(self, caplog):
+ ci_logging.setupLogging()
+ log = ci_logging.getLogger()
+ log.deprecated("deprecated message")
+ assert "DEPRECATED" == caplog.records[0].levelname
+ assert "deprecated message" in caplog.text
+
+ def test_log_deduplication(self, caplog):
+ ci_logging.defineDeprecationLogger()
+ util.deprecate(
+ deprecated="stuff",
+ deprecated_version="19.1",
+ extra_message=":)",
+ )
+ util.deprecate(
+ deprecated="stuff",
+ deprecated_version="19.1",
+ extra_message=":)",
+ )
+ util.deprecate(
+ deprecated="stuff",
+ deprecated_version="19.1",
+ extra_message=":)",
+ schedule=6,
+ )
+ assert 2 == len(caplog.records)
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
index 056aaeb6..832d94fb 100644
--- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py
@@ -13,7 +13,7 @@ from typing import Optional
import pytest
from yaml.serializer import Serializer
-from cloudinit import distros, net
+from cloudinit import distros, log, net
from cloudinit import safeyaml as yaml
from cloudinit import subp, temp_utils, util
from cloudinit.net import (
@@ -5230,6 +5230,7 @@ USERCTL=no
""" # noqa: E501
),
}
+ log.setupLogging()
found = self._render_and_read(network_config=v2_data)
self._compare_files_to_expected(expected, found)