diff --git a/_lastrevision b/_lastrevision index eb8befa..b034e46 100644 --- a/_lastrevision +++ b/_lastrevision @@ -1 +1 @@ -98a9fb14263d76c4873bc68f208aeee04b583044 \ No newline at end of file +20438f0fbeca551fd5a04babf1a686a5455c2ce3 \ No newline at end of file diff --git a/add-sleep-on-exception-handling-on-minion-connection.patch b/add-sleep-on-exception-handling-on-minion-connection.patch new file mode 100644 index 0000000..146f6c2 --- /dev/null +++ b/add-sleep-on-exception-handling-on-minion-connection.patch @@ -0,0 +1,41 @@ +From aafa76ddd04114f699d760577681db75579685d7 Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov <35733135+vzhestkov@users.noreply.github.com> +Date: Thu, 18 Feb 2021 14:49:38 +0300 +Subject: [PATCH] Add sleep on exception handling on minion connection + attempt to the master (bsc#1174855) (#321) + +* Async batch implementation fix + +* Add sleep on exception handling on minion connection attempt to the master (bsc#1174855) +--- + salt/minion.py | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/salt/minion.py b/salt/minion.py +index 4da665a130..dacff1e0a9 100644 +--- a/salt/minion.py ++++ b/salt/minion.py +@@ -1123,6 +1123,9 @@ class MinionManager(MinionBase): + last = 0 # never have we signed in + auth_wait = minion.opts["acceptance_wait_time"] + failed = False ++ retry_wait = 1 ++ retry_wait_inc = 1 ++ max_retry_wait = 20 + while True: + try: + if minion.opts.get("beacons_before_connect", False): +@@ -1158,6 +1161,9 @@ class MinionManager(MinionBase): + minion.opts["master"], + exc_info=True, + ) ++ yield salt.ext.tornado.gen.sleep(retry_wait) ++ if retry_wait < max_retry_wait: ++ retry_wait += retry_wait_inc + + # Multi Master Tune In + def tune_in(self): +-- +2.29.2 + + diff --git a/allow-extra_filerefs-as-sanitized-kwargs-for-ssh-cli.patch b/allow-extra_filerefs-as-sanitized-kwargs-for-ssh-cli.patch new file mode 100644 index 0000000..6a18cf9 --- /dev/null +++ b/allow-extra_filerefs-as-sanitized-kwargs-for-ssh-cli.patch @@ -0,0 +1,28 @@ +From 5e8a9c9eaa18c53b259a3bb1da8df51f5382ed6b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Mon, 22 Feb 2021 11:39:19 +0000 +Subject: [PATCH] Allow extra_filerefs as sanitized kwargs for SSH + client + +(cherry picked from commit 89f843398849633af52cceab2155e9cedf8ad3dd) +--- + salt/client/ssh/client.py | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/salt/client/ssh/client.py b/salt/client/ssh/client.py +index 2cf42f53e7..3631c3bb67 100644 +--- a/salt/client/ssh/client.py ++++ b/salt/client/ssh/client.py +@@ -60,6 +60,7 @@ class SSHClient: + ("rosters", list), + ("ignore_host_keys", bool), + ("raw_shell", bool), ++ ("extra_filerefs", str), + ] + sane_kwargs = {} + for name, kind in roster_vals: +-- +2.30.1 + + diff --git a/async-batch-implementation-fix-320.patch b/async-batch-implementation-fix-320.patch new file mode 100644 index 0000000..911e5bd --- /dev/null +++ b/async-batch-implementation-fix-320.patch @@ -0,0 +1,69 @@ +From 843c76e5889659ec80fea3f39b750b9f907a902d Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov <35733135+vzhestkov@users.noreply.github.com> +Date: Wed, 17 Feb 2021 16:47:11 +0300 +Subject: [PATCH] Async batch implementation fix (#320) + +--- + salt/client/__init__.py | 38 -------------------------------------- + 1 file changed, 38 deletions(-) + +diff --git a/salt/client/__init__.py b/salt/client/__init__.py +index cc8fd4048d..ddb437604b 100644 +--- a/salt/client/__init__.py ++++ b/salt/client/__init__.py +@@ -534,12 +534,6 @@ class LocalClient: + {'dave': {...}} + {'stewart': {...}} + """ +- # We need to re-import salt.utils.args here +- # even though it has already been imported. +- # when cmd_batch is called via the NetAPI +- # the module is unavailable. +- import salt.utils.args +- + # Late import - not used anywhere else in this file + import salt.cli.batch + +@@ -557,38 +551,6 @@ class LocalClient: + + eauth = salt.cli.batch.batch_get_eauth(kwargs) + +- arg = salt.utils.args.condition_input(arg, kwarg) +- opts = { +- "tgt": tgt, +- "fun": fun, +- "arg": arg, +- "tgt_type": tgt_type, +- "ret": ret, +- "batch": batch, +- "failhard": kwargs.get("failhard", self.opts.get("failhard", False)), +- "raw": kwargs.get("raw", False), +- } +- +- if "timeout" in kwargs: +- opts["timeout"] = kwargs["timeout"] +- if "gather_job_timeout" in kwargs: +- opts["gather_job_timeout"] = kwargs["gather_job_timeout"] +- if "batch_wait" in kwargs: +- opts["batch_wait"] = int(kwargs["batch_wait"]) +- +- eauth = {} +- if "eauth" in kwargs: +- eauth["eauth"] = kwargs.pop("eauth") +- if "username" in kwargs: +- eauth["username"] = kwargs.pop("username") +- if "password" in kwargs: +- eauth["password"] = kwargs.pop("password") +- if "token" in kwargs: +- eauth["token"] = kwargs.pop("token") +- +- for key, val in self.opts.items(): +- if key not in opts: +- opts[key] = val + batch = salt.cli.batch.Batch(opts, eauth=eauth, quiet=True) + for ret in batch.run(): + yield ret +-- +2.30.0 + + diff --git a/fix-for-some-cves-bsc1181550.patch b/fix-for-some-cves-bsc1181550.patch new file mode 100644 index 0000000..6722ed0 --- /dev/null +++ b/fix-for-some-cves-bsc1181550.patch @@ -0,0 +1,5284 @@ +From a74b74a640da563618783f309fe1eef391a98f41 Mon Sep 17 00:00:00 2001 +From: "Daniel A. Wozniak" +Date: Fri, 29 Jan 2021 14:30:27 -0700 +Subject: [PATCH] Fix for some cves bsc1181550 + +CVE-2020-28243 CVE-2020-28972 CVE-2020-35662 CVE-2021-3148 CVE-2021-3144 +CVE-2021-25281 CVE-2021-25282 CVE-2021-25283 CVE-2021-25284 +CVE-2021-3197 +--- + salt/auth/__init__.py | 1 + + salt/client/mixins.py | 71 ++-- + salt/client/ssh/client.py | 46 +++ + salt/cloud/clouds/qingcloud.py | 57 +-- + salt/cloud/clouds/vmware.py | 158 ++++---- + salt/config/schemas/vcenter.py | 8 +- + salt/master.py | 2 +- + salt/modules/bigip.py | 25 +- + salt/modules/cmdmod.py | 29 +- + salt/modules/glassfish.py | 32 +- + salt/modules/keystone.py | 148 ++++---- + salt/modules/restartcheck.py | 4 +- + salt/modules/vsphere.py | 660 ++++++++++++++++++++++++++++----- + salt/modules/zenoss.py | 26 +- + salt/pillar/vmware_pillar.py | 26 +- + salt/proxy/cimc.py | 31 +- + salt/proxy/panos.py | 28 +- + salt/proxy/vcenter.py | 6 +- + salt/returners/splunk.py | 34 +- + salt/runners/asam.py | 19 +- + salt/states/esxi.py | 228 ++++++------ + salt/utils/http.py | 20 + + salt/utils/thin.py | 4 +- + salt/utils/vmware.py | 128 ++++--- + salt/wheel/__init__.py | 12 +- + salt/wheel/pillar_roots.py | 21 +- + 26 files changed, 1201 insertions(+), 623 deletions(-) + +diff --git a/salt/auth/__init__.py b/salt/auth/__init__.py +index 22c54e8048..56f8bd57c8 100644 +--- a/salt/auth/__init__.py ++++ b/salt/auth/__init__.py +@@ -270,6 +270,7 @@ class LoadAuth: + + if rm_tok: + self.rm_token(tok) ++ return {} + + return tdata + +diff --git a/salt/client/mixins.py b/salt/client/mixins.py +index b33ee54f27..6f408adbba 100644 +--- a/salt/client/mixins.py ++++ b/salt/client/mixins.py +@@ -1,10 +1,7 @@ +-# coding: utf-8 + """ + A collection of mixins useful for the various *Client interfaces + """ + +-# Import Python libs +-from __future__ import absolute_import, print_function, unicode_literals, with_statement + + import copy as pycopy + import fnmatch +@@ -14,10 +11,7 @@ import traceback + import weakref + from collections.abc import Mapping, MutableMapping + +-# Import Salt libs + import salt.exceptions +- +-# Import 3rd-party libs + import salt.ext.tornado.stack_context + import salt.log.setup + import salt.minion +@@ -122,7 +116,7 @@ class ClientFuncsDict(MutableMapping): + return iter(self.client.functions) + + +-class SyncClientMixin(object): ++class SyncClientMixin: + """ + A mixin for *Client interfaces to abstract common function execution + """ +@@ -182,7 +176,7 @@ class SyncClientMixin(object): + ) + if ret is None: + raise salt.exceptions.SaltClientTimeout( +- "RunnerClient job '{0}' timed out".format(job["jid"]), ++ "RunnerClient job '{}' timed out".format(job["jid"]), + jid=job["jid"], + ) + +@@ -281,7 +275,7 @@ class SyncClientMixin(object): + return True + + try: +- return self.opts["{0}_returns".format(class_name)] ++ return self.opts["{}_returns".format(class_name)] + except KeyError: + # No such option, assume this isn't one we care about gating and + # just return True. +@@ -308,7 +302,7 @@ class SyncClientMixin(object): + tag = low.get("__tag__", salt.utils.event.tagify(jid, prefix=self.tag_prefix)) + + data = { +- "fun": "{0}.{1}".format(self.client, fun), ++ "fun": "{}.{}".format(self.client, fun), + "jid": jid, + "user": low.get("__user__", "UNKNOWN"), + } +@@ -353,14 +347,14 @@ class SyncClientMixin(object): + # namespace only once per module-- not per func + completed_funcs = [] + +- for mod_name in six.iterkeys(self_functions): ++ for mod_name in self_functions.keys(): + if "." not in mod_name: + continue + mod, _ = mod_name.split(".", 1) + if mod in completed_funcs: + continue + completed_funcs.append(mod) +- for global_key, value in six.iteritems(func_globals): ++ for global_key, value in func_globals.items(): + self.functions[mod_name].__globals__[global_key] = value + + # There are some discrepancies of what a "low" structure is in the +@@ -398,7 +392,7 @@ class SyncClientMixin(object): + except TypeError as exc: + data[ + "return" +- ] = "\nPassed invalid arguments: {0}\n\nUsage:\n{1}".format( ++ ] = "\nPassed invalid arguments: {}\n\nUsage:\n{}".format( + exc, func.__doc__ + ) + try: +@@ -413,9 +407,9 @@ class SyncClientMixin(object): + ) + except (Exception, SystemExit) as ex: # pylint: disable=broad-except + if isinstance(ex, salt.exceptions.NotImplemented): +- data["return"] = six.text_type(ex) ++ data["return"] = str(ex) + else: +- data["return"] = "Exception occurred in {0} {1}: {2}".format( ++ data["return"] = "Exception occurred in {} {}: {}".format( + self.client, fun, traceback.format_exc(), + ) + data["success"] = False +@@ -477,7 +471,7 @@ class SyncClientMixin(object): + return salt.utils.doc.strip_rst(docs) + + +-class AsyncClientMixin(object): ++class AsyncClientMixin: + """ + A mixin for *Client interfaces to enable easy asynchronous function execution + """ +@@ -485,10 +479,34 @@ class AsyncClientMixin(object): + client = None + tag_prefix = None + ++ def _proc_function_remote(self, fun, low, user, tag, jid, daemonize=True): ++ """ ++ Run this method in a multiprocess target to execute the function on the ++ master and fire the return data on the event bus ++ """ ++ if daemonize and not salt.utils.platform.is_windows(): ++ # Shutdown the multiprocessing before daemonizing ++ salt.log.setup.shutdown_multiprocessing_logging() ++ ++ salt.utils.process.daemonize() ++ ++ # Reconfigure multiprocessing logging after daemonizing ++ salt.log.setup.setup_multiprocessing_logging() ++ ++ # pack a few things into low ++ low["__jid__"] = jid ++ low["__user__"] = user ++ low["__tag__"] = tag ++ ++ try: ++ return self.cmd_sync(low) ++ except salt.exceptions.EauthAuthenticationError as exc: ++ log.error(exc) ++ + def _proc_function(self, fun, low, user, tag, jid, daemonize=True): + """ +- Run this method in a multiprocess target to execute the function in a +- multiprocess and fire the return data on the event bus ++ Run this method in a multiprocess target to execute the function ++ locally and fire the return data on the event bus + """ + if daemonize and not salt.utils.platform.is_windows(): + # Shutdown the multiprocessing before daemonizing +@@ -504,7 +522,7 @@ class AsyncClientMixin(object): + low["__user__"] = user + low["__tag__"] = tag + +- return self.low(fun, low, full_return=False) ++ return self.low(fun, low) + + def cmd_async(self, low): + """ +@@ -532,14 +550,18 @@ class AsyncClientMixin(object): + tag = salt.utils.event.tagify(jid, prefix=self.tag_prefix) + return {"tag": tag, "jid": jid} + +- def asynchronous(self, fun, low, user="UNKNOWN", pub=None): ++ def asynchronous(self, fun, low, user="UNKNOWN", pub=None, local=True): + """ + Execute the function in a multiprocess and return the event tag to use + to watch for the return + """ ++ if local: ++ proc_func = self._proc_function ++ else: ++ proc_func = self._proc_function_remote + async_pub = pub if pub is not None else self._gen_async_pub() + proc = salt.utils.process.SignalHandlingProcess( +- target=self._proc_function, ++ target=proc_func, + name="ProcessFunc", + args=(fun, low, user, async_pub["tag"], async_pub["jid"]), + ) +@@ -577,9 +599,10 @@ class AsyncClientMixin(object): + if suffix == "ret": + # Check if outputter was passed in the return data. If this is the case, + # then the return data will be a dict two keys: 'data' and 'outputter' +- if isinstance(event.get("return"), dict) and set(event["return"]) == set( +- ("data", "outputter") +- ): ++ if isinstance(event.get("return"), dict) and set(event["return"]) == { ++ "data", ++ "outputter", ++ }: + event_data = event["return"]["data"] + outputter = event["return"]["outputter"] + else: +diff --git a/salt/client/ssh/client.py b/salt/client/ssh/client.py +index d2dbdeb00e..2cf42f53e7 100644 +--- a/salt/client/ssh/client.py ++++ b/salt/client/ssh/client.py +@@ -43,12 +43,58 @@ class SSHClient: + # Salt API should never offer a custom roster! + self.opts["__disable_custom_roster"] = disable_custom_roster + ++ def sanitize_kwargs(self, kwargs): ++ roster_vals = [ ++ ("host", str), ++ ("ssh_user", str), ++ ("ssh_passwd", str), ++ ("ssh_port", int), ++ ("ssh_sudo", bool), ++ ("ssh_sudo_user", str), ++ ("ssh_priv", str), ++ ("ssh_priv_passwd", str), ++ ("ssh_identities_only", bool), ++ ("ssh_remote_port_forwards", str), ++ ("ssh_options", list), ++ ("roster_file", str), ++ ("rosters", list), ++ ("ignore_host_keys", bool), ++ ("raw_shell", bool), ++ ] ++ sane_kwargs = {} ++ for name, kind in roster_vals: ++ if name not in kwargs: ++ continue ++ try: ++ val = kind(kwargs[name]) ++ except ValueError: ++ log.warn("Unable to cast kwarg %s", name) ++ continue ++ if kind is bool or kind is int: ++ sane_kwargs[name] = val ++ elif kind is str: ++ if val.find("ProxyCommand") != -1: ++ log.warn("Filter unsafe value for kwarg %s", name) ++ continue ++ sane_kwargs[name] = val ++ elif kind is list: ++ sane_val = [] ++ for item in val: ++ # This assumes the values are strings ++ if item.find("ProxyCommand") != -1: ++ log.warn("Filter unsafe value for kwarg %s", name) ++ continue ++ sane_val.append(item) ++ sane_kwargs[name] = sane_val ++ return sane_kwargs ++ + def _prep_ssh( + self, tgt, fun, arg=(), timeout=None, tgt_type="glob", kwarg=None, **kwargs + ): + """ + Prepare the arguments + """ ++ kwargs = self.sanitize_kwargs(kwargs) + opts = copy.deepcopy(self.opts) + opts.update(kwargs) + if timeout: +diff --git a/salt/cloud/clouds/qingcloud.py b/salt/cloud/clouds/qingcloud.py +index b388840dd5..f4632e167c 100644 +--- a/salt/cloud/clouds/qingcloud.py ++++ b/salt/cloud/clouds/qingcloud.py +@@ -1,4 +1,3 @@ +-# -*- coding: utf-8 -*- + """ + QingCloud Cloud Module + ====================== +@@ -26,8 +25,6 @@ Set up the cloud configuration at ``/etc/salt/cloud.providers`` or + :depends: requests + """ + +-# Import python libs +-from __future__ import absolute_import, print_function, unicode_literals + + import base64 + import hmac +@@ -46,13 +43,9 @@ from salt.exceptions import ( + SaltCloudNotFound, + SaltCloudSystemExit, + ) +- +-# Import Salt Libs +-from salt.ext import six + from salt.ext.six.moves import range + from salt.ext.six.moves.urllib.parse import quote as _quote + +-# Import Third Party Libs + try: + import requests + +@@ -110,12 +103,12 @@ def _compute_signature(parameters, access_key_secret, method, path): + """ + parameters["signature_method"] = "HmacSHA256" + +- string_to_sign = "{0}\n{1}\n".format(method.upper(), path) ++ string_to_sign = "{}\n{}\n".format(method.upper(), path) + + keys = sorted(parameters.keys()) + pairs = [] + for key in keys: +- val = six.text_type(parameters[key]).encode("utf-8") ++ val = str(parameters[key]).encode("utf-8") + pairs.append(_quote(key, safe="") + "=" + _quote(val, safe="-_~")) + qs = "&".join(pairs) + string_to_sign += qs +@@ -141,6 +134,14 @@ def query(params=None): + "secret_access_key", get_configured_provider(), __opts__, search_global=False + ) + ++ verify_ssl = config.get_cloud_config_value( ++ "verify_ssl", ++ get_configured_provider(), ++ __opts__, ++ default=True, ++ search_global=False, ++ ) ++ + # public interface parameters + real_parameters = { + "access_key_id": access_key_id, +@@ -158,9 +159,9 @@ def query(params=None): + for sk, sv in value[i - 1].items(): + if isinstance(sv, dict) or isinstance(sv, list): + sv = salt.utils.json.dumps(sv, separators=(",", ":")) +- real_parameters["{0}.{1}.{2}".format(key, i, sk)] = sv ++ real_parameters["{}.{}.{}".format(key, i, sk)] = sv + else: +- real_parameters["{0}.{1}".format(key, i)] = value[i - 1] ++ real_parameters["{}.{}".format(key, i)] = value[i - 1] + else: + real_parameters[key] = value + +@@ -171,15 +172,15 @@ def query(params=None): + # print('parameters:') + # pprint.pprint(real_parameters) + +- request = requests.get(path, params=real_parameters, verify=False) ++ request = requests.get(path, params=real_parameters, verify=verify_ssl) + + # print('url:') + # print(request.url) + + if request.status_code != 200: + raise SaltCloudSystemExit( +- "An error occurred while querying QingCloud. HTTP Code: {0} " +- "Error: '{1}'".format(request.status_code, request.text) ++ "An error occurred while querying QingCloud. HTTP Code: {} " ++ "Error: '{}'".format(request.status_code, request.text) + ) + + log.debug(request.url) +@@ -222,7 +223,7 @@ def avail_locations(call=None): + for region in items["zone_set"]: + result[region["zone_id"]] = {} + for key in region: +- result[region["zone_id"]][key] = six.text_type(region[key]) ++ result[region["zone_id"]][key] = str(region[key]) + + return result + +@@ -233,7 +234,7 @@ def _get_location(vm_=None): + """ + locations = avail_locations() + +- vm_location = six.text_type( ++ vm_location = str( + config.get_cloud_config_value("zone", vm_, __opts__, search_global=False) + ) + +@@ -244,7 +245,7 @@ def _get_location(vm_=None): + return vm_location + + raise SaltCloudNotFound( +- "The specified location, '{0}', could not be found.".format(vm_location) ++ "The specified location, '{}', could not be found.".format(vm_location) + ) + + +@@ -302,7 +303,7 @@ def _get_image(vm_): + Return the VM's image. Used by create(). + """ + images = avail_images() +- vm_image = six.text_type( ++ vm_image = str( + config.get_cloud_config_value("image", vm_, __opts__, search_global=False) + ) + +@@ -313,7 +314,7 @@ def _get_image(vm_): + return vm_image + + raise SaltCloudNotFound( +- "The specified image, '{0}', could not be found.".format(vm_image) ++ "The specified image, '{}', could not be found.".format(vm_image) + ) + + +@@ -424,7 +425,7 @@ def _get_size(vm_): + """ + sizes = avail_sizes() + +- vm_size = six.text_type( ++ vm_size = str( + config.get_cloud_config_value("size", vm_, __opts__, search_global=False) + ) + +@@ -435,7 +436,7 @@ def _get_size(vm_): + return vm_size + + raise SaltCloudNotFound( +- "The specified size, '{0}', could not be found.".format(vm_size) ++ "The specified size, '{}', could not be found.".format(vm_size) + ) + + +@@ -616,7 +617,7 @@ def show_instance(instance_id, call=None, kwargs=None): + + if items["total_count"] == 0: + raise SaltCloudNotFound( +- "The specified instance, '{0}', could not be found.".format(instance_id) ++ "The specified instance, '{}', could not be found.".format(instance_id) + ) + + full_node = items["instance_set"][0] +@@ -668,7 +669,7 @@ def create(vm_): + __utils__["cloud.fire_event"]( + "event", + "starting create", +- "salt/cloud/{0}/creating".format(vm_["name"]), ++ "salt/cloud/{}/creating".format(vm_["name"]), + args=__utils__["cloud.filter_event"]( + "creating", vm_, ["name", "profile", "provider", "driver"] + ), +@@ -693,7 +694,7 @@ def create(vm_): + __utils__["cloud.fire_event"]( + "event", + "requesting instance", +- "salt/cloud/{0}/requesting".format(vm_["name"]), ++ "salt/cloud/{}/requesting".format(vm_["name"]), + args={ + "kwargs": __utils__["cloud.filter_event"]( + "requesting", params, list(params) +@@ -724,7 +725,7 @@ def create(vm_): + except SaltCloudSystemExit: + pass + finally: +- raise SaltCloudSystemExit(six.text_type(exc)) ++ raise SaltCloudSystemExit(str(exc)) + + private_ip = data["private_ips"][0] + +@@ -742,7 +743,7 @@ def create(vm_): + __utils__["cloud.fire_event"]( + "event", + "created instance", +- "salt/cloud/{0}/created".format(vm_["name"]), ++ "salt/cloud/{}/created".format(vm_["name"]), + args=__utils__["cloud.filter_event"]( + "created", vm_, ["name", "profile", "provider", "driver"] + ), +@@ -868,7 +869,7 @@ def destroy(instance_id, call=None): + __utils__["cloud.fire_event"]( + "event", + "destroying instance", +- "salt/cloud/{0}/destroying".format(name), ++ "salt/cloud/{}/destroying".format(name), + args={"name": name}, + sock_dir=__opts__["sock_dir"], + transport=__opts__["transport"], +@@ -884,7 +885,7 @@ def destroy(instance_id, call=None): + __utils__["cloud.fire_event"]( + "event", + "destroyed instance", +- "salt/cloud/{0}/destroyed".format(name), ++ "salt/cloud/{}/destroyed".format(name), + args={"name": name}, + sock_dir=__opts__["sock_dir"], + transport=__opts__["transport"], +diff --git a/salt/cloud/clouds/vmware.py b/salt/cloud/clouds/vmware.py +index edaca9618b..851579bf74 100644 +--- a/salt/cloud/clouds/vmware.py ++++ b/salt/cloud/clouds/vmware.py +@@ -1,4 +1,3 @@ +-# -*- coding: utf-8 -*- + # pylint: disable=C0302 + """ + VMware Cloud Module +@@ -114,8 +113,6 @@ To test the connection for ``my-vmware-config`` specified in the cloud + configuration, run :py:func:`test_vcenter_connection` + """ + +-# Import python libs +-from __future__ import absolute_import, print_function, unicode_literals + + import logging + import os.path +@@ -125,10 +122,7 @@ import subprocess + import time + from random import randint + +-# Import salt cloud libs + import salt.config as config +- +-# Import salt libs + import salt.utils.cloud + import salt.utils.network + import salt.utils.stringutils +@@ -136,9 +130,6 @@ import salt.utils.vmware + import salt.utils.xmlutil + from salt.exceptions import SaltCloudSystemExit + +-# Import 3rd-party libs +-from salt.ext import six +- + try: + # Attempt to import pyVmomi libs + from pyVmomi import vim # pylint: disable=no-name-in-module +@@ -230,7 +221,7 @@ def _str_to_bool(var): + if isinstance(var, bool): + return var + +- if isinstance(var, six.string_types): ++ if isinstance(var, str): + return True if var.lower() == "true" else False + + return None +@@ -260,9 +251,15 @@ def _get_si(): + port = config.get_cloud_config_value( + "port", get_configured_provider(), __opts__, search_global=False, default=443 + ) +- ++ verify_ssl = config.get_cloud_config_value( ++ "verify_ssl", ++ get_configured_provider(), ++ __opts__, ++ search_global=False, ++ default=True, ++ ) + return salt.utils.vmware.get_service_instance( +- url, username, password, protocol=protocol, port=port ++ url, username, password, protocol=protocol, port=port, verify_ssl=verify_ssl + ) + + +@@ -299,7 +296,7 @@ def _add_new_hard_disk_helper( + disk_spec.device.key = random_key + disk_spec.device.deviceInfo = vim.Description() + disk_spec.device.deviceInfo.label = disk_label +- disk_spec.device.deviceInfo.summary = "{0} GB".format(size_gb) ++ disk_spec.device.deviceInfo.summary = "{} GB".format(size_gb) + + disk_spec.device.backing = vim.vm.device.VirtualDisk.FlatVer2BackingInfo() + disk_spec.device.backing.thinProvisioned = thin_provision +@@ -320,7 +317,7 @@ def _add_new_hard_disk_helper( + if not datastore_cluster_ref: + # datastore/datastore cluster specified does not exist + raise SaltCloudSystemExit( +- "Specified datastore/datastore cluster ({0}) for disk ({1}) does not exist".format( ++ "Specified datastore/datastore cluster ({}) for disk ({}) does not exist".format( + datastore, disk_label + ) + ) +@@ -351,12 +348,12 @@ def _add_new_hard_disk_helper( + if not datastore_ref: + # datastore cluster specified does not have any accessible datastores + raise SaltCloudSystemExit( +- "Specified datastore cluster ({0}) for disk ({1}) does not have any accessible datastores available".format( ++ "Specified datastore cluster ({}) for disk ({}) does not have any accessible datastores available".format( + datastore, disk_label + ) + ) + +- datastore_path = "[" + six.text_type(datastore_ref.name) + "] " + vm_name ++ datastore_path = "[" + str(datastore_ref.name) + "] " + vm_name + disk_spec.device.backing.fileName = datastore_path + "/" + disk_label + ".vmdk" + disk_spec.device.backing.datastore = datastore_ref + log.trace( +@@ -429,11 +426,11 @@ def _edit_existing_network_adapter( + else: + # If switch type not specified or does not match, show error and return + if not switch_type: +- err_msg = "The switch type to be used by '{0}' has not been specified".format( ++ err_msg = "The switch type to be used by '{}' has not been specified".format( + network_adapter.deviceInfo.label + ) + else: +- err_msg = "Cannot create '{0}'. Invalid/unsupported switch type '{1}'".format( ++ err_msg = "Cannot create '{}'. Invalid/unsupported switch type '{}'".format( + network_adapter.deviceInfo.label, switch_type + ) + raise SaltCloudSystemExit(err_msg) +@@ -516,11 +513,11 @@ def _add_new_network_adapter_helper( + else: + # If switch type not specified or does not match, show error and return + if not switch_type: +- err_msg = "The switch type to be used by '{0}' has not been specified".format( ++ err_msg = "The switch type to be used by '{}' has not been specified".format( + network_adapter_label + ) + else: +- err_msg = "Cannot create '{0}'. Invalid/unsupported switch type '{1}'".format( ++ err_msg = "Cannot create '{}'. Invalid/unsupported switch type '{}'".format( + network_adapter_label, switch_type + ) + raise SaltCloudSystemExit(err_msg) +@@ -572,11 +569,11 @@ def _add_new_scsi_controller_helper(scsi_controller_label, properties, bus_numbe + else: + # If type not specified or does not match, show error and return + if not adapter_type: +- err_msg = "The type of '{0}' has not been specified".format( ++ err_msg = "The type of '{}' has not been specified".format( + scsi_controller_label + ) + else: +- err_msg = "Cannot create '{0}'. Invalid/unsupported type '{1}'".format( ++ err_msg = "Cannot create '{}'. Invalid/unsupported type '{}'".format( + scsi_controller_label, adapter_type + ) + raise SaltCloudSystemExit(err_msg) +@@ -653,7 +650,7 @@ def _set_cd_or_dvd_backing_type(drive, device_type, mode, iso_path): + if datastore_ref: + drive.backing.datastore = datastore_ref + +- drive.deviceInfo.summary = "ISO {0}".format(iso_path) ++ drive.deviceInfo.summary = "ISO {}".format(iso_path) + + elif device_type == "client_device": + if mode == "passthrough": +@@ -735,8 +732,8 @@ def _set_network_adapter_mapping(adapter_specs): + gateway = adapter_specs["gateway"] + adapter_mapping.adapter.gateway = gateway + if "ip" in list(adapter_specs.keys()): +- ip = six.text_type(adapter_specs["ip"]) +- subnet_mask = six.text_type(adapter_specs["subnet_mask"]) ++ ip = str(adapter_specs["ip"]) ++ subnet_mask = str(adapter_specs["subnet_mask"]) + adapter_mapping.adapter.ip = vim.vm.customization.FixedIp(ipAddress=ip) + adapter_mapping.adapter.subnetMask = subnet_mask + else: +@@ -823,8 +820,8 @@ def _manage_devices(devices, vm=None, container_ref=None, new_vm_name=None): + + if device.capacityInKB > size_kb: + raise SaltCloudSystemExit( +- "The specified disk size '{0}GB' for '{1}' is " +- "smaller than the disk image size '{2}GB'. It must " ++ "The specified disk size '{}GB' for '{}' is " ++ "smaller than the disk image size '{}GB'. It must " + "be equal to or greater than the disk image".format( + float( + devices["disk"][device.deviceInfo.label]["size"] +@@ -908,7 +905,7 @@ def _manage_devices(devices, vm=None, container_ref=None, new_vm_name=None): + else None + ) + if bus_sharing and bus_sharing in ["virtual", "physical", "no"]: +- bus_sharing = "{0}Sharing".format(bus_sharing) ++ bus_sharing = "{}Sharing".format(bus_sharing) + if bus_sharing != device.sharedBus: + # Only edit the SCSI controller if bus_sharing is different + scsi_spec = _edit_existing_scsi_controller( +@@ -1112,7 +1109,7 @@ def _manage_devices(devices, vm=None, container_ref=None, new_vm_name=None): + ide_controllers[controller_key] = 0 + break + else: +- for ide_controller_key, num_devices in six.iteritems(ide_controllers): ++ for ide_controller_key, num_devices in ide_controllers.items(): + if num_devices < 2: + controller_key = ide_controller_key + break +@@ -1145,10 +1142,7 @@ def _wait_for_vmware_tools(vm_ref, max_wait): + vm_ref.name, + time_counter, + ) +- if ( +- six.text_type(vm_ref.summary.guest.toolsRunningStatus) +- == "guestToolsRunning" +- ): ++ if str(vm_ref.summary.guest.toolsRunningStatus) == "guestToolsRunning": + log.info( + "[ %s ] Successfully got VMware tools running on the guest in " + "%s seconds", +@@ -1314,23 +1308,21 @@ def _format_instance_info_select(vm, selection): + vm_select_info["id"] = vm["name"] + + if "image" in selection: +- vm_select_info["image"] = "{0} (Detected)".format( ++ vm_select_info["image"] = "{} (Detected)".format( + defaultto(vm, "config.guestFullName") + ) + + if "size" in selection: + cpu = defaultto(vm, "config.hardware.numCPU") +- ram = "{0} MB".format(defaultto(vm, "config.hardware.memoryMB")) +- vm_select_info["size"] = "cpu: {0}\nram: {1}".format(cpu, ram) ++ ram = "{} MB".format(defaultto(vm, "config.hardware.memoryMB")) ++ vm_select_info["size"] = "cpu: {}\nram: {}".format(cpu, ram) + vm_select_info["size_dict"] = { + "cpu": cpu, + "memory": ram, + } + + if "state" in selection: +- vm_select_info["state"] = six.text_type( +- defaultto(vm, "summary.runtime.powerState") +- ) ++ vm_select_info["state"] = str(defaultto(vm, "summary.runtime.powerState")) + + if "guest_id" in selection: + vm_select_info["guest_id"] = defaultto(vm, "config.guestId") +@@ -1342,9 +1334,7 @@ def _format_instance_info_select(vm, selection): + vm_select_info["path"] = defaultto(vm, "config.files.vmPathName") + + if "tools_status" in selection: +- vm_select_info["tools_status"] = six.text_type( +- defaultto(vm, "guest.toolsStatus") +- ) ++ vm_select_info["tools_status"] = str(defaultto(vm, "guest.toolsStatus")) + + if "private_ips" in selection or "networks" in selection: + network_full_info = {} +@@ -1585,18 +1575,18 @@ def _format_instance_info(vm): + + cpu = vm["config.hardware.numCPU"] if "config.hardware.numCPU" in vm else "N/A" + ram = ( +- "{0} MB".format(vm["config.hardware.memoryMB"]) ++ "{} MB".format(vm["config.hardware.memoryMB"]) + if "config.hardware.memoryMB" in vm + else "N/A" + ) + vm_full_info = { +- "id": six.text_type(vm["name"]), +- "image": "{0} (Detected)".format(vm["config.guestFullName"]) ++ "id": str(vm["name"]), ++ "image": "{} (Detected)".format(vm["config.guestFullName"]) + if "config.guestFullName" in vm + else "N/A", +- "size": "cpu: {0}\nram: {1}".format(cpu, ram), ++ "size": "cpu: {}\nram: {}".format(cpu, ram), + "size_dict": {"cpu": cpu, "memory": ram}, +- "state": six.text_type(vm["summary.runtime.powerState"]) ++ "state": str(vm["summary.runtime.powerState"]) + if "summary.runtime.powerState" in vm + else "N/A", + "private_ips": ip_addresses, +@@ -1604,16 +1594,14 @@ def _format_instance_info(vm): + "devices": device_full_info, + "storage": storage_full_info, + "files": file_full_info, +- "guest_id": six.text_type(vm["config.guestId"]) +- if "config.guestId" in vm +- else "N/A", +- "hostname": six.text_type(vm["object"].guest.hostName), ++ "guest_id": str(vm["config.guestId"]) if "config.guestId" in vm else "N/A", ++ "hostname": str(vm["object"].guest.hostName), + "mac_addresses": device_mac_addresses, + "networks": network_full_info, +- "path": six.text_type(vm["config.files.vmPathName"]) ++ "path": str(vm["config.files.vmPathName"]) + if "config.files.vmPathName" in vm + else "N/A", +- "tools_status": six.text_type(vm["guest.toolsStatus"]) ++ "tools_status": str(vm["guest.toolsStatus"]) + if "guest.toolsStatus" in vm + else "N/A", + } +@@ -1624,11 +1612,11 @@ def _format_instance_info(vm): + def _get_snapshots(snapshot_list, current_snapshot=None, parent_snapshot_path=""): + snapshots = {} + for snapshot in snapshot_list: +- snapshot_path = "{0}/{1}".format(parent_snapshot_path, snapshot.name) ++ snapshot_path = "{}/{}".format(parent_snapshot_path, snapshot.name) + snapshots[snapshot_path] = { + "name": snapshot.name, + "description": snapshot.description, +- "created": six.text_type(snapshot.createTime).split(".")[0], ++ "created": str(snapshot.createTime).split(".")[0], + "state": snapshot.state, + "path": snapshot_path, + } +@@ -1760,7 +1748,7 @@ def test_vcenter_connection(kwargs=None, call=None): + # Get the service instance object + _get_si() + except Exception as exc: # pylint: disable=broad-except +- return "failed to connect: {0}".format(exc) ++ return "failed to connect: {}".format(exc) + + return "connection successful" + +@@ -2000,18 +1988,18 @@ def list_nodes(kwargs=None, call=None): + for vm in vm_list: + cpu = vm["config.hardware.numCPU"] if "config.hardware.numCPU" in vm else "N/A" + ram = ( +- "{0} MB".format(vm["config.hardware.memoryMB"]) ++ "{} MB".format(vm["config.hardware.memoryMB"]) + if "config.hardware.memoryMB" in vm + else "N/A" + ) + vm_info = { + "id": vm["name"], +- "image": "{0} (Detected)".format(vm["config.guestFullName"]) ++ "image": "{} (Detected)".format(vm["config.guestFullName"]) + if "config.guestFullName" in vm + else "N/A", +- "size": "cpu: {0}\nram: {1}".format(cpu, ram), ++ "size": "cpu: {}\nram: {}".format(cpu, ram), + "size_dict": {"cpu": cpu, "memory": ram}, +- "state": six.text_type(vm["summary.runtime.powerState"]) ++ "state": str(vm["summary.runtime.powerState"]) + if "summary.runtime.powerState" in vm + else "N/A", + "private_ips": [vm["guest.ipAddress"]] if "guest.ipAddress" in vm else [], +@@ -2660,7 +2648,7 @@ def destroy(name, call=None): + __utils__["cloud.fire_event"]( + "event", + "destroying instance", +- "salt/cloud/{0}/destroying".format(name), ++ "salt/cloud/{}/destroying".format(name), + args={"name": name}, + sock_dir=__opts__["sock_dir"], + transport=__opts__["transport"], +@@ -2706,7 +2694,7 @@ def destroy(name, call=None): + __utils__["cloud.fire_event"]( + "event", + "destroyed instance", +- "salt/cloud/{0}/destroyed".format(name), ++ "salt/cloud/{}/destroyed".format(name), + args={"name": name}, + sock_dir=__opts__["sock_dir"], + transport=__opts__["transport"], +@@ -2748,7 +2736,7 @@ def create(vm_): + __utils__["cloud.fire_event"]( + "event", + "starting create", +- "salt/cloud/{0}/creating".format(vm_["name"]), ++ "salt/cloud/{}/creating".format(vm_["name"]), + args=__utils__["cloud.filter_event"]( + "creating", vm_, ["name", "profile", "provider", "driver"] + ), +@@ -2825,10 +2813,10 @@ def create(vm_): + "win_run_once", vm_, __opts__, search_global=False, default=None + ) + cpu_hot_add = config.get_cloud_config_value( +- 'cpu_hot_add', vm_, __opts__, search_global=False, default=None ++ "cpu_hot_add", vm_, __opts__, search_global=False, default=None + ) + mem_hot_add = config.get_cloud_config_value( +- 'mem_hot_add', vm_, __opts__, search_global=False, default=None ++ "mem_hot_add", vm_, __opts__, search_global=False, default=None + ) + + # Get service instance object +@@ -2988,7 +2976,7 @@ def create(vm_): + ) + if not datastore_ref: + raise SaltCloudSystemExit( +- "Specified datastore: '{0}' does not exist".format(datastore) ++ "Specified datastore: '{}' does not exist".format(datastore) + ) + + if host: +@@ -3004,7 +2992,7 @@ def create(vm_): + # If the hardware version is specified and if it is different from the current + # hardware version, then schedule a hardware version upgrade + if hardware_version and object_ref is not None: +- hardware_version = "vmx-{0:02}".format(hardware_version) ++ hardware_version = "vmx-{:02}".format(hardware_version) + if hardware_version != object_ref.config.version: + log.debug( + "Scheduling hardware version upgrade from %s to %s", +@@ -3034,7 +3022,7 @@ def create(vm_): + elif memory_unit.lower() == "gb": + memory_mb = int(float(memory_num) * 1024.0) + else: +- err_msg = "Invalid memory type specified: '{0}'".format(memory_unit) ++ err_msg = "Invalid memory type specified: '{}'".format(memory_unit) + log.error(err_msg) + return {"Error": err_msg} + except (TypeError, ValueError): +@@ -3048,19 +3036,19 @@ def create(vm_): + ) + config_spec.deviceChange = specs["device_specs"] + +- if cpu_hot_add and hasattr(config_spec, 'cpuHotAddEnabled'): ++ if cpu_hot_add and hasattr(config_spec, "cpuHotAddEnabled"): + config_spec.cpuHotAddEnabled = bool(cpu_hot_add) + +- if mem_hot_add and hasattr(config_spec, 'memoryHotAddEnabled'): ++ if mem_hot_add and hasattr(config_spec, "memoryHotAddEnabled"): + config_spec.memoryHotAddEnabled = bool(mem_hot_add) + + if extra_config: +- for key, value in six.iteritems(extra_config): ++ for key, value in extra_config.items(): + option = vim.option.OptionValue(key=key, value=value) + config_spec.extraConfig.append(option) + + if annotation: +- config_spec.annotation = six.text_type(annotation) ++ config_spec.annotation = str(annotation) + + if "clonefrom" in vm_: + clone_spec = handle_snapshot(config_spec, object_ref, reloc_spec, template, vm_) +@@ -3137,7 +3125,7 @@ def create(vm_): + __utils__["cloud.fire_event"]( + "event", + "requesting instance", +- "salt/cloud/{0}/requesting".format(vm_["name"]), ++ "salt/cloud/{}/requesting".format(vm_["name"]), + args=__utils__["cloud.filter_event"]( + "requesting", event_kwargs, list(event_kwargs) + ), +@@ -3190,7 +3178,7 @@ def create(vm_): + task = folder_ref.CreateVM_Task(config_spec, resourcepool_ref) + salt.utils.vmware.wait_for_task(task, vm_name, "create", 15, "info") + except Exception as exc: # pylint: disable=broad-except +- err_msg = "Error creating {0}: {1}".format(vm_["name"], exc) ++ err_msg = "Error creating {}: {}".format(vm_["name"], exc) + log.error( + err_msg, + # Show the traceback if the debug logging level is enabled +@@ -3235,7 +3223,7 @@ def create(vm_): + __utils__["cloud.fire_event"]( + "event", + "created instance", +- "salt/cloud/{0}/created".format(vm_["name"]), ++ "salt/cloud/{}/created".format(vm_["name"]), + args=__utils__["cloud.filter_event"]( + "created", vm_, ["name", "profile", "provider", "driver"] + ), +@@ -3267,7 +3255,7 @@ def handle_snapshot(config_spec, object_ref, reloc_spec, template, vm_): + raise SaltCloudSystemExit( + "Invalid disk move type specified" + " supported types are" +- " {0}".format(" ".join(allowed_types)) ++ " {}".format(" ".join(allowed_types)) + ) + return clone_spec + +@@ -3470,7 +3458,7 @@ def rescan_hba(kwargs=None, call=None): + if hba: + log.info("Rescanning HBA %s on host %s", hba, host_name) + host_ref.configManager.storageSystem.RescanHba(hba) +- ret = "rescanned HBA {0}".format(hba) ++ ret = "rescanned HBA {}".format(hba) + else: + log.info("Rescanning all HBAs on host %s", host_name) + host_ref.configManager.storageSystem.RescanAllHba() +@@ -3749,7 +3737,7 @@ def list_hbas(kwargs=None, call=None): + + if hba_type and hba_type not in ["parallel", "block", "iscsi", "fibre"]: + raise SaltCloudSystemExit( +- "Specified hba type {0} currently not supported.".format(hba_type) ++ "Specified hba type {} currently not supported.".format(hba_type) + ) + + host_list = salt.utils.vmware.get_mors_with_properties( +@@ -4124,10 +4112,10 @@ def revert_to_snapshot(name, kwargs=None, call=None): + task = vm_ref.RevertToCurrentSnapshot(suppressPowerOn=suppress_power_on) + else: + log.debug("Reverting VM %s to snapshot %s", name, snapshot_name) +- msg = "reverted to snapshot {0}".format(snapshot_name) ++ msg = "reverted to snapshot {}".format(snapshot_name) + snapshot_ref = _get_snapshot_ref_by_name(vm_ref, snapshot_name) + if snapshot_ref is None: +- return "specified snapshot '{0}' does not exist".format(snapshot_name) ++ return "specified snapshot '{}' does not exist".format(snapshot_name) + task = snapshot_ref.snapshot.Revert(suppressPowerOn=suppress_power_on) + + salt.utils.vmware.wait_for_task(task, name, "revert to snapshot", 5, "info") +@@ -4265,7 +4253,7 @@ def convert_to_template(name, kwargs=None, call=None): + vm_ref = salt.utils.vmware.get_mor_by_property(_get_si(), vim.VirtualMachine, name) + + if vm_ref.config.template: +- raise SaltCloudSystemExit("{0} already a template".format(name)) ++ raise SaltCloudSystemExit("{} already a template".format(name)) + + try: + vm_ref.MarkAsTemplate() +@@ -4279,7 +4267,7 @@ def convert_to_template(name, kwargs=None, call=None): + ) + return "failed to convert to teamplate" + +- return "{0} converted to template".format(name) ++ return "{} converted to template".format(name) + + + def add_host(kwargs=None, call=None): +@@ -4399,7 +4387,7 @@ def add_host(kwargs=None, call=None): + ("echo", "-n"), stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + p2 = subprocess.Popen( +- ("openssl", "s_client", "-connect", "{0}:443".format(host_name)), ++ ("openssl", "s_client", "-connect", "{}:443".format(host_name)), + stdin=p1.stdout, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, +@@ -4429,12 +4417,12 @@ def add_host(kwargs=None, call=None): + try: + if cluster_name: + task = cluster_ref.AddHost(spec=spec, asConnected=True) +- ret = "added host system to cluster {0}".format(cluster_name) ++ ret = "added host system to cluster {}".format(cluster_name) + if datacenter_name: + task = datacenter_ref.hostFolder.AddStandaloneHost( + spec=spec, addConnected=True + ) +- ret = "added host system to datacenter {0}".format(datacenter_name) ++ ret = "added host system to datacenter {}".format(datacenter_name) + salt.utils.vmware.wait_for_task(task, host_name, "add host system", 5, "info") + except Exception as exc: # pylint: disable=broad-except + if isinstance(exc, vim.fault.SSLVerifyFault): +diff --git a/salt/config/schemas/vcenter.py b/salt/config/schemas/vcenter.py +index 7db8b67c41..bd82bd1761 100644 +--- a/salt/config/schemas/vcenter.py ++++ b/salt/config/schemas/vcenter.py +@@ -1,4 +1,3 @@ +-# -*- coding: utf-8 -*- + """ + :codeauthor: :email:`Rod McKenzie (roderick.mckenzie@morganstanley.com)` + :codeauthor: :email:`Alexandru Bleotu (alexandru.bleotu@morganstanley.com)` +@@ -9,11 +8,8 @@ + VCenter configuration schemas + """ + +-# Import Python libs +-from __future__ import absolute_import, print_function, unicode_literals + +-# Import Salt libs +-from salt.utils.schema import ArrayItem, IntegerItem, Schema, StringItem ++from salt.utils.schema import ArrayItem, BooleanItem, IntegerItem, Schema, StringItem + + + class VCenterEntitySchema(Schema): +@@ -48,6 +44,8 @@ class VCenterProxySchema(Schema): + mechanism = StringItem(required=True, enum=["userpass", "sspi"]) + username = StringItem() + passwords = ArrayItem(min_items=1, items=StringItem(), unique_items=True) ++ verify_ssl = BooleanItem() ++ ca_bundle = StringItem() + + domain = StringItem() + principal = StringItem(default="host") +diff --git a/salt/master.py b/salt/master.py +index 59bb19ce75..fc103ac489 100644 +--- a/salt/master.py ++++ b/salt/master.py +@@ -2126,7 +2126,7 @@ class ClearFuncs(TransportMethods): + fun = clear_load.pop("fun") + runner_client = salt.runner.RunnerClient(self.opts) + return runner_client.asynchronous( +- fun, clear_load.get("kwarg", {}), username ++ fun, clear_load.get("kwarg", {}), username, local=True + ) + except Exception as exc: # pylint: disable=broad-except + log.error("Exception occurred while introspecting %s: %s", fun, exc) +diff --git a/salt/modules/bigip.py b/salt/modules/bigip.py +index 2b54e4d27c..36168d66b4 100644 +--- a/salt/modules/bigip.py ++++ b/salt/modules/bigip.py +@@ -1,21 +1,14 @@ +-# -*- coding: utf-8 -*- + """ + An execution module which can manipulate an f5 bigip via iControl REST + :maturity: develop + :platform: f5_bigip_11.6 + """ + +-# Import python libs +-from __future__ import absolute_import, print_function, unicode_literals + +-# Import salt libs + import salt.exceptions + import salt.utils.json +- +-# Import 3rd-party libs + from salt.ext import six + +-# Import third party libs + try: + import requests + import requests.exceptions +@@ -52,7 +45,7 @@ def _build_session(username, password, trans_label=None): + + bigip = requests.session() + bigip.auth = (username, password) +- bigip.verify = False ++ bigip.verify = True + bigip.headers.update({"Content-Type": "application/json"}) + + if trans_label: +@@ -109,7 +102,7 @@ def _loop_payload(params): + payload = {} + + # set the payload +- for param, value in six.iteritems(params): ++ for param, value in params.items(): + if value is not None: + payload[param] = value + +@@ -153,7 +146,7 @@ def _determine_toggles(payload, toggles): + Figure out what it likes to hear without confusing the user. + """ + +- for toggle, definition in six.iteritems(toggles): ++ for toggle, definition in toggles.items(): + # did the user specify anything? + if definition["value"] is not None: + # test for yes_no toggle +@@ -1046,7 +1039,7 @@ def replace_pool_members(hostname, username, password, name, members): + # specify members if provided + if members is not None: + +- if isinstance(members, six.string_types): ++ if isinstance(members, str): + members = members.split(",") + + pool_members = [] +@@ -1583,7 +1576,7 @@ def create_virtual( + payload["vlans"] = "none" + elif vlans == "default": + payload["vlans"] = "default" +- elif isinstance(vlans, six.string_types) and ( ++ elif isinstance(vlans, str) and ( + vlans.startswith("enabled") or vlans.startswith("disabled") + ): + try: +@@ -2016,7 +2009,7 @@ def create_monitor(hostname, username, password, monitor_type, name, **kwargs): + + # there's a ton of different monitors and a ton of options for each type of monitor. + # this logic relies that the end user knows which options are meant for which monitor types +- for key, value in six.iteritems(kwargs): ++ for key, value in kwargs.items(): + if not key.startswith("__"): + if key not in ["hostname", "username", "password", "type"]: + key = key.replace("_", "-") +@@ -2067,7 +2060,7 @@ def modify_monitor(hostname, username, password, monitor_type, name, **kwargs): + + # there's a ton of different monitors and a ton of options for each type of monitor. + # this logic relies that the end user knows which options are meant for which monitor types +- for key, value in six.iteritems(kwargs): ++ for key, value in kwargs.items(): + if not key.startswith("__"): + if key not in ["hostname", "username", "password", "type", "name"]: + key = key.replace("_", "-") +@@ -2231,7 +2224,7 @@ def create_profile(hostname, username, password, profile_type, name, **kwargs): + + # there's a ton of different profiles and a ton of options for each type of profile. + # this logic relies that the end user knows which options are meant for which profile types +- for key, value in six.iteritems(kwargs): ++ for key, value in kwargs.items(): + if not key.startswith("__"): + if key not in ["hostname", "username", "password", "profile_type"]: + key = key.replace("_", "-") +@@ -2322,7 +2315,7 @@ def modify_profile(hostname, username, password, profile_type, name, **kwargs): + + # there's a ton of different profiles and a ton of options for each type of profile. + # this logic relies that the end user knows which options are meant for which profile types +- for key, value in six.iteritems(kwargs): ++ for key, value in kwargs.items(): + if not key.startswith("__"): + if key not in ["hostname", "username", "password", "profile_type"]: + key = key.replace("_", "-") +diff --git a/salt/modules/cmdmod.py b/salt/modules/cmdmod.py +index c8eb4d2305..bbc303c3f8 100644 +--- a/salt/modules/cmdmod.py ++++ b/salt/modules/cmdmod.py +@@ -77,6 +77,12 @@ def __virtual__(): + return __virtualname__ + + ++def _log_cmd(cmd): ++ if not isinstance(cmd, list): ++ return cmd.split()[0].strip() ++ return cmd[0].strip() ++ ++ + def _check_cb(cb_): + """ + If the callback is None or is not callable, return a lambda that returns +@@ -386,22 +392,13 @@ def _run( + ) + env[bad_env_key] = "" + +- def _get_stripped(cmd): +- # Return stripped command string copies to improve logging. +- if isinstance(cmd, list): +- return [x.strip() if isinstance(x, str) else x for x in cmd] +- elif isinstance(cmd, str): +- return cmd.strip() +- else: +- return cmd +- + if output_loglevel is not None: + # Always log the shell commands at INFO unless quiet logging is + # requested. The command output is what will be controlled by the + # 'loglevel' parameter. + msg = "Executing command {}{}{} {}{}in directory '{}'{}".format( + "'" if not isinstance(cmd, list) else "", +- _get_stripped(cmd), ++ _log_cmd(cmd), + "'" if not isinstance(cmd, list) else "", + "as user '{}' ".format(runas) if runas else "", + "in group '{}' ".format(group) if group else "", +@@ -723,7 +720,7 @@ def _run( + log.error( + "Failed to decode stdout from command %s, non-decodable " + "characters have been replaced", +- cmd, ++ _log_cmd(cmd), + ) + + try: +@@ -741,7 +738,7 @@ def _run( + log.error( + "Failed to decode stderr from command %s, non-decodable " + "characters have been replaced", +- cmd, ++ _log_cmd(cmd), + ) + + if rstrip: +@@ -841,7 +838,9 @@ def _run( + if not ignore_retcode and ret["retcode"] != 0: + if output_loglevel < LOG_LEVELS["error"]: + output_loglevel = LOG_LEVELS["error"] +- msg = "Command '{}' failed with return code: {}".format(cmd, ret["retcode"]) ++ msg = "Command '{}' failed with return code: {}".format( ++ _log_cmd(cmd), ret["retcode"] ++ ) + log.error(log_callback(msg)) + if ret["stdout"]: + log.log(output_loglevel, "stdout: %s", log_callback(ret["stdout"])) +@@ -1211,7 +1210,9 @@ def run( + if not ignore_retcode and ret["retcode"] != 0: + if lvl < LOG_LEVELS["error"]: + lvl = LOG_LEVELS["error"] +- msg = "Command '{}' failed with return code: {}".format(cmd, ret["retcode"]) ++ msg = "Command '{}' failed with return code: {}".format( ++ _log_cmd(cmd), ret["retcode"] ++ ) + log.error(log_callback(msg)) + if raise_err: + raise CommandExecutionError( +diff --git a/salt/modules/glassfish.py b/salt/modules/glassfish.py +index 44df1d3cbb..59a171d2e6 100644 +--- a/salt/modules/glassfish.py ++++ b/salt/modules/glassfish.py +@@ -1,10 +1,8 @@ +-# -*- coding: utf-8 -*- + """ + Module for working with the Glassfish/Payara 4.x management API + .. versionadded:: Carbon + :depends: requests + """ +-from __future__ import absolute_import, print_function, unicode_literals + + import salt.defaults.exitcodes + import salt.utils.json +@@ -42,7 +40,7 @@ def __virtual__(): + else: + return ( + False, +- 'The "{0}" module could not be loaded: ' ++ 'The "{}" module could not be loaded: ' + '"requests" is not installed.'.format(__virtualname__), + ) + +@@ -73,9 +71,9 @@ def _get_url(ssl, url, port, path): + Returns the URL of the endpoint + """ + if ssl: +- return "https://{0}:{1}/management/domain/{2}".format(url, port, path) ++ return "https://{}:{}/management/domain/{}".format(url, port, path) + else: +- return "http://{0}:{1}/management/domain/{2}".format(url, port, path) ++ return "http://{}:{}/management/domain/{}".format(url, port, path) + + + def _get_server(server): +@@ -128,7 +126,7 @@ def _api_get(path, server=None): + url=_get_url(server["ssl"], server["url"], server["port"], path), + auth=_get_auth(server["user"], server["password"]), + headers=_get_headers(), +- verify=False, ++ verify=True, + ) + return _api_response(response) + +@@ -143,7 +141,7 @@ def _api_post(path, data, server=None): + auth=_get_auth(server["user"], server["password"]), + headers=_get_headers(), + data=salt.utils.json.dumps(data), +- verify=False, ++ verify=True, + ) + return _api_response(response) + +@@ -158,7 +156,7 @@ def _api_delete(path, data, server=None): + auth=_get_auth(server["user"], server["password"]), + headers=_get_headers(), + params=data, +- verify=False, ++ verify=True, + ) + return _api_response(response) + +@@ -183,7 +181,7 @@ def _get_element_properties(name, element_type, server=None): + Get an element's properties + """ + properties = {} +- data = _api_get("{0}/{1}/property".format(element_type, name), server) ++ data = _api_get("{}/{}/property".format(element_type, name), server) + + # Get properties into a dict + if any(data["extraProperties"]["properties"]): +@@ -199,7 +197,7 @@ def _get_element(name, element_type, server=None, with_properties=True): + """ + element = {} + name = quote(name, safe="") +- data = _api_get("{0}/{1}".format(element_type, name), server) ++ data = _api_get("{}/{}".format(element_type, name), server) + + # Format data, get properties if asked, and return the whole thing + if any(data["extraProperties"]["entity"]): +@@ -220,9 +218,9 @@ def _create_element(name, element_type, data, server=None): + data["property"] = "" + for key, value in data["properties"].items(): + if not data["property"]: +- data["property"] += "{0}={1}".format(key, value.replace(":", "\\:")) ++ data["property"] += "{}={}".format(key, value.replace(":", "\\:")) + else: +- data["property"] += ":{0}={1}".format(key, value.replace(":", "\\:")) ++ data["property"] += ":{}={}".format(key, value.replace(":", "\\:")) + del data["properties"] + + # Send request +@@ -242,7 +240,7 @@ def _update_element(name, element_type, data, server=None): + properties = [] + for key, value in data["properties"].items(): + properties.append({"name": key, "value": value}) +- _api_post("{0}/{1}/property".format(element_type, name), properties, server) ++ _api_post("{}/{}/property".format(element_type, name), properties, server) + del data["properties"] + + # If the element only contained properties +@@ -255,10 +253,10 @@ def _update_element(name, element_type, data, server=None): + update_data.update(data) + else: + __context__["retcode"] = salt.defaults.exitcodes.SALT_BUILD_FAIL +- raise CommandExecutionError("Cannot update {0}".format(name)) ++ raise CommandExecutionError("Cannot update {}".format(name)) + + # Finally, update the element +- _api_post("{0}/{1}".format(element_type, name), _clean_data(update_data), server) ++ _api_post("{}/{}".format(element_type, name), _clean_data(update_data), server) + return unquote(name) + + +@@ -266,7 +264,7 @@ def _delete_element(name, element_type, data, server=None): + """ + Delete an element + """ +- _api_delete("{0}/{1}".format(element_type, quote(name, safe="")), data, server) ++ _api_delete("{}/{}".format(element_type, quote(name, safe="")), data, server) + return name + + +@@ -692,4 +690,4 @@ def delete_system_properties(name, server=None): + """ + Delete a system property + """ +- _api_delete("system-properties/{0}".format(name), None, server) ++ _api_delete("system-properties/{}".format(name), None, server) +diff --git a/salt/modules/keystone.py b/salt/modules/keystone.py +index 52cb461339..e8dd2fd99d 100644 +--- a/salt/modules/keystone.py ++++ b/salt/modules/keystone.py +@@ -1,4 +1,3 @@ +-# -*- coding: utf-8 -*- + """ + Module for handling openstack keystone calls. + +@@ -13,6 +12,7 @@ Module for handling openstack keystone calls. + keystone.tenant: admin + keystone.tenant_id: f80919baedab48ec8931f200c65a50df + keystone.auth_url: 'http://127.0.0.1:5000/v2.0/' ++ keystone.verify_ssl: True + + OR (for token based authentication) + +@@ -32,6 +32,7 @@ Module for handling openstack keystone calls. + keystone.tenant: admin + keystone.tenant_id: f80919baedab48ec8931f200c65a50df + keystone.auth_url: 'http://127.0.0.1:5000/v2.0/' ++ keystone.verify_ssl: True + + openstack2: + keystone.user: admin +@@ -39,6 +40,7 @@ Module for handling openstack keystone calls. + keystone.tenant: admin + keystone.tenant_id: f80919baedab48ec8931f200c65a50df + keystone.auth_url: 'http://127.0.0.2:5000/v2.0/' ++ keystone.verify_ssl: True + + With this configuration in place, any of the keystone functions can make use + of a configuration profile by declaring it explicitly. +@@ -49,17 +51,11 @@ Module for handling openstack keystone calls. + salt '*' keystone.tenant_list profile=openstack1 + """ + +-# Import Python libs +-from __future__ import absolute_import, print_function, unicode_literals + + import logging + +-# Import Salt Libs + import salt.utils.http + +-# Import 3rd-party libs +-from salt.ext import six +- + HAS_KEYSTONE = False + try: + # pylint: disable=import-error +@@ -125,6 +121,7 @@ def _get_kwargs(profile=None, **connection_args): + endpoint = get("endpoint", "http://127.0.0.1:35357/v2.0") + user_domain_name = get("user_domain_name", "Default") + project_domain_name = get("project_domain_name", "Default") ++ verify_ssl = get("verify_ssl", True) + if token: + kwargs = {"token": token, "endpoint": endpoint} + else: +@@ -141,6 +138,7 @@ def _get_kwargs(profile=None, **connection_args): + # this ensures it's only passed in when defined + if insecure: + kwargs["insecure"] = True ++ kwargs["verify_ssl"] = verify_ssl + return kwargs + + +@@ -158,7 +156,7 @@ def api_version(profile=None, **connection_args): + auth_url = kwargs.get("auth_url", kwargs.get("endpoint", None)) + try: + return salt.utils.http.query( +- auth_url, decode=True, decode_type="json", verify_ssl=False ++ auth_url, decode=True, decode_type="json", verify_ssl=kwargs["verify_ssl"] + )["dict"]["version"]["id"] + except KeyError: + return None +@@ -269,7 +267,7 @@ def ec2_credentials_delete( + if not user_id: + return {"Error": "Could not resolve User ID"} + kstone.ec2.delete(user_id, access_key) +- return 'ec2 key "{0}" deleted under user id "{1}"'.format(access_key, user_id) ++ return 'ec2 key "{}" deleted under user id "{}"'.format(access_key, user_id) + + + def ec2_credentials_get( +@@ -373,7 +371,7 @@ def endpoint_get(service, region=None, profile=None, interface=None, **connectio + ] + if len(e) > 1: + return { +- "Error": "Multiple endpoints found ({0}) for the {1} service. Please specify region.".format( ++ "Error": "Multiple endpoints found ({}) for the {} service. Please specify region.".format( + e, service + ) + } +@@ -396,12 +394,12 @@ def endpoint_list(profile=None, **connection_args): + ret = {} + + for endpoint in kstone.endpoints.list(): +- ret[endpoint.id] = dict( +- (value, getattr(endpoint, value)) ++ ret[endpoint.id] = { ++ value: getattr(endpoint, value) + for value in dir(endpoint) + if not value.startswith("_") +- and isinstance(getattr(endpoint, value), (six.string_types, dict, bool)) +- ) ++ and isinstance(getattr(endpoint, value), (str, dict, bool)) ++ } + return ret + + +@@ -487,7 +485,7 @@ def role_create(name, profile=None, **connection_args): + + kstone = auth(profile, **connection_args) + if "Error" not in role_get(name=name, profile=profile, **connection_args): +- return {"Error": 'Role "{0}" already exists'.format(name)} ++ return {"Error": 'Role "{}" already exists'.format(name)} + kstone.roles.create(name) + return role_get(name=name, profile=profile, **connection_args) + +@@ -518,9 +516,9 @@ def role_delete(role_id=None, name=None, profile=None, **connection_args): + role = kstone.roles.get(role_id) + kstone.roles.delete(role) + +- ret = "Role ID {0} deleted".format(role_id) ++ ret = "Role ID {} deleted".format(role_id) + if name: +- ret += " ({0})".format(name) ++ ret += " ({})".format(name) + return ret + + +@@ -564,12 +562,12 @@ def role_list(profile=None, **connection_args): + kstone = auth(profile, **connection_args) + ret = {} + for role in kstone.roles.list(): +- ret[role.name] = dict( +- (value, getattr(role, value)) ++ ret[role.name] = { ++ value: getattr(role, value) + for value in dir(role) + if not value.startswith("_") +- and isinstance(getattr(role, value), (six.string_types, dict, bool)) +- ) ++ and isinstance(getattr(role, value), (str, dict, bool)) ++ } + return ret + + +@@ -608,7 +606,7 @@ def service_delete(service_id=None, name=None, profile=None, **connection_args): + "id" + ] + kstone.services.delete(service_id) +- return 'Keystone service ID "{0}" deleted'.format(service_id) ++ return 'Keystone service ID "{}" deleted'.format(service_id) + + + def service_get(service_id=None, name=None, profile=None, **connection_args): +@@ -633,12 +631,12 @@ def service_get(service_id=None, name=None, profile=None, **connection_args): + if not service_id: + return {"Error": "Unable to resolve service id"} + service = kstone.services.get(service_id) +- ret[service.name] = dict( +- (value, getattr(service, value)) ++ ret[service.name] = { ++ value: getattr(service, value) + for value in dir(service) + if not value.startswith("_") +- and isinstance(getattr(service, value), (six.string_types, dict, bool)) +- ) ++ and isinstance(getattr(service, value), (str, dict, bool)) ++ } + return ret + + +@@ -655,12 +653,12 @@ def service_list(profile=None, **connection_args): + kstone = auth(profile, **connection_args) + ret = {} + for service in kstone.services.list(): +- ret[service.name] = dict( +- (value, getattr(service, value)) ++ ret[service.name] = { ++ value: getattr(service, value) + for value in dir(service) + if not value.startswith("_") +- and isinstance(getattr(service, value), (six.string_types, dict, bool)) +- ) ++ and isinstance(getattr(service, value), (str, dict, bool)) ++ } + return ret + + +@@ -741,10 +739,10 @@ def tenant_delete(tenant_id=None, name=None, profile=None, **connection_args): + if not tenant_id: + return {"Error": "Unable to resolve tenant id"} + getattr(kstone, _TENANTS, None).delete(tenant_id) +- ret = "Tenant ID {0} deleted".format(tenant_id) ++ ret = "Tenant ID {} deleted".format(tenant_id) + if name: + +- ret += " ({0})".format(name) ++ ret += " ({})".format(name) + return ret + + +@@ -805,12 +803,12 @@ def tenant_get(tenant_id=None, name=None, profile=None, **connection_args): + if not tenant_id: + return {"Error": "Unable to resolve tenant id"} + tenant = getattr(kstone, _TENANTS, None).get(tenant_id) +- ret[tenant.name] = dict( +- (value, getattr(tenant, value)) ++ ret[tenant.name] = { ++ value: getattr(tenant, value) + for value in dir(tenant) + if not value.startswith("_") +- and isinstance(getattr(tenant, value), (six.string_types, dict, bool)) +- ) ++ and isinstance(getattr(tenant, value), (str, dict, bool)) ++ } + return ret + + +@@ -863,12 +861,12 @@ def tenant_list(profile=None, **connection_args): + ret = {} + + for tenant in getattr(kstone, _TENANTS, None).list(): +- ret[tenant.name] = dict( +- (value, getattr(tenant, value)) ++ ret[tenant.name] = { ++ value: getattr(tenant, value) + for value in dir(tenant) + if not value.startswith("_") +- and isinstance(getattr(tenant, value), (six.string_types, dict, bool)) +- ) ++ and isinstance(getattr(tenant, value), (str, dict, bool)) ++ } + return ret + + +@@ -938,12 +936,12 @@ def tenant_update( + tenant_id, name=name, description=description, enabled=enabled + ) + +- return dict( +- (value, getattr(updated, value)) ++ return { ++ value: getattr(updated, value) + for value in dir(updated) + if not value.startswith("_") +- and isinstance(getattr(updated, value), (six.string_types, dict, bool)) +- ) ++ and isinstance(getattr(updated, value), (str, dict, bool)) ++ } + + + def project_update( +@@ -1034,12 +1032,12 @@ def user_list(profile=None, **connection_args): + kstone = auth(profile, **connection_args) + ret = {} + for user in kstone.users.list(): +- ret[user.name] = dict( +- (value, getattr(user, value, None)) ++ ret[user.name] = { ++ value: getattr(user, value, None) + for value in dir(user) + if not value.startswith("_") +- and isinstance(getattr(user, value, None), (six.string_types, dict, bool)) +- ) ++ and isinstance(getattr(user, value, None), (str, dict, bool)) ++ } + tenant_id = getattr(user, "tenantId", None) + if tenant_id: + ret[user.name]["tenant_id"] = tenant_id +@@ -1070,16 +1068,16 @@ def user_get(user_id=None, name=None, profile=None, **connection_args): + try: + user = kstone.users.get(user_id) + except keystoneclient.exceptions.NotFound: +- msg = "Could not find user '{0}'".format(user_id) ++ msg = "Could not find user '{}'".format(user_id) + log.error(msg) + return {"Error": msg} + +- ret[user.name] = dict( +- (value, getattr(user, value, None)) ++ ret[user.name] = { ++ value: getattr(user, value, None) + for value in dir(user) + if not value.startswith("_") +- and isinstance(getattr(user, value, None), (six.string_types, dict, bool)) +- ) ++ and isinstance(getattr(user, value, None), (str, dict, bool)) ++ } + + tenant_id = getattr(user, "tenantId", None) + if tenant_id: +@@ -1153,10 +1151,10 @@ def user_delete(user_id=None, name=None, profile=None, **connection_args): + if not user_id: + return {"Error": "Unable to resolve user id"} + kstone.users.delete(user_id) +- ret = "User ID {0} deleted".format(user_id) ++ ret = "User ID {} deleted".format(user_id) + if name: + +- ret += " ({0})".format(name) ++ ret += " ({})".format(name) + return ret + + +@@ -1204,7 +1202,7 @@ def user_update( + if description is None: + description = getattr(user, "description", None) + else: +- description = six.text_type(description) ++ description = str(description) + + project_id = None + if project: +@@ -1235,7 +1233,7 @@ def user_update( + if tenant_id: + kstone.users.update_tenant(user_id, tenant_id) + +- ret = "Info updated for user ID {0}".format(user_id) ++ ret = "Info updated for user ID {}".format(user_id) + return ret + + +@@ -1313,9 +1311,9 @@ def user_password_update( + kstone.users.update(user=user_id, password=password) + else: + kstone.users.update_password(user=user_id, password=password) +- ret = "Password updated for user ID {0}".format(user_id) ++ ret = "Password updated for user ID {}".format(user_id) + if name: +- ret += " ({0})".format(name) ++ ret += " ({})".format(name) + return ret + + +@@ -1356,9 +1354,9 @@ role_id=ce377245c4ec9b70e1c639c89e8cead4 + "id" + ) + else: +- user = next( +- six.iterkeys(user_get(user_id, profile=profile, **connection_args)) +- )["name"] ++ user = next(iter(user_get(user_id, profile=profile, **connection_args).keys()))[ ++ "name" ++ ] + if not user_id: + return {"Error": "Unable to resolve user id"} + +@@ -1368,7 +1366,7 @@ role_id=ce377245c4ec9b70e1c639c89e8cead4 + ].get("id") + else: + tenant = next( +- six.iterkeys(tenant_get(tenant_id, profile=profile, **connection_args)) ++ iter(tenant_get(tenant_id, profile=profile, **connection_args).keys()) + )["name"] + if not tenant_id: + return {"Error": "Unable to resolve tenant/project id"} +@@ -1376,9 +1374,9 @@ role_id=ce377245c4ec9b70e1c639c89e8cead4 + if role: + role_id = role_get(name=role, profile=profile, **connection_args)[role]["id"] + else: +- role = next( +- six.iterkeys(role_get(role_id, profile=profile, **connection_args)) +- )["name"] ++ role = next(iter(role_get(role_id, profile=profile, **connection_args).keys()))[ ++ "name" ++ ] + if not role_id: + return {"Error": "Unable to resolve role id"} + +@@ -1427,9 +1425,9 @@ role_id=ce377245c4ec9b70e1c639c89e8cead4 + "id" + ) + else: +- user = next( +- six.iterkeys(user_get(user_id, profile=profile, **connection_args)) +- )["name"] ++ user = next(iter(user_get(user_id, profile=profile, **connection_args).keys()))[ ++ "name" ++ ] + if not user_id: + return {"Error": "Unable to resolve user id"} + +@@ -1439,7 +1437,7 @@ role_id=ce377245c4ec9b70e1c639c89e8cead4 + ].get("id") + else: + tenant = next( +- six.iterkeys(tenant_get(tenant_id, profile=profile, **connection_args)) ++ iter(tenant_get(tenant_id, profile=profile, **connection_args).keys()) + )["name"] + if not tenant_id: + return {"Error": "Unable to resolve tenant/project id"} +@@ -1447,7 +1445,7 @@ role_id=ce377245c4ec9b70e1c639c89e8cead4 + if role: + role_id = role_get(name=role, profile=profile, **connection_args)[role]["id"] + else: +- role = next(six.iterkeys(role_get(role_id)))["name"] ++ role = next(iter(role_get(role_id).keys()))["name"] + if not role_id: + return {"Error": "Unable to resolve role id"} + +@@ -1504,12 +1502,12 @@ tenant_id=7167a092ece84bae8cead4bf9d15bb3b + + if _OS_IDENTITY_API_VERSION > 2: + for role in kstone.roles.list(user=user_id, project=tenant_id): +- ret[role.name] = dict( +- (value, getattr(role, value)) ++ ret[role.name] = { ++ value: getattr(role, value) + for value in dir(role) + if not value.startswith("_") +- and isinstance(getattr(role, value), (six.string_types, dict, bool)) +- ) ++ and isinstance(getattr(role, value), (str, dict, bool)) ++ } + else: + for role in kstone.roles.roles_for_user(user=user_id, tenant=tenant_id): + ret[role.name] = { +diff --git a/salt/modules/restartcheck.py b/salt/modules/restartcheck.py +index 4d541da357..c996e39dc7 100644 +--- a/salt/modules/restartcheck.py ++++ b/salt/modules/restartcheck.py +@@ -11,6 +11,7 @@ https://packages.debian.org/debian-goodies) and psdel by Sam Morris. + """ + import os + import re ++import shlex + import subprocess + import sys + import time +@@ -612,7 +613,8 @@ def restartcheck(ignorelist=None, blacklist=None, excludepid=None, **kwargs): + for package in packages: + _check_timeout(start_time, timeout) + cmd = cmd_pkg_query + package +- paths = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) ++ cmd = shlex.split(cmd) ++ paths = subprocess.Popen(cmd, stdout=subprocess.PIPE) + + while True: + _check_timeout(start_time, timeout) +diff --git a/salt/modules/vsphere.py b/salt/modules/vsphere.py +index b3de8afb64..8fc2c410f2 100644 +--- a/salt/modules/vsphere.py ++++ b/salt/modules/vsphere.py +@@ -336,7 +336,7 @@ def _get_proxy_connection_details(): + details = __salt__["esxvm.get_details"]() + else: + raise CommandExecutionError("'{}' proxy is not supported" "".format(proxytype)) +- return ( ++ proxy_details = [ + details.get("vcenter") if "vcenter" in details else details.get("host"), + details.get("username"), + details.get("password"), +@@ -345,7 +345,10 @@ def _get_proxy_connection_details(): + details.get("mechanism"), + details.get("principal"), + details.get("domain"), +- ) ++ ] ++ if "verify_ssl" in details: ++ proxy_details.append(details.get("verify_ssl")) ++ return tuple(proxy_details) + + + def supports_proxies(*proxy_types): +@@ -429,7 +432,7 @@ def gets_service_instance_via_proxy(fn): + # case 1: The call was made with enough positional + # parameters to include 'service_instance' + if not args[idx]: +- local_service_instance = salt.utils.vmware.get_service_instance( ++ local_service_instance = salt.utils.vmware.get_service_instance( # pylint: disable=no-value-for-parameter + *connection_details + ) + # Tuples are immutable, so if we want to change what +@@ -440,7 +443,7 @@ def gets_service_instance_via_proxy(fn): + # case 2: Not enough positional parameters so + # 'service_instance' must be a named parameter + if not kwargs.get("service_instance"): +- local_service_instance = salt.utils.vmware.get_service_instance( ++ local_service_instance = salt.utils.vmware.get_service_instance( # pylint: disable=no-value-for-parameter + *connection_details + ) + kwargs["service_instance"] = local_service_instance +@@ -448,7 +451,7 @@ def gets_service_instance_via_proxy(fn): + # 'service_instance' is not a paremter in the function definition + # but it will be caught by the **kwargs parameter + if not kwargs.get("service_instance"): +- local_service_instance = salt.utils.vmware.get_service_instance( ++ local_service_instance = salt.utils.vmware.get_service_instance( # pylint: disable=no-value-for-parameter + *connection_details + ) + kwargs["service_instance"] = local_service_instance +@@ -485,7 +488,9 @@ def get_service_instance_via_proxy(service_instance=None): + See note above + """ + connection_details = _get_proxy_connection_details() +- return salt.utils.vmware.get_service_instance(*connection_details) ++ return salt.utils.vmware.get_service_instance( # pylint: disable=no-value-for-parameter ++ *connection_details ++ ) + + + @depends(HAS_PYVMOMI) +@@ -1587,7 +1592,7 @@ def upload_ssh_key( + ssh_key_file=None, + protocol=None, + port=None, +- certificate_verify=False, ++ certificate_verify=None, + ): + """ + Upload an ssh key for root to an ESXi host via http PUT. +@@ -1604,7 +1609,7 @@ def upload_ssh_key( + :param protocol: defaults to https, can be http if ssl is disabled on ESXi + :param port: defaults to 443 for https + :param certificate_verify: If true require that the SSL connection present +- a valid certificate ++ a valid certificate. Default: True + :return: Dictionary with a 'status' key, True if upload is successful. + If upload is unsuccessful, 'status' key will be False and + an 'Error' key will have an informative message. +@@ -1620,6 +1625,8 @@ def upload_ssh_key( + protocol = "https" + if port is None: + port = 443 ++ if certificate_verify is None: ++ certificate_verify = True + + url = "{}://{}:{}/host/ssh_root_authorized_keys".format(protocol, host, port) + ret = {} +@@ -1662,7 +1669,7 @@ def upload_ssh_key( + + @ignores_kwargs("credstore") + def get_ssh_key( +- host, username, password, protocol=None, port=None, certificate_verify=False ++ host, username, password, protocol=None, port=None, certificate_verify=None + ): + """ + Retrieve the authorized_keys entry for root. +@@ -1674,7 +1681,7 @@ def get_ssh_key( + :param protocol: defaults to https, can be http if ssl is disabled on ESXi + :param port: defaults to 443 for https + :param certificate_verify: If true require that the SSL connection present +- a valid certificate ++ a valid certificate. Default: True + :return: True if upload is successful + + CLI Example: +@@ -1688,6 +1695,8 @@ def get_ssh_key( + protocol = "https" + if port is None: + port = 443 ++ if certificate_verify is None: ++ certificate_verify = True + + url = "{}://{}:{}/host/ssh_root_authorized_keys".format(protocol, host, port) + ret = {} +@@ -1717,7 +1726,7 @@ def get_ssh_key( + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") + def get_host_datetime( +- host, username, password, protocol=None, port=None, host_names=None ++ host, username, password, protocol=None, port=None, host_names=None, verify_ssl=True + ): + """ + Get the date/time information for a given host or list of host_names. +@@ -1748,6 +1757,9 @@ def get_host_datetime( + ``host`` location instead. This is useful for when service instance connection + information is used for a single ESXi host. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -1760,7 +1772,12 @@ def get_host_datetime( + host_names='[esxi-1.host.com, esxi-2.host.com]' + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + host_names = _check_hosts(service_instance, host, host_names) + ret = {} +@@ -1775,7 +1792,9 @@ def get_host_datetime( + + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") +-def get_ntp_config(host, username, password, protocol=None, port=None, host_names=None): ++def get_ntp_config( ++ host, username, password, protocol=None, port=None, host_names=None, verify_ssl=True ++): + """ + Get the NTP configuration information for a given host or list of host_names. + +@@ -1805,6 +1824,9 @@ def get_ntp_config(host, username, password, protocol=None, port=None, host_name + ``host`` location instead. This is useful for when service instance connection + information is used for a single ESXi host. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -1817,7 +1839,12 @@ def get_ntp_config(host, username, password, protocol=None, port=None, host_name + host_names='[esxi-1.host.com, esxi-2.host.com]' + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + host_names = _check_hosts(service_instance, host, host_names) + ret = {} +@@ -1832,7 +1859,14 @@ def get_ntp_config(host, username, password, protocol=None, port=None, host_name + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") + def get_service_policy( +- host, username, password, service_name, protocol=None, port=None, host_names=None ++ host, ++ username, ++ password, ++ service_name, ++ protocol=None, ++ port=None, ++ host_names=None, ++ verify_ssl=True, + ): + """ + Get the service name's policy for a given host or list of hosts. +@@ -1879,6 +1913,9 @@ def get_service_policy( + for the ``host`` location instead. This is useful for when service instance + connection information is used for a single ESXi host. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -1891,7 +1928,12 @@ def get_service_policy( + host_names='[esxi-1.host.com, esxi-2.host.com]' + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + valid_services = [ + "DCUI", +@@ -1959,7 +2001,14 @@ def get_service_policy( + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") + def get_service_running( +- host, username, password, service_name, protocol=None, port=None, host_names=None ++ host, ++ username, ++ password, ++ service_name, ++ protocol=None, ++ port=None, ++ host_names=None, ++ verify_ssl=True, + ): + """ + Get the service name's running state for a given host or list of hosts. +@@ -2006,6 +2055,9 @@ def get_service_running( + for the ``host`` location instead. This is useful for when service instance + connection information is used for a single ESXi host. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -2018,7 +2070,12 @@ def get_service_running( + host_names='[esxi-1.host.com, esxi-2.host.com]' + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + valid_services = [ + "DCUI", +@@ -2086,7 +2143,13 @@ def get_service_running( + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") + def get_vmotion_enabled( +- host, username, password, protocol=None, port=None, host_names=None ++ host, ++ username, ++ password, ++ protocol=None, ++ port=None, ++ host_names=None, ++ verify_ssl=True, + ): + """ + Get the VMotion enabled status for a given host or a list of host_names. Returns ``True`` +@@ -2118,6 +2181,9 @@ def get_vmotion_enabled( + ``host`` location instead. This is useful for when service instance + connection information is used for a single ESXi host. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -2130,7 +2196,12 @@ def get_vmotion_enabled( + host_names='[esxi-1.host.com, esxi-2.host.com]' + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + host_names = _check_hosts(service_instance, host, host_names) + ret = {} +@@ -2148,7 +2219,13 @@ def get_vmotion_enabled( + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") + def get_vsan_enabled( +- host, username, password, protocol=None, port=None, host_names=None ++ host, ++ username, ++ password, ++ protocol=None, ++ port=None, ++ host_names=None, ++ verify_ssl=True, + ): + """ + Get the VSAN enabled status for a given host or a list of host_names. Returns ``True`` +@@ -2181,6 +2258,9 @@ def get_vsan_enabled( + ``host`` location instead. This is useful for when service instance + connection information is used for a single ESXi host. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -2193,7 +2273,12 @@ def get_vsan_enabled( + host_names='[esxi-1.host.com, esxi-2.host.com]' + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + host_names = _check_hosts(service_instance, host, host_names) + ret = {} +@@ -2215,7 +2300,13 @@ def get_vsan_enabled( + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") + def get_vsan_eligible_disks( +- host, username, password, protocol=None, port=None, host_names=None ++ host, ++ username, ++ password, ++ protocol=None, ++ port=None, ++ host_names=None, ++ verify_ssl=True, + ): + """ + Returns a list of VSAN-eligible disks for a given host or list of host_names. +@@ -2246,6 +2337,9 @@ def get_vsan_eligible_disks( + for the ``host`` location instead. This is useful for when service instance + connection information is used for a single ESXi host. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -2258,7 +2352,12 @@ def get_vsan_eligible_disks( + host_names='[esxi-1.host.com, esxi-2.host.com]' + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + host_names = _check_hosts(service_instance, host, host_names) + response = _get_vsan_eligible_disks(service_instance, host, host_names) +@@ -2310,7 +2409,9 @@ def test_vcenter_connection(service_instance=None): + + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") +-def system_info(host, username, password, protocol=None, port=None): ++def system_info( ++ host, username, password, protocol=None, port=None, verify_ssl=True, ++): + """ + Return system information about a VMware environment. + +@@ -2331,6 +2432,9 @@ def system_info(host, username, password, protocol=None, port=None): + Optionally set to alternate port if the host is not using the default + port. Default port is ``443``. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -2338,7 +2442,12 @@ def system_info(host, username, password, protocol=None, port=None): + salt '*' vsphere.system_info 1.2.3.4 root bad-password + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + ret = salt.utils.vmware.get_inventory(service_instance).about.__dict__ + if "apiType" in ret: +@@ -2351,7 +2460,9 @@ def system_info(host, username, password, protocol=None, port=None): + + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") +-def list_datacenters(host, username, password, protocol=None, port=None): ++def list_datacenters( ++ host, username, password, protocol=None, port=None, verify_ssl=True ++): + """ + Returns a list of datacenters for the specified host. + +@@ -2372,6 +2483,9 @@ def list_datacenters(host, username, password, protocol=None, port=None): + Optionally set to alternate port if the host is not using the default + port. Default port is ``443``. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -2380,14 +2494,19 @@ def list_datacenters(host, username, password, protocol=None, port=None): + + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + return salt.utils.vmware.list_datacenters(service_instance) + + + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") +-def list_clusters(host, username, password, protocol=None, port=None): ++def list_clusters(host, username, password, protocol=None, port=None, verify_ssl=True): + """ + Returns a list of clusters for the specified host. + +@@ -2408,6 +2527,9 @@ def list_clusters(host, username, password, protocol=None, port=None): + Optionally set to alternate port if the host is not using the default + port. Default port is ``443``. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -2416,14 +2538,21 @@ def list_clusters(host, username, password, protocol=None, port=None): + + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + return salt.utils.vmware.list_clusters(service_instance) + + + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") +-def list_datastore_clusters(host, username, password, protocol=None, port=None): ++def list_datastore_clusters( ++ host, username, password, protocol=None, port=None, verify_ssl=True ++): + """ + Returns a list of datastore clusters for the specified host. + +@@ -2444,6 +2573,9 @@ def list_datastore_clusters(host, username, password, protocol=None, port=None): + Optionally set to alternate port if the host is not using the default + port. Default port is ``443``. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -2451,14 +2583,21 @@ def list_datastore_clusters(host, username, password, protocol=None, port=None): + salt '*' vsphere.list_datastore_clusters 1.2.3.4 root bad-password + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + return salt.utils.vmware.list_datastore_clusters(service_instance) + + + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") +-def list_datastores(host, username, password, protocol=None, port=None): ++def list_datastores( ++ host, username, password, protocol=None, port=None, verify_ssl=True ++): + """ + Returns a list of datastores for the specified host. + +@@ -2479,6 +2618,9 @@ def list_datastores(host, username, password, protocol=None, port=None): + Optionally set to alternate port if the host is not using the default + port. Default port is ``443``. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -2486,14 +2628,19 @@ def list_datastores(host, username, password, protocol=None, port=None): + salt '*' vsphere.list_datastores 1.2.3.4 root bad-password + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + return salt.utils.vmware.list_datastores(service_instance) + + + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") +-def list_hosts(host, username, password, protocol=None, port=None): ++def list_hosts(host, username, password, protocol=None, port=None, verify_ssl=True): + """ + Returns a list of hosts for the specified VMware environment. + +@@ -2514,6 +2661,9 @@ def list_hosts(host, username, password, protocol=None, port=None): + Optionally set to alternate port if the host is not using the default + port. Default port is ``443``. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -2521,14 +2671,21 @@ def list_hosts(host, username, password, protocol=None, port=None): + salt '*' vsphere.list_hosts 1.2.3.4 root bad-password + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + return salt.utils.vmware.list_hosts(service_instance) + + + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") +-def list_resourcepools(host, username, password, protocol=None, port=None): ++def list_resourcepools( ++ host, username, password, protocol=None, port=None, verify_ssl=True ++): + """ + Returns a list of resource pools for the specified host. + +@@ -2549,6 +2706,9 @@ def list_resourcepools(host, username, password, protocol=None, port=None): + Optionally set to alternate port if the host is not using the default + port. Default port is ``443``. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -2556,14 +2716,19 @@ def list_resourcepools(host, username, password, protocol=None, port=None): + salt '*' vsphere.list_resourcepools 1.2.3.4 root bad-password + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + return salt.utils.vmware.list_resourcepools(service_instance) + + + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") +-def list_networks(host, username, password, protocol=None, port=None): ++def list_networks(host, username, password, protocol=None, port=None, verify_ssl=True): + """ + Returns a list of networks for the specified host. + +@@ -2584,6 +2749,9 @@ def list_networks(host, username, password, protocol=None, port=None): + Optionally set to alternate port if the host is not using the default + port. Default port is ``443``. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -2591,14 +2759,19 @@ def list_networks(host, username, password, protocol=None, port=None): + salt '*' vsphere.list_networks 1.2.3.4 root bad-password + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + return salt.utils.vmware.list_networks(service_instance) + + + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") +-def list_vms(host, username, password, protocol=None, port=None): ++def list_vms(host, username, password, protocol=None, port=None, verify_ssl=True): + """ + Returns a list of VMs for the specified host. + +@@ -2619,6 +2792,9 @@ def list_vms(host, username, password, protocol=None, port=None): + Optionally set to alternate port if the host is not using the default + port. Default port is ``443``. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -2626,14 +2802,19 @@ def list_vms(host, username, password, protocol=None, port=None): + salt '*' vsphere.list_vms 1.2.3.4 root bad-password + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + return salt.utils.vmware.list_vms(service_instance) + + + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") +-def list_folders(host, username, password, protocol=None, port=None): ++def list_folders(host, username, password, protocol=None, port=None, verify_ssl=True): + """ + Returns a list of folders for the specified host. + +@@ -2654,6 +2835,9 @@ def list_folders(host, username, password, protocol=None, port=None): + Optionally set to alternate port if the host is not using the default + port. Default port is ``443``. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -2661,14 +2845,19 @@ def list_folders(host, username, password, protocol=None, port=None): + salt '*' vsphere.list_folders 1.2.3.4 root bad-password + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + return salt.utils.vmware.list_folders(service_instance) + + + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") +-def list_dvs(host, username, password, protocol=None, port=None): ++def list_dvs(host, username, password, protocol=None, port=None, verify_ssl=True): + """ + Returns a list of distributed virtual switches for the specified host. + +@@ -2689,6 +2878,9 @@ def list_dvs(host, username, password, protocol=None, port=None): + Optionally set to alternate port if the host is not using the default + port. Default port is ``443``. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -2696,14 +2888,19 @@ def list_dvs(host, username, password, protocol=None, port=None): + salt '*' vsphere.list_dvs 1.2.3.4 root bad-password + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + return salt.utils.vmware.list_dvs(service_instance) + + + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") +-def list_vapps(host, username, password, protocol=None, port=None): ++def list_vapps(host, username, password, protocol=None, port=None, verify_ssl=True): + """ + Returns a list of vApps for the specified host. + +@@ -2724,6 +2921,9 @@ def list_vapps(host, username, password, protocol=None, port=None): + Optionally set to alternate port if the host is not using the default + port. Default port is ``443``. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -2732,14 +2932,21 @@ def list_vapps(host, username, password, protocol=None, port=None): + salt '*' vsphere.list_vapps 1.2.3.4 root bad-password + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + return salt.utils.vmware.list_vapps(service_instance) + + + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") +-def list_ssds(host, username, password, protocol=None, port=None, host_names=None): ++def list_ssds( ++ host, username, password, protocol=None, port=None, host_names=None, verify_ssl=True ++): + """ + Returns a list of SSDs for the given host or list of host_names. + +@@ -2769,6 +2976,9 @@ def list_ssds(host, username, password, protocol=None, port=None, host_names=Non + ``host`` location instead. This is useful for when service instance + connection information is used for a single ESXi host. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -2781,7 +2991,12 @@ def list_ssds(host, username, password, protocol=None, port=None, host_names=Non + host_names='[esxi-1.host.com, esxi-2.host.com]' + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + host_names = _check_hosts(service_instance, host, host_names) + ret = {} +@@ -2798,7 +3013,9 @@ def list_ssds(host, username, password, protocol=None, port=None, host_names=Non + + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") +-def list_non_ssds(host, username, password, protocol=None, port=None, host_names=None): ++def list_non_ssds( ++ host, username, password, protocol=None, port=None, host_names=None, verify_ssl=True ++): + """ + Returns a list of Non-SSD disks for the given host or list of host_names. + +@@ -2835,6 +3052,9 @@ def list_non_ssds(host, username, password, protocol=None, port=None, host_names + ``host`` location instead. This is useful for when service instance + connection information is used for a single ESXi host. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -2847,7 +3067,12 @@ def list_non_ssds(host, username, password, protocol=None, port=None, host_names + host_names='[esxi-1.host.com, esxi-2.host.com]' + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + host_names = _check_hosts(service_instance, host, host_names) + ret = {} +@@ -2865,7 +3090,14 @@ def list_non_ssds(host, username, password, protocol=None, port=None, host_names + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") + def set_ntp_config( +- host, username, password, ntp_servers, protocol=None, port=None, host_names=None ++ host, ++ username, ++ password, ++ ntp_servers, ++ protocol=None, ++ port=None, ++ host_names=None, ++ verify_ssl=True, + ): + """ + Set NTP configuration for a given host of list of host_names. +@@ -2900,6 +3132,9 @@ def set_ntp_config( + ``host`` location instead. This is useful for when service instance connection + information is used for a single ESXi host. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -2912,7 +3147,12 @@ def set_ntp_config( + host_names='[esxi-1.host.com, esxi-2.host.com]' + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + if not isinstance(ntp_servers, list): + raise CommandExecutionError("'ntp_servers' must be a list.") +@@ -2947,7 +3187,14 @@ def set_ntp_config( + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") + def service_start( +- host, username, password, service_name, protocol=None, port=None, host_names=None ++ host, ++ username, ++ password, ++ service_name, ++ protocol=None, ++ port=None, ++ host_names=None, ++ verify_ssl=True, + ): + """ + Start the named service for the given host or list of hosts. +@@ -2994,6 +3241,9 @@ def service_start( + location instead. This is useful for when service instance connection information + is used for a single ESXi host. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -3006,7 +3256,12 @@ def service_start( + host_names='[esxi-1.host.com, esxi-2.host.com]' + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + host_names = _check_hosts(service_instance, host, host_names) + valid_services = [ +@@ -3074,7 +3329,14 @@ def service_start( + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") + def service_stop( +- host, username, password, service_name, protocol=None, port=None, host_names=None ++ host, ++ username, ++ password, ++ service_name, ++ protocol=None, ++ port=None, ++ host_names=None, ++ verify_ssl=True, + ): + """ + Stop the named service for the given host or list of hosts. +@@ -3121,6 +3383,9 @@ def service_stop( + location instead. This is useful for when service instance connection information + is used for a single ESXi host. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -3133,7 +3398,12 @@ def service_stop( + host_names='[esxi-1.host.com, esxi-2.host.com]' + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + host_names = _check_hosts(service_instance, host, host_names) + valid_services = [ +@@ -3199,7 +3469,14 @@ def service_stop( + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") + def service_restart( +- host, username, password, service_name, protocol=None, port=None, host_names=None ++ host, ++ username, ++ password, ++ service_name, ++ protocol=None, ++ port=None, ++ host_names=None, ++ verify_ssl=True, + ): + """ + Restart the named service for the given host or list of hosts. +@@ -3246,6 +3523,9 @@ def service_restart( + location instead. This is useful for when service instance connection information + is used for a single ESXi host. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -3258,7 +3538,12 @@ def service_restart( + host_names='[esxi-1.host.com, esxi-2.host.com]' + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + host_names = _check_hosts(service_instance, host, host_names) + valid_services = [ +@@ -3334,6 +3619,7 @@ def set_service_policy( + protocol=None, + port=None, + host_names=None, ++ verify_ssl=True, + ): + """ + Set the service name's policy for a given host or list of hosts. +@@ -3383,6 +3669,9 @@ def set_service_policy( + for the ``host`` location instead. This is useful for when service instance + connection information is used for a single ESXi host. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -3395,7 +3684,12 @@ def set_service_policy( + host_names='[esxi-1.host.com, esxi-2.host.com]' + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + host_names = _check_hosts(service_instance, host, host_names) + valid_services = [ +@@ -3481,7 +3775,7 @@ def set_service_policy( + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") + def update_host_datetime( +- host, username, password, protocol=None, port=None, host_names=None ++ host, username, password, protocol=None, port=None, host_names=None, verify_ssl=True + ): + """ + Update the date/time on the given host or list of host_names. This function should be +@@ -3513,6 +3807,9 @@ def update_host_datetime( + location instead. This is useful for when service instance connection + information is used for a single ESXi host. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -3525,7 +3822,12 @@ def update_host_datetime( + host_names='[esxi-1.host.com, esxi-2.host.com]' + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + host_names = _check_hosts(service_instance, host, host_names) + ret = {} +@@ -3550,7 +3852,7 @@ def update_host_datetime( + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") + def update_host_password( +- host, username, password, new_password, protocol=None, port=None ++ host, username, password, new_password, protocol=None, port=None, verify_ssl=True + ): + """ + Update the password for a given host. +@@ -3577,6 +3879,9 @@ def update_host_password( + Optionally set to alternate port if the host is not using the default + port. Default port is ``443``. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -3585,7 +3890,12 @@ def update_host_password( + + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + # Get LocalAccountManager object + account_manager = salt.utils.vmware.get_inventory(service_instance).accountManager +@@ -3615,7 +3925,7 @@ def update_host_password( + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") + def vmotion_disable( +- host, username, password, protocol=None, port=None, host_names=None ++ host, username, password, protocol=None, port=None, host_names=None, verify_ssl=True + ): + """ + Disable vMotion for a given host or list of host_names. +@@ -3646,6 +3956,9 @@ def vmotion_disable( + location instead. This is useful for when service instance connection + information is used for a single ESXi host. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -3658,7 +3971,12 @@ def vmotion_disable( + host_names='[esxi-1.host.com, esxi-2.host.com]' + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + host_names = _check_hosts(service_instance, host, host_names) + ret = {} +@@ -3683,7 +4001,14 @@ def vmotion_disable( + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") + def vmotion_enable( +- host, username, password, protocol=None, port=None, host_names=None, device="vmk0" ++ host, ++ username, ++ password, ++ protocol=None, ++ port=None, ++ host_names=None, ++ device="vmk0", ++ verify_ssl=True, + ): + """ + Enable vMotion for a given host or list of host_names. +@@ -3718,6 +4043,9 @@ def vmotion_enable( + The device that uniquely identifies the VirtualNic that will be used for + VMotion for each host. Defaults to ``vmk0``. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -3730,7 +4058,12 @@ def vmotion_enable( + host_names='[esxi-1.host.com, esxi-2.host.com]' + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + host_names = _check_hosts(service_instance, host, host_names) + ret = {} +@@ -3754,7 +4087,9 @@ def vmotion_enable( + + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") +-def vsan_add_disks(host, username, password, protocol=None, port=None, host_names=None): ++def vsan_add_disks( ++ host, username, password, protocol=None, port=None, host_names=None, verify_ssl=True ++): + """ + Add any VSAN-eligible disks to the VSAN System for the given host or list of host_names. + +@@ -3785,6 +4120,9 @@ def vsan_add_disks(host, username, password, protocol=None, port=None, host_name + VSAN system for the ``host`` location instead. This is useful for when service + instance connection information is used for a single ESXi host. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -3797,7 +4135,12 @@ def vsan_add_disks(host, username, password, protocol=None, port=None, host_name + host_names='[esxi-1.host.com, esxi-2.host.com]' + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + host_names = _check_hosts(service_instance, host, host_names) + response = _get_vsan_eligible_disks(service_instance, host, host_names) +@@ -3872,7 +4215,9 @@ def vsan_add_disks(host, username, password, protocol=None, port=None, host_name + + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") +-def vsan_disable(host, username, password, protocol=None, port=None, host_names=None): ++def vsan_disable( ++ host, username, password, protocol=None, port=None, host_names=None, verify_ssl=True ++): + """ + Disable VSAN for a given host or list of host_names. + +@@ -3902,6 +4247,9 @@ def vsan_disable(host, username, password, protocol=None, port=None, host_names= + location instead. This is useful for when service instance connection + information is used for a single ESXi host. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -3914,7 +4262,12 @@ def vsan_disable(host, username, password, protocol=None, port=None, host_names= + host_names='[esxi-1.host.com, esxi-2.host.com]' + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + # Create a VSAN Configuration Object and set the enabled attribute to True + vsan_config = vim.vsan.host.ConfigInfo() +@@ -3961,7 +4314,9 @@ def vsan_disable(host, username, password, protocol=None, port=None, host_names= + + @depends(HAS_PYVMOMI) + @ignores_kwargs("credstore") +-def vsan_enable(host, username, password, protocol=None, port=None, host_names=None): ++def vsan_enable( ++ host, username, password, protocol=None, port=None, host_names=None, verify_ssl=True ++): + """ + Enable VSAN for a given host or list of host_names. + +@@ -3991,6 +4346,9 @@ def vsan_enable(host, username, password, protocol=None, port=None, host_names=N + location instead. This is useful for when service instance connection + information is used for a single ESXi host. + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -4003,7 +4361,12 @@ def vsan_enable(host, username, password, protocol=None, port=None, host_names=N + host_names='[esxi-1.host.com, esxi-2.host.com]' + """ + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + # Create a VSAN Configuration Object and set the enabled attribute to True + vsan_config = vim.vsan.host.ConfigInfo() +@@ -7489,6 +7852,7 @@ def add_host_to_dvs( + protocol=None, + port=None, + host_names=None, ++ verify_ssl=True, + ): + """ + Adds an ESXi host to a vSphere Distributed Virtual Switch and migrates +@@ -7531,6 +7895,9 @@ def add_host_to_dvs( + host_names: + An array of VMware host names to migrate + ++ verify_ssl ++ Verify the SSL certificate. Default: True ++ + CLI Example: + + .. code-block:: bash +@@ -7658,7 +8025,12 @@ def add_host_to_dvs( + ret["success"] = True + ret["message"] = [] + service_instance = salt.utils.vmware.get_service_instance( +- host=host, username=username, password=password, protocol=protocol, port=port ++ host=host, ++ username=username, ++ password=password, ++ protocol=protocol, ++ port=port, ++ verify_ssl=verify_ssl, + ) + dvs = salt.utils.vmware._get_dvs(service_instance, dvs_name) + if not dvs: +@@ -9926,7 +10298,7 @@ def _delete_device(device): + return device_spec + + +-def _get_client(server, username, password): ++def _get_client(server, username, password, verify_ssl=None, ca_bundle=None): + """ + Establish client through proxy or with user provided credentials. + +@@ -9936,12 +10308,17 @@ def _get_client(server, username, password): + Username associated with the vCenter center. + :param basestring password: + Password associated with the vCenter center. ++ :param boolean verify_ssl: ++ Verify the SSL certificate. Default: True ++ :param basestring ca_bundle: ++ Path to the ca bundle to use when verifying SSL certificates. + :returns: + vSphere Client instance. + :rtype: + vSphere.Client + """ + # Get salted vSphere Client ++ details = None + if not (server and username and password): + # User didn't provide CLI args so use proxy information + details = __salt__["vcenter.get_details"]() +@@ -9949,9 +10326,32 @@ def _get_client(server, username, password): + username = details["username"] + password = details["password"] + ++ if verify_ssl is None: ++ if details is None: ++ details = __salt__["vcenter.get_details"]() ++ verify_ssl = details.get("verify_ssl", True) ++ if verify_ssl is None: ++ verify_ssl = True ++ ++ if ca_bundle is None: ++ if details is None: ++ details = __salt__["vcenter.get_details"]() ++ ca_bundle = details.get("ca_bundle", None) ++ ++ if verify_ssl is False and ca_bundle is not None: ++ log.error("Cannot set verify_ssl to False and ca_bundle together") ++ return False ++ ++ if ca_bundle: ++ ca_bundle = salt.utils.http.get_ca_bundle({"ca_bundle": ca_bundle}) ++ + # Establish connection with client + client = salt.utils.vmware.get_vsphere_client( +- server=server, username=username, password=password ++ server=server, ++ username=username, ++ password=password, ++ verify_ssl=verify_ssl, ++ ca_bundle=ca_bundle, + ) + # Will return None if utility function causes Unauthenticated error + return client +@@ -9961,7 +10361,12 @@ def _get_client(server, username, password): + @supports_proxies("vcenter") + @gets_service_instance_via_proxy + def list_tag_categories( +- server=None, username=None, password=None, service_instance=None ++ server=None, ++ username=None, ++ password=None, ++ service_instance=None, ++ verify_ssl=None, ++ ca_bundle=None, + ): + """ + List existing categories a user has access to. +@@ -9978,13 +10383,19 @@ def list_tag_categories( + Username associated with the vCenter center. + :param basestring password: + Password associated with the vCenter center. ++ :param boolean verify_ssl: ++ Verify the SSL certificate. Default: True ++ :param basestring ca_bundle: ++ Path to the ca bundle to use when verifying SSL certificates. + :returns: + Value(s) of category_id. + :rtype: + list of str + """ + categories = None +- client = _get_client(server, username, password) ++ client = _get_client( ++ server, username, password, verify_ssl=verify_ssl, ca_bundle=ca_bundle ++ ) + + if client: + categories = client.tagging.Category.list() +@@ -9994,7 +10405,14 @@ def list_tag_categories( + @depends(HAS_PYVMOMI, HAS_VSPHERE_SDK) + @supports_proxies("vcenter") + @gets_service_instance_via_proxy +-def list_tags(server=None, username=None, password=None, service_instance=None): ++def list_tags( ++ server=None, ++ username=None, ++ password=None, ++ service_instance=None, ++ verify_ssl=None, ++ ca_bundle=None, ++): + """ + List existing tags a user has access to. + +@@ -10010,13 +10428,19 @@ def list_tags(server=None, username=None, password=None, service_instance=None): + Username associated with the vCenter center. + :param basestring password: + Password associated with the vCenter center. ++ :param boolean verify_ssl: ++ Verify the SSL certificate. Default: True ++ :param basestring ca_bundle: ++ Path to the ca bundle to use when verifying SSL certificates. + :return: + Value(s) of tag_id. + :rtype: + list of str + """ + tags = None +- client = _get_client(server, username, password) ++ client = _get_client( ++ server, username, password, verify_ssl=verify_ssl, ca_bundle=ca_bundle ++ ) + + if client: + tags = client.tagging.Tag.list() +@@ -10034,6 +10458,8 @@ def attach_tag( + username=None, + password=None, + service_instance=None, ++ verify_ssl=None, ++ ca_bundle=None, + ): + """ + Attach an existing tag to an input object. +@@ -10066,6 +10492,10 @@ def attach_tag( + Username associated with the vCenter center. + :param basestring password: + Password associated with the vCenter center. ++ :param boolean verify_ssl: ++ Verify the SSL certificate. Default: True ++ :param basestring ca_bundle: ++ Path to the ca bundle to use when verifying SSL certificates. + :return: + The list of all tag identifiers that correspond to the + tags attached to the given object. +@@ -10077,7 +10507,9 @@ def attach_tag( + if the user can not be authenticated. + """ + tag_attached = None +- client = _get_client(server, username, password) ++ client = _get_client( ++ server, username, password, verify_ssl=verify_ssl, ca_bundle=ca_bundle ++ ) + + if client: + # Create dynamic id object associated with a type and an id. +@@ -10110,6 +10542,8 @@ def list_attached_tags( + username=None, + password=None, + service_instance=None, ++ verify_ssl=None, ++ ca_bundle=None, + ): + """ + List existing tags a user has access to. +@@ -10132,6 +10566,10 @@ def list_attached_tags( + Username associated with the vCenter center. + :param basestring password: + Password associated with the vCenter center. ++ :param boolean verify_ssl: ++ Verify the SSL certificate. Default: True ++ :param basestring ca_bundle: ++ Path to the ca bundle to use when verifying SSL certificates. + :return: + The list of all tag identifiers that correspond to the + tags attached to the given object. +@@ -10143,7 +10581,9 @@ def list_attached_tags( + if the user can not be authenticated. + """ + attached_tags = None +- client = _get_client(server, username, password) ++ client = _get_client( ++ server, username, password, verify_ssl=verify_ssl, ca_bundle=ca_bundle ++ ) + + if client: + # Create dynamic id object associated with a type and an id. +@@ -10175,6 +10615,8 @@ def create_tag_category( + username=None, + password=None, + service_instance=None, ++ verify_ssl=None, ++ ca_bundle=None, + ): + """ + Create a category with given cardinality. +@@ -10197,6 +10639,10 @@ def create_tag_category( + Username associated with the vCenter center. + :param basestring password: + Password associated with the vCenter center. ++ :param boolean verify_ssl: ++ Verify the SSL certificate. Default: True ++ :param basestring ca_bundle: ++ Path to the ca bundle to use when verifying SSL certificates. + :return: + Identifier of the created category. + :rtype: +@@ -10210,7 +10656,9 @@ def create_tag_category( + if you do not have the privilege to create a category. + """ + category_created = None +- client = _get_client(server, username, password) ++ client = _get_client( ++ server, username, password, verify_ssl=verify_ssl, ca_bundle=ca_bundle ++ ) + + if client: + if cardinality == "SINGLE": +@@ -10241,7 +10689,13 @@ def create_tag_category( + @supports_proxies("vcenter") + @gets_service_instance_via_proxy + def delete_tag_category( +- category_id, server=None, username=None, password=None, service_instance=None ++ category_id, ++ server=None, ++ username=None, ++ password=None, ++ service_instance=None, ++ verify_ssl=None, ++ ca_bundle=None, + ): + """ + Delete a category. +@@ -10262,6 +10716,10 @@ def delete_tag_category( + Username associated with the vCenter center. + :param basestring password: + Password associated with the vCenter center. ++ :param boolean verify_ssl: ++ Verify the SSL certificate. Default: True ++ :param basestring ca_bundle: ++ Path to the ca bundle to use when verifying SSL certificates. + :raise: NotFound + if the tag for the given tag_id does not exist in the system. + :raise: Unauthorized +@@ -10270,7 +10728,9 @@ def delete_tag_category( + if the user can not be authenticated. + """ + category_deleted = None +- client = _get_client(server, username, password) ++ client = _get_client( ++ server, username, password, verify_ssl=verify_ssl, ca_bundle=ca_bundle ++ ) + + if client: + try: +@@ -10294,6 +10754,8 @@ def create_tag( + username=None, + password=None, + service_instance=None, ++ verify_ssl=None, ++ ca_bundle=None, + ): + """ + Create a tag under a category with given description. +@@ -10316,6 +10778,10 @@ def create_tag( + Given description of tag category. + :param str category_id: + Value of category_id representative of the category created previously. ++ :param boolean verify_ssl: ++ Verify the SSL certificate. Default: True ++ :param basestring ca_bundle: ++ Path to the ca bundle to use when verifying SSL certificates. + :return: + The identifier of the created tag. + :rtype: +@@ -10332,7 +10798,9 @@ def create_tag( + if you do not have the privilege to create tag. + """ + tag_created = None +- client = _get_client(server, username, password) ++ client = _get_client( ++ server, username, password, verify_ssl=verify_ssl, ca_bundle=ca_bundle ++ ) + + if client: + create_spec = client.tagging.Tag.CreateSpec() +@@ -10353,7 +10821,13 @@ def create_tag( + @supports_proxies("vcenter") + @gets_service_instance_via_proxy + def delete_tag( +- tag_id, server=None, username=None, password=None, service_instance=None ++ tag_id, ++ server=None, ++ username=None, ++ password=None, ++ service_instance=None, ++ verify_ssl=None, ++ ca_bundle=None, + ): + """ + Delete a tag. +@@ -10374,6 +10848,10 @@ def delete_tag( + Username associated with the vCenter center. + :param basestring password: + Password associated with the vCenter center. ++ :param boolean verify_ssl: ++ Verify the SSL certificate. Default: True ++ :param basestring ca_bundle: ++ Path to the ca bundle to use when verifying SSL certificates. + :raise: AlreadyExists + if the name provided in the create_spec is the name of an already + existing category. +@@ -10383,7 +10861,9 @@ def delete_tag( + if you do not have the privilege to create a category. + """ + tag_deleted = None +- client = _get_client(server, username, password) ++ client = _get_client( ++ server, username, password, verify_ssl=verify_ssl, ca_bundle=ca_bundle ++ ) + + if client: + try: +diff --git a/salt/modules/zenoss.py b/salt/modules/zenoss.py +index 9c6b7de7b5..5cb64bed18 100644 +--- a/salt/modules/zenoss.py ++++ b/salt/modules/zenoss.py +@@ -1,4 +1,3 @@ +-# -*- coding: utf-8 -*- + """ + Module for working with the Zenoss API + +@@ -16,18 +15,19 @@ Module for working with the Zenoss API + hostname: https://zenoss.example.com + username: admin + password: admin123 ++ verify_ssl: True ++ ca_bundle: /etc/ssl/certs/ca-certificates.crt + """ + + +-from __future__ import absolute_import, print_function, unicode_literals +- + import logging + import re + ++import salt.utils.http + import salt.utils.json + + try: +- import requests ++ import requests # pylint: disable=unused-import + + HAS_LIBS = True + except ImportError: +@@ -53,7 +53,7 @@ def __virtual__(): + else: + return ( + False, +- "The '{0}' module could not be loaded: " ++ "The '{}' module could not be loaded: " + "'requests' is not installed.".format(__virtualname__), + ) + +@@ -79,11 +79,13 @@ def _session(): + """ + + config = __salt__["config.option"]("zenoss") +- session = requests.session() +- session.auth = (config.get("username"), config.get("password")) +- session.verify = False +- session.headers.update({"Content-type": "application/json; charset=utf-8"}) +- return session ++ return salt.utils.http.session( ++ user=config.get("username"), ++ password=config.get("password"), ++ verify_ssl=config.get("verify_ssl", True), ++ ca_bundle=config.get("ca_bundle"), ++ headers={"Content-type": "application/json; charset=utf-8"}, ++ ) + + + def _router_request(router, method, data=None): +@@ -99,7 +101,7 @@ def _router_request(router, method, data=None): + + config = __salt__["config.option"]("zenoss") + log.debug("Making request to router %s with method %s", router, method) +- url = "{0}/zport/dmd/{1}_router".format(config.get("hostname"), ROUTERS[router]) ++ url = "{}/zport/dmd/{}_router".format(config.get("hostname"), ROUTERS[router]) + response = _session().post(url, data=req_data) + + # The API returns a 200 response code even whe auth is bad. +@@ -212,7 +214,7 @@ def set_prod_state(prod_state, device=None): + device_object = find_device(device) + + if not device_object: +- return "Unable to find a device in Zenoss for {0}".format(device) ++ return "Unable to find a device in Zenoss for {}".format(device) + + log.info("Setting prodState to %d on %s device", prod_state, device) + data = dict( +diff --git a/salt/pillar/vmware_pillar.py b/salt/pillar/vmware_pillar.py +index a33b394500..08bdb18e56 100644 +--- a/salt/pillar/vmware_pillar.py ++++ b/salt/pillar/vmware_pillar.py +@@ -1,4 +1,3 @@ +-# -*- coding: utf-8 -*- + """ + Pillar data from vCenter or an ESXi host + +@@ -142,18 +141,12 @@ Optionally, the following keyword arguments can be passed to the ext_pillar for + part of the pillar regardless of this setting. + + """ +-from __future__ import absolute_import, print_function, unicode_literals + +-# Import python libs + import logging + +-# Import salt libs + import salt.utils.dictupdate as dictupdate + import salt.utils.vmware + +-# Import 3rd-party libs +-from salt.ext import six +- + try: + # pylint: disable=no-name-in-module + from pyVmomi import vim +@@ -370,7 +363,12 @@ def ext_pillar(minion_id, pillar, **kwargs): # pylint: disable=W0613 + vmware_pillar[pillar_key] = {} + try: + _conn = salt.utils.vmware.get_service_instance( +- host, username, password, protocol, port ++ host, ++ username, ++ password, ++ protocol, ++ port, ++ verify_ssl=kwargs.get("verify_ssl", True), + ) + if _conn: + data = None +@@ -410,12 +408,10 @@ def ext_pillar(minion_id, pillar, **kwargs): # pylint: disable=W0613 + ) + except RuntimeError: + log.error( +- ( +- "A runtime error occurred in the vmware_pillar, " +- "this is likely caused by an infinite recursion in " +- "a requested attribute. Verify your requested attributes " +- "and reconfigure the pillar." +- ) ++ "A runtime error occurred in the vmware_pillar, " ++ "this is likely caused by an infinite recursion in " ++ "a requested attribute. Verify your requested attributes " ++ "and reconfigure the pillar." + ) + + return vmware_pillar +@@ -435,7 +431,7 @@ def _recurse_config_to_dict(t_data): + return t_list + elif isinstance(t_data, dict): + t_dict = {} +- for k, v in six.iteritems(t_data): ++ for k, v in t_data.items(): + t_dict[k] = _recurse_config_to_dict(v) + return t_dict + else: +diff --git a/salt/proxy/cimc.py b/salt/proxy/cimc.py +index f302eaa6cc..a6002440ef 100644 +--- a/salt/proxy/cimc.py ++++ b/salt/proxy/cimc.py +@@ -1,4 +1,3 @@ +-# -*- coding: utf-8 -*- + """ + Proxy Minion interface module for managing Cisco Integrated Management Controller devices + ========================================================================================= +@@ -40,6 +39,7 @@ the ID. + host: + username: + password: ++ verify_ssl: True + + proxytype + ^^^^^^^^^ +@@ -66,13 +66,10 @@ password + The password used to login to the cimc host. Required. + """ + +-from __future__ import absolute_import, print_function, unicode_literals + +-# Import Python Libs + import logging + import re + +-# Import Salt Libs + import salt.exceptions + from salt._compat import ElementTree as ET + +@@ -102,9 +99,7 @@ def _validate_response_code(response_code_to_check, cookie_to_logout=None): + if formatted_response_code not in ["200", "201", "202", "204"]: + if cookie_to_logout: + logout(cookie_to_logout) +- log.error( +- "Received error HTTP status code: {0}".format(formatted_response_code) +- ) ++ log.error("Received error HTTP status code: {}".format(formatted_response_code)) + raise salt.exceptions.CommandExecutionError( + "Did not receive a valid response from host." + ) +@@ -125,7 +120,7 @@ def init(opts): + log.critical("No 'passwords' key found in pillar for this proxy.") + return False + +- DETAILS["url"] = "https://{0}/nuova".format(opts["proxy"]["host"]) ++ DETAILS["url"] = "https://{}/nuova".format(opts["proxy"]["host"]) + DETAILS["headers"] = { + "Content-Type": "application/x-www-form-urlencoded", + "Content-Length": 62, +@@ -136,6 +131,10 @@ def init(opts): + DETAILS["host"] = opts["proxy"]["host"] + DETAILS["username"] = opts["proxy"].get("username") + DETAILS["password"] = opts["proxy"].get("password") ++ verify_ssl = opts["proxy"].get("verify_ssl") ++ if verify_ssl is None: ++ verify_ssl = True ++ DETAILS["verify_ssl"] = verify_ssl + + # Ensure connectivity to the device + log.debug("Attempting to connect to cimc proxy host.") +@@ -158,8 +157,8 @@ def set_config_modify(dn=None, inconfig=None, hierarchical=False): + h = "true" + + payload = ( +- '' +- "{3}".format(cookie, h, dn, inconfig) ++ '' ++ "{}".format(cookie, h, dn, inconfig) + ) + r = __utils__["http.query"]( + DETAILS["url"], +@@ -167,7 +166,7 @@ def set_config_modify(dn=None, inconfig=None, hierarchical=False): + method="POST", + decode_type="plain", + decode=True, +- verify_ssl=False, ++ verify_ssl=DETAILS["verify_ssl"], + raise_error=True, + status=True, + headers=DETAILS["headers"], +@@ -195,7 +194,7 @@ def get_config_resolver_class(cid=None, hierarchical=False): + if hierarchical is True: + h = "true" + +- payload = ''.format( ++ payload = ''.format( + cookie, h, cid + ) + r = __utils__["http.query"]( +@@ -204,7 +203,7 @@ def get_config_resolver_class(cid=None, hierarchical=False): + method="POST", + decode_type="plain", + decode=True, +- verify_ssl=False, ++ verify_ssl=DETAILS["verify_ssl"], + raise_error=True, + status=True, + headers=DETAILS["headers"], +@@ -226,7 +225,7 @@ def logon(): + Logs into the cimc device and returns the session cookie. + """ + content = {} +- payload = "".format( ++ payload = "".format( + DETAILS["username"], DETAILS["password"] + ) + r = __utils__["http.query"]( +@@ -235,7 +234,7 @@ def logon(): + method="POST", + decode_type="plain", + decode=True, +- verify_ssl=False, ++ verify_ssl=DETAILS["verify_ssl"], + raise_error=False, + status=True, + headers=DETAILS["headers"], +@@ -265,7 +264,7 @@ def logout(cookie=None): + method="POST", + decode_type="plain", + decode=True, +- verify_ssl=False, ++ verify_ssl=DETAILS["verify_ssl"], + raise_error=True, + headers=DETAILS["headers"], + ) +diff --git a/salt/proxy/panos.py b/salt/proxy/panos.py +index 5c298b4f7d..50a4639911 100644 +--- a/salt/proxy/panos.py ++++ b/salt/proxy/panos.py +@@ -1,4 +1,3 @@ +-# -*- coding: utf-8 -*- + """ + Proxy Minion interface module for managing Palo Alto firewall devices + ===================================================================== +@@ -53,6 +52,7 @@ the device with username and password. + host: + username: + password: ++ verify_ssl: True + + proxytype + ^^^^^^^^^ +@@ -203,17 +203,12 @@ apikey + The generated XML API key for the Panorama server. Required. + """ + +-from __future__ import absolute_import, print_function, unicode_literals + +-# Import Python Libs + import logging + + import salt.exceptions + import salt.utils.xmlutil as xml +- +-# Import Salt Libs + from salt._compat import ElementTree as ET +-from salt.ext import six + + # This must be present or the Salt loader won't load this module. + __proxyenabled__ = ["panos"] +@@ -270,10 +265,11 @@ def init(opts): + log.critical("No 'passwords' key found in pillar for this proxy.") + return False + +- DETAILS["url"] = "https://{0}/api/".format(opts["proxy"]["host"]) ++ DETAILS["url"] = "https://{}/api/".format(opts["proxy"]["host"]) + + # Set configuration details + DETAILS["host"] = opts["proxy"]["host"] ++ DETAILS["verify_ssl"] = opts["proxy"].get("verify_ssl", True) + if "serial" in opts["proxy"]: + DETAILS["serial"] = opts["proxy"].get("serial") + if "apikey" in opts["proxy"]: +@@ -321,7 +317,7 @@ def call(payload=None): + method="POST", + decode_type="plain", + decode=True, +- verify_ssl=False, ++ verify_ssl=DETAILS["verify_ssl"], + status=True, + raise_error=True, + ) +@@ -335,7 +331,7 @@ def call(payload=None): + method="POST", + decode_type="plain", + decode=True, +- verify_ssl=False, ++ verify_ssl=DETAILS["verify_ssl"], + status=True, + raise_error=True, + ) +@@ -352,7 +348,7 @@ def call(payload=None): + method="POST", + decode_type="plain", + decode=True, +- verify_ssl=False, ++ verify_ssl=DETAILS["verify_ssl"], + status=True, + raise_error=True, + ) +@@ -368,7 +364,7 @@ def call(payload=None): + method="POST", + decode_type="plain", + decode=True, +- verify_ssl=False, ++ verify_ssl=DETAILS["verify_ssl"], + status=True, + raise_error=True, + ) +@@ -382,21 +378,21 @@ def call(payload=None): + "Did not receive a valid response from host." + ) + +- if six.text_type(r["status"]) not in ["200", "201", "204"]: +- if six.text_type(r["status"]) == "400": ++ if str(r["status"]) not in ["200", "201", "204"]: ++ if str(r["status"]) == "400": + raise salt.exceptions.CommandExecutionError( + "The server cannot process the request due to a client error." + ) +- elif six.text_type(r["status"]) == "401": ++ elif str(r["status"]) == "401": + raise salt.exceptions.CommandExecutionError( + "The server cannot process the request because it lacks valid authentication " + "credentials for the target resource." + ) +- elif six.text_type(r["status"]) == "403": ++ elif str(r["status"]) == "403": + raise salt.exceptions.CommandExecutionError( + "The server refused to authorize the request." + ) +- elif six.text_type(r["status"]) == "404": ++ elif str(r["status"]) == "404": + raise salt.exceptions.CommandExecutionError( + "The requested resource could not be found." + ) +diff --git a/salt/proxy/vcenter.py b/salt/proxy/vcenter.py +index fa1d090bd2..4bbdb0ee66 100644 +--- a/salt/proxy/vcenter.py ++++ b/salt/proxy/vcenter.py +@@ -1,4 +1,3 @@ +-# -*- coding: utf-8 -*- + """ + Proxy Minion interface module for managing VMWare vCenters. + +@@ -182,13 +181,10 @@ and that host would reach out over the network and communicate with the ESXi + host. + """ + +-# Import Python Libs +-from __future__ import absolute_import, print_function, unicode_literals + + import logging + import os + +-# Import Salt Libs + import salt.exceptions + from salt.config.schemas.vcenter import VCenterProxySchema + from salt.utils.dictupdate import merge +@@ -277,6 +273,8 @@ def init(opts): + # Save optional + DETAILS["protocol"] = proxy_conf.get("protocol") + DETAILS["port"] = proxy_conf.get("port") ++ DETAILS["verify_ssl"] = proxy_conf.get("verify_ssl") ++ DETAILS["ca_bundle"] = proxy_conf.get("ca_bundle") + + # Test connection + if DETAILS["mechanism"] == "userpass": +diff --git a/salt/returners/splunk.py b/salt/returners/splunk.py +index 509eab3cf7..fe4194485e 100644 +--- a/salt/returners/splunk.py ++++ b/salt/returners/splunk.py +@@ -1,4 +1,3 @@ +-# -*- coding: utf-8 -*- + """ + + Send json response data to Splunk via the HTTP Event Collector +@@ -11,29 +10,23 @@ Requires the following config values to be specified in config or pillar: + indexer: + sourcetype: + index: ++ verify_ssl: true + + Run a test by using ``salt-call test.ping --return splunk`` + + Written by Scott Pack (github.com/scottjpack) + + """ +-# Import Python libs +-from __future__ import absolute_import, print_function, unicode_literals + + import logging + import socket + import time + + import requests +- +-# Import salt libs + import salt.utils.json +- +-# Import 3rd-party libs + from salt.ext import six + + _max_content_bytes = 100000 +-http_event_collector_SSL_verify = False + http_event_collector_debug = False + + log = logging.getLogger(__name__) +@@ -62,6 +55,9 @@ def _get_options(): + indexer = __salt__["config.get"]("splunk_http_forwarder:indexer") + sourcetype = __salt__["config.get"]("splunk_http_forwarder:sourcetype") + index = __salt__["config.get"]("splunk_http_forwarder:index") ++ verify_ssl = __salt__["config.get"]( ++ "splunk_http_forwarder:verify_ssl", default=True ++ ) + except Exception: # pylint: disable=broad-except + log.error("Splunk HTTP Forwarder parameters not present in config.") + return None +@@ -70,6 +66,7 @@ def _get_options(): + "indexer": indexer, + "sourcetype": sourcetype, + "index": index, ++ "verify_ssl": verify_ssl, + } + return splunk_opts + +@@ -84,14 +81,17 @@ def _send_splunk(event, index_override=None, sourcetype_override=None): + # Get Splunk Options + opts = _get_options() + log.info( +- str("Options: %s"), # future lint: disable=blacklisted-function ++ "Options: %s", # future lint: disable=blacklisted-function + salt.utils.json.dumps(opts), + ) + http_event_collector_key = opts["token"] + http_event_collector_host = opts["indexer"] ++ http_event_collector_verify_ssl = opts["verify_ssl"] + # Set up the collector + splunk_event = http_event_collector( +- http_event_collector_key, http_event_collector_host ++ http_event_collector_key, ++ http_event_collector_host, ++ verify_ssl=http_event_collector_verify_ssl, + ) + # init the payload + payload = {} +@@ -109,7 +109,7 @@ def _send_splunk(event, index_override=None, sourcetype_override=None): + # Add the event + payload.update({"event": event}) + log.info( +- str("Payload: %s"), # future lint: disable=blacklisted-function ++ "Payload: %s", # future lint: disable=blacklisted-function + salt.utils.json.dumps(payload), + ) + # Fire it off +@@ -120,7 +120,7 @@ def _send_splunk(event, index_override=None, sourcetype_override=None): + # Thanks to George Starcher for the http_event_collector class (https://github.com/georgestarcher/) + + +-class http_event_collector(object): ++class http_event_collector: + def __init__( + self, + token, +@@ -129,11 +129,13 @@ class http_event_collector(object): + http_event_port="8088", + http_event_server_ssl=True, + max_bytes=_max_content_bytes, ++ verify_ssl=True, + ): + self.token = token + self.batchEvents = [] + self.maxByteLength = max_bytes + self.currentByteLength = 0 ++ self.verify_ssl = verify_ssl + + # Set host to specified value or default to localhostname if no value provided + if host: +@@ -164,7 +166,7 @@ class http_event_collector(object): + + # If eventtime in epoch not passed as optional argument use current system time in epoch + if not eventtime: +- eventtime = six.text_type(int(time.time())) ++ eventtime = str(int(time.time())) + + # Fill in local hostname if not manually populated + if "host" not in payload: +@@ -179,7 +181,7 @@ class http_event_collector(object): + self.server_uri, + data=salt.utils.json.dumps(data), + headers=headers, +- verify=http_event_collector_SSL_verify, ++ verify=self.verify_ssl, + ) + + # Print debug info if flag set +@@ -207,7 +209,7 @@ class http_event_collector(object): + + # If eventtime in epoch not passed as optional argument use current system time in epoch + if not eventtime: +- eventtime = six.text_type(int(time.time())) ++ eventtime = str(int(time.time())) + + # Update time value on payload if need to use system time + data = {"time": eventtime} +@@ -224,7 +226,7 @@ class http_event_collector(object): + self.server_uri, + data=" ".join(self.batchEvents), + headers=headers, +- verify=http_event_collector_SSL_verify, ++ verify=self.verify_ssl, + ) + self.batchEvents = [] + self.currentByteLength = 0 +diff --git a/salt/runners/asam.py b/salt/runners/asam.py +index f53dfba69d..4c999d3ba2 100644 +--- a/salt/runners/asam.py ++++ b/salt/runners/asam.py +@@ -17,9 +17,11 @@ master configuration at ``/etc/salt/master`` or ``/etc/salt/master.d/asam.conf`` + prov1.domain.com + username: "testuser" + password: "verybadpass" ++ verify_ssl: true + prov2.domain.com + username: "testuser" + password: "verybadpass" ++ verify_ssl: true + + .. note:: + +@@ -84,6 +86,10 @@ def _get_asam_configuration(driver_url=""): + password = service_config.get("password", None) + protocol = service_config.get("protocol", "https") + port = service_config.get("port", 3451) ++ verify_ssl = service_config.get("verify_ssl") ++ ++ if verify_ssl is None: ++ verify_ssl = True + + if not username or not password: + log.error( +@@ -108,6 +114,7 @@ def _get_asam_configuration(driver_url=""): + ), + "username": username, + "password": password, ++ "verify_ssl": verify_ssl, + } + + if (not driver_url) or (driver_url == asam_server): +@@ -206,7 +213,7 @@ def remove_platform(name, server_url): + auth = (config["username"], config["password"]) + + try: +- html_content = _make_post_request(url, data, auth, verify=False) ++ html_content = _make_post_request(url, data, auth, verify=config["verify_ssl"]) + except Exception as exc: # pylint: disable=broad-except + err_msg = "Failed to look up existing platforms on {}".format(server_url) + log.error("%s:\n%s", err_msg, exc) +@@ -222,7 +229,9 @@ def remove_platform(name, server_url): + data["postType"] = "platformRemove" + data["Submit"] = "Yes" + try: +- html_content = _make_post_request(url, data, auth, verify=False) ++ html_content = _make_post_request( ++ url, data, auth, verify=config["verify_ssl"] ++ ) + except Exception as exc: # pylint: disable=broad-except + err_msg = "Failed to delete platform from {}".format(server_url) + log.error("%s:\n%s", err_msg, exc) +@@ -261,7 +270,7 @@ def list_platforms(server_url): + auth = (config["username"], config["password"]) + + try: +- html_content = _make_post_request(url, data, auth, verify=False) ++ html_content = _make_post_request(url, data, auth, verify=config["verify_ssl"]) + except Exception as exc: # pylint: disable=broad-except + err_msg = "Failed to look up existing platforms" + log.error("%s:\n%s", err_msg, exc) +@@ -299,7 +308,7 @@ def list_platform_sets(server_url): + auth = (config["username"], config["password"]) + + try: +- html_content = _make_post_request(url, data, auth, verify=False) ++ html_content = _make_post_request(url, data, auth, verify=config["verify_ssl"]) + except Exception as exc: # pylint: disable=broad-except + err_msg = "Failed to look up existing platform sets" + log.error("%s:\n%s", err_msg, exc) +@@ -351,7 +360,7 @@ def add_platform(name, platform_set, server_url): + auth = (config["username"], config["password"]) + + try: +- html_content = _make_post_request(url, data, auth, verify=False) ++ html_content = _make_post_request(url, data, auth, verify=config["verify_ssl"]) + except Exception as exc: # pylint: disable=broad-except + err_msg = "Failed to add platform on {}".format(server_url) + log.error("%s:\n%s", err_msg, exc) +diff --git a/salt/states/esxi.py b/salt/states/esxi.py +index 6f4d44306b..12a592dc29 100644 +--- a/salt/states/esxi.py ++++ b/salt/states/esxi.py +@@ -1,4 +1,3 @@ +-# -*- coding: utf-8 -*- + """ + Manage VMware ESXi Hosts. + +@@ -91,8 +90,6 @@ configuration examples, dependency installation instructions, how to run remote + execution functions against ESXi hosts via a Salt Proxy Minion, and a larger state + example. + """ +-# Import Python Libs +-from __future__ import absolute_import, print_function, unicode_literals + + import logging + import re +@@ -108,8 +105,6 @@ from salt.exceptions import ( + VMwareObjectRetrievalError, + VMwareSaltError, + ) +- +-# Import Salt Libs + from salt.ext import six + from salt.utils.decorators import depends + +@@ -201,7 +196,7 @@ def coredump_configured(name, enabled, dump_ip, host_vnic="vmk0", dump_port=6500 + current_config = __salt__[esxi_cmd]("get_coredump_network_config").get(host) + error = current_config.get("Error") + if error: +- ret["comment"] = "Error: {0}".format(error) ++ ret["comment"] = "Error: {}".format(error) + return ret + + current_config = current_config.get("Coredump Config") +@@ -217,7 +212,7 @@ def coredump_configured(name, enabled, dump_ip, host_vnic="vmk0", dump_port=6500 + ).get(host) + error = response.get("Error") + if error: +- ret["comment"] = "Error: {0}".format(error) ++ ret["comment"] = "Error: {}".format(error) + return ret + + # Allow users to disable core dump, but then return since +@@ -252,9 +247,9 @@ def coredump_configured(name, enabled, dump_ip, host_vnic="vmk0", dump_port=6500 + changes = True + + current_port = current_config.get("port") +- if current_port != six.text_type(dump_port): ++ if current_port != str(dump_port): + ret["changes"].update( +- {"dump_port": {"old": current_port, "new": six.text_type(dump_port)}} ++ {"dump_port": {"old": current_port, "new": str(dump_port)}} + ) + changes = True + +@@ -270,7 +265,7 @@ def coredump_configured(name, enabled, dump_ip, host_vnic="vmk0", dump_port=6500 + msg = response.get("stderr") + if not msg: + msg = response.get("stdout") +- ret["comment"] = "Error: {0}".format(msg) ++ ret["comment"] = "Error: {}".format(msg) + return ret + + ret["result"] = True +@@ -328,7 +323,7 @@ def password_present(name, password): + __salt__[esxi_cmd]("update_host_password", new_password=password) + except CommandExecutionError as err: + ret["result"] = False +- ret["comment"] = "Error: {0}".format(err) ++ ret["comment"] = "Error: {}".format(err) + return ret + + return ret +@@ -400,7 +395,7 @@ def ntp_configured( + ntp_running = __salt__[esxi_cmd]("get_service_running", service_name=ntpd).get(host) + error = ntp_running.get("Error") + if error: +- ret["comment"] = "Error: {0}".format(error) ++ ret["comment"] = "Error: {}".format(error) + return ret + ntp_running = ntp_running.get(ntpd) + +@@ -413,7 +408,7 @@ def ntp_configured( + ).get(host) + error = response.get("Error") + if error: +- ret["comment"] = "Error: {0}".format(error) ++ ret["comment"] = "Error: {}".format(error) + return ret + # Set changes dictionary for ntp_servers + ret["changes"].update({"ntp_servers": {"old": ntp_config, "new": ntp_servers}}) +@@ -429,7 +424,7 @@ def ntp_configured( + ) + error = response.get("Error") + if error: +- ret["comment"] = "Error: {0}".format(error) ++ ret["comment"] = "Error: {}".format(error) + return ret + # Stop ntpd if service_running=False + else: +@@ -438,7 +433,7 @@ def ntp_configured( + ) + error = response.get("Error") + if error: +- ret["comment"] = "Error: {0}".format(error) ++ ret["comment"] = "Error: {}".format(error) + return ret + ret["changes"].update( + {"service_running": {"old": ntp_running, "new": service_running}} +@@ -451,7 +446,7 @@ def ntp_configured( + ).get(host) + error = current_service_policy.get("Error") + if error: +- ret["comment"] = "Error: {0}".format(error) ++ ret["comment"] = "Error: {}".format(error) + return ret + current_service_policy = current_service_policy.get(ntpd) + +@@ -465,7 +460,7 @@ def ntp_configured( + ).get(host) + error = response.get("Error") + if error: +- ret["comment"] = "Error: {0}".format(error) ++ ret["comment"] = "Error: {}".format(error) + return ret + ret["changes"].update( + { +@@ -483,7 +478,7 @@ def ntp_configured( + response = __salt__[esxi_cmd]("update_host_datetime").get(host) + error = response.get("Error") + if error: +- ret["comment"] = "Error: {0}".format(error) ++ ret["comment"] = "Error: {}".format(error) + return ret + ret["changes"].update( + {"update_datetime": {"old": "", "new": "Host datetime was updated."}} +@@ -498,7 +493,7 @@ def ntp_configured( + ) + error = response.get("Error") + if error: +- ret["comment"] = "Error: {0}".format(error) ++ ret["comment"] = "Error: {}".format(error) + return ret + ret["changes"].update( + {"service_restart": {"old": "", "new": "NTP Daemon Restarted."}} +@@ -559,14 +554,14 @@ def vmotion_configured(name, enabled, device="vmk0"): + response = __salt__[esxi_cmd]("vmotion_enable", device=device).get(host) + error = response.get("Error") + if error: +- ret["comment"] = "Error: {0}".format(error) ++ ret["comment"] = "Error: {}".format(error) + return ret + # Disable VMotion if enabled=False + else: + response = __salt__[esxi_cmd]("vmotion_disable").get(host) + error = response.get("Error") + if error: +- ret["comment"] = "Error: {0}".format(error) ++ ret["comment"] = "Error: {}".format(error) + return ret + ret["changes"].update( + {"enabled": {"old": current_vmotion_enabled, "new": enabled}} +@@ -618,7 +613,7 @@ def vsan_configured(name, enabled, add_disks_to_vsan=False): + current_vsan_enabled = __salt__[esxi_cmd]("get_vsan_enabled").get(host) + error = current_vsan_enabled.get("Error") + if error: +- ret["comment"] = "Error: {0}".format(error) ++ ret["comment"] = "Error: {}".format(error) + return ret + current_vsan_enabled = current_vsan_enabled.get("VSAN Enabled") + +@@ -631,14 +626,14 @@ def vsan_configured(name, enabled, add_disks_to_vsan=False): + response = __salt__[esxi_cmd]("vsan_enable").get(host) + error = response.get("Error") + if error: +- ret["comment"] = "Error: {0}".format(error) ++ ret["comment"] = "Error: {}".format(error) + return ret + # Disable VSAN if enabled=False + else: + response = __salt__[esxi_cmd]("vsan_disable").get(host) + error = response.get("Error") + if error: +- ret["comment"] = "Error: {0}".format(error) ++ ret["comment"] = "Error: {}".format(error) + return ret + ret["changes"].update( + {"enabled": {"old": current_vsan_enabled, "new": enabled}} +@@ -649,7 +644,7 @@ def vsan_configured(name, enabled, add_disks_to_vsan=False): + current_eligible_disks = __salt__[esxi_cmd]("get_vsan_eligible_disks").get(host) + error = current_eligible_disks.get("Error") + if error: +- ret["comment"] = "Error: {0}".format(error) ++ ret["comment"] = "Error: {}".format(error) + return ret + + disks = current_eligible_disks.get("Eligible") +@@ -659,7 +654,7 @@ def vsan_configured(name, enabled, add_disks_to_vsan=False): + response = __salt__[esxi_cmd]("vsan_add_disks").get(host) + error = response.get("Error") + if error: +- ret["comment"] = "Error: {0}".format(error) ++ ret["comment"] = "Error: {}".format(error) + return ret + + ret["changes"].update({"add_disks_to_vsan": {"old": "", "new": disks}}) +@@ -683,7 +678,7 @@ def ssh_configured( + ssh_key_file=None, + service_policy=None, + service_restart=False, +- certificate_verify=False, ++ certificate_verify=None, + ): + """ + Manage the SSH configuration for a host including whether or not SSH is running or +@@ -724,7 +719,7 @@ def ssh_configured( + + certificate_verify + If set to ``True``, the SSL connection must present a valid certificate. +- Default is ``False``. ++ Default is ``True``. + + Example: + +@@ -739,6 +734,8 @@ def ssh_configured( + - certificate_verify: True + + """ ++ if certificate_verify is None: ++ certificate_verify = True + ret = {"name": name, "result": False, "changes": {}, "comment": ""} + esxi_cmd = "esxi.cmd" + host = __pillar__["proxy"]["host"] +@@ -747,7 +744,7 @@ def ssh_configured( + ssh_running = __salt__[esxi_cmd]("get_service_running", service_name=ssh).get(host) + error = ssh_running.get("Error") + if error: +- ret["comment"] = "Error: {0}".format(error) ++ ret["comment"] = "Error: {}".format(error) + return ret + ssh_running = ssh_running.get(ssh) + +@@ -760,14 +757,14 @@ def ssh_configured( + enable = __salt__[esxi_cmd]("service_start", service_name=ssh).get(host) + error = enable.get("Error") + if error: +- ret["comment"] = "Error: {0}".format(error) ++ ret["comment"] = "Error: {}".format(error) + return ret + # Disable SSH if service_running=False + else: + disable = __salt__[esxi_cmd]("service_stop", service_name=ssh).get(host) + error = disable.get("Error") + if error: +- ret["comment"] = "Error: {0}".format(error) ++ ret["comment"] = "Error: {}".format(error) + return ret + + ret["changes"].update( +@@ -783,7 +780,7 @@ def ssh_configured( + ) + error = current_ssh_key.get("Error") + if error: +- ret["comment"] = "Error: {0}".format(error) ++ ret["comment"] = "Error: {}".format(error) + return ret + current_ssh_key = current_ssh_key.get("key") + if current_ssh_key: +@@ -822,7 +819,7 @@ def ssh_configured( + ) + error = response.get("Error") + if error: +- ret["comment"] = "Error: {0}".format(error) ++ ret["comment"] = "Error: {}".format(error) + return ret + ret["changes"].update( + { +@@ -840,7 +837,7 @@ def ssh_configured( + ).get(host) + error = current_service_policy.get("Error") + if error: +- ret["comment"] = "Error: {0}".format(error) ++ ret["comment"] = "Error: {}".format(error) + return ret + current_service_policy = current_service_policy.get(ssh) + +@@ -854,7 +851,7 @@ def ssh_configured( + ).get(host) + error = response.get("Error") + if error: +- ret["comment"] = "Error: {0}".format(error) ++ ret["comment"] = "Error: {}".format(error) + return ret + ret["changes"].update( + { +@@ -872,7 +869,7 @@ def ssh_configured( + response = __salt__[esxi_cmd]("service_restart", service_name=ssh).get(host) + error = response.get("Error") + if error: +- ret["comment"] = "Error: {0}".format(error) ++ ret["comment"] = "Error: {}".format(error) + return ret + ret["changes"].update( + {"service_restart": {"old": "", "new": "SSH service restarted."}} +@@ -965,17 +962,17 @@ def syslog_configured( + reset = __salt__[esxi_cmd]( + "reset_syslog_config", syslog_config=reset_configs + ).get(host) +- for key, val in six.iteritems(reset): ++ for key, val in reset.items(): + if isinstance(val, bool): + continue + if not val.get("success"): + msg = val.get("message") + if not msg: + msg = ( +- "There was an error resetting a syslog config '{0}'." ++ "There was an error resetting a syslog config '{}'." + "Please check debug logs.".format(val) + ) +- ret["comment"] = "Error: {0}".format(msg) ++ ret["comment"] = "Error: {}".format(msg) + return ret + + ret["changes"].update( +@@ -985,7 +982,7 @@ def syslog_configured( + current_firewall = __salt__[esxi_cmd]("get_firewall_status").get(host) + error = current_firewall.get("Error") + if error: +- ret["comment"] = "Error: {0}".format(error) ++ ret["comment"] = "Error: {}".format(error) + return ret + + current_firewall = current_firewall.get("rulesets").get("syslog") +@@ -1000,23 +997,23 @@ def syslog_configured( + if enabled.get("retcode") != 0: + err = enabled.get("stderr") + out = enabled.get("stdout") +- ret["comment"] = "Error: {0}".format(err if err else out) ++ ret["comment"] = "Error: {}".format(err if err else out) + return ret + + ret["changes"].update({"firewall": {"old": current_firewall, "new": firewall}}) + + current_syslog_config = __salt__[esxi_cmd]("get_syslog_config").get(host) +- for key, val in six.iteritems(syslog_configs): ++ for key, val in syslog_configs.items(): + # The output of get_syslog_config has different keys than the keys + # Used to set syslog_config values. We need to look them up first. + try: + lookup_key = _lookup_syslog_config(key) + except KeyError: +- ret["comment"] = "'{0}' is not a valid config variable.".format(key) ++ ret["comment"] = "'{}' is not a valid config variable.".format(key) + return ret + + current_val = current_syslog_config[lookup_key] +- if six.text_type(current_val) != six.text_type(val): ++ if str(current_val) != str(val): + # Only run the command if not using test=True + if not __opts__["test"]: + response = __salt__[esxi_cmd]( +@@ -1031,7 +1028,7 @@ def syslog_configured( + msg = response.get(key).get("message") + if not msg: + msg = ( +- "There was an error setting syslog config '{0}'. " ++ "There was an error setting syslog config '{}'. " + "Please check debug logs.".format(key) + ) + ret["comment"] = msg +@@ -1101,7 +1098,7 @@ def diskgroups_configured(name, diskgroups, erase_disks=False): + if not proxy_details.get("vcenter") + else proxy_details["esxi_host"] + ) +- log.info("Running state {0} for host '{1}'".format(name, hostname)) ++ log.info("Running state {} for host '{}'".format(name, hostname)) + # Variable used to return the result of the invocation + ret = {"name": name, "result": None, "changes": {}, "comments": None} + # Signals if errors have been encountered +@@ -1124,23 +1121,20 @@ def diskgroups_configured(name, diskgroups, erase_disks=False): + host_disks = __salt__["vsphere.list_disks"](service_instance=si) + if not host_disks: + raise VMwareObjectRetrievalError( +- "No disks retrieved from host '{0}'".format(hostname) ++ "No disks retrieved from host '{}'".format(hostname) + ) + scsi_addr_to_disk_map = {d["scsi_address"]: d for d in host_disks} +- log.trace("scsi_addr_to_disk_map = {0}".format(scsi_addr_to_disk_map)) ++ log.trace("scsi_addr_to_disk_map = {}".format(scsi_addr_to_disk_map)) + existing_diskgroups = __salt__["vsphere.list_diskgroups"](service_instance=si) + cache_disk_to_existing_diskgroup_map = { + dg["cache_disk"]: dg for dg in existing_diskgroups + } + except CommandExecutionError as err: +- log.error("Error: {0}".format(err)) ++ log.error("Error: {}".format(err)) + if si: + __salt__["vsphere.disconnect"](si) + ret.update( +- { +- "result": False if not __opts__["test"] else None, +- "comment": six.text_type(err), +- } ++ {"result": False if not __opts__["test"] else None, "comment": str(err),} + ) + return ret + +@@ -1149,7 +1143,7 @@ def diskgroups_configured(name, diskgroups, erase_disks=False): + # Check for cache disk + if not dg["cache_scsi_addr"] in scsi_addr_to_disk_map: + comments.append( +- "No cache disk with scsi address '{0}' was " ++ "No cache disk with scsi address '{}' was " + "found.".format(dg["cache_scsi_addr"]) + ) + log.error(comments[-1]) +@@ -1158,7 +1152,7 @@ def diskgroups_configured(name, diskgroups, erase_disks=False): + + # Check for capacity disks + cache_disk_id = scsi_addr_to_disk_map[dg["cache_scsi_addr"]]["id"] +- cache_disk_display = "{0} (id:{1})".format(dg["cache_scsi_addr"], cache_disk_id) ++ cache_disk_display = "{} (id:{})".format(dg["cache_scsi_addr"], cache_disk_id) + bad_scsi_addrs = [] + capacity_disk_ids = [] + capacity_disk_displays = [] +@@ -1168,13 +1162,13 @@ def diskgroups_configured(name, diskgroups, erase_disks=False): + continue + capacity_disk_ids.append(scsi_addr_to_disk_map[scsi_addr]["id"]) + capacity_disk_displays.append( +- "{0} (id:{1})".format(scsi_addr, capacity_disk_ids[-1]) ++ "{} (id:{})".format(scsi_addr, capacity_disk_ids[-1]) + ) + if bad_scsi_addrs: + comments.append( +- "Error in diskgroup #{0}: capacity disks with " +- "scsi addresses {1} were not found." +- "".format(idx, ", ".join(["'{0}'".format(a) for a in bad_scsi_addrs])) ++ "Error in diskgroup #{}: capacity disks with " ++ "scsi addresses {} were not found." ++ "".format(idx, ", ".join(["'{}'".format(a) for a in bad_scsi_addrs])) + ) + log.error(comments[-1]) + errors = True +@@ -1182,14 +1176,14 @@ def diskgroups_configured(name, diskgroups, erase_disks=False): + + if not cache_disk_to_existing_diskgroup_map.get(cache_disk_id): + # A new diskgroup needs to be created +- log.trace("erase_disks = {0}".format(erase_disks)) ++ log.trace("erase_disks = {}".format(erase_disks)) + if erase_disks: + if __opts__["test"]: + comments.append( +- "State {0} will " +- "erase all disks of disk group #{1}; " +- "cache disk: '{2}', " +- "capacity disk(s): {3}." ++ "State {} will " ++ "erase all disks of disk group #{}; " ++ "cache disk: '{}', " ++ "capacity disk(s): {}." + "".format( + name, + idx, +@@ -1206,13 +1200,13 @@ def diskgroups_configured(name, diskgroups, erase_disks=False): + disk_id=disk_id, service_instance=si + ) + comments.append( +- "Erased disks of diskgroup #{0}; " +- "cache disk: '{1}', capacity disk(s): " +- "{2}".format( ++ "Erased disks of diskgroup #{}; " ++ "cache disk: '{}', capacity disk(s): " ++ "{}".format( + idx, + cache_disk_display, + ", ".join( +- ["'{0}'".format(a) for a in capacity_disk_displays] ++ ["'{}'".format(a) for a in capacity_disk_displays] + ), + ) + ) +@@ -1220,13 +1214,13 @@ def diskgroups_configured(name, diskgroups, erase_disks=False): + + if __opts__["test"]: + comments.append( +- "State {0} will create " +- "the disk group #{1}; cache disk: '{2}', " +- "capacity disk(s): {3}.".format( ++ "State {} will create " ++ "the disk group #{}; cache disk: '{}', " ++ "capacity disk(s): {}.".format( + name, + idx, + cache_disk_display, +- ", ".join(["'{0}'".format(a) for a in capacity_disk_displays]), ++ ", ".join(["'{}'".format(a) for a in capacity_disk_displays]), + ) + ) + log.info(comments[-1]) +@@ -1241,15 +1235,15 @@ def diskgroups_configured(name, diskgroups, erase_disks=False): + ) + except VMwareSaltError as err: + comments.append( +- "Error creating disk group #{0}: " "{1}.".format(idx, err) ++ "Error creating disk group #{}: " "{}.".format(idx, err) + ) + log.error(comments[-1]) + errors = True + continue + +- comments.append("Created disk group #'{0}'.".format(idx)) ++ comments.append("Created disk group #'{}'.".format(idx)) + log.info(comments[-1]) +- diskgroup_changes[six.text_type(idx)] = { ++ diskgroup_changes[str(idx)] = { + "new": {"cache": cache_disk_display, "capacity": capacity_disk_displays} + } + changes = True +@@ -1257,12 +1251,12 @@ def diskgroups_configured(name, diskgroups, erase_disks=False): + + # The diskgroup exists; checking the capacity disks + log.debug( +- "Disk group #{0} exists. Checking capacity disks: " +- "{1}.".format(idx, capacity_disk_displays) ++ "Disk group #{} exists. Checking capacity disks: " ++ "{}.".format(idx, capacity_disk_displays) + ) + existing_diskgroup = cache_disk_to_existing_diskgroup_map.get(cache_disk_id) + existing_capacity_disk_displays = [ +- "{0} (id:{1})".format( ++ "{} (id:{})".format( + [d["scsi_address"] for d in host_disks if d["id"] == disk_id][0], + disk_id, + ) +@@ -1280,7 +1274,7 @@ def diskgroups_configured(name, diskgroups, erase_disks=False): + ][0] + added_capacity_disk_ids.append(disk_id) + added_capacity_disk_displays.append( +- "{0} (id:{1})".format(disk_scsi_addr, disk_id) ++ "{} (id:{})".format(disk_scsi_addr, disk_id) + ) + for disk_id in existing_diskgroup["capacity_disks"]: + if disk_id not in capacity_disk_ids: +@@ -1289,12 +1283,12 @@ def diskgroups_configured(name, diskgroups, erase_disks=False): + ][0] + removed_capacity_disk_ids.append(disk_id) + removed_capacity_disk_displays.append( +- "{0} (id:{1})".format(disk_scsi_addr, disk_id) ++ "{} (id:{})".format(disk_scsi_addr, disk_id) + ) + + log.debug( +- "Disk group #{0}: existing capacity disk ids: {1}; added " +- "capacity disk ids: {2}; removed capacity disk ids: {3}" ++ "Disk group #{}: existing capacity disk ids: {}; added " ++ "capacity disk ids: {}; removed capacity disk ids: {}" + "".format( + idx, + existing_capacity_disk_displays, +@@ -1306,11 +1300,11 @@ def diskgroups_configured(name, diskgroups, erase_disks=False): + # TODO revisit this when removing capacity disks is supported + if removed_capacity_disk_ids: + comments.append( +- "Error removing capacity disk(s) {0} from disk group #{1}; " ++ "Error removing capacity disk(s) {} from disk group #{}; " + "operation is not supported." + "".format( + ", ".join( +- ["'{0}'".format(id) for id in removed_capacity_disk_displays] ++ ["'{}'".format(id) for id in removed_capacity_disk_displays] + ), + idx, + ) +@@ -1324,11 +1318,11 @@ def diskgroups_configured(name, diskgroups, erase_disks=False): + + # Building a string representation of the capacity disks + # that need to be added +- s = ", ".join(["'{0}'".format(id) for id in added_capacity_disk_displays]) ++ s = ", ".join(["'{}'".format(id) for id in added_capacity_disk_displays]) + if __opts__["test"]: + comments.append( +- "State {0} will add " +- "capacity disk(s) {1} to disk group #{2}." ++ "State {} will add " ++ "capacity disk(s) {} to disk group #{}." + "".format(name, s, idx) + ) + log.info(comments[-1]) +@@ -1343,17 +1337,17 @@ def diskgroups_configured(name, diskgroups, erase_disks=False): + ) + except VMwareSaltError as err: + comments.append( +- "Error adding capacity disk(s) {0} to " +- "disk group #{1}: {2}.".format(s, idx, err) ++ "Error adding capacity disk(s) {} to " ++ "disk group #{}: {}.".format(s, idx, err) + ) + log.error(comments[-1]) + errors = True + continue + +- com = "Added capacity disk(s) {0} to disk group #{1}" "".format(s, idx) ++ com = "Added capacity disk(s) {} to disk group #{}" "".format(s, idx) + log.info(com) + comments.append(com) +- diskgroup_changes[six.text_type(idx)] = { ++ diskgroup_changes[str(idx)] = { + "new": { + "cache": cache_disk_display, + "capacity": capacity_disk_displays, +@@ -1367,9 +1361,7 @@ def diskgroups_configured(name, diskgroups, erase_disks=False): + continue + + # No capacity needs to be added +- s = "Disk group #{0} is correctly configured. Nothing to be done." "".format( +- idx +- ) ++ s = "Disk group #{} is correctly configured. Nothing to be done." "".format(idx) + log.info(s) + comments.append(s) + __salt__["vsphere.disconnect"](si) +@@ -1532,11 +1524,11 @@ def host_cache_configured( + ) + if not existing_disks: + raise VMwareObjectRetrievalError( +- "Disk with scsi address '{0}' was not found in host '{1}'" ++ "Disk with scsi address '{}' was not found in host '{}'" + "".format(datastore["backing_disk_scsi_addr"], hostname) + ) + backing_disk = existing_disks[0] +- backing_disk_display = "{0} (id:{1})".format( ++ backing_disk_display = "{} (id:{})".format( + backing_disk["scsi_address"], backing_disk["id"] + ) + log.trace("backing_disk = %s", backing_disk_display) +@@ -1547,8 +1539,8 @@ def host_cache_configured( + if erase_backing_disk: + if __opts__["test"]: + comments.append( +- "State {0} will erase " +- "the backing disk '{1}' on host '{2}'." ++ "State {} will erase " ++ "the backing disk '{}' on host '{}'." + "".format(name, backing_disk_display, hostname) + ) + log.info(comments[-1]) +@@ -1558,16 +1550,16 @@ def host_cache_configured( + disk_id=backing_disk["id"], service_instance=si + ) + comments.append( +- "Erased backing disk '{0}' on host " +- "'{1}'.".format(backing_disk_display, hostname) ++ "Erased backing disk '{}' on host " ++ "'{}'.".format(backing_disk_display, hostname) + ) + log.info(comments[-1]) + # Create the datastore + if __opts__["test"]: + comments.append( +- "State {0} will create " +- "the datastore '{1}', with backing disk " +- "'{2}', on host '{3}'." ++ "State {} will create " ++ "the datastore '{}', with backing disk " ++ "'{}', on host '{}'." + "".format(name, datastore["name"], backing_disk_display, hostname) + ) + log.info(comments[-1]) +@@ -1582,7 +1574,7 @@ def host_cache_configured( + non_mbr_partitions = [p for p in partitions if p["format"] != "mbr"] + if len(non_mbr_partitions) > 0: + raise VMwareApiError( +- "Backing disk '{0}' has unexpected partitions" ++ "Backing disk '{}' has unexpected partitions" + "".format(backing_disk_display) + ) + __salt__["vsphere.create_vmfs_datastore"]( +@@ -1592,8 +1584,8 @@ def host_cache_configured( + service_instance=si, + ) + comments.append( +- "Created vmfs datastore '{0}', backed by " +- "disk '{1}', on host '{2}'." ++ "Created vmfs datastore '{}', backed by " ++ "disk '{}', on host '{}'." + "".format(datastore["name"], backing_disk_display, hostname) + ) + log.info(comments[-1]) +@@ -1615,21 +1607,21 @@ def host_cache_configured( + # Check datastore is backed by the correct disk + if not existing_datastores[0].get("backing_disk_ids"): + raise VMwareSaltError( +- "Datastore '{0}' doesn't have a " ++ "Datastore '{}' doesn't have a " + "backing disk" + "".format(datastore["name"]) + ) + if backing_disk["id"] not in existing_datastores[0]["backing_disk_ids"]: + + raise VMwareSaltError( +- "Datastore '{0}' is not backed by the correct disk: " +- "expected '{1}'; got {2}" ++ "Datastore '{}' is not backed by the correct disk: " ++ "expected '{}'; got {}" + "".format( + datastore["name"], + backing_disk["id"], + ", ".join( + [ +- "'{0}'".format(disk) ++ "'{}'".format(disk) + for disk in existing_datastores[0]["backing_disk_ids"] + ] + ), +@@ -1637,8 +1629,8 @@ def host_cache_configured( + ) + + comments.append( +- "Datastore '{0}' already exists on host '{1}' " +- "and is backed by disk '{2}'. Nothing to be " ++ "Datastore '{}' already exists on host '{}' " ++ "and is backed by disk '{}'. Nothing to be " + "done.".format(datastore["name"], hostname, backing_disk_display) + ) + existing_datastore = existing_datastores[0] +@@ -1686,8 +1678,8 @@ def host_cache_configured( + if needs_setting: + if __opts__["test"]: + comments.append( +- "State {0} will configure " +- "the host cache on host '{1}' to: {2}." ++ "State {} will configure " ++ "the host cache on host '{}' to: {}." + "".format( + name, + hostname, +@@ -1702,8 +1694,8 @@ def host_cache_configured( + if (existing_datastore["capacity"] / 1024.0 ** 2) < swap_size_MiB: + + raise ArgumentValueError( +- "Capacity of host cache datastore '{0}' ({1} MiB) is " +- "smaller than the required swap size ({2} MiB)" ++ "Capacity of host cache datastore '{}' ({} MiB) is " ++ "smaller than the required swap size ({} MiB)" + "".format( + existing_datastore["name"], + existing_datastore["capacity"] / 1024.0 ** 2, +@@ -1717,11 +1709,11 @@ def host_cache_configured( + service_instance=si, + ) + comments.append( +- "Host cache configured on host " "'{0}'.".format(hostname) ++ "Host cache configured on host " "'{}'.".format(hostname) + ) + else: + comments.append( +- "Host cache on host '{0}' is already correctly " ++ "Host cache on host '{}' is already correctly " + "configured. Nothing to be done.".format(hostname) + ) + result = True +diff --git a/salt/utils/http.py b/salt/utils/http.py +index 9522bd6ee4..c532da63d5 100644 +--- a/salt/utils/http.py ++++ b/salt/utils/http.py +@@ -1062,3 +1062,23 @@ def _sanitize_url_components(comp_list, field): + ret = "{}&".format(comp_list[0]) + comp_list.remove(comp_list[0]) + return ret + _sanitize_url_components(comp_list, field) ++ ++ ++def session(user=None, password=None, verify_ssl=True, ca_bundle=None, headers=None): ++ """ ++ create a requests session ++ """ ++ session = requests.session() ++ if user and password: ++ session.auth = (user, password) ++ if ca_bundle and not verify_ssl: ++ log.error("You cannot use both ca_bundle and verify_ssl False together") ++ return False ++ if ca_bundle: ++ opts = {"ca_bundle": ca_bundle} ++ session.verify = get_ca_bundle(opts) ++ if not verify_ssl: ++ session.verify = False ++ if headers: ++ session.headers.update(headers) ++ return session +diff --git a/salt/utils/thin.py b/salt/utils/thin.py +index ce48957374..60ddd0e67c 100644 +--- a/salt/utils/thin.py ++++ b/salt/utils/thin.py +@@ -217,8 +217,8 @@ def get_tops_python(py_ver, exclude=None, ext_py_ver=None): + "{} does not exist. Could not auto detect dependencies".format(py_ver) + ) + return {} +- py_shell_cmd = "{0} -c 'import {1}; print({1}.__file__)'".format(py_ver, mod) +- cmd = subprocess.Popen(py_shell_cmd, stdout=subprocess.PIPE, shell=True) ++ py_shell_cmd = [py_ver, "-c", "import {0}; print({0}.__file__)".format(mod)] ++ cmd = subprocess.Popen(py_shell_cmd, stdout=subprocess.PIPE) + stdout, _ = cmd.communicate() + mod_file = os.path.abspath(salt.utils.data.decode(stdout).rstrip("\n")) + +diff --git a/salt/utils/vmware.py b/salt/utils/vmware.py +index 57aa2aaa69..f801ba2aab 100644 +--- a/salt/utils/vmware.py ++++ b/salt/utils/vmware.py +@@ -80,7 +80,6 @@ import ssl + import time + from http.client import BadStatusLine + +-import requests + import salt.exceptions + import salt.modules.cmdmod + import salt.utils.path +@@ -182,7 +181,9 @@ def esxcli( + return ret + + +-def get_vsphere_client(server, username, password, session=None): ++def get_vsphere_client( ++ server, username, password, session=None, verify_ssl=True, ca_bundle=None ++): + """ + Internal helper method to create an instance of the vSphere API client. + Please provide username and password to authenticate. +@@ -196,6 +197,10 @@ def get_vsphere_client(server, username, password, session=None): + :param Session session: + Request HTTP session instance. If not specified, one + is automatically created and used ++ :param boolean verify_ssl: ++ Verify the SSL certificate. Default: True ++ :param basestring ca_bundle: ++ Path to the ca bundle to use when verifying SSL certificates. + + :returns: + Vsphere Client instance +@@ -204,9 +209,7 @@ def get_vsphere_client(server, username, password, session=None): + """ + if not session: + # Create an https session to be used for a vSphere client +- session = requests.session() +- # If client uses own SSL cert, session should not verify +- session.verify = False ++ session = salt.utils.http.session(verify_ssl=verify_ssl, ca_bundle=ca_bundle) + client = None + try: + client = create_vsphere_client( +@@ -218,7 +221,15 @@ def get_vsphere_client(server, username, password, session=None): + + + def _get_service_instance( +- host, username, password, protocol, port, mechanism, principal, domain ++ host, ++ username, ++ password, ++ protocol, ++ port, ++ mechanism, ++ principal, ++ domain, ++ verify_ssl=True, + ): + """ + Internal method to authenticate with a vCenter server or ESX/ESXi host +@@ -253,21 +264,26 @@ def _get_service_instance( + raise salt.exceptions.CommandExecutionError( + "Unsupported mechanism: '{}'".format(mechanism) + ) ++ ++ log.trace( ++ "Connecting using the '%s' mechanism, with username '%s'", mechanism, username, ++ ) ++ default_msg = ( ++ "Could not connect to host '{}'. " ++ "Please check the debug log for more information.".format(host) ++ ) ++ + try: +- log.trace( +- "Connecting using the '%s' mechanism, with username '%s'", +- mechanism, +- username, +- ) +- service_instance = SmartConnect( +- host=host, +- user=username, +- pwd=password, +- protocol=protocol, +- port=port, +- b64token=token, +- mechanism=mechanism, +- ) ++ if verify_ssl: ++ service_instance = SmartConnect( ++ host=host, ++ user=username, ++ pwd=password, ++ protocol=protocol, ++ port=port, ++ b64token=token, ++ mechanism=mechanism, ++ ) + except TypeError as exc: + if "unexpected keyword argument" in exc.message: + log.error( +@@ -280,30 +296,33 @@ def _get_service_instance( + raise + except Exception as exc: # pylint: disable=broad-except + # pyVmomi's SmartConnect() actually raises Exception in some cases. +- default_msg = ( +- "Could not connect to host '{}'. " +- "Please check the debug log for more information.".format(host) +- ) ++ if ( ++ isinstance(exc, vim.fault.HostConnectFault) ++ and "[SSL: CERTIFICATE_VERIFY_FAILED]" in exc.msg ++ ) or "[SSL: CERTIFICATE_VERIFY_FAILED]" in str(exc): ++ err_msg = ( ++ "Could not verify the SSL certificate. You can use " ++ "verify_ssl: False if you do not want to verify the " ++ "SSL certificate. This is not recommended as it is " ++ "considered insecure." ++ ) ++ else: ++ log.exception(exc) ++ err_msg = exc.msg if hasattr(exc, "msg") else default_msg ++ raise salt.exceptions.VMwareConnectionError(err_msg) + ++ if not verify_ssl: + try: +- if ( +- isinstance(exc, vim.fault.HostConnectFault) +- and "[SSL: CERTIFICATE_VERIFY_FAILED]" in exc.msg +- ) or "[SSL: CERTIFICATE_VERIFY_FAILED]" in str(exc): +- service_instance = SmartConnect( +- host=host, +- user=username, +- pwd=password, +- protocol=protocol, +- port=port, +- sslContext=ssl._create_unverified_context(), +- b64token=token, +- mechanism=mechanism, +- ) +- else: +- log.exception(exc) +- err_msg = exc.msg if hasattr(exc, "msg") else default_msg +- raise salt.exceptions.VMwareConnectionError(err_msg) ++ service_instance = SmartConnect( ++ host=host, ++ user=username, ++ pwd=password, ++ protocol=protocol, ++ port=port, ++ sslContext=ssl._create_unverified_context(), ++ b64token=token, ++ mechanism=mechanism, ++ ) + except Exception as exc: # pylint: disable=broad-except + # pyVmomi's SmartConnect() actually raises Exception in some cases. + if "certificate verify failed" in str(exc): +@@ -330,6 +349,7 @@ def _get_service_instance( + err_msg = exc.msg if hasattr(exc, "msg") else default_msg + log.trace(exc) + raise salt.exceptions.VMwareConnectionError(err_msg) ++ + atexit.register(Disconnect, service_instance) + return service_instance + +@@ -384,6 +404,7 @@ def get_service_instance( + mechanism="userpass", + principal=None, + domain=None, ++ verify_ssl=True, + ): + """ + Authenticate with a vCenter server or ESX/ESXi host and return the service instance object. +@@ -416,6 +437,9 @@ def get_service_instance( + + domain + Kerberos user domain. Required if mechanism is ``sspi`` ++ ++ verify_ssl ++ Verify the SSL certificate. Default: True + """ + + if protocol is None: +@@ -438,7 +462,15 @@ def get_service_instance( + + if not service_instance: + service_instance = _get_service_instance( +- host, username, password, protocol, port, mechanism, principal, domain ++ host, ++ username, ++ password, ++ protocol, ++ port, ++ mechanism, ++ principal, ++ domain, ++ verify_ssl=verify_ssl, + ) + + # Test if data can actually be retrieved or connection has gone stale +@@ -449,7 +481,15 @@ def get_service_instance( + log.trace("Session no longer authenticating. Reconnecting") + Disconnect(service_instance) + service_instance = _get_service_instance( +- host, username, password, protocol, port, mechanism, principal, domain ++ host, ++ username, ++ password, ++ protocol, ++ port, ++ mechanism, ++ principal, ++ domain, ++ verify_ssl=verify_ssl, + ) + except vim.fault.NoPermission as exc: + log.exception(exc) +diff --git a/salt/wheel/__init__.py b/salt/wheel/__init__.py +index 38792a10f6..53c3d8527f 100644 +--- a/salt/wheel/__init__.py ++++ b/salt/wheel/__init__.py +@@ -1,8 +1,6 @@ +-# -*- coding: utf-8 -*- + """ + Modules used to control the master itself + """ +-from __future__ import absolute_import, print_function, unicode_literals + + from collections.abc import Mapping + +@@ -15,7 +13,7 @@ import salt.utils.zeromq + + + class WheelClient( +- salt.client.mixins.SyncClientMixin, salt.client.mixins.AsyncClientMixin, object ++ salt.client.mixins.SyncClientMixin, salt.client.mixins.AsyncClientMixin + ): + """ + An interface to Salt's wheel modules +@@ -123,8 +121,8 @@ class WheelClient( + }) + {'jid': '20131219224744416681', 'tag': 'salt/wheel/20131219224744416681'} + """ +- fun = low.pop("fun") +- return self.asynchronous(fun, low) ++ fun = low.get("fun") ++ return self.asynchronous(fun, low, local=False) + + def cmd( + self, +@@ -143,9 +141,7 @@ class WheelClient( + >>> wheel.cmd('key.finger', ['jerry']) + {'minions': {'jerry': '5d:f6:79:43:5e:d4:42:3f:57:b8:45:a8:7e:a4:6e:ca'}} + """ +- return super(WheelClient, self).cmd( +- fun, arg, pub_data, kwarg, print_event, full_return +- ) ++ return super().cmd(fun, arg, pub_data, kwarg, print_event, full_return) + + + Wheel = WheelClient # for backward-compat +diff --git a/salt/wheel/pillar_roots.py b/salt/wheel/pillar_roots.py +index 2c242ef3a7..7504d28777 100644 +--- a/salt/wheel/pillar_roots.py ++++ b/salt/wheel/pillar_roots.py +@@ -1,19 +1,14 @@ +-# -*- coding: utf-8 -*- + """ + The `pillar_roots` wheel module is used to manage files under the pillar roots + directories on the master server. + """ + +-# Import python libs +-from __future__ import absolute_import, print_function, unicode_literals + + import os + +-# Import salt libs + import salt.utils.files + import salt.utils.path +- +-# Import 3rd-party libs ++import salt.utils.verify + from salt.ext import six + + +@@ -86,7 +81,7 @@ def read(path, saltenv="base"): + ret = [] + files = find(path, saltenv) + for fn_ in files: +- full = next(six.iterkeys(fn_)) ++ full = next(iter(fn_.keys())) + form = fn_[full] + if form == "txt": + with salt.utils.files.fopen(full, "rb") as fp_: +@@ -100,19 +95,23 @@ def write(data, path, saltenv="base", index=0): + index of the file can be specified to write to a lower priority file root + """ + if saltenv not in __opts__["pillar_roots"]: +- return "Named environment {0} is not present".format(saltenv) ++ return "Named environment {} is not present".format(saltenv) + if len(__opts__["pillar_roots"][saltenv]) <= index: +- return "Specified index {0} in environment {1} is not present".format( ++ return "Specified index {} in environment {} is not present".format( + index, saltenv + ) + if os.path.isabs(path): + return ( +- "The path passed in {0} is not relative to the environment " "{1}" ++ "The path passed in {} is not relative to the environment " "{}" + ).format(path, saltenv) ++ roots_dir = __opts__["pillar_roots"][saltenv][index] ++ dest = os.path.join(roots_dir, path) ++ if not salt.utils.verify.clean_path(roots_dir, dest): ++ return "Invalid path" + dest = os.path.join(__opts__["pillar_roots"][saltenv][index], path) + dest_dir = os.path.dirname(dest) + if not os.path.isdir(dest_dir): + os.makedirs(dest_dir) + with salt.utils.files.fopen(dest, "w+") as fp_: + fp_.write(salt.utils.stringutils.to_str(data)) +- return "Wrote data to file {0}".format(dest) ++ return "Wrote data to file {}".format(dest) +-- +2.30.1 + + diff --git a/fix-regression-on-cmd.run-when-passing-tuples-as-cmd.patch b/fix-regression-on-cmd.run-when-passing-tuples-as-cmd.patch new file mode 100644 index 0000000..802e424 --- /dev/null +++ b/fix-regression-on-cmd.run-when-passing-tuples-as-cmd.patch @@ -0,0 +1,29 @@ +From d8538a57553d94290870671db1d5a4fcd4d7e709 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pablo=20Su=C3=A1rez=20Hern=C3=A1ndez?= + +Date: Fri, 26 Feb 2021 09:15:03 +0000 +Subject: [PATCH] Fix regression on cmd.run when passing tuples as cmd + (bsc#1182740) + +(cherry picked from commit 9a76246adedb60e24a75682077654a352a965cb9) +--- + salt/modules/cmdmod.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/salt/modules/cmdmod.py b/salt/modules/cmdmod.py +index bbc303c3f8..f24e7cc9ae 100644 +--- a/salt/modules/cmdmod.py ++++ b/salt/modules/cmdmod.py +@@ -78,7 +78,7 @@ def __virtual__(): + + + def _log_cmd(cmd): +- if not isinstance(cmd, list): ++ if isinstance(cmd, str): + return cmd.split()[0].strip() + return cmd[0].strip() + +-- +2.30.1 + + diff --git a/fixes-56144-to-enable-hotadd-profile-support.patch b/fixes-56144-to-enable-hotadd-profile-support.patch new file mode 100644 index 0000000..11aa9a8 --- /dev/null +++ b/fixes-56144-to-enable-hotadd-profile-support.patch @@ -0,0 +1,63 @@ +From 5761a11227c8d78df62d1a1552a50c0a4b76ae33 Mon Sep 17 00:00:00 2001 +From: nicholasmhughes +Date: Fri, 14 Feb 2020 22:03:42 -0500 +Subject: [PATCH] fixes #56144 to enable hotadd profile support + +--- + doc/topics/cloud/vmware.rst | 8 ++++++++ + salt/cloud/clouds/vmware.py | 12 ++++++++++++ + 2 files changed, 20 insertions(+) + +diff --git a/doc/topics/cloud/vmware.rst b/doc/topics/cloud/vmware.rst +index e4cb607e8d..0ac7c255a8 100644 +--- a/doc/topics/cloud/vmware.rst ++++ b/doc/topics/cloud/vmware.rst +@@ -457,6 +457,14 @@ Set up an initial profile at ``/etc/salt/cloud.profiles`` or + Specifies whether the new virtual machine should be powered on or not. If + ``template: True`` is set, this field is ignored. Default is ``power_on: True``. + ++``cpu_hot_add`` ++ Boolean value that enables hot-add support for modifying CPU resources while ++ the guest is powered on. ++ ++``mem_hot_add`` ++ Boolean value that enables hot-add support for modifying memory resources while ++ the guest is powered on. ++ + ``extra_config`` + Specifies the additional configuration information for the virtual machine. This + describes a set of modifications to the additional options. If the key is already +diff --git a/salt/cloud/clouds/vmware.py b/salt/cloud/clouds/vmware.py +index 5ebf448abc..edaca9618b 100644 +--- a/salt/cloud/clouds/vmware.py ++++ b/salt/cloud/clouds/vmware.py +@@ -2824,6 +2824,12 @@ def create(vm_): + win_run_once = config.get_cloud_config_value( + "win_run_once", vm_, __opts__, search_global=False, default=None + ) ++ cpu_hot_add = config.get_cloud_config_value( ++ 'cpu_hot_add', vm_, __opts__, search_global=False, default=None ++ ) ++ mem_hot_add = config.get_cloud_config_value( ++ 'mem_hot_add', vm_, __opts__, search_global=False, default=None ++ ) + + # Get service instance object + si = _get_si() +@@ -3042,6 +3048,12 @@ def create(vm_): + ) + config_spec.deviceChange = specs["device_specs"] + ++ if cpu_hot_add and hasattr(config_spec, 'cpuHotAddEnabled'): ++ config_spec.cpuHotAddEnabled = bool(cpu_hot_add) ++ ++ if mem_hot_add and hasattr(config_spec, 'memoryHotAddEnabled'): ++ config_spec.memoryHotAddEnabled = bool(mem_hot_add) ++ + if extra_config: + for key, value in six.iteritems(extra_config): + option = vim.option.OptionValue(key=key, value=value) +-- +2.29.2 + + diff --git a/implementation-of-suse_ip-execution-module-bsc-10999.patch b/implementation-of-suse_ip-execution-module-bsc-10999.patch new file mode 100644 index 0000000..d4157ad --- /dev/null +++ b/implementation-of-suse_ip-execution-module-bsc-10999.patch @@ -0,0 +1,1368 @@ +From fc15e6791deaac9b5ac52268b218e202481440a4 Mon Sep 17 00:00:00 2001 +From: Victor Zhestkov <35733135+vzhestkov@users.noreply.github.com> +Date: Thu, 18 Feb 2021 15:56:01 +0300 +Subject: [PATCH] Implementation of suse_ip execution module + (bsc#1099976) (#323) + +--- + salt/modules/linux_ip.py | 2 + + salt/modules/rh_ip.py | 2 +- + salt/modules/suse_ip.py | 1151 ++++++++++++++++++++++++++ + salt/states/network.py | 35 +- + salt/templates/suse_ip/ifcfg.jinja | 34 + + salt/templates/suse_ip/ifroute.jinja | 8 + + salt/templates/suse_ip/network.jinja | 30 + + setup.py | 1 + + 8 files changed, 1248 insertions(+), 15 deletions(-) + create mode 100644 salt/modules/suse_ip.py + create mode 100644 salt/templates/suse_ip/ifcfg.jinja + create mode 100644 salt/templates/suse_ip/ifroute.jinja + create mode 100644 salt/templates/suse_ip/network.jinja + +diff --git a/salt/modules/linux_ip.py b/salt/modules/linux_ip.py +index bac0665de2..e7a268694d 100644 +--- a/salt/modules/linux_ip.py ++++ b/salt/modules/linux_ip.py +@@ -21,6 +21,8 @@ def __virtual__(): + """ + if salt.utils.platform.is_windows(): + return (False, "Module linux_ip: Windows systems are not supported.") ++ if __grains__['os_family'] == "Suse": ++ return (False, "Module linux_ip: SUSE systems are not supported.") + if __grains__["os_family"] == "RedHat": + return (False, "Module linux_ip: RedHat systems are not supported.") + if __grains__["os_family"] == "Debian": +diff --git a/salt/modules/rh_ip.py b/salt/modules/rh_ip.py +index 2da954bdd0..fa13cc85d1 100644 +--- a/salt/modules/rh_ip.py ++++ b/salt/modules/rh_ip.py +@@ -543,7 +543,7 @@ def _parse_settings_eth(opts, iface_type, enabled, iface): + """ + result = {"name": iface} + if "proto" in opts: +- valid = ["none", "bootp", "dhcp"] ++ valid = ["none", "static", "bootp", "dhcp"] + if opts["proto"] in valid: + result["proto"] = opts["proto"] + else: +diff --git a/salt/modules/suse_ip.py b/salt/modules/suse_ip.py +new file mode 100644 +index 0000000000..92dad50351 +--- /dev/null ++++ b/salt/modules/suse_ip.py +@@ -0,0 +1,1151 @@ ++# -*- coding: utf-8 -*- ++""" ++The networking module for SUSE based distros ++""" ++from __future__ import absolute_import, print_function, unicode_literals ++ ++# Import python libs ++import logging ++import os ++ ++# Import third party libs ++import jinja2 ++import jinja2.exceptions ++ ++# Import salt libs ++import salt.utils.files ++import salt.utils.stringutils ++import salt.utils.templates ++import salt.utils.validate.net ++from salt.exceptions import CommandExecutionError ++from salt.ext import six ++ ++# Set up logging ++log = logging.getLogger(__name__) ++ ++# Set up template environment ++JINJA = jinja2.Environment( ++ loader=jinja2.FileSystemLoader( ++ os.path.join(salt.utils.templates.TEMPLATE_DIRNAME, "suse_ip") ++ ) ++) ++ ++# Define the module's virtual name ++__virtualname__ = "ip" ++ ++# Default values for bonding ++_BOND_DEFAULTS = { ++ # 803.ad aggregation selection logic ++ # 0 for stable (default) ++ # 1 for bandwidth ++ # 2 for count ++ "ad_select": "0", ++ # Max number of transmit queues (default = 16) ++ "tx_queues": "16", ++ # lacp_rate 0: Slow - every 30 seconds ++ # lacp_rate 1: Fast - every 1 second ++ "lacp_rate": "0", ++ # Max bonds for this driver ++ "max_bonds": "1", ++ # Used with miimon. ++ # On: driver sends mii ++ # Off: ethtool sends mii ++ "use_carrier": "0", ++ # Default. Don't change unless you know what you are doing. ++ "xmit_hash_policy": "layer2", ++} ++_SUSE_NETWORK_SCRIPT_DIR = "/etc/sysconfig/network" ++_SUSE_NETWORK_FILE = "/etc/sysconfig/network/config" ++_SUSE_NETWORK_ROUTES_FILE = "/etc/sysconfig/network/routes" ++_CONFIG_TRUE = ("yes", "on", "true", "1", True) ++_CONFIG_FALSE = ("no", "off", "false", "0", False) ++_IFACE_TYPES = ( ++ "eth", ++ "bond", ++ "alias", ++ "clone", ++ "ipsec", ++ "dialup", ++ "bridge", ++ "slave", ++ "vlan", ++ "ipip", ++ "ib", ++) ++ ++ ++def __virtual__(): ++ """ ++ Confine this module to SUSE based distros ++ """ ++ if __grains__["os_family"] == "Suse": ++ return __virtualname__ ++ return ( ++ False, ++ "The suse_ip execution module cannot be loaded: this module is only available on SUSE based distributions.", ++ ) ++ ++ ++def _error_msg_iface(iface, option, expected): ++ """ ++ Build an appropriate error message from a given option and ++ a list of expected values. ++ """ ++ if isinstance(expected, six.string_types): ++ expected = (expected,) ++ msg = "Invalid option -- Interface: {0}, Option: {1}, Expected: [{2}]" ++ return msg.format(iface, option, "|".join(str(e) for e in expected)) ++ ++ ++def _error_msg_routes(iface, option, expected): ++ """ ++ Build an appropriate error message from a given option and ++ a list of expected values. ++ """ ++ msg = "Invalid option -- Route interface: {0}, Option: {1}, Expected: [{2}]" ++ return msg.format(iface, option, expected) ++ ++ ++def _log_default_iface(iface, opt, value): ++ log.info( ++ "Using default option -- Interface: %s Option: %s Value: %s", iface, opt, value ++ ) ++ ++ ++def _error_msg_network(option, expected): ++ """ ++ Build an appropriate error message from a given option and ++ a list of expected values. ++ """ ++ if isinstance(expected, six.string_types): ++ expected = (expected,) ++ msg = "Invalid network setting -- Setting: {0}, Expected: [{1}]" ++ return msg.format(option, "|".join(str(e) for e in expected)) ++ ++ ++def _log_default_network(opt, value): ++ log.info("Using existing setting -- Setting: %s Value: %s", opt, value) ++ ++ ++def _parse_suse_config(path): ++ suse_config = _read_file(path) ++ cv_suse_config = {} ++ if suse_config: ++ for line in suse_config: ++ line = line.strip() ++ if len(line) == 0 or line.startswith("!") or line.startswith("#"): ++ continue ++ pair = [p.rstrip() for p in line.split("=", 1)] ++ if len(pair) != 2: ++ continue ++ name, value = pair ++ cv_suse_config[name.upper()] = salt.utils.stringutils.dequote(value) ++ ++ return cv_suse_config ++ ++ ++def _parse_ethtool_opts(opts, iface): ++ """ ++ Filters given options and outputs valid settings for ETHTOOLS_OPTS ++ If an option has a value that is not expected, this ++ function will log what the Interface, Setting and what it was ++ expecting. ++ """ ++ config = {} ++ ++ if "autoneg" in opts: ++ if opts["autoneg"] in _CONFIG_TRUE: ++ config.update({"autoneg": "on"}) ++ elif opts["autoneg"] in _CONFIG_FALSE: ++ config.update({"autoneg": "off"}) ++ else: ++ _raise_error_iface(iface, "autoneg", _CONFIG_TRUE + _CONFIG_FALSE) ++ ++ if "duplex" in opts: ++ valid = ["full", "half"] ++ if opts["duplex"] in valid: ++ config.update({"duplex": opts["duplex"]}) ++ else: ++ _raise_error_iface(iface, "duplex", valid) ++ ++ if "speed" in opts: ++ valid = ["10", "100", "1000", "10000"] ++ if six.text_type(opts["speed"]) in valid: ++ config.update({"speed": opts["speed"]}) ++ else: ++ _raise_error_iface(iface, opts["speed"], valid) ++ ++ if "advertise" in opts: ++ valid = [ ++ "0x001", ++ "0x002", ++ "0x004", ++ "0x008", ++ "0x010", ++ "0x020", ++ "0x20000", ++ "0x8000", ++ "0x1000", ++ "0x40000", ++ "0x80000", ++ "0x200000", ++ "0x400000", ++ "0x800000", ++ "0x1000000", ++ "0x2000000", ++ "0x4000000", ++ ] ++ if six.text_type(opts["advertise"]) in valid: ++ config.update({"advertise": opts["advertise"]}) ++ else: ++ _raise_error_iface(iface, "advertise", valid) ++ ++ valid = _CONFIG_TRUE + _CONFIG_FALSE ++ for option in ("rx", "tx", "sg", "tso", "ufo", "gso", "gro", "lro"): ++ if option in opts: ++ if opts[option] in _CONFIG_TRUE: ++ config.update({option: "on"}) ++ elif opts[option] in _CONFIG_FALSE: ++ config.update({option: "off"}) ++ else: ++ _raise_error_iface(iface, option, valid) ++ ++ return config ++ ++ ++def _parse_settings_bond(opts, iface): ++ """ ++ Filters given options and outputs valid settings for requested ++ operation. If an option has a value that is not expected, this ++ function will log what the Interface, Setting and what it was ++ expecting. ++ """ ++ if opts["mode"] in ("balance-rr", "0"): ++ log.info("Device: %s Bonding Mode: load balancing (round-robin)", iface) ++ return _parse_settings_bond_0(opts, iface) ++ elif opts["mode"] in ("active-backup", "1"): ++ log.info("Device: %s Bonding Mode: fault-tolerance (active-backup)", iface) ++ return _parse_settings_bond_1(opts, iface) ++ elif opts["mode"] in ("balance-xor", "2"): ++ log.info("Device: %s Bonding Mode: load balancing (xor)", iface) ++ return _parse_settings_bond_2(opts, iface) ++ elif opts["mode"] in ("broadcast", "3"): ++ log.info("Device: %s Bonding Mode: fault-tolerance (broadcast)", iface) ++ return _parse_settings_bond_3(opts, iface) ++ elif opts["mode"] in ("802.3ad", "4"): ++ log.info( ++ "Device: %s Bonding Mode: IEEE 802.3ad Dynamic link " "aggregation", iface ++ ) ++ return _parse_settings_bond_4(opts, iface) ++ elif opts["mode"] in ("balance-tlb", "5"): ++ log.info("Device: %s Bonding Mode: transmit load balancing", iface) ++ return _parse_settings_bond_5(opts, iface) ++ elif opts["mode"] in ("balance-alb", "6"): ++ log.info("Device: %s Bonding Mode: adaptive load balancing", iface) ++ return _parse_settings_bond_6(opts, iface) ++ else: ++ valid = ( ++ "0", ++ "1", ++ "2", ++ "3", ++ "4", ++ "5", ++ "6", ++ "balance-rr", ++ "active-backup", ++ "balance-xor", ++ "broadcast", ++ "802.3ad", ++ "balance-tlb", ++ "balance-alb", ++ ) ++ _raise_error_iface(iface, "mode", valid) ++ ++ ++def _parse_settings_miimon(opts, iface): ++ """ ++ Add shared settings for miimon support used by balance-rr, balance-xor ++ bonding types. ++ """ ++ ret = {} ++ for binding in ("miimon", "downdelay", "updelay"): ++ if binding in opts: ++ try: ++ int(opts[binding]) ++ ret.update({binding: opts[binding]}) ++ except Exception: # pylint: disable=broad-except ++ _raise_error_iface(iface, binding, "integer") ++ ++ if "miimon" in opts: ++ if not opts["miimon"]: ++ _raise_error_iface(iface, "miimon", "nonzero integer") ++ ++ for binding in ("downdelay", "updelay"): ++ if binding in ret: ++ if ret[binding] % ret["miimon"]: ++ _raise_error_iface( ++ iface, ++ binding, ++ "0 or a multiple of miimon ({0})".format(ret["miimon"]), ++ ) ++ ++ if "use_carrier" in opts: ++ if opts["use_carrier"] in _CONFIG_TRUE: ++ ret.update({"use_carrier": "1"}) ++ elif opts["use_carrier"] in _CONFIG_FALSE: ++ ret.update({"use_carrier": "0"}) ++ else: ++ valid = _CONFIG_TRUE + _CONFIG_FALSE ++ _raise_error_iface(iface, "use_carrier", valid) ++ else: ++ _log_default_iface(iface, "use_carrier", _BOND_DEFAULTS["use_carrier"]) ++ ret.update({"use_carrier": _BOND_DEFAULTS["use_carrier"]}) ++ ++ return ret ++ ++ ++def _parse_settings_arp(opts, iface): ++ """ ++ Add shared settings for arp used by balance-rr, balance-xor bonding types. ++ """ ++ ret = {} ++ if "arp_interval" in opts: ++ try: ++ int(opts["arp_interval"]) ++ ret.update({"arp_interval": opts["arp_interval"]}) ++ except Exception: # pylint: disable=broad-except ++ _raise_error_iface(iface, "arp_interval", "integer") ++ ++ # ARP targets in n.n.n.n form ++ valid = "list of ips (up to 16)" ++ if "arp_ip_target" in opts: ++ if isinstance(opts["arp_ip_target"], list): ++ if 1 <= len(opts["arp_ip_target"]) <= 16: ++ ret.update({"arp_ip_target": ",".join(opts["arp_ip_target"])}) ++ else: ++ _raise_error_iface(iface, "arp_ip_target", valid) ++ else: ++ _raise_error_iface(iface, "arp_ip_target", valid) ++ else: ++ _raise_error_iface(iface, "arp_ip_target", valid) ++ ++ return ret ++ ++ ++def _parse_settings_bond_0(opts, iface): ++ """ ++ Filters given options and outputs valid settings for bond0. ++ If an option has a value that is not expected, this ++ function will log what the Interface, Setting and what it was ++ expecting. ++ """ ++ bond = {"mode": "0"} ++ bond.update(_parse_settings_miimon(opts, iface)) ++ bond.update(_parse_settings_arp(opts, iface)) ++ ++ if "miimon" not in opts and "arp_interval" not in opts: ++ _raise_error_iface( ++ iface, "miimon or arp_interval", "at least one of these is required" ++ ) ++ ++ return bond ++ ++ ++def _parse_settings_bond_1(opts, iface): ++ ++ """ ++ Filters given options and outputs valid settings for bond1. ++ If an option has a value that is not expected, this ++ function will log what the Interface, Setting and what it was ++ expecting. ++ """ ++ bond = {"mode": "1"} ++ bond.update(_parse_settings_miimon(opts, iface)) ++ ++ if "miimon" not in opts: ++ _raise_error_iface(iface, "miimon", "integer") ++ ++ if "primary" in opts: ++ bond.update({"primary": opts["primary"]}) ++ ++ return bond ++ ++ ++def _parse_settings_bond_2(opts, iface): ++ """ ++ Filters given options and outputs valid settings for bond2. ++ If an option has a value that is not expected, this ++ function will log what the Interface, Setting and what it was ++ expecting. ++ """ ++ bond = {"mode": "2"} ++ bond.update(_parse_settings_miimon(opts, iface)) ++ bond.update(_parse_settings_arp(opts, iface)) ++ ++ if "miimon" not in opts and "arp_interval" not in opts: ++ _raise_error_iface( ++ iface, "miimon or arp_interval", "at least one of these is required" ++ ) ++ ++ if "hashing-algorithm" in opts: ++ valid = ("layer2", "layer2+3", "layer3+4") ++ if opts["hashing-algorithm"] in valid: ++ bond.update({"xmit_hash_policy": opts["hashing-algorithm"]}) ++ else: ++ _raise_error_iface(iface, "hashing-algorithm", valid) ++ ++ return bond ++ ++ ++def _parse_settings_bond_3(opts, iface): ++ ++ """ ++ Filters given options and outputs valid settings for bond3. ++ If an option has a value that is not expected, this ++ function will log what the Interface, Setting and what it was ++ expecting. ++ """ ++ bond = {"mode": "3"} ++ bond.update(_parse_settings_miimon(opts, iface)) ++ ++ if "miimon" not in opts: ++ _raise_error_iface(iface, "miimon", "integer") ++ ++ return bond ++ ++ ++def _parse_settings_bond_4(opts, iface): ++ """ ++ Filters given options and outputs valid settings for bond4. ++ If an option has a value that is not expected, this ++ function will log what the Interface, Setting and what it was ++ expecting. ++ """ ++ bond = {"mode": "4"} ++ bond.update(_parse_settings_miimon(opts, iface)) ++ ++ if "miimon" not in opts: ++ _raise_error_iface(iface, "miimon", "integer") ++ ++ for binding in ("lacp_rate", "ad_select"): ++ if binding in opts: ++ if binding == "lacp_rate": ++ valid = ("fast", "1", "slow", "0") ++ if opts[binding] not in valid: ++ _raise_error_iface(iface, binding, valid) ++ if opts[binding] == "fast": ++ opts.update({binding: "1"}) ++ if opts[binding] == "slow": ++ opts.update({binding: "0"}) ++ else: ++ valid = "integer" ++ try: ++ int(opts[binding]) ++ bond.update({binding: opts[binding]}) ++ except Exception: # pylint: disable=broad-except ++ _raise_error_iface(iface, binding, valid) ++ else: ++ _log_default_iface(iface, binding, _BOND_DEFAULTS[binding]) ++ bond.update({binding: _BOND_DEFAULTS[binding]}) ++ ++ if "hashing-algorithm" in opts: ++ valid = ("layer2", "layer2+3", "layer3+4") ++ if opts["hashing-algorithm"] in valid: ++ bond.update({"xmit_hash_policy": opts["hashing-algorithm"]}) ++ else: ++ _raise_error_iface(iface, "hashing-algorithm", valid) ++ ++ return bond ++ ++ ++def _parse_settings_bond_5(opts, iface): ++ ++ """ ++ Filters given options and outputs valid settings for bond5. ++ If an option has a value that is not expected, this ++ function will log what the Interface, Setting and what it was ++ expecting. ++ """ ++ bond = {"mode": "5"} ++ bond.update(_parse_settings_miimon(opts, iface)) ++ ++ if "miimon" not in opts: ++ _raise_error_iface(iface, "miimon", "integer") ++ ++ if "primary" in opts: ++ bond.update({"primary": opts["primary"]}) ++ ++ return bond ++ ++ ++def _parse_settings_bond_6(opts, iface): ++ ++ """ ++ Filters given options and outputs valid settings for bond6. ++ If an option has a value that is not expected, this ++ function will log what the Interface, Setting and what it was ++ expecting. ++ """ ++ bond = {"mode": "6"} ++ bond.update(_parse_settings_miimon(opts, iface)) ++ ++ if "miimon" not in opts: ++ _raise_error_iface(iface, "miimon", "integer") ++ ++ if "primary" in opts: ++ bond.update({"primary": opts["primary"]}) ++ ++ return bond ++ ++ ++def _parse_settings_vlan(opts, iface): ++ ++ """ ++ Filters given options and outputs valid settings for a vlan ++ """ ++ vlan = {} ++ if "reorder_hdr" in opts: ++ if opts["reorder_hdr"] in _CONFIG_TRUE + _CONFIG_FALSE: ++ vlan.update({"reorder_hdr": opts["reorder_hdr"]}) ++ else: ++ valid = _CONFIG_TRUE + _CONFIG_FALSE ++ _raise_error_iface(iface, "reorder_hdr", valid) ++ ++ if "vlan_id" in opts: ++ if opts["vlan_id"] > 0: ++ vlan.update({"vlan_id": opts["vlan_id"]}) ++ else: ++ _raise_error_iface(iface, "vlan_id", "Positive integer") ++ ++ if "phys_dev" in opts: ++ if len(opts["phys_dev"]) > 0: ++ vlan.update({"phys_dev": opts["phys_dev"]}) ++ else: ++ _raise_error_iface(iface, "phys_dev", "Non-empty string") ++ ++ return vlan ++ ++ ++def _parse_settings_eth(opts, iface_type, enabled, iface): ++ """ ++ Filters given options and outputs valid settings for a ++ network interface. ++ """ ++ result = {"name": iface} ++ if "proto" in opts: ++ valid = ["static", "dhcp", "dhcp4", "dhcp6", "autoip", "dhcp+autoip", "auto6", "6to4", "none"] ++ if opts["proto"] in valid: ++ result["proto"] = opts["proto"] ++ else: ++ _raise_error_iface(iface, opts["proto"], valid) ++ ++ if "mtu" in opts: ++ try: ++ result["mtu"] = int(opts["mtu"]) ++ except ValueError: ++ _raise_error_iface(iface, "mtu", ["integer"]) ++ ++ if "hwaddr" in opts and "macaddr" in opts: ++ msg = "Cannot pass both hwaddr and macaddr. Must use either hwaddr or macaddr" ++ log.error(msg) ++ raise AttributeError(msg) ++ ++ if iface_type not in ("bridge",): ++ ethtool = _parse_ethtool_opts(opts, iface) ++ if ethtool: ++ result["ethtool"] = " ".join( ++ ["{0} {1}".format(x, y) for x, y in ethtool.items()] ++ ) ++ ++ if iface_type == "slave": ++ result["proto"] = "none" ++ ++ ++ if iface_type == "bond": ++ if "mode" not in opts: ++ msg = "Missing required option 'mode'" ++ log.error("%s for bond interface '%s'", msg, iface) ++ raise AttributeError(msg) ++ bonding = _parse_settings_bond(opts, iface) ++ if bonding: ++ result["bonding"] = " ".join( ++ ["{0}={1}".format(x, y) for x, y in bonding.items()] ++ ) ++ result["devtype"] = "Bond" ++ if "slaves" in opts: ++ if isinstance(opts["slaves"], list): ++ result["slaves"] = opts["slaves"] ++ else: ++ result["slaves"] = opts["slaves"].split() ++ ++ if iface_type == "vlan": ++ vlan = _parse_settings_vlan(opts, iface) ++ if vlan: ++ result["devtype"] = "Vlan" ++ for opt in vlan: ++ result[opt] = opts[opt] ++ ++ if iface_type == "eth": ++ result["devtype"] = "Ethernet" ++ ++ if iface_type == "bridge": ++ result["devtype"] = "Bridge" ++ bypassfirewall = True ++ valid = _CONFIG_TRUE + _CONFIG_FALSE ++ for opt in ("bypassfirewall",): ++ if opt in opts: ++ if opts[opt] in _CONFIG_TRUE: ++ bypassfirewall = True ++ elif opts[opt] in _CONFIG_FALSE: ++ bypassfirewall = False ++ else: ++ _raise_error_iface(iface, opts[opt], valid) ++ ++ bridgectls = [ ++ "net.bridge.bridge-nf-call-ip6tables", ++ "net.bridge.bridge-nf-call-iptables", ++ "net.bridge.bridge-nf-call-arptables", ++ ] ++ ++ if bypassfirewall: ++ sysctl_value = 0 ++ else: ++ sysctl_value = 1 ++ ++ for sysctl in bridgectls: ++ try: ++ __salt__["sysctl.persist"](sysctl, sysctl_value) ++ except CommandExecutionError: ++ log.warning("Failed to set sysctl: %s", sysctl) ++ ++ else: ++ if "bridge" in opts: ++ result["bridge"] = opts["bridge"] ++ ++ if iface_type == "ipip": ++ result["devtype"] = "IPIP" ++ for opt in ("my_inner_ipaddr", "my_outer_ipaddr"): ++ if opt not in opts: ++ _raise_error_iface(iface, opt, "1.2.3.4") ++ else: ++ result[opt] = opts[opt] ++ if iface_type == "ib": ++ result["devtype"] = "InfiniBand" ++ ++ if "prefix" in opts: ++ if "netmask" in opts: ++ msg = "Cannot use prefix and netmask together" ++ log.error(msg) ++ raise AttributeError(msg) ++ result["prefix"] = opts["prefix"] ++ elif "netmask" in opts: ++ result["netmask"] = opts["netmask"] ++ ++ for opt in ( ++ "ipaddr", ++ "master", ++ "srcaddr", ++ "delay", ++ "domain", ++ "gateway", ++ "uuid", ++ "nickname", ++ "zone", ++ ): ++ if opt in opts: ++ result[opt] = opts[opt] ++ ++ if "ipaddrs" in opts or "ipv6addr" in opts or "ipv6addrs" in opts: ++ result["ipaddrs"] = [] ++ addrs = list ++ for opt in opts["ipaddrs"]: ++ if salt.utils.validate.net.ipv4_addr(opt) or salt.utils.validate.net.ipv6_addr(opt): ++ result['ipaddrs'].append(opt) ++ else: ++ msg = "{0} is invalid ipv4 or ipv6 CIDR" ++ log.error(msg) ++ raise AttributeError(msg) ++ if salt.utils.validate.net.ipv6_addr(opts["ipv6addr"]): ++ result['ipaddrs'].append(opts["ipv6addr"]) ++ else: ++ msg = "{0} is invalid ipv6 CIDR" ++ log.error(msg) ++ raise AttributeError(msg) ++ for opt in opts["ipv6addrs"]: ++ if salt.utils.validate.net.ipv6_addr(opt): ++ result['ipaddrs'].append(opt) ++ else: ++ msg = "{0} is invalid ipv6 CIDR" ++ log.error(msg) ++ raise AttributeError(msg) ++ ++ if "enable_ipv6" in opts: ++ result["enable_ipv6"] = opts["enable_ipv6"] ++ ++ valid = _CONFIG_TRUE + _CONFIG_FALSE ++ for opt in ( ++ "onparent", ++ "peerdns", ++ "peerroutes", ++ "slave", ++ "vlan", ++ "defroute", ++ "stp", ++ "ipv6_peerdns", ++ "ipv6_defroute", ++ "ipv6_peerroutes", ++ "ipv6_autoconf", ++ "ipv4_failure_fatal", ++ "dhcpv6c", ++ ): ++ if opt in opts: ++ if opts[opt] in _CONFIG_TRUE: ++ result[opt] = "yes" ++ elif opts[opt] in _CONFIG_FALSE: ++ result[opt] = "no" ++ else: ++ _raise_error_iface(iface, opts[opt], valid) ++ ++ if "onboot" in opts: ++ log.warning( ++ "The 'onboot' option is controlled by the 'enabled' option. " ++ "Interface: %s Enabled: %s", ++ iface, ++ enabled, ++ ) ++ ++ if "startmode" in opts: ++ valid = ("manual", "auto", "nfsroot", "hotplug", "off") ++ if opts["startmode"] in valid: ++ result["startmode"] = opts["startmode"] ++ else: ++ _raise_error_iface(iface, opts["startmode"], valid) ++ else: ++ if enabled: ++ result["startmode"] = "auto" ++ else: ++ result["startmode"] = "off" ++ ++ # This vlan is in opts, and should be only used in range interface ++ # will affect jinja template for interface generating ++ if "vlan" in opts: ++ if opts["vlan"] in _CONFIG_TRUE: ++ result["vlan"] = "yes" ++ elif opts["vlan"] in _CONFIG_FALSE: ++ result["vlan"] = "no" ++ else: ++ _raise_error_iface(iface, opts["vlan"], valid) ++ ++ if "arpcheck" in opts: ++ if opts["arpcheck"] in _CONFIG_FALSE: ++ result["arpcheck"] = "no" ++ ++ if "ipaddr_start" in opts: ++ result["ipaddr_start"] = opts["ipaddr_start"] ++ ++ if "ipaddr_end" in opts: ++ result["ipaddr_end"] = opts["ipaddr_end"] ++ ++ if "clonenum_start" in opts: ++ result["clonenum_start"] = opts["clonenum_start"] ++ ++ if "hwaddr" in opts: ++ result["hwaddr"] = opts["hwaddr"] ++ ++ if "macaddr" in opts: ++ result["macaddr"] = opts["macaddr"] ++ ++ # If NetworkManager is available, we can control whether we use ++ # it or not ++ if "nm_controlled" in opts: ++ if opts["nm_controlled"] in _CONFIG_TRUE: ++ result["nm_controlled"] = "yes" ++ elif opts["nm_controlled"] in _CONFIG_FALSE: ++ result["nm_controlled"] = "no" ++ else: ++ _raise_error_iface(iface, opts["nm_controlled"], valid) ++ else: ++ result["nm_controlled"] = "no" ++ ++ return result ++ ++ ++def _parse_routes(iface, opts): ++ """ ++ Filters given options and outputs valid settings for ++ the route settings file. ++ """ ++ # Normalize keys ++ opts = dict((k.lower(), v) for (k, v) in six.iteritems(opts)) ++ result = {} ++ if "routes" not in opts: ++ _raise_error_routes(iface, "routes", "List of routes") ++ ++ for opt in opts: ++ result[opt] = opts[opt] ++ ++ return result ++ ++ ++def _parse_network_settings(opts, current): ++ """ ++ Filters given options and outputs valid settings for ++ the global network settings file. ++ """ ++ # Normalize keys ++ opts = dict((k.lower(), v) for (k, v) in six.iteritems(opts)) ++ current = dict((k.lower(), v) for (k, v) in six.iteritems(current)) ++ ++ # Check for supported parameters ++ retain_settings = opts.get("retain_settings", False) ++ result = {} ++ if retain_settings: ++ for opt in current: ++ nopt = opt ++ if opt == "netconfig_dns_static_servers": ++ nopt = "dns" ++ result[nopt] = current[opt].split() ++ elif opt == "netconfig_dns_static_searchlist": ++ nopt = "dns_search" ++ result[nopt] = current[opt].split() ++ elif opt.startswith("netconfig_") and opt not in ("netconfig_modules_order", "netconfig_verbose", "netconfig_force_replace"): ++ nopt = opt[10:] ++ result[nopt] = current[opt] ++ else: ++ result[nopt] = current[opt] ++ _log_default_network(nopt, current[opt]) ++ ++ for opt in opts: ++ if opt in ("dns", "dns_search") and not isinstance(opts[opt], list): ++ result[opt] = opts[opt].split() ++ else: ++ result[opt] = opts[opt] ++ return result ++ ++ ++def _raise_error_iface(iface, option, expected): ++ """ ++ Log and raise an error with a logical formatted message. ++ """ ++ msg = _error_msg_iface(iface, option, expected) ++ log.error(msg) ++ raise AttributeError(msg) ++ ++ ++def _raise_error_network(option, expected): ++ """ ++ Log and raise an error with a logical formatted message. ++ """ ++ msg = _error_msg_network(option, expected) ++ log.error(msg) ++ raise AttributeError(msg) ++ ++ ++def _raise_error_routes(iface, option, expected): ++ """ ++ Log and raise an error with a logical formatted message. ++ """ ++ msg = _error_msg_routes(iface, option, expected) ++ log.error(msg) ++ raise AttributeError(msg) ++ ++ ++def _read_file(path): ++ """ ++ Reads and returns the contents of a file ++ """ ++ try: ++ with salt.utils.files.fopen(path, "rb") as rfh: ++ lines = salt.utils.stringutils.to_unicode(rfh.read()).splitlines() ++ try: ++ lines.remove("") ++ except ValueError: ++ pass ++ return lines ++ except Exception: # pylint: disable=broad-except ++ return [] # Return empty list for type consistency ++ ++ ++def _write_file_iface(iface, data, folder, pattern): ++ ''' ++ Writes a file to disk ++ ''' ++ filename = os.path.join(folder, pattern.format(iface)) ++ if not os.path.exists(folder): ++ msg = '{0} cannot be written. {1} does not exist' ++ msg = msg.format(filename, folder) ++ log.error(msg) ++ raise AttributeError(msg) ++ with salt.utils.files.fopen(filename, 'w') as fp_: ++ fp_.write(salt.utils.stringutils.to_str(data)) ++ ++ ++def _write_file_network(data, filename): ++ """ ++ Writes a file to disk ++ """ ++ with salt.utils.files.fopen(filename, "w") as fp_: ++ fp_.write(salt.utils.stringutils.to_str(data)) ++ ++ ++def _read_temp(data): ++ lines = data.splitlines() ++ try: # Discard newlines if they exist ++ lines.remove("") ++ except ValueError: ++ pass ++ return lines ++ ++ ++def build_interface(iface, iface_type, enabled, **settings): ++ """ ++ Build an interface script for a network interface. ++ ++ CLI Example: ++ ++ .. code-block:: bash ++ ++ salt '*' ip.build_interface eth0 eth ++ """ ++ iface_type = iface_type.lower() ++ ++ if iface_type not in _IFACE_TYPES: ++ _raise_error_iface(iface, iface_type, _IFACE_TYPES) ++ ++ if iface_type == "slave": ++ settings["slave"] = "yes" ++ if "master" not in settings: ++ msg = "master is a required setting for slave interfaces" ++ log.error(msg) ++ raise AttributeError(msg) ++ ++ if iface_type == "bond": ++ if "mode" not in settings: ++ msg = "mode is required for bond interfaces" ++ log.error(msg) ++ raise AttributeError(msg) ++ settings["mode"] = str(settings["mode"]) ++ ++ if iface_type == "vlan": ++ settings["vlan"] = "yes" ++ ++ if iface_type == "bridge" and not __salt__["pkg.version"]("bridge-utils"): ++ __salt__["pkg.install"]("bridge-utils") ++ ++ if iface_type in ( ++ "eth", ++ "bond", ++ "bridge", ++ "slave", ++ "vlan", ++ "ipip", ++ "ib", ++ "alias", ++ ): ++ opts = _parse_settings_eth(settings, iface_type, enabled, iface) ++ try: ++ template = JINJA.get_template("ifcfg.jinja") ++ except jinja2.exceptions.TemplateNotFound: ++ log.error("Could not load template ifcfg.jinja") ++ return "" ++ log.debug("Interface opts: \n %s", opts) ++ ifcfg = template.render(opts) ++ ++ if settings.get("test"): ++ return _read_temp(ifcfg) ++ ++ _write_file_iface(iface, ifcfg, _SUSE_NETWORK_SCRIPT_DIR, "ifcfg-{0}") ++ path = os.path.join(_SUSE_NETWORK_SCRIPT_DIR, "ifcfg-{0}".format(iface)) ++ ++ return _read_file(path) ++ ++ ++def build_routes(iface, **settings): ++ """ ++ Build a route script for a network interface. ++ ++ CLI Example: ++ ++ .. code-block:: bash ++ ++ salt '*' ip.build_routes eth0 ++ """ ++ ++ template = "ifroute.jinja" ++ log.debug("Template name: %s", template) ++ ++ opts = _parse_routes(iface, settings) ++ log.debug("Opts: \n %s", opts) ++ try: ++ template = JINJA.get_template(template) ++ except jinja2.exceptions.TemplateNotFound: ++ log.error("Could not load template %s", template) ++ return "" ++ log.debug("IP routes:\n%s", opts["routes"]) ++ ++ if iface == "routes": ++ routecfg = template.render(routes=opts["routes"]) ++ else: ++ routecfg = template.render(routes=opts["routes"], iface=iface) ++ ++ if settings["test"]: ++ return _read_temp(routecfg) ++ ++ if iface == "routes": ++ path = _SUSE_NETWORK_ROUTES_FILE ++ else: ++ path = os.path.join(_SUSE_NETWORK_SCRIPT_DIR, "ifroute-{0}".format(iface)) ++ ++ _write_file_network(routecfg, path) ++ ++ return _read_file(path) ++ ++ ++def down(iface, iface_type=None): ++ """ ++ Shutdown a network interface ++ ++ CLI Example: ++ ++ .. code-block:: bash ++ ++ salt '*' ip.down eth0 ++ """ ++ # Slave devices are controlled by the master. ++ if not iface_type or iface_type.lower() != "slave": ++ return __salt__["cmd.run"]("ifdown {0}".format(iface)) ++ return None ++ ++ ++def get_interface(iface): ++ """ ++ Return the contents of an interface script ++ ++ CLI Example: ++ ++ .. code-block:: bash ++ ++ salt '*' ip.get_interface eth0 ++ """ ++ path = os.path.join(_SUSE_NETWORK_SCRIPT_DIR, "ifcfg-{0}".format(iface)) ++ return _read_file(path) ++ ++ ++def up(iface, iface_type=None): ++ """ ++ Start up a network interface ++ ++ CLI Example: ++ ++ .. code-block:: bash ++ ++ salt '*' ip.up eth0 ++ """ ++ # Slave devices are controlled by the master. ++ if not iface_type or iface_type.lower() != "slave": ++ return __salt__["cmd.run"]("ifup {0}".format(iface)) ++ return None ++ ++ ++def get_routes(iface): ++ """ ++ Return the contents of the interface routes script. ++ ++ CLI Example: ++ ++ .. code-block:: bash ++ ++ salt '*' ip.get_routes eth0 ++ """ ++ if iface == "routes": ++ path = _SUSE_NETWORK_ROUTES_FILE ++ else: ++ path = os.path.join(_SUSE_NETWORK_SCRIPT_DIR, "ifroute-{0}".format(iface)) ++ return _read_file(path) ++ ++ ++def get_network_settings(): ++ """ ++ Return the contents of the global network script. ++ ++ CLI Example: ++ ++ .. code-block:: bash ++ ++ salt '*' ip.get_network_settings ++ """ ++ return _read_file(_SUSE_NETWORK_FILE) ++ ++ ++def apply_network_settings(**settings): ++ """ ++ Apply global network configuration. ++ ++ CLI Example: ++ ++ .. code-block:: bash ++ ++ salt '*' ip.apply_network_settings ++ """ ++ if "require_reboot" not in settings: ++ settings["require_reboot"] = False ++ ++ if "apply_hostname" not in settings: ++ settings["apply_hostname"] = False ++ ++ hostname_res = True ++ if settings["apply_hostname"] in _CONFIG_TRUE: ++ if "hostname" in settings: ++ hostname_res = __salt__["network.mod_hostname"](settings["hostname"]) ++ else: ++ log.warning( ++ "The network state sls is trying to apply hostname " ++ "changes but no hostname is defined." ++ ) ++ hostname_res = False ++ ++ res = True ++ if settings["require_reboot"] in _CONFIG_TRUE: ++ log.warning( ++ "The network state sls is requiring a reboot of the system to " ++ "properly apply network configuration." ++ ) ++ res = True ++ else: ++ res = __salt__["service.reload"]("network") ++ ++ return hostname_res and res ++ ++ ++def build_network_settings(**settings): ++ """ ++ Build the global network script. ++ ++ CLI Example: ++ ++ .. code-block:: bash ++ ++ salt '*' ip.build_network_settings ++ """ ++ # Read current configuration and store default values ++ current_network_settings = _parse_suse_config(_SUSE_NETWORK_FILE) ++ ++ # Build settings ++ opts = _parse_network_settings(settings, current_network_settings) ++ try: ++ template = JINJA.get_template("network.jinja") ++ except jinja2.exceptions.TemplateNotFound: ++ log.error("Could not load template network.jinja") ++ return "" ++ network = template.render(opts) ++ ++ if settings["test"]: ++ return _read_temp(network) ++ ++ # Write settings ++ _write_file_network(network, _SUSE_NETWORK_FILE) ++ ++ __salt__["cmd.run"]("netconfig update -f") ++ ++ return _read_file(_SUSE_NETWORK_FILE) +diff --git a/salt/states/network.py b/salt/states/network.py +index 30bd07810e..04d6a71f34 100644 +--- a/salt/states/network.py ++++ b/salt/states/network.py +@@ -504,6 +504,8 @@ def managed(name, enabled=True, **kwargs): + msg += " Update your SLS file to get rid of this warning." + ret.setdefault("warnings", []).append(msg) + ++ is_suse = (__grains__["os_family"] == "Suse") ++ + # Build interface + try: + old = __salt__["ip.get_interface"](name) +@@ -649,25 +651,30 @@ def managed(name, enabled=True, **kwargs): + present_slaves = __salt__["cmd.run"]( + ["cat", "/sys/class/net/{0}/bonding/slaves".format(name)] + ).split() +- desired_slaves = kwargs["slaves"].split() ++ if isinstance(kwargs['slaves'], list): ++ desired_slaves = kwargs['slaves'] ++ else: ++ desired_slaves = kwargs['slaves'].split() + missing_slaves = set(desired_slaves) - set(present_slaves) + + # Enslave only slaves missing in master + if missing_slaves: +- ifenslave_path = __salt__["cmd.run"](["which", "ifenslave"]).strip() +- if ifenslave_path: +- log.info( +- "Adding slaves '%s' to the master %s", +- " ".join(missing_slaves), +- name, ++ log.debug("Missing slaves of {0}: {1}".format(name, missing_slaves)) ++ if not is_suse: ++ ifenslave_path = __salt__["cmd.run"](["which", "ifenslave"]).strip() ++ if ifenslave_path: ++ log.info( ++ "Adding slaves '%s' to the master %s", ++ " ".join(missing_slaves), ++ name, ++ ) ++ cmd = [ifenslave_path, name] + list(missing_slaves) ++ __salt__["cmd.run"](cmd, python_shell=False) ++ else: ++ log.error("Command 'ifenslave' not found") ++ ret["changes"]["enslave"] = "Added slaves '{0}' to master '{1}'".format( ++ " ".join(missing_slaves), name + ) +- cmd = [ifenslave_path, name] + list(missing_slaves) +- __salt__["cmd.run"](cmd, python_shell=False) +- else: +- log.error("Command 'ifenslave' not found") +- ret["changes"]["enslave"] = "Added slaves '{0}' to master '{1}'".format( +- " ".join(missing_slaves), name +- ) + else: + log.info( + "All slaves '%s' are already added to the master %s" +diff --git a/salt/templates/suse_ip/ifcfg.jinja b/salt/templates/suse_ip/ifcfg.jinja +new file mode 100644 +index 0000000000..8384d0eab7 +--- /dev/null ++++ b/salt/templates/suse_ip/ifcfg.jinja +@@ -0,0 +1,34 @@ ++{% if nickname %}NAME='{{nickname}}' ++{%endif%}{% if startmode %}STARTMODE='{{startmode}}' ++{%endif%}{% if proto %}BOOTPROTO='{{proto}}' ++{%endif%}{% if uuid %}UUID='{{uuid}}' ++{%endif%}{% if vlan %}VLAN='{{vlan}}' ++{%endif%}{% if team_config %}TEAM_CONFIG='{{team_config}}' ++{%endif%}{% if team_port_config %}TEAM_PORT_CONFIG='{{team_port_config}}' ++{%endif%}{% if team_master %}TEAM_MASTER='{{team_master}}' ++{%endif%}{% if ipaddr %}IPADDR='{{ipaddr}}' ++{%endif%}{% if netmask %}NETMASK='{{netmask}}' ++{%endif%}{% if prefix %}PREFIXLEN="{{prefix}}" ++{%endif%}{% if ipaddrs %}{% for i in ipaddrs -%} ++IPADDR{{loop.index}}='{{i}}' ++{% endfor -%} ++{%endif%}{% if clonenum_start %}CLONENUM_START="{{clonenum_start}}" ++{%endif%}{% if gateway %}GATEWAY="{{gateway}}" ++{%endif%}{% if arpcheck %}ARPCHECK="{{arpcheck}}" ++{%endif%}{% if srcaddr %}SRCADDR="{{srcaddr}}" ++{%endif%}{% if defroute %}DEFROUTE="{{defroute}}" ++{%endif%}{% if bridge %}BRIDGE="{{bridge}}" ++{%endif%}{% if stp %}STP="{{stp}}" ++{%endif%}{% if delay or delay == 0 %}DELAY="{{delay}}" ++{%endif%}{% if mtu %}MTU='{{mtu}}' ++{%endif%}{% if zone %}ZONE='{{zone}}' ++{%endif%}{% if bonding %}BONDING_MODULE_OPTS='{{bonding}}' ++BONDING_MASTER='yes' ++{% for sl in slaves -%} ++BONDING_SLAVE{{loop.index}}='{{sl}}' ++{% endfor -%} ++{%endif%}{% if ethtool %}ETHTOOL_OPTIONS='{{ethtool}}' ++{%endif%}{% if phys_dev %}ETHERDEVICE='{{phys_dev}}' ++{%endif%}{% if vlan_id %}VLAN_ID='{{vlan_id}}' ++{%endif%}{% if userctl %}USERCONTROL='{{userctl}}' ++{%endif%} +diff --git a/salt/templates/suse_ip/ifroute.jinja b/salt/templates/suse_ip/ifroute.jinja +new file mode 100644 +index 0000000000..0081e4c688 +--- /dev/null ++++ b/salt/templates/suse_ip/ifroute.jinja +@@ -0,0 +1,8 @@ ++{%- for route in routes -%} ++{% if route.name %}# {{route.name}} {%- endif %} ++{{ route.ipaddr }} ++{%- if route.gateway %} {{route.gateway}}{% else %} -{% endif %} ++{%- if route.netmask %} {{route.netmask}}{% else %} -{% endif %} ++{%- if route.dev %} {{route.dev}}{% else %}{%- if iface and iface != "routes" %} {{iface}}{% else %} -{% endif %}{% endif %} ++{%- if route.metric %} metric {{route.metric}} {%- endif %} ++{% endfor -%} +diff --git a/salt/templates/suse_ip/network.jinja b/salt/templates/suse_ip/network.jinja +new file mode 100644 +index 0000000000..64ae911271 +--- /dev/null ++++ b/salt/templates/suse_ip/network.jinja +@@ -0,0 +1,30 @@ ++{% if auto6_wait_at_boot %}AUTO6_WAIT_AT_BOOT="{{auto6_wait_at_boot}}" ++{%endif%}{% if auto6_update %}AUTO6_UPDATE="{{auto6_update}}" ++{%endif%}{% if link_required %}LINK_REQUIRED="{{link_required}}" ++{%endif%}{% if wicked_debug %}WICKED_DEBUG="{{wicked_debug}}" ++{%endif%}{% if wicked_log_level %}WICKED_LOG_LEVEL="{{wicked_log_level}}" ++{%endif%}{% if check_duplicate_ip %}CHECK_DUPLICATE_IP="{{check_duplicate_ip}}" ++{%endif%}{% if send_gratuitous_arp %}SEND_GRATUITOUS_ARP="{{send_gratuitous_arp}}" ++{%endif%}{% if debug %}DEBUG="{{debug}}" ++{%endif%}{% if wait_for_interfaces %}WAIT_FOR_INTERFACES="{{wait_for_interfaces}}" ++{%endif%}{% if firewall %}FIREWALL="{{firewall}}" ++{%endif%}{% if nm_online_timeout %}NM_ONLINE_TIMEOUT="{{nm_online_timeout}}" ++{%endif%}{% if netconfig_modules_order %}NETCONFIG_MODULES_ORDER="{{netconfig_modules_order}}" ++{%endif%}{% if netconfig_verbose %}NETCONFIG_VERBOSE="{{netconfig_verbose}}" ++{%endif%}{% if netconfig_force_replace %}NETCONFIG_FORCE_REPLACE="{{netconfig_force_replace}}" ++{%endif%}{% if dns_policy %}NETCONFIG_DNS_POLICY="{{dns_policy}}" ++{%endif%}{% if dns_forwarder %}NETCONFIG_DNS_FORWARDER="{{dns_forwarder}}" ++{%endif%}{% if dns_forwarder_fallback %}NETCONFIG_DNS_FORWARDER_FALLBACK="{{dns_forwarder_fallback}}" ++{%endif%}{% if dns_search %}NETCONFIG_DNS_STATIC_SEARCHLIST="{{ dns_search|join(' ') }}" ++{%endif%}{% if dns %}NETCONFIG_DNS_STATIC_SERVERS="{{ dns|join(' ') }}" ++{%endif%}{% if dns_ranking %}NETCONFIG_DNS_RANKING="{{dns_ranking}}" ++{%endif%}{% if dns_resolver_options %}NETCONFIG_DNS_RESOLVER_OPTIONS="{{dns_resolver_options}}" ++{%endif%}{% if dns_resolver_sortlist %}NETCONFIG_DNS_RESOLVER_SORTLIST="{{dns_resolver_sortlist}}" ++{%endif%}{% if ntp_policy %}NETCONFIG_NTP_POLICY="{{ntp_policy}}" ++{%endif%}{% if ntp_static_servers %}NETCONFIG_NTP_STATIC_SERVERS="{{ntp_static_servers}}" ++{%endif%}{% if nis_policy %}NETCONFIG_NIS_POLICY="{{nis_policy}}" ++{%endif%}{% if nis_setdomainname %}NETCONFIG_NIS_SETDOMAINNAME="{{nis_setdomainname}}" ++{%endif%}{% if nis_static_domain %}NETCONFIG_NIS_STATIC_DOMAIN="{{nis_static_domain}}" ++{%endif%}{% if nis_static_servers %}NETCONFIG_NIS_STATIC_SERVERS="{{nis_static_servers}}" ++{%endif%}{% if wireless_regulatory_domain %}WIRELESS_REGULATORY_DOMAIN="{{wireless_regulatory_domain}}" ++{%endif%} +diff --git a/setup.py b/setup.py +index d9c3d6e303..c6bd4a3c03 100755 +--- a/setup.py ++++ b/setup.py +@@ -1108,6 +1108,7 @@ class SaltDistribution(distutils.dist.Distribution): + package_data = { + "salt.templates": [ + "rh_ip/*.jinja", ++ "suse_ip/*.jinja", + "debian_ip/*.jinja", + "virt/*.jinja", + "git/*", +-- +2.30.0 + + diff --git a/salt.changes b/salt.changes index d61f807..7c77be2 100644 --- a/salt.changes +++ b/salt.changes @@ -1,3 +1,33 @@ +------------------------------------------------------------------- +Mon Mar 1 11:21:01 UTC 2021 - Alexander Graul + +- Allow extra_filerefs as sanitized kwargs for SSH client +- Fix regression on cmd.run when passing tuples as cmd (bsc#1182740) +- Fix for multiple for security issues + (CVE-2020-28243) (CVE-2020-28972) (CVE-2020-35662) (CVE-2021-3148) (CVE-2021-3144) + (CVE-2021-25281) (CVE-2021-25282) (CVE-2021-25283) (CVE-2021-25284) (CVE-2021-3197) + (bsc#1181550) (bsc#1181556) (bsc#1181557) (bsc#1181558) (bsc#1181559) (bsc#1181560) + (bsc#1181561) (bsc#1181562) (bsc#1181563) (bsc#1181564) (bsc#1181565) +- Implementation of suse_ip execution module to prevent issues with network.managed (bsc#1099976) +- Add sleep on exception handling on minion connection attempt to the master (bsc#1174855) +- Allows for the VMware provider to handle CPU and memory hot-add in newer versions of the software. (bsc#1181347) +- Always require python-certifi (used by salt.ext.tornado) +- Bring missing part of async batch implementation back (bsc#1182382) (CVE-2021-25315) + +- Added: + * implementation-of-suse_ip-execution-module-bsc-10999.patch + * fix-regression-on-cmd.run-when-passing-tuples-as-cmd.patch + * async-batch-implementation-fix-320.patch + * add-sleep-on-exception-handling-on-minion-connection.patch + * allow-extra_filerefs-as-sanitized-kwargs-for-ssh-cli.patch + * fix-for-some-cves-bsc1181550.patch + * fixes-56144-to-enable-hotadd-profile-support.patch + +------------------------------------------------------------------- +Tue Feb 16 17:10:30 UTC 2021 - Alexander Graul + +- Always require python3-distro (bsc#1182293) + ------------------------------------------------------------------- Thu Feb 11 16:02:59 UTC 2021 - Pablo Suárez Hernández diff --git a/salt.spec b/salt.spec index 32d1c59..a805ac5 100644 --- a/salt.spec +++ b/salt.spec @@ -359,6 +359,20 @@ Patch148: virt-uefi-fix-backport-312.patch Patch149: 3002.2-xen-spicevmc-dns-srv-records-backports-314.patch # PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/59485 Patch150: open-suse-3002.2-xen-grub-316.patch +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/320 +Patch151: async-batch-implementation-fix-320.patch +# PATCH-FIX_UPSTREAM: https://github.com/saltstack/salt/pull/56173 +Patch152: fixes-56144-to-enable-hotadd-profile-support.patch +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/307 +Patch153: add-sleep-on-exception-handling-on-minion-connection.patch +# PATCH-FIX_OPENSUSE: https://github.com/openSUSE/salt/pull/323 +Patch154: implementation-of-suse_ip-execution-module-bsc-10999.patch +# PATCH-FIX_UPSTREAM: no PR to link to yet +Patch155: fix-for-some-cves-bsc1181550.patch +# PATCH-FIX_UPSTREAM: no PR to link to yet +Patch156: allow-extra_filerefs-as-sanitized-kwargs-for-ssh-cli.patch +# PATCH-FIX_UPSTREAM: no PR to link to yet +Patch157: fix-regression-on-cmd.run-when-passing-tuples-as-cmd.patch BuildRoot: %{_tmppath}/%{name}-%{version}-build BuildRequires: logrotate @@ -458,7 +472,6 @@ BuildRequires: python3-MarkupSafe BuildRequires: python3-msgpack-python > 0.3 BuildRequires: python3-pyzmq >= 2.2.0 %if 0%{?suse_version} >= 1500 -BuildRequires: python3-distro BuildRequires: python3-M2Crypto %else BuildRequires: python3-pycrypto >= 2.6.1 @@ -467,6 +480,7 @@ BuildRequires: python3-pycrypto >= 2.6.1 BuildRequires: python3-PyYAML BuildRequires: python3-psutil BuildRequires: python3-requests >= 1.0.0 +BuildRequires: python3-distro # requirements/zeromq.txt %if %{with test} @@ -486,10 +500,7 @@ Requires: platform-python %else Requires: python3 %endif -# -%if ! 0%{?suse_version} > 1110 Requires: python3-certifi -%endif # requirements/base.txt %if 0%{?rhel} || 0%{?fedora} Requires: python3-jinja2 @@ -510,7 +521,6 @@ Requires: python3-Jinja2 Requires: python3-MarkupSafe Requires: python3-msgpack-python > 0.3 %if 0%{?suse_version} >= 1500 -Requires: python3-distro Requires: python3-M2Crypto %else Requires: python3-pycrypto >= 2.6.1 @@ -520,6 +530,7 @@ Requires: python3-pyzmq >= 2.2.0 Requires: python3-PyYAML Requires: python3-psutil Requires: python3-requests >= 1.0.0 +Requires: python3-distro %if 0%{?suse_version} # required for zypper.py Requires: python3-rpm @@ -908,6 +919,13 @@ cp %{S:5} ./.travis.yml %patch148 -p1 %patch149 -p1 %patch150 -p1 +%patch151 -p1 +%patch152 -p1 +%patch153 -p1 +%patch154 -p1 +%patch155 -p1 +%patch156 -p1 +%patch157 -p1 %build # Putting /usr/bin at the front of $PATH is needed for RHEL/RES 7. Without this