From 08a3d951d02c6371ed2d9b77408e639f064fbbe4 Mon Sep 17 00:00:00 2001 From: Mike Graves Date: Mon, 24 Jan 2022 10:42:40 -0500 Subject: [PATCH] Move module dependency functions outside of module (#342) Move module dependency functions outside of module SUMMARY This moves the has_at_least and requires functions that had been on the module to top level functions. The functions on the module now call these with a few added bits of functionality. Moving these functions to the top level and removing their requirement on having a module makes them usable in situations where we may not yet have a module, such as during client creation. ISSUE TYPE COMPONENT NAME ADDITIONAL INFORMATION Reviewed-by: Alina Buzachis Reviewed-by: None --- plugins/module_utils/k8s/client.py | 19 +-- plugins/module_utils/k8s/core.py | 134 ++++++++++-------- .../targets/k8s_validate/tasks/main.yml | 2 +- 3 files changed, 83 insertions(+), 72 deletions(-) diff --git a/plugins/module_utils/k8s/client.py b/plugins/module_utils/k8s/client.py index 1dc674fb..4cfac198 100644 --- a/plugins/module_utils/k8s/client.py +++ b/plugins/module_utils/k8s/client.py @@ -3,7 +3,6 @@ import os import hashlib -from distutils.version import LooseVersion from typing import Any, Dict, List, Optional from ansible.module_utils.six import iteritems, string_types @@ -13,6 +12,7 @@ from ansible_collections.kubernetes.core.plugins.module_utils.args_common import AUTH_ARG_SPEC, AUTH_PROXY_HEADERS_SPEC, ) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import requires try: from ansible_collections.kubernetes.core.plugins.module_utils import ( @@ -40,20 +40,9 @@ except ImportError: pass -module = None _pool = {} -def _requires_kubernetes_at_least(version: str): - if module: - module.requires("kubernetes", version) - else: - if LooseVersion(kubernetes.__version__) < LooseVersion(version): - raise Exception( - f"kubernetes >= {version} is required to use in-memory kubeconfig." - ) - - def _create_auth_spec(module=None, **kwargs) -> Dict: auth: Dict = {} # If authorization variables aren't defined, look for them in environment variables @@ -97,7 +86,6 @@ def _load_config(auth: Dict) -> None: if isinstance(kubeconfig, string_types): kubernetes.config.load_kube_config(config_file=kubeconfig, **optional_arg) elif isinstance(kubeconfig, dict): - _requires_kubernetes_at_least("17.17.0") kubernetes.config.load_kube_config_from_dict( config_dict=kubeconfig, **optional_arg ) @@ -241,6 +229,11 @@ class K8SClient: def get_api_client(module=None, **kwargs: Optional[Any]) -> K8SClient: auth_spec = _create_auth_spec(module, **kwargs) + if isinstance(auth_spec.get("kubeconfig"), dict): + if module: + module.requires("kubernetes", "17.17.0", "to use in-memory config") + else: + requires("kubernetes", "17.17.0", "to use in-memory config") configuration = _create_configuration(auth_spec) client = create_api_client(configuration) diff --git a/plugins/module_utils/k8s/core.py b/plugins/module_utils/k8s/core.py index 0deca7eb..4365922a 100644 --- a/plugins/module_utils/k8s/core.py +++ b/plugins/module_utils/k8s/core.py @@ -3,6 +3,7 @@ from typing import Optional from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import missing_required_lib +from ansible.module_utils.common.text.converters import to_text class AnsibleK8SModule: @@ -68,67 +69,84 @@ class AnsibleK8SModule: def fail_json(self, *args, **kwargs): return self._module.fail_json(*args, **kwargs) - def _gather_versions(self) -> dict: - versions = {} - try: - import jsonpatch - - versions["jsonpatch"] = jsonpatch.__version__ - except ImportError: - pass - - try: - import kubernetes - - versions["kubernetes"] = kubernetes.__version__ - except ImportError: - pass - - try: - import yaml - - versions["pyyaml"] = yaml.__version__ - except ImportError: - pass - - return versions - def has_at_least( self, dependency: str, minimum: Optional[str] = None, warn: bool = False ) -> bool: - """Check if a specific dependency is present at a minimum version. - - If a minimum version is not specified it will check only that the - dependency is present. Additionally, if ``warn`` is ``True``, a warning - will be emitted if the actual version is less than the specified - minimum version. - """ - dependencies = self._gather_versions() - current = dependencies.get(dependency) - if current is not None: - if minimum is None: - return True - supported = LooseVersion(current) >= LooseVersion(minimum) - if not supported and warn: - self.warn( - "{0}<{1} is not supported or tested. Some features may not work.".format( - dependency, minimum - ) + supported = has_at_least(dependency, minimum) + if not supported and warn: + self.warn( + "{0}<{1} is not supported or tested. Some features may not work.".format( + dependency, minimum ) - return supported - return False + ) + return supported - def requires(self, dependency: str, minimum: Optional[str] = None) -> None: - """Fail if a specific dependency is not present at a minimum version. + def requires( + self, + dependency: str, + minimum: Optional[str] = None, + reason: Optional[str] = None, + ) -> None: + try: + requires(dependency, minimum, reason=reason) + except Exception as e: + self.fail_json(msg=to_text(e)) - If a minimum version is not specified it will require only that the - dependency is present. This function calls ``fail_json()`` when the - dependency is not found at the required version and will stop module - execution. - """ - if not self.has_at_least(dependency, minimum): - if minimum is not None: - lib = "{0}>={1}".format(dependency, minimum) - else: - lib = dependency - self._module.fail_json(msg=missing_required_lib(lib)) + +def gather_versions() -> dict: + versions = {} + try: + import jsonpatch + + versions["jsonpatch"] = jsonpatch.__version__ + except ImportError: + pass + + try: + import kubernetes + + versions["kubernetes"] = kubernetes.__version__ + except ImportError: + pass + + try: + import yaml + + versions["pyyaml"] = yaml.__version__ + except ImportError: + pass + + return versions + + +def has_at_least(dependency: str, minimum: Optional[str] = None) -> bool: + """Check if a specific dependency is present at a minimum version. + + If a minimum version is not specified it will check only that the + dependency is present. + """ + dependencies = gather_versions() + current = dependencies.get(dependency) + if current is not None: + if minimum is None: + return True + supported = LooseVersion(current) >= LooseVersion(minimum) + return supported + return False + + +def requires( + dependency: str, minimum: Optional[str] = None, reason: Optional[str] = None +) -> None: + """Fail if a specific dependency is not present at a minimum version. + + If a minimum version is not specified it will require only that the + dependency is present. This function raises an exception when the + dependency is not found at the required version. + """ + if not has_at_least(dependency, minimum): + if minimum is not None: + lib = "{0}>={1}".format(dependency, minimum) + else: + lib = dependency + raise Exception(missing_required_lib(lib, reason=reason)) diff --git a/tests/integration/targets/k8s_validate/tasks/main.yml b/tests/integration/targets/k8s_validate/tasks/main.yml index f910c608..b05e2a56 100644 --- a/tests/integration/targets/k8s_validate/tasks/main.yml +++ b/tests/integration/targets/k8s_validate/tasks/main.yml @@ -201,7 +201,7 @@ - name: assert that task failed with proper message assert: that: - - '"kubernetes >= 17.17.0 is required" in _result.module_stderr' + - '"This is required to use in-memory config." in _result.msg' when: - _stat.stat.exists - _stat.stat.readable