From 2a682f5ea32f6e37e778040032aff9332aac1a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= Date: Thu, 20 Jun 2019 12:52:45 +0100 Subject: [PATCH] Provide the missing features required for Yomi (Yet one more installer) --- salt/grains/core.py | 6 +- salt/modules/kubeadm.py | 91 +++++++++------ salt/modules/rpm_lowpkg.py | 42 +++---- salt/modules/systemd_service.py | 24 ++-- salt/modules/zypperpkg.py | 87 ++++++++------ salt/states/btrfs.py | 44 +++++-- salt/states/file.py | 7 +- salt/states/loop.py | 15 +-- salt/states/pkgrepo.py | 5 - salt/utils/oset.py | 8 +- tests/unit/modules/test_kubeadm.py | 43 ++++--- tests/unit/modules/test_rpm_lowpkg.py | 15 ++- tests/unit/modules/test_systemd_service.py | 13 +-- tests/unit/modules/test_zypperpkg.py | 60 ++-------- tests/unit/states/test_btrfs.py | 130 ++++++--------------- tests/unit/states/test_pkg.py | 39 ++----- tests/unit/test_loader.py | 97 ++++++++++++++- 17 files changed, 373 insertions(+), 353 deletions(-) diff --git a/salt/grains/core.py b/salt/grains/core.py index bebb4581bc..d7d03c5e70 100644 --- a/salt/grains/core.py +++ b/salt/grains/core.py @@ -2759,7 +2759,7 @@ def _hw_data(osdata): contents_file = os.path.join("/sys/class/dmi/id", fw_file) if os.path.exists(contents_file): try: - with salt.utils.files.fopen(contents_file, "rb") as ifile: + with salt.utils.files.fopen(contents_file, "r") as ifile: grains[key] = salt.utils.stringutils.to_unicode( ifile.read().strip(), errors="replace" ) @@ -2768,9 +2768,7 @@ def _hw_data(osdata): except UnicodeDecodeError: # Some firmwares provide non-valid 'product_name' # files, ignore them - log.debug( - "The content in /sys/devices/virtual/dmi/id/product_name is not valid" - ) + pass except OSError as err: # PermissionError is new to Python 3, but corresponds to the EACESS and # EPERM error numbers. Use those instead here for PY2 compatibility. diff --git a/salt/modules/kubeadm.py b/salt/modules/kubeadm.py index 8baf5f85fd..966e9e848f 100644 --- a/salt/modules/kubeadm.py +++ b/salt/modules/kubeadm.py @@ -1,3 +1,25 @@ +# +# Author: Alberto Planas +# +# Copyright 2019 SUSE LINUX GmbH, Nuernberg, Germany. +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + """ :maintainer: Alberto Planas :maturity: new @@ -11,6 +33,7 @@ import re import salt.utils.files from salt.exceptions import CommandExecutionError +from salt.ext.six.moves import zip ADMIN_CFG = "/etc/kubernetes/admin.conf" @@ -37,23 +60,22 @@ def _api_server_endpoint(config=None): endpoint = re.search( r"^\s*server: https?://(.*)$", fp_.read(), re.MULTILINE ).group(1) - # pylint:disable=broad-except except Exception: # Any error or exception is mapped to None pass return endpoint -def _token(create_if_needed=False): +def _token(create_if_needed=True): """ Return a valid bootstrap token """ tokens = token_list() - if not tokens and create_if_needed: + if not tokens: token_create(description="Token created by kubeadm salt module") tokens = token_list() - # We expect that the token is valid for authentication and signing - return tokens[0]["token"] if tokens else None + # We expect that the token is valid for authestication and signing + return tokens[0]["token"] def _discovery_token_ca_cert_hash(): @@ -92,10 +114,6 @@ def join_params(create_if_needed=False): Return the parameters required for joining into the cluster - create_if_needed - If the token bucket is empty and this parameter is True, a new - token will be created. - CLI Example: .. code-block:: bash @@ -169,7 +187,7 @@ def token_create( Create bootstrap tokens on the server token - Token to write, if None one will be generated. The token must + Token to write, if None one will be gerenared. The token must match a regular expression, that by default is [a-z0-9]{6}.[a-z0-9]{16} @@ -180,7 +198,7 @@ def token_create( A human friendly description of how this token is used groups - List of extra groups that this token will authenticate, default + List of extra groups that this token will authenticate, defaut to ['system:bootstrappers:kubeadm:default-node-token'] ttl @@ -189,7 +207,7 @@ def token_create( is 24h0m0s usages - Describes the ways in which this token can be used. The default + Describes the ways in wich this token can be used. The default value is ['signing', 'authentication'] kubeconfig @@ -239,7 +257,7 @@ def token_delete(token, kubeconfig=None, rootfs=None): Delete bootstrap tokens on the server token - Token to write, if None one will be generated. The token must + Token to write, if None one will be gerenared. The token must match a regular expression, that by default is [a-z0-9]{6}.[a-z0-9]{16} @@ -328,21 +346,20 @@ def token_list(kubeconfig=None, rootfs=None): lines = _cmd(cmd).splitlines() + # Find the header and parse it. We do not need to validate the + # content, as the regex will take care of future changes. + header = lines.pop(0) + header = [i.lower() for i in re.findall(r"(\w+(?:\s\w+)*)", header)] + tokens = [] - if lines: - # Find the header and parse it. We do not need to validate - # the content, as the regex will take care of future changes. - header = lines.pop(0) - header = [i.lower() for i in re.findall(r"(\w+(?:\s\w+)*)", header)] - - for line in lines: - # TODO(aplanas): descriptions with multiple spaces can - # break the parser. - values = re.findall(r"(\S+(?:\s\S+)*)", line) - if len(header) != len(values): - log.error("Error parsing line: {}".format(line)) - continue - tokens.append({key: value for key, value in zip(header, values)}) + for line in lines: + # TODO(aplanas): descriptions with multiple spaces can break + # the parser. + values = re.findall(r"(\S+(?:\s\S+)*)", line) + if len(header) != len(values): + log.error("Error parsing line: {}".format(line)) + continue + tokens.append({key: value for key, value in zip(header, values)}) return tokens @@ -869,7 +886,7 @@ def config_upload_from_flags( flags apiserver_advertise_address - The IP address the API server will advertise it's listening on + The IP address the API server will adversite it's listening on apiserver_bind_port The port the API server is accessible on (default 6443) @@ -900,11 +917,11 @@ def config_upload_from_flags( Specify range of IP addresses for the pod network service_cidr - Use alternative range of IP address for service VIPs (default + Use alternative range of IP address dor service VIPs (default "10.96.0.0/12") service_dns_domain - Use alternative domain for services (default "cluster.local") + Use alternative domain for serivces (default "cluster.local") kubeconfig The kubeconfig file to use when talking to the cluster. The @@ -1004,7 +1021,7 @@ def init( Command to set up the Kubernetes control plane apiserver_advertise_address - The IP address the API server will advertise it's listening on + The IP address the API server will adversite it's listening on apiserver_bind_port The port the API server is accessible on (default 6443) @@ -1035,10 +1052,10 @@ def init( various features ignore_preflight_errors - A list of checks whose errors will be shown as warnings + A list of checkt whose errors will be shown as warnings image_repository - Choose a container registry to pull control plane images from + Choose a container registry to pull controll plane images from kubernetes_version Choose a specifig Kubernetes version for the control plane @@ -1051,11 +1068,11 @@ def init( Specify range of IP addresses for the pod network service_cidr - Use alternative range of IP address for service VIPs (default + Use alternative range of IP address dor service VIPs (default "10.96.0.0/12") service_dns_domain - Use alternative domain for services (default "cluster.local") + Use alternative domain for serivces (default "cluster.local") skip_certificate_key_print Don't print the key used to encrypt the control-plane @@ -1190,10 +1207,10 @@ def join( apiserver_advertise_address If the node should host a new control plane instance, the IP - address the API Server will advertise it's listening on + address the API Server will adversise it's listening on apiserver_bind_port - If the node should host a new control plane instance, the port + If the node shoult host a new control plane instance, the port the API Server to bind to (default 6443) certificate_key diff --git a/salt/modules/rpm_lowpkg.py b/salt/modules/rpm_lowpkg.py index 54b7014440..393b0f453a 100644 --- a/salt/modules/rpm_lowpkg.py +++ b/salt/modules/rpm_lowpkg.py @@ -1,17 +1,13 @@ -# -*- coding: utf-8 -*- """ Support for rpm """ -# Import python libs -from __future__ import absolute_import, print_function, unicode_literals import datetime import logging import os import re -# Import Salt libs import salt.utils.decorators.path import salt.utils.itertools import salt.utils.path @@ -105,14 +101,14 @@ def bin_pkg_info(path, saltenv="base"): newpath = __salt__["cp.cache_file"](path, saltenv) if not newpath: raise CommandExecutionError( - "Unable to retrieve {0} from saltenv '{1}'".format(path, saltenv) + "Unable to retrieve {} from saltenv '{}'".format(path, saltenv) ) path = newpath else: if not os.path.exists(path): - raise CommandExecutionError("{0} does not exist on minion".format(path)) + raise CommandExecutionError("{} does not exist on minion".format(path)) elif not os.path.isabs(path): - raise SaltInvocationError("{0} does not exist on minion".format(path)) + raise SaltInvocationError("{} does not exist on minion".format(path)) # REPOID is not a valid tag for the rpm command. Remove it and replace it # with 'none' @@ -187,28 +183,26 @@ def verify(*packages, **kwargs): ftypes = {"c": "config", "d": "doc", "g": "ghost", "l": "license", "r": "readme"} ret = {} ignore_types = kwargs.get("ignore_types", []) - if not isinstance(ignore_types, (list, six.string_types)): + if not isinstance(ignore_types, (list, (str,))): raise SaltInvocationError( "ignore_types must be a list or a comma-separated string" ) - if isinstance(ignore_types, six.string_types): + if isinstance(ignore_types, str): try: ignore_types = [x.strip() for x in ignore_types.split(",")] except AttributeError: - ignore_types = [x.strip() for x in six.text_type(ignore_types).split(",")] + ignore_types = [x.strip() for x in str(ignore_types).split(",")] verify_options = kwargs.get("verify_options", []) - if not isinstance(verify_options, (list, six.string_types)): + if not isinstance(verify_options, (list, (str,))): raise SaltInvocationError( "verify_options must be a list or a comma-separated string" ) - if isinstance(verify_options, six.string_types): + if isinstance(verify_options, str): try: verify_options = [x.strip() for x in verify_options.split(",")] except AttributeError: - verify_options = [ - x.strip() for x in six.text_type(verify_options).split(",") - ] + verify_options = [x.strip() for x in str(verify_options).split(",")] cmd = ["rpm"] if kwargs.get("root"): @@ -229,7 +223,7 @@ def verify(*packages, **kwargs): # succeeded, but if the retcode is nonzero, then the command failed. msg = "Failed to verify package(s)" if out["stderr"]: - msg += ": {0}".format(out["stderr"]) + msg += ": {}".format(out["stderr"]) raise CommandExecutionError(msg) for line in salt.utils.itertools.split(out["stdout"], "\n"): @@ -492,7 +486,7 @@ def diff(package_path, path): ) res = __salt__["cmd.shell"](cmd.format(package_path, path), output_loglevel="trace") if res and res.startswith("Binary file"): - return "File '{0}' is binary and its content has been " "modified.".format(path) + return "File '{}' is binary and its content has been " "modified.".format(path) return res @@ -590,7 +584,7 @@ def info(*packages, **kwargs): attr.append("edition") query.append(attr_map["edition"]) else: - for attr_k, attr_v in six.iteritems(attr_map): + for attr_k, attr_v in attr_map.items(): if attr_k != "description": query.append(attr_v) if attr and "description" in attr or not attr: @@ -599,7 +593,7 @@ def info(*packages, **kwargs): cmd = " ".join(cmd) call = __salt__["cmd.run_all"]( - cmd + (" --queryformat '{0}'".format("".join(query))), + cmd + (" --queryformat '{}'".format("".join(query))), output_loglevel="trace", env={"TZ": "UTC"}, clean_env=True, @@ -706,11 +700,7 @@ def version_cmp(ver1, ver2, ignore_epoch=False): salt '*' pkg.version_cmp '0.2-001' '0.2.0.1-002' """ - normalize = ( - lambda x: six.text_type(x).split(":", 1)[-1] - if ignore_epoch - else six.text_type(x) - ) + normalize = lambda x: str(x).split(":", 1)[-1] if ignore_epoch else str(x) ver1 = normalize(ver1) ver2 = normalize(ver2) @@ -747,7 +737,7 @@ def version_cmp(ver1, ver2, ignore_epoch=False): # rpmdev-vercmp always uses epochs, even when zero def _ensure_epoch(ver): def _prepend(ver): - return "0:{0}".format(ver) + return "0:{}".format(ver) try: if ":" not in ver: @@ -798,7 +788,7 @@ def version_cmp(ver1, ver2, ignore_epoch=False): cmp_result = cmp_func((ver1_e, ver1_v, ver1_r), (ver2_e, ver2_v, ver2_r)) if cmp_result not in (-1, 0, 1): raise CommandExecutionError( - "Comparison result '{0}' is invalid".format(cmp_result) + "Comparison result '{}' is invalid".format(cmp_result) ) return cmp_result diff --git a/salt/modules/systemd_service.py b/salt/modules/systemd_service.py index 176e1dabaa..03e7268cd4 100644 --- a/salt/modules/systemd_service.py +++ b/salt/modules/systemd_service.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Provides the service module for systemd @@ -15,8 +14,6 @@ Provides the service module for systemd call it under the name 'service' and NOT 'systemd'. You can see that also in the examples below. """ -# Import Python libs -from __future__ import absolute_import, print_function, unicode_literals import errno import fnmatch @@ -26,15 +23,12 @@ import os import re import shlex -# Import Salt libs import salt.utils.files import salt.utils.itertools import salt.utils.path import salt.utils.stringutils import salt.utils.systemd from salt.exceptions import CommandExecutionError - -# Import 3rd-party libs from salt.ext import six log = logging.getLogger(__name__) @@ -94,8 +88,8 @@ def _canonical_unit_name(name): Build a canonical unit name treating unit names without one of the valid suffixes as a service. """ - if not isinstance(name, six.string_types): - name = six.text_type(name) + if not isinstance(name, str): + name = str(name) if any(name.endswith(suffix) for suffix in VALID_UNIT_TYPES): return name return "%s.service" % name @@ -137,7 +131,7 @@ def _check_for_unit_changes(name): Check for modified/updated unit files, and run a daemon-reload if any are found. """ - contextkey = "systemd._check_for_unit_changes.{0}".format(name) + contextkey = "systemd._check_for_unit_changes.{}".format(name) if contextkey not in __context__: if _untracked_custom_unit_found(name) or _unit_file_changed(name): systemctl_reload() @@ -199,9 +193,7 @@ def _default_runlevel(): # The default runlevel can also be set via the kernel command-line. try: - valid_strings = set( - ("0", "1", "2", "3", "4", "5", "6", "s", "S", "-s", "single") - ) + valid_strings = {"0", "1", "2", "3", "4", "5", "6", "s", "S", "-s", "single"} with salt.utils.files.fopen("/proc/cmdline") as fp_: for line in fp_: line = salt.utils.stringutils.to_unicode(line) @@ -291,7 +283,7 @@ def _get_service_exec(): break else: raise CommandExecutionError( - "Unable to find sysv service manager (tried {0})".format( + "Unable to find sysv service manager (tried {})".format( ", ".join(executables) ) ) @@ -345,7 +337,7 @@ def _systemctl_cmd(action, name=None, systemd_scope=False, no_block=False, root= ret.append("--no-block") if root: ret.extend(["--root", root]) - if isinstance(action, six.string_types): + if isinstance(action, str): action = shlex.split(action) ret.extend(action) if name is not None: @@ -507,7 +499,7 @@ def get_enabled(root=None): ret.add(unit_name if unit_type == "service" else fullname) # Add in any sysvinit services that are enabled - ret.update(set([x for x in _get_sysv_services(root) if _sysv_enabled(x, root)])) + ret.update({x for x in _get_sysv_services(root) if _sysv_enabled(x, root)}) return sorted(ret) @@ -549,7 +541,7 @@ def get_disabled(root=None): ret.add(unit_name if unit_type == "service" else fullname) # Add in any sysvinit services that are disabled - ret.update(set([x for x in _get_sysv_services(root) if not _sysv_enabled(x, root)])) + ret.update({x for x in _get_sysv_services(root) if not _sysv_enabled(x, root)}) return sorted(ret) diff --git a/salt/modules/zypperpkg.py b/salt/modules/zypperpkg.py index dfaaf420a1..75cb5ce4a8 100644 --- a/salt/modules/zypperpkg.py +++ b/salt/modules/zypperpkg.py @@ -879,6 +879,7 @@ def list_pkgs(versions_as_list=False, root=None, includes=None, **kwargs): # inclusion types are passed contextkey = "pkg.list_pkgs_{}_{}".format(root, includes) + # TODO(aplanas): this cached value depends on the parameters if contextkey not in __context__: ret = {} cmd = ["rpm"] @@ -958,6 +959,28 @@ def list_pkgs(versions_as_list=False, root=None, includes=None, **kwargs): } ] + for include in includes: + if include in ("pattern", "patch"): + if include == "pattern": + pkgs = list_installed_patterns(root=root) + elif include == "patch": + pkgs = list_installed_patches(root=root) + else: + pkgs = [] + for pkg in pkgs: + pkg_extended_name = "{}:{}".format(include, pkg) + info = info_available(pkg_extended_name, refresh=False, root=root) + _ret[pkg_extended_name] = [ + { + "epoch": None, + "version": info[pkg]["version"], + "release": None, + "arch": info[pkg]["arch"], + "install_date": None, + "install_date_time_t": None, + } + ] + __context__[contextkey] = _ret return __salt__["pkg_resource.format_pkg_list"]( @@ -1401,7 +1424,9 @@ def refresh_db(force=None, root=None): def _find_types(pkgs): - """Form a package names list, find prefixes of packages types.""" + """ + Form a package names list, find prefixes of packages types. + """ return sorted({pkg.split(":", 1)[0] for pkg in pkgs if len(pkg.split(":", 1)) == 2}) @@ -1596,12 +1621,7 @@ def install( 'Advisory id "{}" not found'.format(advisory_id) ) else: - # If we add here the `patch:` prefix, the - # `_find_types` helper will take the patches into the - # list of packages. Usually this is the correct thing - # to do, but we can break software the depends on the - # old behaviour. - targets.append(advisory_id) + targets.append("patch:{}".format(advisory_id)) else: targets = pkg_params @@ -1639,16 +1659,6 @@ def install( errors = [] - # If the type is 'advisory', we manually add the 'patch:' - # prefix. This kind of package will not appear in pkg_list in this - # way. - # - # Note that this enable a different mechanism to install a patch; - # if the name of the package is already prefixed with 'patch:' we - # can avoid listing them in the `advisory_ids` field. - if pkg_type == "advisory": - targets = ["patch:{}".format(t) for t in targets] - # Split the targets into batches of 500 packages each, so that # the maximal length of the command line is not broken systemd_scope = _systemd_scope() @@ -1805,6 +1815,10 @@ def upgrade( cmd_update.append("--no-recommends") log.info("Disabling recommendations") + if no_recommends: + cmd_update.append("--no-recommends") + log.info("Disabling recommendations") + if dryrun: # Creates a solver test case for debugging. log.info("Executing debugsolver and performing a dry-run dist-upgrade") @@ -2035,13 +2049,13 @@ def list_locks(root=None): for element in [el for el in meta if el]: if ":" in element: lock.update( - dict([tuple([i.strip() for i in element.split(":", 1)])]) + dict([tuple([i.strip() for i in element.split(":", 1)]),]) ) if lock.get("solvable_name"): locks[lock.pop("solvable_name")] = lock except OSError: pass - except Exception: # pylint: disable=broad-except + except Exception: log.warning("Detected a problem when accessing {}".format(_locks)) return locks @@ -2092,12 +2106,13 @@ def unhold(name=None, pkgs=None, **kwargs): salt '*' pkg.remove_lock pkgs='["foo", "bar"]' """ ret = {} + root = kwargs.get("root") if (not name and not pkgs) or (name and pkgs): raise CommandExecutionError("Name or packages must be specified.") elif name: pkgs = [name] - locks = list_locks() + locks = list_locks(root) try: pkgs = list(__salt__["pkg_resource.parse_targets"](pkgs)[0].keys()) except MinionError as exc: @@ -2114,15 +2129,18 @@ def unhold(name=None, pkgs=None, **kwargs): ret[pkg]["comment"] = "Package {} unable to be unheld.".format(pkg) if removed: - __zypper__.call("rl", *removed) + __zypper__(root=root).call("rl", *removed) return ret -def remove_lock(packages, **kwargs): # pylint: disable=unused-argument +def remove_lock(packages, root=None, **kwargs): # pylint: disable=unused-argument """ Remove specified package lock. + root + operate on a different root directory. + CLI Example: .. code-block:: bash @@ -2134,7 +2152,7 @@ def remove_lock(packages, **kwargs): # pylint: disable=unused-argument salt.utils.versions.warn_until( "Sodium", "This function is deprecated. Please use unhold() instead." ) - locks = list_locks() + locks = list_locks(root) try: packages = list(__salt__["pkg_resource.parse_targets"](packages)[0].keys()) except MinionError as exc: @@ -2158,6 +2176,9 @@ def hold(name=None, pkgs=None, **kwargs): """ Add a package lock. Specify packages to lock by exact name. + root + operate on a different root directory. + CLI Example: .. code-block:: bash @@ -2172,12 +2193,13 @@ def hold(name=None, pkgs=None, **kwargs): :return: """ ret = {} + root = kwargs.get("root") if (not name and not pkgs) or (name and pkgs): raise CommandExecutionError("Name or packages must be specified.") elif name: pkgs = [name] - locks = list_locks() + locks = list_locks(root=root) added = [] try: pkgs = list(__salt__["pkg_resource.parse_targets"](pkgs)[0].keys()) @@ -2193,12 +2215,12 @@ def hold(name=None, pkgs=None, **kwargs): ret[pkg]["comment"] = "Package {} is already set to be held.".format(pkg) if added: - __zypper__.call("al", *added) + __zypper__(root=root).call("al", *added) return ret -def add_lock(packages, **kwargs): # pylint: disable=unused-argument +def add_lock(packages, root=None, **kwargs): # pylint: disable=unused-argument """ Add a package lock. Specify packages to lock by exact name. @@ -2216,7 +2238,7 @@ def add_lock(packages, **kwargs): # pylint: disable=unused-argument salt.utils.versions.warn_until( "Sodium", "This function is deprecated. Please use hold() instead." ) - locks = list_locks() + locks = list_locks(root) added = [] try: packages = list(__salt__["pkg_resource.parse_targets"](packages)[0].keys()) @@ -2410,14 +2432,11 @@ def _get_installed_patterns(root=None): # a real error. output = __salt__["cmd.run"](cmd, ignore_retcode=True) - # On <= SLE12SP4 we have patterns that have multiple names (alias) - # and that are duplicated. The alias start with ".", so we filter - # them. - installed_patterns = { + installed_patterns = [ _pattern_name(line) for line in output.splitlines() - if line.startswith("pattern() = ") and not _pattern_name(line).startswith(".") - } + if line.startswith("pattern() = ") + ] patterns = { k: v for k, v in _get_visible_patterns(root=root).items() if v["installed"] @@ -2735,7 +2754,7 @@ def download(*packages, **kwargs): ) -def list_downloaded(root=None, **kwargs): +def list_downloaded(root=None): """ .. versionadded:: 2017.7.0 diff --git a/salt/states/btrfs.py b/salt/states/btrfs.py index ec84d862c3..1374bbffb4 100644 --- a/salt/states/btrfs.py +++ b/salt/states/btrfs.py @@ -1,10 +1,31 @@ +# +# Author: Alberto Planas +# +# Copyright 2018 SUSE LINUX GmbH, Nuernberg, Germany. +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + """ :maintainer: Alberto Planas :maturity: new :depends: None :platform: Linux """ - import functools import logging import os.path @@ -22,7 +43,7 @@ def _mount(device, use_default): """ Mount the device in a temporary place. """ - opts = "defaults" if use_default else "subvol=/" + opts = "subvol=/" if not use_default else "defaults" dest = tempfile.mkdtemp() res = __states__["mount.mounted"]( dest, device=device, fstype="btrfs", opts=opts, persist=False @@ -82,8 +103,8 @@ def __mount_device(action): @functools.wraps(action) def wrapper(*args, **kwargs): - name = kwargs.get("name", args[0] if args else None) - device = kwargs.get("device", args[1] if len(args) > 1 else None) + name = kwargs["name"] + device = kwargs["device"] use_default = kwargs.get("use_default", False) ret = { @@ -100,9 +121,10 @@ def __mount_device(action): ret["comment"].append(msg) kwargs["__dest"] = dest ret = action(*args, **kwargs) - except Exception as e: # pylint: disable=broad-except - log.error("""Traceback: {}""".format(traceback.format_exc())) - ret["comment"].append(e) + except Exception: + tb = str(traceback.format_exc()) + log.exception("Exception captured in wrapper %s", tb) + ret["comment"].append(tb) finally: if device: _umount(dest) @@ -165,7 +187,7 @@ def subvolume_created( if __opts__["test"]: ret["result"] = None if not exists: - ret["changes"][name] = "Subvolume {} will be created".format(name) + ret["comment"].append("Subvolume {} will be created".format(name)) return ret if not exists: @@ -231,7 +253,7 @@ def subvolume_deleted(name, device, commit=False, __dest=None): if __opts__["test"]: ret["result"] = None if exists: - ret["changes"][name] = "Subvolume {} will be removed".format(name) + ret["comment"].append("Subvolume {} will be removed".format(name)) return ret # If commit is set, we wait until all is over @@ -344,10 +366,10 @@ def properties(name, device, use_default=False, __dest=None, **properties): if __opts__["test"]: ret["result"] = None if properties_to_set: - ret["changes"] = properties_to_set + msg = "Properties {} will be changed in {}".format(properties_to_set, name) else: msg = "No properties will be changed in {}".format(name) - ret["comment"].append(msg) + ret["comment"].append(msg) return ret if properties_to_set: diff --git a/salt/states/file.py b/salt/states/file.py index 9873f8dcc7..9e24e389d8 100644 --- a/salt/states/file.py +++ b/salt/states/file.py @@ -290,7 +290,6 @@ import sys import time import traceback from collections import defaultdict -from collections.abc import Iterable, Mapping from datetime import date, datetime # python3 problem in the making? import salt.loader @@ -312,6 +311,12 @@ from salt.ext.six.moves.urllib.parse import urlparse as _urlparse from salt.serializers import DeserializationError from salt.state import get_accumulator_dir as _get_accumulator_dir +try: + from collections.abc import Iterable, Mapping +except ImportError: + from collections import Iterable, Mapping + + if salt.utils.platform.is_windows(): import salt.utils.win_dacl import salt.utils.win_functions diff --git a/salt/states/loop.py b/salt/states/loop.py index 25e54e1faf..de37b7d60c 100644 --- a/salt/states/loop.py +++ b/salt/states/loop.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Loop state @@ -58,8 +57,6 @@ The function :py:func:`data.subdict_match ` check instances: "{{ instance }}" """ -# Import python libs -from __future__ import absolute_import, print_function, unicode_literals import logging import operator @@ -99,7 +96,7 @@ def until(name, m_args=None, m_kwargs=None, condition=None, period=1, timeout=60 m_kwargs = {} if name not in __salt__: - ret["comment"] = "Cannot find module {0}".format(name) + ret["comment"] = "Cannot find module {}".format(name) elif condition is None: ret["comment"] = "An exit condition must be specified" elif not isinstance(period, (int, float)): @@ -107,7 +104,7 @@ def until(name, m_args=None, m_kwargs=None, condition=None, period=1, timeout=60 elif not isinstance(timeout, (int, float)): ret["comment"] = "Timeout must be specified as a float in seconds" elif __opts__["test"]: - ret["comment"] = "The execution module {0} will be run".format(name) + ret["comment"] = "The execution module {} will be run".format(name) ret["result"] = None else: if m_args is None: @@ -120,11 +117,11 @@ def until(name, m_args=None, m_kwargs=None, condition=None, period=1, timeout=60 m_ret = __salt__[name](*m_args, **m_kwargs) if eval(condition): # pylint: disable=W0123 ret["result"] = True - ret["comment"] = "Condition {0} was met".format(condition) + ret["comment"] = "Condition {} was met".format(condition) break time.sleep(period) else: - ret["comment"] = "Timed out while waiting for condition {0}".format( + ret["comment"] = "Timed out while waiting for condition {}".format( condition ) return ret @@ -185,6 +182,10 @@ def until_no_eval( ) if ret["comment"]: return ret + if not m_args: + m_args = [] + if not m_kwargs: + m_kwargs = {} if init_wait: time.sleep(init_wait) diff --git a/salt/states/pkgrepo.py b/salt/states/pkgrepo.py index 99440874c2..70cb7a1c7e 100644 --- a/salt/states/pkgrepo.py +++ b/salt/states/pkgrepo.py @@ -92,7 +92,6 @@ package managers are APT, DNF, YUM and Zypper. Here is some example SLS: """ -# Import Python libs import sys @@ -101,11 +100,7 @@ import salt.utils.files import salt.utils.pkg.deb import salt.utils.pkg.rpm import salt.utils.versions - -# Import salt libs from salt.exceptions import CommandExecutionError, SaltInvocationError - -# Import 3rd-party libs from salt.state import STATE_INTERNAL_KEYWORDS as _STATE_INTERNAL_KEYWORDS diff --git a/salt/utils/oset.py b/salt/utils/oset.py index d6fb961ede..31a6a4acca 100644 --- a/salt/utils/oset.py +++ b/salt/utils/oset.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Available at repository https://github.com/LuminosoInsight/ordered-set @@ -21,9 +20,10 @@ Rob Speer's changes are as follows: - added a __getstate__ and __setstate__ so it can be pickled - added __getitem__ """ -from __future__ import absolute_import, print_function, unicode_literals - -from collections.abc import MutableSet +try: + from collections.abc import MutableSet +except ImportError: + from collections import MutableSet SLICE_ALL = slice(None) __version__ = "2.0.1" diff --git a/tests/unit/modules/test_kubeadm.py b/tests/unit/modules/test_kubeadm.py index af319e01b1..91e4a9e68e 100644 --- a/tests/unit/modules/test_kubeadm.py +++ b/tests/unit/modules/test_kubeadm.py @@ -1,20 +1,41 @@ +# +# Author: Alberto Planas +# +# Copyright 2019 SUSE LINUX GmbH, Nuernberg, Germany. +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + import pytest import salt.modules.kubeadm as kubeadm from salt.exceptions import CommandExecutionError - -# Import Salt Testing Libs from tests.support.mixins import LoaderModuleMockMixin -from tests.support.mock import MagicMock, patch -from tests.support.unit import TestCase +from tests.support.mock import NO_MOCK, NO_MOCK_REASON, MagicMock, patch +from tests.support.unit import TestCase, skipIf +@skipIf(NO_MOCK, NO_MOCK_REASON) class KubeAdmTestCase(TestCase, LoaderModuleMockMixin): """ Test cases for salt.modules.kubeadm """ def setup_loader_modules(self): - return {kubeadm: {"__salt__": {}, "__utils__": {}}} + return {kubeadm: {"__salt__": {}, "__utils__": {},}} def test_version(self): """ @@ -223,18 +244,6 @@ class KubeAdmTestCase(TestCase, LoaderModuleMockMixin): with pytest.raises(CommandExecutionError): assert kubeadm.token_generate() - def test_token_empty(self): - """ - Test kuebadm.token_list when no outout - """ - result = {"retcode": 0, "stdout": ""} - salt_mock = { - "cmd.run_all": MagicMock(return_value=result), - } - with patch.dict(kubeadm.__salt__, salt_mock): - assert kubeadm.token_list() == [] - salt_mock["cmd.run_all"].assert_called_with(["kubeadm", "token", "list"]) - def test_token_list(self): """ Test kuebadm.token_list without parameters diff --git a/tests/unit/modules/test_rpm_lowpkg.py b/tests/unit/modules/test_rpm_lowpkg.py index e7e8230510..b41e8daf17 100644 --- a/tests/unit/modules/test_rpm_lowpkg.py +++ b/tests/unit/modules/test_rpm_lowpkg.py @@ -1,15 +1,9 @@ -# -*- coding: utf-8 -*- """ :codeauthor: Jayesh Kariya """ -# Import Python Libs -from __future__ import absolute_import -# Import Salt Libs import salt.modules.rpm_lowpkg as rpm - -# Import Salt Testing Libs from tests.support.mixins import LoaderModuleMockMixin from tests.support.mock import MagicMock, patch from tests.support.unit import TestCase @@ -108,6 +102,15 @@ class RpmTestCase(TestCase, LoaderModuleMockMixin): self.assertDictEqual(rpm.file_dict("httpd"), {"errors": [], "packages": {}}) self.assertFalse(_called_with_root(mock)) + def test_file_dict_root(self): + """ + Test if it list the files that belong to a package + """ + mock = MagicMock(return_value="") + with patch.dict(rpm.__salt__, {"cmd.run": mock}): + self.assertDictEqual(rpm.file_dict("httpd"), {"errors": [], "packages": {}}) + self.assertFalse(_called_with_root(mock)) + def test_file_dict_root(self): """ Test if it list the files that belong to a package diff --git a/tests/unit/modules/test_systemd_service.py b/tests/unit/modules/test_systemd_service.py index 32741969ce..bbd89bb3d0 100644 --- a/tests/unit/modules/test_systemd_service.py +++ b/tests/unit/modules/test_systemd_service.py @@ -1,23 +1,16 @@ -# -*- coding: utf-8 -*- """ :codeauthor: Rahul Handay """ -# Import Python libs -from __future__ import absolute_import, print_function, unicode_literals import os import pytest - -# Import Salt Libs import salt.modules.systemd_service as systemd import salt.utils.systemd from salt.exceptions import CommandExecutionError from tests.support.mixins import LoaderModuleMockMixin -from tests.support.mock import MagicMock, patch - -# Import Salt Testing Libs +from tests.support.mock import MagicMock, Mock, patch from tests.support.unit import TestCase _SYSTEMCTL_STATUS = { @@ -93,7 +86,7 @@ class SystemdTestCase(TestCase, LoaderModuleMockMixin): cmd_mock = MagicMock(return_value=_LIST_UNIT_FILES) listdir_mock = MagicMock(return_value=["foo", "bar", "baz", "README"]) sd_mock = MagicMock( - return_value=set([x.replace(".service", "") for x in _SYSTEMCTL_STATUS]) + return_value={x.replace(".service", "") for x in _SYSTEMCTL_STATUS} ) access_mock = MagicMock( side_effect=lambda x, y: x @@ -124,7 +117,7 @@ class SystemdTestCase(TestCase, LoaderModuleMockMixin): # only 'baz' will be considered an enabled sysv service). listdir_mock = MagicMock(return_value=["foo", "bar", "baz", "README"]) sd_mock = MagicMock( - return_value=set([x.replace(".service", "") for x in _SYSTEMCTL_STATUS]) + return_value={x.replace(".service", "") for x in _SYSTEMCTL_STATUS} ) access_mock = MagicMock( side_effect=lambda x, y: x diff --git a/tests/unit/modules/test_zypperpkg.py b/tests/unit/modules/test_zypperpkg.py index b07f9a3af7..032785395e 100644 --- a/tests/unit/modules/test_zypperpkg.py +++ b/tests/unit/modules/test_zypperpkg.py @@ -1639,6 +1639,9 @@ Repository 'DUMMY' not found by its alias, number, or URI. self.assertTrue( zypper.__zypper__(root=None).refreshable.xml.call.call_count == 0 ) + self.assertTrue( + zypper.__zypper__(root=None).refreshable.xml.call.call_count == 0 + ) def test_repo_noadd_nomod_ref(self): """ @@ -1919,8 +1922,8 @@ Repository 'DUMMY' not found by its alias, number, or URI. def test__get_installed_patterns(self, get_visible_patterns): """Test installed patterns in the system""" get_visible_patterns.return_value = { - "package-a": {"installed": True, "summary": "description a"}, - "package-b": {"installed": False, "summary": "description b"}, + "package-a": {"installed": True, "summary": "description a",}, + "package-b": {"installed": False, "summary": "description b",}, } salt_mock = { @@ -1932,59 +1935,18 @@ pattern() = package-c""" } with patch.dict("salt.modules.zypperpkg.__salt__", salt_mock): assert zypper._get_installed_patterns() == { - "package-a": {"installed": True, "summary": "description a"}, - "package-c": {"installed": True, "summary": "Non-visible pattern"}, - } - - @patch("salt.modules.zypperpkg._get_visible_patterns") - def test__get_installed_patterns_with_alias(self, get_visible_patterns): - """Test installed patterns in the system if they have alias""" - get_visible_patterns.return_value = { - "package-a": {"installed": True, "summary": "description a"}, - "package-b": {"installed": False, "summary": "description b"}, - } - - salt_mock = { - "cmd.run": MagicMock( - return_value="""pattern() = .package-a-alias -pattern() = package-a -pattern-visible() -pattern() = package-c""" - ), - } - with patch.dict("salt.modules.zypperpkg.__salt__", salt_mock): - assert zypper._get_installed_patterns() == { - "package-a": {"installed": True, "summary": "description a"}, - "package-c": {"installed": True, "summary": "Non-visible pattern"}, + "package-a": {"installed": True, "summary": "description a",}, + "package-c": {"installed": True, "summary": "Non-visible pattern",}, } @patch("salt.modules.zypperpkg._get_visible_patterns") def test_list_patterns(self, get_visible_patterns): """Test available patterns in the repo""" get_visible_patterns.return_value = { - "package-a": {"installed": True, "summary": "description a"}, - "package-b": {"installed": False, "summary": "description b"}, + "package-a": {"installed": True, "summary": "description a",}, + "package-b": {"installed": False, "summary": "description b",}, } assert zypper.list_patterns() == { - "package-a": {"installed": True, "summary": "description a"}, - "package-b": {"installed": False, "summary": "description b"}, - } - - def test__clean_cache_empty(self): - """Test that an empty cached can be cleaned""" - context = {} - with patch.dict(zypper.__context__, context): - zypper._clean_cache() - assert context == {} - - def test__clean_cache_filled(self): - """Test that a filled cached can be cleaned""" - context = { - "pkg.list_pkgs_/mnt_[]": None, - "pkg.list_pkgs_/mnt_[patterns]": None, - "pkg.list_provides": None, - "pkg.other_data": None, + "package-a": {"installed": True, "summary": "description a",}, + "package-b": {"installed": False, "summary": "description b",}, } - with patch.dict(zypper.__context__, context): - zypper._clean_cache() - self.assertEqual(zypper.__context__, {"pkg.other_data": None}) diff --git a/tests/unit/states/test_btrfs.py b/tests/unit/states/test_btrfs.py index fdbf06bd13..74e44641b8 100644 --- a/tests/unit/states/test_btrfs.py +++ b/tests/unit/states/test_btrfs.py @@ -1,27 +1,45 @@ +# +# Author: Alberto Planas +# +# Copyright 2018 SUSE LINUX GmbH, Nuernberg, Germany. +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + """ :maintainer: Alberto Planas :platform: Linux """ - import pytest import salt.states.btrfs as btrfs -import salt.utils.platform from salt.exceptions import CommandExecutionError - -# Import Salt Testing Libs from tests.support.mixins import LoaderModuleMockMixin -from tests.support.mock import MagicMock, patch +from tests.support.mock import NO_MOCK, NO_MOCK_REASON, MagicMock, patch from tests.support.unit import TestCase, skipIf -@skipIf(salt.utils.platform.is_windows(), "Non-Windows feature") +@skipIf(NO_MOCK, NO_MOCK_REASON) class BtrfsTestCase(TestCase, LoaderModuleMockMixin): """ Test cases for salt.states.btrfs """ def setup_loader_modules(self): - return {btrfs: {"__salt__": {}, "__states__": {}, "__utils__": {}}} + return {btrfs: {"__salt__": {}, "__states__": {}, "__utils__": {},}} @patch("salt.states.btrfs._umount") @patch("tempfile.mkdtemp") @@ -112,9 +130,9 @@ class BtrfsTestCase(TestCase, LoaderModuleMockMixin): """ salt_mock = { "btrfs.subvolume_show": MagicMock( - return_value={"@/var": {"subvolume id": "256"}} + return_value={"@/var": {"subvolume id": "256"},} ), - "btrfs.subvolume_get_default": MagicMock(return_value={"id": "5"}), + "btrfs.subvolume_get_default": MagicMock(return_value={"id": "5",}), } with patch.dict(btrfs.__salt__, salt_mock): assert not btrfs._is_default("/tmp/xxx/@/var", "/tmp/xxx", "@/var") @@ -127,9 +145,9 @@ class BtrfsTestCase(TestCase, LoaderModuleMockMixin): """ salt_mock = { "btrfs.subvolume_show": MagicMock( - return_value={"@/var": {"subvolume id": "256"}} + return_value={"@/var": {"subvolume id": "256"},} ), - "btrfs.subvolume_get_default": MagicMock(return_value={"id": "256"}), + "btrfs.subvolume_get_default": MagicMock(return_value={"id": "256",}), } with patch.dict(btrfs.__salt__, salt_mock): assert btrfs._is_default("/tmp/xxx/@/var", "/tmp/xxx", "@/var") @@ -142,7 +160,7 @@ class BtrfsTestCase(TestCase, LoaderModuleMockMixin): """ salt_mock = { "btrfs.subvolume_show": MagicMock( - return_value={"@/var": {"subvolume id": "256"}} + return_value={"@/var": {"subvolume id": "256"},} ), "btrfs.subvolume_set_default": MagicMock(return_value=True), } @@ -158,7 +176,7 @@ class BtrfsTestCase(TestCase, LoaderModuleMockMixin): Test if the subvolume is copy on write. """ salt_mock = { - "file.lsattr": MagicMock(return_value={"/tmp/xxx/@/var": ["C"]}), + "file.lsattr": MagicMock(return_value={"/tmp/xxx/@/var": ["C"],}), } with patch.dict(btrfs.__salt__, salt_mock): assert not btrfs._is_cow("/tmp/xxx/@/var") @@ -169,7 +187,7 @@ class BtrfsTestCase(TestCase, LoaderModuleMockMixin): Test if the subvolume is copy on write. """ salt_mock = { - "file.lsattr": MagicMock(return_value={"/tmp/xxx/@/var": []}), + "file.lsattr": MagicMock(return_value={"/tmp/xxx/@/var": [],}), } with patch.dict(btrfs.__salt__, salt_mock): assert btrfs._is_cow("/tmp/xxx/@/var") @@ -188,7 +206,6 @@ class BtrfsTestCase(TestCase, LoaderModuleMockMixin): "/tmp/xxx/@/var", operator="add", attributes="C" ) - @skipIf(salt.utils.platform.is_windows(), "Skip on Windows") @patch("salt.states.btrfs._umount") @patch("salt.states.btrfs._mount") def test_subvolume_created_exists(self, mount, umount): @@ -215,34 +232,6 @@ class BtrfsTestCase(TestCase, LoaderModuleMockMixin): mount.assert_called_once() umount.assert_called_once() - @skipIf(salt.utils.platform.is_windows(), "Skip on Windows") - @patch("salt.states.btrfs._umount") - @patch("salt.states.btrfs._mount") - def test_subvolume_created_exists_decorator(self, mount, umount): - """ - Test creating a subvolume using a non-kwargs call - """ - mount.return_value = "/tmp/xxx" - salt_mock = { - "btrfs.subvolume_exists": MagicMock(return_value=True), - } - opts_mock = { - "test": False, - } - with patch.dict(btrfs.__salt__, salt_mock), patch.dict( - btrfs.__opts__, opts_mock - ): - assert btrfs.subvolume_created("@/var", "/dev/sda1") == { - "name": "@/var", - "result": True, - "changes": {}, - "comment": ["Subvolume @/var already present"], - } - salt_mock["btrfs.subvolume_exists"].assert_called_with("/tmp/xxx/@/var") - mount.assert_called_once() - umount.assert_called_once() - - @skipIf(salt.utils.platform.is_windows(), "Skip on Windows") @patch("salt.states.btrfs._umount") @patch("salt.states.btrfs._mount") def test_subvolume_created_exists_test(self, mount, umount): @@ -269,7 +258,6 @@ class BtrfsTestCase(TestCase, LoaderModuleMockMixin): mount.assert_called_once() umount.assert_called_once() - @skipIf(salt.utils.platform.is_windows(), "Skip on Windows") @patch("salt.states.btrfs._is_default") @patch("salt.states.btrfs._umount") @patch("salt.states.btrfs._mount") @@ -300,7 +288,6 @@ class BtrfsTestCase(TestCase, LoaderModuleMockMixin): mount.assert_called_once() umount.assert_called_once() - @skipIf(salt.utils.platform.is_windows(), "Skip on Windows") @patch("salt.states.btrfs._set_default") @patch("salt.states.btrfs._is_default") @patch("salt.states.btrfs._umount") @@ -335,7 +322,6 @@ class BtrfsTestCase(TestCase, LoaderModuleMockMixin): mount.assert_called_once() umount.assert_called_once() - @skipIf(salt.utils.platform.is_windows(), "Skip on Windows") @patch("salt.states.btrfs._set_default") @patch("salt.states.btrfs._is_default") @patch("salt.states.btrfs._umount") @@ -373,7 +359,6 @@ class BtrfsTestCase(TestCase, LoaderModuleMockMixin): mount.assert_called_once() umount.assert_called_once() - @skipIf(salt.utils.platform.is_windows(), "Skip on Windows") @patch("salt.states.btrfs._is_cow") @patch("salt.states.btrfs._umount") @patch("salt.states.btrfs._mount") @@ -404,7 +389,6 @@ class BtrfsTestCase(TestCase, LoaderModuleMockMixin): mount.assert_called_once() umount.assert_called_once() - @skipIf(salt.utils.platform.is_windows(), "Skip on Windows") @patch("salt.states.btrfs._unset_cow") @patch("salt.states.btrfs._is_cow") @patch("salt.states.btrfs._umount") @@ -437,7 +421,6 @@ class BtrfsTestCase(TestCase, LoaderModuleMockMixin): mount.assert_called_once() umount.assert_called_once() - @skipIf(salt.utils.platform.is_windows(), "Skip on Windows") @patch("salt.states.btrfs._umount") @patch("salt.states.btrfs._mount") def test_subvolume_created(self, mount, umount): @@ -469,7 +452,6 @@ class BtrfsTestCase(TestCase, LoaderModuleMockMixin): mount.assert_called_once() umount.assert_called_once() - @skipIf(salt.utils.platform.is_windows(), "Skip on Windows") @patch("salt.states.btrfs._umount") @patch("salt.states.btrfs._mount") def test_subvolume_created_fails_directory(self, mount, umount): @@ -499,7 +481,6 @@ class BtrfsTestCase(TestCase, LoaderModuleMockMixin): mount.assert_called_once() umount.assert_called_once() - @skipIf(salt.utils.platform.is_windows(), "Skip on Windows") @patch("salt.states.btrfs._umount") @patch("salt.states.btrfs._mount") def test_subvolume_created_fails(self, mount, umount): @@ -541,7 +522,7 @@ class BtrfsTestCase(TestCase, LoaderModuleMockMixin): "description": "Set/get compression for a file or directory", "value": "N/A", }, - "label": {"description": "Set/get label of device.", "value": "N/A"}, + "label": {"description": "Set/get label of device.", "value": "N/A",}, "ro": { "description": "Set/get read-only flag or subvolume", "value": "N/A", @@ -560,7 +541,7 @@ class BtrfsTestCase(TestCase, LoaderModuleMockMixin): "description": "Set/get compression for a file or directory", "value": "N/A", }, - "label": {"description": "Set/get label of device.", "value": "N/A"}, + "label": {"description": "Set/get label of device.", "value": "N/A",}, "ro": { "description": "Set/get read-only flag or subvolume", "value": "N/A", @@ -578,7 +559,7 @@ class BtrfsTestCase(TestCase, LoaderModuleMockMixin): "description": "Set/get compression for a file or directory", "value": "N/A", }, - "label": {"description": "Set/get label of device.", "value": "mylabel"}, + "label": {"description": "Set/get label of device.", "value": "mylabel",}, "ro": { "description": "Set/get read-only flag or subvolume", "value": "N/A", @@ -596,7 +577,7 @@ class BtrfsTestCase(TestCase, LoaderModuleMockMixin): "description": "Set/get compression for a file or directory", "value": "N/A", }, - "label": {"description": "Set/get label of device.", "value": "N/A"}, + "label": {"description": "Set/get label of device.", "value": "N/A",}, "ro": { "description": "Set/get read-only flag or subvolume", "value": True, @@ -614,7 +595,7 @@ class BtrfsTestCase(TestCase, LoaderModuleMockMixin): "description": "Set/get compression for a file or directory", "value": "N/A", }, - "label": {"description": "Set/get label of device.", "value": "N/A"}, + "label": {"description": "Set/get label of device.", "value": "N/A",}, "ro": { "description": "Set/get read-only flag or subvolume", "value": "N/A", @@ -755,40 +736,3 @@ class BtrfsTestCase(TestCase, LoaderModuleMockMixin): ) mount.assert_called_once() umount.assert_called_once() - - @patch("salt.states.btrfs._umount") - @patch("salt.states.btrfs._mount") - @patch("os.path.exists") - def test_properties_test(self, exists, mount, umount): - """ - Test setting a property in test mode. - """ - exists.return_value = True - mount.return_value = "/tmp/xxx" - salt_mock = { - "btrfs.properties": MagicMock( - side_effect=[ - { - "ro": { - "description": "Set/get read-only flag or subvolume", - "value": "N/A", - }, - }, - ] - ), - } - opts_mock = { - "test": True, - } - with patch.dict(btrfs.__salt__, salt_mock), patch.dict( - btrfs.__opts__, opts_mock - ): - assert btrfs.properties(name="@/var", device="/dev/sda1", ro=True) == { - "name": "@/var", - "result": None, - "changes": {"ro": "true"}, - "comment": [], - } - salt_mock["btrfs.properties"].assert_called_with("/tmp/xxx/@/var") - mount.assert_called_once() - umount.assert_called_once() diff --git a/tests/unit/states/test_pkg.py b/tests/unit/states/test_pkg.py index 15ca937e13..a7ddfece14 100644 --- a/tests/unit/states/test_pkg.py +++ b/tests/unit/states/test_pkg.py @@ -1,15 +1,6 @@ -# -*- coding: utf-8 -*- - -# Import Python libs -from __future__ import absolute_import - import salt.states.pkg as pkg - -# Import Salt Libs from salt.ext import six from salt.ext.six.moves import zip - -# Import Salt Testing Libs from tests.support.mixins import LoaderModuleMockMixin from tests.support.mock import MagicMock, patch from tests.support.unit import TestCase @@ -35,7 +26,7 @@ class PkgTestCase(TestCase, LoaderModuleMockMixin): """ list_upgrades = MagicMock( return_value={ - pkgname: pkgver["new"] for pkgname, pkgver in six.iteritems(self.pkgs) + pkgname: pkgver["new"] for pkgname, pkgver in self.pkgs.items() } ) upgrade = MagicMock(return_value=self.pkgs) @@ -75,7 +66,7 @@ class PkgTestCase(TestCase, LoaderModuleMockMixin): list_upgrades = MagicMock( return_value={ - pkgname: pkgver["new"] for pkgname, pkgver in six.iteritems(self.pkgs) + pkgname: pkgver["new"] for pkgname, pkgver in self.pkgs.items() } ) upgrade = MagicMock(return_value=self.pkgs) @@ -92,9 +83,7 @@ class PkgTestCase(TestCase, LoaderModuleMockMixin): # Run state with test=false with patch.dict(pkg.__opts__, {"test": False}): ret = pkg.uptodate( - "dummy", - test=True, - pkgs=[pkgname for pkgname in six.iterkeys(self.pkgs)], + "dummy", test=True, pkgs=[pkgname for pkgname in self.pkgs.keys()], ) self.assertTrue(ret["result"]) self.assertDictEqual(ret["changes"], pkgs) @@ -102,9 +91,7 @@ class PkgTestCase(TestCase, LoaderModuleMockMixin): # Run state with test=true with patch.dict(pkg.__opts__, {"test": True}): ret = pkg.uptodate( - "dummy", - test=True, - pkgs=[pkgname for pkgname in six.iterkeys(self.pkgs)], + "dummy", test=True, pkgs=[pkgname for pkgname in self.pkgs.keys()], ) self.assertIsNone(ret["result"]) self.assertDictEqual(ret["changes"], pkgs) @@ -146,9 +133,7 @@ class PkgTestCase(TestCase, LoaderModuleMockMixin): # Run state with test=false with patch.dict(pkg.__opts__, {"test": False}): ret = pkg.uptodate( - "dummy", - test=True, - pkgs=[pkgname for pkgname in six.iterkeys(self.pkgs)], + "dummy", test=True, pkgs=[pkgname for pkgname in self.pkgs.keys()], ) self.assertTrue(ret["result"]) self.assertDictEqual(ret["changes"], {}) @@ -156,9 +141,7 @@ class PkgTestCase(TestCase, LoaderModuleMockMixin): # Run state with test=true with patch.dict(pkg.__opts__, {"test": True}): ret = pkg.uptodate( - "dummy", - test=True, - pkgs=[pkgname for pkgname in six.iterkeys(self.pkgs)], + "dummy", test=True, pkgs=[pkgname for pkgname in self.pkgs.keys()], ) self.assertTrue(ret["result"]) self.assertDictEqual(ret["changes"], {}) @@ -176,7 +159,7 @@ class PkgTestCase(TestCase, LoaderModuleMockMixin): list_upgrades = MagicMock( return_value={ - pkgname: pkgver["new"] for pkgname, pkgver in six.iteritems(self.pkgs) + pkgname: pkgver["new"] for pkgname, pkgver in self.pkgs.items() } ) upgrade = MagicMock(return_value={}) @@ -193,9 +176,7 @@ class PkgTestCase(TestCase, LoaderModuleMockMixin): # Run state with test=false with patch.dict(pkg.__opts__, {"test": False}): ret = pkg.uptodate( - "dummy", - test=True, - pkgs=[pkgname for pkgname in six.iterkeys(self.pkgs)], + "dummy", test=True, pkgs=[pkgname for pkgname in self.pkgs.keys()], ) self.assertFalse(ret["result"]) self.assertDictEqual(ret["changes"], {}) @@ -203,9 +184,7 @@ class PkgTestCase(TestCase, LoaderModuleMockMixin): # Run state with test=true with patch.dict(pkg.__opts__, {"test": True}): ret = pkg.uptodate( - "dummy", - test=True, - pkgs=[pkgname for pkgname in six.iterkeys(self.pkgs)], + "dummy", test=True, pkgs=[pkgname for pkgname in self.pkgs.keys()], ) self.assertIsNone(ret["result"]) self.assertDictEqual(ret["changes"], pkgs) diff --git a/tests/unit/test_loader.py b/tests/unit/test_loader.py index 9f826e007f..863e2182b9 100644 --- a/tests/unit/test_loader.py +++ b/tests/unit/test_loader.py @@ -215,6 +215,96 @@ class LazyLoaderUtilsTest(TestCase): self.assertTrue(self.module_name + ".run" not in loader) +loader_template_module = """ +import my_utils + +def run(): + return my_utils.run() +""" + +loader_template_utils = """ +def run(): + return True +""" + + +class LazyLoaderUtilsTest(TestCase): + """ + Test the loader + """ + + module_name = "lazyloaderutilstest" + utils_name = "my_utils" + + @classmethod + def setUpClass(cls): + cls.opts = salt.config.minion_config(None) + cls.opts["grains"] = salt.loader.grains(cls.opts) + if not os.path.isdir(TMP): + os.makedirs(TMP) + + def setUp(self): + # Setup the module + self.module_dir = tempfile.mkdtemp(dir=TMP) + self.module_file = os.path.join( + self.module_dir, "{}.py".format(self.module_name) + ) + with salt.utils.files.fopen(self.module_file, "w") as fh: + fh.write(salt.utils.stringutils.to_str(loader_template_module)) + fh.flush() + os.fsync(fh.fileno()) + + self.utils_dir = tempfile.mkdtemp(dir=TMP) + self.utils_file = os.path.join(self.utils_dir, "{}.py".format(self.utils_name)) + with salt.utils.files.fopen(self.utils_file, "w") as fh: + fh.write(salt.utils.stringutils.to_str(loader_template_utils)) + fh.flush() + os.fsync(fh.fileno()) + + def tearDown(self): + shutil.rmtree(self.module_dir) + if os.path.isdir(self.module_dir): + shutil.rmtree(self.module_dir) + shutil.rmtree(self.utils_dir) + if os.path.isdir(self.utils_dir): + shutil.rmtree(self.utils_dir) + del self.module_dir + del self.module_file + del self.utils_dir + del self.utils_file + + if self.module_name in sys.modules: + del sys.modules[self.module_name] + if self.utils_name in sys.modules: + del sys.modules[self.utils_name] + + @classmethod + def tearDownClass(cls): + del cls.opts + + def test_utils_found(self): + """ + Test that the extra module directory is available for imports + """ + loader = salt.loader.LazyLoader( + [self.module_dir], + copy.deepcopy(self.opts), + tag="module", + extra_module_dirs=[self.utils_dir], + ) + self.assertTrue(inspect.isfunction(loader[self.module_name + ".run"])) + self.assertTrue(loader[self.module_name + ".run"]()) + + def test_utils_not_found(self): + """ + Test that the extra module directory is not available for imports + """ + loader = salt.loader.LazyLoader( + [self.module_dir], copy.deepcopy(self.opts), tag="module" + ) + self.assertTrue(self.module_name + ".run" not in loader) + + class LazyLoaderVirtualEnabledTest(TestCase): """ Test the base loader of salt. @@ -1342,9 +1432,10 @@ class LoaderGlobalsTest(ModuleCase): ) # Now, test each module! - for item in global_vars.values(): - for name in names: - self.assertIn(name, list(item.keys())) + for item in global_vars: + if item["__name__"].startswith("salt.loaded"): + for name in names: + self.assertIn(name, list(item.keys())) def test_auth(self): """ -- 2.29.2