diff --git a/changelogs/fragments/481-refactor-common.yml b/changelogs/fragments/481-refactor-common.yml new file mode 100644 index 00000000..8727330a --- /dev/null +++ b/changelogs/fragments/481-refactor-common.yml @@ -0,0 +1,3 @@ +--- +major_changes: + - refactor K8sAnsibleMixin into module_utils/k8s/ (https://github.com/ansible-collections/kubernetes.core/pull/481). diff --git a/plugins/module_utils/common.py b/plugins/module_utils/common.py index 08838a9f..6ed7c7f3 100644 --- a/plugins/module_utils/common.py +++ b/plugins/module_utils/common.py @@ -130,6 +130,7 @@ except ImportError as e: def configuration_digest(configuration, **kwargs): + """This function has been deprecated and will be removed in version 4.0.0.""" m = hashlib.sha256() for k in AUTH_ARG_MAP: if not hasattr(configuration, k): @@ -150,6 +151,8 @@ def configuration_digest(configuration, **kwargs): class unique_string(str): + """This function has been deprecated and will be removed in version 4.0.0.""" + _low = None def __hash__(self): @@ -169,6 +172,7 @@ class unique_string(str): def get_api_client(module=None, **kwargs): + """This function has been deprecated and will be removed in version 4.0.0. Please use module_utils.k8s.client.get_api_client() instead.""" auth = {} def _raise_or_fail(exc, msg): @@ -340,6 +344,11 @@ get_api_client._pool = {} class K8sAnsibleMixin(object): def __init__(self, module, pyyaml_required=True, *args, **kwargs): + module.deprecate( + msg="The K8sAnsibleMixin class has been deprecated and refactored into the module_utils/k8s/ directory.", + version="4.0.0", + collection_name="kubernetes.core", + ) if not HAS_K8S_MODULE_HELPER: module.fail_json( msg=missing_required_lib("kubernetes"), diff --git a/plugins/module_utils/copy.py b/plugins/module_utils/copy.py index 0b7b58a0..3a335f6b 100644 --- a/plugins/module_utils/copy.py +++ b/plugins/module_utils/copy.py @@ -24,6 +24,9 @@ from abc import ABCMeta, abstractmethod import tarfile # from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import AnsibleModule +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import ( + CoreException, +) from ansible.module_utils._text import to_native try: @@ -376,12 +379,17 @@ class K8SCopyToPod(K8SCopy): ) -def check_pod(k8s_ansible_mixin, module): - resource = k8s_ansible_mixin.find_resource("Pod", None, True) +def check_pod(svc): + module = svc.module namespace = module.params.get("namespace") name = module.params.get("pod") container = module.params.get("container") + try: + resource = svc.find_resource("Pod", None, True) + except CoreException as e: + module.fail_json(msg=to_native(e)) + def _fail(exc): arg = {} if hasattr(exc, "body"): @@ -398,7 +406,7 @@ def check_pod(k8s_ansible_mixin, module): module.fail_json(msg=msg, **arg) try: - result = resource.get(name=name, namespace=namespace) + result = svc.client.get(resource, name=name, namespace=namespace) containers = [ c["name"] for c in result.to_dict()["status"]["containerStatuses"] ] diff --git a/plugins/module_utils/k8s/client.py b/plugins/module_utils/k8s/client.py new file mode 100644 index 00000000..c9986d3c --- /dev/null +++ b/plugins/module_utils/k8s/client.py @@ -0,0 +1,355 @@ +# Copyright: (c) 2021, Red Hat | Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +import os +import hashlib +from typing import Any, Dict, List, Optional + +from ansible.module_utils.six import iteritems, string_types + +from ansible_collections.kubernetes.core.plugins.module_utils.args_common import ( + AUTH_ARG_MAP, + AUTH_ARG_SPEC, + AUTH_PROXY_HEADERS_SPEC, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import ( + requires as _requires, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import ( + CoreException, +) + +try: + from ansible_collections.kubernetes.core.plugins.module_utils import ( + k8sdynamicclient, + ) + from ansible_collections.kubernetes.core.plugins.module_utils.client.discovery import ( + LazyDiscoverer, + ) +except ImportError: + # Handled in module setup + pass + +try: + import kubernetes + from kubernetes.dynamic.exceptions import ( + ResourceNotFoundError, + ResourceNotUniqueError, + ) + from kubernetes.dynamic.resource import Resource +except ImportError: + # kubernetes import error is handled in module setup + # This is defined only for the sake of Ansible's checked import requirement + Resource = Any # type: ignore + +try: + import urllib3 + + urllib3.disable_warnings() +except ImportError: + # Handled in module setup + pass + + +_pool = {} + + +class unique_string(str): + _low = None + + def __hash__(self): + return id(self) + + def __eq__(self, other): + return self is other + + def lower(self): + if self._low is None: + lower = str.lower(self) + if str.__eq__(lower, self): + self._low = self + else: + self._low = unique_string(lower) + return self._low + + +def _create_auth_spec(module=None, **kwargs) -> Dict: + auth: Dict = {} + # If authorization variables aren't defined, look for them in environment variables + for true_name, arg_name in AUTH_ARG_MAP.items(): + if module and module.params.get(arg_name) is not None: + auth[true_name] = module.params.get(arg_name) + elif arg_name in kwargs and kwargs.get(arg_name) is not None: + auth[true_name] = kwargs.get(arg_name) + elif arg_name == "proxy_headers": + # specific case for 'proxy_headers' which is a dictionary + proxy_headers = {} + for key in AUTH_PROXY_HEADERS_SPEC.keys(): + env_value = os.getenv( + "K8S_AUTH_PROXY_HEADERS_{0}".format(key.upper()), None + ) + if env_value is not None: + if AUTH_PROXY_HEADERS_SPEC[key].get("type") == "bool": + env_value = env_value.lower() not in ["0", "false", "no"] + proxy_headers[key] = env_value + if proxy_headers is not {}: + auth[true_name] = proxy_headers + else: + env_value = os.getenv( + "K8S_AUTH_{0}".format(arg_name.upper()), None + ) or os.getenv("K8S_AUTH_{0}".format(true_name.upper()), None) + if env_value is not None: + if AUTH_ARG_SPEC[arg_name].get("type") == "bool": + env_value = env_value.lower() not in ["0", "false", "no"] + auth[true_name] = env_value + + return auth + + +def _load_config(auth: Dict) -> None: + kubeconfig = auth.get("kubeconfig") + optional_arg = { + "context": auth.get("context"), + "persist_config": auth.get("persist_config"), + } + if kubeconfig: + if isinstance(kubeconfig, string_types): + kubernetes.config.load_kube_config(config_file=kubeconfig, **optional_arg) + elif isinstance(kubeconfig, dict): + kubernetes.config.load_kube_config_from_dict( + config_dict=kubeconfig, **optional_arg + ) + else: + kubernetes.config.load_kube_config(config_file=None, **optional_arg) + + +def _create_configuration(auth: Dict): + def auth_set(*names: list) -> bool: + return all(auth.get(name) for name in names) + + if auth_set("host"): + # Removing trailing slashes if any from hostname + auth["host"] = auth.get("host").rstrip("/") + + if auth_set("username", "password", "host") or auth_set("api_key", "host"): + # We have enough in the parameters to authenticate, no need to load incluster or kubeconfig + pass + elif auth_set("kubeconfig") or auth_set("context"): + try: + _load_config(auth) + except Exception as err: + raise err + + else: + # First try to do incluster config, then kubeconfig + try: + kubernetes.config.load_incluster_config() + except kubernetes.config.ConfigException: + try: + _load_config(auth) + except Exception as err: + raise err + + # Override any values in the default configuration with Ansible parameters + # As of kubernetes-client v12.0.0, get_default_copy() is required here + try: + configuration = kubernetes.client.Configuration().get_default_copy() + except AttributeError: + configuration = kubernetes.client.Configuration() + + for key, value in iteritems(auth): + if key in AUTH_ARG_MAP.keys() and value is not None: + if key == "api_key": + setattr( + configuration, key, {"authorization": "Bearer {0}".format(value)} + ) + elif key == "proxy_headers": + headers = urllib3.util.make_headers(**value) + setattr(configuration, key, headers) + else: + setattr(configuration, key, value) + + return configuration + + +def _create_headers(module=None, **kwargs): + header_map = { + "impersonate_user": "Impersonate-User", + "impersonate_groups": "Impersonate-Group", + } + + headers = {} + for arg_name, header_name in header_map.items(): + value = None + if module and module.params.get(arg_name) is not None: + value = module.params.get(arg_name) + elif arg_name in kwargs and kwargs.get(arg_name) is not None: + value = kwargs.get(arg_name) + else: + value = os.getenv("K8S_AUTH_{0}".format(arg_name.upper()), None) + if value is not None: + if AUTH_ARG_SPEC[arg_name].get("type") == "list": + value = [x for x in value.split(",") if x != ""] + if value: + headers[header_name] = value + return headers + + +def _configuration_digest(configuration, **kwargs) -> str: + m = hashlib.sha256() + for k in AUTH_ARG_MAP: + if not hasattr(configuration, k): + v = None + else: + v = getattr(configuration, k) + if v and k in ["ssl_ca_cert", "cert_file", "key_file"]: + with open(str(v), "r") as fd: + content = fd.read() + m.update(content.encode()) + else: + m.update(str(v).encode()) + for k, v in kwargs.items(): + content = "{0}: {1}".format(k, v) + m.update(content.encode()) + digest = m.hexdigest() + + return digest + + +def _set_header(client, header, value): + if isinstance(value, list): + for v in value: + client.set_default_header(header_name=unique_string(header), header_value=v) + else: + client.set_default_header(header_name=header, header_value=value) + + +def cache(func): + def wrapper(*args, **kwargs): + client = None + hashable_kwargs = {} + for k, v in kwargs.items(): + if isinstance(v, list): + hashable_kwargs[k] = ",".join(sorted(v)) + else: + hashable_kwargs[k] = v + digest = _configuration_digest(*args, **hashable_kwargs) + if digest in _pool: + client = _pool[digest] + else: + client = func(*args, **kwargs) + _pool[digest] = client + + return client + + return wrapper + + +@cache +def create_api_client(configuration, **headers): + client = kubernetes.client.ApiClient(configuration) + for header, value in headers.items(): + _set_header(client, header, value) + return k8sdynamicclient.K8SDynamicClient(client, discoverer=LazyDiscoverer) + + +class K8SClient: + """A Client class for K8S modules. + + This class has the primary purpose to proxy the kubernetes client and resource objects. + If there is a need for other methods or attributes to be proxied, they can be added here. + """ + + def __init__(self, configuration, client, dry_run: bool = False) -> None: + self.configuration = configuration + self.client = client + self.dry_run = dry_run + + @property + def resources(self) -> List[Any]: + return self.client.resources + + def _find_resource_with_prefix( + self, prefix: str, kind: str, api_version: str + ) -> Resource: + for attribute in ["kind", "name", "singular_name"]: + try: + return self.client.resources.get( + **{"prefix": prefix, "api_version": api_version, attribute: kind} + ) + except (ResourceNotFoundError, ResourceNotUniqueError): + pass + return self.client.resources.get( + prefix=prefix, api_version=api_version, short_names=[kind] + ) + + def resource(self, kind: str, api_version: str) -> Resource: + """Fetch a kubernetes client resource. + + This will attempt to find a kubernetes resource trying, in order, kind, + name, singular_name and short_names. + """ + try: + if api_version == "v1": + return self._find_resource_with_prefix("api", kind, api_version) + except ResourceNotFoundError: + pass + return self._find_resource_with_prefix(None, kind, api_version) + + def _ensure_dry_run(self, params: Dict) -> Dict: + if self.dry_run: + params["dry_run"] = True + return params + + def validate( + self, resource, version: Optional[str] = None, strict: Optional[bool] = False + ): + return self.client.validate(resource, version, strict) + + def get(self, resource, **params): + return resource.get(**params) + + def delete(self, resource, **params): + return resource.delete(**self._ensure_dry_run(params)) + + def apply(self, resource, definition, namespace, **params): + return resource.apply( + definition, namespace=namespace, **self._ensure_dry_run(params) + ) + + def create(self, resource, definition, **params): + return resource.create(definition, **self._ensure_dry_run(params)) + + def replace(self, resource, definition, **params): + return resource.replace(definition, **self._ensure_dry_run(params)) + + def patch(self, resource, definition, **params): + return resource.patch(definition, **self._ensure_dry_run(params)) + + +def get_api_client(module=None, **kwargs: Optional[Any]) -> K8SClient: + auth_spec = _create_auth_spec(module, **kwargs) + if module: + requires = module.requires + else: + requires = _requires + if isinstance(auth_spec.get("kubeconfig"), dict): + requires("kubernetes", "17.17.0", "to use in-memory config") + if auth_spec.get("no_proxy"): + requires("kubernetes", "19.15.0", "to use the no_proxy feature") + + try: + configuration = _create_configuration(auth_spec) + headers = _create_headers(module, **kwargs) + client = create_api_client(configuration, **headers) + except kubernetes.config.ConfigException as e: + msg = "Could not create API client: {0}".format(e) + raise CoreException(msg) from e + + k8s_client = K8SClient( + configuration=configuration, + client=client, + dry_run=module.params.get("dry_run", False), + ) + + return k8s_client diff --git a/plugins/module_utils/k8s/core.py b/plugins/module_utils/k8s/core.py new file mode 100644 index 00000000..95815dee --- /dev/null +++ b/plugins/module_utils/k8s/core.py @@ -0,0 +1,171 @@ +import traceback + +from typing import Optional + +from ansible_collections.kubernetes.core.plugins.module_utils.version import ( + LooseVersion, +) + +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: + """A base module class for K8S modules. + + This class should be used instead of directly using AnsibleModule. If there + is a need for other methods or attributes to be proxied, they can be added + here. + """ + + default_settings = { + "check_k8s": True, + "check_pyyaml": True, + "module_class": AnsibleModule, + } + + def __init__(self, **kwargs) -> None: + local_settings = {} + for key in AnsibleK8SModule.default_settings: + try: + local_settings[key] = kwargs.pop(key) + except KeyError: + local_settings[key] = AnsibleK8SModule.default_settings[key] + self.settings = local_settings + + self._module = self.settings["module_class"](**kwargs) + + if self.settings["check_k8s"]: + self.requires("kubernetes") + self.has_at_least("kubernetes", "12.0.0", warn=True) + + if self.settings["check_pyyaml"]: + self.requires("pyyaml") + + @property + def check_mode(self): + return self._module.check_mode + + @property + def _diff(self): + return self._module._diff + + @property + def _name(self): + return self._module._name + + @property + def params(self): + return self._module.params + + def warn(self, *args, **kwargs): + return self._module.warn(*args, **kwargs) + + def deprecate(self, *args, **kwargs): + return self._module.deprecate(*args, **kwargs) + + def debug(self, *args, **kwargs): + return self._module.debug(*args, **kwargs) + + def exit_json(self, *args, **kwargs): + return self._module.exit_json(*args, **kwargs) + + def fail_json(self, *args, **kwargs): + return self._module.fail_json(*args, **kwargs) + + def fail_from_exception(self, exception): + msg = to_text(exception) + tb = "".join( + traceback.format_exception(None, exception, exception.__traceback__) + ) + return self.fail_json(msg=msg, exception=tb) + + def has_at_least( + self, dependency: str, minimum: Optional[str] = None, warn: bool = False + ) -> bool: + 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 + + 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)) + + +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 kubernetes_validate + + versions["kubernetes-validate"] = kubernetes_validate.__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/plugins/module_utils/k8s/exceptions.py b/plugins/module_utils/k8s/exceptions.py new file mode 100644 index 00000000..f3a82c6b --- /dev/null +++ b/plugins/module_utils/k8s/exceptions.py @@ -0,0 +1,12 @@ +# Copyright: (c) 2021, Red Hat | Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +class CoreException(Exception): + pass + + +class ResourceTimeout(CoreException): + def __init__(self, message="", result=None): + self.result = result or {} + super().__init__(message) diff --git a/plugins/module_utils/k8s/resource.py b/plugins/module_utils/k8s/resource.py new file mode 100644 index 00000000..797979d7 --- /dev/null +++ b/plugins/module_utils/k8s/resource.py @@ -0,0 +1,129 @@ +# Copyright: (c) 2021, Red Hat | Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +import os +from typing import cast, Dict, Iterable, List, Optional, Union + +from ansible.module_utils.six import string_types + +try: + import yaml +except ImportError: + # Handled in module setup + pass + + +class ResourceDefinition(dict): + """Representation of a resource definition. + + This is a thin wrapper around a dictionary representation of a resource + definition, with a few properties defined for conveniently accessing the + commonly used fields. + """ + + @property + def kind(self) -> Optional[str]: + return self.get("kind") + + @property + def api_version(self) -> Optional[str]: + return self.get("apiVersion") + + @property + def namespace(self) -> Optional[str]: + metadata = self.get("metadata", {}) + return metadata.get("namespace") + + @property + def name(self) -> Optional[str]: + metadata = self.get("metadata", {}) + return metadata.get("name") + + +def create_definitions(params: Dict) -> List[ResourceDefinition]: + """Create a list of ResourceDefinitions from module inputs. + + This will take the module's inputs and return a list of ResourceDefintion + objects. The resource definitions returned by this function should be as + complete a definition as we can create based on the input. Any *List kinds + will be removed and replaced by the resources contained in it. + """ + if params.get("resource_definition"): + d = cast(Union[str, List, Dict], params.get("resource_definition")) + definitions = from_yaml(d) + elif params.get("src"): + d = cast(str, params.get("src")) + definitions = from_file(d) + else: + # We'll create an empty definition and let merge_params set values + # from the module parameters. + definitions = [{}] + + resource_definitions: List[Dict] = [] + for definition in definitions: + merge_params(definition, params) + kind = cast(Optional[str], definition.get("kind")) + if kind and kind.endswith("List"): + resource_definitions += flatten_list_kind(definition, params) + else: + resource_definitions.append(definition) + return list(map(ResourceDefinition, resource_definitions)) + + +def from_yaml(definition: Union[str, List, Dict]) -> Iterable[Dict]: + """Load resource definitions from a yaml definition.""" + definitions: List[Dict] = [] + if isinstance(definition, string_types): + definitions += yaml.safe_load_all(definition) + elif isinstance(definition, list): + for item in definition: + if isinstance(item, string_types): + definitions += yaml.safe_load_all(item) + else: + definitions.append(item) + else: + definition = cast(Dict, definition) + definitions.append(definition) + return filter(None, definitions) + + +def from_file(filepath: str) -> Iterable[Dict]: + """Load resource definitions from a path to a yaml file.""" + path = os.path.normpath(filepath) + with open(path, "rb") as f: + definitions = list(yaml.safe_load_all(f)) + return filter(None, definitions) + + +def merge_params(definition: Dict, params: Dict) -> Dict: + """Merge module parameters with the resource definition. + + Fields in the resource definition take precedence over module parameters. + """ + definition.setdefault("kind", params.get("kind")) + definition.setdefault("apiVersion", params.get("api_version")) + metadata = definition.setdefault("metadata", {}) + # The following should only be set if we have values for them + if params.get("namespace"): + metadata.setdefault("namespace", params.get("namespace")) + if params.get("name"): + metadata.setdefault("name", params.get("name")) + if params.get("generate_name"): + metadata.setdefault("generateName", params.get("generate_name")) + return definition + + +def flatten_list_kind(definition: Dict, params: Dict) -> List[Dict]: + """Replace *List kind with the items it contains. + + This will take a definition for a *List resource and return a list of + definitions for the items contained within the List. + """ + items = [] + kind = cast(str, definition.get("kind"))[:-4] + api_version = definition.get("apiVersion") + for item in definition.get("items", []): + item.setdefault("kind", kind) + item.setdefault("apiVersion", api_version) + items.append(merge_params(item, params)) + return items diff --git a/plugins/module_utils/k8s/runner.py b/plugins/module_utils/k8s/runner.py new file mode 100644 index 00000000..438b3211 --- /dev/null +++ b/plugins/module_utils/k8s/runner.py @@ -0,0 +1,199 @@ +# Copyright: (c) 2021, Red Hat | Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from typing import Dict + +from ansible.module_utils._text import to_native + +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import ( + get_api_client, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import ( + CoreException, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.resource import ( + create_definitions, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import ( + K8sService, + diff_objects, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import ( + ResourceTimeout, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.waiter import exists +from ansible_collections.kubernetes.core.plugins.module_utils.selector import ( + LabelSelectorFilter, +) + + +def validate(client, module, resource): + def _prepend_resource_info(resource, msg): + return "%s %s: %s" % (resource["kind"], resource["metadata"]["name"], msg) + + module.requires("kubernetes-validate") + + warnings, errors = client.validate( + resource, + module.params["validate"].get("version"), + module.params["validate"].get("strict"), + ) + + if errors and module.params["validate"]["fail_on_error"]: + module.fail_json( + msg="\n".join([_prepend_resource_info(resource, error) for error in errors]) + ) + return [_prepend_resource_info(resource, msg) for msg in warnings + errors] + + +def run_module(module) -> None: + results = [] + changed = False + client = get_api_client(module) + svc = K8sService(client, module) + try: + definitions = create_definitions(module.params) + except Exception as e: + msg = "Failed to load resource definition: {0}".format(e) + raise CoreException(msg) from e + + for definition in definitions: + result = {"changed": False, "result": {}} + warnings = [] + + if module.params.get("validate") is not None: + warnings = validate(client, module, definition) + + try: + result = perform_action(svc, definition, module.params) + except Exception as e: + try: + error = e.result + except AttributeError: + error = {} + try: + error["reason"] = e.__cause__.reason + except AttributeError: + pass + error["msg"] = to_native(e) + if warnings: + error.setdefault("warnings", []).extend(warnings) + + if module.params.get("continue_on_error"): + result["error"] = error + else: + module.fail_json(**error) + + if warnings: + result.setdefault("warnings", []).extend(warnings) + changed |= result["changed"] + results.append(result) + + if len(results) == 1: + module.exit_json(**results[0]) + + module.exit_json(**{"changed": changed, "result": {"results": results}}) + + +def perform_action(svc, definition: Dict, params: Dict) -> Dict: + origin_name = definition["metadata"].get("name") + namespace = definition["metadata"].get("namespace") + label_selectors = params.get("label_selectors") + state = params.get("state", None) + kind = definition.get("kind") + api_version = definition.get("apiVersion") + + result = {"changed": False, "result": {}} + instance = {} + + resource = svc.find_resource(kind, api_version, fail=True) + definition["kind"] = resource.kind + definition["apiVersion"] = resource.group_version + existing = svc.retrieve(resource, definition) + + if state == "absent": + if exists(existing) and existing.kind.endswith("List"): + instance = [] + for item in existing.items: + r = svc.delete(resource, item, existing) + instance.append(r) + else: + instance = svc.delete(resource, definition, existing) + result["method"] = "delete" + if exists(existing): + result["changed"] = True + else: + if label_selectors: + filter_selector = LabelSelectorFilter(label_selectors) + if not filter_selector.isMatching(definition): + result["changed"] = False + result["msg"] = ( + "resource 'kind={kind},name={name},namespace={namespace}' " + "filtered by label_selectors.".format( + kind=kind, + name=origin_name, + namespace=namespace, + ) + ) + return result + + if params.get("apply"): + instance = svc.apply(resource, definition, existing) + result["method"] = "apply" + elif not existing: + if state == "patched": + result.setdefault("warnings", []).append( + "resource 'kind={kind},name={name}' was not found but will not be " + "created as 'state' parameter has been set to '{state}'".format( + kind=kind, name=definition["metadata"].get("name"), state=state + ) + ) + return result + instance = svc.create(resource, definition) + result["method"] = "create" + result["changed"] = True + elif params.get("force", False): + instance = svc.replace(resource, definition, existing) + result["method"] = "replace" + else: + instance = svc.update(resource, definition, existing) + result["method"] = "update" + + # If needed, wait and/or create diff + success = True + + if result["method"] == "delete": + # wait logic is a bit different for delete as `instance` may be a status object + if params.get("wait") and not svc.module.check_mode: + success, waited, duration = svc.wait(resource, definition) + result["duration"] = duration + else: + if params.get("wait") and not svc.module.check_mode: + success, instance, duration = svc.wait(resource, instance) + result["duration"] = duration + + if result["method"] not in ("create", "delete"): + if existing: + existing = existing.to_dict() + else: + existing = {} + match, diffs = diff_objects(existing, instance) + if match and diffs: + result.setdefault("warnings", []).append( + "No meaningful diff was generated, but the API may not be idempotent " + "(only metadata.generation or metadata.resourceVersion were changed)" + ) + result["changed"] = not match + if svc.module._diff: + result["diff"] = diffs + + result["result"] = instance + if not success: + raise ResourceTimeout( + '"{0}" "{1}": Timed out waiting on resource'.format( + definition["kind"], origin_name + ), + result, + ) + + return result diff --git a/plugins/module_utils/k8s/service.py b/plugins/module_utils/k8s/service.py new file mode 100644 index 00000000..121b63c0 --- /dev/null +++ b/plugins/module_utils/k8s/service.py @@ -0,0 +1,502 @@ +# Copyright: (c) 2021, Red Hat | Ansible +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from typing import Any, Dict, List, Optional, Tuple + +from ansible_collections.kubernetes.core.plugins.module_utils.hashes import ( + generate_hash, +) + +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.waiter import ( + Waiter, + exists, + resource_absent, + get_waiter, +) + +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import ( + requires, +) + +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import ( + CoreException, +) + +from ansible.module_utils.common.dict_transformations import dict_merge +from ansible.module_utils._text import to_native + +try: + from kubernetes.dynamic.exceptions import ( + NotFoundError, + ResourceNotFoundError, + ResourceNotUniqueError, + ConflictError, + ForbiddenError, + MethodNotAllowedError, + BadRequestError, + ) +except ImportError: + # Handled in module setup + pass + +try: + from kubernetes.dynamic.resource import Resource, ResourceInstance +except ImportError: + # These are defined only for the sake of Ansible's checked import requirement + Resource = Any # type: ignore + ResourceInstance = Any # type: ignore + +try: + from ansible_collections.kubernetes.core.plugins.module_utils.apply import ( + apply_object, + ) +except ImportError: + # Handled in module setup + pass + +try: + from ansible_collections.kubernetes.core.plugins.module_utils.apply import ( + recursive_diff, + ) +except ImportError: + from ansible.module_utils.common.dict_transformations import recursive_diff + + +try: + from ansible_collections.kubernetes.core.plugins.module_utils.common import ( + _encode_stringdata, + ) +except ImportError: + # Handled in module setup + pass + + +class K8sService: + """A Service class for K8S modules. + This class has the primary purpose is to perform work on the cluster (e.g., create, apply, replace, update, delete). + """ + + def __init__(self, client, module) -> None: + self.client = client + self.module = module + + def find_resource( + self, kind: str, api_version: str, fail: bool = False + ) -> Optional[Resource]: + try: + return self.client.resource(kind, api_version) + except (ResourceNotFoundError, ResourceNotUniqueError): + if fail: + raise CoreException( + "Failed to find exact match for %s.%s by [kind, name, singularName, shortNames]" + % (api_version, kind) + ) + + def wait( + self, resource: Resource, instance: Dict + ) -> Tuple[bool, Optional[Dict], int]: + wait_sleep = self.module.params.get("wait_sleep") + wait_timeout = self.module.params.get("wait_timeout") + wait_condition = None + if self.module.params.get("wait_condition") and self.module.params[ + "wait_condition" + ].get("type"): + wait_condition = self.module.params["wait_condition"] + state = "present" + if self.module.params.get("state") == "absent": + state = "absent" + label_selectors = self.module.params.get("label_selectors") + + waiter = get_waiter( + self.client, resource, condition=wait_condition, state=state + ) + return waiter.wait( + timeout=wait_timeout, + sleep=wait_sleep, + name=instance["metadata"].get("name"), + namespace=instance["metadata"].get("namespace"), + label_selectors=label_selectors, + ) + + def create_project_request(self, definition: Dict) -> Dict: + definition["kind"] = "ProjectRequest" + results = {"changed": False, "result": {}} + resource = self.find_resource( + "ProjectRequest", definition["apiVersion"], fail=True + ) + if not self.module.check_mode: + try: + k8s_obj = self.client.create(resource, definition) + results["result"] = k8s_obj.to_dict() + except Exception as e: + reason = e.body if hasattr(e, "body") else e + msg = "Failed to create object: {0}".format(reason) + raise CoreException(msg) from e + + results["changed"] = True + + return results + + def patch_resource( + self, + resource: Resource, + definition: Dict, + name: str, + namespace: str, + merge_type: str = None, + ) -> Dict: + if merge_type == "json": + self.module.deprecate( + msg="json as a merge_type value is deprecated. Please use the k8s_json_patch module instead.", + version="3.0.0", + collection_name="kubernetes.core", + ) + try: + params = dict(name=name, namespace=namespace) + if self.module.check_mode: + params["dry_run"] = "All" + if merge_type: + params["content_type"] = "application/{0}-patch+json".format(merge_type) + return self.client.patch(resource, definition, **params).to_dict() + except Exception as e: + reason = e.body if hasattr(e, "body") else e + msg = "Failed to patch object: {0}".format(reason) + raise CoreException(msg) from e + + def retrieve(self, resource: Resource, definition: Dict) -> ResourceInstance: + state = self.module.params.get("state", None) + append_hash = self.module.params.get("append_hash", False) + name = definition["metadata"].get("name") + generate_name = definition["metadata"].get("generateName") + namespace = definition["metadata"].get("namespace") + label_selectors = self.module.params.get("label_selectors") + existing: ResourceInstance = None + + try: + # ignore append_hash for resources other than ConfigMap and Secret + if append_hash and definition["kind"] in ["ConfigMap", "Secret"]: + if name: + name = "%s-%s" % (name, generate_hash(definition)) + definition["metadata"]["name"] = name + elif generate_name: + definition["metadata"]["generateName"] = "%s-%s" % ( + generate_name, + generate_hash(definition), + ) + params = {} + if name: + params["name"] = name + if namespace: + params["namespace"] = namespace + if label_selectors: + params["label_selector"] = ",".join(label_selectors) + if "name" in params or "label_selector" in params: + existing = self.client.get(resource, **params) + except (NotFoundError, MethodNotAllowedError): + pass + except ForbiddenError as e: + if ( + definition["kind"] in ["Project", "ProjectRequest"] + and state != "absent" + ): + return self.create_project_request(definition) + reason = e.body if hasattr(e, "body") else e + msg = "Failed to retrieve requested object: {0}".format(reason) + raise CoreException(msg) from e + except Exception as e: + reason = e.body if hasattr(e, "body") else e + msg = "Failed to retrieve requested object: {0}".format(reason) + raise CoreException(msg) from e + + return existing + + def find( + self, + kind: str, + api_version: str, + name: str = None, + namespace: Optional[str] = None, + label_selectors: Optional[List[str]] = None, + field_selectors: Optional[List[str]] = None, + wait: Optional[bool] = False, + wait_sleep: Optional[int] = 5, + wait_timeout: Optional[int] = 120, + state: Optional[str] = "present", + condition: Optional[Dict] = None, + ) -> Dict: + resource = self.find_resource(kind, api_version) + api_found = bool(resource) + if not api_found: + return dict( + resources=[], + msg='Failed to find API for resource with apiVersion "{0}" and kind "{1}"'.format( + api_version, kind + ), + api_found=False, + ) + + if not label_selectors: + label_selectors = [] + if not field_selectors: + field_selectors = [] + + result = {"resources": [], "api_found": True} + + # With a timeout of 0 the waiter will do a single check and return, effectively not waiting. + if not wait: + wait_timeout = 0 + + if state == "present": + predicate = exists + else: + predicate = resource_absent + + waiter = Waiter(self.client, resource, predicate) + + # This is an initial check to get the resource or resources that we then need to wait on individually. + try: + success, resources, duration = waiter.wait( + timeout=wait_timeout, + sleep=wait_sleep, + name=name, + namespace=namespace, + label_selectors=label_selectors, + field_selectors=field_selectors, + ) + except BadRequestError: + return result + except CoreException as e: + result["msg"] = to_native(e) + return result + + # There is either no result or there is a List resource with no items + if ( + not resources + or resources["kind"].endswith("List") + and not resources.get("items") + ): + return result + + instances = resources.get("items") or [resources] + + if not wait: + result["resources"] = instances + return result + + # Now wait for the specified state of any resource instances we have found. + waiter = get_waiter(self.client, resource, state=state, condition=condition) + for instance in instances: + name = instance["metadata"].get("name") + namespace = instance["metadata"].get("namespace") + success, res, duration = waiter.wait( + timeout=wait_timeout, + sleep=wait_sleep, + name=name, + namespace=namespace, + ) + if not success: + raise CoreException( + "Failed to gather information about %s(s) even" + " after waiting for %s seconds" % (res.get("kind"), duration) + ) + result["resources"].append(res) + return result + + def create(self, resource: Resource, definition: Dict) -> Dict: + namespace = definition["metadata"].get("namespace") + name = definition["metadata"].get("name") + results = {"changed": False, "result": {}} + + if self.module.check_mode and not self.client.dry_run: + k8s_obj = _encode_stringdata(definition) + else: + params = {} + if self.module.check_mode: + params["dry_run"] = "All" + try: + k8s_obj = self.client.create( + resource, definition, namespace=namespace, **params + ).to_dict() + except ConflictError: + # Some resources, like ProjectRequests, can't be created multiple times, + # because the resources that they create don't match their kind + # In this case we'll mark it as unchanged and warn the user + self.module.warn( + "{0} was not found, but creating it returned a 409 Conflict error. This can happen \ + if the resource you are creating does not directly create a resource of the same kind.".format( + name + ) + ) + return results + except Exception as e: + reason = e.body if hasattr(e, "body") else e + msg = "Failed to create object: {0}".format(reason) + raise CoreException(msg) from e + return k8s_obj + + def apply( + self, + resource: Resource, + definition: Dict, + existing: Optional[ResourceInstance] = None, + ) -> Dict: + namespace = definition["metadata"].get("namespace") + + server_side_apply = self.module.params.get("server_side_apply") + if server_side_apply: + requires("kubernetes", "19.15.0", reason="to use server side apply") + if self.module.check_mode and not self.client.dry_run: + ignored, patch = apply_object(resource, _encode_stringdata(definition)) + if existing: + k8s_obj = dict_merge(existing.to_dict(), patch) + else: + k8s_obj = patch + else: + try: + params = {} + if self.module.check_mode: + params["dry_run"] = "All" + if server_side_apply: + params["server_side"] = True + params.update(server_side_apply) + k8s_obj = self.client.apply( + resource, definition, namespace=namespace, **params + ).to_dict() + except Exception as e: + reason = e.body if hasattr(e, "body") else e + msg = "Failed to apply object: {0}".format(reason) + raise CoreException(msg) from e + return k8s_obj + + def replace( + self, + resource: Resource, + definition: Dict, + existing: ResourceInstance, + ) -> Dict: + append_hash = self.module.params.get("append_hash", False) + name = definition["metadata"].get("name") + namespace = definition["metadata"].get("namespace") + + if self.module.check_mode and not self.module.client.dry_run: + k8s_obj = _encode_stringdata(definition) + else: + params = {} + if self.module.check_mode: + params["dry_run"] = "All" + try: + k8s_obj = self.client.replace( + resource, + definition, + name=name, + namespace=namespace, + append_hash=append_hash, + **params + ).to_dict() + except Exception as e: + reason = e.body if hasattr(e, "body") else e + msg = "Failed to replace object: {0}".format(reason) + raise CoreException(msg) from e + return k8s_obj + + def update( + self, resource: Resource, definition: Dict, existing: ResourceInstance + ) -> Dict: + name = definition["metadata"].get("name") + namespace = definition["metadata"].get("namespace") + + if self.module.check_mode and not self.client.dry_run: + k8s_obj = dict_merge(existing.to_dict(), _encode_stringdata(definition)) + else: + exception = None + for merge_type in self.module.params.get("merge_type") or [ + "strategic-merge", + "merge", + ]: + try: + k8s_obj = self.patch_resource( + resource, + definition, + name, + namespace, + merge_type=merge_type, + ) + exception = None + except CoreException as e: + exception = e + continue + break + if exception: + raise exception + return k8s_obj + + def delete( + self, + resource: Resource, + definition: Dict, + existing: Optional[ResourceInstance] = None, + ) -> Dict: + delete_options = self.module.params.get("delete_options") + label_selectors = self.module.params.get("label_selectors") + name = definition["metadata"].get("name") + namespace = definition["metadata"].get("namespace") + params = {} + + if not exists(existing): + return {} + + # Delete the object + if self.module.check_mode and not self.client.dry_run: + return {} + + if name: + params["name"] = name + + if namespace: + params["namespace"] = namespace + + if label_selectors: + params["label_selector"] = ",".join(label_selectors) + + if delete_options: + body = { + "apiVersion": "v1", + "kind": "DeleteOptions", + } + body.update(delete_options) + params["body"] = body + + if self.module.check_mode: + params["dry_run"] = "All" + try: + k8s_obj = self.client.delete(resource, **params).to_dict() + except Exception as e: + reason = e.body if hasattr(e, "body") else e + msg = "Failed to delete object: {0}".format(reason) + raise CoreException(msg) from e + return k8s_obj + + +def diff_objects(existing: Dict, new: Dict) -> Tuple[bool, Dict]: + result = {} + diff = recursive_diff(existing, new) + if not diff: + return True, result + + result["before"] = diff[0] + result["after"] = diff[1] + + if list(result["after"].keys()) != ["metadata"] or list( + result["before"].keys() + ) != ["metadata"]: + return False, result + + # If only metadata.generation and metadata.resourceVersion changed, ignore it + ignored_keys = set(["generation", "resourceVersion"]) + + if not set(result["after"]["metadata"].keys()).issubset(ignored_keys): + return False, result + if not set(result["before"]["metadata"].keys()).issubset(ignored_keys): + return False, result + + return True, result diff --git a/plugins/module_utils/k8s/waiter.py b/plugins/module_utils/k8s/waiter.py new file mode 100644 index 00000000..653e1708 --- /dev/null +++ b/plugins/module_utils/k8s/waiter.py @@ -0,0 +1,238 @@ +import time +from functools import partial +from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple, Union + +from ansible.module_utils.parsing.convert_bool import boolean + +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import ( + CoreException, +) + +try: + from kubernetes.dynamic.exceptions import NotFoundError + from kubernetes.dynamic.resource import Resource, ResourceField, ResourceInstance +except ImportError: + # These are defined only for the sake of Ansible's checked import requirement + Resource = Any # type: ignore + ResourceInstance = Any # type: ignore + pass + +try: + from urllib3.exceptions import HTTPError +except ImportError: + # Handled during module setup + pass + + +def deployment_ready(deployment: ResourceInstance) -> bool: + # FIXME: frustratingly bool(deployment.status) is True even if status is empty + # Furthermore deployment.status.availableReplicas == deployment.status.replicas == None if status is empty + # deployment.status.replicas is None is perfectly ok if desired replicas == 0 + # Scaling up means that we also need to check that we're not in a + # situation where status.replicas == status.availableReplicas + # but spec.replicas != status.replicas + return bool( + deployment.status + and deployment.spec.replicas == (deployment.status.replicas or 0) + and deployment.status.availableReplicas == deployment.status.replicas + and deployment.status.observedGeneration == deployment.metadata.generation + and not deployment.status.unavailableReplicas + ) + + +def pod_ready(pod: ResourceInstance) -> bool: + return bool( + pod.status + and pod.status.containerStatuses is not None + and all(container.ready for container in pod.status.containerStatuses) + ) + + +def daemonset_ready(daemonset: ResourceInstance) -> bool: + return bool( + daemonset.status + and daemonset.status.desiredNumberScheduled is not None + and daemonset.status.updatedNumberScheduled + == daemonset.status.desiredNumberScheduled + and daemonset.status.numberReady == daemonset.status.desiredNumberScheduled + and daemonset.status.observedGeneration == daemonset.metadata.generation + and not daemonset.status.unavailableReplicas + ) + + +def statefulset_ready(statefulset: ResourceInstance) -> bool: + # These may be None + updated_replicas = statefulset.status.updatedReplicas or 0 + ready_replicas = statefulset.status.readyReplicas or 0 + return bool( + statefulset.status + and statefulset.spec.updateStrategy.type == "RollingUpdate" + and statefulset.status.observedGeneration + == (statefulset.metadata.generation or 0) + and statefulset.status.updateRevision == statefulset.status.currentRevision + and updated_replicas == statefulset.spec.replicas + and ready_replicas == statefulset.spec.replicas + and statefulset.status.replicas == statefulset.spec.replicas + ) + + +def custom_condition(condition: Dict, resource: ResourceInstance) -> bool: + if not resource.status or not resource.status.conditions: + return False + matches = [x for x in resource.status.conditions if x.type == condition["type"]] + if not matches: + return False + # There should never be more than one condition of a specific type + match: ResourceField = matches[0] + if match.status == "Unknown": + if match.status == condition["status"]: + if "reason" not in condition: + return True + if condition["reason"]: + return match.reason == condition["reason"] + return False + status = True if match.status == "True" else False + if status == boolean(condition["status"], strict=False): + if condition.get("reason"): + return match.reason == condition["reason"] + return True + return False + + +def resource_absent(resource: ResourceInstance) -> bool: + return not exists(resource) + + +def exists(resource: Optional[ResourceInstance]) -> bool: + """Simple predicate to check for existence of a resource. + + While a List type resource technically always exists, this will only return + true if the List contains items.""" + return bool(resource) and not empty_list(resource) + + +RESOURCE_PREDICATES = { + "DaemonSet": daemonset_ready, + "Deployment": deployment_ready, + "Pod": pod_ready, + "StatefulSet": statefulset_ready, +} + + +def empty_list(resource: ResourceInstance) -> bool: + return resource["kind"].endswith("List") and not resource.get("items") + + +def clock(total: int, interval: int) -> Iterator[int]: + start = time.monotonic() + yield 0 + while (time.monotonic() - start) < total: + time.sleep(interval) + yield int(time.monotonic() - start) + + +class Waiter: + def __init__( + self, client, resource: Resource, predicate: Callable[[ResourceInstance], bool] + ): + self.client = client + self.resource = resource + self.predicate = predicate + + def wait( + self, + timeout: int, + sleep: int, + name: Optional[str] = None, + namespace: Optional[str] = None, + label_selectors: Optional[List[str]] = None, + field_selectors: Optional[List[str]] = None, + ) -> Tuple[bool, Dict, int]: + params = {} + + if name: + params["name"] = name + + if namespace: + params["namespace"] = namespace + + if label_selectors: + params["label_selector"] = ",".join(label_selectors) + + if field_selectors: + params["field_selector"] = ",".join(field_selectors) + + instance = {} + response = None + elapsed = 0 + for i in clock(timeout, sleep): + exception = None + elapsed = i + try: + response = self.client.get(self.resource, **params) + except NotFoundError: + response = None + # Retry connection errors as it may be intermittent network issues + except HTTPError as e: + exception = e + if self.predicate(response): + break + if exception: + msg = ( + "Exception '{0}' raised while trying to get resource using {1}".format( + exception, params + ) + ) + raise CoreException(msg) from exception + if response: + instance = response.to_dict() + return self.predicate(response), instance, elapsed + + +class DummyWaiter: + """A no-op waiter that simply returns the item being waited on. + + No API call will be made with this waiter; the function returns + immediately. This waiter is useful for waiting on resource instances in + check mode, for example. + """ + + def wait( + self, + definition: Dict, + timeout: int, + sleep: int, + label_selectors: Optional[List[str]] = None, + ) -> Tuple[bool, Optional[Dict], int]: + return True, definition, 0 + + +# The better solution would be typing.Protocol, but this is only in 3.8+ +SupportsWait = Union[Waiter, DummyWaiter] + + +def get_waiter( + client, + resource: Resource, + state: str = "present", + condition: Optional[Dict] = None, + check_mode: Optional[bool] = False, +) -> SupportsWait: + """Create a Waiter object based on the specified resource. + + This is a convenience method for creating a waiter from a resource. + Based on the arguments and the kind of resource, an appropriate waiter + will be returned. A waiter can also be created directly, of course. + """ + if check_mode: + return DummyWaiter() + if state == "present": + if condition: + predicate: Callable[[ResourceInstance], bool] = partial( + custom_condition, condition + ) + else: + predicate = RESOURCE_PREDICATES.get(resource.kind, exists) + else: + predicate = resource_absent + return Waiter(client, resource, predicate) diff --git a/plugins/modules/k8s.py b/plugins/modules/k8s.py index d7a42472..31e5099e 100644 --- a/plugins/modules/k8s.py +++ b/plugins/modules/k8s.py @@ -391,6 +391,15 @@ from ansible_collections.kubernetes.core.plugins.module_utils.args_common import RESOURCE_ARG_SPEC, DELETE_OPTS_ARG_SPEC, ) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import ( + AnsibleK8SModule, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import ( + CoreException, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.runner import ( + run_module, +) def validate_spec(): @@ -437,28 +446,6 @@ def argspec(): return argument_spec -def execute_module(module, k8s_ansible_mixin): - k8s_ansible_mixin.module = module - k8s_ansible_mixin.argspec = module.argument_spec - k8s_ansible_mixin.check_mode = k8s_ansible_mixin.module.check_mode - k8s_ansible_mixin.params = k8s_ansible_mixin.module.params - k8s_ansible_mixin.fail_json = k8s_ansible_mixin.module.fail_json - k8s_ansible_mixin.fail = k8s_ansible_mixin.module.fail_json - k8s_ansible_mixin.exit_json = k8s_ansible_mixin.module.exit_json - k8s_ansible_mixin.warn = k8s_ansible_mixin.module.warn - k8s_ansible_mixin.warnings = [] - - k8s_ansible_mixin.kind = k8s_ansible_mixin.params.get("kind") - k8s_ansible_mixin.api_version = k8s_ansible_mixin.params.get("api_version") - k8s_ansible_mixin.name = k8s_ansible_mixin.params.get("name") - k8s_ansible_mixin.generate_name = k8s_ansible_mixin.params.get("generate_name") - k8s_ansible_mixin.namespace = k8s_ansible_mixin.params.get("namespace") - - k8s_ansible_mixin.check_library_version() - k8s_ansible_mixin.set_resource_definitions(module) - k8s_ansible_mixin.execute_module() - - def main(): mutually_exclusive = [ ("resource_definition", "src"), @@ -467,19 +454,17 @@ def main(): ("template", "src"), ("name", "generate_name"), ] - module = AnsibleModule( + + module = AnsibleK8SModule( + module_class=AnsibleModule, argument_spec=argspec(), mutually_exclusive=mutually_exclusive, supports_check_mode=True, ) - from ansible_collections.kubernetes.core.plugins.module_utils.common import ( - K8sAnsibleMixin, - get_api_client, - ) - - k8s_ansible_mixin = K8sAnsibleMixin(module) - k8s_ansible_mixin.client = get_api_client(module=module) - execute_module(module, k8s_ansible_mixin) + try: + run_module(module) + except CoreException as e: + module.fail_from_exception(e) if __name__ == "__main__": diff --git a/plugins/modules/k8s_cluster_info.py b/plugins/modules/k8s_cluster_info.py index e84b9eb7..9cd2ac17 100644 --- a/plugins/modules/k8s_cluster_info.py +++ b/plugins/modules/k8s_cluster_info.py @@ -141,36 +141,32 @@ apis: import copy -import traceback from collections import defaultdict -HAS_K8S = False try: from ansible_collections.kubernetes.core.plugins.module_utils.client.resource import ( ResourceList, ) +except ImportError: + # Handled during module setup + pass - HAS_K8S = True -except ImportError as e: - K8S_IMP_ERR = e - K8S_IMP_EXC = traceback.format_exc() - -from ansible.module_utils._text import to_native -from ansible.module_utils.basic import missing_required_lib -from ansible.module_utils.parsing.convert_bool import boolean from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import ( AnsibleModule, ) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import ( + AnsibleK8SModule, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import ( + CoreException, +) from ansible_collections.kubernetes.core.plugins.module_utils.args_common import ( AUTH_ARG_SPEC, ) def execute_module(module, client): - invalidate_cache = boolean( - module.params.get("invalidate_cache", True), strict=False - ) - if invalidate_cache: + if module.params.get("invalidate_cache"): client.resources.invalidate_cache() results = defaultdict(dict) for resource in list(client.resources): @@ -204,7 +200,7 @@ def execute_module(module, client): version_info = { "client": version, - "server": client.version, + "server": client.client.version, } module.exit_json( changed=False, apis=results, connection=connection, version=version_info @@ -218,18 +214,18 @@ def argspec(): def main(): - module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True) - if not HAS_K8S: - module.fail_json( - msg=missing_required_lib("kubernetes"), - exception=K8S_IMP_EXC, - error=to_native(K8S_IMP_ERR), - ) - from ansible_collections.kubernetes.core.plugins.module_utils.common import ( + module = AnsibleK8SModule( + module_class=AnsibleModule, argument_spec=argspec(), supports_check_mode=True + ) + + from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import ( get_api_client, ) - execute_module(module, client=get_api_client(module=module)) + try: + execute_module(module, client=get_api_client(module=module)) + except CoreException as e: + module.fail_from_exception(e) if __name__ == "__main__": diff --git a/plugins/modules/k8s_cp.py b/plugins/modules/k8s_cp.py index 11111a1d..cd277018 100644 --- a/plugins/modules/k8s_cp.py +++ b/plugins/modules/k8s_cp.py @@ -142,6 +142,19 @@ import copy from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import ( AnsibleModule, ) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import ( + get_api_client, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import ( + AnsibleK8SModule, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import ( + CoreException, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import ( + K8sService, +) + from ansible_collections.kubernetes.core.plugins.module_utils.args_common import ( AUTH_ARG_SPEC, ) @@ -171,23 +184,9 @@ def argspec(): def execute_module(module): - - from ansible_collections.kubernetes.core.plugins.module_utils.common import ( - K8sAnsibleMixin, - get_api_client, - ) - - k8s_ansible_mixin = K8sAnsibleMixin(module, pyyaml_required=False) - k8s_ansible_mixin.check_library_version() - - k8s_ansible_mixin.module = module - k8s_ansible_mixin.argspec = module.argument_spec - k8s_ansible_mixin.params = k8s_ansible_mixin.module.params - k8s_ansible_mixin.fail_json = k8s_ansible_mixin.module.fail_json - k8s_ansible_mixin.fail = k8s_ansible_mixin.module.fail_json - - k8s_ansible_mixin.client = get_api_client(module=module) - containers = check_pod(k8s_ansible_mixin, module) + client = get_api_client(module=module) + svc = K8sService(client, module) + containers = check_pod(svc) if len(containers) > 1 and module.params.get("container") is None: module.fail_json( msg="Pod contains more than 1 container, option 'container' should be set" @@ -195,9 +194,9 @@ def execute_module(module): state = module.params.get("state") if state == "to_pod": - k8s_copy = K8SCopyToPod(module, k8s_ansible_mixin.client) + k8s_copy = K8SCopyToPod(module, client.client) else: - k8s_copy = K8SCopyFromPod(module, k8s_ansible_mixin.client) + k8s_copy = K8SCopyFromPod(module, client.client) try: k8s_copy.run() @@ -206,15 +205,20 @@ def execute_module(module): def main(): - module = AnsibleModule( + module = AnsibleK8SModule( + module_class=AnsibleModule, argument_spec=argspec(), + check_pyyaml=False, mutually_exclusive=[("local_path", "content")], required_if=[("state", "from_pod", ["local_path"])], required_one_of=[["local_path", "content"]], supports_check_mode=True, ) - execute_module(module) + try: + execute_module(module) + except CoreException as e: + module.fail_from_exception(e) if __name__ == "__main__": diff --git a/plugins/modules/k8s_drain.py b/plugins/modules/k8s_drain.py index af3c6907..31596d8c 100644 --- a/plugins/modules/k8s_drain.py +++ b/plugins/modules/k8s_drain.py @@ -136,6 +136,16 @@ from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule impo from ansible_collections.kubernetes.core.plugins.module_utils.args_common import ( AUTH_ARG_SPEC, ) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import ( + get_api_client, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import ( + AnsibleK8SModule, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import ( + CoreException, +) + from ansible.module_utils._text import to_native try: @@ -244,30 +254,9 @@ def filter_pods(pods, force, ignore_daemonset, delete_emptydir_data): class K8sDrainAnsible(object): - def __init__(self, module): - from ansible_collections.kubernetes.core.plugins.module_utils.common import ( - K8sAnsibleMixin, - get_api_client, - ) - + def __init__(self, module, client): self._module = module - self._k8s_ansible_mixin = K8sAnsibleMixin(module) - self._k8s_ansible_mixin.client = get_api_client(module=self._module) - - self._k8s_ansible_mixin.module = self._module - self._k8s_ansible_mixin.argspec = self._module.argument_spec - self._k8s_ansible_mixin.check_mode = self._module.check_mode - self._k8s_ansible_mixin.params = self._module.params - self._k8s_ansible_mixin.fail_json = self._module.fail_json - self._k8s_ansible_mixin.fail = self._module.fail_json - self._k8s_ansible_mixin.exit_json = self._module.exit_json - self._k8s_ansible_mixin.warn = self._module.warn - self._k8s_ansible_mixin.warnings = [] - - self._api_instance = core_v1_api.CoreV1Api( - self._k8s_ansible_mixin.client.client - ) - self._k8s_ansible_mixin.check_library_version() + self._api_instance = core_v1_api.CoreV1Api(client.client) # delete options self._drain_options = module.params.get("delete_options", {}) @@ -503,7 +492,7 @@ def argspec(): def main(): - module = AnsibleModule(argument_spec=argspec()) + module = AnsibleK8SModule(module_class=AnsibleModule, argument_spec=argspec()) if not HAS_EVICTION_API: module.fail_json( @@ -512,8 +501,12 @@ def main(): error=to_native(k8s_import_exception), ) - k8s_drain = K8sDrainAnsible(module) - k8s_drain.execute_module() + try: + client = get_api_client(module=module) + k8s_drain = K8sDrainAnsible(module, client.client) + k8s_drain.execute_module() + except CoreException as e: + module.fail_from_exception(e) if __name__ == "__main__": diff --git a/plugins/modules/k8s_exec.py b/plugins/modules/k8s_exec.py index 646e1f9e..c54c23c0 100644 --- a/plugins/modules/k8s_exec.py +++ b/plugins/modules/k8s_exec.py @@ -138,6 +138,15 @@ from ansible.module_utils._text import to_native from ansible_collections.kubernetes.core.plugins.module_utils.common import ( AUTH_ARG_SPEC, ) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import ( + AnsibleK8SModule, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import ( + get_api_client, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import ( + CoreException, +) try: from kubernetes.client.apis import core_v1_api @@ -157,10 +166,9 @@ def argspec(): return spec -def execute_module(module, k8s_ansible_mixin): - +def execute_module(module, client): # Load kubernetes.client.Configuration - api = core_v1_api.CoreV1Api(k8s_ansible_mixin.client.client) + api = core_v1_api.CoreV1Api(client.client) # hack because passing the container as None breaks things optional_kwargs = {} @@ -228,18 +236,18 @@ def execute_module(module, k8s_ansible_mixin): def main(): - module = AnsibleModule( + module = AnsibleK8SModule( + module_class=AnsibleModule, + check_pyyaml=False, argument_spec=argspec(), supports_check_mode=True, ) - from ansible_collections.kubernetes.core.plugins.module_utils.common import ( - K8sAnsibleMixin, - get_api_client, - ) - k8s_ansible_mixin = K8sAnsibleMixin(module) - k8s_ansible_mixin.client = get_api_client(module=module) - execute_module(module, k8s_ansible_mixin) + try: + client = get_api_client(module) + execute_module(module, client.client) + except CoreException as e: + module.fail_from_exception(e) if __name__ == "__main__": diff --git a/plugins/modules/k8s_info.py b/plugins/modules/k8s_info.py index 65d4cac8..4b29be11 100644 --- a/plugins/modules/k8s_info.py +++ b/plugins/modules/k8s_info.py @@ -155,10 +155,22 @@ from ansible_collections.kubernetes.core.plugins.module_utils.args_common import AUTH_ARG_SPEC, WAIT_ARG_SPEC, ) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import ( + AnsibleK8SModule, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import ( + get_api_client, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import ( + CoreException, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import ( + K8sService, +) -def execute_module(module, k8s_ansible_mixin): - facts = k8s_ansible_mixin.kubernetes_facts( +def execute_module(module, svc): + facts = svc.find( module.params["kind"], module.params["api_version"], name=module.params["name"], @@ -190,19 +202,15 @@ def argspec(): def main(): - module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True) - from ansible_collections.kubernetes.core.plugins.module_utils.common import ( - K8sAnsibleMixin, - get_api_client, + module = AnsibleK8SModule( + module_class=AnsibleModule, argument_spec=argspec(), supports_check_mode=True ) - - k8s_ansible_mixin = K8sAnsibleMixin(module) - k8s_ansible_mixin.client = get_api_client(module=module) - k8s_ansible_mixin.fail_json = module.fail_json - k8s_ansible_mixin.fail = module.fail_json - k8s_ansible_mixin.exit_json = module.exit_json - k8s_ansible_mixin.warn = module.warn - execute_module(module, k8s_ansible_mixin) + try: + client = get_api_client(module) + svc = K8sService(client, module) + execute_module(module, svc) + except CoreException as e: + module.fail_from_exception(e) if __name__ == "__main__": diff --git a/plugins/modules/k8s_json_patch.py b/plugins/modules/k8s_json_patch.py index 27b5c8f4..5ea8dbc9 100644 --- a/plugins/modules/k8s_json_patch.py +++ b/plugins/modules/k8s_json_patch.py @@ -136,10 +136,22 @@ from ansible_collections.kubernetes.core.plugins.module_utils.args_common import AUTH_ARG_SPEC, WAIT_ARG_SPEC, ) -from ansible_collections.kubernetes.core.plugins.module_utils.common import ( +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import ( get_api_client, - K8sAnsibleMixin, ) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import ( + AnsibleK8SModule, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import ( + CoreException, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import ( + diff_objects, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.waiter import ( + get_waiter, +) + try: from kubernetes.dynamic.exceptions import DynamicApiError @@ -185,7 +197,7 @@ def json_patch(existing, patch): return None, error -def execute_module(k8s_module, module): +def execute_module(module, client): kind = module.params.get("kind") api_version = module.params.get("api_version") name = module.params.get("name") @@ -200,19 +212,14 @@ def execute_module(k8s_module, module): "type" ): wait_condition = module.params["wait_condition"] - # definition is needed for wait - definition = { - "kind": kind, - "metadata": {"name": name, "namespace": namespace}, - } def build_error_msg(kind, name, msg): return "%s %s: %s" % (kind, name, msg) - resource = k8s_module.find_resource(kind, api_version, fail=True) + resource = client.resource(kind, api_version) try: - existing = resource.get(name=name, namespace=namespace) + existing = client.get(resource, name=name, namespace=namespace) except DynamicApiError as exc: msg = "Failed to retrieve requested object: {0}".format(exc.body) module.fail_json( @@ -227,7 +234,7 @@ def execute_module(k8s_module, module): msg=build_error_msg(kind, name, msg), error="", status="", reason="" ) - if module.check_mode and not k8s_module.supports_dry_run: + if module.check_mode and not client.dry_run: obj, error = json_patch(existing.to_dict(), patch) if error: module.fail_json(**error) @@ -236,7 +243,8 @@ def execute_module(k8s_module, module): if module.check_mode: params["dry_run"] = "All" try: - obj = resource.patch( + obj = client.patch( + resource, patch, name=name, namespace=namespace, @@ -255,10 +263,11 @@ def execute_module(k8s_module, module): success = True result = {"result": obj} if wait and not module.check_mode: - success, result["result"], result["duration"] = k8s_module.wait( - resource, definition, wait_sleep, wait_timeout, condition=wait_condition + waiter = get_waiter(client, resource, condition=wait_condition) + success, result["result"], result["duration"] = waiter.wait( + wait_timeout, wait_sleep, name, namespace ) - match, diffs = k8s_module.diff_objects(existing.to_dict(), obj) + match, diffs = diff_objects(existing.to_dict(), obj) result["changed"] = not match if module._diff: result["diff"] = diffs @@ -274,13 +283,14 @@ def main(): args = copy.deepcopy(AUTH_ARG_SPEC) args.update(copy.deepcopy(WAIT_ARG_SPEC)) args.update(JSON_PATCH_ARGS) - module = AnsibleModule(argument_spec=args, supports_check_mode=True) - k8s_module = K8sAnsibleMixin(module) - k8s_module.params = module.params - k8s_module.check_library_version() - client = get_api_client(module) - k8s_module.client = client - execute_module(k8s_module, module) + module = AnsibleK8SModule( + module_class=AnsibleModule, argument_spec=args, supports_check_mode=True + ) + try: + client = get_api_client(module) + execute_module(module, client) + except CoreException as e: + module.fail_from_exception(e) if __name__ == "__main__": diff --git a/plugins/modules/k8s_log.py b/plugins/modules/k8s_log.py index db63e14b..94e5d5f9 100644 --- a/plugins/modules/k8s_log.py +++ b/plugins/modules/k8s_log.py @@ -128,12 +128,22 @@ import copy from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import ( AnsibleModule, ) -from ansible.module_utils.six import PY2 - from ansible_collections.kubernetes.core.plugins.module_utils.args_common import ( AUTH_ARG_SPEC, NAME_ARG_SPEC, ) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import ( + get_api_client, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import ( + AnsibleK8SModule, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import ( + CoreException, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import ( + K8sService, +) def argspec(): @@ -151,32 +161,30 @@ def argspec(): return args -def execute_module(module, k8s_ansible_mixin): - name = module.params.get("name") - namespace = module.params.get("namespace") - label_selector = ",".join(module.params.get("label_selectors", {})) +def execute_module(svc, params): + name = params.get("name") + namespace = params.get("namespace") + label_selector = ",".join(params.get("label_selectors", {})) if name and label_selector: - module.fail(msg="Only one of name or label_selectors can be provided") + raise CoreException("Only one of name or label_selectors can be provided") - resource = k8s_ansible_mixin.find_resource( - module.params["kind"], module.params["api_version"], fail=True - ) - v1_pods = k8s_ansible_mixin.find_resource("Pod", "v1", fail=True) + resource = svc.find_resource(params["kind"], params["api_version"], fail=True) + v1_pods = svc.find_resource("Pod", "v1", fail=True) if "log" not in resource.subresources: if not name: - module.fail( - msg="name must be provided for resources that do not support the log subresource" + raise CoreException( + "name must be provided for resources that do not support the log subresource" ) instance = resource.get(name=name, namespace=namespace) - label_selector = ",".join(extract_selectors(module, instance)) + label_selector = ",".join(extract_selectors(instance)) resource = v1_pods if label_selector: instances = v1_pods.get(namespace=namespace, label_selector=label_selector) if not instances.items: - module.fail( - msg="No pods in namespace {0} matched selector {1}".format( + raise CoreException( + "No pods in namespace {0} matched selector {1}".format( namespace, label_selector ) ) @@ -185,33 +193,32 @@ def execute_module(module, k8s_ansible_mixin): resource = v1_pods kwargs = {} - if module.params.get("container"): - kwargs["query_params"] = dict(container=module.params["container"]) + if params.get("container"): + kwargs["query_params"] = {"container": params["container"]} - if module.params.get("since_seconds"): + if params.get("since_seconds"): kwargs.setdefault("query_params", {}).update( - {"sinceSeconds": module.params["since_seconds"]} + {"sinceSeconds": params["since_seconds"]} ) - if module.params.get("previous"): - kwargs.setdefault("query_params", {}).update( - {"previous": module.params["previous"]} - ) + if params.get("previous"): + kwargs.setdefault("query_params", {}).update({"previous": params["previous"]}) - log = serialize_log( - resource.log.get(name=name, namespace=namespace, serialize=False, **kwargs) + response = resource.log.get( + name=name, namespace=namespace, serialize=False, **kwargs ) + log = response.data.decode("utf8") - module.exit_json(changed=False, log=log, log_lines=log.split("\n")) + return {"changed": False, "log": log, "log_lines": log.split("\n")} -def extract_selectors(module, instance): +def extract_selectors(instance): # Parses selectors on an object based on the specifications documented here: # https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors selectors = [] if not instance.spec.selector: - module.fail( - msg="{0} {1} does not support the log subresource directly, and no Pod selector was found on the object".format( + raise CoreException( + "{0} {1} does not support the log subresource directly, and no Pod selector was found on the object".format( "/".join(instance.group, instance.apiVersion), instance.kind ) ) @@ -245,8 +252,8 @@ def extract_selectors(module, instance): ) ) else: - module.fail( - msg="The k8s_log module does not support the {0} matchExpression operator".format( + raise CoreException( + "The k8s_log module does not support the {0} matchExpression operator".format( operator.lower() ) ) @@ -254,22 +261,18 @@ def extract_selectors(module, instance): return selectors -def serialize_log(response): - if PY2: - return response.data - return response.data.decode("utf8") - - def main(): - module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True) - from ansible_collections.kubernetes.core.plugins.module_utils.common import ( - K8sAnsibleMixin, - get_api_client, + module = AnsibleK8SModule( + module_class=AnsibleModule, argument_spec=argspec(), supports_check_mode=True ) - k8s_ansible_mixin = K8sAnsibleMixin(module) - k8s_ansible_mixin.client = get_api_client(module=module) - execute_module(module, k8s_ansible_mixin) + try: + client = get_api_client(module=module) + svc = K8sService(client, module) + result = execute_module(svc, module.params) + module.exit_json(**result) + except CoreException as e: + module.fail_from_exception(e) if __name__ == "__main__": diff --git a/plugins/modules/k8s_rollback.py b/plugins/modules/k8s_rollback.py index df4f419d..8dfab459 100644 --- a/plugins/modules/k8s_rollback.py +++ b/plugins/modules/k8s_rollback.py @@ -86,12 +86,23 @@ from ansible_collections.kubernetes.core.plugins.module_utils.args_common import AUTH_ARG_SPEC, NAME_ARG_SPEC, ) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import ( + get_api_client, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import ( + AnsibleK8SModule, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import ( + CoreException, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import ( + K8sService, +) -def get_managed_resource(module): +def get_managed_resource(kind): managed_resource = {} - kind = module.params["kind"] if kind == "DaemonSet": managed_resource["kind"] = "ControllerRevision" managed_resource["api_version"] = "apps/v1" @@ -99,14 +110,17 @@ def get_managed_resource(module): managed_resource["kind"] = "ReplicaSet" managed_resource["api_version"] = "apps/v1" else: - module.fail(msg="Cannot perform rollback on resource of kind {0}".format(kind)) + raise CoreException( + "Cannot perform rollback on resource of kind {0}".format(kind) + ) return managed_resource -def execute_module(module, k8s_ansible_mixin): +def execute_module(svc): results = [] + module = svc.module - resources = k8s_ansible_mixin.kubernetes_facts( + resources = svc.find( module.params["kind"], module.params["api_version"], module.params["name"], @@ -117,14 +131,16 @@ def execute_module(module, k8s_ansible_mixin): changed = False for resource in resources["resources"]: - result = perform_action(module, k8s_ansible_mixin, resource) + result = perform_action(svc, resource) changed = result["changed"] or changed results.append(result) module.exit_json(**{"changed": changed, "rollback_info": results}) -def perform_action(module, k8s_ansible_mixin, resource): +def perform_action(svc, resource): + module = svc.module + if module.params["kind"] == "DaemonSet": current_revision = resource["metadata"]["generation"] elif module.params["kind"] == "Deployment": @@ -132,8 +148,8 @@ def perform_action(module, k8s_ansible_mixin, resource): "deployment.kubernetes.io/revision" ] - managed_resource = get_managed_resource(module) - managed_resources = k8s_ansible_mixin.kubernetes_facts( + managed_resource = get_managed_resource(module.params["kind"]) + managed_resources = svc.find( managed_resource["kind"], managed_resource["api_version"], "", @@ -185,7 +201,7 @@ def perform_action(module, k8s_ansible_mixin, resource): rollback = resource if not module.check_mode: - rollback = k8s_ansible_mixin.client.request( + rollback = svc.client.client.request( "PATCH", "/apis/{0}/namespaces/{1}/{2}/{3}".format( module.params["api_version"], @@ -242,15 +258,16 @@ def get_previous_revision(all_resources, current_revision): def main(): - module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True) - from ansible_collections.kubernetes.core.plugins.module_utils.common import ( - K8sAnsibleMixin, - get_api_client, + module = AnsibleK8SModule( + module_class=AnsibleModule, argument_spec=argspec(), supports_check_mode=True ) - k8s_ansible_mixin = K8sAnsibleMixin(module) - k8s_ansible_mixin.client = get_api_client(module=module) - execute_module(module, k8s_ansible_mixin) + try: + client = get_api_client(module=module) + svc = K8sService(client, module) + execute_module(svc) + except CoreException as e: + module.fail_from_exception(e) if __name__ == "__main__": diff --git a/plugins/modules/k8s_scale.py b/plugins/modules/k8s_scale.py index f0c209f3..01edfa6d 100644 --- a/plugins/modules/k8s_scale.py +++ b/plugins/modules/k8s_scale.py @@ -143,6 +143,14 @@ result: import copy +try: + from kubernetes.dynamic.exceptions import NotFoundError +except ImportError: + # Handled in module setup + pass + +from ansible.module_utils._text import to_native + from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import ( AnsibleModule, ) @@ -151,7 +159,25 @@ from ansible_collections.kubernetes.core.plugins.module_utils.args_common import RESOURCE_ARG_SPEC, NAME_ARG_SPEC, ) - +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import ( + get_api_client, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import ( + AnsibleK8SModule, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import ( + CoreException, + ResourceTimeout, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import ( + diff_objects, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.waiter import ( + get_waiter, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.resource import ( + create_definitions, +) SCALE_ARG_SPEC = { "replicas": {"type": "int", "required": True}, @@ -163,27 +189,20 @@ SCALE_ARG_SPEC = { } -def execute_module( - module, - k8s_ansible_mixin, -): - k8s_ansible_mixin.set_resource_definitions(module) - - definition = k8s_ansible_mixin.resource_definitions[0] - - name = definition["metadata"]["name"] - namespace = definition["metadata"].get("namespace") - api_version = definition["apiVersion"] - kind = definition["kind"] +def execute_module(client, module): current_replicas = module.params.get("current_replicas") replicas = module.params.get("replicas") resource_version = module.params.get("resource_version") - + definitions = create_definitions(module.params) + definition = definitions[0] + name = definition["metadata"].get("name") + namespace = definition["metadata"].get("namespace") + api_version = definition["apiVersion"] + kind = definition["kind"] label_selectors = module.params.get("label_selectors") if not label_selectors: label_selectors = [] continue_on_error = module.params.get("continue_on_error") - wait = module.params.get("wait") wait_time = module.params.get("wait_timeout") wait_sleep = module.params.get("wait_sleep") @@ -195,12 +214,7 @@ def execute_module( if wait: return_attributes["duration"] = 0 - resource = k8s_ansible_mixin.find_resource(kind, api_version, fail=True) - - from ansible_collections.kubernetes.core.plugins.module_utils.common import ( - NotFoundError, - ) - + resource = client.resource(kind, api_version) multiple_scale = False try: existing = resource.get( @@ -211,11 +225,10 @@ def execute_module( multiple_scale = len(existing_items) > 1 else: existing_items = [existing] - except NotFoundError as exc: - module.fail_json( - msg="Failed to retrieve requested object: {0}".format(exc), - error=exc.value.get("status"), - ) + except NotFoundError as e: + reason = e.body if hasattr(e, "body") else e + msg = "Failed to retrieve requested object: {0}".format(reason) + raise CoreException(msg) from e if multiple_scale: # when scaling multiple resource, the 'result' is changed to 'results' and is a list @@ -278,23 +291,28 @@ def execute_module( if module.check_mode: result["result"] = existing.to_dict() else: - result["result"] = resource.patch(existing.to_dict()).to_dict() + result["result"] = client.patch( + resource, existing.to_dict() + ).to_dict() else: - result = scale( - module, - k8s_ansible_mixin, - resource, - existing, - replicas, - wait, - wait_time, - wait_sleep, - ) + try: + result = scale( + client, + module, + resource, + existing, + replicas, + wait, + wait_time, + wait_sleep, + ) + except CoreException as e: + module.fail_json(msg=to_native(e)) changed = changed or result["changed"] else: name = existing.metadata.name namespace = existing.metadata.namespace - existing = resource.get(name=name, namespace=namespace) + existing = client.get(resource, name=name, namespace=namespace) result = {"changed": False, "result": existing.to_dict()} if module._diff: result["diff"] = {} @@ -320,8 +338,8 @@ def argspec(): def scale( + client, module, - k8s_ansible_mixin, resource, existing_object, replicas, @@ -334,8 +352,8 @@ def scale( kind = existing_object.kind if not hasattr(resource, "scale"): - module.fail_json( - msg="Cannot perform scale on resource of kind {0}".format(resource.kind) + raise CoreException( + "Cannot perform scale on resource of kind {0}".format(resource.kind) ) scale_obj = { @@ -344,32 +362,37 @@ def scale( "spec": {"replicas": replicas}, } - existing = resource.get(name=name, namespace=namespace) + existing = client.get(resource, name=name, namespace=namespace) result = dict() if module.check_mode: k8s_obj = copy.deepcopy(existing.to_dict()) k8s_obj["spec"]["replicas"] = replicas - match, diffs = k8s_ansible_mixin.diff_objects(existing.to_dict(), k8s_obj) if wait: result["duration"] = 0 result["result"] = k8s_obj else: try: resource.scale.patch(body=scale_obj) - except Exception as exc: - module.fail_json(msg="Scale request failed: {0}".format(exc)) + except Exception as e: + reason = e.body if hasattr(e, "body") else e + msg = "Scale request failed: {0}".format(reason) + raise CoreException(msg) from e - k8s_obj = resource.get(name=name, namespace=namespace).to_dict() + k8s_obj = client.get(resource, name=name, namespace=namespace).to_dict() result["result"] = k8s_obj - if wait and not module.check_mode: - success, result["result"], result["duration"] = k8s_ansible_mixin.wait( - resource, scale_obj, wait_sleep, wait_time + if wait: + waiter = get_waiter(client, resource) + success, result["result"], result["duration"] = waiter.wait( + timeout=wait_time, + sleep=wait_sleep, + name=name, + namespace=namespace, ) if not success: - module.fail_json(msg="Resource scaling timed out", **result) + raise ResourceTimeout("Resource scaling timed out", **result) - match, diffs = k8s_ansible_mixin.diff_objects(existing.to_dict(), k8s_obj) + match, diffs = diff_objects(existing.to_dict(), result["result"]) result["changed"] = not match if module._diff: result["diff"] = diffs @@ -381,19 +404,18 @@ def main(): mutually_exclusive = [ ("resource_definition", "src"), ] - module = AnsibleModule( + module = AnsibleK8SModule( + module_class=AnsibleModule, argument_spec=argspec(), mutually_exclusive=mutually_exclusive, supports_check_mode=True, ) - from ansible_collections.kubernetes.core.plugins.module_utils.common import ( - K8sAnsibleMixin, - get_api_client, - ) - k8s_ansible_mixin = K8sAnsibleMixin(module) - k8s_ansible_mixin.client = get_api_client(module=module) - execute_module(module, k8s_ansible_mixin) + try: + client = get_api_client(module=module) + execute_module(client, module) + except CoreException as e: + module.fail_from_exception(e) if __name__ == "__main__": diff --git a/plugins/modules/k8s_service.py b/plugins/modules/k8s_service.py index 1e992f7b..4eb280bf 100644 --- a/plugins/modules/k8s_service.py +++ b/plugins/modules/k8s_service.py @@ -154,6 +154,22 @@ from ansible_collections.kubernetes.core.plugins.module_utils.args_common import COMMON_ARG_SPEC, RESOURCE_ARG_SPEC, ) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import ( + AnsibleK8SModule, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import ( + get_api_client, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import ( + CoreException, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import ( + K8sService, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.resource import ( + create_definitions, +) + SERVICE_ARG_SPEC = { "apply": {"type": "bool", "default": False}, @@ -195,10 +211,35 @@ def argspec(): return argument_spec -def execute_module(module, k8s_ansible_mixin): - """Module execution""" - k8s_ansible_mixin.set_resource_definitions(module) +def perform_action(svc, resource, definition, params): + state = params.get("state", None) + result = {} + existing = svc.retrieve(resource, definition) + + if state == "absent": + result = svc.delete(resource, definition, existing) + result["method"] = "delete" + else: + if params.get("apply"): + result = svc.apply(resource, definition, existing) + result["method"] = "apply" + elif not existing: + result = svc.create(resource, definition) + result["method"] = "create" + elif params.get("force", False): + result = svc.replace(resource, definition, existing) + result["method"] = "replace" + else: + result = svc.update(resource, definition, existing) + result["method"] = "update" + + return result + + +def execute_module(svc): + """Module execution""" + module = svc.module api_version = "v1" selector = module.params.get("selector") service_type = module.params.get("type") @@ -218,28 +259,30 @@ def execute_module(module, k8s_ansible_mixin): def_meta["name"] = module.params.get("name") def_meta["namespace"] = module.params.get("namespace") - # 'resource_definition:' has lower priority than module parameters - definition = dict( - merge_dicts(k8s_ansible_mixin.resource_definitions[0], definition) - ) + definitions = create_definitions(module.params) - resource = k8s_ansible_mixin.find_resource("Service", api_version, fail=True) - definition = k8s_ansible_mixin.set_defaults(resource, definition) - result = k8s_ansible_mixin.perform_action(resource, definition) + # 'resource_definition:' has lower priority than module parameters + definition = dict(merge_dicts(definitions[0], definition)) + resource = svc.find_resource("Service", api_version, fail=True) + + result = perform_action(svc, resource, definition, module.params) module.exit_json(**result) def main(): - module = AnsibleModule(argument_spec=argspec(), supports_check_mode=True) - from ansible_collections.kubernetes.core.plugins.module_utils.common import ( - K8sAnsibleMixin, - get_api_client, + module = AnsibleK8SModule( + module_class=AnsibleModule, + argument_spec=argspec(), + supports_check_mode=True, ) - k8s_ansible_mixin = K8sAnsibleMixin(module) - k8s_ansible_mixin.client = get_api_client(module=module) - execute_module(module, k8s_ansible_mixin) + try: + client = get_api_client(module=module) + svc = K8sService(client, module) + execute_module(svc) + except CoreException as e: + module.fail_from_exception(e) if __name__ == "__main__": diff --git a/plugins/modules/k8s_taint.py b/plugins/modules/k8s_taint.py index 4146a0e2..bfa80db5 100644 --- a/plugins/modules/k8s_taint.py +++ b/plugins/modules/k8s_taint.py @@ -126,22 +126,29 @@ result: import copy -from ansible.module_utils.basic import AnsibleModule from ansible.module_utils._text import to_native -from ansible_collections.kubernetes.core.plugins.module_utils.common import ( - K8sAnsibleMixin, - get_api_client, +from ansible_collections.kubernetes.core.plugins.module_utils.ansiblemodule import ( + AnsibleModule, ) from ansible_collections.kubernetes.core.plugins.module_utils.args_common import ( AUTH_ARG_SPEC, ) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import ( + get_api_client, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import ( + AnsibleK8SModule, +) +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions import ( + CoreException, +) try: from kubernetes.client.api import core_v1_api from kubernetes.client.exceptions import ApiException except ImportError: - # ImportError are managed by the common module already. + # ImportErrors are handled during module setup pass @@ -191,21 +198,9 @@ def argspec(): class K8sTaintAnsible: - def __init__(self, module): + def __init__(self, module, client): self.module = module - self.k8s_ansible_mixin = K8sAnsibleMixin(module=self.module) - self.k8s_ansible_mixin.client = get_api_client(module=self.module) - self.k8s_ansible_mixin.module = self.module - self.k8s_ansible_mixin.argspec = self.module.argument_spec - self.k8s_ansible_mixin.check_mode = self.module.check_mode - self.k8s_ansible_mixin.params = self.module.params - self.k8s_ansible_mixin.fail_json = self.module.fail_json - self.k8s_ansible_mixin.fail = self.module.fail_json - self.k8s_ansible_mixin.exit_json = self.module.exit_json - self.k8s_ansible_mixin.warn = self.module.warn - self.k8s_ansible_mixin.warnings = [] - self.api_instance = core_v1_api.CoreV1Api(self.k8s_ansible_mixin.client.client) - self.k8s_ansible_mixin.check_library_version() + self.api_instance = core_v1_api.CoreV1Api(client.client) self.changed = False def get_node(self, name): @@ -301,12 +296,17 @@ class K8sTaintAnsible: def main(): - module = AnsibleModule( + module = AnsibleK8SModule( + module_class=AnsibleModule, argument_spec=argspec(), supports_check_mode=True, ) - k8s_taint = K8sTaintAnsible(module) - k8s_taint.execute_module() + try: + client = get_api_client(module) + k8s_taint = K8sTaintAnsible(module, client.client) + k8s_taint.execute_module() + except CoreException as e: + module.fail_from_exception(e) if __name__ == "__main__": diff --git a/test-requirements.txt b/test-requirements.txt index 880eec68..e20bfb9f 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,4 +1,6 @@ kubernetes-validate coverage==4.5.4 +mock pytest pytest-xdist +pytest-mock diff --git a/tests/config.yml b/tests/config.yml new file mode 100644 index 00000000..9e402bda --- /dev/null +++ b/tests/config.yml @@ -0,0 +1,2 @@ +modules: + python_requires: ">=3.6" diff --git a/tests/integration/targets/k8s_gc/tasks/main.yml b/tests/integration/targets/k8s_gc/tasks/main.yml index 923ffc3f..a2f60c8a 100644 --- a/tests/integration/targets/k8s_gc/tasks/main.yml +++ b/tests/integration/targets/k8s_gc/tasks/main.yml @@ -5,7 +5,7 @@ # This is a job definition that runs for 10 minutes and won't gracefully # shutdown. It allows us to test foreground vs background deletion. job_definition: - apiVersion: v1 + apiVersion: batch/v1 kind: Job metadata: name: "{{ gc_name }}" diff --git a/tests/integration/targets/k8s_merge_type/tasks/main.yml b/tests/integration/targets/k8s_merge_type/tasks/main.yml index 32e41d8b..3c544c76 100644 --- a/tests/integration/targets/k8s_merge_type/tasks/main.yml +++ b/tests/integration/targets/k8s_merge_type/tasks/main.yml @@ -97,6 +97,7 @@ - name: patch service using json merge patch kubernetes.core.k8s: kind: Deployment + api_version: apps/v1 namespace: "{{ k8s_patch_namespace }}" name: "{{ k8s_merge }}" merge_type: diff --git a/tests/integration/targets/k8s_patched/tasks/main.yml b/tests/integration/targets/k8s_patched/tasks/main.yml index 6fe514d3..8846d064 100644 --- a/tests/integration/targets/k8s_patched/tasks/main.yml +++ b/tests/integration/targets/k8s_patched/tasks/main.yml @@ -52,7 +52,7 @@ assert: that: - patch_resource.changed - - patch_resource.result.results | selectattr('warning', 'defined') | list | length == 1 + - patch_resource.result.results | selectattr('warnings', 'defined') | list | length == 1 - name: Ensure namespace {{ patch_only_namespace[0] }} was patched correctly kubernetes.core.k8s_info: diff --git a/tests/integration/targets/k8s_scale/tasks/main.yml b/tests/integration/targets/k8s_scale/tasks/main.yml index d58a01bc..eb2107d3 100644 --- a/tests/integration/targets/k8s_scale/tasks/main.yml +++ b/tests/integration/targets/k8s_scale/tasks/main.yml @@ -150,7 +150,7 @@ - name: Reapply the earlier deployment k8s: definition: - api_version: apps/v1 + apiVersion: apps/v1 kind: Deployment metadata: name: scale-deploy diff --git a/tests/integration/targets/k8s_validate/tasks/main.yml b/tests/integration/targets/k8s_validate/tasks/main.yml index 18587560..900e6f70 100644 --- a/tests/integration/targets/k8s_validate/tasks/main.yml +++ b/tests/integration/targets/k8s_validate/tasks/main.yml @@ -42,7 +42,7 @@ - assert: that: - k8s_no_validate is failed - - "k8s_no_validate.msg == 'kubernetes-validate python library is required to validate resources'" + - "'Failed to import the required Python library (kubernetes-validate)' in k8s_no_validate.msg" - file: path: "{{ virtualenv }}" @@ -201,7 +201,7 @@ - name: assert that task failed with proper message assert: that: - - '"kubernetes >= 17.17.0 is required" in _result.msg' + - '"This is required to use in-memory config." in _result.msg' when: - _stat.stat.exists - _stat.stat.readable diff --git a/tests/sanity/ignore-2.10.txt b/tests/sanity/ignore-2.10.txt index 433223cc..dd9afccb 100644 --- a/tests/sanity/ignore-2.10.txt +++ b/tests/sanity/ignore-2.10.txt @@ -1,262 +1,178 @@ -tests/integration/targets/k8s_scale/files/deployment.yaml yamllint!skip -tests/integration/targets/helm/files/appversionless-chart-v2/templates/configmap.yaml yamllint!skip -tests/integration/targets/helm/files/appversionless-chart/templates/configmap.yaml yamllint!skip -tests/integration/targets/helm/files/test-chart-v2/templates/configmap.yaml yamllint!skip -tests/integration/targets/helm/files/test-chart/templates/configmap.yaml yamllint!skip -plugins/module_utils/__init__.py compile-2.6!skip -plugins/module_utils/__init__.py compile-2.7!skip -plugins/module_utils/__init__.py compile-3.5!skip -plugins/module_utils/__init__.py future-import-boilerplate!skip -plugins/module_utils/__init__.py import-2.6!skip -plugins/module_utils/__init__.py import-2.7!skip -plugins/module_utils/__init__.py import-3.5!skip -plugins/module_utils/__init__.py metaclass-boilerplate!skip -plugins/module_utils/ansiblemodule.py compile-2.6!skip -plugins/module_utils/ansiblemodule.py compile-2.7!skip -plugins/module_utils/ansiblemodule.py compile-3.5!skip -plugins/module_utils/ansiblemodule.py future-import-boilerplate!skip -plugins/module_utils/ansiblemodule.py import-2.6!skip -plugins/module_utils/ansiblemodule.py import-2.7!skip -plugins/module_utils/ansiblemodule.py import-3.5!skip -plugins/module_utils/ansiblemodule.py metaclass-boilerplate!skip -plugins/module_utils/apply.py compile-2.6!skip -plugins/module_utils/apply.py compile-2.7!skip -plugins/module_utils/apply.py compile-3.5!skip -plugins/module_utils/apply.py future-import-boilerplate!skip -plugins/module_utils/apply.py import-2.6!skip -plugins/module_utils/apply.py import-2.7!skip -plugins/module_utils/apply.py import-3.5!skip -plugins/module_utils/apply.py metaclass-boilerplate!skip -plugins/module_utils/args_common.py compile-2.6!skip -plugins/module_utils/args_common.py compile-2.7!skip -plugins/module_utils/args_common.py compile-3.5!skip -plugins/module_utils/args_common.py future-import-boilerplate!skip -plugins/module_utils/args_common.py import-2.6!skip -plugins/module_utils/args_common.py import-2.7!skip -plugins/module_utils/args_common.py import-3.5!skip -plugins/module_utils/args_common.py metaclass-boilerplate!skip -plugins/module_utils/client/discovery.py future-import-boilerplate!skip -plugins/module_utils/client/discovery.py import-2.6!skip -plugins/module_utils/client/discovery.py import-2.7!skip -plugins/module_utils/client/discovery.py import-3.5!skip plugins/module_utils/client/discovery.py import-3.6!skip plugins/module_utils/client/discovery.py import-3.7!skip plugins/module_utils/client/discovery.py import-3.8!skip plugins/module_utils/client/discovery.py import-3.9!skip -plugins/module_utils/client/discovery.py metaclass-boilerplate!skip -plugins/module_utils/client/resource.py import-2.6!skip -plugins/module_utils/client/resource.py import-2.7!skip -plugins/module_utils/client/resource.py import-3.5!skip plugins/module_utils/client/resource.py import-3.6!skip plugins/module_utils/client/resource.py import-3.7!skip plugins/module_utils/client/resource.py import-3.8!skip plugins/module_utils/client/resource.py import-3.9!skip -plugins/module_utils/common.py compile-2.6!skip -plugins/module_utils/common.py compile-2.7!skip -plugins/module_utils/common.py compile-3.5!skip -plugins/module_utils/common.py future-import-boilerplate!skip -plugins/module_utils/common.py import-2.6!skip -plugins/module_utils/common.py import-2.7!skip -plugins/module_utils/common.py import-3.5!skip -plugins/module_utils/common.py metaclass-boilerplate!skip -plugins/module_utils/exceptions.py compile-2.6!skip -plugins/module_utils/exceptions.py compile-2.7!skip -plugins/module_utils/exceptions.py compile-3.5!skip -plugins/module_utils/exceptions.py future-import-boilerplate!skip -plugins/module_utils/exceptions.py import-2.6!skip -plugins/module_utils/exceptions.py import-2.7!skip -plugins/module_utils/exceptions.py import-3.5!skip -plugins/module_utils/exceptions.py metaclass-boilerplate!skip -plugins/module_utils/hashes.py compile-2.6!skip -plugins/module_utils/hashes.py compile-2.7!skip -plugins/module_utils/hashes.py compile-3.5!skip -plugins/module_utils/hashes.py future-import-boilerplate!skip -plugins/module_utils/hashes.py import-2.6!skip -plugins/module_utils/hashes.py import-2.7!skip -plugins/module_utils/hashes.py import-3.5!skip -plugins/module_utils/hashes.py metaclass-boilerplate!skip -plugins/module_utils/helm.py compile-2.6!skip -plugins/module_utils/helm.py compile-2.7!skip -plugins/module_utils/helm.py compile-3.5!skip -plugins/module_utils/helm.py future-import-boilerplate!skip -plugins/module_utils/helm.py import-2.6!skip -plugins/module_utils/helm.py import-2.7!skip -plugins/module_utils/helm.py import-3.5!skip -plugins/module_utils/helm.py metaclass-boilerplate!skip -plugins/module_utils/k8sdynamicclient.py compile-2.6!skip -plugins/module_utils/k8sdynamicclient.py compile-2.7!skip -plugins/module_utils/k8sdynamicclient.py compile-3.5!skip -plugins/module_utils/k8sdynamicclient.py future-import-boilerplate!skip -plugins/module_utils/k8sdynamicclient.py import-2.6!skip -plugins/module_utils/k8sdynamicclient.py import-2.7!skip -plugins/module_utils/k8sdynamicclient.py import-3.5!skip plugins/module_utils/k8sdynamicclient.py import-3.6!skip plugins/module_utils/k8sdynamicclient.py import-3.7!skip plugins/module_utils/k8sdynamicclient.py import-3.8!skip plugins/module_utils/k8sdynamicclient.py import-3.9!skip -plugins/module_utils/k8sdynamicclient.py metaclass-boilerplate!skip -plugins/modules/__init__.py compile-2.6!skip -plugins/modules/__init__.py compile-2.7!skip -plugins/modules/__init__.py compile-3.5!skip -plugins/modules/__init__.py future-import-boilerplate!skip -plugins/modules/__init__.py import-2.6!skip -plugins/modules/__init__.py import-2.7!skip -plugins/modules/__init__.py import-3.5!skip -plugins/modules/__init__.py metaclass-boilerplate!skip -plugins/modules/helm.py compile-2.6!skip -plugins/modules/helm.py compile-2.7!skip -plugins/modules/helm.py compile-3.5!skip -plugins/modules/helm.py future-import-boilerplate!skip -plugins/modules/helm.py import-2.6!skip -plugins/modules/helm.py import-2.7!skip -plugins/modules/helm.py import-3.5!skip -plugins/modules/helm.py metaclass-boilerplate!skip -plugins/modules/helm_info.py compile-2.6!skip -plugins/modules/helm_info.py compile-2.7!skip -plugins/modules/helm_info.py compile-3.5!skip -plugins/modules/helm_info.py future-import-boilerplate!skip -plugins/modules/helm_info.py import-2.6!skip -plugins/modules/helm_info.py import-2.7!skip -plugins/modules/helm_info.py import-3.5!skip -plugins/modules/helm_info.py metaclass-boilerplate!skip -plugins/modules/helm_plugin.py compile-2.6!skip -plugins/modules/helm_plugin.py compile-2.7!skip -plugins/modules/helm_plugin.py compile-3.5!skip -plugins/modules/helm_plugin.py future-import-boilerplate!skip -plugins/modules/helm_plugin.py import-2.6!skip -plugins/modules/helm_plugin.py import-2.7!skip -plugins/modules/helm_plugin.py import-3.5!skip -plugins/modules/helm_plugin.py metaclass-boilerplate!skip -plugins/modules/helm_plugin_info.py compile-2.6!skip -plugins/modules/helm_plugin_info.py compile-2.7!skip -plugins/modules/helm_plugin_info.py compile-3.5!skip -plugins/modules/helm_plugin_info.py future-import-boilerplate!skip -plugins/modules/helm_plugin_info.py import-2.6!skip -plugins/modules/helm_plugin_info.py import-2.7!skip -plugins/modules/helm_plugin_info.py import-3.5!skip -plugins/modules/helm_plugin_info.py metaclass-boilerplate!skip -plugins/modules/helm_repository.py compile-2.6!skip -plugins/modules/helm_repository.py compile-2.7!skip -plugins/modules/helm_repository.py compile-3.5!skip -plugins/modules/helm_repository.py future-import-boilerplate!skip -plugins/modules/helm_repository.py import-2.6!skip -plugins/modules/helm_repository.py import-2.7!skip -plugins/modules/helm_repository.py import-3.5!skip -plugins/modules/helm_repository.py metaclass-boilerplate!skip -plugins/modules/helm_template.py compile-2.6!skip -plugins/modules/helm_template.py compile-2.7!skip -plugins/modules/helm_template.py compile-3.5!skip -plugins/modules/helm_template.py future-import-boilerplate!skip -plugins/modules/helm_template.py import-2.6!skip -plugins/modules/helm_template.py import-2.7!skip -plugins/modules/helm_template.py import-3.5!skip -plugins/modules/helm_template.py metaclass-boilerplate!skip -plugins/modules/k8s.py compile-2.6!skip -plugins/modules/k8s.py compile-2.7!skip -plugins/modules/k8s.py compile-3.5!skip -plugins/modules/k8s.py future-import-boilerplate!skip -plugins/modules/k8s.py import-2.6!skip -plugins/modules/k8s.py import-2.7!skip -plugins/modules/k8s.py import-3.5!skip -plugins/modules/k8s.py metaclass-boilerplate!skip plugins/modules/k8s.py validate-modules:parameter-type-not-in-doc -plugins/modules/k8s.py validate-modules:return-syntax-error -plugins/modules/k8s_cluster_info.py compile-2.6!skip -plugins/modules/k8s_cluster_info.py compile-2.7!skip -plugins/modules/k8s_cluster_info.py compile-3.5!skip -plugins/modules/k8s_cluster_info.py future-import-boilerplate!skip -plugins/modules/k8s_cluster_info.py import-2.6!skip -plugins/modules/k8s_cluster_info.py import-2.7!skip -plugins/modules/k8s_cluster_info.py import-3.5!skip -plugins/modules/k8s_cluster_info.py metaclass-boilerplate!skip -plugins/modules/k8s_exec.py compile-2.6!skip -plugins/modules/k8s_exec.py compile-2.7!skip -plugins/modules/k8s_exec.py compile-3.5!skip -plugins/modules/k8s_exec.py future-import-boilerplate!skip -plugins/modules/k8s_exec.py import-2.6!skip -plugins/modules/k8s_exec.py import-2.7!skip -plugins/modules/k8s_exec.py import-3.5!skip -plugins/modules/k8s_exec.py metaclass-boilerplate!skip -plugins/modules/k8s_info.py compile-2.6!skip -plugins/modules/k8s_info.py compile-2.7!skip -plugins/modules/k8s_info.py compile-3.5!skip -plugins/modules/k8s_info.py future-import-boilerplate!skip -plugins/modules/k8s_info.py import-2.6!skip -plugins/modules/k8s_info.py import-2.7!skip -plugins/modules/k8s_info.py import-3.5!skip -plugins/modules/k8s_info.py metaclass-boilerplate!skip -plugins/modules/k8s_json_patch.py compile-2.6!skip -plugins/modules/k8s_json_patch.py compile-2.7!skip -plugins/modules/k8s_json_patch.py compile-3.5!skip -plugins/modules/k8s_json_patch.py future-import-boilerplate!skip -plugins/modules/k8s_json_patch.py import-2.6!skip -plugins/modules/k8s_json_patch.py import-2.7!skip -plugins/modules/k8s_json_patch.py import-3.5!skip -plugins/modules/k8s_json_patch.py metaclass-boilerplate!skip -plugins/modules/k8s_log.py compile-2.6!skip -plugins/modules/k8s_log.py compile-2.7!skip -plugins/modules/k8s_log.py compile-3.5!skip -plugins/modules/k8s_log.py future-import-boilerplate!skip -plugins/modules/k8s_log.py import-2.6!skip -plugins/modules/k8s_log.py import-2.7!skip -plugins/modules/k8s_log.py import-3.5!skip -plugins/modules/k8s_log.py metaclass-boilerplate!skip -plugins/modules/k8s_rollback.py compile-2.6!skip -plugins/modules/k8s_rollback.py compile-2.7!skip -plugins/modules/k8s_rollback.py compile-3.5!skip -plugins/modules/k8s_rollback.py future-import-boilerplate!skip -plugins/modules/k8s_rollback.py import-2.6!skip -plugins/modules/k8s_rollback.py import-2.7!skip -plugins/modules/k8s_rollback.py import-3.5!skip -plugins/modules/k8s_rollback.py metaclass-boilerplate!skip -plugins/modules/k8s_scale.py compile-2.6!skip -plugins/modules/k8s_scale.py compile-2.7!skip -plugins/modules/k8s_scale.py compile-3.5!skip -plugins/modules/k8s_scale.py future-import-boilerplate!skip -plugins/modules/k8s_scale.py import-2.6!skip -plugins/modules/k8s_scale.py import-2.7!skip -plugins/modules/k8s_scale.py import-3.5!skip -plugins/modules/k8s_scale.py metaclass-boilerplate!skip plugins/modules/k8s_scale.py validate-modules:parameter-type-not-in-doc -plugins/modules/k8s_scale.py validate-modules:return-syntax-error -plugins/modules/k8s_service.py compile-2.6!skip -plugins/modules/k8s_service.py compile-2.7!skip -plugins/modules/k8s_service.py compile-3.5!skip -plugins/modules/k8s_service.py future-import-boilerplate!skip -plugins/modules/k8s_service.py import-2.6!skip -plugins/modules/k8s_service.py import-2.7!skip -plugins/modules/k8s_service.py import-3.5!skip -plugins/modules/k8s_service.py metaclass-boilerplate!skip plugins/modules/k8s_service.py validate-modules:parameter-type-not-in-doc -plugins/modules/k8s_service.py validate-modules:return-syntax-error +tests/unit/module_utils/fixtures/definitions.yml yamllint!skip +tests/unit/module_utils/fixtures/deployments.yml yamllint!skip +tests/unit/module_utils/fixtures/pods.yml yamllint!skip +tests/integration/targets/helm/files/appversionless-chart-v2/templates/configmap.yaml yamllint!skip +tests/integration/targets/helm/files/appversionless-chart/templates/configmap.yaml yamllint!skip +tests/integration/targets/helm/files/test-chart-v2/templates/configmap.yaml yamllint!skip +tests/integration/targets/helm/files/test-chart/templates/configmap.yaml yamllint!skip +tests/integration/targets/k8s_scale/files/deployment.yaml yamllint!skip tests/sanity/refresh_ignore_files shebang!skip -tests/unit/module_utils/test_discoverer.py future-import-boilerplate!skip -tests/unit/module_utils/test_discoverer.py metaclass-boilerplate!skip -plugins/modules/k8s_cp.py compile-2.6!skip -plugins/modules/k8s_cp.py compile-2.7!skip -plugins/modules/k8s_cp.py import-2.6!skip -plugins/modules/k8s_cp.py import-2.7!skip -tests/integration/targets/k8s_copy/library/kubectl_file_compare.py compile-2.6!skip -tests/integration/targets/k8s_copy/library/kubectl_file_compare.py compile-2.7!skip -tests/integration/targets/k8s_copy/library/kubectl_file_compare.py compile-3.5!skip -tests/unit/module_utils/test_selector.py future-import-boilerplate!skip -tests/unit/module_utils/test_selector.py metaclass-boilerplate!skip -plugins/module_utils/selector.py future-import-boilerplate!skip -plugins/module_utils/selector.py metaclass-boilerplate!skip -plugins/lookup/kustomize.py future-import-boilerplate!skip -plugins/lookup/kustomize.py metaclass-boilerplate!skip -tests/integration/targets/helm/library/helm_test_version.py metaclass-boilerplate!skip -tests/integration/targets/helm/library/helm_test_version.py future-import-boilerplate!skip -plugins/modules/k8s_taint.py compile-2.7!skip -plugins/modules/k8s_taint.py compile-3.5!skip -plugins/modules/k8s_taint.py future-import-boilerplate!skip -plugins/modules/k8s_taint.py import-2.7!skip -plugins/modules/k8s_taint.py import-3.5!skip -plugins/modules/k8s_taint.py metaclass-boilerplate!skip +plugins/modules/k8s.py validate-modules:return-syntax-error +plugins/modules/k8s_scale.py validate-modules:return-syntax-error +plugins/modules/k8s_service.py validate-modules:return-syntax-error plugins/modules/k8s_taint.py validate-modules:return-syntax-error +plugins/doc_fragments/k8s_name_options.py future-import-boilerplate!skip +plugins/doc_fragments/k8s_auth_options.py future-import-boilerplate!skip +plugins/doc_fragments/helm_common_options.py future-import-boilerplate!skip +plugins/doc_fragments/k8s_state_options.py future-import-boilerplate!skip +plugins/doc_fragments/k8s_wait_options.py future-import-boilerplate!skip +plugins/doc_fragments/k8s_scale_options.py future-import-boilerplate!skip +plugins/doc_fragments/k8s_delete_options.py future-import-boilerplate!skip +plugins/doc_fragments/__init__.py future-import-boilerplate!skip +plugins/doc_fragments/k8s_resource_options.py future-import-boilerplate!skip +plugins/module_utils/helm.py future-import-boilerplate!skip +plugins/module_utils/apply.py future-import-boilerplate!skip +plugins/module_utils/hashes.py future-import-boilerplate!skip +plugins/module_utils/version.py future-import-boilerplate!skip +plugins/module_utils/_version.py future-import-boilerplate!skip plugins/module_utils/copy.py future-import-boilerplate!skip +plugins/module_utils/args_common.py future-import-boilerplate!skip +plugins/module_utils/__init__.py future-import-boilerplate!skip +plugins/module_utils/selector.py future-import-boilerplate!skip +plugins/module_utils/k8sdynamicclient.py future-import-boilerplate!skip +plugins/module_utils/common.py future-import-boilerplate!skip +plugins/module_utils/ansiblemodule.py future-import-boilerplate!skip +plugins/module_utils/exceptions.py future-import-boilerplate!skip +plugins/module_utils/client/resource.py future-import-boilerplate!skip +plugins/module_utils/client/discovery.py future-import-boilerplate!skip +plugins/module_utils/k8s/resource.py future-import-boilerplate!skip +plugins/module_utils/k8s/core.py future-import-boilerplate!skip +plugins/module_utils/k8s/waiter.py future-import-boilerplate!skip +plugins/module_utils/k8s/client.py future-import-boilerplate!skip +plugins/module_utils/k8s/runner.py future-import-boilerplate!skip +plugins/module_utils/k8s/service.py future-import-boilerplate!skip +plugins/module_utils/k8s/exceptions.py future-import-boilerplate!skip +plugins/connection/kubectl.py future-import-boilerplate!skip +plugins/inventory/k8s.py future-import-boilerplate!skip +plugins/lookup/k8s.py future-import-boilerplate!skip +plugins/lookup/kustomize.py future-import-boilerplate!skip +plugins/modules/k8s_scale.py future-import-boilerplate!skip +plugins/modules/helm_template.py future-import-boilerplate!skip +plugins/modules/k8s_exec.py future-import-boilerplate!skip +plugins/modules/helm.py future-import-boilerplate!skip +plugins/modules/helm_plugin_info.py future-import-boilerplate!skip +plugins/modules/helm_info.py future-import-boilerplate!skip +plugins/modules/helm_repository.py future-import-boilerplate!skip +plugins/modules/k8s_rollback.py future-import-boilerplate!skip +plugins/modules/k8s_log.py future-import-boilerplate!skip +plugins/modules/k8s_drain.py future-import-boilerplate!skip +plugins/modules/helm_plugin.py future-import-boilerplate!skip +plugins/modules/k8s_taint.py future-import-boilerplate!skip +plugins/modules/k8s.py future-import-boilerplate!skip +plugins/modules/k8s_service.py future-import-boilerplate!skip +plugins/modules/k8s_cluster_info.py future-import-boilerplate!skip +plugins/modules/k8s_info.py future-import-boilerplate!skip +plugins/modules/k8s_cp.py future-import-boilerplate!skip +plugins/modules/__init__.py future-import-boilerplate!skip +plugins/modules/k8s_json_patch.py future-import-boilerplate!skip +plugins/action/k8s_info.py future-import-boilerplate!skip +plugins/filter/k8s.py future-import-boilerplate!skip +tests/unit/conftest.py future-import-boilerplate!skip +tests/unit/utils/ansible_module_mock.py future-import-boilerplate!skip +tests/unit/module_utils/test_helm.py future-import-boilerplate!skip +tests/unit/module_utils/test_marshal.py future-import-boilerplate!skip +tests/unit/module_utils/test_discoverer.py future-import-boilerplate!skip +tests/unit/module_utils/test_hashes.py future-import-boilerplate!skip +tests/unit/module_utils/test_resource.py future-import-boilerplate!skip +tests/unit/module_utils/test_service.py future-import-boilerplate!skip +tests/unit/module_utils/test_waiter.py future-import-boilerplate!skip +tests/unit/module_utils/test_common.py future-import-boilerplate!skip +tests/unit/module_utils/test_selector.py future-import-boilerplate!skip +tests/unit/module_utils/test_apply.py future-import-boilerplate!skip +tests/unit/module_utils/test_runner.py future-import-boilerplate!skip +tests/unit/module_utils/test_client.py future-import-boilerplate!skip +tests/unit/module_utils/test_core.py future-import-boilerplate!skip +tests/unit/modules/test_helm_template_module.py future-import-boilerplate!skip +tests/unit/modules/test_helm_template.py future-import-boilerplate!skip +tests/unit/modules/test_module_helm.py future-import-boilerplate!skip +tests/unit/action/test_remove_omit.py future-import-boilerplate!skip +plugins/doc_fragments/k8s_name_options.py metaclass-boilerplate!skip +plugins/doc_fragments/k8s_auth_options.py metaclass-boilerplate!skip +plugins/doc_fragments/helm_common_options.py metaclass-boilerplate!skip +plugins/doc_fragments/k8s_state_options.py metaclass-boilerplate!skip +plugins/doc_fragments/k8s_wait_options.py metaclass-boilerplate!skip +plugins/doc_fragments/k8s_scale_options.py metaclass-boilerplate!skip +plugins/doc_fragments/k8s_delete_options.py metaclass-boilerplate!skip +plugins/doc_fragments/__init__.py metaclass-boilerplate!skip +plugins/doc_fragments/k8s_resource_options.py metaclass-boilerplate!skip +plugins/module_utils/helm.py metaclass-boilerplate!skip +plugins/module_utils/apply.py metaclass-boilerplate!skip +plugins/module_utils/hashes.py metaclass-boilerplate!skip +plugins/module_utils/version.py metaclass-boilerplate!skip +plugins/module_utils/_version.py metaclass-boilerplate!skip plugins/module_utils/copy.py metaclass-boilerplate!skip -plugins/module_utils/copy.py compile-2.6!skip -plugins/module_utils/copy.py compile-2.7!skip -plugins/module_utils/copy.py import-2.6!skip -plugins/module_utils/copy.py import-2.7!skip +plugins/module_utils/args_common.py metaclass-boilerplate!skip +plugins/module_utils/__init__.py metaclass-boilerplate!skip +plugins/module_utils/selector.py metaclass-boilerplate!skip +plugins/module_utils/k8sdynamicclient.py metaclass-boilerplate!skip +plugins/module_utils/common.py metaclass-boilerplate!skip +plugins/module_utils/ansiblemodule.py metaclass-boilerplate!skip +plugins/module_utils/exceptions.py metaclass-boilerplate!skip +plugins/module_utils/client/resource.py metaclass-boilerplate!skip +plugins/module_utils/client/discovery.py metaclass-boilerplate!skip +plugins/module_utils/k8s/resource.py metaclass-boilerplate!skip +plugins/module_utils/k8s/core.py metaclass-boilerplate!skip +plugins/module_utils/k8s/waiter.py metaclass-boilerplate!skip +plugins/module_utils/k8s/client.py metaclass-boilerplate!skip +plugins/module_utils/k8s/runner.py metaclass-boilerplate!skip +plugins/module_utils/k8s/service.py metaclass-boilerplate!skip +plugins/module_utils/k8s/exceptions.py metaclass-boilerplate!skip +plugins/connection/kubectl.py metaclass-boilerplate!skip +plugins/inventory/k8s.py metaclass-boilerplate!skip +plugins/lookup/k8s.py metaclass-boilerplate!skip +plugins/lookup/kustomize.py metaclass-boilerplate!skip +plugins/modules/k8s_scale.py metaclass-boilerplate!skip +plugins/modules/helm_template.py metaclass-boilerplate!skip +plugins/modules/k8s_exec.py metaclass-boilerplate!skip +plugins/modules/helm.py metaclass-boilerplate!skip +plugins/modules/helm_plugin_info.py metaclass-boilerplate!skip +plugins/modules/helm_info.py metaclass-boilerplate!skip +plugins/modules/helm_repository.py metaclass-boilerplate!skip +plugins/modules/k8s_rollback.py metaclass-boilerplate!skip +plugins/modules/k8s_log.py metaclass-boilerplate!skip +plugins/modules/k8s_drain.py metaclass-boilerplate!skip +plugins/modules/helm_plugin.py metaclass-boilerplate!skip +plugins/modules/k8s_taint.py metaclass-boilerplate!skip +plugins/modules/k8s.py metaclass-boilerplate!skip +plugins/modules/k8s_service.py metaclass-boilerplate!skip +plugins/modules/k8s_cluster_info.py metaclass-boilerplate!skip +plugins/modules/k8s_info.py metaclass-boilerplate!skip +plugins/modules/k8s_cp.py metaclass-boilerplate!skip +plugins/modules/__init__.py metaclass-boilerplate!skip +plugins/modules/k8s_json_patch.py metaclass-boilerplate!skip +plugins/action/k8s_info.py metaclass-boilerplate!skip +plugins/filter/k8s.py metaclass-boilerplate!skip +tests/unit/conftest.py metaclass-boilerplate!skip +tests/unit/utils/ansible_module_mock.py metaclass-boilerplate!skip +tests/unit/module_utils/test_helm.py metaclass-boilerplate!skip +tests/unit/module_utils/test_marshal.py metaclass-boilerplate!skip +tests/unit/module_utils/test_discoverer.py metaclass-boilerplate!skip +tests/unit/module_utils/test_hashes.py metaclass-boilerplate!skip +tests/unit/module_utils/test_resource.py metaclass-boilerplate!skip +tests/unit/module_utils/test_service.py metaclass-boilerplate!skip +tests/unit/module_utils/test_waiter.py metaclass-boilerplate!skip +tests/unit/module_utils/test_common.py metaclass-boilerplate!skip +tests/unit/module_utils/test_selector.py metaclass-boilerplate!skip +tests/unit/module_utils/test_apply.py metaclass-boilerplate!skip +tests/unit/module_utils/test_runner.py metaclass-boilerplate!skip +tests/unit/module_utils/test_client.py metaclass-boilerplate!skip +tests/unit/module_utils/test_core.py metaclass-boilerplate!skip +tests/unit/modules/test_helm_template_module.py metaclass-boilerplate!skip +tests/unit/modules/test_helm_template.py metaclass-boilerplate!skip +tests/unit/modules/test_module_helm.py metaclass-boilerplate!skip +tests/unit/action/test_remove_omit.py metaclass-boilerplate!skip diff --git a/tests/sanity/ignore-2.11.txt b/tests/sanity/ignore-2.11.txt index 433223cc..dd9afccb 100644 --- a/tests/sanity/ignore-2.11.txt +++ b/tests/sanity/ignore-2.11.txt @@ -1,262 +1,178 @@ -tests/integration/targets/k8s_scale/files/deployment.yaml yamllint!skip -tests/integration/targets/helm/files/appversionless-chart-v2/templates/configmap.yaml yamllint!skip -tests/integration/targets/helm/files/appversionless-chart/templates/configmap.yaml yamllint!skip -tests/integration/targets/helm/files/test-chart-v2/templates/configmap.yaml yamllint!skip -tests/integration/targets/helm/files/test-chart/templates/configmap.yaml yamllint!skip -plugins/module_utils/__init__.py compile-2.6!skip -plugins/module_utils/__init__.py compile-2.7!skip -plugins/module_utils/__init__.py compile-3.5!skip -plugins/module_utils/__init__.py future-import-boilerplate!skip -plugins/module_utils/__init__.py import-2.6!skip -plugins/module_utils/__init__.py import-2.7!skip -plugins/module_utils/__init__.py import-3.5!skip -plugins/module_utils/__init__.py metaclass-boilerplate!skip -plugins/module_utils/ansiblemodule.py compile-2.6!skip -plugins/module_utils/ansiblemodule.py compile-2.7!skip -plugins/module_utils/ansiblemodule.py compile-3.5!skip -plugins/module_utils/ansiblemodule.py future-import-boilerplate!skip -plugins/module_utils/ansiblemodule.py import-2.6!skip -plugins/module_utils/ansiblemodule.py import-2.7!skip -plugins/module_utils/ansiblemodule.py import-3.5!skip -plugins/module_utils/ansiblemodule.py metaclass-boilerplate!skip -plugins/module_utils/apply.py compile-2.6!skip -plugins/module_utils/apply.py compile-2.7!skip -plugins/module_utils/apply.py compile-3.5!skip -plugins/module_utils/apply.py future-import-boilerplate!skip -plugins/module_utils/apply.py import-2.6!skip -plugins/module_utils/apply.py import-2.7!skip -plugins/module_utils/apply.py import-3.5!skip -plugins/module_utils/apply.py metaclass-boilerplate!skip -plugins/module_utils/args_common.py compile-2.6!skip -plugins/module_utils/args_common.py compile-2.7!skip -plugins/module_utils/args_common.py compile-3.5!skip -plugins/module_utils/args_common.py future-import-boilerplate!skip -plugins/module_utils/args_common.py import-2.6!skip -plugins/module_utils/args_common.py import-2.7!skip -plugins/module_utils/args_common.py import-3.5!skip -plugins/module_utils/args_common.py metaclass-boilerplate!skip -plugins/module_utils/client/discovery.py future-import-boilerplate!skip -plugins/module_utils/client/discovery.py import-2.6!skip -plugins/module_utils/client/discovery.py import-2.7!skip -plugins/module_utils/client/discovery.py import-3.5!skip plugins/module_utils/client/discovery.py import-3.6!skip plugins/module_utils/client/discovery.py import-3.7!skip plugins/module_utils/client/discovery.py import-3.8!skip plugins/module_utils/client/discovery.py import-3.9!skip -plugins/module_utils/client/discovery.py metaclass-boilerplate!skip -plugins/module_utils/client/resource.py import-2.6!skip -plugins/module_utils/client/resource.py import-2.7!skip -plugins/module_utils/client/resource.py import-3.5!skip plugins/module_utils/client/resource.py import-3.6!skip plugins/module_utils/client/resource.py import-3.7!skip plugins/module_utils/client/resource.py import-3.8!skip plugins/module_utils/client/resource.py import-3.9!skip -plugins/module_utils/common.py compile-2.6!skip -plugins/module_utils/common.py compile-2.7!skip -plugins/module_utils/common.py compile-3.5!skip -plugins/module_utils/common.py future-import-boilerplate!skip -plugins/module_utils/common.py import-2.6!skip -plugins/module_utils/common.py import-2.7!skip -plugins/module_utils/common.py import-3.5!skip -plugins/module_utils/common.py metaclass-boilerplate!skip -plugins/module_utils/exceptions.py compile-2.6!skip -plugins/module_utils/exceptions.py compile-2.7!skip -plugins/module_utils/exceptions.py compile-3.5!skip -plugins/module_utils/exceptions.py future-import-boilerplate!skip -plugins/module_utils/exceptions.py import-2.6!skip -plugins/module_utils/exceptions.py import-2.7!skip -plugins/module_utils/exceptions.py import-3.5!skip -plugins/module_utils/exceptions.py metaclass-boilerplate!skip -plugins/module_utils/hashes.py compile-2.6!skip -plugins/module_utils/hashes.py compile-2.7!skip -plugins/module_utils/hashes.py compile-3.5!skip -plugins/module_utils/hashes.py future-import-boilerplate!skip -plugins/module_utils/hashes.py import-2.6!skip -plugins/module_utils/hashes.py import-2.7!skip -plugins/module_utils/hashes.py import-3.5!skip -plugins/module_utils/hashes.py metaclass-boilerplate!skip -plugins/module_utils/helm.py compile-2.6!skip -plugins/module_utils/helm.py compile-2.7!skip -plugins/module_utils/helm.py compile-3.5!skip -plugins/module_utils/helm.py future-import-boilerplate!skip -plugins/module_utils/helm.py import-2.6!skip -plugins/module_utils/helm.py import-2.7!skip -plugins/module_utils/helm.py import-3.5!skip -plugins/module_utils/helm.py metaclass-boilerplate!skip -plugins/module_utils/k8sdynamicclient.py compile-2.6!skip -plugins/module_utils/k8sdynamicclient.py compile-2.7!skip -plugins/module_utils/k8sdynamicclient.py compile-3.5!skip -plugins/module_utils/k8sdynamicclient.py future-import-boilerplate!skip -plugins/module_utils/k8sdynamicclient.py import-2.6!skip -plugins/module_utils/k8sdynamicclient.py import-2.7!skip -plugins/module_utils/k8sdynamicclient.py import-3.5!skip plugins/module_utils/k8sdynamicclient.py import-3.6!skip plugins/module_utils/k8sdynamicclient.py import-3.7!skip plugins/module_utils/k8sdynamicclient.py import-3.8!skip plugins/module_utils/k8sdynamicclient.py import-3.9!skip -plugins/module_utils/k8sdynamicclient.py metaclass-boilerplate!skip -plugins/modules/__init__.py compile-2.6!skip -plugins/modules/__init__.py compile-2.7!skip -plugins/modules/__init__.py compile-3.5!skip -plugins/modules/__init__.py future-import-boilerplate!skip -plugins/modules/__init__.py import-2.6!skip -plugins/modules/__init__.py import-2.7!skip -plugins/modules/__init__.py import-3.5!skip -plugins/modules/__init__.py metaclass-boilerplate!skip -plugins/modules/helm.py compile-2.6!skip -plugins/modules/helm.py compile-2.7!skip -plugins/modules/helm.py compile-3.5!skip -plugins/modules/helm.py future-import-boilerplate!skip -plugins/modules/helm.py import-2.6!skip -plugins/modules/helm.py import-2.7!skip -plugins/modules/helm.py import-3.5!skip -plugins/modules/helm.py metaclass-boilerplate!skip -plugins/modules/helm_info.py compile-2.6!skip -plugins/modules/helm_info.py compile-2.7!skip -plugins/modules/helm_info.py compile-3.5!skip -plugins/modules/helm_info.py future-import-boilerplate!skip -plugins/modules/helm_info.py import-2.6!skip -plugins/modules/helm_info.py import-2.7!skip -plugins/modules/helm_info.py import-3.5!skip -plugins/modules/helm_info.py metaclass-boilerplate!skip -plugins/modules/helm_plugin.py compile-2.6!skip -plugins/modules/helm_plugin.py compile-2.7!skip -plugins/modules/helm_plugin.py compile-3.5!skip -plugins/modules/helm_plugin.py future-import-boilerplate!skip -plugins/modules/helm_plugin.py import-2.6!skip -plugins/modules/helm_plugin.py import-2.7!skip -plugins/modules/helm_plugin.py import-3.5!skip -plugins/modules/helm_plugin.py metaclass-boilerplate!skip -plugins/modules/helm_plugin_info.py compile-2.6!skip -plugins/modules/helm_plugin_info.py compile-2.7!skip -plugins/modules/helm_plugin_info.py compile-3.5!skip -plugins/modules/helm_plugin_info.py future-import-boilerplate!skip -plugins/modules/helm_plugin_info.py import-2.6!skip -plugins/modules/helm_plugin_info.py import-2.7!skip -plugins/modules/helm_plugin_info.py import-3.5!skip -plugins/modules/helm_plugin_info.py metaclass-boilerplate!skip -plugins/modules/helm_repository.py compile-2.6!skip -plugins/modules/helm_repository.py compile-2.7!skip -plugins/modules/helm_repository.py compile-3.5!skip -plugins/modules/helm_repository.py future-import-boilerplate!skip -plugins/modules/helm_repository.py import-2.6!skip -plugins/modules/helm_repository.py import-2.7!skip -plugins/modules/helm_repository.py import-3.5!skip -plugins/modules/helm_repository.py metaclass-boilerplate!skip -plugins/modules/helm_template.py compile-2.6!skip -plugins/modules/helm_template.py compile-2.7!skip -plugins/modules/helm_template.py compile-3.5!skip -plugins/modules/helm_template.py future-import-boilerplate!skip -plugins/modules/helm_template.py import-2.6!skip -plugins/modules/helm_template.py import-2.7!skip -plugins/modules/helm_template.py import-3.5!skip -plugins/modules/helm_template.py metaclass-boilerplate!skip -plugins/modules/k8s.py compile-2.6!skip -plugins/modules/k8s.py compile-2.7!skip -plugins/modules/k8s.py compile-3.5!skip -plugins/modules/k8s.py future-import-boilerplate!skip -plugins/modules/k8s.py import-2.6!skip -plugins/modules/k8s.py import-2.7!skip -plugins/modules/k8s.py import-3.5!skip -plugins/modules/k8s.py metaclass-boilerplate!skip plugins/modules/k8s.py validate-modules:parameter-type-not-in-doc -plugins/modules/k8s.py validate-modules:return-syntax-error -plugins/modules/k8s_cluster_info.py compile-2.6!skip -plugins/modules/k8s_cluster_info.py compile-2.7!skip -plugins/modules/k8s_cluster_info.py compile-3.5!skip -plugins/modules/k8s_cluster_info.py future-import-boilerplate!skip -plugins/modules/k8s_cluster_info.py import-2.6!skip -plugins/modules/k8s_cluster_info.py import-2.7!skip -plugins/modules/k8s_cluster_info.py import-3.5!skip -plugins/modules/k8s_cluster_info.py metaclass-boilerplate!skip -plugins/modules/k8s_exec.py compile-2.6!skip -plugins/modules/k8s_exec.py compile-2.7!skip -plugins/modules/k8s_exec.py compile-3.5!skip -plugins/modules/k8s_exec.py future-import-boilerplate!skip -plugins/modules/k8s_exec.py import-2.6!skip -plugins/modules/k8s_exec.py import-2.7!skip -plugins/modules/k8s_exec.py import-3.5!skip -plugins/modules/k8s_exec.py metaclass-boilerplate!skip -plugins/modules/k8s_info.py compile-2.6!skip -plugins/modules/k8s_info.py compile-2.7!skip -plugins/modules/k8s_info.py compile-3.5!skip -plugins/modules/k8s_info.py future-import-boilerplate!skip -plugins/modules/k8s_info.py import-2.6!skip -plugins/modules/k8s_info.py import-2.7!skip -plugins/modules/k8s_info.py import-3.5!skip -plugins/modules/k8s_info.py metaclass-boilerplate!skip -plugins/modules/k8s_json_patch.py compile-2.6!skip -plugins/modules/k8s_json_patch.py compile-2.7!skip -plugins/modules/k8s_json_patch.py compile-3.5!skip -plugins/modules/k8s_json_patch.py future-import-boilerplate!skip -plugins/modules/k8s_json_patch.py import-2.6!skip -plugins/modules/k8s_json_patch.py import-2.7!skip -plugins/modules/k8s_json_patch.py import-3.5!skip -plugins/modules/k8s_json_patch.py metaclass-boilerplate!skip -plugins/modules/k8s_log.py compile-2.6!skip -plugins/modules/k8s_log.py compile-2.7!skip -plugins/modules/k8s_log.py compile-3.5!skip -plugins/modules/k8s_log.py future-import-boilerplate!skip -plugins/modules/k8s_log.py import-2.6!skip -plugins/modules/k8s_log.py import-2.7!skip -plugins/modules/k8s_log.py import-3.5!skip -plugins/modules/k8s_log.py metaclass-boilerplate!skip -plugins/modules/k8s_rollback.py compile-2.6!skip -plugins/modules/k8s_rollback.py compile-2.7!skip -plugins/modules/k8s_rollback.py compile-3.5!skip -plugins/modules/k8s_rollback.py future-import-boilerplate!skip -plugins/modules/k8s_rollback.py import-2.6!skip -plugins/modules/k8s_rollback.py import-2.7!skip -plugins/modules/k8s_rollback.py import-3.5!skip -plugins/modules/k8s_rollback.py metaclass-boilerplate!skip -plugins/modules/k8s_scale.py compile-2.6!skip -plugins/modules/k8s_scale.py compile-2.7!skip -plugins/modules/k8s_scale.py compile-3.5!skip -plugins/modules/k8s_scale.py future-import-boilerplate!skip -plugins/modules/k8s_scale.py import-2.6!skip -plugins/modules/k8s_scale.py import-2.7!skip -plugins/modules/k8s_scale.py import-3.5!skip -plugins/modules/k8s_scale.py metaclass-boilerplate!skip plugins/modules/k8s_scale.py validate-modules:parameter-type-not-in-doc -plugins/modules/k8s_scale.py validate-modules:return-syntax-error -plugins/modules/k8s_service.py compile-2.6!skip -plugins/modules/k8s_service.py compile-2.7!skip -plugins/modules/k8s_service.py compile-3.5!skip -plugins/modules/k8s_service.py future-import-boilerplate!skip -plugins/modules/k8s_service.py import-2.6!skip -plugins/modules/k8s_service.py import-2.7!skip -plugins/modules/k8s_service.py import-3.5!skip -plugins/modules/k8s_service.py metaclass-boilerplate!skip plugins/modules/k8s_service.py validate-modules:parameter-type-not-in-doc -plugins/modules/k8s_service.py validate-modules:return-syntax-error +tests/unit/module_utils/fixtures/definitions.yml yamllint!skip +tests/unit/module_utils/fixtures/deployments.yml yamllint!skip +tests/unit/module_utils/fixtures/pods.yml yamllint!skip +tests/integration/targets/helm/files/appversionless-chart-v2/templates/configmap.yaml yamllint!skip +tests/integration/targets/helm/files/appversionless-chart/templates/configmap.yaml yamllint!skip +tests/integration/targets/helm/files/test-chart-v2/templates/configmap.yaml yamllint!skip +tests/integration/targets/helm/files/test-chart/templates/configmap.yaml yamllint!skip +tests/integration/targets/k8s_scale/files/deployment.yaml yamllint!skip tests/sanity/refresh_ignore_files shebang!skip -tests/unit/module_utils/test_discoverer.py future-import-boilerplate!skip -tests/unit/module_utils/test_discoverer.py metaclass-boilerplate!skip -plugins/modules/k8s_cp.py compile-2.6!skip -plugins/modules/k8s_cp.py compile-2.7!skip -plugins/modules/k8s_cp.py import-2.6!skip -plugins/modules/k8s_cp.py import-2.7!skip -tests/integration/targets/k8s_copy/library/kubectl_file_compare.py compile-2.6!skip -tests/integration/targets/k8s_copy/library/kubectl_file_compare.py compile-2.7!skip -tests/integration/targets/k8s_copy/library/kubectl_file_compare.py compile-3.5!skip -tests/unit/module_utils/test_selector.py future-import-boilerplate!skip -tests/unit/module_utils/test_selector.py metaclass-boilerplate!skip -plugins/module_utils/selector.py future-import-boilerplate!skip -plugins/module_utils/selector.py metaclass-boilerplate!skip -plugins/lookup/kustomize.py future-import-boilerplate!skip -plugins/lookup/kustomize.py metaclass-boilerplate!skip -tests/integration/targets/helm/library/helm_test_version.py metaclass-boilerplate!skip -tests/integration/targets/helm/library/helm_test_version.py future-import-boilerplate!skip -plugins/modules/k8s_taint.py compile-2.7!skip -plugins/modules/k8s_taint.py compile-3.5!skip -plugins/modules/k8s_taint.py future-import-boilerplate!skip -plugins/modules/k8s_taint.py import-2.7!skip -plugins/modules/k8s_taint.py import-3.5!skip -plugins/modules/k8s_taint.py metaclass-boilerplate!skip +plugins/modules/k8s.py validate-modules:return-syntax-error +plugins/modules/k8s_scale.py validate-modules:return-syntax-error +plugins/modules/k8s_service.py validate-modules:return-syntax-error plugins/modules/k8s_taint.py validate-modules:return-syntax-error +plugins/doc_fragments/k8s_name_options.py future-import-boilerplate!skip +plugins/doc_fragments/k8s_auth_options.py future-import-boilerplate!skip +plugins/doc_fragments/helm_common_options.py future-import-boilerplate!skip +plugins/doc_fragments/k8s_state_options.py future-import-boilerplate!skip +plugins/doc_fragments/k8s_wait_options.py future-import-boilerplate!skip +plugins/doc_fragments/k8s_scale_options.py future-import-boilerplate!skip +plugins/doc_fragments/k8s_delete_options.py future-import-boilerplate!skip +plugins/doc_fragments/__init__.py future-import-boilerplate!skip +plugins/doc_fragments/k8s_resource_options.py future-import-boilerplate!skip +plugins/module_utils/helm.py future-import-boilerplate!skip +plugins/module_utils/apply.py future-import-boilerplate!skip +plugins/module_utils/hashes.py future-import-boilerplate!skip +plugins/module_utils/version.py future-import-boilerplate!skip +plugins/module_utils/_version.py future-import-boilerplate!skip plugins/module_utils/copy.py future-import-boilerplate!skip +plugins/module_utils/args_common.py future-import-boilerplate!skip +plugins/module_utils/__init__.py future-import-boilerplate!skip +plugins/module_utils/selector.py future-import-boilerplate!skip +plugins/module_utils/k8sdynamicclient.py future-import-boilerplate!skip +plugins/module_utils/common.py future-import-boilerplate!skip +plugins/module_utils/ansiblemodule.py future-import-boilerplate!skip +plugins/module_utils/exceptions.py future-import-boilerplate!skip +plugins/module_utils/client/resource.py future-import-boilerplate!skip +plugins/module_utils/client/discovery.py future-import-boilerplate!skip +plugins/module_utils/k8s/resource.py future-import-boilerplate!skip +plugins/module_utils/k8s/core.py future-import-boilerplate!skip +plugins/module_utils/k8s/waiter.py future-import-boilerplate!skip +plugins/module_utils/k8s/client.py future-import-boilerplate!skip +plugins/module_utils/k8s/runner.py future-import-boilerplate!skip +plugins/module_utils/k8s/service.py future-import-boilerplate!skip +plugins/module_utils/k8s/exceptions.py future-import-boilerplate!skip +plugins/connection/kubectl.py future-import-boilerplate!skip +plugins/inventory/k8s.py future-import-boilerplate!skip +plugins/lookup/k8s.py future-import-boilerplate!skip +plugins/lookup/kustomize.py future-import-boilerplate!skip +plugins/modules/k8s_scale.py future-import-boilerplate!skip +plugins/modules/helm_template.py future-import-boilerplate!skip +plugins/modules/k8s_exec.py future-import-boilerplate!skip +plugins/modules/helm.py future-import-boilerplate!skip +plugins/modules/helm_plugin_info.py future-import-boilerplate!skip +plugins/modules/helm_info.py future-import-boilerplate!skip +plugins/modules/helm_repository.py future-import-boilerplate!skip +plugins/modules/k8s_rollback.py future-import-boilerplate!skip +plugins/modules/k8s_log.py future-import-boilerplate!skip +plugins/modules/k8s_drain.py future-import-boilerplate!skip +plugins/modules/helm_plugin.py future-import-boilerplate!skip +plugins/modules/k8s_taint.py future-import-boilerplate!skip +plugins/modules/k8s.py future-import-boilerplate!skip +plugins/modules/k8s_service.py future-import-boilerplate!skip +plugins/modules/k8s_cluster_info.py future-import-boilerplate!skip +plugins/modules/k8s_info.py future-import-boilerplate!skip +plugins/modules/k8s_cp.py future-import-boilerplate!skip +plugins/modules/__init__.py future-import-boilerplate!skip +plugins/modules/k8s_json_patch.py future-import-boilerplate!skip +plugins/action/k8s_info.py future-import-boilerplate!skip +plugins/filter/k8s.py future-import-boilerplate!skip +tests/unit/conftest.py future-import-boilerplate!skip +tests/unit/utils/ansible_module_mock.py future-import-boilerplate!skip +tests/unit/module_utils/test_helm.py future-import-boilerplate!skip +tests/unit/module_utils/test_marshal.py future-import-boilerplate!skip +tests/unit/module_utils/test_discoverer.py future-import-boilerplate!skip +tests/unit/module_utils/test_hashes.py future-import-boilerplate!skip +tests/unit/module_utils/test_resource.py future-import-boilerplate!skip +tests/unit/module_utils/test_service.py future-import-boilerplate!skip +tests/unit/module_utils/test_waiter.py future-import-boilerplate!skip +tests/unit/module_utils/test_common.py future-import-boilerplate!skip +tests/unit/module_utils/test_selector.py future-import-boilerplate!skip +tests/unit/module_utils/test_apply.py future-import-boilerplate!skip +tests/unit/module_utils/test_runner.py future-import-boilerplate!skip +tests/unit/module_utils/test_client.py future-import-boilerplate!skip +tests/unit/module_utils/test_core.py future-import-boilerplate!skip +tests/unit/modules/test_helm_template_module.py future-import-boilerplate!skip +tests/unit/modules/test_helm_template.py future-import-boilerplate!skip +tests/unit/modules/test_module_helm.py future-import-boilerplate!skip +tests/unit/action/test_remove_omit.py future-import-boilerplate!skip +plugins/doc_fragments/k8s_name_options.py metaclass-boilerplate!skip +plugins/doc_fragments/k8s_auth_options.py metaclass-boilerplate!skip +plugins/doc_fragments/helm_common_options.py metaclass-boilerplate!skip +plugins/doc_fragments/k8s_state_options.py metaclass-boilerplate!skip +plugins/doc_fragments/k8s_wait_options.py metaclass-boilerplate!skip +plugins/doc_fragments/k8s_scale_options.py metaclass-boilerplate!skip +plugins/doc_fragments/k8s_delete_options.py metaclass-boilerplate!skip +plugins/doc_fragments/__init__.py metaclass-boilerplate!skip +plugins/doc_fragments/k8s_resource_options.py metaclass-boilerplate!skip +plugins/module_utils/helm.py metaclass-boilerplate!skip +plugins/module_utils/apply.py metaclass-boilerplate!skip +plugins/module_utils/hashes.py metaclass-boilerplate!skip +plugins/module_utils/version.py metaclass-boilerplate!skip +plugins/module_utils/_version.py metaclass-boilerplate!skip plugins/module_utils/copy.py metaclass-boilerplate!skip -plugins/module_utils/copy.py compile-2.6!skip -plugins/module_utils/copy.py compile-2.7!skip -plugins/module_utils/copy.py import-2.6!skip -plugins/module_utils/copy.py import-2.7!skip +plugins/module_utils/args_common.py metaclass-boilerplate!skip +plugins/module_utils/__init__.py metaclass-boilerplate!skip +plugins/module_utils/selector.py metaclass-boilerplate!skip +plugins/module_utils/k8sdynamicclient.py metaclass-boilerplate!skip +plugins/module_utils/common.py metaclass-boilerplate!skip +plugins/module_utils/ansiblemodule.py metaclass-boilerplate!skip +plugins/module_utils/exceptions.py metaclass-boilerplate!skip +plugins/module_utils/client/resource.py metaclass-boilerplate!skip +plugins/module_utils/client/discovery.py metaclass-boilerplate!skip +plugins/module_utils/k8s/resource.py metaclass-boilerplate!skip +plugins/module_utils/k8s/core.py metaclass-boilerplate!skip +plugins/module_utils/k8s/waiter.py metaclass-boilerplate!skip +plugins/module_utils/k8s/client.py metaclass-boilerplate!skip +plugins/module_utils/k8s/runner.py metaclass-boilerplate!skip +plugins/module_utils/k8s/service.py metaclass-boilerplate!skip +plugins/module_utils/k8s/exceptions.py metaclass-boilerplate!skip +plugins/connection/kubectl.py metaclass-boilerplate!skip +plugins/inventory/k8s.py metaclass-boilerplate!skip +plugins/lookup/k8s.py metaclass-boilerplate!skip +plugins/lookup/kustomize.py metaclass-boilerplate!skip +plugins/modules/k8s_scale.py metaclass-boilerplate!skip +plugins/modules/helm_template.py metaclass-boilerplate!skip +plugins/modules/k8s_exec.py metaclass-boilerplate!skip +plugins/modules/helm.py metaclass-boilerplate!skip +plugins/modules/helm_plugin_info.py metaclass-boilerplate!skip +plugins/modules/helm_info.py metaclass-boilerplate!skip +plugins/modules/helm_repository.py metaclass-boilerplate!skip +plugins/modules/k8s_rollback.py metaclass-boilerplate!skip +plugins/modules/k8s_log.py metaclass-boilerplate!skip +plugins/modules/k8s_drain.py metaclass-boilerplate!skip +plugins/modules/helm_plugin.py metaclass-boilerplate!skip +plugins/modules/k8s_taint.py metaclass-boilerplate!skip +plugins/modules/k8s.py metaclass-boilerplate!skip +plugins/modules/k8s_service.py metaclass-boilerplate!skip +plugins/modules/k8s_cluster_info.py metaclass-boilerplate!skip +plugins/modules/k8s_info.py metaclass-boilerplate!skip +plugins/modules/k8s_cp.py metaclass-boilerplate!skip +plugins/modules/__init__.py metaclass-boilerplate!skip +plugins/modules/k8s_json_patch.py metaclass-boilerplate!skip +plugins/action/k8s_info.py metaclass-boilerplate!skip +plugins/filter/k8s.py metaclass-boilerplate!skip +tests/unit/conftest.py metaclass-boilerplate!skip +tests/unit/utils/ansible_module_mock.py metaclass-boilerplate!skip +tests/unit/module_utils/test_helm.py metaclass-boilerplate!skip +tests/unit/module_utils/test_marshal.py metaclass-boilerplate!skip +tests/unit/module_utils/test_discoverer.py metaclass-boilerplate!skip +tests/unit/module_utils/test_hashes.py metaclass-boilerplate!skip +tests/unit/module_utils/test_resource.py metaclass-boilerplate!skip +tests/unit/module_utils/test_service.py metaclass-boilerplate!skip +tests/unit/module_utils/test_waiter.py metaclass-boilerplate!skip +tests/unit/module_utils/test_common.py metaclass-boilerplate!skip +tests/unit/module_utils/test_selector.py metaclass-boilerplate!skip +tests/unit/module_utils/test_apply.py metaclass-boilerplate!skip +tests/unit/module_utils/test_runner.py metaclass-boilerplate!skip +tests/unit/module_utils/test_client.py metaclass-boilerplate!skip +tests/unit/module_utils/test_core.py metaclass-boilerplate!skip +tests/unit/modules/test_helm_template_module.py metaclass-boilerplate!skip +tests/unit/modules/test_helm_template.py metaclass-boilerplate!skip +tests/unit/modules/test_module_helm.py metaclass-boilerplate!skip +tests/unit/action/test_remove_omit.py metaclass-boilerplate!skip diff --git a/tests/sanity/ignore-2.12.txt b/tests/sanity/ignore-2.12.txt index 73fa1b70..8572453e 100644 --- a/tests/sanity/ignore-2.12.txt +++ b/tests/sanity/ignore-2.12.txt @@ -1,248 +1,31 @@ -tests/integration/targets/k8s_scale/files/deployment.yaml yamllint!skip -tests/integration/targets/helm/files/appversionless-chart-v2/templates/configmap.yaml yamllint!skip -tests/integration/targets/helm/files/appversionless-chart/templates/configmap.yaml yamllint!skip -tests/integration/targets/helm/files/test-chart-v2/templates/configmap.yaml yamllint!skip -tests/integration/targets/helm/files/test-chart/templates/configmap.yaml yamllint!skip -plugins/module_utils/__init__.py compile-2.6!skip -plugins/module_utils/__init__.py compile-2.7!skip -plugins/module_utils/__init__.py compile-3.5!skip -plugins/module_utils/__init__.py future-import-boilerplate!skip -plugins/module_utils/__init__.py import-2.6!skip -plugins/module_utils/__init__.py import-2.7!skip -plugins/module_utils/__init__.py import-3.5!skip -plugins/module_utils/__init__.py metaclass-boilerplate!skip -plugins/module_utils/ansiblemodule.py compile-2.6!skip -plugins/module_utils/ansiblemodule.py compile-2.7!skip -plugins/module_utils/ansiblemodule.py compile-3.5!skip -plugins/module_utils/ansiblemodule.py future-import-boilerplate!skip -plugins/module_utils/ansiblemodule.py import-2.6!skip -plugins/module_utils/ansiblemodule.py import-2.7!skip -plugins/module_utils/ansiblemodule.py import-3.5!skip -plugins/module_utils/ansiblemodule.py metaclass-boilerplate!skip -plugins/module_utils/apply.py compile-2.6!skip -plugins/module_utils/apply.py compile-2.7!skip -plugins/module_utils/apply.py compile-3.5!skip -plugins/module_utils/apply.py future-import-boilerplate!skip -plugins/module_utils/apply.py import-2.6!skip -plugins/module_utils/apply.py import-2.7!skip -plugins/module_utils/apply.py import-3.5!skip -plugins/module_utils/apply.py metaclass-boilerplate!skip -plugins/module_utils/args_common.py compile-2.6!skip -plugins/module_utils/args_common.py compile-2.7!skip -plugins/module_utils/args_common.py compile-3.5!skip -plugins/module_utils/args_common.py future-import-boilerplate!skip -plugins/module_utils/args_common.py import-2.6!skip -plugins/module_utils/args_common.py import-2.7!skip -plugins/module_utils/args_common.py import-3.5!skip -plugins/module_utils/args_common.py metaclass-boilerplate!skip -plugins/module_utils/client/discovery.py future-import-boilerplate!skip -plugins/module_utils/client/discovery.py import-2.6!skip -plugins/module_utils/client/discovery.py import-2.7!skip -plugins/module_utils/client/discovery.py import-3.5!skip plugins/module_utils/client/discovery.py import-3.6!skip plugins/module_utils/client/discovery.py import-3.7!skip plugins/module_utils/client/discovery.py import-3.8!skip plugins/module_utils/client/discovery.py import-3.9!skip plugins/module_utils/client/discovery.py import-3.10!skip -plugins/module_utils/client/discovery.py metaclass-boilerplate!skip -plugins/module_utils/client/resource.py import-2.6!skip -plugins/module_utils/client/resource.py import-2.7!skip -plugins/module_utils/client/resource.py import-3.5!skip plugins/module_utils/client/resource.py import-3.6!skip plugins/module_utils/client/resource.py import-3.7!skip plugins/module_utils/client/resource.py import-3.8!skip plugins/module_utils/client/resource.py import-3.9!skip plugins/module_utils/client/resource.py import-3.10!skip -plugins/module_utils/common.py compile-2.6!skip -plugins/module_utils/common.py compile-2.7!skip -plugins/module_utils/common.py compile-3.5!skip -plugins/module_utils/common.py future-import-boilerplate!skip -plugins/module_utils/common.py import-2.6!skip -plugins/module_utils/common.py import-2.7!skip -plugins/module_utils/common.py import-3.5!skip -plugins/module_utils/common.py metaclass-boilerplate!skip -plugins/module_utils/exceptions.py compile-2.6!skip -plugins/module_utils/exceptions.py compile-2.7!skip -plugins/module_utils/exceptions.py compile-3.5!skip -plugins/module_utils/exceptions.py future-import-boilerplate!skip -plugins/module_utils/exceptions.py import-2.6!skip -plugins/module_utils/exceptions.py import-2.7!skip -plugins/module_utils/exceptions.py import-3.5!skip -plugins/module_utils/exceptions.py metaclass-boilerplate!skip -plugins/module_utils/hashes.py compile-2.6!skip -plugins/module_utils/hashes.py compile-2.7!skip -plugins/module_utils/hashes.py compile-3.5!skip -plugins/module_utils/hashes.py future-import-boilerplate!skip -plugins/module_utils/hashes.py import-2.6!skip -plugins/module_utils/hashes.py import-2.7!skip -plugins/module_utils/hashes.py import-3.5!skip -plugins/module_utils/hashes.py metaclass-boilerplate!skip -plugins/module_utils/helm.py compile-2.6!skip -plugins/module_utils/helm.py compile-2.7!skip -plugins/module_utils/helm.py compile-3.5!skip -plugins/module_utils/helm.py future-import-boilerplate!skip -plugins/module_utils/helm.py import-2.6!skip -plugins/module_utils/helm.py import-2.7!skip -plugins/module_utils/helm.py import-3.5!skip -plugins/module_utils/helm.py metaclass-boilerplate!skip -plugins/module_utils/k8sdynamicclient.py compile-2.6!skip -plugins/module_utils/k8sdynamicclient.py compile-2.7!skip -plugins/module_utils/k8sdynamicclient.py compile-3.5!skip -plugins/module_utils/k8sdynamicclient.py future-import-boilerplate!skip -plugins/module_utils/k8sdynamicclient.py import-2.6!skip -plugins/module_utils/k8sdynamicclient.py import-2.7!skip -plugins/module_utils/k8sdynamicclient.py import-3.5!skip plugins/module_utils/k8sdynamicclient.py import-3.6!skip plugins/module_utils/k8sdynamicclient.py import-3.7!skip plugins/module_utils/k8sdynamicclient.py import-3.8!skip plugins/module_utils/k8sdynamicclient.py import-3.9!skip plugins/module_utils/k8sdynamicclient.py import-3.10!skip -plugins/module_utils/k8sdynamicclient.py metaclass-boilerplate!skip -plugins/modules/__init__.py compile-2.6!skip -plugins/modules/__init__.py compile-2.7!skip -plugins/modules/__init__.py compile-3.5!skip -plugins/modules/__init__.py future-import-boilerplate!skip -plugins/modules/__init__.py import-2.6!skip -plugins/modules/__init__.py import-2.7!skip -plugins/modules/__init__.py import-3.5!skip -plugins/modules/__init__.py metaclass-boilerplate!skip -plugins/modules/helm.py compile-2.6!skip -plugins/modules/helm.py compile-2.7!skip -plugins/modules/helm.py compile-3.5!skip -plugins/modules/helm.py future-import-boilerplate!skip -plugins/modules/helm.py import-2.6!skip -plugins/modules/helm.py import-2.7!skip -plugins/modules/helm.py import-3.5!skip -plugins/modules/helm.py metaclass-boilerplate!skip -plugins/modules/helm_info.py compile-2.6!skip -plugins/modules/helm_info.py compile-2.7!skip -plugins/modules/helm_info.py compile-3.5!skip -plugins/modules/helm_info.py future-import-boilerplate!skip -plugins/modules/helm_info.py import-2.6!skip -plugins/modules/helm_info.py import-2.7!skip -plugins/modules/helm_info.py import-3.5!skip -plugins/modules/helm_info.py metaclass-boilerplate!skip -plugins/modules/helm_plugin.py compile-2.6!skip -plugins/modules/helm_plugin.py compile-2.7!skip -plugins/modules/helm_plugin.py compile-3.5!skip -plugins/modules/helm_plugin.py future-import-boilerplate!skip -plugins/modules/helm_plugin.py import-2.6!skip -plugins/modules/helm_plugin.py import-2.7!skip -plugins/modules/helm_plugin.py import-3.5!skip -plugins/modules/helm_plugin.py metaclass-boilerplate!skip -plugins/modules/helm_plugin_info.py compile-2.6!skip -plugins/modules/helm_plugin_info.py compile-2.7!skip -plugins/modules/helm_plugin_info.py compile-3.5!skip -plugins/modules/helm_plugin_info.py future-import-boilerplate!skip -plugins/modules/helm_plugin_info.py import-2.6!skip -plugins/modules/helm_plugin_info.py import-2.7!skip -plugins/modules/helm_plugin_info.py import-3.5!skip -plugins/modules/helm_plugin_info.py metaclass-boilerplate!skip -plugins/modules/helm_repository.py compile-2.6!skip -plugins/modules/helm_repository.py compile-2.7!skip -plugins/modules/helm_repository.py compile-3.5!skip -plugins/modules/helm_repository.py future-import-boilerplate!skip -plugins/modules/helm_repository.py import-2.6!skip -plugins/modules/helm_repository.py import-2.7!skip -plugins/modules/helm_repository.py import-3.5!skip -plugins/modules/helm_repository.py metaclass-boilerplate!skip -plugins/modules/helm_template.py compile-2.6!skip -plugins/modules/helm_template.py compile-2.7!skip -plugins/modules/helm_template.py compile-3.5!skip -plugins/modules/helm_template.py future-import-boilerplate!skip -plugins/modules/helm_template.py import-2.6!skip -plugins/modules/helm_template.py import-2.7!skip -plugins/modules/helm_template.py import-3.5!skip -plugins/modules/helm_template.py metaclass-boilerplate!skip -plugins/modules/k8s.py compile-2.6!skip -plugins/modules/k8s.py compile-2.7!skip -plugins/modules/k8s.py compile-3.5!skip -plugins/modules/k8s.py future-import-boilerplate!skip -plugins/modules/k8s.py import-2.6!skip -plugins/modules/k8s.py import-2.7!skip -plugins/modules/k8s.py import-3.5!skip -plugins/modules/k8s.py metaclass-boilerplate!skip plugins/modules/k8s.py validate-modules:parameter-type-not-in-doc -plugins/modules/k8s.py validate-modules:return-syntax-error -plugins/modules/k8s_cluster_info.py compile-2.6!skip -plugins/modules/k8s_cluster_info.py compile-2.7!skip -plugins/modules/k8s_cluster_info.py compile-3.5!skip -plugins/modules/k8s_cluster_info.py future-import-boilerplate!skip -plugins/modules/k8s_cluster_info.py import-2.6!skip -plugins/modules/k8s_cluster_info.py import-2.7!skip -plugins/modules/k8s_cluster_info.py import-3.5!skip -plugins/modules/k8s_cluster_info.py metaclass-boilerplate!skip -plugins/modules/k8s_exec.py compile-2.6!skip -plugins/modules/k8s_exec.py compile-2.7!skip -plugins/modules/k8s_exec.py compile-3.5!skip -plugins/modules/k8s_exec.py future-import-boilerplate!skip -plugins/modules/k8s_exec.py import-2.6!skip -plugins/modules/k8s_exec.py import-2.7!skip -plugins/modules/k8s_exec.py import-3.5!skip -plugins/modules/k8s_exec.py metaclass-boilerplate!skip -plugins/modules/k8s_info.py compile-2.6!skip -plugins/modules/k8s_info.py compile-2.7!skip -plugins/modules/k8s_info.py compile-3.5!skip -plugins/modules/k8s_info.py future-import-boilerplate!skip -plugins/modules/k8s_info.py import-2.6!skip -plugins/modules/k8s_info.py import-2.7!skip -plugins/modules/k8s_info.py import-3.5!skip -plugins/modules/k8s_info.py metaclass-boilerplate!skip -plugins/modules/k8s_json_patch.py compile-2.6!skip -plugins/modules/k8s_json_patch.py compile-2.7!skip -plugins/modules/k8s_json_patch.py compile-3.5!skip -plugins/modules/k8s_json_patch.py future-import-boilerplate!skip -plugins/modules/k8s_json_patch.py import-2.6!skip -plugins/modules/k8s_json_patch.py import-2.7!skip -plugins/modules/k8s_json_patch.py import-3.5!skip -plugins/modules/k8s_json_patch.py metaclass-boilerplate!skip -plugins/modules/k8s_log.py compile-2.6!skip -plugins/modules/k8s_log.py compile-2.7!skip -plugins/modules/k8s_log.py compile-3.5!skip -plugins/modules/k8s_log.py future-import-boilerplate!skip -plugins/modules/k8s_log.py import-2.6!skip -plugins/modules/k8s_log.py import-2.7!skip -plugins/modules/k8s_log.py import-3.5!skip -plugins/modules/k8s_log.py metaclass-boilerplate!skip -plugins/modules/k8s_rollback.py compile-2.6!skip -plugins/modules/k8s_rollback.py compile-2.7!skip -plugins/modules/k8s_rollback.py compile-3.5!skip -plugins/modules/k8s_rollback.py future-import-boilerplate!skip -plugins/modules/k8s_rollback.py import-2.6!skip -plugins/modules/k8s_rollback.py import-2.7!skip -plugins/modules/k8s_rollback.py import-3.5!skip -plugins/modules/k8s_rollback.py metaclass-boilerplate!skip -plugins/modules/k8s_scale.py compile-2.6!skip -plugins/modules/k8s_scale.py compile-2.7!skip -plugins/modules/k8s_scale.py compile-3.5!skip -plugins/modules/k8s_scale.py future-import-boilerplate!skip -plugins/modules/k8s_scale.py import-2.6!skip -plugins/modules/k8s_scale.py import-2.7!skip -plugins/modules/k8s_scale.py import-3.5!skip -plugins/modules/k8s_scale.py metaclass-boilerplate!skip plugins/modules/k8s_scale.py validate-modules:parameter-type-not-in-doc -plugins/modules/k8s_scale.py validate-modules:return-syntax-error -plugins/modules/k8s_service.py compile-2.6!skip -plugins/modules/k8s_service.py compile-2.7!skip -plugins/modules/k8s_service.py compile-3.5!skip -plugins/modules/k8s_service.py future-import-boilerplate!skip -plugins/modules/k8s_service.py import-2.6!skip -plugins/modules/k8s_service.py import-2.7!skip -plugins/modules/k8s_service.py import-3.5!skip -plugins/modules/k8s_service.py metaclass-boilerplate!skip plugins/modules/k8s_service.py validate-modules:parameter-type-not-in-doc -plugins/modules/k8s_service.py validate-modules:return-syntax-error +tests/unit/module_utils/fixtures/definitions.yml yamllint!skip +tests/unit/module_utils/fixtures/deployments.yml yamllint!skip +tests/unit/module_utils/fixtures/pods.yml yamllint!skip +tests/integration/targets/helm/files/appversionless-chart-v2/templates/configmap.yaml yamllint!skip +tests/integration/targets/helm/files/appversionless-chart/templates/configmap.yaml yamllint!skip +tests/integration/targets/helm/files/test-chart-v2/templates/configmap.yaml yamllint!skip +tests/integration/targets/helm/files/test-chart/templates/configmap.yaml yamllint!skip +tests/integration/targets/k8s_scale/files/deployment.yaml yamllint!skip tests/sanity/refresh_ignore_files shebang!skip -plugins/modules/k8s_cp.py compile-2.6!skip -plugins/modules/k8s_cp.py compile-2.7!skip -plugins/modules/k8s_cp.py import-2.6!skip -plugins/modules/k8s_cp.py import-2.7!skip -plugins/module_utils/selector.py future-import-boilerplate!skip -plugins/module_utils/selector.py metaclass-boilerplate!skip -plugins/modules/k8s_taint.py compile-2.7!skip -plugins/modules/k8s_taint.py compile-3.5!skip -plugins/modules/k8s_taint.py future-import-boilerplate!skip -plugins/modules/k8s_taint.py import-2.7!skip -plugins/modules/k8s_taint.py import-3.5!skip -plugins/modules/k8s_taint.py metaclass-boilerplate!skip -plugins/modules/k8s_taint.py validate-modules:return-syntax-error \ No newline at end of file +plugins/modules/k8s.py validate-modules:return-syntax-error +plugins/modules/k8s_scale.py validate-modules:return-syntax-error +plugins/modules/k8s_service.py validate-modules:return-syntax-error +plugins/modules/k8s_taint.py validate-modules:return-syntax-error diff --git a/tests/sanity/ignore-2.13.txt b/tests/sanity/ignore-2.13.txt index cde97150..8572453e 100644 --- a/tests/sanity/ignore-2.13.txt +++ b/tests/sanity/ignore-2.13.txt @@ -1,196 +1,31 @@ -tests/integration/targets/k8s_scale/files/deployment.yaml yamllint!skip -tests/integration/targets/helm/files/appversionless-chart-v2/templates/configmap.yaml yamllint!skip -tests/integration/targets/helm/files/appversionless-chart/templates/configmap.yaml yamllint!skip -tests/integration/targets/helm/files/test-chart-v2/templates/configmap.yaml yamllint!skip -tests/integration/targets/helm/files/test-chart/templates/configmap.yaml yamllint!skip -plugins/module_utils/__init__.py compile-2.7!skip -plugins/module_utils/__init__.py compile-3.5!skip -plugins/module_utils/__init__.py future-import-boilerplate!skip -plugins/module_utils/__init__.py import-2.7!skip -plugins/module_utils/__init__.py import-3.5!skip -plugins/module_utils/__init__.py metaclass-boilerplate!skip -plugins/module_utils/ansiblemodule.py compile-2.7!skip -plugins/module_utils/ansiblemodule.py compile-3.5!skip -plugins/module_utils/ansiblemodule.py future-import-boilerplate!skip -plugins/module_utils/ansiblemodule.py import-2.7!skip -plugins/module_utils/ansiblemodule.py import-3.5!skip -plugins/module_utils/ansiblemodule.py metaclass-boilerplate!skip -plugins/module_utils/apply.py compile-2.7!skip -plugins/module_utils/apply.py compile-3.5!skip -plugins/module_utils/apply.py future-import-boilerplate!skip -plugins/module_utils/apply.py import-2.7!skip -plugins/module_utils/apply.py import-3.5!skip -plugins/module_utils/apply.py metaclass-boilerplate!skip -plugins/module_utils/args_common.py compile-2.7!skip -plugins/module_utils/args_common.py compile-3.5!skip -plugins/module_utils/args_common.py future-import-boilerplate!skip -plugins/module_utils/args_common.py import-2.7!skip -plugins/module_utils/args_common.py import-3.5!skip -plugins/module_utils/args_common.py metaclass-boilerplate!skip -plugins/module_utils/client/discovery.py future-import-boilerplate!skip -plugins/module_utils/client/discovery.py import-2.7!skip -plugins/module_utils/client/discovery.py import-3.5!skip plugins/module_utils/client/discovery.py import-3.6!skip plugins/module_utils/client/discovery.py import-3.7!skip plugins/module_utils/client/discovery.py import-3.8!skip plugins/module_utils/client/discovery.py import-3.9!skip plugins/module_utils/client/discovery.py import-3.10!skip -plugins/module_utils/client/discovery.py metaclass-boilerplate!skip -plugins/module_utils/client/resource.py import-2.7!skip -plugins/module_utils/client/resource.py import-3.5!skip plugins/module_utils/client/resource.py import-3.6!skip plugins/module_utils/client/resource.py import-3.7!skip plugins/module_utils/client/resource.py import-3.8!skip plugins/module_utils/client/resource.py import-3.9!skip plugins/module_utils/client/resource.py import-3.10!skip -plugins/module_utils/common.py compile-2.7!skip -plugins/module_utils/common.py compile-3.5!skip -plugins/module_utils/common.py future-import-boilerplate!skip -plugins/module_utils/common.py import-2.7!skip -plugins/module_utils/common.py import-3.5!skip -plugins/module_utils/common.py metaclass-boilerplate!skip -plugins/module_utils/exceptions.py compile-2.7!skip -plugins/module_utils/exceptions.py compile-3.5!skip -plugins/module_utils/exceptions.py future-import-boilerplate!skip -plugins/module_utils/exceptions.py import-2.7!skip -plugins/module_utils/exceptions.py import-3.5!skip -plugins/module_utils/exceptions.py metaclass-boilerplate!skip -plugins/module_utils/hashes.py compile-2.7!skip -plugins/module_utils/hashes.py compile-3.5!skip -plugins/module_utils/hashes.py future-import-boilerplate!skip -plugins/module_utils/hashes.py import-2.7!skip -plugins/module_utils/hashes.py import-3.5!skip -plugins/module_utils/hashes.py metaclass-boilerplate!skip -plugins/module_utils/helm.py compile-2.7!skip -plugins/module_utils/helm.py compile-3.5!skip -plugins/module_utils/helm.py future-import-boilerplate!skip -plugins/module_utils/helm.py import-2.7!skip -plugins/module_utils/helm.py import-3.5!skip -plugins/module_utils/helm.py metaclass-boilerplate!skip -plugins/module_utils/k8sdynamicclient.py compile-2.7!skip -plugins/module_utils/k8sdynamicclient.py compile-3.5!skip -plugins/module_utils/k8sdynamicclient.py future-import-boilerplate!skip -plugins/module_utils/k8sdynamicclient.py import-2.7!skip -plugins/module_utils/k8sdynamicclient.py import-3.5!skip plugins/module_utils/k8sdynamicclient.py import-3.6!skip plugins/module_utils/k8sdynamicclient.py import-3.7!skip plugins/module_utils/k8sdynamicclient.py import-3.8!skip plugins/module_utils/k8sdynamicclient.py import-3.9!skip plugins/module_utils/k8sdynamicclient.py import-3.10!skip -plugins/module_utils/k8sdynamicclient.py metaclass-boilerplate!skip -plugins/modules/__init__.py compile-2.7!skip -plugins/modules/__init__.py compile-3.5!skip -plugins/modules/__init__.py future-import-boilerplate!skip -plugins/modules/__init__.py import-2.7!skip -plugins/modules/__init__.py import-3.5!skip -plugins/modules/__init__.py metaclass-boilerplate!skip -plugins/modules/helm.py compile-2.7!skip -plugins/modules/helm.py compile-3.5!skip -plugins/modules/helm.py future-import-boilerplate!skip -plugins/modules/helm.py import-2.7!skip -plugins/modules/helm.py import-3.5!skip -plugins/modules/helm.py metaclass-boilerplate!skip -plugins/modules/helm_info.py compile-2.7!skip -plugins/modules/helm_info.py compile-3.5!skip -plugins/modules/helm_info.py future-import-boilerplate!skip -plugins/modules/helm_info.py import-2.7!skip -plugins/modules/helm_info.py import-3.5!skip -plugins/modules/helm_info.py metaclass-boilerplate!skip -plugins/modules/helm_plugin.py compile-2.7!skip -plugins/modules/helm_plugin.py compile-3.5!skip -plugins/modules/helm_plugin.py future-import-boilerplate!skip -plugins/modules/helm_plugin.py import-2.7!skip -plugins/modules/helm_plugin.py import-3.5!skip -plugins/modules/helm_plugin.py metaclass-boilerplate!skip -plugins/modules/helm_plugin_info.py compile-2.7!skip -plugins/modules/helm_plugin_info.py compile-3.5!skip -plugins/modules/helm_plugin_info.py future-import-boilerplate!skip -plugins/modules/helm_plugin_info.py import-2.7!skip -plugins/modules/helm_plugin_info.py import-3.5!skip -plugins/modules/helm_plugin_info.py metaclass-boilerplate!skip -plugins/modules/helm_repository.py compile-2.7!skip -plugins/modules/helm_repository.py compile-3.5!skip -plugins/modules/helm_repository.py future-import-boilerplate!skip -plugins/modules/helm_repository.py import-2.7!skip -plugins/modules/helm_repository.py import-3.5!skip -plugins/modules/helm_repository.py metaclass-boilerplate!skip -plugins/modules/helm_template.py compile-2.7!skip -plugins/modules/helm_template.py compile-3.5!skip -plugins/modules/helm_template.py future-import-boilerplate!skip -plugins/modules/helm_template.py import-2.7!skip -plugins/modules/helm_template.py import-3.5!skip -plugins/modules/helm_template.py metaclass-boilerplate!skip -plugins/modules/k8s.py compile-2.7!skip -plugins/modules/k8s.py compile-3.5!skip -plugins/modules/k8s.py future-import-boilerplate!skip -plugins/modules/k8s.py import-2.7!skip -plugins/modules/k8s.py import-3.5!skip -plugins/modules/k8s.py metaclass-boilerplate!skip plugins/modules/k8s.py validate-modules:parameter-type-not-in-doc -plugins/modules/k8s.py validate-modules:return-syntax-error -plugins/modules/k8s_cluster_info.py compile-2.7!skip -plugins/modules/k8s_cluster_info.py compile-3.5!skip -plugins/modules/k8s_cluster_info.py future-import-boilerplate!skip -plugins/modules/k8s_cluster_info.py import-2.7!skip -plugins/modules/k8s_cluster_info.py import-3.5!skip -plugins/modules/k8s_cluster_info.py metaclass-boilerplate!skip -plugins/modules/k8s_exec.py compile-2.7!skip -plugins/modules/k8s_exec.py compile-3.5!skip -plugins/modules/k8s_exec.py future-import-boilerplate!skip -plugins/modules/k8s_exec.py import-2.7!skip -plugins/modules/k8s_exec.py import-3.5!skip -plugins/modules/k8s_exec.py metaclass-boilerplate!skip -plugins/modules/k8s_info.py compile-2.7!skip -plugins/modules/k8s_info.py compile-3.5!skip -plugins/modules/k8s_info.py future-import-boilerplate!skip -plugins/modules/k8s_info.py import-2.7!skip -plugins/modules/k8s_info.py import-3.5!skip -plugins/modules/k8s_info.py metaclass-boilerplate!skip -plugins/modules/k8s_json_patch.py compile-2.7!skip -plugins/modules/k8s_json_patch.py compile-3.5!skip -plugins/modules/k8s_json_patch.py future-import-boilerplate!skip -plugins/modules/k8s_json_patch.py import-2.7!skip -plugins/modules/k8s_json_patch.py import-3.5!skip -plugins/modules/k8s_json_patch.py metaclass-boilerplate!skip -plugins/modules/k8s_log.py compile-2.7!skip -plugins/modules/k8s_log.py compile-3.5!skip -plugins/modules/k8s_log.py future-import-boilerplate!skip -plugins/modules/k8s_log.py import-2.7!skip -plugins/modules/k8s_log.py import-3.5!skip -plugins/modules/k8s_log.py metaclass-boilerplate!skip -plugins/modules/k8s_rollback.py compile-2.7!skip -plugins/modules/k8s_rollback.py compile-3.5!skip -plugins/modules/k8s_rollback.py future-import-boilerplate!skip -plugins/modules/k8s_rollback.py import-2.7!skip -plugins/modules/k8s_rollback.py import-3.5!skip -plugins/modules/k8s_rollback.py metaclass-boilerplate!skip -plugins/modules/k8s_scale.py compile-2.7!skip -plugins/modules/k8s_scale.py compile-3.5!skip -plugins/modules/k8s_scale.py future-import-boilerplate!skip -plugins/modules/k8s_scale.py import-2.7!skip -plugins/modules/k8s_scale.py import-3.5!skip -plugins/modules/k8s_scale.py metaclass-boilerplate!skip plugins/modules/k8s_scale.py validate-modules:parameter-type-not-in-doc -plugins/modules/k8s_scale.py validate-modules:return-syntax-error -plugins/modules/k8s_service.py compile-2.7!skip -plugins/modules/k8s_service.py compile-3.5!skip -plugins/modules/k8s_service.py future-import-boilerplate!skip -plugins/modules/k8s_service.py import-2.7!skip -plugins/modules/k8s_service.py import-3.5!skip -plugins/modules/k8s_service.py metaclass-boilerplate!skip plugins/modules/k8s_service.py validate-modules:parameter-type-not-in-doc -plugins/modules/k8s_service.py validate-modules:return-syntax-error +tests/unit/module_utils/fixtures/definitions.yml yamllint!skip +tests/unit/module_utils/fixtures/deployments.yml yamllint!skip +tests/unit/module_utils/fixtures/pods.yml yamllint!skip +tests/integration/targets/helm/files/appversionless-chart-v2/templates/configmap.yaml yamllint!skip +tests/integration/targets/helm/files/appversionless-chart/templates/configmap.yaml yamllint!skip +tests/integration/targets/helm/files/test-chart-v2/templates/configmap.yaml yamllint!skip +tests/integration/targets/helm/files/test-chart/templates/configmap.yaml yamllint!skip +tests/integration/targets/k8s_scale/files/deployment.yaml yamllint!skip tests/sanity/refresh_ignore_files shebang!skip -plugins/modules/k8s_cp.py compile-2.7!skip -plugins/modules/k8s_cp.py import-2.7!skip -plugins/module_utils/selector.py future-import-boilerplate!skip -plugins/module_utils/selector.py metaclass-boilerplate!skip -plugins/modules/k8s_taint.py compile-2.7!skip -plugins/modules/k8s_taint.py compile-3.5!skip -plugins/modules/k8s_taint.py future-import-boilerplate!skip -plugins/modules/k8s_taint.py import-2.7!skip -plugins/modules/k8s_taint.py import-3.5!skip -plugins/modules/k8s_taint.py metaclass-boilerplate!skip +plugins/modules/k8s.py validate-modules:return-syntax-error +plugins/modules/k8s_scale.py validate-modules:return-syntax-error +plugins/modules/k8s_service.py validate-modules:return-syntax-error plugins/modules/k8s_taint.py validate-modules:return-syntax-error -plugins/module_utils/copy.py future-import-boilerplate!skip -plugins/module_utils/copy.py metaclass-boilerplate!skip diff --git a/tests/sanity/ignore-2.14.txt b/tests/sanity/ignore-2.14.txt index cde97150..8572453e 100644 --- a/tests/sanity/ignore-2.14.txt +++ b/tests/sanity/ignore-2.14.txt @@ -1,196 +1,31 @@ -tests/integration/targets/k8s_scale/files/deployment.yaml yamllint!skip -tests/integration/targets/helm/files/appversionless-chart-v2/templates/configmap.yaml yamllint!skip -tests/integration/targets/helm/files/appversionless-chart/templates/configmap.yaml yamllint!skip -tests/integration/targets/helm/files/test-chart-v2/templates/configmap.yaml yamllint!skip -tests/integration/targets/helm/files/test-chart/templates/configmap.yaml yamllint!skip -plugins/module_utils/__init__.py compile-2.7!skip -plugins/module_utils/__init__.py compile-3.5!skip -plugins/module_utils/__init__.py future-import-boilerplate!skip -plugins/module_utils/__init__.py import-2.7!skip -plugins/module_utils/__init__.py import-3.5!skip -plugins/module_utils/__init__.py metaclass-boilerplate!skip -plugins/module_utils/ansiblemodule.py compile-2.7!skip -plugins/module_utils/ansiblemodule.py compile-3.5!skip -plugins/module_utils/ansiblemodule.py future-import-boilerplate!skip -plugins/module_utils/ansiblemodule.py import-2.7!skip -plugins/module_utils/ansiblemodule.py import-3.5!skip -plugins/module_utils/ansiblemodule.py metaclass-boilerplate!skip -plugins/module_utils/apply.py compile-2.7!skip -plugins/module_utils/apply.py compile-3.5!skip -plugins/module_utils/apply.py future-import-boilerplate!skip -plugins/module_utils/apply.py import-2.7!skip -plugins/module_utils/apply.py import-3.5!skip -plugins/module_utils/apply.py metaclass-boilerplate!skip -plugins/module_utils/args_common.py compile-2.7!skip -plugins/module_utils/args_common.py compile-3.5!skip -plugins/module_utils/args_common.py future-import-boilerplate!skip -plugins/module_utils/args_common.py import-2.7!skip -plugins/module_utils/args_common.py import-3.5!skip -plugins/module_utils/args_common.py metaclass-boilerplate!skip -plugins/module_utils/client/discovery.py future-import-boilerplate!skip -plugins/module_utils/client/discovery.py import-2.7!skip -plugins/module_utils/client/discovery.py import-3.5!skip plugins/module_utils/client/discovery.py import-3.6!skip plugins/module_utils/client/discovery.py import-3.7!skip plugins/module_utils/client/discovery.py import-3.8!skip plugins/module_utils/client/discovery.py import-3.9!skip plugins/module_utils/client/discovery.py import-3.10!skip -plugins/module_utils/client/discovery.py metaclass-boilerplate!skip -plugins/module_utils/client/resource.py import-2.7!skip -plugins/module_utils/client/resource.py import-3.5!skip plugins/module_utils/client/resource.py import-3.6!skip plugins/module_utils/client/resource.py import-3.7!skip plugins/module_utils/client/resource.py import-3.8!skip plugins/module_utils/client/resource.py import-3.9!skip plugins/module_utils/client/resource.py import-3.10!skip -plugins/module_utils/common.py compile-2.7!skip -plugins/module_utils/common.py compile-3.5!skip -plugins/module_utils/common.py future-import-boilerplate!skip -plugins/module_utils/common.py import-2.7!skip -plugins/module_utils/common.py import-3.5!skip -plugins/module_utils/common.py metaclass-boilerplate!skip -plugins/module_utils/exceptions.py compile-2.7!skip -plugins/module_utils/exceptions.py compile-3.5!skip -plugins/module_utils/exceptions.py future-import-boilerplate!skip -plugins/module_utils/exceptions.py import-2.7!skip -plugins/module_utils/exceptions.py import-3.5!skip -plugins/module_utils/exceptions.py metaclass-boilerplate!skip -plugins/module_utils/hashes.py compile-2.7!skip -plugins/module_utils/hashes.py compile-3.5!skip -plugins/module_utils/hashes.py future-import-boilerplate!skip -plugins/module_utils/hashes.py import-2.7!skip -plugins/module_utils/hashes.py import-3.5!skip -plugins/module_utils/hashes.py metaclass-boilerplate!skip -plugins/module_utils/helm.py compile-2.7!skip -plugins/module_utils/helm.py compile-3.5!skip -plugins/module_utils/helm.py future-import-boilerplate!skip -plugins/module_utils/helm.py import-2.7!skip -plugins/module_utils/helm.py import-3.5!skip -plugins/module_utils/helm.py metaclass-boilerplate!skip -plugins/module_utils/k8sdynamicclient.py compile-2.7!skip -plugins/module_utils/k8sdynamicclient.py compile-3.5!skip -plugins/module_utils/k8sdynamicclient.py future-import-boilerplate!skip -plugins/module_utils/k8sdynamicclient.py import-2.7!skip -plugins/module_utils/k8sdynamicclient.py import-3.5!skip plugins/module_utils/k8sdynamicclient.py import-3.6!skip plugins/module_utils/k8sdynamicclient.py import-3.7!skip plugins/module_utils/k8sdynamicclient.py import-3.8!skip plugins/module_utils/k8sdynamicclient.py import-3.9!skip plugins/module_utils/k8sdynamicclient.py import-3.10!skip -plugins/module_utils/k8sdynamicclient.py metaclass-boilerplate!skip -plugins/modules/__init__.py compile-2.7!skip -plugins/modules/__init__.py compile-3.5!skip -plugins/modules/__init__.py future-import-boilerplate!skip -plugins/modules/__init__.py import-2.7!skip -plugins/modules/__init__.py import-3.5!skip -plugins/modules/__init__.py metaclass-boilerplate!skip -plugins/modules/helm.py compile-2.7!skip -plugins/modules/helm.py compile-3.5!skip -plugins/modules/helm.py future-import-boilerplate!skip -plugins/modules/helm.py import-2.7!skip -plugins/modules/helm.py import-3.5!skip -plugins/modules/helm.py metaclass-boilerplate!skip -plugins/modules/helm_info.py compile-2.7!skip -plugins/modules/helm_info.py compile-3.5!skip -plugins/modules/helm_info.py future-import-boilerplate!skip -plugins/modules/helm_info.py import-2.7!skip -plugins/modules/helm_info.py import-3.5!skip -plugins/modules/helm_info.py metaclass-boilerplate!skip -plugins/modules/helm_plugin.py compile-2.7!skip -plugins/modules/helm_plugin.py compile-3.5!skip -plugins/modules/helm_plugin.py future-import-boilerplate!skip -plugins/modules/helm_plugin.py import-2.7!skip -plugins/modules/helm_plugin.py import-3.5!skip -plugins/modules/helm_plugin.py metaclass-boilerplate!skip -plugins/modules/helm_plugin_info.py compile-2.7!skip -plugins/modules/helm_plugin_info.py compile-3.5!skip -plugins/modules/helm_plugin_info.py future-import-boilerplate!skip -plugins/modules/helm_plugin_info.py import-2.7!skip -plugins/modules/helm_plugin_info.py import-3.5!skip -plugins/modules/helm_plugin_info.py metaclass-boilerplate!skip -plugins/modules/helm_repository.py compile-2.7!skip -plugins/modules/helm_repository.py compile-3.5!skip -plugins/modules/helm_repository.py future-import-boilerplate!skip -plugins/modules/helm_repository.py import-2.7!skip -plugins/modules/helm_repository.py import-3.5!skip -plugins/modules/helm_repository.py metaclass-boilerplate!skip -plugins/modules/helm_template.py compile-2.7!skip -plugins/modules/helm_template.py compile-3.5!skip -plugins/modules/helm_template.py future-import-boilerplate!skip -plugins/modules/helm_template.py import-2.7!skip -plugins/modules/helm_template.py import-3.5!skip -plugins/modules/helm_template.py metaclass-boilerplate!skip -plugins/modules/k8s.py compile-2.7!skip -plugins/modules/k8s.py compile-3.5!skip -plugins/modules/k8s.py future-import-boilerplate!skip -plugins/modules/k8s.py import-2.7!skip -plugins/modules/k8s.py import-3.5!skip -plugins/modules/k8s.py metaclass-boilerplate!skip plugins/modules/k8s.py validate-modules:parameter-type-not-in-doc -plugins/modules/k8s.py validate-modules:return-syntax-error -plugins/modules/k8s_cluster_info.py compile-2.7!skip -plugins/modules/k8s_cluster_info.py compile-3.5!skip -plugins/modules/k8s_cluster_info.py future-import-boilerplate!skip -plugins/modules/k8s_cluster_info.py import-2.7!skip -plugins/modules/k8s_cluster_info.py import-3.5!skip -plugins/modules/k8s_cluster_info.py metaclass-boilerplate!skip -plugins/modules/k8s_exec.py compile-2.7!skip -plugins/modules/k8s_exec.py compile-3.5!skip -plugins/modules/k8s_exec.py future-import-boilerplate!skip -plugins/modules/k8s_exec.py import-2.7!skip -plugins/modules/k8s_exec.py import-3.5!skip -plugins/modules/k8s_exec.py metaclass-boilerplate!skip -plugins/modules/k8s_info.py compile-2.7!skip -plugins/modules/k8s_info.py compile-3.5!skip -plugins/modules/k8s_info.py future-import-boilerplate!skip -plugins/modules/k8s_info.py import-2.7!skip -plugins/modules/k8s_info.py import-3.5!skip -plugins/modules/k8s_info.py metaclass-boilerplate!skip -plugins/modules/k8s_json_patch.py compile-2.7!skip -plugins/modules/k8s_json_patch.py compile-3.5!skip -plugins/modules/k8s_json_patch.py future-import-boilerplate!skip -plugins/modules/k8s_json_patch.py import-2.7!skip -plugins/modules/k8s_json_patch.py import-3.5!skip -plugins/modules/k8s_json_patch.py metaclass-boilerplate!skip -plugins/modules/k8s_log.py compile-2.7!skip -plugins/modules/k8s_log.py compile-3.5!skip -plugins/modules/k8s_log.py future-import-boilerplate!skip -plugins/modules/k8s_log.py import-2.7!skip -plugins/modules/k8s_log.py import-3.5!skip -plugins/modules/k8s_log.py metaclass-boilerplate!skip -plugins/modules/k8s_rollback.py compile-2.7!skip -plugins/modules/k8s_rollback.py compile-3.5!skip -plugins/modules/k8s_rollback.py future-import-boilerplate!skip -plugins/modules/k8s_rollback.py import-2.7!skip -plugins/modules/k8s_rollback.py import-3.5!skip -plugins/modules/k8s_rollback.py metaclass-boilerplate!skip -plugins/modules/k8s_scale.py compile-2.7!skip -plugins/modules/k8s_scale.py compile-3.5!skip -plugins/modules/k8s_scale.py future-import-boilerplate!skip -plugins/modules/k8s_scale.py import-2.7!skip -plugins/modules/k8s_scale.py import-3.5!skip -plugins/modules/k8s_scale.py metaclass-boilerplate!skip plugins/modules/k8s_scale.py validate-modules:parameter-type-not-in-doc -plugins/modules/k8s_scale.py validate-modules:return-syntax-error -plugins/modules/k8s_service.py compile-2.7!skip -plugins/modules/k8s_service.py compile-3.5!skip -plugins/modules/k8s_service.py future-import-boilerplate!skip -plugins/modules/k8s_service.py import-2.7!skip -plugins/modules/k8s_service.py import-3.5!skip -plugins/modules/k8s_service.py metaclass-boilerplate!skip plugins/modules/k8s_service.py validate-modules:parameter-type-not-in-doc -plugins/modules/k8s_service.py validate-modules:return-syntax-error +tests/unit/module_utils/fixtures/definitions.yml yamllint!skip +tests/unit/module_utils/fixtures/deployments.yml yamllint!skip +tests/unit/module_utils/fixtures/pods.yml yamllint!skip +tests/integration/targets/helm/files/appversionless-chart-v2/templates/configmap.yaml yamllint!skip +tests/integration/targets/helm/files/appversionless-chart/templates/configmap.yaml yamllint!skip +tests/integration/targets/helm/files/test-chart-v2/templates/configmap.yaml yamllint!skip +tests/integration/targets/helm/files/test-chart/templates/configmap.yaml yamllint!skip +tests/integration/targets/k8s_scale/files/deployment.yaml yamllint!skip tests/sanity/refresh_ignore_files shebang!skip -plugins/modules/k8s_cp.py compile-2.7!skip -plugins/modules/k8s_cp.py import-2.7!skip -plugins/module_utils/selector.py future-import-boilerplate!skip -plugins/module_utils/selector.py metaclass-boilerplate!skip -plugins/modules/k8s_taint.py compile-2.7!skip -plugins/modules/k8s_taint.py compile-3.5!skip -plugins/modules/k8s_taint.py future-import-boilerplate!skip -plugins/modules/k8s_taint.py import-2.7!skip -plugins/modules/k8s_taint.py import-3.5!skip -plugins/modules/k8s_taint.py metaclass-boilerplate!skip +plugins/modules/k8s.py validate-modules:return-syntax-error +plugins/modules/k8s_scale.py validate-modules:return-syntax-error +plugins/modules/k8s_service.py validate-modules:return-syntax-error plugins/modules/k8s_taint.py validate-modules:return-syntax-error -plugins/module_utils/copy.py future-import-boilerplate!skip -plugins/module_utils/copy.py metaclass-boilerplate!skip diff --git a/tests/sanity/ignore-2.9.txt b/tests/sanity/ignore-2.9.txt index 0a58a934..ce09f79c 100644 --- a/tests/sanity/ignore-2.9.txt +++ b/tests/sanity/ignore-2.9.txt @@ -1,255 +1,171 @@ -tests/integration/targets/k8s_scale/files/deployment.yaml yamllint!skip +plugins/module_utils/client/discovery.py import-3.6!skip +plugins/module_utils/client/discovery.py import-3.7!skip +plugins/module_utils/client/discovery.py import-3.8!skip +plugins/module_utils/client/resource.py import-3.6!skip +plugins/module_utils/client/resource.py import-3.7!skip +plugins/module_utils/client/resource.py import-3.8!skip +plugins/module_utils/k8sdynamicclient.py import-3.6!skip +plugins/module_utils/k8sdynamicclient.py import-3.7!skip +plugins/module_utils/k8sdynamicclient.py import-3.8!skip +plugins/modules/k8s.py validate-modules:parameter-type-not-in-doc +plugins/modules/k8s_scale.py validate-modules:parameter-type-not-in-doc +plugins/modules/k8s_service.py validate-modules:parameter-type-not-in-doc +tests/unit/module_utils/fixtures/definitions.yml yamllint!skip +tests/unit/module_utils/fixtures/deployments.yml yamllint!skip +tests/unit/module_utils/fixtures/pods.yml yamllint!skip tests/integration/targets/helm/files/appversionless-chart-v2/templates/configmap.yaml yamllint!skip tests/integration/targets/helm/files/appversionless-chart/templates/configmap.yaml yamllint!skip tests/integration/targets/helm/files/test-chart-v2/templates/configmap.yaml yamllint!skip tests/integration/targets/helm/files/test-chart/templates/configmap.yaml yamllint!skip -plugins/module_utils/__init__.py compile-2.6!skip -plugins/module_utils/__init__.py compile-2.7!skip -plugins/module_utils/__init__.py compile-3.5!skip -plugins/module_utils/__init__.py future-import-boilerplate!skip -plugins/module_utils/__init__.py import-2.6!skip -plugins/module_utils/__init__.py import-2.7!skip -plugins/module_utils/__init__.py import-3.5!skip -plugins/module_utils/__init__.py metaclass-boilerplate!skip -plugins/module_utils/ansiblemodule.py compile-2.6!skip -plugins/module_utils/ansiblemodule.py compile-2.7!skip -plugins/module_utils/ansiblemodule.py compile-3.5!skip -plugins/module_utils/ansiblemodule.py future-import-boilerplate!skip -plugins/module_utils/ansiblemodule.py import-2.6!skip -plugins/module_utils/ansiblemodule.py import-2.7!skip -plugins/module_utils/ansiblemodule.py import-3.5!skip -plugins/module_utils/ansiblemodule.py metaclass-boilerplate!skip -plugins/module_utils/apply.py compile-2.6!skip -plugins/module_utils/apply.py compile-2.7!skip -plugins/module_utils/apply.py compile-3.5!skip -plugins/module_utils/apply.py future-import-boilerplate!skip -plugins/module_utils/apply.py import-2.6!skip -plugins/module_utils/apply.py import-2.7!skip -plugins/module_utils/apply.py import-3.5!skip -plugins/module_utils/apply.py metaclass-boilerplate!skip -plugins/module_utils/args_common.py compile-2.6!skip -plugins/module_utils/args_common.py compile-2.7!skip -plugins/module_utils/args_common.py compile-3.5!skip -plugins/module_utils/args_common.py future-import-boilerplate!skip -plugins/module_utils/args_common.py import-2.6!skip -plugins/module_utils/args_common.py import-2.7!skip -plugins/module_utils/args_common.py import-3.5!skip -plugins/module_utils/args_common.py metaclass-boilerplate!skip -plugins/module_utils/client/discovery.py future-import-boilerplate!skip -plugins/module_utils/client/discovery.py import-2.6!skip -plugins/module_utils/client/discovery.py import-2.7!skip -plugins/module_utils/client/discovery.py import-3.5!skip -plugins/module_utils/client/discovery.py import-3.6!skip -plugins/module_utils/client/discovery.py import-3.7!skip -plugins/module_utils/client/discovery.py import-3.8!skip -plugins/module_utils/client/discovery.py metaclass-boilerplate!skip -plugins/module_utils/client/resource.py import-2.6!skip -plugins/module_utils/client/resource.py import-2.7!skip -plugins/module_utils/client/resource.py import-3.5!skip -plugins/module_utils/client/resource.py import-3.6!skip -plugins/module_utils/client/resource.py import-3.7!skip -plugins/module_utils/client/resource.py import-3.8!skip -plugins/module_utils/common.py compile-2.6!skip -plugins/module_utils/common.py compile-2.7!skip -plugins/module_utils/common.py compile-3.5!skip -plugins/module_utils/common.py future-import-boilerplate!skip -plugins/module_utils/common.py import-2.6!skip -plugins/module_utils/common.py import-2.7!skip -plugins/module_utils/common.py import-3.5!skip -plugins/module_utils/common.py metaclass-boilerplate!skip -plugins/module_utils/exceptions.py compile-2.6!skip -plugins/module_utils/exceptions.py compile-2.7!skip -plugins/module_utils/exceptions.py compile-3.5!skip -plugins/module_utils/exceptions.py future-import-boilerplate!skip -plugins/module_utils/exceptions.py import-2.6!skip -plugins/module_utils/exceptions.py import-2.7!skip -plugins/module_utils/exceptions.py import-3.5!skip -plugins/module_utils/exceptions.py metaclass-boilerplate!skip -plugins/module_utils/hashes.py compile-2.6!skip -plugins/module_utils/hashes.py compile-2.7!skip -plugins/module_utils/hashes.py compile-3.5!skip -plugins/module_utils/hashes.py future-import-boilerplate!skip -plugins/module_utils/hashes.py import-2.6!skip -plugins/module_utils/hashes.py import-2.7!skip -plugins/module_utils/hashes.py import-3.5!skip -plugins/module_utils/hashes.py metaclass-boilerplate!skip -plugins/module_utils/helm.py compile-2.6!skip -plugins/module_utils/helm.py compile-2.7!skip -plugins/module_utils/helm.py compile-3.5!skip -plugins/module_utils/helm.py future-import-boilerplate!skip -plugins/module_utils/helm.py import-2.6!skip -plugins/module_utils/helm.py import-2.7!skip -plugins/module_utils/helm.py import-3.5!skip -plugins/module_utils/helm.py metaclass-boilerplate!skip -plugins/module_utils/k8sdynamicclient.py compile-2.6!skip -plugins/module_utils/k8sdynamicclient.py compile-2.7!skip -plugins/module_utils/k8sdynamicclient.py compile-3.5!skip -plugins/module_utils/k8sdynamicclient.py future-import-boilerplate!skip -plugins/module_utils/k8sdynamicclient.py import-2.6!skip -plugins/module_utils/k8sdynamicclient.py import-2.7!skip -plugins/module_utils/k8sdynamicclient.py import-3.5!skip -plugins/module_utils/k8sdynamicclient.py import-3.6!skip -plugins/module_utils/k8sdynamicclient.py import-3.7!skip -plugins/module_utils/k8sdynamicclient.py import-3.8!skip -plugins/module_utils/k8sdynamicclient.py metaclass-boilerplate!skip -plugins/modules/__init__.py compile-2.6!skip -plugins/modules/__init__.py compile-2.7!skip -plugins/modules/__init__.py compile-3.5!skip -plugins/modules/__init__.py future-import-boilerplate!skip -plugins/modules/__init__.py import-2.6!skip -plugins/modules/__init__.py import-2.7!skip -plugins/modules/__init__.py import-3.5!skip -plugins/modules/__init__.py metaclass-boilerplate!skip -plugins/modules/helm.py compile-2.6!skip -plugins/modules/helm.py compile-2.7!skip -plugins/modules/helm.py compile-3.5!skip -plugins/modules/helm.py future-import-boilerplate!skip -plugins/modules/helm.py import-2.6!skip -plugins/modules/helm.py import-2.7!skip -plugins/modules/helm.py import-3.5!skip -plugins/modules/helm.py metaclass-boilerplate!skip -plugins/modules/helm_info.py compile-2.6!skip -plugins/modules/helm_info.py compile-2.7!skip -plugins/modules/helm_info.py compile-3.5!skip -plugins/modules/helm_info.py future-import-boilerplate!skip -plugins/modules/helm_info.py import-2.6!skip -plugins/modules/helm_info.py import-2.7!skip -plugins/modules/helm_info.py import-3.5!skip -plugins/modules/helm_info.py metaclass-boilerplate!skip -plugins/modules/helm_plugin.py compile-2.6!skip -plugins/modules/helm_plugin.py compile-2.7!skip -plugins/modules/helm_plugin.py compile-3.5!skip -plugins/modules/helm_plugin.py future-import-boilerplate!skip -plugins/modules/helm_plugin.py import-2.6!skip -plugins/modules/helm_plugin.py import-2.7!skip -plugins/modules/helm_plugin.py import-3.5!skip -plugins/modules/helm_plugin.py metaclass-boilerplate!skip -plugins/modules/helm_plugin_info.py compile-2.6!skip -plugins/modules/helm_plugin_info.py compile-2.7!skip -plugins/modules/helm_plugin_info.py compile-3.5!skip -plugins/modules/helm_plugin_info.py future-import-boilerplate!skip -plugins/modules/helm_plugin_info.py import-2.6!skip -plugins/modules/helm_plugin_info.py import-2.7!skip -plugins/modules/helm_plugin_info.py import-3.5!skip -plugins/modules/helm_plugin_info.py metaclass-boilerplate!skip -plugins/modules/helm_repository.py compile-2.6!skip -plugins/modules/helm_repository.py compile-2.7!skip -plugins/modules/helm_repository.py compile-3.5!skip -plugins/modules/helm_repository.py future-import-boilerplate!skip -plugins/modules/helm_repository.py import-2.6!skip -plugins/modules/helm_repository.py import-2.7!skip -plugins/modules/helm_repository.py import-3.5!skip -plugins/modules/helm_repository.py metaclass-boilerplate!skip -plugins/modules/helm_template.py compile-2.6!skip -plugins/modules/helm_template.py compile-2.7!skip -plugins/modules/helm_template.py compile-3.5!skip -plugins/modules/helm_template.py future-import-boilerplate!skip -plugins/modules/helm_template.py import-2.6!skip -plugins/modules/helm_template.py import-2.7!skip -plugins/modules/helm_template.py import-3.5!skip -plugins/modules/helm_template.py metaclass-boilerplate!skip -plugins/modules/k8s.py compile-2.6!skip -plugins/modules/k8s.py compile-2.7!skip -plugins/modules/k8s.py compile-3.5!skip -plugins/modules/k8s.py future-import-boilerplate!skip -plugins/modules/k8s.py import-2.6!skip -plugins/modules/k8s.py import-2.7!skip -plugins/modules/k8s.py import-3.5!skip -plugins/modules/k8s.py metaclass-boilerplate!skip -plugins/modules/k8s.py validate-modules:parameter-type-not-in-doc -plugins/modules/k8s_cluster_info.py compile-2.6!skip -plugins/modules/k8s_cluster_info.py compile-2.7!skip -plugins/modules/k8s_cluster_info.py compile-3.5!skip -plugins/modules/k8s_cluster_info.py future-import-boilerplate!skip -plugins/modules/k8s_cluster_info.py import-2.6!skip -plugins/modules/k8s_cluster_info.py import-2.7!skip -plugins/modules/k8s_cluster_info.py import-3.5!skip -plugins/modules/k8s_cluster_info.py metaclass-boilerplate!skip -plugins/modules/k8s_exec.py compile-2.6!skip -plugins/modules/k8s_exec.py compile-2.7!skip -plugins/modules/k8s_exec.py compile-3.5!skip -plugins/modules/k8s_exec.py future-import-boilerplate!skip -plugins/modules/k8s_exec.py import-2.6!skip -plugins/modules/k8s_exec.py import-2.7!skip -plugins/modules/k8s_exec.py import-3.5!skip -plugins/modules/k8s_exec.py metaclass-boilerplate!skip -plugins/modules/k8s_info.py compile-2.6!skip -plugins/modules/k8s_info.py compile-2.7!skip -plugins/modules/k8s_info.py compile-3.5!skip -plugins/modules/k8s_info.py future-import-boilerplate!skip -plugins/modules/k8s_info.py import-2.6!skip -plugins/modules/k8s_info.py import-2.7!skip -plugins/modules/k8s_info.py import-3.5!skip -plugins/modules/k8s_info.py metaclass-boilerplate!skip -plugins/modules/k8s_json_patch.py compile-2.6!skip -plugins/modules/k8s_json_patch.py compile-2.7!skip -plugins/modules/k8s_json_patch.py compile-3.5!skip -plugins/modules/k8s_json_patch.py future-import-boilerplate!skip -plugins/modules/k8s_json_patch.py import-2.6!skip -plugins/modules/k8s_json_patch.py import-2.7!skip -plugins/modules/k8s_json_patch.py import-3.5!skip -plugins/modules/k8s_json_patch.py metaclass-boilerplate!skip -plugins/modules/k8s_log.py compile-2.6!skip -plugins/modules/k8s_log.py compile-2.7!skip -plugins/modules/k8s_log.py compile-3.5!skip -plugins/modules/k8s_log.py future-import-boilerplate!skip -plugins/modules/k8s_log.py import-2.6!skip -plugins/modules/k8s_log.py import-2.7!skip -plugins/modules/k8s_log.py import-3.5!skip -plugins/modules/k8s_log.py metaclass-boilerplate!skip -plugins/modules/k8s_rollback.py compile-2.6!skip -plugins/modules/k8s_rollback.py compile-2.7!skip -plugins/modules/k8s_rollback.py compile-3.5!skip -plugins/modules/k8s_rollback.py future-import-boilerplate!skip -plugins/modules/k8s_rollback.py import-2.6!skip -plugins/modules/k8s_rollback.py import-2.7!skip -plugins/modules/k8s_rollback.py import-3.5!skip -plugins/modules/k8s_rollback.py metaclass-boilerplate!skip -plugins/modules/k8s_scale.py compile-2.6!skip -plugins/modules/k8s_scale.py compile-2.7!skip -plugins/modules/k8s_scale.py compile-3.5!skip -plugins/modules/k8s_scale.py future-import-boilerplate!skip -plugins/modules/k8s_scale.py import-2.6!skip -plugins/modules/k8s_scale.py import-2.7!skip -plugins/modules/k8s_scale.py import-3.5!skip -plugins/modules/k8s_scale.py metaclass-boilerplate!skip -plugins/modules/k8s_scale.py validate-modules:parameter-type-not-in-doc -plugins/modules/k8s_service.py compile-2.6!skip -plugins/modules/k8s_service.py compile-2.7!skip -plugins/modules/k8s_service.py compile-3.5!skip -plugins/modules/k8s_service.py future-import-boilerplate!skip -plugins/modules/k8s_service.py import-2.6!skip -plugins/modules/k8s_service.py import-2.7!skip -plugins/modules/k8s_service.py import-3.5!skip -plugins/modules/k8s_service.py metaclass-boilerplate!skip -plugins/modules/k8s_service.py validate-modules:parameter-type-not-in-doc +tests/integration/targets/k8s_scale/files/deployment.yaml yamllint!skip tests/sanity/refresh_ignore_files shebang!skip -tests/unit/module_utils/test_discoverer.py future-import-boilerplate!skip -tests/unit/module_utils/test_discoverer.py metaclass-boilerplate!skip -plugins/modules/k8s_cp.py compile-2.6!skip -plugins/modules/k8s_cp.py compile-2.7!skip -plugins/modules/k8s_cp.py import-2.6!skip -plugins/modules/k8s_cp.py import-2.7!skip -tests/integration/targets/k8s_copy/library/kubectl_file_compare.py compile-2.6!skip -tests/integration/targets/k8s_copy/library/kubectl_file_compare.py compile-2.7!skip -tests/integration/targets/k8s_copy/library/kubectl_file_compare.py compile-3.5!skip -tests/unit/module_utils/test_selector.py future-import-boilerplate!skip -tests/unit/module_utils/test_selector.py metaclass-boilerplate!skip -plugins/module_utils/selector.py future-import-boilerplate!skip -plugins/module_utils/selector.py metaclass-boilerplate!skip -plugins/lookup/kustomize.py future-import-boilerplate!skip -plugins/lookup/kustomize.py metaclass-boilerplate!skip -tests/integration/targets/helm/library/helm_test_version.py metaclass-boilerplate!skip -tests/integration/targets/helm/library/helm_test_version.py future-import-boilerplate!skip -plugins/modules/k8s_taint.py compile-2.7!skip -plugins/modules/k8s_taint.py compile-3.5!skip -plugins/modules/k8s_taint.py future-import-boilerplate!skip -plugins/modules/k8s_taint.py import-2.7!skip -plugins/modules/k8s_taint.py import-3.5!skip -plugins/modules/k8s_taint.py metaclass-boilerplate!skip +plugins/doc_fragments/k8s_name_options.py future-import-boilerplate!skip +plugins/doc_fragments/k8s_auth_options.py future-import-boilerplate!skip +plugins/doc_fragments/helm_common_options.py future-import-boilerplate!skip +plugins/doc_fragments/k8s_state_options.py future-import-boilerplate!skip +plugins/doc_fragments/k8s_wait_options.py future-import-boilerplate!skip +plugins/doc_fragments/k8s_scale_options.py future-import-boilerplate!skip +plugins/doc_fragments/k8s_delete_options.py future-import-boilerplate!skip +plugins/doc_fragments/__init__.py future-import-boilerplate!skip +plugins/doc_fragments/k8s_resource_options.py future-import-boilerplate!skip +plugins/module_utils/helm.py future-import-boilerplate!skip +plugins/module_utils/apply.py future-import-boilerplate!skip +plugins/module_utils/hashes.py future-import-boilerplate!skip +plugins/module_utils/version.py future-import-boilerplate!skip +plugins/module_utils/_version.py future-import-boilerplate!skip plugins/module_utils/copy.py future-import-boilerplate!skip +plugins/module_utils/args_common.py future-import-boilerplate!skip +plugins/module_utils/__init__.py future-import-boilerplate!skip +plugins/module_utils/selector.py future-import-boilerplate!skip +plugins/module_utils/k8sdynamicclient.py future-import-boilerplate!skip +plugins/module_utils/common.py future-import-boilerplate!skip +plugins/module_utils/ansiblemodule.py future-import-boilerplate!skip +plugins/module_utils/exceptions.py future-import-boilerplate!skip +plugins/module_utils/client/resource.py future-import-boilerplate!skip +plugins/module_utils/client/discovery.py future-import-boilerplate!skip +plugins/module_utils/k8s/resource.py future-import-boilerplate!skip +plugins/module_utils/k8s/core.py future-import-boilerplate!skip +plugins/module_utils/k8s/waiter.py future-import-boilerplate!skip +plugins/module_utils/k8s/client.py future-import-boilerplate!skip +plugins/module_utils/k8s/runner.py future-import-boilerplate!skip +plugins/module_utils/k8s/service.py future-import-boilerplate!skip +plugins/module_utils/k8s/exceptions.py future-import-boilerplate!skip +plugins/connection/kubectl.py future-import-boilerplate!skip +plugins/inventory/k8s.py future-import-boilerplate!skip +plugins/lookup/k8s.py future-import-boilerplate!skip +plugins/lookup/kustomize.py future-import-boilerplate!skip +plugins/modules/k8s_scale.py future-import-boilerplate!skip +plugins/modules/helm_template.py future-import-boilerplate!skip +plugins/modules/k8s_exec.py future-import-boilerplate!skip +plugins/modules/helm.py future-import-boilerplate!skip +plugins/modules/helm_plugin_info.py future-import-boilerplate!skip +plugins/modules/helm_info.py future-import-boilerplate!skip +plugins/modules/helm_repository.py future-import-boilerplate!skip +plugins/modules/k8s_rollback.py future-import-boilerplate!skip +plugins/modules/k8s_log.py future-import-boilerplate!skip +plugins/modules/k8s_drain.py future-import-boilerplate!skip +plugins/modules/helm_plugin.py future-import-boilerplate!skip +plugins/modules/k8s_taint.py future-import-boilerplate!skip +plugins/modules/k8s.py future-import-boilerplate!skip +plugins/modules/k8s_service.py future-import-boilerplate!skip +plugins/modules/k8s_cluster_info.py future-import-boilerplate!skip +plugins/modules/k8s_info.py future-import-boilerplate!skip +plugins/modules/k8s_cp.py future-import-boilerplate!skip +plugins/modules/__init__.py future-import-boilerplate!skip +plugins/modules/k8s_json_patch.py future-import-boilerplate!skip +plugins/action/k8s_info.py future-import-boilerplate!skip +plugins/filter/k8s.py future-import-boilerplate!skip +tests/unit/conftest.py future-import-boilerplate!skip +tests/unit/utils/ansible_module_mock.py future-import-boilerplate!skip +tests/unit/module_utils/test_helm.py future-import-boilerplate!skip +tests/unit/module_utils/test_marshal.py future-import-boilerplate!skip +tests/unit/module_utils/test_discoverer.py future-import-boilerplate!skip +tests/unit/module_utils/test_hashes.py future-import-boilerplate!skip +tests/unit/module_utils/test_resource.py future-import-boilerplate!skip +tests/unit/module_utils/test_service.py future-import-boilerplate!skip +tests/unit/module_utils/test_waiter.py future-import-boilerplate!skip +tests/unit/module_utils/test_common.py future-import-boilerplate!skip +tests/unit/module_utils/test_selector.py future-import-boilerplate!skip +tests/unit/module_utils/test_apply.py future-import-boilerplate!skip +tests/unit/module_utils/test_runner.py future-import-boilerplate!skip +tests/unit/module_utils/test_client.py future-import-boilerplate!skip +tests/unit/module_utils/test_core.py future-import-boilerplate!skip +tests/unit/modules/test_helm_template_module.py future-import-boilerplate!skip +tests/unit/modules/test_helm_template.py future-import-boilerplate!skip +tests/unit/modules/test_module_helm.py future-import-boilerplate!skip +tests/unit/action/test_remove_omit.py future-import-boilerplate!skip +plugins/doc_fragments/k8s_name_options.py metaclass-boilerplate!skip +plugins/doc_fragments/k8s_auth_options.py metaclass-boilerplate!skip +plugins/doc_fragments/helm_common_options.py metaclass-boilerplate!skip +plugins/doc_fragments/k8s_state_options.py metaclass-boilerplate!skip +plugins/doc_fragments/k8s_wait_options.py metaclass-boilerplate!skip +plugins/doc_fragments/k8s_scale_options.py metaclass-boilerplate!skip +plugins/doc_fragments/k8s_delete_options.py metaclass-boilerplate!skip +plugins/doc_fragments/__init__.py metaclass-boilerplate!skip +plugins/doc_fragments/k8s_resource_options.py metaclass-boilerplate!skip +plugins/module_utils/helm.py metaclass-boilerplate!skip +plugins/module_utils/apply.py metaclass-boilerplate!skip +plugins/module_utils/hashes.py metaclass-boilerplate!skip +plugins/module_utils/version.py metaclass-boilerplate!skip +plugins/module_utils/_version.py metaclass-boilerplate!skip plugins/module_utils/copy.py metaclass-boilerplate!skip -plugins/module_utils/copy.py compile-2.6!skip -plugins/module_utils/copy.py compile-2.7!skip -plugins/module_utils/copy.py import-2.6!skip -plugins/module_utils/copy.py import-2.7!skip +plugins/module_utils/args_common.py metaclass-boilerplate!skip +plugins/module_utils/__init__.py metaclass-boilerplate!skip +plugins/module_utils/selector.py metaclass-boilerplate!skip +plugins/module_utils/k8sdynamicclient.py metaclass-boilerplate!skip +plugins/module_utils/common.py metaclass-boilerplate!skip +plugins/module_utils/ansiblemodule.py metaclass-boilerplate!skip +plugins/module_utils/exceptions.py metaclass-boilerplate!skip +plugins/module_utils/client/resource.py metaclass-boilerplate!skip +plugins/module_utils/client/discovery.py metaclass-boilerplate!skip +plugins/module_utils/k8s/resource.py metaclass-boilerplate!skip +plugins/module_utils/k8s/core.py metaclass-boilerplate!skip +plugins/module_utils/k8s/waiter.py metaclass-boilerplate!skip +plugins/module_utils/k8s/client.py metaclass-boilerplate!skip +plugins/module_utils/k8s/runner.py metaclass-boilerplate!skip +plugins/module_utils/k8s/service.py metaclass-boilerplate!skip +plugins/module_utils/k8s/exceptions.py metaclass-boilerplate!skip +plugins/connection/kubectl.py metaclass-boilerplate!skip +plugins/inventory/k8s.py metaclass-boilerplate!skip +plugins/lookup/k8s.py metaclass-boilerplate!skip +plugins/lookup/kustomize.py metaclass-boilerplate!skip +plugins/modules/k8s_scale.py metaclass-boilerplate!skip +plugins/modules/helm_template.py metaclass-boilerplate!skip +plugins/modules/k8s_exec.py metaclass-boilerplate!skip +plugins/modules/helm.py metaclass-boilerplate!skip +plugins/modules/helm_plugin_info.py metaclass-boilerplate!skip +plugins/modules/helm_info.py metaclass-boilerplate!skip +plugins/modules/helm_repository.py metaclass-boilerplate!skip +plugins/modules/k8s_rollback.py metaclass-boilerplate!skip +plugins/modules/k8s_log.py metaclass-boilerplate!skip +plugins/modules/k8s_drain.py metaclass-boilerplate!skip +plugins/modules/helm_plugin.py metaclass-boilerplate!skip +plugins/modules/k8s_taint.py metaclass-boilerplate!skip +plugins/modules/k8s.py metaclass-boilerplate!skip +plugins/modules/k8s_service.py metaclass-boilerplate!skip +plugins/modules/k8s_cluster_info.py metaclass-boilerplate!skip +plugins/modules/k8s_info.py metaclass-boilerplate!skip +plugins/modules/k8s_cp.py metaclass-boilerplate!skip +plugins/modules/__init__.py metaclass-boilerplate!skip +plugins/modules/k8s_json_patch.py metaclass-boilerplate!skip +plugins/action/k8s_info.py metaclass-boilerplate!skip +plugins/filter/k8s.py metaclass-boilerplate!skip +tests/unit/conftest.py metaclass-boilerplate!skip +tests/unit/utils/ansible_module_mock.py metaclass-boilerplate!skip +tests/unit/module_utils/test_helm.py metaclass-boilerplate!skip +tests/unit/module_utils/test_marshal.py metaclass-boilerplate!skip +tests/unit/module_utils/test_discoverer.py metaclass-boilerplate!skip +tests/unit/module_utils/test_hashes.py metaclass-boilerplate!skip +tests/unit/module_utils/test_resource.py metaclass-boilerplate!skip +tests/unit/module_utils/test_service.py metaclass-boilerplate!skip +tests/unit/module_utils/test_waiter.py metaclass-boilerplate!skip +tests/unit/module_utils/test_common.py metaclass-boilerplate!skip +tests/unit/module_utils/test_selector.py metaclass-boilerplate!skip +tests/unit/module_utils/test_apply.py metaclass-boilerplate!skip +tests/unit/module_utils/test_runner.py metaclass-boilerplate!skip +tests/unit/module_utils/test_client.py metaclass-boilerplate!skip +tests/unit/module_utils/test_core.py metaclass-boilerplate!skip +tests/unit/modules/test_helm_template_module.py metaclass-boilerplate!skip +tests/unit/modules/test_helm_template.py metaclass-boilerplate!skip +tests/unit/modules/test_module_helm.py metaclass-boilerplate!skip +tests/unit/action/test_remove_omit.py metaclass-boilerplate!skip diff --git a/tests/sanity/refresh_ignore_files b/tests/sanity/refresh_ignore_files index ede60a83..85afa676 100644 --- a/tests/sanity/refresh_ignore_files +++ b/tests/sanity/refresh_ignore_files @@ -1,39 +1,124 @@ #!/usr/bin/env python3 + +import itertools + from pathlib import Path -target_dir = Path('.') -ignore_dir = target_dir / "tests" / "sanity" -module_dir = target_dir / "plugins" / "modules" -module_utils_dir = target_dir / "plugins" / "module_utils" -ignore_dir.mkdir(parents=True, exist_ok=True) +# Mapping of Ansible versions to supported Python versions +ANSIBLE_VERSIONS = { + "2.9": ["3.6", "3.7", "3.8"], + "2.10": ["3.6", "3.7", "3.8", "3.9"], + "2.11": ["3.6", "3.7", "3.8", "3.9"], + "2.12": ["3.6", "3.7", "3.8", "3.9", "3.10"], + "2.13": ["3.6", "3.7", "3.8", "3.9", "3.10"], + "2.14": ["3.6", "3.7", "3.8", "3.9", "3.10"], +} -skip_list = [ - "compile-2.6!skip", # Py3.8+ - "compile-2.7!skip", # Py3.8+ - "compile-3.5!skip", # Py3.8+ - "import-2.6!skip", # Py3.8+ - "import-2.7!skip", # Py3.8+ - "import-3.5!skip", # Py3.8+ - "future-import-boilerplate!skip", # Py2 only - "metaclass-boilerplate!skip", # Py2 only +IMPORT_SKIPS = [ + "plugins/module_utils/client/discovery.py", + "plugins/module_utils/client/resource.py", + "plugins/module_utils/k8sdynamicclient.py", ] -for version in ["2.9", "2.10", "2.11", "2.12"]: - ignore_file = ignore_dir / f"ignore-{version}.txt" - ignore_content = ignore_file.read_text().split("\n") - ignore_content.append(f"tests/sanity/refresh_ignore_files shebang!skip") +# Adds validate-modules:parameter-type-not-in-doc +PARAM_TYPE_SKIPS = [ + "plugins/modules/k8s.py", + "plugins/modules/k8s_scale.py", + "plugins/modules/k8s_service.py", +] - for f in module_dir.glob("*.py"): - if f.is_symlink(): - continue - for test in skip_list: - ignore_content.append(f"{f} {test}") - for f in module_utils_dir.glob("*.py"): - if f.is_symlink(): - continue - for test in skip_list: - ignore_content.append(f"{f} {test}") - ignore_file = ignore_dir / f"ignore-{version}.txt" - ignore_file.write_text("\n".join(sorted(set(ignore_content))).lstrip("\n")) +# Adds validate-modules:return-syntax-error +RETURN_SYNTAX_SKIPS = [ + "plugins/modules/k8s.py", + "plugins/modules/k8s_scale.py", + "plugins/modules/k8s_service.py", + "plugins/modules/k8s_taint.py", +] + +YAML_LINT_SKIPS = [ + "tests/unit/module_utils/fixtures/definitions.yml", + "tests/unit/module_utils/fixtures/deployments.yml", + "tests/unit/module_utils/fixtures/pods.yml", + "tests/integration/targets/helm/files/appversionless-chart-v2/templates/configmap.yaml", + "tests/integration/targets/helm/files/appversionless-chart/templates/configmap.yaml", + "tests/integration/targets/helm/files/test-chart-v2/templates/configmap.yaml", + "tests/integration/targets/helm/files/test-chart/templates/configmap.yaml", + "tests/integration/targets/k8s_scale/files/deployment.yaml", +] + +# Add shebang!skip +SHEBANG_SKIPS = [ + "tests/sanity/refresh_ignore_files", +] + + +def import_skips(*versions): + for f in IMPORT_SKIPS: + for v in versions: + yield f"{f} import-{v}!skip" + +def param_type_skips(): + for f in PARAM_TYPE_SKIPS: + yield f"{f} validate-modules:parameter-type-not-in-doc" + + +def return_syntax_skips(ansible_version): + if ansible_version != "2.9": + for f in RETURN_SYNTAX_SKIPS: + yield f"{f} validate-modules:return-syntax-error" + else: + yield + + +def yaml_lint_skips(): + for f in YAML_LINT_SKIPS: + yield f"{f} yamllint!skip" + + +def shebang_skips(): + for f in SHEBANG_SKIPS: + yield f"{f} shebang!skip" + + +def import_boilerplate(path, ansible_version): + if ansible_version in ("2.9", "2.10", "2.11"): + for f in (p for p in path.glob("**/*.py") if not p.is_symlink()): + yield f"{f} future-import-boilerplate!skip" + else: + yield + + +def metaclass_boilerplate(path, ansible_version): + if ansible_version in ("2.9", "2.10", "2.11"): + for f in (p for p in path.glob("**/*.py") if not p.is_symlink()): + yield f"{f} metaclass-boilerplate!skip" + else: + yield + + +def main(): + target_dir = Path('.') + sanity_dir = target_dir / "tests" / "sanity" + plugins = target_dir / "plugins" + units = target_dir / "tests" / "unit" + + for ansible, python in ANSIBLE_VERSIONS.items(): + with open(sanity_dir / f"ignore-{ansible}.txt", "w") as fp: + ignores = itertools.chain( + import_skips(*python), + param_type_skips(), + yaml_lint_skips(), + shebang_skips(), + return_syntax_skips(ansible), + import_boilerplate(plugins, ansible), + import_boilerplate(units, ansible), + metaclass_boilerplate(plugins, ansible), + metaclass_boilerplate(units, ansible)) + for f in filter(None, ignores): + fp.write(f + "\n") + + +if __name__ == "__main__": + main() diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py new file mode 100644 index 00000000..20615adb --- /dev/null +++ b/tests/unit/conftest.py @@ -0,0 +1,44 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import json +import sys +from io import BytesIO + +import pytest + +import ansible.module_utils.basic +from ansible.module_utils.six import string_types +from ansible.module_utils._text import to_bytes +from ansible.module_utils.common._collections_compat import MutableMapping + + +@pytest.fixture +def stdin(mocker, request): + old_args = ansible.module_utils.basic._ANSIBLE_ARGS + ansible.module_utils.basic._ANSIBLE_ARGS = None + old_argv = sys.argv + sys.argv = ["ansible_unittest"] + + if isinstance(request.param, string_types): + args = request.param + elif isinstance(request.param, MutableMapping): + if "ANSIBLE_MODULE_ARGS" not in request.param: + request.param = {"ANSIBLE_MODULE_ARGS": request.param} + if "_ansible_remote_tmp" not in request.param["ANSIBLE_MODULE_ARGS"]: + request.param["ANSIBLE_MODULE_ARGS"]["_ansible_remote_tmp"] = "/tmp" + if "_ansible_keep_remote_files" not in request.param["ANSIBLE_MODULE_ARGS"]: + request.param["ANSIBLE_MODULE_ARGS"]["_ansible_keep_remote_files"] = False + args = json.dumps(request.param) + else: + raise Exception("Malformed data to the stdin pytest fixture") + + fake_stdin = BytesIO(to_bytes(args, errors="surrogate_or_strict")) + mocker.patch("ansible.module_utils.basic.sys.stdin", mocker.MagicMock()) + mocker.patch("ansible.module_utils.basic.sys.stdin.buffer", fake_stdin) + + yield fake_stdin + + ansible.module_utils.basic._ANSIBLE_ARGS = old_args + sys.argv = old_argv diff --git a/tests/unit/module_utils/fixtures/definitions.yml b/tests/unit/module_utils/fixtures/definitions.yml new file mode 100644 index 00000000..a8f6de81 --- /dev/null +++ b/tests/unit/module_utils/fixtures/definitions.yml @@ -0,0 +1,34 @@ +--- +kind: Namespace +apiVersion: v1 +metadata: + name: test-1 +--- +kind: Pod +apiVersion: v1 +metadata: + name: pod-1 + namespace: test-1 +spec: + containers: + - image: busybox + name: busybox +--- +kind: PodList +apiVersion: v1 +metadata: {} +items: + - kind: Pod + apiVersion: v1 + metadata: + name: pod-1 + namespace: test-1 + spec: + containers: + - image: busybox + name: busybox +--- +kind: ConfigMapList +apiVersion: v1 +metadata: {} +items: [] diff --git a/tests/unit/module_utils/fixtures/deployments.yml b/tests/unit/module_utils/fixtures/deployments.yml new file mode 100644 index 00000000..530035fc --- /dev/null +++ b/tests/unit/module_utils/fixtures/deployments.yml @@ -0,0 +1,48 @@ +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: deploy-1 + namespace: test-1 + generation: 1 +spec: + replicas: 2 + selector: + matchLabels: + app: foo + template: + metadata: + labels: + app: foo + spec: + containers: + - image: busybox + name: busybox +status: + availableReplicas: 2 + replicas: 2 + observedGeneration: 1 +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: deploy-2 + namespace: test-1 + generation: 1 +spec: + replicas: 2 + selector: + matchLabels: + app: foo + template: + metadata: + labels: + app: foo + spec: + containers: + - image: busybox + name: busybox +status: + availableReplicas: 1 + replicas: 2 + observedGeneration: 1 diff --git a/tests/unit/module_utils/fixtures/pods.yml b/tests/unit/module_utils/fixtures/pods.yml new file mode 100644 index 00000000..250354e2 --- /dev/null +++ b/tests/unit/module_utils/fixtures/pods.yml @@ -0,0 +1,63 @@ +--- +kind: Pod +apiVersion: v1 +metadata: + namespace: test-1 + name: pod-1 +spec: + containers: + - image: busybox + name: busybox +status: + containerStatuses: + - name: busybox + ready: true + conditions: + - type: "www.example.com/gate" + status: "True" +--- +kind: Pod +apiVersion: v1 +metadata: + namespace: test-1 + name: pod-2 +spec: + containers: + - image: busybox + name: busybox +--- +kind: Pod +apiVersion: v1 +metadata: + namespace: test-1 + name: pod-3 +spec: + containers: + - image: busybox + name: busybox +status: + phase: Pending + conditions: + - type: "www.example.com/gate" + status: "Unknown" + containerStatuses: + - name: busybox + ready: true +--- +kind: Pod +apiVersion: v1 +metadata: + namespace: test-1 + name: pod-4 +spec: + containers: + - image: busybox + name: busybox +status: + phase: Pending + conditions: + - type: "www.example.com/other" + status: "Unknown" + containerStatuses: + - name: busybox + ready: true diff --git a/tests/unit/module_utils/test_client.py b/tests/unit/module_utils/test_client.py new file mode 100644 index 00000000..8265b768 --- /dev/null +++ b/tests/unit/module_utils/test_client.py @@ -0,0 +1,162 @@ +import os +import base64 +import tempfile +import yaml +import mock +from mock import MagicMock + +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.client import ( + _create_auth_spec, + _create_configuration, +) + +TEST_HOST = "test-host" +TEST_SSL_HOST = "https://test-host" +TEST_CLIENT_CERT = "/dev/null" +TEST_CLIENT_KEY = "/dev/null" +TEST_CERTIFICATE_AUTH = "/dev/null" +TEST_DATA = "test-data" +TEST_BEARER_TOKEN = "Bearer %s" % base64.standard_b64encode(TEST_DATA.encode()).decode() +TEST_KUBE_CONFIG = { + "current-context": "federal-context", + "contexts": [ + { + "name": "simple_token", + "context": {"cluster": "default", "user": "simple_token"}, + } + ], + "clusters": [{"name": "default", "cluster": {"server": TEST_HOST}}], + "users": [ + { + "name": "ssl-no_file", + "user": { + "token": TEST_BEARER_TOKEN, + "client-certificate": TEST_CLIENT_CERT, + "client-key": TEST_CLIENT_KEY, + }, + } + ], +} + +_temp_files = [] + + +def _remove_temp_file(): + for f in _temp_files: + try: + os.remove(f) + except FileNotFoundError: + pass + + +def _create_temp_file(content=""): + handler, name = tempfile.mkstemp() + _temp_files.append(name) + os.write(handler, str.encode(content)) + os.close(handler) + return name + + +def test_create_auth_spec_ssl_no_options(): + module = MagicMock() + module.params = {} + actual_auth_spec = _create_auth_spec(module) + + assert "proxy_headers" in actual_auth_spec + + +def test_create_auth_spec_ssl_options(): + ssl_options = { + "host": TEST_SSL_HOST, + "token": TEST_BEARER_TOKEN, + "client_cert": TEST_CLIENT_CERT, + "client_key": TEST_CLIENT_KEY, + "ca_cert": TEST_CERTIFICATE_AUTH, + "validate_certs": True, + } + expected_auth_spec = { + "host": TEST_SSL_HOST, + "cert_file": TEST_CLIENT_CERT, + "key_file": TEST_CLIENT_KEY, + "ssl_ca_cert": TEST_CERTIFICATE_AUTH, + "verify_ssl": True, + "proxy_headers": {}, + } + + module = MagicMock() + module.params = ssl_options + actual_auth_spec = _create_auth_spec(module) + + assert expected_auth_spec.items() <= actual_auth_spec.items() + + +def test_create_auth_spec_ssl_options_no_verify(): + ssl_options = { + "host": TEST_SSL_HOST, + "token": TEST_BEARER_TOKEN, + "client_cert": TEST_CLIENT_CERT, + "client_key": TEST_CLIENT_KEY, + "validate_certs": False, + } + + expected_auth_spec = { + "host": TEST_SSL_HOST, + "cert_file": TEST_CLIENT_CERT, + "key_file": TEST_CLIENT_KEY, + "verify_ssl": False, + "proxy_headers": {}, + } + + module = MagicMock() + module.params = ssl_options + actual_auth_spec = _create_auth_spec(module) + + assert expected_auth_spec.items() <= actual_auth_spec.items() + + +@mock.patch.dict(os.environ, {"K8S_AUTH_PROXY_HEADERS_PROXY_BASIC_AUTH": "foo:bar"}) +@mock.patch.dict(os.environ, {"K8S_AUTH_PROXY_HEADERS_USER_AGENT": "foo/1.0"}) +@mock.patch.dict(os.environ, {"K8S_AUTH_CERT_FILE": TEST_CLIENT_CERT}) +def test_create_auth_spec_ssl_proxy(): + expected_auth_spec = { + "kubeconfig": "~/.kube/customconfig", + "verify_ssl": True, + "cert_file": TEST_CLIENT_CERT, + "proxy_headers": {"proxy_basic_auth": "foo:bar", "user_agent": "foo/1.0"}, + } + module = MagicMock() + options = {"validate_certs": True, "kubeconfig": "~/.kube/customconfig"} + + module.params = options + actual_auth_spec = _create_auth_spec(module) + + assert expected_auth_spec.items() <= actual_auth_spec.items() + + +def test_load_kube_config_from_file_path(): + config_file = _create_temp_file(yaml.safe_dump(TEST_KUBE_CONFIG)) + auth = {"kubeconfig": config_file, "context": "simple_token"} + actual_configuration = _create_configuration(auth) + + expected_configuration = { + "host": TEST_HOST, + "kubeconfig": config_file, + "context": "simple_token", + } + + assert expected_configuration.items() <= actual_configuration.__dict__.items() + _remove_temp_file() + + +def test_load_kube_config_from_dict(): + auth_spec = {"kubeconfig": TEST_KUBE_CONFIG, "context": "simple_token"} + actual_configuration = _create_configuration(auth_spec) + + expected_configuration = { + "host": TEST_HOST, + "kubeconfig": TEST_KUBE_CONFIG, + "context": "simple_token", + } + + assert expected_configuration.items() <= actual_configuration.__dict__.items() + _remove_temp_file() diff --git a/tests/unit/module_utils/test_core.py b/tests/unit/module_utils/test_core.py new file mode 100644 index 00000000..189f1f95 --- /dev/null +++ b/tests/unit/module_utils/test_core.py @@ -0,0 +1,91 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import json + +import kubernetes +import pytest + +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.core import ( + AnsibleK8SModule, +) + +MINIMAL_K8S_VERSION = "12.0.0" +UNSUPPORTED_K8S_VERSION = "11.0.0" + + +@pytest.mark.parametrize("stdin", [{}], indirect=["stdin"]) +def test_no_warn(monkeypatch, stdin, capfd): + monkeypatch.setattr(kubernetes, "__version__", MINIMAL_K8S_VERSION) + + module = AnsibleK8SModule(argument_spec={}) + with pytest.raises(SystemExit): + module.exit_json() + out, err = capfd.readouterr() + + return_value = json.loads(out) + + assert return_value.get("exception") is None + assert return_value.get("warnings") is None + assert return_value.get("failed") is None + + +@pytest.mark.parametrize("stdin", [{}], indirect=["stdin"]) +def test_warn_on_k8s_version(monkeypatch, stdin, capfd): + monkeypatch.setattr(kubernetes, "__version__", UNSUPPORTED_K8S_VERSION) + + module = AnsibleK8SModule(argument_spec={}) + with pytest.raises(SystemExit): + module.exit_json() + out, err = capfd.readouterr() + + return_value = json.loads(out) + + assert return_value.get("warnings") is not None + warnings = return_value["warnings"] + assert len(warnings) == 1 + assert "kubernetes" in warnings[0] + assert MINIMAL_K8S_VERSION in warnings[0] + + +dependencies = [ + ["18.20.0", "12.0.1", False], + ["18.20.0", "18.20.0", True], + ["12.0.1", "18.20.0", True], +] + + +@pytest.mark.parametrize( + "stdin,desired,actual,result", [({}, *d) for d in dependencies], indirect=["stdin"] +) +def test_has_at_least(monkeypatch, stdin, desired, actual, result, capfd): + monkeypatch.setattr(kubernetes, "__version__", actual) + + module = AnsibleK8SModule(argument_spec={}) + + assert module.has_at_least("kubernetes", desired) is result + + +dependencies = [ + ["kubernetes", "18.20.0", "(kubernetes>=18.20.0)"], + ["foobar", "1.0.0", "(foobar>=1.0.0)"], + ["foobar", None, "(foobar)"], +] + + +@pytest.mark.parametrize( + "stdin,dependency,version,msg", [({}, *d) for d in dependencies], indirect=["stdin"] +) +def test_requires_fails_with_message( + monkeypatch, stdin, dependency, version, msg, capfd +): + monkeypatch.setattr(kubernetes, "__version__", "12.0.0") + module = AnsibleK8SModule(argument_spec={}) + with pytest.raises(SystemExit): + module.requires(dependency, version) + out, err = capfd.readouterr() + return_value = json.loads(out) + + assert return_value.get("failed") + assert msg in return_value.get("msg") diff --git a/tests/unit/module_utils/test_resource.py b/tests/unit/module_utils/test_resource.py new file mode 100644 index 00000000..c27c01a8 --- /dev/null +++ b/tests/unit/module_utils/test_resource.py @@ -0,0 +1,187 @@ +import os +from pathlib import Path + +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.resource import ( + create_definitions, + flatten_list_kind, + from_file, + from_yaml, + merge_params, +) + + +def test_create_definitions_loads_from_definition(): + params = { + "resource_definition": { + "kind": "Pod", + "apiVersion": "v1", + "metadata": {"name": "foo", "namespace": "bar"}, + } + } + results = create_definitions(params) + assert len(results) == 1 + assert results[0].kind == "Pod" + assert results[0].api_version == "v1" + assert results[0].name == "foo" + assert results[0].namespace == "bar" + + +def test_create_definitions_loads_from_file(): + current = Path(os.path.dirname(os.path.abspath(__file__))) + params = {"src": current / "fixtures/definitions.yml"} + results = create_definitions(params) + assert len(results) == 3 + assert results[0].kind == "Namespace" + assert results[1].kind == "Pod" + + +def test_create_definitions_loads_from_params(): + params = { + "kind": "Pod", + "api_version": "v1", + "name": "foo", + "namespace": "foobar", + } + results = create_definitions(params) + assert len(results) == 1 + assert results[0] == { + "kind": "Pod", + "apiVersion": "v1", + "metadata": {"name": "foo", "namespace": "foobar"}, + } + + +def test_create_definitions_loads_list_kind(): + params = { + "resource_definition": { + "kind": "PodList", + "apiVersion": "v1", + "items": [ + {"kind": "Pod", "metadata": {"name": "foo"}}, + {"kind": "Pod", "metadata": {"name": "bar"}}, + ], + } + } + results = create_definitions(params) + assert len(results) == 2 + assert results[0].name == "foo" + assert results[1].name == "bar" + + +def test_merge_params_does_not_overwrite(): + definition = { + "kind": "Pod", + "apiVersion": "v1", + "metadata": {"name": "foo", "namespace": "bar"}, + } + params = { + "kind": "Service", + "api_version": "v2", + "name": "baz", + "namespace": "gaz", + } + result = merge_params(definition, params) + assert result == definition + + +def test_merge_params_adds_module_params(): + params = { + "kind": "Pod", + "api_version": "v1", + "namespace": "bar", + "generate_name": "foo-", + } + result = merge_params({}, params) + assert result == { + "kind": "Pod", + "apiVersion": "v1", + "metadata": {"generateName": "foo-", "namespace": "bar"}, + } + + +def test_from_yaml_loads_string_docs(): + definition = """ +kind: Pod +apiVersion: v1 +metadata: + name: foo + namespace: bar +--- +kind: ConfigMap +apiVersion: v1 +metadata: + name: foo + namespace: bar +""" + result = list(from_yaml(definition)) + assert result[0]["kind"] == "Pod" + assert result[1]["kind"] == "ConfigMap" + + +def test_from_yaml_loads_list(): + definition = [ + """ + kind: Pod + apiVersion: v1 + metadata: + name: foo + namespace: bar + """, + """ + kind: ConfigMap + apiVersion: v1 + metadata: + name: foo + namespace: bar + """, + { + "kind": "Pod", + "apiVersion": "v1", + "metadata": {"name": "baz", "namespace": "bar"}, + }, + ] + result = list(from_yaml(definition)) + assert len(result) == 3 + assert result[0]["kind"] == "Pod" + assert result[1]["kind"] == "ConfigMap" + assert result[2]["metadata"]["name"] == "baz" + + +def test_from_yaml_loads_dictionary(): + definition = { + "kind": "Pod", + "apiVersion": "v1", + "metadata": {"name": "foo", "namespace": "bar"}, + } + result = list(from_yaml(definition)) + assert result[0]["kind"] == "Pod" + + +def test_from_file_loads_definitions(): + current = Path(os.path.dirname(os.path.abspath(__file__))) + result = list(from_file(current / "fixtures/definitions.yml")) + assert result[0]["kind"] == "Namespace" + assert result[1]["kind"] == "Pod" + + +def test_flatten_list_kind_flattens(): + definition = { + "kind": "PodList", + "apiVersion": "v1", + "items": [ + {"kind": "Pod", "metadata": {"name": "foo"}}, + {"kind": "Pod", "metadata": {"name": "bar"}}, + ], + } + result = flatten_list_kind(definition, {"namespace": "foobar"}) + assert len(result) == 2 + + assert result[0]["kind"] == "Pod" + assert result[0]["apiVersion"] == "v1" + assert result[0]["metadata"]["name"] == "foo" + assert result[0]["metadata"]["namespace"] == "foobar" + + assert result[1]["kind"] == "Pod" + assert result[1]["apiVersion"] == "v1" + assert result[1]["metadata"]["name"] == "bar" + assert result[1]["metadata"]["namespace"] == "foobar" diff --git a/tests/unit/module_utils/test_runner.py b/tests/unit/module_utils/test_runner.py new file mode 100644 index 00000000..45c6f29a --- /dev/null +++ b/tests/unit/module_utils/test_runner.py @@ -0,0 +1,135 @@ +import pytest +from copy import deepcopy +from unittest.mock import Mock + +from kubernetes.dynamic.resource import ResourceInstance + +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.runner import ( + perform_action, +) + +definition = { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "foo", + "labels": {"environment": "production", "app": "nginx"}, + "namespace": "foo", + }, + "spec": { + "containers": [ + { + "name": "nginx", + "image": "nginx:1.14.2", + "command": ["/bin/sh", "-c", "sleep 10"], + } + ] + }, +} + +modified_def = deepcopy(definition) +modified_def["metadata"]["labels"]["environment"] = "testing" + + +@pytest.mark.parametrize( + "action, params, existing, instance, expected", + [ + ( + "delete", + {"state": "absent"}, + {}, + {}, + {"changed": False, "method": "delete", "result": {}}, + ), + ( + "delete", + {"state": "absent"}, + definition, + {"kind": "Status"}, + {"changed": True, "method": "delete", "result": {"kind": "Status"}}, + ), + ( + "apply", + {"apply": "yes"}, + {}, + definition, + {"changed": True, "method": "apply", "result": definition}, + ), + ( + "create", + {"state": "patched"}, + {}, + {}, + { + "changed": False, + "result": {}, + "warnings": [ + "resource 'kind=Pod,name=foo' was not found but will not be created as 'state' parameter has been set to 'patched'" + ], + }, + ), + ( + "create", + {}, + {}, + definition, + {"changed": True, "method": "create", "result": definition}, + ), + ( + "replace", + {"force": "yes"}, + definition, + definition, + {"changed": False, "method": "replace", "result": definition}, + ), + ( + "replace", + {"force": "yes"}, + definition, + modified_def, + {"changed": True, "method": "replace", "result": modified_def}, + ), + ( + "update", + {}, + definition, + definition, + {"changed": False, "method": "update", "result": definition}, + ), + ( + "update", + {}, + definition, + modified_def, + {"changed": True, "method": "update", "result": modified_def}, + ), + ( + "create", + {"label_selectors": ["app=foo"]}, + {}, + definition, + { + "changed": False, + "msg": "resource 'kind=Pod,name=foo,namespace=foo' filtered by label_selectors.", + }, + ), + ( + "create", + {"label_selectors": ["app=nginx"]}, + {}, + definition, + {"changed": True, "method": "create", "result": definition}, + ), + ], +) +def test_perform_action(action, params, existing, instance, expected): + svc = Mock() + svc.find_resource.return_value = Mock( + kind=definition["kind"], group_version=definition["apiVersion"] + ) + svc.retrieve.return_value = ResourceInstance(None, existing) if existing else None + spec = {action + ".return_value": instance} + svc.configure_mock(**spec) + + result = perform_action(svc, definition, params) + assert expected.items() <= result.items() diff --git a/tests/unit/module_utils/test_service.py b/tests/unit/module_utils/test_service.py new file mode 100644 index 00000000..a1822de6 --- /dev/null +++ b/tests/unit/module_utils/test_service.py @@ -0,0 +1,292 @@ +from unittest.mock import Mock + +import pytest +from kubernetes.dynamic.resource import ResourceInstance, Resource + +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.service import ( + K8sService, + diff_objects, +) + +from kubernetes.dynamic.exceptions import NotFoundError + +pod_definition = { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "foo", + "labels": {"environment": "production", "app": "nginx"}, + "namespace": "foo", + }, + "spec": { + "containers": [ + { + "name": "nginx", + "image": "nginx:1.14.2", + "command": ["/bin/sh", "-c", "sleep 10"], + } + ] + }, +} + +pod_definition_updated = { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "foo", + "labels": {"environment": "testing", "app": "nginx"}, + "namespace": "bar", + }, + "spec": { + "containers": [ + { + "name": "nginx", + "image": "nginx:1.14.2", + "command": ["/bin/sh", "-c", "sleep 10"], + } + ] + }, +} + + +@pytest.fixture(scope="module") +def mock_pod_resource_instance(): + return ResourceInstance(None, pod_definition) + + +@pytest.fixture(scope="module") +def mock_pod_updated_resource_instance(): + return ResourceInstance(None, pod_definition_updated) + + +def test_diff_objects_no_diff(): + match, diff = diff_objects(pod_definition, pod_definition) + + assert match is True + assert diff == {} + + +def test_diff_objects_meta_diff(): + match, diff = diff_objects(pod_definition, pod_definition_updated) + + assert match is False + assert diff["before"] == { + "metadata": {"labels": {"environment": "production"}, "namespace": "foo"} + } + assert diff["after"] == { + "metadata": {"labels": {"environment": "testing"}, "namespace": "bar"} + } + + +def test_diff_objects_spec_diff(): + pod_definition_updated = { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "foo", + "labels": {"environment": "production", "app": "nginx"}, + "namespace": "foo", + }, + "spec": { + "containers": [ + { + "name": "busybox", + "image": "busybox", + "command": ["/bin/sh", "-c", "sleep 3600"], + } + ] + }, + } + match, diff = diff_objects(pod_definition, pod_definition_updated) + + assert match is False + assert diff["before"]["spec"] == pod_definition["spec"] + assert diff["after"]["spec"] == pod_definition_updated["spec"] + + +def test_find_resource(): + mock_pod_resource = Resource( + api_version="v1", kind="Pod", namespaced=False, preferred=True, prefix="api" + ) + spec = {"resource.return_value": mock_pod_resource} + client = Mock(**spec) + svc = K8sService(client, Mock()) + resource = svc.find_resource("Pod", "v1") + + assert isinstance(resource, Resource) + assert resource.to_dict().items() <= mock_pod_resource.to_dict().items() + + +def test_service_delete_existing_resource(mock_pod_resource_instance): + spec = {"delete.side_effect": [mock_pod_resource_instance]} + client = Mock(**spec) + module = Mock( + params={"delete_options": {"gracePeriodSeconds": 2}}, check_mode=False + ) + resource = Mock() + svc = K8sService(client, module) + result = svc.delete(resource, pod_definition, mock_pod_resource_instance) + + assert isinstance(result, dict) + assert result == mock_pod_resource_instance.to_dict() + client.delete.assert_called_with( + resource, + name=pod_definition["metadata"]["name"], + namespace=pod_definition["metadata"]["namespace"], + body={"apiVersion": "v1", "kind": "DeleteOptions", "gracePeriodSeconds": 2}, + ) + + +def test_service_delete_no_existing_resource(): + module = Mock() + module.params = {} + module.check_mode = False + client = Mock() + client.delete.return_value = mock_pod_resource_instance + svc = K8sService(client, module) + result = svc.delete(Mock(), pod_definition) + + assert result == {} + client.delete.assert_not_called() + + +def test_service_delete_existing_resource_check_mode(mock_pod_resource_instance): + module = Mock(params={}, check_mode=True) + client = Mock(dry_run=False) + client.delete.return_value = mock_pod_resource_instance + svc = K8sService(client, module) + result = svc.delete(Mock(), pod_definition, mock_pod_resource_instance) + + assert result == {} + client.delete.assert_not_called() + + +def test_service_create_resource(mock_pod_resource_instance): + spec = {"create.side_effect": [mock_pod_resource_instance]} + client = Mock(**spec) + module = Mock() + module.params = {} + module.check_mode = False + svc = K8sService(client, module) + result = svc.create(Mock(), pod_definition) + + assert result == mock_pod_resource_instance.to_dict() + + +def test_service_create_resource_check_mode(): + client = Mock(dry_run=False) + client.create.return_value = mock_pod_resource_instance + module = Mock(params={}, check_mode=True) + svc = K8sService(client, module) + result = svc.create(Mock(), pod_definition) + + assert result == pod_definition + client.create.assert_not_called() + + +def test_service_retrieve_existing_resource(mock_pod_resource_instance): + spec = {"get.side_effect": [mock_pod_resource_instance]} + client = Mock(**spec) + module = Mock() + module.params = {} + svc = K8sService(client, module) + results = svc.retrieve(Mock(), pod_definition) + + assert isinstance(results, ResourceInstance) + assert results.to_dict() == pod_definition + + +def test_service_retrieve_no_existing_resource(): + spec = {"get.side_effect": [NotFoundError(Mock())]} + client = Mock(**spec) + module = Mock() + module.params = {} + svc = K8sService(client, module) + results = svc.retrieve(Mock(), pod_definition) + + assert results is None + + +def test_create_project_request(): + project_definition = { + "apiVersion": "v1", + "kind": "ProjectRequest", + "metadata": {"name": "test"}, + } + spec = {"create.side_effect": [ResourceInstance(None, project_definition)]} + client = Mock(**spec) + module = Mock() + module.check_mode = False + module.params = {"state": "present"} + svc = K8sService(client, module) + results = svc.create_project_request(project_definition) + + assert isinstance(results, dict) + assert results["changed"] is True + assert results["result"] == project_definition + + +def test_service_apply_existing_resource(mock_pod_resource_instance): + spec = {"apply.side_effect": [mock_pod_resource_instance]} + client = Mock(**spec) + module = Mock() + module.params = {"apply": True} + module.check_mode = False + svc = K8sService(client, module) + result = svc.apply(Mock(), pod_definition_updated, mock_pod_resource_instance) + + assert result == mock_pod_resource_instance.to_dict() + + +def test_service_replace_existing_resource(mock_pod_resource_instance): + spec = {"replace.side_effect": [mock_pod_resource_instance]} + client = Mock(**spec) + module = Mock() + module.params = {} + module.check_mode = False + svc = K8sService(client, module) + result = svc.replace(Mock(), pod_definition, mock_pod_resource_instance) + + assert result == mock_pod_resource_instance.to_dict() + + +def test_service_update_existing_resource(mock_pod_resource_instance): + spec = {"replace.side_effect": [mock_pod_resource_instance]} + client = Mock(**spec) + module = Mock() + module.params = {} + module.check_mode = False + svc = K8sService(client, module) + result = svc.replace(Mock(), pod_definition, mock_pod_resource_instance) + + assert result == mock_pod_resource_instance.to_dict() + + +def test_service_find(mock_pod_resource_instance): + spec = {"get.side_effect": [mock_pod_resource_instance]} + client = Mock(**spec) + module = Mock() + module.params = {} + module.check_mode = False + svc = K8sService(client, module) + results = svc.find("Pod", "v1", name="foo", namespace="foo") + + assert isinstance(results, dict) + assert results["api_found"] is True + assert results["resources"] is not [] + assert len(results["resources"]) == 1 + assert results["resources"][0] == pod_definition + + +def test_service_find_error(): + spec = {"get.side_effect": [NotFoundError(Mock())]} + client = Mock(**spec) + module = Mock() + module.params = {} + module.check_mode = False + svc = K8sService(client, module) + results = svc.find("Pod", "v1", name="foo", namespace="foo") + + assert isinstance(results, dict) + assert results["api_found"] is True + assert results["resources"] == [] diff --git a/tests/unit/module_utils/test_waiter.py b/tests/unit/module_utils/test_waiter.py new file mode 100644 index 00000000..b5ce10a5 --- /dev/null +++ b/tests/unit/module_utils/test_waiter.py @@ -0,0 +1,122 @@ +import os +import time +from pathlib import Path +from unittest.mock import Mock + +import pytest +import yaml +from kubernetes.dynamic.resource import ResourceInstance +from kubernetes.dynamic.exceptions import NotFoundError + +from ansible_collections.kubernetes.core.plugins.module_utils.k8s.waiter import ( + clock, + custom_condition, + deployment_ready, + DummyWaiter, + exists, + get_waiter, + pod_ready, + resource_absent, + Waiter, +) + + +def resources(filepath): + current = Path(os.path.dirname(os.path.abspath(__file__))) + with open(current / filepath) as fp: + return [ResourceInstance(None, d) for d in yaml.safe_load_all(fp)] + + +RESOURCES = resources("fixtures/definitions.yml") +PODS = resources("fixtures/pods.yml") +DEPLOYMENTS = resources("fixtures/deployments.yml") + + +def test_clock_times_out(): + start = time.monotonic() + for x in clock(5, 1): + pass + elapsed = int(time.monotonic() - start) + assert x == 5 + assert 5 <= elapsed <= 6 + + +@pytest.mark.parametrize( + "resource,expected", + zip(RESOURCES + [None, {}], [True, True, True, False, False, False]), +) +def test_exists_and_absent_checks_for_existence(resource, expected): + assert exists(resource) is expected + assert resource_absent(resource) is not expected + + +@pytest.mark.parametrize("pod,expected", zip(PODS, [True, False, True, True])) +def test_pod_ready_checks_readiness(pod, expected): + assert pod_ready(pod) is expected + + +@pytest.mark.parametrize("pod,expected", zip(PODS, [True, False, False, False])) +def test_custom_condition_checks_readiness(pod, expected): + condition = {"type": "www.example.com/gate", "status": "True"} + assert custom_condition(condition, pod) is expected + + +@pytest.mark.parametrize("deployment,expected", zip(DEPLOYMENTS, [True, False])) +def test_deployment_ready_checks_readiness(deployment, expected): + assert deployment_ready(deployment) is expected + + +def test_dummywaiter_returns_resource_immediately(): + resource = { + "kind": "Pod", + "apiVersion": "v1", + "metadata": {"name": "foopod", "namespace": "foobar"}, + } + result, instance, elapsed = DummyWaiter().wait(resource, 10, 100) + assert result is True + assert instance == resource + assert elapsed == 0 + + +def test_waiter_waits_for_missing_resource(): + spec = {"get.side_effect": NotFoundError(Mock())} + client = Mock(**spec) + resource = Mock() + result, instance, elapsed = Waiter(client, resource, exists).wait( + timeout=3, + sleep=1, + name=RESOURCES[0]["metadata"].get("name"), + namespace=RESOURCES[0]["metadata"].get("namespace"), + ) + assert result is False + assert instance == {} + assert abs(elapsed - 3) <= 1 + + +@pytest.mark.parametrize("resource,expected", zip(RESOURCES, [True, True, True, False])) +def test_waiter_waits_for_resource_to_exist(resource, expected): + result = resource.to_dict() + spec = {"get.side_effect": [NotFoundError(Mock()), resource, resource, resource]} + client = Mock(**spec) + success, instance, elapsed = Waiter(client, Mock(), exists).wait( + timeout=3, + sleep=1, + name=result["metadata"].get("name"), + namespace=result["metadata"].get("namespace"), + ) + assert success is expected + assert instance == result + assert abs(elapsed - 2) <= 1 + + +def test_get_waiter_returns_correct_waiter(): + assert get_waiter(Mock(), PODS[0]).predicate == pod_ready + waiter = get_waiter(Mock(), PODS[0], check_mode=True) + assert isinstance(waiter, DummyWaiter) + assert get_waiter(Mock(), PODS[0], state="absent").predicate == resource_absent + assert ( + get_waiter( + Mock(), PODS[0], condition={"type": "Ready", "status": "True"} + ).predicate.func + == custom_condition + )