summaryrefslogtreecommitdiff
path: root/cloudinit/util.py
diff options
context:
space:
mode:
Diffstat (limited to 'cloudinit/util.py')
-rw-r--r--cloudinit/util.py83
1 files changed, 82 insertions, 1 deletions
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