summaryrefslogtreecommitdiffstats
path: root/ansible_collections/purestorage/flasharray/plugins
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-18 05:52:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-18 05:52:22 +0000
commit38b7c80217c4e72b1d8988eb1e60bb6e77334114 (patch)
tree356e9fd3762877d07cde52d21e77070aeff7e789 /ansible_collections/purestorage/flasharray/plugins
parentAdding upstream version 7.7.0+dfsg. (diff)
downloadansible-38b7c80217c4e72b1d8988eb1e60bb6e77334114.tar.xz
ansible-38b7c80217c4e72b1d8988eb1e60bb6e77334114.zip
Adding upstream version 9.4.0+dfsg.upstream/9.4.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/purestorage/flasharray/plugins')
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/doc_fragments/purestorage.py1
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/module_utils/common.py89
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/module_utils/purefa.py48
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/module_utils/version.py344
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_ad.py25
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_admin.py11
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_apiclient.py11
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_arrayname.py25
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_banner.py25
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_cbsexpand.py153
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_certs.py218
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_connect.py46
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_default_protection.py12
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_directory.py11
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_dirsnap.py15
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_dns.py7
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_ds.py115
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_eradication.py86
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_eula.py72
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_export.py11
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_file.py183
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_fs.py16
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_hardware.py110
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_host.py16
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_info.py686
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_inventory.py345
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_kmip.py11
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_logging.py13
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_messages.py56
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_network.py452
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_ntp.py91
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_offload.py314
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_pg.py107
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_pgsched.py87
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_pgsnap.py471
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_pod.py29
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_policy.py508
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_proxy.py14
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_ra.py84
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_saml.py11
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_smis.py11
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_snap.py65
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_snmp_agent.py10
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_sso.py11
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_subnet.py58
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_syslog.py149
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_syslog_settings.py11
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_token.py49
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_vg.py93
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_vnc.py82
-rw-r--r--ansible_collections/purestorage/flasharray/plugins/modules/purefa_volume.py55
51 files changed, 4030 insertions, 1493 deletions
diff --git a/ansible_collections/purestorage/flasharray/plugins/doc_fragments/purestorage.py b/ansible_collections/purestorage/flasharray/plugins/doc_fragments/purestorage.py
index 7c19925e6..e05cbf6a7 100644
--- a/ansible_collections/purestorage/flasharray/plugins/doc_fragments/purestorage.py
+++ b/ansible_collections/purestorage/flasharray/plugins/doc_fragments/purestorage.py
@@ -42,5 +42,4 @@ requirements:
- netaddr
- requests
- pycountry
- - packaging
"""
diff --git a/ansible_collections/purestorage/flasharray/plugins/module_utils/common.py b/ansible_collections/purestorage/flasharray/plugins/module_utils/common.py
new file mode 100644
index 000000000..ddc093731
--- /dev/null
+++ b/ansible_collections/purestorage/flasharray/plugins/module_utils/common.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2024 Simon Dodsley, <simon@purestorage.com>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+"""
+This module adds shared functions for the FlashArray modules
+"""
+
+
+def human_to_bytes(size):
+ """Given a human-readable byte string (e.g. 2G, 30M),
+ return the number of bytes. Will return 0 if the argument has
+ unexpected form.
+ """
+ bytes = size[:-1]
+ unit = size[-1].upper()
+ if bytes.isdigit():
+ bytes = int(bytes)
+ if unit == "P":
+ bytes *= 1125899906842624
+ elif unit == "T":
+ bytes *= 1099511627776
+ elif unit == "G":
+ bytes *= 1073741824
+ elif unit == "M":
+ bytes *= 1048576
+ elif unit == "K":
+ bytes *= 1024
+ else:
+ bytes = 0
+ else:
+ bytes = 0
+ return bytes
+
+
+def human_to_real(iops):
+ """Given a human-readable string (e.g. 2K, 30M IOPs),
+ return the real number. Will return 0 if the argument has
+ unexpected form.
+ """
+ digit = iops[:-1]
+ unit = iops[-1].upper()
+ if unit.isdigit():
+ digit = iops
+ elif digit.isdigit():
+ digit = int(digit)
+ if unit == "M":
+ digit *= 1000000
+ elif unit == "K":
+ digit *= 1000
+ else:
+ digit = 0
+ else:
+ digit = 0
+ return digit
+
+
+def convert_to_millisecs(hour):
+ """Convert a 12-hour clock to milliseconds from
+ midnight"""
+ if hour[-2:].upper() == "AM" and hour[:2] == "12":
+ return 0
+ elif hour[-2:].upper() == "AM":
+ return int(hour[:-2]) * 3600000
+ elif hour[-2:].upper() == "PM" and hour[:2] == "12":
+ return 43200000
+ return (int(hour[:-2]) + 12) * 3600000
+
+
+def convert_time_to_millisecs(time):
+ """Convert a time period in milliseconds"""
+ if time[-1:].lower() not in ["w", "d", "h", "m", "s"]:
+ return 0
+ try:
+ if time[-1:].lower() == "w":
+ return int(time[:-1]) * 7 * 86400000
+ elif time[-1:].lower() == "d":
+ return int(time[:-1]) * 86400000
+ elif time[-1:].lower() == "h":
+ return int(time[:-1]) * 3600000
+ elif time[-1:].lower() == "m":
+ return int(time[:-1]) * 60000
+ except Exception:
+ return 0
diff --git a/ansible_collections/purestorage/flasharray/plugins/module_utils/purefa.py b/ansible_collections/purestorage/flasharray/plugins/module_utils/purefa.py
index b85ce0e29..82d048bcb 100644
--- a/ansible_collections/purestorage/flasharray/plugins/module_utils/purefa.py
+++ b/ansible_collections/purestorage/flasharray/plugins/module_utils/purefa.py
@@ -32,6 +32,12 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
+HAS_DISTRO = True
+try:
+ import distro
+except ImportError:
+ HAS_DISTRO = False
+
HAS_PURESTORAGE = True
try:
from purestorage import purestorage
@@ -47,18 +53,26 @@ except ImportError:
from os import environ
import platform
-VERSION = 1.4
+VERSION = 1.5
USER_AGENT_BASE = "Ansible"
def get_system(module):
"""Return System Object or Fail"""
- user_agent = "%(base)s %(class)s/%(version)s (%(platform)s)" % {
- "base": USER_AGENT_BASE,
- "class": __name__,
- "version": VERSION,
- "platform": platform.platform(),
- }
+ if HAS_DISTRO:
+ user_agent = "%(base)s %(class)s/%(version)s (%(platform)s)" % {
+ "base": USER_AGENT_BASE,
+ "class": __name__,
+ "version": VERSION,
+ "platform": distro.name(pretty=True),
+ }
+ else:
+ user_agent = "%(base)s %(class)s/%(version)s (%(platform)s)" % {
+ "base": USER_AGENT_BASE,
+ "class": __name__,
+ "version": VERSION,
+ "platform": platform.platform(),
+ }
array_name = module.params["fa_url"]
api = module.params["api_token"]
if HAS_PURESTORAGE:
@@ -91,12 +105,20 @@ def get_system(module):
def get_array(module):
"""Return System Object or Fail"""
- user_agent = "%(base)s %(class)s/%(version)s (%(platform)s)" % {
- "base": USER_AGENT_BASE,
- "class": __name__,
- "version": VERSION,
- "platform": platform.platform(),
- }
+ if HAS_DISTRO:
+ user_agent = "%(base)s %(class)s/%(version)s (%(platform)s)" % {
+ "base": USER_AGENT_BASE,
+ "class": __name__,
+ "version": VERSION,
+ "platform": distro.name(pretty=True),
+ }
+ else:
+ user_agent = "%(base)s %(class)s/%(version)s (%(platform)s)" % {
+ "base": USER_AGENT_BASE,
+ "class": __name__,
+ "version": VERSION,
+ "platform": platform.platform(),
+ }
array_name = module.params["fa_url"]
api = module.params["api_token"]
if HAS_PYPURECLIENT:
diff --git a/ansible_collections/purestorage/flasharray/plugins/module_utils/version.py b/ansible_collections/purestorage/flasharray/plugins/module_utils/version.py
new file mode 100644
index 000000000..d91cf3ab4
--- /dev/null
+++ b/ansible_collections/purestorage/flasharray/plugins/module_utils/version.py
@@ -0,0 +1,344 @@
+# Vendored copy of distutils/version.py from CPython 3.9.5
+#
+# Implements multiple version numbering conventions for the
+# Python Module Distribution Utilities.
+#
+# PSF License (see PSF-license.txt or https://opensource.org/licenses/Python-2.0)
+#
+
+"""Provides classes to represent module version numbers (one class for
+each style of version numbering). There are currently two such classes
+implemented: StrictVersion and LooseVersion.
+
+Every version number class implements the following interface:
+ * the 'parse' method takes a string and parses it to some internal
+ representation; if the string is an invalid version number,
+ 'parse' raises a ValueError exception
+ * the class constructor takes an optional string argument which,
+ if supplied, is passed to 'parse'
+ * __str__ reconstructs the string that was passed to 'parse' (or
+ an equivalent string -- ie. one that will generate an equivalent
+ version number instance)
+ * __repr__ generates Python code to recreate the version number instance
+ * _cmp compares the current instance with either another instance
+ of the same class or a string (which will be parsed to an instance
+ of the same class, thus must follow the same rules)
+"""
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+import re
+
+try:
+ RE_FLAGS = re.VERBOSE | re.ASCII
+except AttributeError:
+ RE_FLAGS = re.VERBOSE
+
+
+class Version:
+ """Abstract base class for version numbering classes. Just provides
+ constructor (__init__) and reproducer (__repr__), because those
+ seem to be the same for all version numbering classes; and route
+ rich comparisons to _cmp.
+ """
+
+ def __init__(self, vstring=None):
+ if vstring:
+ self.parse(vstring)
+
+ def __repr__(self):
+ return "%s ('%s')" % (self.__class__.__name__, str(self))
+
+ def __eq__(self, other):
+ c = self._cmp(other)
+ if c is NotImplemented:
+ return c
+ return c == 0
+
+ def __lt__(self, other):
+ c = self._cmp(other)
+ if c is NotImplemented:
+ return c
+ return c < 0
+
+ def __le__(self, other):
+ c = self._cmp(other)
+ if c is NotImplemented:
+ return c
+ return c <= 0
+
+ def __gt__(self, other):
+ c = self._cmp(other)
+ if c is NotImplemented:
+ return c
+ return c > 0
+
+ def __ge__(self, other):
+ c = self._cmp(other)
+ if c is NotImplemented:
+ return c
+ return c >= 0
+
+
+# Interface for version-number classes -- must be implemented
+# by the following classes (the concrete ones -- Version should
+# be treated as an abstract class).
+# __init__ (string) - create and take same action as 'parse'
+# (string parameter is optional)
+# parse (string) - convert a string representation to whatever
+# internal representation is appropriate for
+# this style of version numbering
+# __str__ (self) - convert back to a string; should be very similar
+# (if not identical to) the string supplied to parse
+# __repr__ (self) - generate Python code to recreate
+# the instance
+# _cmp (self, other) - compare two version numbers ('other' may
+# be an unparsed version string, or another
+# instance of your version class)
+
+
+class StrictVersion(Version):
+ """Version numbering for anal retentives and software idealists.
+ Implements the standard interface for version number classes as
+ described above. A version number consists of two or three
+ dot-separated numeric components, with an optional "pre-release" tag
+ on the end. The pre-release tag consists of the letter 'a' or 'b'
+ followed by a number. If the numeric components of two version
+ numbers are equal, then one with a pre-release tag will always
+ be deemed earlier (lesser) than one without.
+
+ The following are valid version numbers (shown in the order that
+ would be obtained by sorting according to the supplied cmp function):
+
+ 0.4 0.4.0 (these two are equivalent)
+ 0.4.1
+ 0.5a1
+ 0.5b3
+ 0.5
+ 0.9.6
+ 1.0
+ 1.0.4a3
+ 1.0.4b1
+ 1.0.4
+
+ The following are examples of invalid version numbers:
+
+ 1
+ 2.7.2.2
+ 1.3.a4
+ 1.3pl1
+ 1.3c4
+
+ The rationale for this version numbering system will be explained
+ in the distutils documentation.
+ """
+
+ version_re = re.compile(r"^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$", RE_FLAGS)
+
+ def parse(self, vstring):
+ match = self.version_re.match(vstring)
+ if not match:
+ raise ValueError("invalid version number '%s'" % vstring)
+
+ (major, minor, patch, prerelease, prerelease_num) = match.group(1, 2, 4, 5, 6)
+
+ if patch:
+ self.version = tuple(map(int, [major, minor, patch]))
+ else:
+ self.version = tuple(map(int, [major, minor])) + (0,)
+
+ if prerelease:
+ self.prerelease = (prerelease[0], int(prerelease_num))
+ else:
+ self.prerelease = None
+
+ def __str__(self):
+ if self.version[2] == 0:
+ vstring = ".".join(map(str, self.version[0:2]))
+ else:
+ vstring = ".".join(map(str, self.version))
+
+ if self.prerelease:
+ vstring = vstring + self.prerelease[0] + str(self.prerelease[1])
+
+ return vstring
+
+ def _cmp(self, other):
+ if isinstance(other, str):
+ other = StrictVersion(other)
+ elif not isinstance(other, StrictVersion):
+ return NotImplemented
+
+ if self.version != other.version:
+ # numeric versions don't match
+ # prerelease stuff doesn't matter
+ if self.version < other.version:
+ return -1
+ else:
+ return 1
+
+ # have to compare prerelease
+ # case 1: neither has prerelease; they're equal
+ # case 2: self has prerelease, other doesn't; other is greater
+ # case 3: self doesn't have prerelease, other does: self is greater
+ # case 4: both have prerelease: must compare them!
+
+ if not self.prerelease and not other.prerelease:
+ return 0
+ elif self.prerelease and not other.prerelease:
+ return -1
+ elif not self.prerelease and other.prerelease:
+ return 1
+ elif self.prerelease and other.prerelease:
+ if self.prerelease == other.prerelease:
+ return 0
+ elif self.prerelease < other.prerelease:
+ return -1
+ else:
+ return 1
+ else:
+ raise AssertionError("never get here")
+
+
+# end class StrictVersion
+
+# The rules according to Greg Stein:
+# 1) a version number has 1 or more numbers separated by a period or by
+# sequences of letters. If only periods, then these are compared
+# left-to-right to determine an ordering.
+# 2) sequences of letters are part of the tuple for comparison and are
+# compared lexicographically
+# 3) recognize the numeric components may have leading zeroes
+#
+# The LooseVersion class below implements these rules: a version number
+# string is split up into a tuple of integer and string components, and
+# comparison is a simple tuple comparison. This means that version
+# numbers behave in a predictable and obvious way, but a way that might
+# not necessarily be how people *want* version numbers to behave. There
+# wouldn't be a problem if people could stick to purely numeric version
+# numbers: just split on period and compare the numbers as tuples.
+# However, people insist on putting letters into their version numbers;
+# the most common purpose seems to be:
+# - indicating a "pre-release" version
+# ('alpha', 'beta', 'a', 'b', 'pre', 'p')
+# - indicating a post-release patch ('p', 'pl', 'patch')
+# but of course this can't cover all version number schemes, and there's
+# no way to know what a programmer means without asking him.
+#
+# The problem is what to do with letters (and other non-numeric
+# characters) in a version number. The current implementation does the
+# obvious and predictable thing: keep them as strings and compare
+# lexically within a tuple comparison. This has the desired effect if
+# an appended letter sequence implies something "post-release":
+# eg. "0.99" < "0.99pl14" < "1.0", and "5.001" < "5.001m" < "5.002".
+#
+# However, if letters in a version number imply a pre-release version,
+# the "obvious" thing isn't correct. Eg. you would expect that
+# "1.5.1" < "1.5.2a2" < "1.5.2", but under the tuple/lexical comparison
+# implemented here, this just isn't so.
+#
+# Two possible solutions come to mind. The first is to tie the
+# comparison algorithm to a particular set of semantic rules, as has
+# been done in the StrictVersion class above. This works great as long
+# as everyone can go along with bondage and discipline. Hopefully a
+# (large) subset of Python module programmers will agree that the
+# particular flavour of bondage and discipline provided by StrictVersion
+# provides enough benefit to be worth using, and will submit their
+# version numbering scheme to its domination. The free-thinking
+# anarchists in the lot will never give in, though, and something needs
+# to be done to accommodate them.
+#
+# Perhaps a "moderately strict" version class could be implemented that
+# lets almost anything slide (syntactically), and makes some heuristic
+# assumptions about non-digits in version number strings. This could
+# sink into special-case-hell, though; if I was as talented and
+# idiosyncratic as Larry Wall, I'd go ahead and implement a class that
+# somehow knows that "1.2.1" < "1.2.2a2" < "1.2.2" < "1.2.2pl3", and is
+# just as happy dealing with things like "2g6" and "1.13++". I don't
+# think I'm smart enough to do it right though.
+#
+# In any case, I've coded the test suite for this module (see
+# ../test/test_version.py) specifically to fail on things like comparing
+# "1.2a2" and "1.2". That's not because the *code* is doing anything
+# wrong, it's because the simple, obvious design doesn't match my
+# complicated, hairy expectations for real-world version numbers. It
+# would be a snap to fix the test suite to say, "Yep, LooseVersion does
+# the Right Thing" (ie. the code matches the conception). But I'd rather
+# have a conception that matches common notions about version numbers.
+
+
+class LooseVersion(Version):
+ """Version numbering for anarchists and software realists.
+ Implements the standard interface for version number classes as
+ described above. A version number consists of a series of numbers,
+ separated by either periods or strings of letters. When comparing
+ version numbers, the numeric components will be compared
+ numerically, and the alphabetic components lexically. The following
+ are all valid version numbers, in no particular order:
+
+ 1.5.1
+ 1.5.2b2
+ 161
+ 3.10a
+ 8.02
+ 3.4j
+ 1996.07.12
+ 3.2.pl0
+ 3.1.1.6
+ 2g6
+ 11g
+ 0.960923
+ 2.2beta29
+ 1.13++
+ 5.5.kw
+ 2.0b1pl0
+
+ In fact, there is no such thing as an invalid version number under
+ this scheme; the rules for comparison are simple and predictable,
+ but may not always give the results you want (for some definition
+ of "want").
+ """
+
+ component_re = re.compile(r"(\d+ | [a-z]+ | \.)", re.VERBOSE)
+
+ def __init__(self, vstring=None):
+ if vstring:
+ self.parse(vstring)
+
+ def parse(self, vstring):
+ # I've given up on thinking I can reconstruct the version string
+ # from the parsed tuple -- so I just store the string here for
+ # use by __str__
+ self.vstring = vstring
+ components = [x for x in self.component_re.split(vstring) if x and x != "."]
+ for i, obj in enumerate(components):
+ try:
+ components[i] = int(obj)
+ except ValueError:
+ pass
+
+ self.version = components
+
+ def __str__(self):
+ return self.vstring
+
+ def __repr__(self):
+ return "LooseVersion ('%s')" % str(self)
+
+ def _cmp(self, other):
+ if isinstance(other, str):
+ other = LooseVersion(other)
+ elif not isinstance(other, LooseVersion):
+ return NotImplemented
+
+ if self.version == other.version:
+ return 0
+ if self.version < other.version:
+ return -1
+ if self.version > other.version:
+ return 1
+
+
+# end class LooseVersion
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_ad.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_ad.py
index d9eee96ac..35530bdf8 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_ad.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_ad.py
@@ -152,8 +152,10 @@ except ImportError:
HAS_PURESTORAGE = False
from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
- get_system,
get_array,
purefa_argument_spec,
)
@@ -205,7 +207,7 @@ def update_account(module, array):
def create_account(module, array, api_version):
"""Create Active Directory Account"""
changed = True
- if MIN_JOIN_OU_API_VERSION not in api_version:
+ if LooseVersion(MIN_JOIN_OU_API_VERSION) > LooseVersion(api_version):
ad_config = ActiveDirectoryPost(
computer_name=module.params["computer"],
directory_servers=module.params["directory_servers"],
@@ -214,7 +216,7 @@ def create_account(module, array, api_version):
user=module.params["username"],
password=module.params["password"],
)
- elif MIN_TLS_API_VERSION in api_version:
+ elif LooseVersion(MIN_TLS_API_VERSION) <= LooseVersion(api_version):
ad_config = ActiveDirectoryPost(
computer_name=module.params["computer"],
directory_servers=module.params["directory_servers"],
@@ -284,15 +286,14 @@ def main():
if not HAS_PURESTORAGE:
module.fail_json(msg="py-pure-client sdk is required for this module")
- array = get_system(module)
- api_version = array._list_available_rest_versions()
- if MIN_REQUIRED_API_VERSION not in api_version:
+ array = get_array(module)
+ api_version = array.get_rest_version()
+ if LooseVersion(MIN_REQUIRED_API_VERSION) > LooseVersion(api_version):
module.fail_json(
msg="FlashArray REST version not supported. "
"Minimum version required: {0}".format(MIN_REQUIRED_API_VERSION)
)
state = module.params["state"]
- array = get_array(module)
exists = bool(
array.get_active_directory(names=[module.params["name"]]).status_code == 200
)
@@ -300,18 +301,22 @@ def main():
if not module.params["computer"]:
module.params["computer"] = module.params["name"].replace("_", "-")
if module.params["kerberos_servers"]:
- if SERVER_API_VERSION in api_version:
+ if LooseVersion(SERVER_API_VERSION) <= LooseVersion(api_version):
module.params["kerberos_servers"] = module.params["kerberos_servers"][0:3]
else:
module.params["kerberos_servers"] = module.params["kerberos_servers"][0:1]
if module.params["directory_servers"]:
- if SERVER_API_VERSION in api_version:
+ if LooseVersion(SERVER_API_VERSION) <= LooseVersion(api_version):
module.params["directory_servers"] = module.params["directory_servers"][0:3]
else:
module.params["directory_servers"] = module.params["directory_servers"][0:1]
if not exists and state == "present":
create_account(module, array, api_version)
- elif exists and state == "present" and MIN_TLS_API_VERSION in api_version:
+ elif (
+ exists
+ and state == "present"
+ and LooseVersion(MIN_TLS_API_VERSION) <= LooseVersion(api_version)
+ ):
update_account(module, array)
elif exists and state == "absent":
delete_account(module, array)
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_admin.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_admin.py
index becb86893..21eac3896 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_admin.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_admin.py
@@ -70,10 +70,12 @@ except ImportError:
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
- get_system,
get_array,
purefa_argument_spec,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
MIN_API_VERSION = "2.2"
@@ -95,11 +97,10 @@ def main():
module.fail_json(msg="py-pure-client sdk is required for this module")
if module.params["lockout"] and not 1 <= module.params["lockout"] <= 7776000:
module.fail_json(msg="Lockout must be between 1 and 7776000 seconds")
- array = get_system(module)
- api_version = array._list_available_rest_versions()
+ array = get_array(module)
+ api_version = array.get_rest_version()
changed = False
- if MIN_API_VERSION in api_version:
- array = get_array(module)
+ if LooseVersion(MIN_API_VERSION) <= LooseVersion(api_version):
current_settings = list(array.get_admins_settings().items)[0]
if (
module.params["sso"]
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_apiclient.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_apiclient.py
index 12970dddb..9334c6733 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_apiclient.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_apiclient.py
@@ -109,10 +109,12 @@ except ImportError:
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
- get_system,
get_array,
purefa_argument_spec,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
MIN_REQUIRED_API_VERSION = "2.1"
@@ -219,15 +221,14 @@ def main():
if not HAS_PURESTORAGE:
module.fail_json(msg="py-pure-client sdk is required for this module")
- array = get_system(module)
- api_version = array._list_available_rest_versions()
+ array = get_array(module)
+ api_version = array.get_rest_version()
- if MIN_REQUIRED_API_VERSION not in api_version:
+ if LooseVersion(MIN_REQUIRED_API_VERSION) > LooseVersion(api_version):
module.fail_json(
msg="FlashArray REST version not supported. "
"Minimum version required: {0}".format(MIN_REQUIRED_API_VERSION)
)
- array = get_array(module)
state = module.params["state"]
try:
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_arrayname.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_arrayname.py
index cf5202c6f..550ae1401 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_arrayname.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_arrayname.py
@@ -52,10 +52,17 @@ EXAMPLES = r"""
RETURN = r"""
"""
+HAS_PURESTORAGE = True
+try:
+ from pypureclient.flasharray import Arrays
+except ImportError:
+ HAS_PURESTORAGE = False
+
+
import re
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
- get_system,
+ get_array,
purefa_argument_spec,
)
@@ -64,11 +71,12 @@ def update_name(module, array):
"""Change aray name"""
changed = True
if not module.check_mode:
- try:
- array.set(name=module.params["name"])
- except Exception:
+ res = array.patch_arrays(array=Arrays(name=module.params["name"]))
+ if res.status_code != 200:
module.fail_json(
- msg="Failed to change array name to {0}".format(module.params["name"])
+ msg="Failed to change array name to {0}. Error: {1}".format(
+ module.params["name"], res.errors[0].message
+ )
)
module.exit_json(changed=changed)
@@ -85,7 +93,10 @@ def main():
module = AnsibleModule(argument_spec, supports_check_mode=True)
- array = get_system(module)
+ if not HAS_PURESTORAGE:
+ module.fail_json(msg="py-pure-client sdk is required for this module")
+
+ array = get_array(module)
pattern = re.compile("^[a-zA-Z0-9]([a-zA-Z0-9-]{0,54}[a-zA-Z0-9])?$")
if not pattern.match(module.params["name"]):
module.fail_json(
@@ -93,7 +104,7 @@ def main():
module.params["name"]
)
)
- if module.params["name"] != array.get()["array_name"]:
+ if module.params["name"] != list(array.get_arrays().items)[0].name:
update_name(module, array)
module.exit_json(changed=False)
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_banner.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_banner.py
index bd7a367a5..c3c2346b3 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_banner.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_banner.py
@@ -61,10 +61,16 @@ RETURN = r"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
- get_system,
+ get_array,
purefa_argument_spec,
)
+HAS_PURESTORAGE = True
+try:
+ from pypureclient.flasharray import Arrays
+except ImportError:
+ HAS_PURESTORAGE = False
+
def set_banner(module, array):
"""Set MOTD banner text"""
@@ -72,9 +78,8 @@ def set_banner(module, array):
if not module.params["banner"]:
module.fail_json(msg="Invalid MOTD banner given")
if not module.check_mode:
- try:
- array.set(banner=module.params["banner"])
- except Exception:
+ res = array.patch_arrays(array=Arrays(banner=module.params["banner"]))
+ if res.status_code != 200:
module.fail_json(msg="Failed to set MOTD banner text")
module.exit_json(changed=changed)
@@ -84,9 +89,8 @@ def delete_banner(module, array):
"""Delete MOTD banner text"""
changed = True
if not module.check_mode:
- try:
- array.set(banner="")
- except Exception:
+ res = array.patch_arrays(array=Arrays(banner=""))
+ if res.status_code != 200:
module.fail_json(msg="Failed to delete current MOTD banner text")
module.exit_json(changed=changed)
@@ -106,9 +110,12 @@ def main():
argument_spec, required_if=required_if, supports_check_mode=True
)
+ if not HAS_PURESTORAGE:
+ module.fail_json(msg="py-pure-client sdk is required for this module")
+
state = module.params["state"]
- array = get_system(module)
- current_banner = array.get(banner=True)["banner"]
+ array = get_array(module)
+ current_banner = list(array.get_arrays().items)[0].banner
# set banner if empty value or value differs
if state == "present" and (
not current_banner or current_banner != module.params["banner"]
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_cbsexpand.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_cbsexpand.py
new file mode 100644
index 000000000..4221e9013
--- /dev/null
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_cbsexpand.py
@@ -0,0 +1,153 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# (c) 2023, Simon Dodsley (simon@purestorage.com)
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+ANSIBLE_METADATA = {
+ "metadata_version": "1.1",
+ "status": ["preview"],
+ "supported_by": "community",
+}
+
+DOCUMENTATION = r"""
+---
+module: purefa_cbsexpand
+version_added: '1.0.0'
+short_description: Modify the CBS array capacity
+description:
+- Expand the CBS array capacity. Capacity can only be updated to specific values.
+author:
+- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
+options:
+ state:
+ description:
+ - Action to be performed on the CBS array.
+ - I{list) will provide the options that I(capacity), in bytes, can be set to.
+ default: show
+ choices: [ show, expand ]
+ type: str
+ capacity:
+ description:
+ - Requested capacity of CBS array in bytes.
+ type: int
+extends_documentation_fragment:
+- purestorage.flasharray.purestorage.fa
+"""
+
+EXAMPLES = r"""
+- name: Show available expansion capacities
+ purestorage.flasharray.purefa_cbsexpand:
+ fa_url: 10.10.10.2
+ api_token: e31060a7-21fc-e277-6240-25983c6c4592
+
+- name: Expand CBS to new capacity
+ purestorage.flasharray.purefa_cbsexpand:
+ state: expand
+ capacity: 10995116277760
+ fa_url: 10.10.10.2
+ api_token: e31060a7-21fc-e277-6240-25983c6c4592
+"""
+
+RETURN = r"""
+"""
+
+HAS_PURESTORAGE = True
+try:
+ from pypureclient import flasharray
+except ImportError:
+ HAS_PURESTORAGE = False
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
+ get_array,
+ purefa_argument_spec,
+)
+
+
+EXPAND_API_VERSION = "2.29"
+
+
+def _is_cbs(array):
+ """Is the selected array a Cloud Block Store"""
+ model = list(array.get_hardware(filter="type='controller'").items)[0].model
+ is_cbs = bool("CBS" in model)
+ return is_cbs
+
+
+def list_capacity(module, array):
+ """Get avaible expansion points"""
+ steps = list(array.get_arrays_cloud_capacity_supported_steps().items)
+ available = []
+ for step in range(0, len(steps)):
+ available.append(steps[step].supported_capacity)
+ module.exit_json(changed=True, available=available)
+
+
+def update_capacity(module, array):
+ """Expand CBS capacity"""
+ steps = list(array.get_arrays_cloud_capacity_supported_steps().items)
+ available = []
+ for step in range(0, len(steps)):
+ available.append(steps[step].supported_capacity)
+ if module.params["capacity"] not in available:
+ module.fail_json(
+ msg="Selected capacity is not available. "
+ "Run this module with `list` to get available capapcity points."
+ )
+ expanded = array.patch_arrays_cloud_capacity(
+ capacity=flasharray.CloudCapacityStatus(
+ requested_capacity=module.params["capacity"]
+ )
+ )
+ if expanded.sttaus_code != 200:
+ module.fail_json(
+ msg="Expansion request failed. Error: {0}".format(
+ expanded.errors[0].message
+ )
+ )
+
+
+def main():
+ argument_spec = purefa_argument_spec()
+ argument_spec.update(
+ dict(
+ state=dict(type="str", default="show", choices=["show", "expand"]),
+ capacity=dict(type="int"),
+ )
+ )
+
+ required_if = [["state", "expand", ["capacity"]]]
+ module = AnsibleModule(
+ argument_spec, required_if=required_if, supports_check_mode=True
+ )
+
+ array = get_array(module)
+ if not HAS_PURESTORAGE:
+ module.fail_json(
+ msg="py-pure-client sdk is required to support 'count' parameter"
+ )
+ if not _is_cbs(array):
+ module.fail_json(msg="Module only valid on Cloud Block Store array")
+ api_version = array.get_rest_version()
+ if LooseVersion(EXPAND_API_VERSION) > LooseVersion(api_version):
+ module.fail_json(
+ msg="FlashArray REST version not supported. "
+ "Minimum version required: {0}".format(EXPAND_API_VERSION)
+ )
+ if module.params["state"] == "show":
+ list_capacity(module, array)
+ else:
+ update_capacity(module, array)
+ module.exit_json(changed=False)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_certs.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_certs.py
index 33ffb60cc..bcc602610 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_certs.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_certs.py
@@ -151,6 +151,13 @@ EXAMPLES = r"""
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
+- name: Request CSR with updated fields
+ purestorage.flasharray.purefa_certs:
+ state: sign
+ org_unit: Development
+ fa_url: 10.10.10.2
+ api_token: e31060a7-21fc-e277-6240-25983c6c4592
+
- name: Regenerate key for SSL foo
purestorage.flasharray.purefa_certs:
generate: true
@@ -187,81 +194,95 @@ except ImportError:
import re
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
- get_system,
get_array,
purefa_argument_spec,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
MIN_REQUIRED_API_VERSION = "2.4"
def update_cert(module, array):
"""Update existing SSL Certificate"""
- changed = True
+ changed = False
current_cert = list(array.get_certificates(names=[module.params["name"]]).items)[0]
- try:
- if module.params["common_name"] != current_cert.common_name:
- module.params["common_name"] = current_cert.common_name
- except AttributeError:
- pass
- try:
- if module.params["country"] != current_cert.country:
- module.params["country"] = current_cert.country
- except AttributeError:
- pass
- try:
- if module.params["email"] != current_cert.email:
- module.params["email"] = current_cert.email
- except AttributeError:
- pass
- try:
- if module.params["key_size"] != current_cert.key_size:
- module.params["key_size"] = current_cert.key_size
- except AttributeError:
- pass
- try:
- if module.params["locality"] != current_cert.locality:
- module.params["locality"] = current_cert.locality
- except AttributeError:
- pass
- try:
- if module.params["province"] != current_cert.state:
- module.params["province"] = current_cert.state
- except AttributeError:
- pass
- try:
- if module.params["organization"] != current_cert.organization:
- module.params["organization"] = current_cert.organization
- except AttributeError:
- pass
- try:
- if module.params["org_unit"] != current_cert.organizational_unit:
- module.params["org_unit"] = current_cert.organizational_unit
- except AttributeError:
- pass
- certificate = flasharray.CertificatePost(
- common_name=module.params["common_name"],
- country=module.params["country"],
- email=module.params["email"],
- key_size=module.params["key_size"],
- locality=module.params["locality"],
- organization=module.params["organization"],
- organizational_unit=module.params["org_unit"],
- state=module.params["province"],
- days=module.params["days"],
- )
- if not module.check_mode:
- res = array.patch_certificates(
- names=[module.params["name"]],
- certificate=certificate,
- generate_new_key=module.params["generate"],
+ new_cert = current_cert
+ if module.params["common_name"] and module.params["common_name"] != getattr(
+ current_cert, "common_name", None
+ ):
+ new_cert.common_name = module.params["common_name"]
+ else:
+ new_cert.common_name = getattr(current_cert, "common_name", None)
+ if module.params["country"] and module.params["country"] != getattr(
+ current_cert, "country", None
+ ):
+ new_cert.country = module.params["country"]
+ else:
+ new_cert.country = getattr(current_cert, "country")
+ if module.params["email"] and module.params["email"] != getattr(
+ current_cert, "email", None
+ ):
+ new_cert.email = module.params["email"]
+ else:
+ new_cert.email = getattr(current_cert, "email", None)
+ if module.params["key_size"] and module.params["key_size"] != getattr(
+ current_cert, "key_size", None
+ ):
+ new_cert.key_size = module.params["key_size"]
+ else:
+ new_cert.key_size = getattr(current_cert, "key_size", None)
+ if module.params["locality"] and module.params["locality"] != getattr(
+ current_cert, "locality", None
+ ):
+ new_cert.locality = module.params["locality"]
+ else:
+ new_cert.locality = getattr(current_cert, "locality", None)
+ if module.params["province"] and module.params["province"] != getattr(
+ current_cert, "state", None
+ ):
+ new_cert.state = module.params["province"]
+ else:
+ new_cert.state = getattr(current_cert, "state", None)
+ if module.params["organization"] and module.params["organization"] != getattr(
+ current_cert, "organization", None
+ ):
+ new_cert.organization = module.params["organization"]
+ else:
+ new_cert.organization = getattr(current_cert, "organization", None)
+ if module.params["org_unit"] and module.params["org_unit"] != getattr(
+ current_cert, "organizational_unit", None
+ ):
+ new_cert.organizational_unit = module.params["org_unit"]
+ else:
+ new_cert.organizational_unit = getattr(
+ current_cert, "organizational_unit", None
)
- if res.status_code != 200:
- module.fail_json(
- msg="Updating existing SSL certificate {0} failed. Error: {1}".format(
- module.params["name"], res.errors[0].message
- )
+ if new_cert != current_cert:
+ changed = True
+ certificate = flasharray.CertificatePost(
+ common_name=new_cert.common_name,
+ country=getattr(new_cert, "country", None),
+ email=getattr(new_cert, "email", None),
+ key_size=getattr(new_cert, "key_size", None),
+ locality=getattr(new_cert, "locality", None),
+ organization=getattr(new_cert, "organization", None),
+ organizational_unit=getattr(new_cert, "organizational_unit", None),
+ state=getattr(new_cert, "state", None),
+ )
+ if not module.check_mode:
+ res = array.patch_certificates(
+ names=[module.params["name"]],
+ certificate=certificate,
+ generate_new_key=module.params["generate"],
)
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Updating existing SSL certificate {0} failed. Error: {1}".format(
+ module.params["name"], res.errors[0].message
+ )
+ )
module.exit_json(changed=changed)
@@ -312,12 +333,11 @@ def delete_cert(module, array):
def import_cert(module, array, reimport=False):
"""Import a CA provided SSL certificate"""
changed = True
- if len(module.params["certificate"]) > 3000:
- module.fail_json(msg="Imported Certificate exceeds 3000 characters")
certificate = flasharray.CertificatePost(
certificate=module.params["certificate"],
intermediate_certificate=module.params["intermeadiate_cert"],
key=module.params["key"],
+ key_size=module.params["key_size"],
passphrase=module.params["passphrase"],
status="imported",
)
@@ -364,50 +384,64 @@ def create_csr(module, array):
changed = True
current_attr = list(array.get_certificates(names=[module.params["name"]]).items)[0]
try:
- if module.params["common_name"] != current_attr.common_name:
- module.params["common_name"] = current_attr.common_name
+ if module.params["common_name"] and module.params["common_name"] != getattr(
+ current_attr, "common_name", None
+ ):
+ current_attr.common_name = module.params["common_name"]
except AttributeError:
pass
try:
- if module.params["country"] != current_attr.country:
- module.params["country"] = current_attr.country
+ if module.params["country"] and module.params["country"] != getattr(
+ current_attr, "country", None
+ ):
+ current_attr.country = module.params["country"]
except AttributeError:
pass
try:
- if module.params["email"] != current_attr.email:
- module.params["email"] = current_attr.email
+ if module.params["email"] and module.params["email"] != getattr(
+ current_attr, "email", None
+ ):
+ current_attr.email = module.params["email"]
except AttributeError:
pass
try:
- if module.params["locality"] != current_attr.locality:
- module.params["locality"] = current_attr.locality
+ if module.params["locality"] and module.params["locality"] != getattr(
+ current_attr, "locality", None
+ ):
+ current_attr.locality = module.params["locality"]
except AttributeError:
pass
try:
- if module.params["province"] != current_attr.state:
- module.params["province"] = current_attr.state
+ if module.params["province"] and module.params["province"] != getattr(
+ current_attr, "state", None
+ ):
+ current_attr.state = module.params["province"]
except AttributeError:
pass
try:
- if module.params["organization"] != current_attr.organization:
- module.params["organization"] = current_attr.organization
+ if module.params["organization"] and module.params["organization"] != getattr(
+ current_attr, "organization", None
+ ):
+ current_attr.organization = module.params["organization"]
except AttributeError:
pass
try:
- if module.params["org_unit"] != current_attr.organization_unit:
- module.params["org_unit"] = current_attr.organization_unit
+ if module.params["org_unit"] and module.params["org_unit"] != getattr(
+ current_attr, "organizational_unit", None
+ ):
+ current_attr.organizational_unit = module.params["org_unit"]
except AttributeError:
pass
if not module.check_mode:
certificate = flasharray.CertificateSigningRequestPost(
- certificate={"name": "management"},
- common_name=module.params["common_name"],
- country=module.params["country"],
- email=module.params["email"],
- locality=module.params["locality"],
- state=module.params["province"],
- organization=module.params["organization"],
- organization_unit=module.params["org_unit"],
+ certificate={"name": module.params["name"]},
+ common_name=getattr(current_attr, "common_name", None),
+ country=getattr(current_attr, "country", None),
+ email=getattr(current_attr, "email", None),
+ locality=getattr(current_attr, "locality", None),
+ state=getattr(current_attr, "state", None),
+ organization=getattr(current_attr, "organization", None),
+ organizational_unit=getattr(current_attr, "organizational_unit", None),
)
csr = list(
array.post_certificates_certificate_signing_requests(
@@ -452,6 +486,7 @@ def main():
required_if = [
["state", "import", ["certificate"]],
["state", "export", ["export_file"]],
+ ["state", "sign", ["export_file"]],
]
module = AnsibleModule(
@@ -468,16 +503,15 @@ def main():
module.fail_json(msg="pycountry sdk is required for this module")
email_pattern = r"^(\w|\.|\_|\-)+[@](\w|\_|\-|\.)+[.]\w{2,3}$"
- array = get_system(module)
- api_version = array._list_available_rest_versions()
+ array = get_array(module)
+ api_version = array.get_rest_version()
- if MIN_REQUIRED_API_VERSION not in api_version:
+ if LooseVersion(MIN_REQUIRED_API_VERSION) > LooseVersion(api_version):
module.fail_json(
msg="FlashArray REST version not supported. "
"Minimum version required: {0}".format(MIN_REQUIRED_API_VERSION)
)
- array = get_array(module)
if module.params["email"]:
if not re.search(email_pattern, module.params["email"]):
module.fail_json(
@@ -493,7 +527,7 @@ def main():
)
)
state = module.params["state"]
- if state in ["present", "sign"]:
+ if state in ["present"]:
if not module.params["common_name"]:
module.params["common_name"] = list(array.get_arrays().items)[0].name
module.params["common_name"] = module.params["common_name"][:64]
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_connect.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_connect.py
index 3148ea482..d4d65c20a 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_connect.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_connect.py
@@ -56,13 +56,20 @@ extends_documentation_fragment:
"""
EXAMPLES = r"""
-- name: Create an async connection to remote array
+- name: Create an IPv4 async connection to remote array
purestorage.flasharray.purefa_connect:
target_url: 10.10.10.20
target_api: 9c0b56bc-f941-f7a6-9f85-dcc3e9a8f7d6
connection: async
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
+- name: Create an IPv6 async connection to remote array
+ purestorage.flasharray.purefa_connect:
+ target_url: "[2001:db8:abcd:12::10]"
+ target_api: 9c0b56bc-f941-f7a6-9f85-dcc3e9a8f7d6
+ connection: async
+ fa_url: "[2001:db8:abcd:12::13]"
+ api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Delete connection to remote array
purestorage.flasharray.purefa_connect:
state: absent
@@ -87,14 +94,19 @@ try:
except ImportError:
HAS_PYPURECLIENT = False
-import platform
+HAS_DISTRO = True
+try:
+ import distro
+except ImportError:
+ HAS_DISTRO = False
+
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
get_array,
get_system,
purefa_argument_spec,
)
-
+import platform
P53_API_VERSION = "1.17"
FC_REPL_VERSION = "2.4"
@@ -107,14 +119,14 @@ def _check_connected(module, array):
if P53_API_VERSION in api_version:
if (
connected_arrays[target]["management_address"]
- == module.params["target_url"]
+ == module.params["target_url"].strip("[]")
and "connected" in connected_arrays[target]["status"]
):
return connected_arrays[target]
else:
if (
connected_arrays[target]["management_address"]
- == module.params["target_url"]
+ == module.params["target_url"].strip("[]")
and connected_arrays[target]["connected"]
):
return connected_arrays[target]
@@ -145,12 +157,20 @@ def create_connection(module, array):
"""Create connection between arrays"""
changed = True
remote_array = module.params["target_url"]
- user_agent = "%(base)s %(class)s/%(version)s (%(platform)s)" % {
- "base": "Ansible",
- "class": __name__,
- "version": 1.2,
- "platform": platform.platform(),
- }
+ if HAS_DISTRO:
+ user_agent = "%(base)s %(class)s/%(version)s (%(platform)s)" % {
+ "base": "Ansible",
+ "class": __name__,
+ "version": 1.5,
+ "platform": distro.name(pretty=True),
+ }
+ else:
+ user_agent = "%(base)s %(class)s/%(version)s (%(platform)s)" % {
+ "base": "Ansible",
+ "class": __name__,
+ "version": 1.5,
+ "platform": platform.platform(),
+ }
try:
remote_system = FlashArray(
module.params["target_url"],
@@ -171,7 +191,7 @@ def create_connection(module, array):
)
array_connection = flasharray.ArrayConnectionPost(
type="sync-replication",
- management_address=module.params["target_url"],
+ management_address=module.params["target_url"].strip("[]"),
replication_transport="fc",
connection_key=connection_key,
)
@@ -187,7 +207,7 @@ def create_connection(module, array):
else:
if not module.check_mode:
array.connect_array(
- module.params["target_url"],
+ module.params["target_url"].strip("[]"),
connection_key,
[module.params["connection"]],
)
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_default_protection.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_default_protection.py
index 5038de423..cdb953c0c 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_default_protection.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_default_protection.py
@@ -99,11 +99,12 @@ except ImportError:
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
- get_system,
get_array,
purefa_argument_spec,
)
-
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
DEFAULT_API_VERSION = "2.16"
@@ -282,14 +283,13 @@ def main():
module.fail_json(
msg="py-pure-client sdk is required to support 'count' parameter"
)
- arrayv5 = get_system(module)
module.params["name"] = sorted(module.params["name"])
- api_version = arrayv5._list_available_rest_versions()
- if DEFAULT_API_VERSION not in api_version:
+ array = get_array(module)
+ api_version = array.get_rest_version()
+ if LooseVersion(DEFAULT_API_VERSION) > LooseVersion(api_version):
module.fail_json(
msg="Default Protection is not supported. Purity//FA 6.3.4, or higher, is required."
)
- array = get_array(module)
if module.params["scope"] == "pod":
if not _get_pod(module, array):
module.fail_json(
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_directory.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_directory.py
index 125b84172..2cd769771 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_directory.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_directory.py
@@ -90,10 +90,12 @@ except ImportError:
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
- get_system,
get_array,
purefa_argument_spec,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
MIN_REQUIRED_API_VERSION = "2.2"
@@ -190,14 +192,13 @@ def main():
if not HAS_PURESTORAGE:
module.fail_json(msg="py-pure-client sdk is required for this module")
- array = get_system(module)
- api_version = array._list_available_rest_versions()
- if MIN_REQUIRED_API_VERSION not in api_version:
+ array = get_array(module)
+ api_version = array.get_rest_version()
+ if LooseVersion(MIN_REQUIRED_API_VERSION) > LooseVersion(api_version):
module.fail_json(
msg="FlashArray REST version not supported. "
"Minimum version required: {0}".format(MIN_REQUIRED_API_VERSION)
)
- array = get_array(module)
state = module.params["state"]
try:
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_dirsnap.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_dirsnap.py
index 4c090bde8..1c1c11a18 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_dirsnap.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_dirsnap.py
@@ -173,10 +173,12 @@ except ImportError:
import re
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
- get_system,
get_array,
purefa_argument_spec,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
MIN_REQUIRED_API_VERSION = "2.2"
MIN_RENAME_API_VERSION = "2.10"
@@ -413,19 +415,20 @@ def main():
)
)
- array = get_system(module)
- api_version = array._list_available_rest_versions()
- if MIN_REQUIRED_API_VERSION not in api_version:
+ array = get_array(module)
+ api_version = array.get_rest_version()
+ if LooseVersion(MIN_REQUIRED_API_VERSION) > LooseVersion(api_version):
module.fail_json(
msg="FlashArray REST version not supported. "
"Minimum version required: {0}".format(MIN_REQUIRED_API_VERSION)
)
- if module.params["rename"] and MIN_RENAME_API_VERSION not in api_version:
+ if module.params["rename"] and LooseVersion(MIN_RENAME_API_VERSION) > LooseVersion(
+ api_version
+ ):
module.fail_json(
msg="Directory snapshot rename not supported. "
"Minimum Purity//FA version required: 6.2.1"
)
- array = get_array(module)
state = module.params["state"]
snapshot_root = module.params["filesystem"] + ":" + module.params["name"]
if bool(
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_dns.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_dns.py
index 746a4ed52..7085a19f6 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_dns.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_dns.py
@@ -207,12 +207,9 @@ def delete_multi_dns(module, array):
"""Delete a DNS configuration"""
changed = True
if module.params["name"] == "management":
- res = array.update_dns(
+ res = array.patch_dns(
names=[module.params["name"]],
- dns=flasharray.DnsPatch(
- domain=module.params["domain"],
- nameservers=module.params["nameservers"],
- ),
+ dns=flasharray.DnsPatch(domain="", nameservers=[]),
)
if res.status_code != 200:
module.fail_json(
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_ds.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_ds.py
index 195aa2155..ce96b1afb 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_ds.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_ds.py
@@ -139,6 +139,23 @@ options:
I(shadowAccount) for OpenLDAP servers dependent on the group type
of the server, or person for all other directory servers.
- Supported from Purity 6.0 or higher.
+ check_peer:
+ type: bool
+ description:
+ - Whether or not server authenticity is enforced when a certificate
+ is provided
+ default: false
+ version_added: 1.24.0
+ certificate:
+ type: str
+ description:
+ - The certificate of the Certificate Authority (CA) that signed the
+ certificates of the directory servers, which is used to validate the
+ authenticity of the configured servers
+ - A valid signed certicate in PEM format (Base64 encoded)
+ - Includes the "-----BEGIN CERTIFICATE-----" and "-----END CERTIFICATE-----" lines
+ - Does not exceed 3000 characters in length
+ version_added: 1.24.0
extends_documentation_fragment:
- purestorage.flasharray.purestorage.fa
"""
@@ -209,6 +226,15 @@ EXAMPLES = r"""
bind_password: password
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
+
+- name: Upload CA certificate for management DNS and check peer
+ purestorage.flasharray.purefa_ds:
+ enable: true
+ dstype: management
+ certificate: "{{lookup('file', 'ca_cert.pem') }}"
+ check_peer: True
+ fa_url: 10.10.10.2
+ api_token: e31060a7-21fc-e277-6240-25983c6c4592
"""
RETURN = r"""
@@ -409,33 +435,32 @@ def update_ds_v6(module, array):
changed = False
ds_change = False
password_required = False
- dirserv = list(
- array.get_directory_services(
- filter="name='" + module.params["dstype"] + "'"
- ).items
- )[0]
- current_ds = dirserv
+ current_ds = []
+ dirservlist = list(array.get_directory_services().items)
+ for dirs in range(0, len(dirservlist)):
+ if dirservlist[dirs].name == module.params["dstype"]:
+ current_ds = dirservlist[dirs]
if module.params["uri"] and current_ds.uris is None:
password_required = True
- if current_ds.uris != module.params["uri"]:
+ if module.params["uri"] and current_ds.uris != module.params["uri"]:
uris = module.params["uri"]
ds_change = True
else:
uris = current_ds.uris
- try:
- base_dn = current_ds.base_dn
- except AttributeError:
- base_dn = ""
- try:
- bind_user = current_ds.bind_user
- except AttributeError:
- bind_user = ""
- if module.params["base_dn"] != "" and module.params["base_dn"] != base_dn:
+
+ base_dn = getattr(current_ds, "base_dn", "")
+ bind_user = getattr(current_ds, "bind_user", "")
+ cert = getattr(current_ds, "ca_certificate", None)
+ if module.params["base_dn"] and module.params["base_dn"] != base_dn:
base_dn = module.params["base_dn"]
ds_change = True
- if module.params["bind_user"] != "":
- bind_user = module.params["bind_user"]
+ if module.params["enable"] != current_ds.enabled:
+ ds_change = True
+ if getattr(current_ds, "bind_password", None) is None:
+ password_required = True
+ if module.params["bind_user"] is not None:
if module.params["bind_user"] != bind_user:
+ bind_user = module.params["bind_user"]
password_required = True
ds_change = True
elif module.params["force_bind_password"]:
@@ -444,19 +469,27 @@ def update_ds_v6(module, array):
if module.params["bind_password"] is not None and password_required:
bind_password = module.params["bind_password"]
ds_change = True
- if module.params["enable"] != current_ds.enabled:
- ds_change = True
if password_required and not module.params["bind_password"]:
module.fail_json(msg="'bind_password' must be provided for this task")
if module.params["dstype"] == "management":
- try:
- user_login = current_ds.management.user_login_attribute
- except AttributeError:
- user_login = ""
- try:
- user_object = current_ds.management.user_object_class
- except AttributeError:
- user_object = ""
+ if module.params["certificate"] is not None:
+ if cert is None and module.params["certificate"] != "":
+ cert = module.params["certificate"]
+ ds_change = True
+ elif cert is None and module.params["certificate"] == "":
+ pass
+ elif module.params["certificate"] != cert:
+ cert = module.params["certificate"]
+ ds_change = True
+ if module.params["check_peer"] and not cert:
+ module.warn(
+ "Cannot check_peer without a CA certificate. Disabling check_peer"
+ )
+ module.params["check_peer"] = False
+ if module.params["check_peer"] != current_ds.check_peer:
+ ds_change = True
+ user_login = getattr(current_ds.management, "user_login_attribute", "")
+ user_object = getattr(current_ds.management, "user_object_class", "")
if (
module.params["user_object"] is not None
and user_object != module.params["user_object"]
@@ -481,6 +514,8 @@ def update_ds_v6(module, array):
enabled=module.params["enable"],
services=module.params["dstype"],
management=management,
+ check_peer=module.params["check_peer"],
+ ca_certificate=cert,
)
else:
directory_service = flasharray.DirectoryService(
@@ -490,6 +525,8 @@ def update_ds_v6(module, array):
enabled=module.params["enable"],
services=module.params["dstype"],
management=management,
+ check_peer=module.params["check_peer"],
+ ca_certificate=cert,
)
else:
if password_required:
@@ -544,6 +581,8 @@ def main():
dstype=dict(
type="str", default="management", choices=["management", "data"]
),
+ check_peer=dict(type="bool", default=False),
+ certificate=dict(type="str"),
)
)
@@ -571,15 +610,17 @@ def main():
state = module.params["state"]
ds_exists = False
if FAFILES_API_VERSION in api_version:
- dirserv = list(
- arrayv6.get_directory_services(
- filter="name='" + module.params["dstype"] + "'"
- ).items
- )[0]
- if state == "absent" and dirserv.uris != []:
- delete_ds_v6(module, arrayv6)
- else:
- update_ds_v6(module, arrayv6)
+ dirserv = []
+ dirservlist = list(arrayv6.get_directory_services().items)
+ for dirs in range(0, len(dirservlist)):
+ if dirservlist[dirs].name == module.params["dstype"]:
+ dirserv = dirservlist[dirs]
+ if dirserv:
+ if state == "absent":
+ if dirserv.uris != []:
+ delete_ds_v6(module, arrayv6)
+ else:
+ update_ds_v6(module, arrayv6)
else:
dirserv = array.get_directory_service()
ds_enabled = dirserv["enabled"]
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_eradication.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_eradication.py
index ea7bd48bc..52cef0bae 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_eradication.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_eradication.py
@@ -31,6 +31,24 @@ options:
- Allowed values are integers from 1 to 30. Default is 1
default: 1
type: int
+ disabled_delay:
+ description:
+ - Configures the eradication delay
+ for destroyed objects that I(are) protected by SafeMode (objects for which
+ eradication is disabled)
+ - Allowed values are integers from 1 to 30. Default is 1
+ default: 1
+ type: int
+ version_added: "1.22.0"
+ enabled_delay:
+ description:
+ - Configures the eradication delay
+ for destroyed objects that I(are not) protected by SafeMode (objects for which
+ eradication is disabled)
+ - Allowed values are integers from 1 to 30. Default is 1
+ default: 1
+ type: int
+ version_added: "1.22.0"
extends_documentation_fragment:
- purestorage.flasharray.purestorage.fa
"""
@@ -60,39 +78,69 @@ except ImportError:
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
- get_system,
get_array,
purefa_argument_spec,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
SEC_PER_DAY = 86400000
ERADICATION_API_VERSION = "2.6"
+DELAY_API_VERSION = "2.26"
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(
dict(
- timer=dict(type="int", default="1"),
+ timer=dict(type="int", default=1),
+ disabled_delay=dict(type="int", default=1),
+ enabled_delay=dict(type="int", default=1),
)
)
module = AnsibleModule(argument_spec, supports_check_mode=True)
if not 30 >= module.params["timer"] >= 1:
module.fail_json(msg="Eradication Timer must be between 1 and 30 days.")
+ if not 30 >= module.params["disabled_delay"] >= 1:
+ module.fail_json(msg="disabled_delay must be between 1 and 30 days.")
+ if not 30 >= module.params["enabled_delay"] >= 1:
+ module.fail_json(msg="enabled_delay must be between 1 and 30 days.")
if not HAS_PURESTORAGE:
module.fail_json(msg="py-pure-client sdk is required for this module")
- array = get_system(module)
- api_version = array._list_available_rest_versions()
+ array = get_array(module)
+ api_version = array.get_rest_version()
changed = False
- if ERADICATION_API_VERSION in api_version:
+ current_disabled = None
+ current_enabled = None
+ if LooseVersion(ERADICATION_API_VERSION) <= LooseVersion(api_version):
array = get_array(module)
- current_timer = (
- list(array.get_arrays().items)[0].eradication_config.eradication_delay
- / SEC_PER_DAY
+ base_eradication_timer = getattr(
+ list(array.get_arrays().items)[0].eradication_config,
+ "eradication_delay",
+ None,
)
- if module.params["timer"] != current_timer:
+ if base_eradication_timer:
+ current_eradication_timer = base_eradication_timer / SEC_PER_DAY
+ if (
+ LooseVersion(DELAY_API_VERSION) <= LooseVersion(api_version)
+ and not base_eradication_timer
+ ):
+ current_disabled = (
+ list(array.get_arrays().items)[0].eradication_config.disabled_delay
+ / SEC_PER_DAY
+ )
+ current_enabled = (
+ list(array.get_arrays().items)[0].eradication_config.enabled_delay
+ / SEC_PER_DAY
+ )
+
+ if (
+ base_eradication_timer
+ and module.params["timer"] != current_eradication_timer
+ ):
changed = True
if not module.check_mode:
new_timer = SEC_PER_DAY * module.params["timer"]
@@ -106,6 +154,26 @@ def main():
res.errors[0].message
)
)
+ if current_disabled and (
+ module.params["enabled_delay"] != current_enabled
+ or module.params["disabled_delay"] != current_disabled
+ ):
+ changed = True
+ if not module.check_mode:
+ new_disabled = SEC_PER_DAY * module.params["disabled_delay"]
+ new_enabled = SEC_PER_DAY * module.params["enabled_delay"]
+ eradication_config = EradicationConfig(
+ enabled_delay=new_enabled, disabled_delay=new_disabled
+ )
+ res = array.patch_arrays(
+ array=Arrays(eradication_config=eradication_config)
+ )
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Failed to change Eradication Timers. Error: {0}".format(
+ res.errors[0].message
+ )
+ )
else:
module.fail_json(
msg="Purity version does not support changing Eradication Timer"
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_eula.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_eula.py
index 8d4d9536c..c810708b1 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_eula.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_eula.py
@@ -29,19 +29,16 @@ options:
- Full legal name of the entity.
- The value must be between 1 and 64 characters in length.
type: str
- required: true
name:
description:
- Full legal name of the individual at the company who has the authority to accept the terms of the agreement.
- The value must be between 1 and 64 characters in length.
type: str
- required: true
title:
description:
- Individual's job title at the company.
- The value must be between 1 and 64 characters in length.
type: str
- required: true
extends_documentation_fragment:
- purestorage.flasharray.purestorage.fa
"""
@@ -61,36 +58,48 @@ RETURN = r"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
- get_system,
+ get_array,
purefa_argument_spec,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
+
+HAS_PURESTORAGE = True
+try:
+ from pypureclient.flasharray import Eula, EulaSignature
+except ImportError:
+ HAS_PURESTORAGE = False
-EULA_API_VERSION = "1.17"
+EULA_V2 = "2.30"
def set_eula(module, array):
"""Sign EULA"""
changed = False
try:
- current_eula = array.get_eula()
+ current_eula = list(array.get_arrays_eula().items)[0]
except Exception:
module.fail_json(msg="Failed to get current EULA")
- if (
- current_eula["acceptance"]["company"] != module.params["company"]
- or current_eula["acceptance"]["title"] != module.params["title"]
- or current_eula["acceptance"]["name"] != module.params["name"]
- ):
- try:
- changed = True
- if not module.check_mode:
- array.set_eula(
- company=module.params["company"],
- title=module.params["title"],
- name=module.params["name"],
+ if not current_eula.signature.accepted:
+ changed = True
+ if not module.check_mode:
+ res = array.patch_arrays_eula(
+ eula=Eula(
+ signature=EulaSignature(
+ company=module.params["company"],
+ title=module.params["title"],
+ name=module.params["name"],
+ )
+ )
+ )
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Signing EULA failed. Error: {0}".format(res.erroros[0].message)
)
- except Exception:
- module.fail_json(msg="Signing EULA failed")
+ else:
+ module.warn("EULA already signed")
module.exit_json(changed=changed)
@@ -98,18 +107,27 @@ def main():
argument_spec = purefa_argument_spec()
argument_spec.update(
dict(
- company=dict(type="str", required=True),
- name=dict(type="str", required=True),
- title=dict(type="str", required=True),
+ company=dict(type="str"),
+ name=dict(type="str"),
+ title=dict(type="str"),
)
)
module = AnsibleModule(argument_spec, supports_check_mode=True)
+ if not HAS_PURESTORAGE:
+ module.fail_json(msg="py-pure-client sdk is required for this module")
+
+ array = get_array(module)
+ api_version = array.get_rest_version()
+ if LooseVersion(EULA_V2) > LooseVersion(api_version):
+ if not (
+ module.params["company"]
+ and module.params["title"]
+ and module.params["name"]
+ ):
+ module.fail_json(msg="missing required arguments: company, name, title")
+ set_eula(module, array)
- array = get_system(module)
- api_version = array._list_available_rest_versions()
- if EULA_API_VERSION in api_version:
- set_eula(module, array)
module.exit_json(changed=False)
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_export.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_export.py
index 5188dbd96..ba8b29041 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_export.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_export.py
@@ -91,10 +91,12 @@ except ImportError:
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
- get_system,
get_array,
purefa_argument_spec,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
MIN_REQUIRED_API_VERSION = "2.3"
@@ -224,14 +226,13 @@ def main():
if not HAS_PURESTORAGE:
module.fail_json(msg="py-pure-client sdk is required for this module")
- array = get_system(module)
- api_version = array._list_available_rest_versions()
- if MIN_REQUIRED_API_VERSION not in api_version:
+ array = get_array(module)
+ api_version = array.get_rest_version()
+ if LooseVersion(MIN_REQUIRED_API_VERSION) > LooseVersion(api_version):
module.fail_json(
msg="FlashArray REST version not supported. "
"Minimum version required: {0}".format(MIN_REQUIRED_API_VERSION)
)
- array = get_array(module)
state = module.params["state"]
exists = bool(
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_file.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_file.py
new file mode 100644
index 000000000..70371602c
--- /dev/null
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_file.py
@@ -0,0 +1,183 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# (c) 2020, Simon Dodsley (simon@purestorage.com)
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+ANSIBLE_METADATA = {
+ "metadata_version": "1.1",
+ "status": ["preview"],
+ "supported_by": "community",
+}
+
+DOCUMENTATION = r"""
+---
+module: purefa_file
+version_added: '1.22.0'
+short_description: Manage FlashArray File Copies
+description:
+- Copy FlashArray File from one filesystem to another
+author:
+- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
+options:
+ source_file:
+ description:
+ - Name of the file to copy
+ - Include full path from the perspective of the source managed directory
+ type: str
+ required: true
+ source_dir:
+ description:
+ - Name of the source managed directory containing the source file to be copied
+ type: str
+ required: true
+ target_file:
+ description:
+ - Name of the file to copy to
+ - Include full path from the perspective of the target managed directory
+ - If not provided the file will be copied to the relative path specified by I(name)
+ type: str
+ target_dir:
+ description:
+ - Name of the target managed directory containing the source file to be copied
+ - If not provided will use managed directory specified by I(source_dir)
+ type: str
+ overwrite:
+ description:
+ - Define whether to overwrite an existing target file
+ type: bool
+ default: false
+extends_documentation_fragment:
+- purestorage.flasharray.purestorage.fa
+"""
+
+EXAMPLES = r"""
+- name: Copy a file from dir foo to dir bar
+ purestorage.flasharray.purefa_file:
+ source_file: "/directory1/file1"
+ source_dir: "fs1:root"
+ target_file: "/diff_dir/file1"
+ target_dir: "fs1:root"
+ fa_url: 10.10.10.2
+ api_token: e31060a7-21fc-e277-6240-25983c6c4592
+
+- name: Copy a file in a direcotry to the same directory with a different name
+ purestorage.flasharray.purefa_file:
+ source_file: "/directory1/file1"
+ source_dir: "fs1:root"
+ target_file: "/directory_1/file2"
+ fa_url: 10.10.10.2
+ api_token: e31060a7-21fc-e277-6240-25983c6c4592
+
+- name: Copy a file in a direcotry to an existing file with overwrite
+ purestorage.flasharray.purefa_file:
+ source_file: "/directory1/file1"
+ source_dir: "fs1:root"
+ target_file: "/diff_dir/file1"
+ target_dir: "fs2:root"
+ overwrite: true
+ fa_url: 10.10.10.2
+ api_token: e31060a7-21fc-e277-6240-25983c6c4592
+"""
+
+RETURN = r"""
+"""
+
+HAS_PURESTORAGE = True
+try:
+ from pypureclient import flasharray
+except ImportError:
+ HAS_PURESTORAGE = False
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
+ get_array,
+ purefa_argument_spec,
+)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
+
+MIN_REQUIRED_API_VERSION = "2.26"
+
+
+def _check_dirs(module, array):
+ if array.get_directories(names=[module.params["source_dir"]]).status_code != 200:
+ module.fail_json(
+ msg="Source directory {0} does not exist".format(
+ module.params["source_dir"]
+ )
+ )
+ if array.get_directories(names=[module.params["target_dir"]]).status_code != 200:
+ module.fail_json(
+ msg="Target directory {0} does not exist".format(
+ module.params["target_dir"]
+ )
+ )
+
+
+def main():
+ argument_spec = purefa_argument_spec()
+ argument_spec.update(
+ dict(
+ overwrite=dict(type="bool", default=False),
+ source_file=dict(type="str", required=True),
+ source_dir=dict(type="str", required=True),
+ target_file=dict(type="str"),
+ target_dir=dict(type="str"),
+ )
+ )
+
+ required_one_of = [["target_file", "target_dir"]]
+ module = AnsibleModule(
+ argument_spec, required_one_of=required_one_of, supports_check_mode=True
+ )
+
+ if not HAS_PURESTORAGE:
+ module.fail_json(msg="py-pure-client sdk is required for this module")
+
+ array = get_array(module)
+ api_version = array.get_rest_version()
+
+ if LooseVersion(MIN_REQUIRED_API_VERSION) > LooseVersion(api_version):
+ module.fail_json(
+ msg="FlashArray REST version not supported. "
+ "Minimum version required: {0}".format(MIN_REQUIRED_API_VERSION)
+ )
+
+ if not module.params["target_file"]:
+ module.params["target_file"] = module.params["source_file"]
+ if not module.params["target_dir"]:
+ module.params["target_dir"] = module.params["source_dir"]
+ if ":" not in module.params["target_dir"]:
+ module.fail_json(msg="Target Direcotry is not formatted correctly")
+ if ":" not in module.params["source_dir"]:
+ module.fail_json(msg="Source Direcotry is not formatted correctly")
+ _check_dirs(module, array)
+ changed = True
+ if not module.check_mode:
+ res = array.post_files(
+ source_file=flasharray.FilePost(
+ source=flasharray.ReferenceWithType(
+ name=module.params["source_dir"], resource_type="directories"
+ ),
+ source_path=module.params["source_file"],
+ ),
+ overwrite=module.params["overwrite"],
+ paths=[module.params["target_file"]],
+ directory_names=[module.params["target_dir"]],
+ )
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Failed to copy file. Error: {0}".format(res.errors[0].message)
+ )
+
+ module.exit_json(changed=changed)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_fs.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_fs.py
index 05fbcb29b..1936bf0ff 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_fs.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_fs.py
@@ -93,10 +93,12 @@ except ImportError:
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
- get_system,
get_array,
purefa_argument_spec,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
MIN_REQUIRED_API_VERSION = "2.2"
REPL_SUPPORT_API = "2.13"
@@ -295,19 +297,21 @@ def main():
if not HAS_PURESTORAGE:
module.fail_json(msg="py-pure-client sdk is required for this module")
- array = get_system(module)
- api_version = array._list_available_rest_versions()
+ array = get_array(module)
+ api_version = array.get_rest_version()
- if MIN_REQUIRED_API_VERSION not in api_version:
+ if LooseVersion(MIN_REQUIRED_API_VERSION) > LooseVersion(api_version):
module.fail_json(
msg="FlashArray REST version not supported. "
"Minimum version required: {0}".format(MIN_REQUIRED_API_VERSION)
)
- if REPL_SUPPORT_API not in api_version and "::" in module.params["name"]:
+ if (
+ LooseVersion(REPL_SUPPORT_API) > LooseVersion(api_version)
+ and "::" in module.params["name"]
+ ):
module.fail_json(
msg="Filesystem Replication is only supported in Purity//FA 6.3.0 or higher"
)
- array = get_array(module)
state = module.params["state"]
try:
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_hardware.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_hardware.py
new file mode 100644
index 000000000..ffe9718fe
--- /dev/null
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_hardware.py
@@ -0,0 +1,110 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# (c) 2021, Simon Dodsley (simon@purestorage.com)
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+ANSIBLE_METADATA = {
+ "metadata_version": "1.1",
+ "status": ["preview"],
+ "supported_by": "community",
+}
+
+DOCUMENTATION = r"""
+---
+module: purefa_hardware
+version_added: '1.24.0'
+short_description: Manage FlashArray Hardware Identification
+description:
+- Enable or disable FlashArray visual identification lights
+author:
+- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
+options:
+ name:
+ description:
+ - Name of hardware component
+ type: str
+ required: true
+ enabled:
+ description:
+ - State of the component identification LED
+ type: bool
+extends_documentation_fragment:
+- purestorage.flasharray.purestorage.fa
+"""
+
+EXAMPLES = r"""
+- name: Enable identification LED
+ purestorage.flasharray.purefa_hardware:
+ name: "CH1.FB1"
+ enabled: True
+ fa_url: 10.10.10.2
+ api_token: T-68618f31-0c9e-4e57-aa44-5306a2cf10e3
+
+- name: Disable identification LED
+ purestorage.flasharray.purefa_hardware:
+ name: "CH1.FB1"
+ enabled: False
+ fa_url: 10.10.10.2
+ api_token: T-68618f31-0c9e-4e57-aa44-5306a2cf10e3
+"""
+
+RETURN = r"""
+"""
+
+HAS_PURESTORAGE = True
+try:
+ from pypureclient import flasharray
+except ImportError:
+ HAS_PURESTORAGE = False
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
+ get_array,
+ purefa_argument_spec,
+)
+
+
+def main():
+ argument_spec = purefa_argument_spec()
+ argument_spec.update(
+ dict(
+ enabled=dict(type="bool"),
+ name=dict(type="str", required=True),
+ )
+ )
+
+ module = AnsibleModule(argument_spec, supports_check_mode=True)
+ if not HAS_PURESTORAGE:
+ module.fail_json(msg="py-pure-client sdk is required for this module")
+
+ array = get_array(module)
+ changed = False
+ res = array.get_hardware(names=[module.params["name"]])
+ if res.status_code == 200:
+ id_state = getattr(list(res.items)[0], "identify_enabled", None)
+ if id_state is not None and id_state != module.params["enabled"]:
+ changed = True
+ if not module.check_mode:
+ res = array.patch_hardware(
+ names=[module.params["name"]],
+ hardware=flasharray.Hardware(
+ identify_enabled=module.params["enabled"]
+ ),
+ )
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Failed to set identification LED for {0}. Error: {1}".format(
+ module.params["name"], res.errors[0].message
+ )
+ )
+
+ module.exit_json(changed=changed)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_host.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_host.py
index 9054d8f30..c396975a2 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_host.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_host.py
@@ -479,7 +479,7 @@ def _set_chap_security(module, array):
host_password=module.params["host_password"],
)
except Exception:
- module.params(msg="Failed to set CHAP host username and password")
+ module.fail_json(msg="Failed to set CHAP host username and password")
if module.params["target_user"]:
if not pattern.match(module.params["target_password"]):
module.fail_json(
@@ -492,7 +492,7 @@ def _set_chap_security(module, array):
target_password=module.params["target_password"],
)
except Exception:
- module.params(msg="Failed to set CHAP target username and password")
+ module.fail_json(msg="Failed to set CHAP target username and password")
def _update_chap_security(module, array, answer=False):
@@ -507,7 +507,7 @@ def _update_chap_security(module, array, answer=False):
try:
array.set_host(module.params["name"], host_user="")
except Exception:
- module.params(
+ module.fail_json(
msg="Failed to clear CHAP host username and password"
)
else:
@@ -524,7 +524,9 @@ def _update_chap_security(module, array, answer=False):
host_password=module.params["host_password"],
)
except Exception:
- module.params(msg="Failed to set CHAP host username and password")
+ module.fail_json(
+ msg="Failed to set CHAP host username and password"
+ )
if module.params["target_user"]:
if module.params["target_password"] == "clear":
if chap["target_user"]:
@@ -533,7 +535,7 @@ def _update_chap_security(module, array, answer=False):
try:
array.set_host(module.params["name"], target_user="")
except Exception:
- module.params(
+ module.fail_json(
msg="Failed to clear CHAP target username and password"
)
else:
@@ -550,7 +552,9 @@ def _update_chap_security(module, array, answer=False):
target_password=module.params["target_password"],
)
except Exception:
- module.params(msg="Failed to set CHAP target username and password")
+ module.fail_json(
+ msg="Failed to set CHAP target username and password"
+ )
return answer
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_info.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_info.py
index de7f05002..262d227be 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_info.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_info.py
@@ -24,7 +24,7 @@ description:
Purity//FA operating system. By default, the module will collect basic
information including hosts, host groups, protection
groups and volume counts. Additional information can be collected
- based on the configured set of arguements.
+ based on the configured set of arguments.
author:
- Pure Storage ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
options:
@@ -35,7 +35,7 @@ options:
capacity, network, subnet, interfaces, hgroups, pgroups, hosts,
admins, volumes, snapshots, pods, replication, vgroups, offload, apps,
arrays, certs, kmip, clients, policies, dir_snaps, filesystems,
- alerts and virtual_machines.
+ alerts, virtual_machines, hosts_balance and subscriptions.
type: list
elements: str
required: false
@@ -90,16 +90,15 @@ from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa impo
get_system,
purefa_argument_spec,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
-HAS_PACKAGING = True
-try:
- from packaging import version
-except ImportError:
- HAS_PACKAGING = False
try:
from purestorage import purestorage
except ImportError:
purestorage = None
+from datetime import datetime
import time
SEC_TO_DAY = 86400000
@@ -129,6 +128,18 @@ VM_VERSION = "2.14"
VLAN_VERSION = "2.17"
NEIGHBOR_API_VERSION = "2.22"
POD_QUOTA_VERSION = "2.23"
+AUTODIR_API_VERSION = "2.24"
+SUBS_API_VERSION = "2.26"
+NSID_API_VERSION = "2.27"
+NFS_SECURITY_VERSION = "2.29"
+UPTIME_API_VERSION = "2.30"
+
+
+def _is_cbs(array):
+ """Is the selected array a Cloud Block Store"""
+ model = list(array.get_hardware(filter="type='controller'").items)[0].model
+ is_cbs = bool("CBS" in model)
+ return is_cbs
def generate_default_dict(module, array):
@@ -164,14 +175,38 @@ def generate_default_dict(module, array):
default_info["encryption_algorithm"] = encryption.data_at_rest.algorithm
default_info["encryption_module_version"] = encryption.module_version
eradication = array_data.eradication_config
- default_info["eradication_days_timer"] = int(
- eradication.eradication_delay / SEC_TO_DAY
- )
+ if SUBS_API_VERSION in api_version:
+ default_info["eradication_disabled_days_timer"] = int(
+ eradication.disabled_delay / SEC_TO_DAY
+ )
+ default_info["eradication_enabled_days_timer"] = int(
+ eradication.enabled_delay / SEC_TO_DAY
+ )
+ eradication_delay = getattr(eradication, "eradication_delay", None)
+ if eradication_delay is not None:
+ default_info["eradication_days_timer"] = int(
+ eradication_delay / SEC_TO_DAY
+ )
if SAFE_MODE_VERSION in api_version:
if eradication.manual_eradication == "all-enabled":
default_info["safe_mode"] = "Disabled"
else:
default_info["safe_mode"] = "Enabled"
+ if UPTIME_API_VERSION in api_version:
+ default_info["controller_uptime"] = []
+ controllers = list(arrayv6.get_controllers().items)
+ timenow = datetime.fromtimestamp(time.time())
+ for controller in range(0, len(controllers)):
+ boottime = datetime.fromtimestamp(
+ controllers[controller].mode_since / 1000
+ )
+ delta = timenow - boottime
+ default_info["controller_uptime"].append(
+ {
+ "controller": controllers[controller].name,
+ "uptime": str(delta),
+ }
+ )
if AC_REQUIRED_API_VERSION in api_version:
default_info["volume_groups"] = len(array.list_vgroups())
default_info["connected_arrays"] = len(array.list_array_connections())
@@ -282,12 +317,9 @@ def generate_config_dict(module, array):
"nameservers": dns_configs[config].nameservers,
"domain": dns_configs[config].domain,
}
- try:
- config_info["dns"][dns_configs[config].services[0]][
- "source"
- ] = dns_configs[config].source["name"]
- except Exception:
- pass
+ config_info["dns"][dns_configs[config].services[0]]["source"] = getattr(
+ dns_configs[config].source, "name", None
+ )
if SAML2_VERSION in api_version:
config_info["saml2sso"] = {}
saml2 = list(arrayv6.get_sso_saml2_idps().items)
@@ -355,6 +387,12 @@ def generate_config_dict(module, array):
.name,
}
)
+ if SUBS_API_VERSION in api_version:
+ array_info = list(arrayv6.get_arrays().items)[0]
+ config_info["ntp_keys"] = bool(
+ getattr(array_info, "ntp_symmetric_key", None)
+ )
+ config_info["timezone"] = array_info.time_zone
else:
config_info["directory_service"] = {}
@@ -419,6 +457,10 @@ def generate_filesystems_dict(array):
),
"exports": {},
}
+ if LooseVersion(SUBS_API_VERSION) <= LooseVersion(array.get_rest_version()):
+ files_info[fs_name]["directories"][d_name]["total_used"] = directories[
+ directory
+ ].space.total_used
exports = list(
array.get_directory_exports(
directory_names=[
@@ -485,6 +527,8 @@ def generate_dir_snaps_dict(array):
snapshots[snapshot].space, "used_provisioned", None
),
}
+ if LooseVersion(SUBS_API_VERSION) <= LooseVersion(array.get_rest_version()):
+ dir_snaps_info[s_name]["total_used"] = snapshots[snapshot].space.total_used
try:
dir_snaps_info[s_name]["policy"] = snapshots[snapshot].policy.name
except Exception:
@@ -496,7 +540,7 @@ def generate_dir_snaps_dict(array):
return dir_snaps_info
-def generate_policies_dict(array, quota_available, nfs_user_mapping):
+def generate_policies_dict(array, quota_available, autodir_available, nfs_user_mapping):
policy_info = {}
policies = list(array.get_policies().items)
for policy in range(0, len(policies)):
@@ -528,6 +572,18 @@ def generate_policies_dict(array, quota_available, nfs_user_mapping):
policy_info[p_name][
"user_mapping_enabled"
] = nfs_policy.user_mapping_enabled
+ if LooseVersion(SUBS_API_VERSION) <= LooseVersion(
+ array.get_rest_version()
+ ):
+ policy_info[p_name]["nfs_version"] = getattr(
+ nfs_policy, "nfs_version", None
+ )
+ if LooseVersion(NFS_SECURITY_VERSION) <= LooseVersion(
+ array.get_rest_version()
+ ):
+ policy_info[p_name]["security"] = getattr(
+ nfs_policy, "security", None
+ )
rules = list(
array.get_policies_nfs_client_rules(policy_names=[p_name]).items
)
@@ -537,14 +593,16 @@ def generate_policies_dict(array, quota_available, nfs_user_mapping):
"permission": rules[rule].permission,
"client": rules[rule].client,
}
+ if LooseVersion(SUBS_API_VERSION) <= LooseVersion(
+ array.get_rest_version()
+ ):
+ nfs_rules_dict["nfs_version"] = rules[rule].nfs_version
policy_info[p_name]["rules"].append(nfs_rules_dict)
if policies[policy].policy_type == "snapshot":
- if HAS_PACKAGING:
- suffix_enabled = version.parse(
- array.get_rest_version()
- ) >= version.parse(SHARED_CAP_API_VERSION)
- else:
- suffix_enabled = False
+ suffix_enabled = bool(
+ LooseVersion(array.get_rest_version())
+ >= LooseVersion(SHARED_CAP_API_VERSION)
+ )
rules = list(array.get_policies_snapshot_rules(policy_names=[p_name]).items)
for rule in range(0, len(rules)):
try:
@@ -576,6 +634,8 @@ def generate_policies_dict(array, quota_available, nfs_user_mapping):
"notifications": rules[rule].notifications,
}
policy_info[p_name]["rules"].append(quota_rules_dict)
+ if policies[policy].policy_type == "autodir" and autodir_available:
+ pass # there are currently no rules for autodir policies
return policy_info
@@ -657,8 +717,144 @@ def generate_network_dict(module, array):
for neighbor in range(0, len(neighbors)):
neighbor_info = neighbors[neighbor]
int_name = neighbor_info.local_port.name
- net_info[int_name].update(
- {
+ try:
+ net_info[int_name].update(
+ {
+ "neighbor": {
+ "initial_ttl_in_sec": neighbor_info.initial_ttl_in_sec,
+ "neighbor_port": {
+ "description": getattr(
+ neighbor_info.neighbor_port, "description", None
+ ),
+ "name": getattr(
+ neighbor_info.neighbor_chassis, "name", None
+ ),
+ "id": getattr(
+ neighbor_info.neighbor_port.id, "value", None
+ ),
+ },
+ "neighbor_chassis": {
+ "addresses": getattr(
+ neighbor_info.neighbor_chassis, "addresses", None
+ ),
+ "description": getattr(
+ neighbor_info.neighbor_chassis, "description", None
+ ),
+ "name": getattr(
+ neighbor_info.neighbor_chassis, "name", None
+ ),
+ "bridge": {
+ "enabled": getattr(
+ neighbor_info.neighbor_chassis.bridge,
+ "enabled",
+ False,
+ ),
+ "supported": getattr(
+ neighbor_info.neighbor_chassis.bridge,
+ "supported",
+ False,
+ ),
+ },
+ "repeater": {
+ "enabled": getattr(
+ neighbor_info.neighbor_chassis.repeater,
+ "enabled",
+ False,
+ ),
+ "supported": getattr(
+ neighbor_info.neighbor_chassis.repeater,
+ "supported",
+ False,
+ ),
+ },
+ "router": {
+ "enabled": getattr(
+ neighbor_info.neighbor_chassis.router,
+ "enabled",
+ False,
+ ),
+ "supported": getattr(
+ neighbor_info.neighbor_chassis.router,
+ "supported",
+ False,
+ ),
+ },
+ "station_only": {
+ "enabled": getattr(
+ neighbor_info.neighbor_chassis.station_only,
+ "enabled",
+ False,
+ ),
+ "supported": getattr(
+ neighbor_info.neighbor_chassis.station_only,
+ "supported",
+ False,
+ ),
+ },
+ "telephone": {
+ "enabled": getattr(
+ neighbor_info.neighbor_chassis.telephone,
+ "enabled",
+ False,
+ ),
+ "supported": getattr(
+ neighbor_info.neighbor_chassis.telephone,
+ "supported",
+ False,
+ ),
+ },
+ "wlan_access_point": {
+ "enabled": getattr(
+ neighbor_info.neighbor_chassis.wlan_access_point,
+ "enabled",
+ False,
+ ),
+ "supported": getattr(
+ neighbor_info.neighbor_chassis.wlan_access_point,
+ "supported",
+ False,
+ ),
+ },
+ "docsis_cable_device": {
+ "enabled": getattr(
+ neighbor_info.neighbor_chassis.docsis_cable_device,
+ "enabled",
+ False,
+ ),
+ "supported": getattr(
+ neighbor_info.neighbor_chassis.docsis_cable_device,
+ "supported",
+ False,
+ ),
+ },
+ "id": {
+ "type": getattr(
+ neighbor_info.neighbor_chassis.id,
+ "type",
+ None,
+ ),
+ "value": getattr(
+ neighbor_info.neighbor_chassis.id,
+ "value",
+ None,
+ ),
+ },
+ },
+ }
+ }
+ )
+ except KeyError:
+ net_info[int_name] = {
+ "hwaddr": None,
+ "mtu": None,
+ "enabled": None,
+ "speed": None,
+ "address": None,
+ "slaves": None,
+ "services": None,
+ "gateway": None,
+ "netmask": None,
+ "subnet": None,
"neighbor": {
"initial_ttl_in_sec": neighbor_info.initial_ttl_in_sec,
"neighbor_port": {
@@ -779,9 +975,9 @@ def generate_network_dict(module, array):
),
},
},
- }
+ },
}
- )
+
return net_info
@@ -830,6 +1026,8 @@ def generate_capacity_dict(module, array):
capacity_info["used_provisioned"] = getattr(
capacity.space, "used_provisioned", 0
)
+ if SUBS_API_VERSION in api_version:
+ capacity_info["total_used"] = capacity.space.total_used
else:
capacity_info["provisioned_space"] = capacity.space["total_provisioned"]
capacity_info["free_space"] = (
@@ -843,6 +1041,13 @@ def generate_capacity_dict(module, array):
capacity_info["thin_provisioning"] = capacity.space["thin_provisioning"]
capacity_info["total_reduction"] = capacity.space["total_reduction"]
capacity_info["replication"] = capacity.space["replication"]
+ if NFS_SECURITY_VERSION in api_version and _is_cbs(arrayv6):
+ cloud = list(arrayv6.get_arrays_cloud_capacity().items)[0]
+ capacity_info["cloud_capacity"] = {
+ "current_capacity": cloud.current_capacity,
+ "requested_capacity": cloud.requested_capacity,
+ "status": cloud.status,
+ }
elif CAP_REQUIRED_API_VERSION in api_version:
volumes = array.list_volumes(pending=True)
capacity_info["provisioned_space"] = sum(item["size"] for item in volumes)
@@ -890,9 +1095,11 @@ def generate_snap_dict(module, array):
].space.total_provisioned
snap_info[snapshot]["unique_space"] = snapsv6[snap].space.unique
if SHARED_CAP_API_VERSION in api_version:
- snap_info[snapshot]["snapshots_effective"] = snapsv6[
- snap
- ].space.snapshots_effective
+ snap_info[snapshot]["snapshots_effective"] = getattr(
+ snapsv6[snap].space, "snapshots_effective", None
+ )
+ if SUBS_API_VERSION in api_version:
+ snap_info[snapshot]["total_used"] = snapsv6[snap].space.total_used
offloads = list(arrayv6.get_offloads().items)
for offload in range(0, len(offloads)):
offload_name = offloads[offload].name
@@ -976,6 +1183,8 @@ def generate_del_snap_dict(module, array):
snap
].space.total_provisioned
snap_info[snapshot]["unique_space"] = snapsv6[snap].space.unique
+ if SUBS_API_VERSION in api_version:
+ snap_info[snapshot]["total_used"] = snapsv6[snap].space.total_used
offloads = list(arrayv6.get_offloads().items)
for offload in range(0, len(offloads)):
offload_name = offloads[offload].name
@@ -1073,15 +1282,17 @@ def generate_del_vol_dict(module, array):
vol
].space.thin_provisioning
if SHARED_CAP_API_VERSION in api_version:
- volume_info[name]["snapshots_effective"] = vols_space[
- vol
- ].space.snapshots_effective
- volume_info[name]["unique_effective"] = vols_space[
- vol
- ].space.unique_effective
+ volume_info[name]["snapshots_effective"] = getattr(
+ vols_space[vol].space, "snapshots_effective", None
+ )
+ volume_info[name]["unique_effective"] = getattr(
+ vols_space[vol].space, "unique_effective", None
+ )
volume_info[name]["used_provisioned"] = (
getattr(vols_space[vol].space, "used_provisioned", None),
)
+ if SUBS_API_VERSION in api_version:
+ volume_info[name]["total_used"] = vols_space[vol].space.total_used
if ACTIVE_DR_API in api_version:
voltags = array.list_volumes(tags=True, pending_only=True)
for voltag in range(0, len(voltags)):
@@ -1094,16 +1305,22 @@ def generate_del_vol_dict(module, array):
"namespace": voltags[voltag]["namespace"],
}
volume_info[volume]["tags"].append(tagdict)
- if SAFE_MODE_VERSION in api_version:
+ if V6_MINIMUM_API_VERSION in api_version:
volumes = list(arrayv6.get_volumes(destroyed=True).items)
for vol in range(0, len(volumes)):
name = volumes[vol].name
- volume_info[name]["priority"] = volumes[vol].priority
- volume_info[name]["priority_adjustment"] = volumes[
+ volume_info[name]["promotion_status"] = volumes[vol].promotion_status
+ volume_info[name]["requested_promotion_state"] = volumes[
vol
- ].priority_adjustment.priority_adjustment_operator + str(
- volumes[vol].priority_adjustment.priority_adjustment_value
- )
+ ].requested_promotion_state
+ if SAFE_MODE_VERSION in api_version:
+ volume_info[name]["subtype"] = volumes[vol].subtype
+ volume_info[name]["priority"] = volumes[vol].priority
+ volume_info[name]["priority_adjustment"] = volumes[
+ vol
+ ].priority_adjustment.priority_adjustment_operator + str(
+ volumes[vol].priority_adjustment.priority_adjustment_value
+ )
return volume_info
@@ -1146,18 +1363,20 @@ def generate_vol_dict(module, array):
vol
].space.total_physical
if SHARED_CAP_API_VERSION in api_version:
- volume_info[name]["snapshots_effective"] = vols_space[
- vol
- ].space.snapshots_effective
- volume_info[name]["unique_effective"] = vols_space[
- vol
- ].space.unique_effective
- volume_info[name]["total_effective"] = vols_space[
- vol
- ].space.total_effective
+ volume_info[name]["snapshots_effective"] = getattr(
+ vols_space[vol].space, "snapshots_effective", None
+ )
+ volume_info[name]["unique_effective"] = getattr(
+ vols_space[vol].space, "unique_effective", None
+ )
+ volume_info[name]["total_effective"] = getattr(
+ vols_space[vol].space, "total_effective", None
+ )
volume_info[name]["used_provisioned"] = (
getattr(vols_space[vol].space, "used_provisioned", None),
)
+ if SUBS_API_VERSION in api_version:
+ volume_info[name]["total_used"] = vols_space[vol].space.total_used
if AC_REQUIRED_API_VERSION in api_version:
qvols = array.list_volumes(qos=True)
for qvol in range(0, len(qvols)):
@@ -1176,9 +1395,9 @@ def generate_vol_dict(module, array):
"source": vvols[vvol]["source"],
"serial": vvols[vvol]["serial"],
"nvme_nguid": "eui.00"
- + vols[vol]["serial"][0:14].lower()
+ + vvols[vvol]["serial"][0:14].lower()
+ "24a937"
- + vols[vol]["serial"][-10:].lower(),
+ + vvols[vvol]["serial"][-10:].lower(),
"page83_naa": PURE_OUI + vvols[vvol]["serial"],
"tags": [],
"hosts": [],
@@ -1190,16 +1409,22 @@ def generate_vol_dict(module, array):
volume_info[volume]["host_encryption_key_status"] = e2ees[e2ee][
"host_encryption_key_status"
]
- if SAFE_MODE_VERSION in api_version:
+ if V6_MINIMUM_API_VERSION in api_version:
volumes = list(arrayv6.get_volumes(destroyed=False).items)
for vol in range(0, len(volumes)):
name = volumes[vol].name
- volume_info[name]["priority"] = volumes[vol].priority
- volume_info[name]["priority_adjustment"] = volumes[
+ volume_info[name]["promotion_status"] = volumes[vol].promotion_status
+ volume_info[name]["requested_promotion_state"] = volumes[
vol
- ].priority_adjustment.priority_adjustment_operator + str(
- volumes[vol].priority_adjustment.priority_adjustment_value
- )
+ ].requested_promotion_state
+ volume_info[name]["subtype"] = volumes[vol].subtype
+ if SAFE_MODE_VERSION in api_version:
+ volume_info[name]["priority"] = volumes[vol].priority
+ volume_info[name]["priority_adjustment"] = volumes[
+ vol
+ ].priority_adjustment.priority_adjustment_operator + str(
+ volumes[vol].priority_adjustment.priority_adjustment_value
+ )
cvols = array.list_volumes(connect=True)
for cvol in range(0, len(cvols)):
volume = cvols[cvol]["name"]
@@ -1223,6 +1448,9 @@ def generate_vol_dict(module, array):
def generate_host_dict(module, array):
api_version = array._list_available_rest_versions()
host_info = {}
+ if FC_REPL_API_VERSION in api_version:
+ arrayv6 = get_array(module)
+ hostsv6 = list(arrayv6.get_hosts().items)
hosts = array.list_hosts()
for host in range(0, len(hosts)):
hostname = hosts[host]["name"]
@@ -1246,15 +1474,31 @@ def generate_host_dict(module, array):
"personality": array.get_host(hostname, personality=True)["personality"],
"target_port": all_tports,
"volumes": [],
+ "performance_balance": [],
}
- host_connections = array.list_host_connections(hostname)
- for connection in range(0, len(host_connections)):
- connection_dict = {
- "hostgroup": host_connections[connection]["hgroup"],
- "volume": host_connections[connection]["vol"],
- "lun": host_connections[connection]["lun"],
- }
- host_info[hostname]["volumes"].append(connection_dict)
+ if FC_REPL_API_VERSION in api_version:
+ host_connections = list(
+ arrayv6.get_connections(host_names=[hostname]).items
+ )
+ for connection in range(0, len(host_connections)):
+ connection_dict = {
+ "hostgroup": getattr(
+ host_connections[connection].host_group, "name", ""
+ ),
+ "volume": host_connections[connection].volume.name,
+ "lun": getattr(host_connections[connection], "lun", ""),
+ "nsid": getattr(host_connections[connection], "nsid", ""),
+ }
+ host_info[hostname]["volumes"].append(connection_dict)
+ else:
+ host_connections = array.list_host_connections(hostname)
+ for connection in range(0, len(host_connections)):
+ connection_dict = {
+ "hostgroup": host_connections[connection]["hgroup"],
+ "volume": host_connections[connection]["vol"],
+ "lun": host_connections[connection]["lun"],
+ }
+ host_info[hostname]["volumes"].append(connection_dict)
if host_info[hostname]["iqn"]:
chap_data = array.get_host(hostname, chap=True)
host_info[hostname]["target_user"] = chap_data["target_user"]
@@ -1266,6 +1510,35 @@ def generate_host_dict(module, array):
for host in range(0, len(hosts)):
hostname = hosts[host]["name"]
host_info[hostname]["preferred_array"] = hosts[host]["preferred_array"]
+ if FC_REPL_API_VERSION in api_version:
+ hosts_balance = list(arrayv6.get_hosts_performance_balance().items)
+ for host in range(0, len(hostsv6)):
+ if hostsv6[host].is_local:
+ host_info[hostsv6[host].name]["port_connectivity"] = hostsv6[
+ host
+ ].port_connectivity.details
+ host_perf_balance = []
+ for balance in range(0, len(hosts_balance)):
+ if hosts[host]["name"] == hosts_balance[balance].name:
+ host_balance = {
+ "fraction_relative_to_max": getattr(
+ hosts_balance[balance],
+ "fraction_relative_to_max",
+ None,
+ ),
+ "op_count": getattr(hosts_balance[balance], "op_count", 0),
+ "target": getattr(
+ hosts_balance[balance].target, "name", None
+ ),
+ "failed": bool(
+ getattr(hosts_balance[balance].target, "failover", 0)
+ ),
+ }
+ if host_balance["target"]:
+ host_perf_balance.append(host_balance)
+ host_info[hosts[host]["name"]]["performance_balance"].append(
+ host_perf_balance
+ )
if VLAN_VERSION in api_version:
arrayv6 = get_array(module)
hosts = list(arrayv6.get_hosts().items)
@@ -1276,6 +1549,131 @@ def generate_host_dict(module, array):
return host_info
+def generate_del_pgroups_dict(module, array):
+ pgroups_info = {}
+ api_version = array._list_available_rest_versions()
+ pgroups = array.list_pgroups(pending_only=True)
+ if SHARED_CAP_API_VERSION in api_version:
+ array_v6 = get_array(module)
+ deleted_enabled = True
+ else:
+ deleted_enabled = False
+ for pgroup in range(0, len(pgroups)):
+ protgroup = pgroups[pgroup]["name"]
+ pgroups_info[protgroup] = {
+ "hgroups": pgroups[pgroup]["hgroups"],
+ "hosts": pgroups[pgroup]["hosts"],
+ "source": pgroups[pgroup]["source"],
+ "targets": pgroups[pgroup]["targets"],
+ "volumes": pgroups[pgroup]["volumes"],
+ "time_remaining": pgroups[pgroup]["time_remaining"],
+ }
+ try:
+ prot_sched = array.get_pgroup(protgroup, schedule=True, pending=True)
+ prot_reten = array.get_pgroup(protgroup, retention=True, pending=True)
+ snap_transfers = array.get_pgroup(
+ protgroup, snap=True, transfer=True, pending=True
+ )
+ except purestorage.PureHTTPError as err:
+ if err.code == 400:
+ continue
+ if prot_sched["snap_enabled"] or prot_sched["replicate_enabled"]:
+ pgroups_info[protgroup]["snap_frequency"] = prot_sched["snap_frequency"]
+ pgroups_info[protgroup]["replicate_frequency"] = prot_sched[
+ "replicate_frequency"
+ ]
+ pgroups_info[protgroup]["snap_enabled"] = prot_sched["snap_enabled"]
+ pgroups_info[protgroup]["replicate_enabled"] = prot_sched[
+ "replicate_enabled"
+ ]
+ pgroups_info[protgroup]["snap_at"] = prot_sched["snap_at"]
+ pgroups_info[protgroup]["replicate_at"] = prot_sched["replicate_at"]
+ pgroups_info[protgroup]["replicate_blackout"] = prot_sched[
+ "replicate_blackout"
+ ]
+ pgroups_info[protgroup]["per_day"] = prot_reten["per_day"]
+ pgroups_info[protgroup]["target_per_day"] = prot_reten["target_per_day"]
+ pgroups_info[protgroup]["target_days"] = prot_reten["target_days"]
+ pgroups_info[protgroup]["days"] = prot_reten["days"]
+ pgroups_info[protgroup]["all_for"] = prot_reten["all_for"]
+ pgroups_info[protgroup]["target_all_for"] = prot_reten["target_all_for"]
+ pgroups_info[protgroup]["snaps"] = {}
+ for snap_transfer in range(0, len(snap_transfers)):
+ snap = snap_transfers[snap_transfer]["name"]
+ pgroups_info[protgroup]["snaps"][snap] = {
+ "time_remaining": snap_transfers[snap_transfer]["time_remaining"],
+ "created": snap_transfers[snap_transfer]["created"],
+ "started": snap_transfers[snap_transfer]["started"],
+ "completed": snap_transfers[snap_transfer]["completed"],
+ "physical_bytes_written": snap_transfers[snap_transfer][
+ "physical_bytes_written"
+ ],
+ "data_transferred": snap_transfers[snap_transfer]["data_transferred"],
+ "progress": snap_transfers[snap_transfer]["progress"],
+ }
+ if deleted_enabled:
+ pgroups_info[protgroup]["deleted_volumes"] = []
+ volumes = list(
+ array_v6.get_protection_groups_volumes(group_names=[protgroup]).items
+ )
+ if volumes:
+ for volume in range(0, len(volumes)):
+ if volumes[volume].member["destroyed"]:
+ pgroups_info[protgroup]["deleted_volumes"].append(
+ volumes[volume].member["name"]
+ )
+ else:
+ pgroups_info[protgroup]["deleted_volumes"] = None
+ if PER_PG_VERSION in api_version:
+ try:
+ pgroups_info[protgroup]["retention_lock"] = list(
+ array_v6.get_protection_groups(names=[protgroup]).items
+ )[0].retention_lock
+ pgroups_info[protgroup]["manual_eradication"] = list(
+ array_v6.get_protection_groups(names=[protgroup]).items
+ )[0].eradication_config.manual_eradication
+ except Exception:
+ pass
+ if V6_MINIMUM_API_VERSION in api_version:
+ pgroups = list(array_v6.get_protection_groups(destroyed=True).items)
+ for pgroup in range(0, len(pgroups)):
+ name = pgroups[pgroup].name
+ pgroups_info[name]["snapshots"] = getattr(
+ pgroups[pgroup].space, "snapshots", None
+ )
+ pgroups_info[name]["shared"] = getattr(
+ pgroups[pgroup].space, "shared", None
+ )
+ pgroups_info[name]["data_reduction"] = getattr(
+ pgroups[pgroup].space, "data_reduction", None
+ )
+ pgroups_info[name]["thin_provisioning"] = getattr(
+ pgroups[pgroup].space, "thin_provisioning", None
+ )
+ pgroups_info[name]["total_physical"] = getattr(
+ pgroups[pgroup].space, "total_physical", None
+ )
+ pgroups_info[name]["total_provisioned"] = getattr(
+ pgroups[pgroup].space, "total_provisioned", None
+ )
+ pgroups_info[name]["total_reduction"] = getattr(
+ pgroups[pgroup].space, "total_reduction", None
+ )
+ pgroups_info[name]["unique"] = getattr(
+ pgroups[pgroup].space, "unique", None
+ )
+ pgroups_info[name]["virtual"] = getattr(
+ pgroups[pgroup].space, "virtual", None
+ )
+ pgroups_info[name]["replication"] = getattr(
+ pgroups[pgroup].space, "replication", None
+ )
+ pgroups_info[name]["used_provisioned"] = getattr(
+ pgroups[pgroup].space, "used_provisioned", None
+ )
+ return pgroups_info
+
+
def generate_pgroups_dict(module, array):
pgroups_info = {}
api_version = array._list_available_rest_versions()
@@ -1361,7 +1759,7 @@ def generate_pgroups_dict(module, array):
except Exception:
pass
if V6_MINIMUM_API_VERSION in api_version:
- pgroups = list(array_v6.get_protection_groups().items)
+ pgroups = list(array_v6.get_protection_groups(destroyed=False).items)
for pgroup in range(0, len(pgroups)):
name = pgroups[pgroup].name
pgroups_info[name]["snapshots"] = getattr(
@@ -1408,14 +1806,19 @@ def generate_rl_dict(module, array):
rlinks = array.list_pod_replica_links()
for rlink in range(0, len(rlinks)):
link_name = rlinks[rlink]["local_pod_name"]
- since_epoch = rlinks[rlink]["recovery_point"] / 1000
- recovery_datatime = time.strftime(
- "%Y-%m-%d %H:%M:%S", time.localtime(since_epoch)
- )
+ if rlinks[rlink]["recovery_point"]:
+ since_epoch = rlinks[rlink]["recovery_point"] / 1000
+ recovery_datatime = time.strftime(
+ "%Y-%m-%d %H:%M:%S", time.localtime(since_epoch)
+ )
+ else:
+ recovery_datatime = None
+ if rlinks[rlink]["lag"]:
+ lag = str(rlinks[rlink]["lag"] / 1000) + "s"
rl_info[link_name] = {
"status": rlinks[rlink]["status"],
"direction": rlinks[rlink]["direction"],
- "lag": str(rlinks[rlink]["lag"] / 1000) + "s",
+ "lag": lag,
"remote_pod_name": rlinks[rlink]["remote_pod_name"],
"remote_names": rlinks[rlink]["remote_names"],
"recovery_point": recovery_datatime,
@@ -1464,21 +1867,37 @@ def generate_del_pods_dict(module, array):
pods = list(arrayv6.get_pods(destroyed=True).items)
for pod in range(0, len(pods)):
name = pods[pod].name
- pods_info[name]["snapshots"] = pods[pod].space.snapshots
- pods_info[name]["shared"] = pods[pod].space.shared
- pods_info[name]["data_reduction"] = pods[pod].space.data_reduction
- pods_info[name]["thin_provisioning"] = pods[pod].space.thin_provisioning
- pods_info[name]["total_physical"] = pods[pod].space.total_physical
- pods_info[name]["total_provisioned"] = pods[pod].space.total_provisioned
- pods_info[name]["total_reduction"] = pods[pod].space.total_reduction
- pods_info[name]["unique"] = pods[pod].space.unique
- pods_info[name]["virtual"] = pods[pod].space.virtual
+ pods_info[name]["snapshots"] = getattr(
+ pods[pod].space, "snapshots", None
+ )
+ pods_info[name]["shared"] = getattr(pods[pod].space, "shared", None)
+ pods_info[name]["data_reduction"] = getattr(
+ pods[pod].space, "data_reduction", None
+ )
+ pods_info[name]["thin_provisioning"] = getattr(
+ pods[pod].space, "thin_provisioning", None
+ )
+ pods_info[name]["total_physical"] = getattr(
+ pods[pod].space, "total_physical", None
+ )
+ pods_info[name]["total_provisioned"] = getattr(
+ pods[pod].space, "total_provisioned", None
+ )
+ pods_info[name]["total_reduction"] = getattr(
+ pods[pod].space, "total_reduction", None
+ )
+ pods_info[name]["unique"] = getattr(pods[pod].space, "unique", None)
+ pods_info[name]["virtual"] = getattr(pods[pod].space, "virtual", None)
pods_info[name]["replication"] = pods[pod].space.replication
pods_info[name]["used_provisioned"] = getattr(
pods[pod].space, "used_provisioned", None
)
if POD_QUOTA_VERSION in api_version:
- pods_info[name]["quota_limit"] = pods[pod].quota_limit
+ pods_info[name]["quota_limit"] = getattr(
+ pods[pod], "quota_limit", None
+ )
+ if SUBS_API_VERSION in api_version:
+ pods_info[name]["total_used"] = pods[pod].space.total_used
return pods_info
@@ -1547,6 +1966,8 @@ def generate_pods_dict(module, array):
pods_info[name]["used_provisioned"] = getattr(
pods[pod].space, "used_provisioned", None
)
+ if SUBS_API_VERSION in api_version:
+ pods_info[name]["total_used"] = pods[pod].space.total_used
return pods_info
@@ -1605,16 +2026,16 @@ def generate_conn_array_dict(module, array):
pass
try:
if bool(carrays[carray].throttle.default_limit):
- conn_array_info[arrayname]["throttling"][
- "default_limit"
- ] = carrays[carray].throttle.default_limit
+ conn_array_info[arrayname]["throttling"]["default_limit"] = (
+ carrays[carray].throttle.default_limit
+ )
except AttributeError:
pass
try:
if bool(carrays[carray].throttle.window_limit):
- conn_array_info[arrayname]["throttling"][
- "window_limit"
- ] = carrays[carray].throttle.window_limit
+ conn_array_info[arrayname]["throttling"]["window_limit"] = (
+ carrays[carray].throttle.window_limit
+ )
except AttributeError:
pass
else:
@@ -1682,6 +2103,10 @@ def generate_vgroups_dict(module, array):
vgroups_info[name]["iops_limit"] = getattr(
vgroups[vgroup].qos, "iops_limit", ""
)
+ if SUBS_API_VERSION in api_version:
+ vgroups_info[name]["total_used"] = getattr(
+ vgroups[vgroup].space, "total_used", None
+ )
if SAFE_MODE_VERSION in api_version:
for vgroup in range(0, len(vgroups)):
name = vgroups[vgroup].name
@@ -1844,6 +2269,8 @@ def generate_nfs_offload_dict(module, array):
offload_info[name]["used_provisioned"] = getattr(
offloads[offload].space, "used_provisioned", None
)
+ if SUBS_API_VERSION in api_version:
+ offload_info[name]["total_used"] = offloads[offload].space.total_used
return offload_info
@@ -1902,6 +2329,8 @@ def generate_s3_offload_dict(module, array):
offload_info[name]["used_provisioned"] = getattr(
offloads[offload].space, "used_provisioned", None
)
+ if SUBS_API_VERSION in api_version:
+ offload_info[name]["total_used"] = offloads[offload].space.total_used
return offload_info
@@ -1957,6 +2386,8 @@ def generate_azure_offload_dict(module, array):
offload_info[name]["used_provisioned"] = getattr(
offloads[offload].space, "used_provisioned", None
)
+ if SUBS_API_VERSION in api_version:
+ offload_info[name]["total_used"] = offloads[offload].space.total_used
return offload_info
@@ -1989,6 +2420,8 @@ def generate_google_offload_dict(array):
offloads[offload].space, "used_provisioned", None
),
}
+ if LooseVersion(SUBS_API_VERSION) <= LooseVersion(array.get_rest_version()):
+ offload_info[name]["total_used"] = offloads[offload].space.total_used
return offload_info
@@ -2016,24 +2449,37 @@ def generate_hgroups_dict(module, array):
arrayv6 = get_array(module)
hgroups = list(arrayv6.get_host_groups().items)
for hgroup in range(0, len(hgroups)):
- name = hgroups[hgroup].name
- hgroups_info[name]["snapshots"] = hgroups[hgroup].space.snapshots
- hgroups_info[name]["data_reduction"] = hgroups[hgroup].space.data_reduction
- hgroups_info[name]["thin_provisioning"] = hgroups[
- hgroup
- ].space.thin_provisioning
- hgroups_info[name]["total_physical"] = hgroups[hgroup].space.total_physical
- hgroups_info[name]["total_provisioned"] = hgroups[
- hgroup
- ].space.total_provisioned
- hgroups_info[name]["total_reduction"] = hgroups[
- hgroup
- ].space.total_reduction
- hgroups_info[name]["unique"] = hgroups[hgroup].space.unique
- hgroups_info[name]["virtual"] = hgroups[hgroup].space.virtual
- hgroups_info[name]["used_provisioned"] = getattr(
- hgroups[hgroup].space, "used_provisioned", None
- )
+ if hgroups[hgroup].is_local:
+ name = hgroups[hgroup].name
+ hgroups_info[name]["snapshots"] = getattr(
+ hgroups[hgroup].space, "snapshots", None
+ )
+ hgroups_info[name]["data_reduction"] = getattr(
+ hgroups[hgroup].space, "data_reduction", None
+ )
+ hgroups_info[name]["thin_provisioning"] = getattr(
+ hgroups[hgroup].space, "thin_provisioning", None
+ )
+ hgroups_info[name]["total_physical"] = getattr(
+ hgroups[hgroup].space, "total_physical", None
+ )
+ hgroups_info[name]["total_provisioned"] = getattr(
+ hgroups[hgroup].space, "total_provisioned", None
+ )
+ hgroups_info[name]["total_reduction"] = getattr(
+ hgroups[hgroup].space, "total_reduction", None
+ )
+ hgroups_info[name]["unique"] = getattr(
+ hgroups[hgroup].space, "unique", None
+ )
+ hgroups_info[name]["virtual"] = getattr(
+ hgroups[hgroup].space, "virtual", None
+ )
+ hgroups_info[name]["used_provisioned"] = getattr(
+ hgroups[hgroup].space, "used_provisioned", None
+ )
+ if SUBS_API_VERSION in api_version:
+ hgroups_info[name]["total_used"] = hgroups[hgroup].space.total_used
return hgroups_info
@@ -2150,6 +2596,17 @@ def generate_vmsnap_dict(array):
return vmsnap_info
+def generate_subs_dict(array):
+ subs_info = {}
+ subs = list(array.get_subscription_assets().items)
+ for sub in range(0, len(subs)):
+ name = subs[sub].name
+ subs_info[name] = {
+ "subscription_id": subs[sub].subscription.id,
+ }
+ return subs_info
+
+
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(
@@ -2188,7 +2645,9 @@ def main():
"policies",
"dir_snaps",
"filesystems",
+ "alerts",
"virtual_machines",
+ "subscriptions",
)
subset_test = (test in valid_subsets for test in subset)
if not all(subset_test):
@@ -2225,6 +2684,7 @@ def main():
info["hgroups"] = generate_hgroups_dict(module, array)
if "pgroups" in subset or "all" in subset:
info["pgroups"] = generate_pgroups_dict(module, array)
+ info["deleted_pgroups"] = generate_del_pgroups_dict(module, array)
if "pods" in subset or "all" in subset or "replication" in subset:
info["replica_links"] = generate_rl_dict(module, array)
info["pods"] = generate_pods_dict(module, array)
@@ -2264,7 +2724,13 @@ def main():
quota = True
else:
quota = False
- info["policies"] = generate_policies_dict(array_v6, quota, user_map)
+ if AUTODIR_API_VERSION in api_version:
+ autodir = True
+ else:
+ autodir = False
+ info["policies"] = generate_policies_dict(
+ array_v6, quota, autodir, user_map
+ )
if "clients" in subset or "all" in subset:
info["clients"] = generate_clients_dict(array_v6)
if "dir_snaps" in subset or "all" in subset:
@@ -2273,6 +2739,10 @@ def main():
info["pg_snapshots"] = generate_pgsnaps_dict(array_v6)
if "alerts" in subset or "all" in subset:
info["alerts"] = generate_alerts_dict(array_v6)
+ if SUBS_API_VERSION in api_version and (
+ "subscriptions" in subset or "all" in subset
+ ):
+ info["subscriptions"] = generate_subs_dict(array_v6)
if VM_VERSION in api_version and (
"virtual_machines" in subset or "all" in subset
):
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_inventory.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_inventory.py
index 8e65ee07e..396699b58 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_inventory.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_inventory.py
@@ -48,17 +48,18 @@ purefa_inventory:
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
- get_system,
get_array,
purefa_argument_spec,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
-NEW_API_VERSION = "2.2"
SFP_API_VERSION = "2.16"
-def generate_new_hardware_dict(array, versions):
+def generate_new_hardware_dict(array):
hw_info = {
"fans": {},
"controllers": {},
@@ -137,216 +138,137 @@ def generate_new_hardware_dict(array, versions):
"protocol": getattr(drives[drive], "protocol", None),
"type": drives[drive].type,
}
- if SFP_API_VERSION in versions:
- port_details = list(array.get_network_interfaces_port_details().items)
- for port_detail in range(0, len(port_details)):
- port_name = port_details[port_detail].name
- hw_info["interfaces"][port_name]["interface_type"] = port_details[
- port_detail
- ].interface_type
- hw_info["interfaces"][port_name]["rx_los"] = (
- port_details[port_detail].rx_los[0].flag
- )
- hw_info["interfaces"][port_name]["rx_power"] = (
- port_details[port_detail].rx_power[0].measurement
- )
- hw_info["interfaces"][port_name]["static"] = {
- "connector_type": port_details[port_detail].static.connector_type,
- "vendor_name": port_details[port_detail].static.vendor_name,
- "vendor_oui": port_details[port_detail].static.vendor_oui,
- "vendor_serial_number": port_details[
- port_detail
- ].static.vendor_serial_number,
- "vendor_part_number": port_details[
+ api_version = array.get_rest_version()
+ if LooseVersion(SFP_API_VERSION) <= LooseVersion(api_version):
+ try:
+ port_details = list(array.get_network_interfaces_port_details().items)
+ for port_detail in range(0, len(port_details)):
+ port_name = port_details[port_detail].name
+ hw_info["interfaces"][port_name]["interface_type"] = port_details[
port_detail
- ].static.vendor_part_number,
- "vendor_date_code": port_details[port_detail].static.vendor_date_code,
- "signaling_rate": port_details[port_detail].static.signaling_rate,
- "wavelength": port_details[port_detail].static.wavelength,
- "rate_identifier": port_details[port_detail].static.rate_identifier,
- "identifier": port_details[port_detail].static.identifier,
- "link_length": port_details[port_detail].static.link_length,
- "voltage_thresholds": {
- "alarm_high": port_details[
- port_detail
- ].static.voltage_thresholds.alarm_high,
- "alarm_low": port_details[
- port_detail
- ].static.voltage_thresholds.alarm_low,
- "warn_high": port_details[
- port_detail
- ].static.voltage_thresholds.warn_high,
- "warn_low": port_details[
- port_detail
- ].static.voltage_thresholds.warn_low,
- },
- "tx_power_thresholds": {
- "alarm_high": port_details[
- port_detail
- ].static.tx_power_thresholds.alarm_high,
- "alarm_low": port_details[
- port_detail
- ].static.tx_power_thresholds.alarm_low,
- "warn_high": port_details[
- port_detail
- ].static.tx_power_thresholds.warn_high,
- "warn_low": port_details[
- port_detail
- ].static.tx_power_thresholds.warn_low,
- },
- "rx_power_thresholds": {
- "alarm_high": port_details[
- port_detail
- ].static.rx_power_thresholds.alarm_high,
- "alarm_low": port_details[
- port_detail
- ].static.rx_power_thresholds.alarm_low,
- "warn_high": port_details[
+ ].interface_type
+ hw_info["interfaces"][port_name]["rx_los"] = (
+ port_details[port_detail].rx_los[0].flag
+ )
+ hw_info["interfaces"][port_name]["rx_power"] = (
+ port_details[port_detail].rx_power[0].measurement
+ )
+ hw_info["interfaces"][port_name]["static"] = {
+ "connector_type": port_details[port_detail].static.connector_type,
+ "vendor_name": port_details[port_detail].static.vendor_name,
+ "vendor_oui": port_details[port_detail].static.vendor_oui,
+ "vendor_serial_number": port_details[
port_detail
- ].static.rx_power_thresholds.warn_high,
- "warn_low": port_details[
+ ].static.vendor_serial_number,
+ "vendor_part_number": port_details[
port_detail
- ].static.rx_power_thresholds.warn_low,
- },
- "tx_bias_thresholds": {
- "alarm_high": port_details[
+ ].static.vendor_part_number,
+ "vendor_date_code": port_details[
port_detail
- ].static.tx_bias_thresholds.alarm_high,
- "alarm_low": port_details[
+ ].static.vendor_date_code,
+ "signaling_rate": port_details[port_detail].static.signaling_rate,
+ "wavelength": port_details[port_detail].static.wavelength,
+ "rate_identifier": port_details[port_detail].static.rate_identifier,
+ "identifier": port_details[port_detail].static.identifier,
+ "link_length": port_details[port_detail].static.link_length,
+ "voltage_thresholds": {
+ "alarm_high": port_details[
+ port_detail
+ ].static.voltage_thresholds.alarm_high,
+ "alarm_low": port_details[
+ port_detail
+ ].static.voltage_thresholds.alarm_low,
+ "warn_high": port_details[
+ port_detail
+ ].static.voltage_thresholds.warn_high,
+ "warn_low": port_details[
+ port_detail
+ ].static.voltage_thresholds.warn_low,
+ },
+ "tx_power_thresholds": {
+ "alarm_high": port_details[
+ port_detail
+ ].static.tx_power_thresholds.alarm_high,
+ "alarm_low": port_details[
+ port_detail
+ ].static.tx_power_thresholds.alarm_low,
+ "warn_high": port_details[
+ port_detail
+ ].static.tx_power_thresholds.warn_high,
+ "warn_low": port_details[
+ port_detail
+ ].static.tx_power_thresholds.warn_low,
+ },
+ "rx_power_thresholds": {
+ "alarm_high": port_details[
+ port_detail
+ ].static.rx_power_thresholds.alarm_high,
+ "alarm_low": port_details[
+ port_detail
+ ].static.rx_power_thresholds.alarm_low,
+ "warn_high": port_details[
+ port_detail
+ ].static.rx_power_thresholds.warn_high,
+ "warn_low": port_details[
+ port_detail
+ ].static.rx_power_thresholds.warn_low,
+ },
+ "tx_bias_thresholds": {
+ "alarm_high": port_details[
+ port_detail
+ ].static.tx_bias_thresholds.alarm_high,
+ "alarm_low": port_details[
+ port_detail
+ ].static.tx_bias_thresholds.alarm_low,
+ "warn_high": port_details[
+ port_detail
+ ].static.tx_bias_thresholds.warn_high,
+ "warn_low": port_details[
+ port_detail
+ ].static.tx_bias_thresholds.warn_low,
+ },
+ "temperature_thresholds": {
+ "alarm_high": port_details[
+ port_detail
+ ].static.temperature_thresholds.alarm_high,
+ "alarm_low": port_details[
+ port_detail
+ ].static.temperature_thresholds.alarm_low,
+ "warn_high": port_details[
+ port_detail
+ ].static.temperature_thresholds.warn_high,
+ "warn_low": port_details[
+ port_detail
+ ].static.temperature_thresholds.warn_low,
+ },
+ "fc_speeds": port_details[port_detail].static.fc_speeds,
+ "fc_technology": port_details[port_detail].static.fc_technology,
+ "encoding": port_details[port_detail].static.encoding,
+ "fc_link_lengths": port_details[port_detail].static.fc_link_lengths,
+ "fc_transmission_media": port_details[
port_detail
- ].static.tx_bias_thresholds.alarm_low,
- "warn_high": port_details[
+ ].static.fc_transmission_media,
+ "extended_identifier": port_details[
port_detail
- ].static.tx_bias_thresholds.warn_high,
- "warn_low": port_details[
- port_detail
- ].static.tx_bias_thresholds.warn_low,
- },
- "temperature_thresholds": {
- "alarm_high": port_details[
- port_detail
- ].static.temperature_thresholds.alarm_high,
- "alarm_low": port_details[
- port_detail
- ].static.temperature_thresholds.alarm_low,
- "warn_high": port_details[
- port_detail
- ].static.temperature_thresholds.warn_high,
- "warn_low": port_details[
- port_detail
- ].static.temperature_thresholds.warn_low,
- },
- "fc_speeds": port_details[port_detail].static.fc_speeds,
- "fc_technology": port_details[port_detail].static.fc_technology,
- "encoding": port_details[port_detail].static.encoding,
- "fc_link_lengths": port_details[port_detail].static.fc_link_lengths,
- "fc_transmission_media": port_details[
- port_detail
- ].static.fc_transmission_media,
- "extended_identifier": port_details[
- port_detail
- ].static.extended_identifier,
- }
- hw_info["interfaces"][port_name]["temperature"] = (
- port_details[port_detail].temperature[0].measurement
- )
- hw_info["interfaces"][port_name]["tx_bias"] = (
- port_details[port_detail].tx_bias[0].measurement
- )
- hw_info["interfaces"][port_name]["tx_fault"] = (
- port_details[port_detail].tx_fault[0].flag
- )
- hw_info["interfaces"][port_name]["tx_power"] = (
- port_details[port_detail].tx_power[0].measurement
- )
- hw_info["interfaces"][port_name]["voltage"] = (
- port_details[port_detail].voltage[0].measurement
- )
- return hw_info
-
-
-def generate_hardware_dict(array):
- hw_info = {
- "fans": {},
- "controllers": {},
- "temps": {},
- "drives": {},
- "interfaces": {},
- "power": {},
- "chassis": {},
- }
- components = array.list_hardware()
- for component in range(0, len(components)):
- component_name = components[component]["name"]
- if "FAN" in component_name:
- fan_name = component_name
- hw_info["fans"][fan_name] = {"status": components[component]["status"]}
- if "PWR" in component_name:
- pwr_name = component_name
- hw_info["power"][pwr_name] = {
- "status": components[component]["status"],
- "voltage": components[component]["voltage"],
- "serial": components[component]["serial"],
- "model": components[component]["model"],
- }
- if "IB" in component_name:
- ib_name = component_name
- hw_info["interfaces"][ib_name] = {
- "status": components[component]["status"],
- "speed": components[component]["speed"],
- }
- if "SAS" in component_name:
- sas_name = component_name
- hw_info["interfaces"][sas_name] = {
- "status": components[component]["status"],
- "speed": components[component]["speed"],
- }
- if "ETH" in component_name:
- eth_name = component_name
- hw_info["interfaces"][eth_name] = {
- "status": components[component]["status"],
- "speed": components[component]["speed"],
- }
- if "FC" in component_name:
- eth_name = component_name
- hw_info["interfaces"][eth_name] = {
- "status": components[component]["status"],
- "speed": components[component]["speed"],
- }
- if "TMP" in component_name:
- tmp_name = component_name
- hw_info["temps"][tmp_name] = {
- "status": components[component]["status"],
- "temperature": components[component]["temperature"],
- }
- if component_name in ["CT0", "CT1"]:
- cont_name = component_name
- hw_info["controllers"][cont_name] = {
- "status": components[component]["status"],
- "serial": components[component]["serial"],
- "model": components[component]["model"],
- }
- if component_name in ["CH0"]:
- cont_name = component_name
- hw_info["chassis"][cont_name] = {
- "status": components[component]["status"],
- "serial": components[component]["serial"],
- "model": components[component]["model"],
- }
-
- drives = array.list_drives()
- for drive in range(0, len(drives)):
- drive_name = drives[drive]["name"]
- hw_info["drives"][drive_name] = {
- "capacity": drives[drive]["capacity"],
- "status": drives[drive]["status"],
- "protocol": drives[drive]["protocol"],
- "type": drives[drive]["type"],
- }
- for disk in range(0, len(components)):
- if components[disk]["name"] == drive_name:
- hw_info["drives"][drive_name]["serial"] = components[disk]["serial"]
-
+ ].static.extended_identifier,
+ }
+ hw_info["interfaces"][port_name]["temperature"] = (
+ port_details[port_detail].temperature[0].measurement
+ )
+ hw_info["interfaces"][port_name]["tx_bias"] = (
+ port_details[port_detail].tx_bias[0].measurement
+ )
+ hw_info["interfaces"][port_name]["tx_fault"] = (
+ port_details[port_detail].tx_fault[0].flag
+ )
+ hw_info["interfaces"][port_name]["tx_power"] = (
+ port_details[port_detail].tx_power[0].measurement
+ )
+ hw_info["interfaces"][port_name]["voltage"] = (
+ port_details[port_detail].voltage[0].measurement
+ )
+ except AttributeError:
+ pass
return hw_info
@@ -354,13 +276,8 @@ def main():
argument_spec = purefa_argument_spec()
inv_info = {}
module = AnsibleModule(argument_spec, supports_check_mode=True)
- array = get_system(module)
- api_version = array._list_available_rest_versions()
- if NEW_API_VERSION in api_version:
- arrayv6 = get_array(module)
- inv_info = generate_new_hardware_dict(arrayv6, api_version)
- else:
- inv_info = generate_hardware_dict(array)
+ array = get_array(module)
+ inv_info = generate_new_hardware_dict(array)
module.exit_json(changed=False, purefa_inv=inv_info)
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_kmip.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_kmip.py
index 8774abe87..b422f6f1e 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_kmip.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_kmip.py
@@ -97,10 +97,12 @@ except ImportError:
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
- get_system,
get_array,
purefa_argument_spec,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
MIN_REQUIRED_API_VERSION = "2.2"
@@ -222,16 +224,15 @@ def main():
if not HAS_PURESTORAGE:
module.fail_json(msg="py-pure-client sdk is required for this module")
- array = get_system(module)
- api_version = array._list_available_rest_versions()
+ array = get_array(module)
+ api_version = array.get_rest_version()
- if MIN_REQUIRED_API_VERSION not in api_version:
+ if LooseVersion(MIN_REQUIRED_API_VERSION) > LooseVersion(api_version):
module.fail_json(
msg="FlashArray REST version not supported. "
"Minimum version required: {0}".format(MIN_REQUIRED_API_VERSION)
)
- array = get_array(module)
state = module.params["state"]
exists = bool(array.get_kmip(names=[module.params["name"]]).status_code == 200)
if module.params["certificate"] and len(module.params["certificate"]) > 3000:
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_logging.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_logging.py
index a2f8e136d..c16818498 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_logging.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_logging.py
@@ -66,10 +66,12 @@ import time
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
- get_system,
get_array,
purefa_argument_spec,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
AUDIT_API_VERSION = "2.2"
@@ -87,13 +89,12 @@ def main():
if not HAS_PURESTORAGE:
module.fail_json(msg="py-pure-client sdk is required for this module")
- array = get_system(module)
- api_version = array._list_available_rest_versions()
+ array = get_array(module)
+ api_version = array.get_rest_version()
audits = []
changed = False
- if AUDIT_API_VERSION in api_version:
+ if LooseVersion(AUDIT_API_VERSION) <= LooseVersion(api_version):
changed = True
- array = get_array(module)
if not module.check_mode:
if module.params["log_type"] == "audit":
all_audits = list(
@@ -151,7 +152,7 @@ def main():
"command": all_audits[audit].command,
"subcommand": all_audits[audit].subcommand,
"user": all_audits[audit].user,
- "origin": all_audits[audit].origin.name,
+ "origin": getattr(all_audits[audit].origin, "name", None),
}
audits.append(data)
else:
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_messages.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_messages.py
index a28bd56b2..131b7971a 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_messages.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_messages.py
@@ -27,15 +27,14 @@ options:
severity:
description:
- severity of the alerts to show
- type: list
- elements: str
- choices: [ all, critical, warning, info ]
- default: [ all ]
+ type: str
+ choices: [ critical, warning, info ]
+ default: info
state:
description:
- State of alerts to show
default: open
- choices: [ all, open, closed ]
+ choices: [ open, closed ]
type: str
flagged:
description:
@@ -57,8 +56,7 @@ EXAMPLES = r"""
purefa_messages:
history: 4w
flagged : false
- severity:
- - critical
+ severity: critical
fa_url: 10.10.10.2
api_token: 89a9356f-c203-d263-8a89-c229486a13ba
"""
@@ -70,10 +68,12 @@ import time
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
- get_system,
get_array,
purefa_argument_spec,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
MIN_REQUIRED_API_VERSION = "2.2"
ALLOWED_PERIODS = ["h", "d", "w", "y"]
@@ -101,28 +101,26 @@ def main():
argument_spec = purefa_argument_spec()
argument_spec.update(
dict(
- state=dict(type="str", default="open", choices=["all", "open", "closed"]),
+ state=dict(type="str", default="open", choices=["open", "closed"]),
history=dict(type="str", default="1w"),
flagged=dict(type="bool", default=False),
severity=dict(
- type="list",
- elements="str",
- default=["all"],
- choices=["all", "critical", "warning", "info"],
+ type="str",
+ default="info",
+ choices=["critical", "warning", "info"],
),
)
)
module = AnsibleModule(argument_spec, supports_check_mode=True)
time_now = int(time.time() * 1000)
- array = get_system(module)
- api_version = array._list_available_rest_versions()
- if MIN_REQUIRED_API_VERSION not in api_version:
+ array = get_array(module)
+ api_version = array.get_rest_version()
+ if LooseVersion(MIN_REQUIRED_API_VERSION) > LooseVersion(api_version):
module.fail_json(
msg="FlashArray REST version not supported. "
"Minimum version required: {0}".format(MIN_REQUIRED_API_VERSION)
)
- array_v6 = get_array(module)
if module.params["history"][-1].lower() not in ALLOWED_PERIODS:
module.fail_json(msg="historical window value is not an allowsd time period")
since_time = str(time_now - _create_time_window(module.params["history"].lower()))
@@ -131,30 +129,12 @@ def main():
else:
flagged = " and flagged='False'"
- multi_sev = False
- if len(module.params["severity"]) > 1:
- if "all" in module.params["severity"]:
- module.params["severity"] = ["*"]
- else:
- multi_sev = True
- if multi_sev:
- severity = " and ("
- for level in range(0, len(module.params["severity"])):
- severity += "severity='" + str(module.params["severity"][level]) + "' or "
- severity = severity[0:-4] + ")"
- else:
- if module.params["severity"] == ["all"]:
- severity = " and severity='*'"
- else:
- severity = " and severity='" + str(module.params["severity"][0]) + "'"
messages = {}
- if module.params["state"] == "all":
- state = " and state='*'"
- else:
- state = " and state='" + module.params["state"] + "'"
+ severity = " and severity='" + module.params["severity"] + "'"
+ state = " and state='" + module.params["state"] + "'"
filter_string = "notified>" + since_time + state + flagged + severity
try:
- res = array_v6.get_alerts(filter=filter_string)
+ res = array.get_alerts(filter=filter_string)
alerts = list(res.items)
except Exception:
module.fail_json(
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_network.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_network.py
index e5004568a..c296707d0 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_network.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_network.py
@@ -67,6 +67,39 @@ options:
type: list
choices: [ "replication", "management", "ds", "file", "iscsi", "scsi-fc", "nvme-fc", "nvme-tcp", "nvme-roce", "system"]
version_added: '1.15.0'
+ interface:
+ description:
+ - Type of interface to create if subinterfaces is supplied
+ type: str
+ choices: [ "vif", "lacp" ]
+ version_added: '1.22.0'
+ subordinates:
+ description:
+ - List of one or more child devices to be added to a LACP interface
+ - Subordinates must be on the same controller, therefore the full device needs
+ to be provided.
+ type: list
+ elements: str
+ version_added: '1.22.0'
+ subinterfaces:
+ description:
+ - List of one or more child devices to be added to a VIF interface
+ - Only the 'eth' name needs to be provided, such as 'eth6'. This interface on
+ all controllers will be assigned to the interface.
+ type: list
+ elements: str
+ version_added: '1.22.0'
+ subnet:
+ description:
+ - Name of the subnet which interface is to be attached
+ type: str
+ version_added: '1.22.0'
+ enabled:
+ description:
+ - State of the network interface
+ type: bool
+ default: true
+ version_added: '1.22.0'
extends_documentation_fragment:
- purestorage.flasharray.purestorage.fa
"""
@@ -117,14 +150,21 @@ RETURN = """
"""
try:
- from netaddr import IPAddress, IPNetwork
+ from netaddr import IPAddress, IPNetwork, valid_ipv4, valid_ipv6
HAS_NETADDR = True
except ImportError:
HAS_NETADDR = False
try:
- from pypureclient.flasharray import NetworkInterfacePatch
+ from pypureclient.flasharray import (
+ NetworkInterfacePatch,
+ NetworkInterfacePost,
+ NetworkinterfacepostEth,
+ NetworkinterfacepatchEth,
+ FixedReferenceNoId,
+ ReferenceNoId,
+ )
HAS_PYPURECLIENT = True
except ImportError:
@@ -154,8 +194,7 @@ def _get_fc_interface(module, array):
if interface_list.status_code == 200:
interface = list(interface_list.items)[0]
return interface
- else:
- return None
+ return None
def _get_interface(module, array):
@@ -227,35 +266,107 @@ def update_fc_interface(module, array, interface, api_version):
module.exit_json(changed=changed)
+def _create_subordinates(module, array):
+ subordinates_v1 = []
+ subordinates_v2 = []
+ all_children = True
+ if module.params["subordinates"]:
+ for inter in sorted(module.params["subordinates"]):
+ if array.get_network_interfaces(names=[inter]).status_code != 200:
+ all_children = False
+ if not all_children:
+ module.fail_json(
+ msg="Subordinate {0} does not exist. Ensure you have specified the controller.".format(
+ inter
+ )
+ )
+ subordinates_v2.append(FixedReferenceNoId(name=inter))
+ subordinates_v1.append(inter)
+ return subordinates_v1, subordinates_v2
+
+
+def _create_subinterfaces(module, array):
+ subinterfaces_v1 = []
+ subinterfaces_v2 = []
+ all_children = True
+ purity_vm = bool(len(array.get_controllers().items) == 1)
+ if module.params["subinterfaces"]:
+ for inter in sorted(module.params["subinterfaces"]):
+ # As we may be on a single controller device, only check for the ct0 version of the interface
+ if array.get_network_interfaces(names=["ct0." + inter]).status_code != 200:
+ all_children = False
+ if not all_children:
+ module.fail_json(
+ msg="Child subinterface {0} does not exist".format(inter)
+ )
+ subinterfaces_v2.append(FixedReferenceNoId(name="ct0." + inter))
+ subinterfaces_v1.append("ct0." + inter)
+ if not purity_vm:
+ subinterfaces_v2.append(FixedReferenceNoId(name="ct1." + inter))
+ subinterfaces_v1.append("ct1." + inter)
+ return subinterfaces_v1, subinterfaces_v2
+
+
def update_interface(module, array, interface):
"""Modify Interface settings"""
changed = False
current_state = {
+ "enabled": interface["enabled"],
"mtu": interface["mtu"],
"gateway": interface["gateway"],
"address": interface["address"],
"netmask": interface["netmask"],
"services": sorted(interface["services"]),
+ "slaves": sorted(interface["slaves"]),
}
+ array6 = get_array(module)
+ subinterfaces = sorted(current_state["slaves"])
+ if module.params["subinterfaces"]:
+ new_subinterfaces, dummy = _create_subinterfaces(module, array6)
+ if new_subinterfaces != subinterfaces:
+ subinterfaces = new_subinterfaces
+ else:
+ subinterfaces = current_state["slaves"]
+ if module.params["subordinates"]:
+ new_subordinates, dummy = _create_subordinates(module, array6)
+ if new_subordinates != subinterfaces:
+ subinterfaces = new_subordinates
+ else:
+ subinterfaces = current_state["slaves"]
+ if module.params["enabled"] != current_state["enabled"]:
+ enabled = module.params["enabled"]
+ else:
+ enabled = current_state["enabled"]
+ if not current_state["gateway"]:
+ try:
+ if valid_ipv4(interface["address"]):
+ current_state["gateway"] = None
+ elif valid_ipv6(interface["address"]):
+ current_state["gateway"] = None
+ except AttributeError:
+ current_state["gateway"] = None
if not module.params["servicelist"]:
services = sorted(interface["services"])
else:
services = sorted(module.params["servicelist"])
if not module.params["address"]:
address = interface["address"]
+ netmask = interface["netmask"]
else:
- if module.params["gateway"]:
- if module.params["gateway"] and module.params["gateway"] not in IPNetwork(
- module.params["address"]
- ):
- module.fail_json(msg="Gateway and subnet are not compatible.")
- elif not module.params["gateway"] and interface["gateway"] not in [
- None,
- IPNetwork(module.params["address"]),
- ]:
+ if module.params["gateway"] and module.params["gateway"] not in [
+ "0.0.0.0",
+ "::",
+ ]:
+ if module.params["gateway"] not in IPNetwork(module.params["address"]):
module.fail_json(msg="Gateway and subnet are not compatible.")
+ if not module.params["gateway"] and interface["gateway"] not in [
+ None,
+ IPNetwork(module.params["address"]),
+ ]:
+ module.fail_json(msg="Gateway and subnet are not compatible.")
address = str(module.params["address"].split("/", 1)[0])
- ip_version = str(IPAddress(address).version)
+ if address in ["0.0.0.0", "::"]:
+ address = None
if not module.params["mtu"]:
mtu = interface["mtu"]
else:
@@ -268,36 +379,87 @@ def update_interface(module, array, interface):
else:
mtu = module.params["mtu"]
if module.params["address"]:
- netmask = str(IPNetwork(module.params["address"]).netmask)
+ if valid_ipv4(address):
+ netmask = str(IPNetwork(module.params["address"]).netmask)
+ else:
+ netmask = str(module.params["address"].split("/", 1)[1])
+ if netmask in ["0.0.0.0", "0"]:
+ netmask = None
else:
netmask = interface["netmask"]
if not module.params["gateway"]:
gateway = interface["gateway"]
- else:
+ elif module.params["gateway"] in ["0.0.0.0", "::"]:
+ gateway = None
+ elif valid_ipv4(address):
cidr = str(IPAddress(netmask).netmask_bits())
full_addr = address + "/" + cidr
if module.params["gateway"] not in IPNetwork(full_addr):
module.fail_json(msg="Gateway and subnet are not compatible.")
gateway = module.params["gateway"]
- if ip_version == "6":
- netmask = str(IPAddress(netmask).netmask_bits())
+ else:
+ gateway = module.params["gateway"]
+
new_state = {
+ "enabled": enabled,
"address": address,
"mtu": mtu,
"gateway": gateway,
"netmask": netmask,
- "services": services,
+ "services": sorted(services),
+ "slaves": sorted(subinterfaces),
}
+ if new_state["address"]:
+ if (
+ current_state["address"]
+ and IPAddress(new_state["address"]).version
+ != IPAddress(current_state["address"]).version
+ ):
+ if new_state["gateway"]:
+ if (
+ IPAddress(new_state["gateway"]).version
+ != IPAddress(new_state["address"]).version
+ ):
+ module.fail_json(
+ msg="Changing IP protocol requires gateway to change as well."
+ )
if new_state != current_state:
changed = True
if (
+ module.params["servicelist"]
+ and sorted(module.params["servicelist"]) != interface["services"]
+ ):
+ api_version = array._list_available_rest_versions()
+ if FC_ENABLE_API in api_version:
+ if HAS_PYPURECLIENT:
+ if not module.check_mode:
+ network = NetworkInterfacePatch(
+ services=module.params["servicelist"]
+ )
+ res = array6.patch_network_interfaces(
+ names=[module.params["name"]], network=network
+ )
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Failed to update interface service list {0}. Error: {1}".format(
+ module.params["name"], res.errors[0].message
+ )
+ )
+ else:
+ module.warn_json(
+ "Servicelist not updated as pypureclient module is required"
+ )
+ if (
"management" in interface["services"] or "app" in interface["services"]
- ) and address == "0.0.0.0/0":
+ ) and address in ["0.0.0.0/0", "::/0"]:
module.fail_json(
msg="Removing IP address from a management or app port is not supported"
)
if not module.check_mode:
try:
+ array.set_network_interface(
+ interface["name"], enabled=new_state["enabled"]
+ )
if new_state["gateway"] is not None:
array.set_network_interface(
interface["name"],
@@ -306,67 +468,200 @@ def update_interface(module, array, interface):
netmask=new_state["netmask"],
gateway=new_state["gateway"],
)
+ if (
+ current_state["slaves"] != new_state["slaves"]
+ and new_state["slaves"] != []
+ ):
+ array.set_network_interface(
+ interface["name"],
+ subinterfacelist=new_state["slaves"],
+ )
else:
+ if valid_ipv4(new_state["address"]):
+ empty_gateway = "0.0.0.0"
+ else:
+ empty_gateway = "::"
array.set_network_interface(
interface["name"],
address=new_state["address"],
mtu=new_state["mtu"],
netmask=new_state["netmask"],
+ gateway=empty_gateway,
)
+ if (
+ current_state["slaves"] != new_state["slaves"]
+ and new_state["slaves"] != []
+ ):
+ array.set_network_interface(
+ interface["name"],
+ subinterfacelist=new_state["slaves"],
+ )
except Exception:
module.fail_json(
msg="Failed to change settings for interface {0}.".format(
interface["name"]
)
)
- if not interface["enabled"] and module.params["state"] == "present":
- changed = True
- if not module.check_mode:
- try:
- array.enable_network_interface(interface["name"])
- except Exception:
+ module.exit_json(changed=changed)
+
+
+def create_interface(module, array):
+ changed = True
+ subnet_exists = bool(
+ array.get_subnets(names=[module.params["subnet"]]).status_code == 200
+ )
+ if module.params["subnet"] and not subnet_exists:
+ module.fail_json(
+ msg="Subnet {0} does not exist".format(module.params["subnet"])
+ )
+
+ if module.params["interface"] == "vif":
+ dummy, subinterfaces = _create_subinterfaces(module, array)
+ else:
+ dummy, subinterfaces = _create_subordinates(module, array)
+
+ if not module.check_mode:
+ if module.params["address"]:
+ address = str(module.params["address"].strip("[]").split("/", 1)[0])
+ if valid_ipv4(address):
+ netmask = str(IPNetwork(module.params["address"]).netmask)
+ else:
+ netmask = str(module.params["address"].strip("[]").split("/", 1)[1])
+ else:
+ netmask = None
+ address = None
+ if module.params["gateway"]:
+ gateway = str(module.params["gateway"].strip("[]"))
+ if gateway not in ["0.0.0.0", "::"]:
+ if address and gateway not in IPNetwork(module.params["address"]):
+ module.fail_json(msg="Gateway and subnet are not compatible.")
+ else:
+ gateway = None
+ if module.params["interface"] == "vif":
+ res = array.post_network_interfaces(
+ names=[module.params["name"]],
+ network=NetworkInterfacePost(
+ eth=NetworkinterfacepostEth(subtype="vif")
+ ),
+ )
+ else:
+ res = array.post_network_interfaces(
+ names=[module.params["name"]],
+ network=NetworkInterfacePost(
+ eth=NetworkinterfacepostEth(
+ subtype="lacpbond", subinterfaces=subinterfaces
+ ),
+ ),
+ )
+
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Failed to create interface {0}. Error: {1}".format(
+ module.params["name"], res.errors[0].message
+ )
+ )
+
+ if module.params["subinterfaces"] and module.params["subnet"]:
+ res = array.patch_network_interfaces(
+ names=[module.params["name"]],
+ network=NetworkInterfacePatch(
+ enabled=module.params["enabled"],
+ eth=NetworkinterfacepatchEth(
+ subinterfaces=subinterfaces,
+ address=address,
+ gateway=gateway,
+ mtu=module.params["mtu"],
+ netmask=netmask,
+ subnet=ReferenceNoId(name=module.params["subnet"]),
+ ),
+ ),
+ )
+ if res.status_code != 200:
+ array.delete_network_interfaces(names=[module.params["name"]])
module.fail_json(
- msg="Failed to enable interface {0}.".format(interface["name"])
+ msg="Failed to create interface {0}. Error: {1}".format(
+ module.params["name"], res.errors[0].message
+ )
)
- if interface["enabled"] and module.params["state"] == "absent":
- changed = True
- if not module.check_mode:
- try:
- array.disable_network_interface(interface["name"])
- except Exception:
+ elif module.params["subinterfaces"] and not module.params["subnet"]:
+ res = array.patch_network_interfaces(
+ names=[module.params["name"]],
+ network=NetworkInterfacePatch(
+ enabled=module.params["enabled"],
+ eth=NetworkinterfacepatchEth(
+ subinterfaces=subinterfaces,
+ address=address,
+ gateway=gateway,
+ mtu=module.params["mtu"],
+ netmask=netmask,
+ ),
+ ),
+ )
+ if res.status_code != 200:
+ array.delete_network_interfaces(names=[module.params["name"]])
module.fail_json(
- msg="Failed to disable interface {0}.".format(interface["name"])
+ msg="Failed to create interface {0}. Error: {1}".format(
+ module.params["name"], res.errors[0].message
+ )
)
- if (
- module.params["servicelist"]
- and sorted(module.params["servicelist"]) != interface["services"]
- ):
- api_version = array._list_available_rest_versions()
- if FC_ENABLE_API in api_version:
- if HAS_PYPURECLIENT:
- array = get_array(module)
- changed = True
- if not module.check_mode:
- network = NetworkInterfacePatch(
- services=module.params["servicelist"]
+ elif not module.params["subinterfaces"] and module.params["subnet"]:
+ res = array.patch_network_interfaces(
+ names=[module.params["name"]],
+ network=NetworkInterfacePatch(
+ enabled=module.params["enabled"],
+ eth=NetworkinterfacepatchEth(
+ address=address,
+ gateway=gateway,
+ mtu=module.params["mtu"],
+ netmask=netmask,
+ subnet=ReferenceNoId(name=module.params["subnet"]),
+ ),
+ ),
+ )
+ if res.status_code != 200:
+ array.delete_network_interfaces(names=[module.params["name"]])
+ module.fail_json(
+ msg="Failed to create interface {0}. Error: {1}".format(
+ module.params["name"], res.errors[0].message
)
- res = array.patch_network_interfaces(
- names=[module.params["name"]], network=network
+ )
+ else:
+ res = array.patch_network_interfaces(
+ names=[module.params["name"]],
+ network=NetworkInterfacePatch(
+ enabled=module.params["enabled"],
+ eth=NetworkinterfacepatchEth(
+ address=address,
+ gateway=gateway,
+ mtu=module.params["mtu"],
+ netmask=netmask,
+ ),
+ ),
+ )
+ if res.status_code != 200:
+ array.delete_network_interfaces(names=[module.params["name"]])
+ module.fail_json(
+ msg="Failed to create interface {0}. Error: {1}".format(
+ module.params["name"], res.errors[0].message
)
- if res.status_code != 200:
- module.fail_json(
- msg="Failed to update interface service list {0}. Error: {1}".format(
- module.params["name"], res.errors[0].message
- )
- )
- else:
- module.warn_json(
- "Servicelist not update as pypureclient module is required"
)
module.exit_json(changed=changed)
+def delete_interface(module, array):
+ changed = True
+ if not module.check_mode:
+ res = array.delete_network_interfaces(names=[module.params["name"]])
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Failed to delete network interface {0}. Error: {1}".format(
+ module.params["name"], res.errors[0].message
+ )
+ )
+ module.exit_json(changed=changed)
+
+
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(
@@ -392,15 +687,34 @@ def main():
"system",
],
),
+ interface=dict(type="str", choices=["vif", "lacp"]),
+ subinterfaces=dict(type="list", elements="str"),
+ subordinates=dict(type="list", elements="str"),
+ subnet=dict(type="str"),
+ enabled=dict(type="bool", default=True),
)
)
module = AnsibleModule(argument_spec, supports_check_mode=True)
+ if module.params["state"] == "present":
+ if module.params["interface"] == "lacp" and not module.params["subordinates"]:
+ module.fail_json(
+ msg="interface is lacp but all of the following are missing: subordinates"
+ )
+
+ creating_new_if = bool(module.params["interface"])
+
if not HAS_NETADDR:
module.fail_json(msg="netaddr module is required")
array = get_system(module)
+ if module.params["address"]:
+ module.params["address"] = module.params["address"].strip("[]")
+ if module.params["gateway"]:
+ module.params["gateway"] = module.params["gateway"].strip("[]")
+ if "/" not in module.params["address"]:
+ module.fail_json(msg="address must include valid netmask bits")
api_version = array._list_available_rest_versions()
if not _is_cbs(array):
if module.params["servicelist"] and "system" in module.params["servicelist"]:
@@ -424,11 +738,29 @@ def main():
else:
update_interface(module, array, interface)
else:
+ if (module.params["interface"] == "vif" and module.params["subordinates"]) or (
+ module.params["interface"] == "lacp" and module.params["subinterfaces"]
+ ):
+ module.fail_json(
+ msg="interface type not compatable with provided subinterfaces | subordinates"
+ )
interface = _get_interface(module, array)
- if not interface:
- module.fail_json(msg="Invalid network interface specified.")
- else:
+ array6 = get_array(module)
+ if not creating_new_if:
+ if not interface:
+ module.fail_json(msg="Invalid network interface specified.")
+ elif module.params["state"] == "present":
+ update_interface(module, array, interface)
+ else:
+ delete_interface(module, array6)
+ elif not interface and module.params["state"] == "present":
+ create_interface(module, array6)
+ elif interface and module.params["state"] == "absent":
+ delete_interface(module, array6)
+ elif module.params["state"] == "present":
update_interface(module, array, interface)
+ else:
+ module.exit_json(changed=False)
module.exit_json(changed=False)
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_ntp.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_ntp.py
index e2a5c8f18..348d1fed8 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_ntp.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_ntp.py
@@ -40,6 +40,15 @@ options:
- If more than 4 servers are provided, only the first 4 unique
nameservers will be used.
- if no servers are given a default of I(0.pool.ntp.org) will be used.
+ ntp_key:
+ type: str
+ description:
+ - The NTP symmetric key to be used for NTP authentication.
+ - If it is an ASCII string, it cannot contain the character "#"
+ and cannot be longer than 20 characters.
+ - If it is a hex-encoded string, it cannot be longer than 64 characters.
+ - Setting this parameter is not idempotent.
+ version_added: "1.22.0"
extends_documentation_fragment:
- purestorage.flasharray.purestorage.fa
"""
@@ -68,14 +77,26 @@ RETURN = r"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
- get_system,
+ get_array,
purefa_argument_spec,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
+
+HAS_PURESTORAGE = True
+try:
+ from pypureclient.flasharray import Arrays
+except ImportError:
+ HAS_PURESTORAGE = False
+
+
+KEY_API_VERSION = "2.26"
def _is_cbs(array, is_cbs=False):
"""Is the selected array a Cloud Block Store"""
- model = array.get(controllers=True)[0]["model"]
+ model = list(array.get_controllers().items)[0].model
is_cbs = bool("CBS" in model)
return is_cbs
@@ -94,7 +115,7 @@ def delete_ntp(module, array):
changed = True
if not module.check_mode:
try:
- array.set(ntpserver=[])
+ array.patch_arrays(array=Arrays(ntp_servers=[]))
except Exception:
module.fail_json(msg="Deletion of NTP servers failed")
else:
@@ -109,41 +130,85 @@ def create_ntp(module, array):
if not module.params["ntp_servers"]:
module.params["ntp_servers"] = ["0.pool.ntp.org"]
try:
- array.set(ntpserver=module.params["ntp_servers"][0:4])
+ array.patch_arrays(
+ array=Arrays(ntp_servers=module.params["ntp_servers"][0:4])
+ )
except Exception:
module.fail_json(msg="Update of NTP servers failed")
module.exit_json(changed=changed)
+def update_ntp_key(module, array):
+ """Update NTP Symmetric Key"""
+ if module.params["ntp_key"] == "" and not getattr(
+ list(array.get_arrays().items)[0], "ntp_symmetric_key", None
+ ):
+ changed = False
+ else:
+ try:
+ int(module.params["ntp_key"], 16)
+ if len(module.params["ntp_key"]) > 64:
+ module.fail_json(msg="HEX string cannot be longer than 64 characters")
+ except ValueError:
+ if len(module.params["ntp_key"]) > 20:
+ module.fail_json(msg="ASCII string cannot be longer than 20 characters")
+ if "#" in module.params["ntp_key"]:
+ module.fail_json(msg="ASCII string cannot contain # character")
+ if not all(ord(c) < 128 for c in module.params["ntp_key"]):
+ module.fail_json(msg="NTP key is non-ASCII")
+ changed = True
+ res = array.patch_arrays(
+ array=Arrays(ntp_symmetric_key=module.params["ntp_key"])
+ )
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Failed to update NTP Symmetric Key. Error: {0}".format(
+ res.errors[0].message
+ )
+ )
+ module.exit_json(changed=changed)
+
+ if len(module.params["ntp_key"]) > 20:
+ # Must be HEX string is greter than 20 characters
+ try:
+ int(module.params["ntp_key"], 16)
+ except ValueError:
+ module.fail_json(msg="NTP key is not HEX")
+
+
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(
dict(
ntp_servers=dict(type="list", elements="str"),
+ ntp_key=dict(type="str", no_log=True),
state=dict(type="str", default="present", choices=["absent", "present"]),
)
)
- required_if = [["state", "present", ["ntp_servers"]]]
-
- module = AnsibleModule(
- argument_spec, required_if=required_if, supports_check_mode=True
- )
+ module = AnsibleModule(argument_spec, supports_check_mode=True)
+ if not HAS_PURESTORAGE:
+ module.fail_json(msg="py-pure-client sdk is required for this module")
- array = get_system(module)
+ array = get_array(module)
+ api_version = array.get_rest_version()
if _is_cbs(array):
module.warn("NTP settings are not necessary for a CBS array - ignoring...")
module.exit_json(changed=False)
if module.params["state"] == "absent":
delete_ntp(module, array)
- else:
+ elif module.params["ntp_servers"]:
module.params["ntp_servers"] = remove(module.params["ntp_servers"])
- if sorted(array.get(ntpserver=True)["ntpserver"]) != sorted(
+ if sorted(list(array.get_arrays().items)[0].ntp_servers) != sorted(
module.params["ntp_servers"][0:4]
):
create_ntp(module, array)
-
+ if module.params["ntp_key"] or module.params["ntp_key"] == "":
+ if LooseVersion(KEY_API_VERSION) > LooseVersion(api_version):
+ module.fail_json(msg="REST API does not support setting NTP Symmetric Key")
+ else:
+ update_ntp_key(module, array)
module.exit_json(changed=False)
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_offload.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_offload.py
index 1265911fe..5b911a24d 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_offload.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_offload.py
@@ -40,6 +40,7 @@ options:
protocol:
description:
- Define which protocol the offload engine uses
+ - NFS is not a supported protocl from Purity//FA 6.6.0 and higher
default: nfs
choices: [ nfs, s3, azure, gcp ]
type: str
@@ -91,7 +92,14 @@ options:
type: str
choices: ['retention-based', 'aws-standard-class']
default: retention-based
-
+ profile:
+ description:
+ - The Offload target profile that will be selected for this target.
+ - This option allows more granular configuration for the target on top
+ of the protocol parameter
+ type: str
+ version_added: '1.21.0'
+ choices: ['azure', 'gcp', 'nfs', 'nfs-flashblade', 's3-aws', 's3-flashblade', 's3-scality-ring', 's3-wasabi-pay-as-you-go', 's3-wasabi-rcs', 's3-other']
extends_documentation_fragment:
- purestorage.flasharray.purestorage.fa
"""
@@ -143,137 +151,129 @@ RETURN = r"""
HAS_PURESTORAGE = True
try:
- from pypureclient import flasharray
+ from pypureclient.flasharray import (
+ OffloadAzure,
+ OffloadGoogleCloud,
+ OffloadNfs,
+ OffloadPost,
+ OffloadS3,
+ )
except ImportError:
HAS_PURESTORAGE = False
-HAS_PACKAGING = True
-try:
- from packaging import version
-except ImportError:
- HAS_PACKAGING = False
-
import re
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
get_array,
- get_system,
purefa_argument_spec,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
-MIN_REQUIRED_API_VERSION = "1.16"
REGEX_TARGET_NAME = re.compile(r"^[a-zA-Z0-9\-]*$")
-P53_API_VERSION = "1.17"
-GCP_API_VERSION = "2.3"
-MULTIOFFLOAD_API_VERSION = "2.11"
-MULTIOFFLOAD_LIMIT = 5
+MULTIOFFLOAD_LIMIT = 1
+PROFILE_API_VERSION = "2.25"
+NO_SNAP2NFS_VERSION = "2.27"
def get_target(module, array):
"""Return target or None"""
- try:
- return array.get_offload(module.params["name"])
- except Exception:
+ res = array.get_offloads(names=[module.params["name"]])
+ if res.status_code == 200:
+ return list(res.items)[0]
+ else:
return None
def create_offload(module, array):
"""Create offload target"""
changed = True
- api_version = array._list_available_rest_versions()
+ api_version = array.get_rest_version()
# First check if the offload network inteface is there and enabled
- try:
- if not array.get_network_interface("@offload.data")["enabled"]:
- module.fail_json(
- msg="Offload Network interface not enabled. Please resolve."
- )
- except Exception:
+ res = array.get_network_interfaces(names=["@offload.data0"])
+ if res.status != 200:
+ module.fail_json(msg="Offload Network interface doesn't exist. Please resolve.")
+ if not list(res.items)[0].enabled:
module.fail_json(
msg="Offload Network interface not correctly configured. Please resolve."
)
if not module.check_mode:
- if module.params["protocol"] == "nfs":
- try:
- array.connect_nfs_offload(
- module.params["name"],
- mount_point=module.params["share"],
- address=module.params["address"],
- mount_options=module.params["options"],
+ if module.params["protocol"] == "gcp":
+ if PROFILE_API_VERSION in api_version and module.params["profile"]:
+ bucket = OffloadGoogleCloud(
+ access_key_id=module.params["access_key"],
+ bucket=module.params["bucket"],
+ secret_access_key=module.params["secret"],
+ profile=module.params["profile"],
)
- except Exception:
- module.fail_json(
- msg="Failed to create NFS offload {0}. "
- "Please perform diagnostic checks.".format(module.params["name"])
+ else:
+ bucket = OffloadGoogleCloud(
+ access_key_id=module.params["access_key"],
+ bucket=module.params["bucket"],
+ secret_access_key=module.params["secret"],
+ )
+ offload = OffloadPost(google_cloud=bucket)
+ if module.params["protocol"] == "azure" and module.params["profile"]:
+ if PROFILE_API_VERSION in api_version:
+ bucket = OffloadAzure(
+ container_name=module.params["container"],
+ secret_access_key=module.params["secret"],
+ account_name=module.params[".bucket"],
+ profile=module.params["profile"],
)
- if module.params["protocol"] == "s3":
- if P53_API_VERSION in api_version:
- try:
- array.connect_s3_offload(
- module.params["name"],
- access_key_id=module.params["access_key"],
- secret_access_key=module.params["secret"],
- bucket=module.params["bucket"],
- placement_strategy=module.params["placement"],
- initialize=module.params["initialize"],
- )
- except Exception:
- module.fail_json(
- msg="Failed to create S3 offload {0}. "
- "Please perform diagnostic checks.".format(
- module.params["name"]
- )
- )
else:
- try:
- array.connect_s3_offload(
- module.params["name"],
- access_key_id=module.params["access_key"],
- secret_access_key=module.params["secret"],
- bucket=module.params["bucket"],
- initialize=module.params["initialize"],
- )
- except Exception:
- module.fail_json(
- msg="Failed to create S3 offload {0}. "
- "Please perform diagnostic checks.".format(
- module.params["name"]
- )
- )
- if module.params["protocol"] == "azure" and P53_API_VERSION in api_version:
- try:
- array.connect_azure_offload(
- module.params["name"],
+ bucket = OffloadAzure(
container_name=module.params["container"],
secret_access_key=module.params["secret"],
account_name=module.params[".bucket"],
- initialize=module.params["initialize"],
)
- except Exception:
- module.fail_json(
- msg="Failed to create Azure offload {0}. "
- "Please perform diagnostic checks.".format(module.params["name"])
+ offload = OffloadPost(azure=bucket)
+ if module.params["protocol"] == "s3" and module.params["profile"]:
+ if PROFILE_API_VERSION in api_version:
+ bucket = OffloadS3(
+ access_key_id=module.params["access_key"],
+ bucket=module.params["bucket"],
+ secret_access_key=module.params["secret"],
+ profile=module.params["profile"],
)
- if module.params["protocol"] == "gcp" and GCP_API_VERSION in api_version:
- arrayv6 = get_array(module)
- bucket = flasharray.OffloadGoogleCloud(
- access_key_id=module.params["access_key"],
- bucket=module.params["bucket"],
- secret_access_key=module.params["secret"],
- )
- offload = flasharray.OffloadPost(google_cloud=bucket)
- res = arrayv6.post_offloads(
- offload=offload,
- initialize=module.params["initialize"],
- names=[module.params["name"]],
- )
- if res.status_code != 200:
- module.fail_json(
- msg="Failed to create GCP offload {0}. Error: {1}"
- "Please perform diagnostic checks.".format(
- module.params["name"], res.errors[0].message
- )
+ else:
+ bucket = OffloadS3(
+ access_key_id=module.params["access_key"],
+ bucket=module.params["bucket"],
+ secret_access_key=module.params["secret"],
)
+ offload = OffloadPost(s3=bucket)
+ if module.params["protocol"] == "nfs" and module.params["profile"]:
+ if PROFILE_API_VERSION in api_version:
+ bucket = OffloadNfs(
+ mount_point=module.params["share"],
+ address=module.params["address"],
+ mount_options=module.params["options"],
+ profile=module.params["profile"],
+ )
+ else:
+ bucket = OffloadNfs(
+ mount_point=module.params["share"],
+ address=module.params["address"],
+ mount_options=module.params["options"],
+ )
+ offload = OffloadPost(nfs=bucket)
+ res = array.post_offloads(
+ offload=offload,
+ initialize=module.params["initialize"],
+ names=[module.params["name"]],
+ )
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Failed to create {0} offload {1}. Error: {2}"
+ "Please perform diagnostic checks.".format(
+ module.params["protocol"].upper(),
+ module.params["name"],
+ res.errors[0].message,
+ )
+ )
module.exit_json(changed=changed)
@@ -286,33 +286,14 @@ def update_offload(module, array):
def delete_offload(module, array):
"""Delete offload target"""
changed = True
- api_version = array._list_available_rest_versions()
if not module.check_mode:
- if module.params["protocol"] == "nfs":
- try:
- array.disconnect_nfs_offload(module.params["name"])
- except Exception:
- module.fail_json(
- msg="Failed to delete NFS offload {0}.".format(
- module.params["name"]
- )
- )
- if module.params["protocol"] == "s3":
- try:
- array.disconnect_s3_offload(module.params["name"])
- except Exception:
- module.fail_json(
- msg="Failed to delete S3 offload {0}.".format(module.params["name"])
- )
- if module.params["protocol"] == "azure" and P53_API_VERSION in api_version:
- try:
- array.disconnect_azure_offload(module.params["name"])
- except Exception:
- module.fail_json(
- msg="Failed to delete Azure offload {0}.".format(
- module.params["name"]
- )
+ res = array.delete_offloads(names=[module.params["name"]])
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Failed to delete offload {0}. Error: {1}".format(
+ module.params["name"], res.errors[0].message
)
+ )
module.exit_json(changed=changed)
@@ -329,6 +310,21 @@ def main():
default="retention-based",
choices=["retention-based", "aws-standard-class"],
),
+ profile=dict(
+ type="str",
+ choices=[
+ "azure",
+ "gcp",
+ "nfs",
+ "nfs-flashblade",
+ "s3-aws",
+ "s3-flashblade",
+ "s3-scality-ring",
+ "s3-wasabi-pay-as-you-go",
+ "s3-wasabi-rcs",
+ "s3-other",
+ ],
+ ),
name=dict(type="str", required=True),
initialize=dict(default=True, type="bool"),
access_key=dict(type="str", no_log=False),
@@ -356,19 +352,47 @@ def main():
argument_spec, required_if=required_if, supports_check_mode=True
)
- if not HAS_PACKAGING:
- module.fail_json(msg="packagingsdk is required for this module")
- if not HAS_PURESTORAGE and module.params["protocol"] == "gcp":
+ if not HAS_PURESTORAGE:
module.fail_json(msg="py-pure-client sdk is required for this module")
- array = get_system(module)
- api_version = array._list_available_rest_versions()
+ array = get_array(module)
+ api_version = array.get_rest_version()
- if MIN_REQUIRED_API_VERSION not in api_version:
+ if (
+ LooseVersion(NO_SNAP2NFS_VERSION) <= LooseVersion(api_version)
+ and module.params["protocol"] == "nfs"
+ ):
module.fail_json(
- msg="FlashArray REST version not supported. "
- "Minimum version required: {0}".format(MIN_REQUIRED_API_VERSION)
+ msg="NFS offload target is not supported from Purity//FA 6.6.0 and higher"
+ )
+ if (
+ (
+ module.params["protocol"].lower() == "azure"
+ and module.params["profile"] != "azure"
+ )
+ or (
+ module.params["protocol"].lower() == "gcp"
+ and module.params["profile"] != "gcp"
)
+ or (
+ module.params["protocol"].lower() == "nfs"
+ and module.params["profile"] not in ["nfs", "nfs-flashblade"]
+ )
+ or (
+ module.params["protocol"].lower() == "s3"
+ and module.params["profile"]
+ not in [
+ "s3-aws",
+ "s3-flashblade",
+ "s3-scality-ring",
+ "s3-wasabi-pay-as-you-go",
+ "s3-wasabi-rcs",
+ "s3-other",
+ ]
+ )
+ ):
+ module.warn("Specified profile not valid, ignoring...")
+ module.params["profile"] = None
if (
not re.match(r"^[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9]$", module.params["name"])
@@ -391,45 +415,29 @@ def main():
"and begin and end with a letter or number."
)
- apps = array.list_apps()
- app_version = 0
- all_good = False
- for app in range(0, len(apps)):
- if apps[app]["name"] == "offload":
- if (
- apps[app]["enabled"]
- and apps[app]["status"] == "healthy"
- and version.parse(apps[app]["version"]) >= version.parse("5.2.0")
- ):
- all_good = True
- app_version = apps[app]["version"]
- break
-
- if not all_good:
+ res = array.get_apps(names=["offload"])
+ if res.status_code != 200:
module.fail_json(
msg="Correct Offload app not installed or incorrectly configured"
)
else:
- if version.parse(array.get()["version"]) != version.parse(app_version):
+ app_state = list(res.items)[0]
+ if LooseVersion(app_state.version) != LooseVersion(array.get_rest_version()):
module.fail_json(
msg="Offload app version must match Purity version. Please upgrade."
)
target = get_target(module, array)
if module.params["state"] == "present" and not target:
- offloads = array.list_offload()
- target_count = len(offloads)
- if MIN_REQUIRED_API_VERSION not in api_version:
- MULTIOFFLOAD_LIMIT = 1
- if target_count >= MULTIOFFLOAD_LIMIT:
+ offloads = list(array.get_offloads().items)
+ if len(offloads) >= MULTIOFFLOAD_LIMIT:
module.fail_json(
msg="Cannot add offload target {0}. Offload Target Limit of {1} would be exceeded.".format(
module.params["name"], MULTIOFFLOAD_LIMIT
)
)
- # TODO: (SD) Remove this check when multi-protocol offloads are supported
- if offloads[0].protocol != module.params["protocol"]:
- module.fail_json(msg="Currently all offloads must be of the same type.")
+ if offloads[0].protocol != module.params["protocol"]:
+ module.fail_json(msg="Currently all offloads must be of the same type.")
create_offload(module, array)
elif module.params["state"] == "present" and target:
update_offload(module, array)
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_pg.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_pg.py
index 3fa51ebbb..3344c0895 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_pg.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_pg.py
@@ -374,68 +374,65 @@ def make_pgroup(module, array):
module.fail_json(
msg="Creation of pgroup {0} failed.".format(module.params["name"])
)
+ if not module.check_mode:
+ try:
+ if module.params["target"]:
+ array.set_pgroup(
+ module.params["name"],
+ replicate_enabled=module.params["enabled"],
+ )
+ else:
+ array.set_pgroup(
+ module.params["name"], snap_enabled=module.params["enabled"]
+ )
+ except Exception:
+ module.fail_json(
+ msg="Enabling pgroup {0} failed.".format(module.params["name"])
+ )
+ if module.params["volume"]:
try:
- if module.params["target"]:
- array.set_pgroup(
- module.params["name"],
- replicate_enabled=module.params["enabled"],
- )
- else:
- array.set_pgroup(
- module.params["name"], snap_enabled=module.params["enabled"]
- )
+ array.set_pgroup(module.params["name"], vollist=module.params["volume"])
except Exception:
module.fail_json(
- msg="Enabling pgroup {0} failed.".format(module.params["name"])
- )
- if module.params["volume"]:
- try:
- array.set_pgroup(
- module.params["name"], vollist=module.params["volume"]
- )
- except Exception:
- module.fail_json(
- msg="Adding volumes to pgroup {0} failed.".format(
- module.params["name"]
- )
- )
- if module.params["host"]:
- try:
- array.set_pgroup(
- module.params["name"], hostlist=module.params["host"]
- )
- except Exception:
- module.fail_json(
- msg="Adding hosts to pgroup {0} failed.".format(
- module.params["name"]
- )
- )
- if module.params["hostgroup"]:
- try:
- array.set_pgroup(
- module.params["name"], hgrouplist=module.params["hostgroup"]
+ msg="Adding volumes to pgroup {0} failed.".format(
+ module.params["name"]
)
- except Exception:
- module.fail_json(
- msg="Adding hostgroups to pgroup {0} failed.".format(
- module.params["name"]
- )
+ )
+ if module.params["host"]:
+ try:
+ array.set_pgroup(module.params["name"], hostlist=module.params["host"])
+ except Exception:
+ module.fail_json(
+ msg="Adding hosts to pgroup {0} failed.".format(
+ module.params["name"]
)
- if module.params["safe_mode"]:
- arrayv6 = get_array(module)
- try:
- arrayv6.patch_protection_groups(
- names=[module.params["name"]],
- protection_group=flasharray.ProtectionGroup(
- retention_lock="ratcheted"
- ),
+ )
+ if module.params["hostgroup"]:
+ try:
+ array.set_pgroup(
+ module.params["name"], hgrouplist=module.params["hostgroup"]
+ )
+ except Exception:
+ module.fail_json(
+ msg="Adding hostgroups to pgroup {0} failed.".format(
+ module.params["name"]
)
- except Exception:
- module.fail_json(
- msg="Failed to set SafeMode on pgroup {0}".format(
- module.params["name"]
- )
+ )
+ if module.params["safe_mode"]:
+ arrayv6 = get_array(module)
+ try:
+ arrayv6.patch_protection_groups(
+ names=[module.params["name"]],
+ protection_group=flasharray.ProtectionGroup(
+ retention_lock="ratcheted"
+ ),
+ )
+ except Exception:
+ module.fail_json(
+ msg="Failed to set SafeMode on pgroup {0}".format(
+ module.params["name"]
)
+ )
module.exit_json(changed=changed)
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_pgsched.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_pgsched.py
index dc0a488d4..c3ebcb09f 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_pgsched.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_pgsched.py
@@ -48,8 +48,9 @@ options:
default: true
replicate_at:
description:
- - Specifies the preferred time as HH:MM:SS, using 24-hour clock, at which to generate snapshots.
- type: int
+ - Provide a time in 12-hour AM/PM format, eg. 11AM
+ - Only valid if I(replicate_frequency) is an exact multiple of 86400, ie 1 day.
+ type: str
blackout_start:
description:
- Specifies the time at which to suspend replication.
@@ -68,9 +69,9 @@ options:
type: int
snap_at:
description:
- - Specifies the preferred time as HH:MM:SS, using 24-hour clock, at which to generate snapshots.
+ - Provide a time in 12-hour AM/PM format, eg. 11AM
- Only valid if I(snap_frequency) is an exact multiple of 86400, ie 1 day.
- type: int
+ type: str
snap_frequency:
description:
- Specifies the snapshot frequency in seconds.
@@ -120,7 +121,7 @@ EXAMPLES = r"""
schedule: snapshot
enabled: true
snap_frequency: 86400
- snap_at: 15:30:00
+ snap_at: 3PM
per_day: 5
all_for: 5
fa_url: 10.10.10.2
@@ -132,7 +133,7 @@ EXAMPLES = r"""
schedule: replication
enabled: true
replicate_frequency: 86400
- replicate_at: 15:30:00
+ replicate_at: 3PM
target_per_day: 5
target_all_for: 5
blackout_start: 2AM
@@ -217,7 +218,7 @@ def _convert_to_minutes(hour):
return (int(hour[:-2]) + 12) * 3600
-def update_schedule(module, array):
+def update_schedule(module, array, snap_time, repl_time):
"""Update Protection Group Schedule"""
changed = False
try:
@@ -260,10 +261,15 @@ def update_schedule(module, array):
else:
snap_frequency = module.params["snap_frequency"]
+ if module.params["enabled"] is None:
+ snap_enabled = current_snap["snap_enabled"]
+ else:
+ snap_enabled = module.params["enabled"]
+
if not module.params["snap_at"]:
snap_at = current_snap["snap_at"]
else:
- snap_at = module.params["snap_at"]
+ snap_at = _convert_to_minutes(module.params["snap_at"].upper())
if not module.params["days"]:
if isinstance(module.params["days"], int):
@@ -294,11 +300,12 @@ def update_schedule(module, array):
new_snap = {
"days": days,
"snap_frequency": snap_frequency,
- "snap_enabled": module.params["enabled"],
+ "snap_enabled": snap_enabled,
"snap_at": snap_at,
"per_day": per_day,
"all_for": all_for,
}
+ module.warn("current {0}; new: {1}".format(current_snap, new_snap))
if current_snap != new_snap:
changed = True
if not module.check_mode:
@@ -306,11 +313,17 @@ def update_schedule(module, array):
array.set_pgroup(
module.params["name"], snap_enabled=module.params["enabled"]
)
- array.set_pgroup(
- module.params["name"],
- snap_frequency=snap_frequency,
- snap_at=snap_at,
- )
+ if snap_time:
+ array.set_pgroup(
+ module.params["name"],
+ snap_frequency=snap_frequency,
+ snap_at=snap_at,
+ )
+ else:
+ array.set_pgroup(
+ module.params["name"],
+ snap_frequency=snap_frequency,
+ )
array.set_pgroup(
module.params["name"],
days=days,
@@ -343,10 +356,15 @@ def update_schedule(module, array):
else:
replicate_frequency = module.params["replicate_frequency"]
+ if module.params["enabled"] is None:
+ replicate_enabled = current_repl["replicate_enabled"]
+ else:
+ replicate_enabled = module.params["enabled"]
+
if not module.params["replicate_at"]:
replicate_at = current_repl["replicate_at"]
else:
- replicate_at = module.params["replicate_at"]
+ replicate_at = _convert_to_minutes(module.params["replicate_at"].upper())
if not module.params["target_days"]:
if isinstance(module.params["target_days"], int):
@@ -380,15 +398,17 @@ def update_schedule(module, array):
if not module.params["blackout_end"]:
blackout_end = current_repl["blackout_start"]
else:
- blackout_end = _convert_to_minutes(module.params["blackout_end"])
+ blackout_end = _convert_to_minutes(module.params["blackout_end"].upper())
if not module.params["blackout_start"]:
blackout_start = current_repl["blackout_start"]
else:
- blackout_start = _convert_to_minutes(module.params["blackout_start"])
+ blackout_start = _convert_to_minutes(
+ module.params["blackout_start"].upper()
+ )
new_repl = {
"replicate_frequency": replicate_frequency,
- "replicate_enabled": module.params["enabled"],
+ "replicate_enabled": replicate_enabled,
"target_days": target_days,
"replicate_at": replicate_at,
"target_per_day": target_per_day,
@@ -405,11 +425,17 @@ def update_schedule(module, array):
module.params["name"],
replicate_enabled=module.params["enabled"],
)
- array.set_pgroup(
- module.params["name"],
- replicate_frequency=replicate_frequency,
- replicate_at=replicate_at,
- )
+ if repl_time:
+ array.set_pgroup(
+ module.params["name"],
+ replicate_frequency=replicate_frequency,
+ replicate_at=replicate_at,
+ )
+ else:
+ array.set_pgroup(
+ module.params["name"],
+ replicate_frequency=replicate_frequency,
+ )
if blackout_start == 0:
array.set_pgroup(module.params["name"], replicate_blackout=None)
else:
@@ -482,8 +508,8 @@ def main():
),
blackout_start=dict(type="str"),
blackout_end=dict(type="str"),
- snap_at=dict(type="int"),
- replicate_at=dict(type="int"),
+ snap_at=dict(type="str"),
+ replicate_at=dict(type="str"),
replicate_frequency=dict(type="int"),
snap_frequency=dict(type="int"),
all_for=dict(type="int"),
@@ -506,13 +532,22 @@ def main():
array = get_system(module)
pgroup = get_pgroup(module, array)
+ repl_time = False
+ if module.params["replicate_at"] and module.params["replicate_frequency"]:
+ if not module.params["replicate_frequency"] % 86400 == 0:
+ module.fail_json(
+ msg="replicate_at not valid unless replicate frequency is measured in days, ie. a multiple of 86400"
+ )
+ repl_time = True
+ snap_time = False
if module.params["snap_at"] and module.params["snap_frequency"]:
if not module.params["snap_frequency"] % 86400 == 0:
module.fail_json(
msg="snap_at not valid unless snapshot frequency is measured in days, ie. a multiple of 86400"
)
+ snap_time = True
if pgroup and state == "present":
- update_schedule(module, array)
+ update_schedule(module, array, snap_time, repl_time)
elif pgroup and state == "absent":
delete_schedule(module, array)
elif pgroup is None:
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_pgsnap.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_pgsnap.py
index 822b0491f..4f3f6f16c 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_pgsnap.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_pgsnap.py
@@ -42,7 +42,7 @@ options:
Copy (added in 2.7) will create a full read/write clone of the
snapshot.
type: str
- choices: [ absent, present, copy ]
+ choices: [ absent, present, copy, rename ]
default: present
eradicate:
description:
@@ -64,6 +64,7 @@ options:
description:
- Volume to restore a specified volume to.
- If not supplied this will default to the volume defined in I(restore)
+ - Name of new snapshot suffix if renaming a snapshot
type: str
offload:
description:
@@ -85,6 +86,27 @@ options:
- Force immeadiate snapshot to remote targets
type: bool
default: false
+ throttle:
+ description:
+ - If set to true, allows snapshot to fail if array health is not optimal.
+ type: bool
+ default: false
+ version_added: '1.21.0'
+ with_default_protection:
+ description:
+ - Whether to add the default container protection groups to
+ those specified in I(add_to_pgs) as the inital protection
+ of a volume created from a snapshot.
+ type: bool
+ default: true
+ version_added: '1.27.0'
+ add_to_pgs:
+ description:
+ - A volume created from a snapshot will be added to the specified
+ protection groups
+ type: list
+ elements: str
+ version_added: '1.27.0'
extends_documentation_fragment:
- purestorage.flasharray.purestorage.fa
"""
@@ -114,6 +136,7 @@ EXAMPLES = r"""
restore: data
target: data2
overwrite: true
+ with_default_protection: false
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: copy
@@ -127,7 +150,7 @@ EXAMPLES = r"""
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: copy
-- name: Restore AC pod protection group snapshot pod1::pgname.snap.data to pdo1::data2
+- name: Restore AC pod protection group snapshot pod1::pgname.snap.data to pod1::data2
purestorage.flasharray.purefa_pgsnap:
name: pod1::pgname
suffix: snap
@@ -156,28 +179,53 @@ EXAMPLES = r"""
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: absent
+
+- name: Rename protection group snapshot foo.fred to foo.dave
+ purestorage.flasharray.purefa_pgsnap:
+ name: foo
+ suffix: fred
+ target: dave
+ fa_url: 10.10.10.2
+ api_token: e31060a7-21fc-e277-6240-25983c6c4592
+ state: rename
"""
RETURN = r"""
"""
+HAS_PURESTORAGE = True
+try:
+ from pypureclient.flasharray import (
+ ProtectionGroupSnapshot,
+ ProtectionGroupSnapshotPatch,
+ VolumePost,
+ Reference,
+ FixedReference,
+ DestroyedPatchPost,
+ )
+except ImportError:
+ HAS_PURESTORAGE = False
+
import re
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
- get_system,
+ get_array,
purefa_argument_spec,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
from datetime import datetime
-OFFLOAD_API = "1.16"
-POD_SNAPSHOT = "2.4"
+THROTTLE_API = "2.25"
+DEFAULT_API = "2.16"
def _check_offload(module, array):
try:
- offload = array.get_offload(module.params["offload"])
- if offload["status"] == "connected":
+ offload = list(array.get_offloads(names=[module.params["offload"]]).items)[0]
+ if offload.status == "connected":
return True
return False
except Exception:
@@ -187,7 +235,7 @@ def _check_offload(module, array):
def get_pgroup(module, array):
"""Return Protection Group or None"""
try:
- return array.get_pgroup(module.params["name"])
+ return list(array.get_protection_groups(names=[module.params["name"]]).items)[0]
except Exception:
return None
@@ -195,14 +243,71 @@ def get_pgroup(module, array):
def get_pgroupvolume(module, array):
"""Return Protection Group Volume or None"""
try:
- pgroup = array.get_pgroup(module.params["name"])
+ volumes = []
+ pgroup = list(array.get_protection_groups(names=[module.params["name"]]).items)[
+ 0
+ ]
+ if pgroup.host_count > 0: # We have a host PG
+ host_dict = list(
+ array.get_protection_groups_hosts(
+ group_names=[module.params["name"]]
+ ).items
+ )
+ for host in range(0, len(host_dict)):
+ hostvols = list(
+ array.get_connections(
+ host_names=[host_dict[host].member["name"]]
+ ).items
+ )
+ for hvol in range(0, len(hostvols)):
+ volumes.append(hostvols[hvol].volume["name"])
+ elif pgroup.host_group_count > 0: # We have a hostgroup PG
+ hgroup_dict = list(
+ array.get_protection_groups_host_groups(
+ group_names=[module.params["name"]]
+ ).items
+ )
+ hgroups = []
+ # First check if there are any volumes in the host groups
+ for hgentry in range(0, len(hgroup_dict)):
+ hgvols = list(
+ array.get_connections(
+ host_group_names=[hgroup_dict[hgentry].member["name"]]
+ ).items
+ )
+ for hgvol in range(0, len(hgvols)):
+ volumes.append(hgvols[hgvol].volume["name"])
+ # Second check for host specific volumes
+ for hgroup in range(0, len(hgroup_dict)):
+ hg_hosts = list(
+ array.get_host_groups_hosts(
+ group_names=[hgroup_dict[hgroup].member["name"]]
+ ).items
+ )
+ for hg_host in range(0, len(hg_hosts)):
+ host_vols = list(
+ array.get_connections(
+ host_names=[hg_hosts[hg_host].member["name"]]
+ ).items
+ )
+ for host_vol in range(0, len(host_vols)):
+ volumes.append(host_vols[host_vol].volume["name"])
+ else: # We have a volume PG
+ vol_dict = list(
+ array.get_protection_groups_volumes(
+ group_names=[module.params["name"]]
+ ).items
+ )
+ for entry in range(0, len(vol_dict)):
+ volumes.append(vol_dict[entry].member["name"])
+ volumes = list(set(volumes))
if "::" in module.params["name"]:
restore_volume = (
module.params["name"].split("::")[0] + "::" + module.params["restore"]
)
else:
restore_volume = module.params["restore"]
- for volume in pgroup["volumes"]:
+ for volume in volumes:
if volume == restore_volume:
return volume
except Exception:
@@ -210,7 +315,7 @@ def get_pgroupvolume(module, array):
def get_rpgsnapshot(module, array):
- """Return iReplicated Snapshot or None"""
+ """Return Replicated Snapshot or None"""
try:
snapname = (
module.params["name"]
@@ -219,83 +324,103 @@ def get_rpgsnapshot(module, array):
+ "."
+ module.params["restore"]
)
- for snap in array.list_volumes(snap=True):
- if snap["name"] == snapname:
- return snapname
- except Exception:
- return None
-
-
-def get_offload_snapshot(module, array):
- """Return Snapshot (active or deleted) or None"""
- try:
- snapname = module.params["name"] + "." + module.params["suffix"]
- for snap in array.get_pgroup(
- module.params["name"], snap=True, on=module.params["offload"]
- ):
- if snap["name"] == snapname:
- return snapname
- except Exception:
+ array.get_volume_snapshots(names=[snapname])
+ return snapname
+ except AttributeError:
return None
def get_pgsnapshot(module, array):
"""Return Snapshot (active or deleted) or None"""
- try:
- snapname = module.params["name"] + "." + module.params["suffix"]
- for snap in array.get_pgroup(module.params["name"], pending=True, snap=True):
- if snap["name"] == snapname:
- return snapname
- except Exception:
+ snapname = module.params["name"] + "." + module.params["suffix"]
+ res = array.get_protection_group_snapshots(names=[snapname])
+ if res.status_code == 200:
+ return list(res.items)[0]
+ else:
return None
def create_pgsnapshot(module, array):
"""Create Protection Group Snapshot"""
+ api_version = array.get_rest_version()
changed = True
if not module.check_mode:
- try:
+ suffix = ProtectionGroupSnapshot(suffix=module.params["suffix"])
+ if LooseVersion(THROTTLE_API) >= LooseVersion(api_version):
if (
- module.params["now"]
- and array.get_pgroup(module.params["name"])["targets"] is not None
+ list(array.get_protection_groups(names=[module.params["name"]]).items)[
+ 0
+ ].target_count
+ > 0
):
- array.create_pgroup_snapshot(
- source=module.params["name"],
- suffix=module.params["suffix"],
- snap=True,
+ if module.params["now"]:
+ res = array.post_protection_group_snapshots(
+ source_names=[module.params["name"]],
+ apply_retention=module.params["apply_retention"],
+ replicate_now=True,
+ protection_group_snapshot=suffix,
+ )
+ else:
+ res = array.post_protection_group_snapshots(
+ source_names=[module.params["name"]],
+ apply_retention=module.params["apply_retention"],
+ protection_group_snapshot=suffix,
+ replicate=module.params["remote"],
+ )
+ else:
+ res = array.post_protection_group_snapshots(
+ source_names=[module.params["name"]],
apply_retention=module.params["apply_retention"],
- replicate_now=module.params["remote"],
+ protection_group_snapshot=suffix,
)
+ else:
+ if (
+ list(array.get_protection_groups(names=[module.params["name"]]).items)[
+ 0
+ ].target_count
+ > 0
+ ):
+ if module.params["now"]:
+ res = array.post_protection_group_snapshots(
+ source_names=[module.params["name"]],
+ apply_retention=module.params["apply_retention"],
+ replicate_now=True,
+ allow_throttle=module.params["throttle"],
+ protection_group_snapshot=suffix,
+ )
+ else:
+ res = array.post_protection_group_snapshots(
+ source_names=[module.params["name"]],
+ apply_retention=module.params["apply_retention"],
+ allow_throttle=module.params["throttle"],
+ protection_group_snapshot=suffix,
+ replicate=module.params["remote"],
+ )
else:
- array.create_pgroup_snapshot(
- source=module.params["name"],
- suffix=module.params["suffix"],
- snap=True,
+ res = array.post_protection_group_snapshots(
+ source_names=[module.params["name"]],
apply_retention=module.params["apply_retention"],
+ allow_throttle=module.params["throttle"],
+ protection_group_snapshot=suffix,
)
- except Exception:
+
+ if res.status_code != 200:
module.fail_json(
- msg="Snapshot of pgroup {0} failed.".format(module.params["name"])
+ msg="Snapshot of pgroup {0} failed. Error: {1}".format(
+ module.params["name"], res.errors[0].message
+ )
)
module.exit_json(changed=changed)
def restore_pgsnapvolume(module, array):
"""Restore a Protection Group Snapshot Volume"""
- api_version = array._list_available_rest_versions()
changed = True
if module.params["suffix"] == "latest":
- all_snaps = array.get_pgroup(
- module.params["name"], snap=True, transfer=True
- ).reverse()
- for snap in all_snaps:
- if not snap["completed"]:
- latest_snap = snap["name"]
- break
- try:
- module.params["suffix"] = latest_snap.split(".")[1]
- except NameError:
- module.fail_json(msg="There is no completed snapshot available.")
+ latest_snapshot = list(
+ array.get_protection_group_snapshots(names=[module.params["name"]]).items
+ )[-1].suffix
+ module.params["suffix"] = latest_snapshot
if ":" in module.params["name"] and "::" not in module.params["name"]:
if get_rpgsnapshot(module, array) is None:
module.fail_json(
@@ -310,7 +435,7 @@ def restore_pgsnapvolume(module, array):
module.params["restore"]
)
)
- volume = (
+ source_volume = (
module.params["name"]
+ "."
+ module.params["suffix"]
@@ -324,20 +449,49 @@ def restore_pgsnapvolume(module, array):
else:
source_pod_name = ""
if source_pod_name != target_pod_name:
- if (
- len(array.get_pod(target_pod_name, mediator=True)["arrays"]) > 1
- and POD_SNAPSHOT not in api_version
- ):
+ if list(array.get_pods(names=[target_pod_name]).items)[0].array_count > 1:
module.fail_json(msg="Volume cannot be restored to a stretched pod")
if not module.check_mode:
- try:
- array.copy_volume(
- volume, module.params["target"], overwrite=module.params["overwrite"]
+ if LooseVersion(DEFAULT_API) <= LooseVersion(array.get_rest_version()):
+ if module.params["add_to_pgs"]:
+ add_to_pgs = []
+ for add_pg in range(0, len(module.params["add_to_pgs"])):
+ add_to_pgs.append(
+ FixedReference(name=module.params["add_to_pgs"][add_pg])
+ )
+ res = array.post_volumes(
+ names=[module.params["target"]],
+ volume=VolumePost(source=Reference(name=source_volume)),
+ with_default_protection=module.params["with_default_protection"],
+ add_to_protection_group_names=add_to_pgs,
+ )
+ else:
+ if module.params["overwrite"]:
+ res = array.post_volumes(
+ names=[module.params["target"]],
+ volume=VolumePost(source=Reference(name=source_volume)),
+ overwrite=module.params["overwrite"],
+ )
+ else:
+ res = array.post_volumes(
+ names=[module.params["target"]],
+ volume=VolumePost(source=Reference(name=source_volume)),
+ with_default_protection=module.params[
+ "with_default_protection"
+ ],
+ )
+ else:
+ res = array.post_volumes(
+ names=[module.params["target"]],
+ overwrite=module.params["overwrite"],
+ volume=VolumePost(source=Reference(name=source_volume)),
)
- except Exception:
+ if res.status_code != 200:
module.fail_json(
- msg="Failed to restore {0} from pgroup {1}".format(
- volume, module.params["name"]
+ msg="Failed to restore {0} from pgroup {1}. Error: {2}".format(
+ module.params["restore"],
+ module.params["name"],
+ res.errors[0].message,
)
)
module.exit_json(changed=changed)
@@ -349,23 +503,61 @@ def delete_offload_snapshot(module, array):
snapname = module.params["name"] + "." + module.params["suffix"]
if ":" in module.params["name"] and module.params["offload"]:
if _check_offload(module, array):
- changed = True
+ res = array.get_remote_protection_group_snapshots(
+ names=[snapname], on=module.params["offload"]
+ )
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Offload snapshot {0} does not exist on {1}".format(
+ snapname, module.params["offload"]
+ )
+ )
+
+ rpg_destroyed = list(res.items)[0].destroyed
if not module.check_mode:
- try:
- array.destroy_pgroup(snapname, on=module.params["offload"])
+ if not rpg_destroyed:
+ changed = True
+ res = array.patch_remote_protection_group_snapshots(
+ names=[snapname],
+ on=module.params["offload"],
+ remote_protection_group_snapshot=DestroyedPatchPost(
+ destroyed=True
+ ),
+ )
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Failed to delete offloaded snapshot {0} on target {1}. Error: {2}".format(
+ snapname,
+ module.params["offload"],
+ res.errors[0].message,
+ )
+ )
if module.params["eradicate"]:
- try:
- array.eradicate_pgroup(
- snapname, on=module.params["offload"]
+ res = array.delete_remote_protection_group_snapshots(
+ names=[snapname], on=module.params["offload"]
+ )
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Failed to eradicate offloaded snapshot {0} on target {1}. Error: {2}".format(
+ snapname,
+ module.params["offload"],
+ res.errors[0].message,
+ )
)
- except Exception:
+ else:
+ if module.params["eradicate"]:
+ changed = True
+ res = array.delete_remote_protection_group_snapshots(
+ names=[snapname], on=module.params["offload"]
+ )
+ if res.status_code != 200:
module.fail_json(
- msg="Failed to eradicate offloaded snapshot {0} on target {1}".format(
- snapname, module.params["offload"]
+ msg="Failed to eradicate offloaded snapshot {0} on target {1}. Error: {2}".format(
+ snapname,
+ module.params["offload"],
+ res.errors[0].message,
)
)
- except Exception:
- pass
else:
module.fail_json(
msg="Offload target {0} does not exist or not connected".format(
@@ -383,17 +575,58 @@ def delete_pgsnapshot(module, array):
changed = True
if not module.check_mode:
snapname = module.params["name"] + "." + module.params["suffix"]
- try:
- array.destroy_pgroup(snapname)
- if module.params["eradicate"]:
- try:
- array.eradicate_pgroup(snapname)
- except Exception:
- module.fail_json(
- msg="Failed to eradicate pgroup {0}".format(snapname)
+ res = array.patch_protection_group_snapshots(
+ names=[snapname],
+ protection_group_snapshot=ProtectionGroupSnapshotPatch(destroyed=True),
+ )
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Failed to delete pgroup {0}. Error {1}".format(
+ snapname, res.errors[0].message
+ )
+ )
+ if module.params["eradicate"]:
+ res = array.delete_protection_group_snapshots(names=[snapname])
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Failed to delete pgroup {0}. Error {1}".format(
+ snapname, res.errors[0].message
)
- except Exception:
- module.fail_json(msg="Failed to delete pgroup {0}".format(snapname))
+ )
+ module.exit_json(changed=changed)
+
+
+def eradicate_pgsnapshot(module, array):
+ """Eradicate Protection Group Snapshot"""
+ changed = True
+ if not module.check_mode:
+ snapname = module.params["name"] + "." + module.params["suffix"]
+ res = array.delete_protection_group_snapshots(names=[snapname])
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Failed to delete pgroup {0}. Error {1}".format(
+ snapname, res.errors[0].message
+ )
+ )
+ module.exit_json(changed=changed)
+
+
+def update_pgsnapshot(module, array):
+ """Update Protection Group Snapshot - basically just rename..."""
+ changed = True
+ if not module.check_mode:
+ current_name = module.params["name"] + "." + module.params["suffix"]
+ new_name = module.params["name"] + "." + module.params["target"]
+ res = array.patch_protection_group_snapshots(
+ names=[current_name],
+ protection_group_snapshot=ProtectionGroupSnapshotPatch(name=new_name),
+ )
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Failed to rename {0} to {1}. Error: {2}".format(
+ current_name, new_name, res.errors[0].message
+ )
+ )
module.exit_json(changed=changed)
@@ -405,6 +638,7 @@ def main():
suffix=dict(type="str"),
restore=dict(type="str"),
offload=dict(type="str"),
+ throttle=dict(type="bool", default=False),
overwrite=dict(type="bool", default=False),
target=dict(type="str"),
eradicate=dict(type="bool", default=False),
@@ -412,18 +646,26 @@ def main():
apply_retention=dict(type="bool", default=False),
remote=dict(type="bool", default=False),
state=dict(
- type="str", default="present", choices=["absent", "present", "copy"]
+ type="str",
+ default="present",
+ choices=["absent", "present", "copy", "rename"],
),
+ with_default_protection=dict(type="bool", default=True),
+ add_to_pgs=dict(type="list", elements="str"),
)
)
required_if = [("state", "copy", ["suffix", "restore"])]
+ mutually_exclusive = [["now", "remote"]]
module = AnsibleModule(
- argument_spec, required_if=required_if, supports_check_mode=True
+ argument_spec,
+ required_if=required_if,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
)
- pattern = re.compile("^(?=.*[a-zA-Z-])[a-zA-Z0-9]([a-zA-Z0-9-]{0,63}[a-zA-Z0-9])?$")
state = module.params["state"]
+ pattern = re.compile("^(?=.*[a-zA-Z-])[a-zA-Z0-9]([a-zA-Z0-9-]{0,63}[a-zA-Z0-9])?$")
if state == "present":
if module.params["suffix"] is None:
suffix = "snap-" + str(
@@ -431,6 +673,10 @@ def main():
)
module.params["suffix"] = suffix.replace(".", "")
else:
+ if module.params["restore"]:
+ pattern = re.compile(
+ "^[0-9]{0,63}$|^(?=.*[a-zA-Z-])[a-zA-Z0-9]([a-zA-Z0-9-]{0,63}[a-zA-Z0-9])?$"
+ )
if not pattern.match(module.params["suffix"]):
module.fail_json(
msg="Suffix name {0} does not conform to suffix name rules".format(
@@ -441,11 +687,18 @@ def main():
if not module.params["target"] and module.params["restore"]:
module.params["target"] = module.params["restore"]
- array = get_system(module)
- api_version = array._list_available_rest_versions()
- if OFFLOAD_API not in api_version and module.params["offload"]:
- module.fail_json(
- msg="Minimum version {0} required for offload support".format(OFFLOAD_API)
+ if state == "rename" and module.params["target"] is not None:
+ if not pattern.match(module.params["target"]):
+ module.fail_json(
+ msg="Suffix target {0} does not conform to suffix name rules".format(
+ module.params["target"]
+ )
+ )
+ array = get_array(module)
+ api_version = array.get_rest_version()
+ if not HAS_PURESTORAGE and module.params["throttle"]:
+ module.warn(
+ "Throttle capability disable as py-pure-client sdk is not installed"
)
pgroup = get_pgroup(module, array)
if pgroup is None:
@@ -453,24 +706,34 @@ def main():
msg="Protection Group {0} does not exist.".format(module.params["name"])
)
pgsnap = get_pgsnapshot(module, array)
+ if pgsnap:
+ pgsnap_deleted = pgsnap.destroyed
if state != "absent" and module.params["offload"]:
module.fail_json(
msg="offload parameter not supported for state {0}".format(state)
)
elif state == "copy":
+ if module.params["overwrite"] and (
+ module.params["add_to_pgs"] or module.params["with_default_protection"]
+ ):
+ module.fail_json(
+ msg="overwrite and add_to_pgs or with_default_protection are incompatable"
+ )
restore_pgsnapvolume(module, array)
elif state == "present" and not pgsnap:
create_pgsnapshot(module, array)
elif state == "present" and pgsnap:
module.exit_json(changed=False)
elif (
- state == "absent"
- and module.params["offload"]
- and get_offload_snapshot(module, array)
+ state == "absent" and module.params["offload"] and get_pgsnapshot(module, array)
):
delete_offload_snapshot(module, array)
- elif state == "absent" and pgsnap:
+ elif state == "rename" and pgsnap:
+ update_pgsnapshot(module, array)
+ elif state == "absent" and pgsnap and not pgsnap_deleted:
delete_pgsnapshot(module, array)
+ elif state == "absent" and pgsnap and pgsnap_deleted and module.params["eradicate"]:
+ eradicate_pgsnapshot(module, array)
elif state == "absent" and not pgsnap:
module.exit_json(changed=False)
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_pod.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_pod.py
index 75c4eb6c9..a41e346eb 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_pod.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_pod.py
@@ -179,38 +179,15 @@ from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa impo
get_array,
purefa_argument_spec,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.common import (
+ human_to_bytes,
+)
POD_API_VERSION = "1.13"
POD_QUOTA_VERSION = "2.23"
-def human_to_bytes(size):
- """Given a human-readable byte string (e.g. 2G, 30M),
- return the number of bytes. Will return 0 if the argument has
- unexpected form.
- """
- bytes = size[:-1]
- unit = size[-1].upper()
- if bytes.isdigit():
- bytes = int(bytes)
- if unit == "P":
- bytes *= 1125899906842624
- elif unit == "T":
- bytes *= 1099511627776
- elif unit == "G":
- bytes *= 1073741824
- elif unit == "M":
- bytes *= 1048576
- elif unit == "K":
- bytes *= 1024
- else:
- bytes = 0
- else:
- bytes = 0
- return bytes
-
-
def get_pod(module, array):
"""Return Pod or None"""
try:
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_policy.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_policy.py
index 37017e4df..7247d376f 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_policy.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_policy.py
@@ -38,7 +38,7 @@ options:
policy:
description:
- The type of policy to use
- choices: [ nfs, smb, snapshot, quota ]
+ choices: [ nfs, smb, snapshot, quota, autodir ]
required: true
type: str
enabled:
@@ -73,10 +73,18 @@ options:
choices: [ ro, rw ]
default: rw
type: str
+ nfs_version:
+ description:
+ - NFS protocol version allowed for the export
+ type: list
+ elements: str
+ choices: [ nfsv3, nfsv4 ]
+ version_added: "1.22.0"
user_mapping:
description:
- Defines if user mapping is enabled
type: bool
+ default: true
version_added: 1.14.0
snap_at:
description:
@@ -174,6 +182,21 @@ options:
type: str
default: "65534"
version_added: 1.14.0
+ security:
+ description:
+ - The security flavors to use for accessing files on a mount point.
+ - If the server does not support the requested flavor, the mount operation fails.
+ - This operation updates all rules of the specified policy.
+ type: list
+ elements: str
+ choices: [ auth_sys, krb5, krb5i, krb5p ]
+ version_added: 1.25.0
+ access_based_enumeration:
+ description:
+ - Defines if access based enumeration for SMB is enabled
+ type: bool
+ default: false
+ version_added: 1.26.0
extends_documentation_fragment:
- purestorage.flasharray.purestorage.fa
"""
@@ -332,60 +355,29 @@ try:
except ImportError:
HAS_PURESTORAGE = False
-HAS_PACKAGING = True
-try:
- from packaging import version
-except ImportError:
- HAS_PACKAGING = False
-
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
get_system,
get_array,
purefa_argument_spec,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.common import (
+ human_to_bytes,
+ convert_to_millisecs,
+)
MIN_REQUIRED_API_VERSION = "2.3"
MIN_QUOTA_API_VERSION = "2.7"
MIN_SUFFIX_API_VERSION = "2.9"
USER_MAP_VERSION = "2.15"
ALL_SQUASH_VERSION = "2.16"
-
-
-def _human_to_bytes(size):
- """Given a human-readable byte string (e.g. 2G, 30M),
- return the number of bytes. Will return 0 if the argument has
- unexpected form.
- """
- bytes = size[:-1]
- unit = size[-1].upper()
- if bytes.isdigit():
- bytes = int(bytes)
- if unit == "P":
- bytes *= 1125899906842624
- elif unit == "T":
- bytes *= 1099511627776
- elif unit == "G":
- bytes *= 1073741824
- elif unit == "M":
- bytes *= 1048576
- elif unit == "K":
- bytes *= 1024
- else:
- bytes = 0
- else:
- bytes = 0
- return bytes
-
-
-def _convert_to_millisecs(hour):
- if hour[-2:].upper() == "AM" and hour[:2] == "12":
- return 0
- elif hour[-2:].upper() == "AM":
- return int(hour[:-2]) * 3600000
- elif hour[-2:].upper() == "PM" and hour[:2] == "12":
- return 43200000
- return (int(hour[:-2]) + 12) * 3600000
+AUTODIR_VERSION = "2.24"
+NFS_VERSION = "2.26"
+SECURITY_VERSION = "2.29"
+ABE_VERSION = "2.4"
def rename_policy(module, array):
@@ -560,7 +552,7 @@ def delete_policy(module, array):
if module.params["directory"][old_dir] in dirs:
old_dirs.append(module.params["directory"][old_dir])
else:
- old_dirs = module.params["directory"]
+ pass
if old_dirs:
changed = True
for rem_dir in range(0, len(old_dirs)):
@@ -607,9 +599,53 @@ def delete_policy(module, array):
deleted.errors[0].message,
)
)
- else:
+ elif module.params["policy"] == "autodir":
+ if not module.params["directory"]:
+ res = array.delete_policies_autodir(names=[module.params["name"]])
+ if res.status_code == 200:
+ changed = True
+ else:
+ module.fail_json(
+ msg="Deletion of Autodir policy {0} failed. Error: {1}".format(
+ module.params["name"], res.errors[0].message
+ )
+ )
+ if module.params["directory"]:
+ dirs = []
+ old_dirs = []
+ current_dirs = list(
+ array.get_directories_policies_autodir(
+ policy_names=[module.params["name"]]
+ ).items
+ )
+ if current_dirs:
+ for current_dir in range(0, len(current_dirs)):
+ dirs.append(current_dirs[current_dir].member.name)
+ for old_dir in range(0, len(module.params["directory"])):
+ if module.params["directory"][old_dir] in dirs:
+ old_dirs.append(module.params["directory"][old_dir])
+ else:
+ pass
+ if old_dirs:
+ changed = True
+ for rem_dir in range(0, len(old_dirs)):
+ if not module.check_mode:
+ directory_removed = (
+ array.delete_directories_policies_autodir(
+ member_names=[old_dirs[rem_dir]],
+ policy_names=module.params["name"],
+ )
+ )
+ if directory_removed.status_code != 200:
+ module.fail_json(
+ msg="Failed to remove directory from Autodir policy {0}. Error: {1}".format(
+ module.params["name"],
+ directory_removed.errors[0].message,
+ )
+ )
+ else: # quota
if module.params["quota_limit"]:
- quota_limit = _human_to_bytes(module.params["quota_limit"])
+ quota_limit = human_to_bytes(module.params["quota_limit"])
rules = list(
array.get_policies_quota_rules(
policy_names=[module.params["name"]]
@@ -704,18 +740,7 @@ def create_policy(module, array, all_squash):
)
if created.status_code == 200:
- policy = flasharray.PolicyNfsPost(
- user_mapping_enabled=module.params["user_mapping"],
- )
- res = array.patch_policies_nfs(
- names=[module.params["name"]], policy=policy
- )
- if res.status_code != 200:
- module.fail_json(
- msg="Failed to set NFS policy {0}. Error: {1}".format(
- module.params["name"], res.errors[0].message
- )
- )
+ changed = True
if module.params["client"]:
if all_squash:
rules = flasharray.PolicyrulenfsclientpostRules(
@@ -741,7 +766,52 @@ def create_policy(module, array, all_squash):
module.params["name"], rule_created.errors[0].message
)
)
- changed = True
+ policy = flasharray.PolicyNfsPatch(
+ user_mapping_enabled=module.params["user_mapping"],
+ )
+ res = array.patch_policies_nfs(
+ names=[module.params["name"]], policy=policy
+ )
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Failed to set NFS policy user_mapping {0}. Error: {1}".format(
+ module.params["name"], res.errors[0].message
+ )
+ )
+ if (
+ LooseVersion(array.get_rest_version()) >= LooseVersion(NFS_VERSION)
+ and module.params["client"]
+ and module.params["nfs_version"]
+ ):
+ policy = flasharray.PolicyNfsPatch(
+ nfs_version=module.params["nfs_version"],
+ )
+ res = array.patch_policies_nfs(
+ names=[module.params["name"]], policy=policy
+ )
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Failed to set NFS policy version {0}. Error: {1}".format(
+ module.params["name"], res.errors[0].message
+ )
+ )
+ if (
+ LooseVersion(array.get_rest_version())
+ >= LooseVersion(SECURITY_VERSION)
+ and module.params["security"]
+ ):
+ policy = flasharray.PolicyNfsPatch(
+ security=module.params["security"],
+ )
+ res = array.patch_policies_nfs(
+ names=[module.params["name"]], policy=policy
+ )
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Failed to set NFS policy security {0}. Error: {1}".format(
+ module.params["name"], res.errors[0].message
+ )
+ )
else:
module.fail_json(
msg="Failed to create NFS policy {0}. Error: {1}".format(
@@ -754,7 +824,21 @@ def create_policy(module, array, all_squash):
policy=flasharray.PolicyPost(enabled=module.params["enabled"]),
)
if created.status_code == 200:
- changed = True
+ if LooseVersion(ABE_VERSION) <= LooseVersion(array.get_rest_version()):
+ res = array.patch_policies_smb(
+ names=[module.params["name"]],
+ policy=flasharray.PolicySmbPatch(
+ access_based_enumeration_enabled=module.params[
+ "access_based_enumeration"
+ ]
+ ),
+ )
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Failed to set SMB policy {0}. Error: {1}".format(
+ module.params["name"], res.errors[0].message
+ )
+ )
if module.params["client"]:
rules = flasharray.PolicyrulesmbclientpostRules(
anonymous_access_allowed=module.params["smb_anon_allowed"],
@@ -771,6 +855,7 @@ def create_policy(module, array, all_squash):
module.params["name"], rule_created.errors[0].message
)
)
+ changed = True
else:
module.fail_json(
msg="Failed to create SMB policy {0}. Error: {1}".format(
@@ -778,12 +863,10 @@ def create_policy(module, array, all_squash):
)
)
elif module.params["policy"] == "snapshot":
- if HAS_PACKAGING:
- suffix_enabled = version.parse(
- array.get_rest_version()
- ) >= version.parse(MIN_SUFFIX_API_VERSION)
- else:
- suffix_enabled = False
+ suffix_enabled = bool(
+ LooseVersion(array.get_rest_version())
+ >= LooseVersion(MIN_SUFFIX_API_VERSION)
+ )
created = array.post_policies_snapshot(
names=[module.params["name"]],
policy=flasharray.PolicyPost(enabled=module.params["enabled"]),
@@ -802,7 +885,7 @@ def create_policy(module, array, all_squash):
)
if suffix_enabled:
rules = flasharray.PolicyrulesnapshotpostRules(
- at=_convert_to_millisecs(module.params["snap_at"]),
+ at=convert_to_millisecs(module.params["snap_at"]),
client_name=module.params["snap_client_name"],
every=module.params["snap_every"] * 60000,
keep_for=module.params["snap_keep_for"] * 60000,
@@ -810,7 +893,7 @@ def create_policy(module, array, all_squash):
)
else:
rules = flasharray.PolicyrulesnapshotpostRules(
- at=_convert_to_millisecs(module.params["snap_at"]),
+ at=convert_to_millisecs(module.params["snap_at"]),
client_name=module.params["snap_client_name"],
every=module.params["snap_every"] * 60000,
keep_for=module.params["snap_keep_for"] * 60000,
@@ -863,7 +946,38 @@ def create_policy(module, array, all_squash):
module.params["name"], created.errors[0].message
)
)
- else:
+ elif module.params["policy"] == "autodir":
+ created = array.post_policies_autodir(
+ names=[module.params["name"]],
+ policy=flasharray.PolicyPost(enabled=module.params["enabled"]),
+ )
+ if created.status_code == 200:
+ changed = True
+ if module.params["directory"]:
+ policies = flasharray.DirectoryPolicyPost(
+ policies=[
+ flasharray.DirectorypolicypostPolicies(
+ policy=flasharray.Reference(name=module.params["name"])
+ )
+ ]
+ )
+ directory_added = array.post_directories_policies_autodir(
+ member_names=module.params["directory"], policies=policies
+ )
+ if directory_added.status_code != 200:
+ module.fail_json(
+ msg="Failed to add directory for Autodir policy {0}. Error: {1}".format(
+ module.params["name"],
+ directory_added.errors[0].message,
+ )
+ )
+ else:
+ module.fail_json(
+ msg="Failed to create Autodir policy {0}. Error: {1}".format(
+ module.params["name"], created.errors[0].message
+ )
+ )
+ else: # quota
created = array.post_policies_quota(
names=[module.params["name"]],
policy=flasharray.PolicyPost(enabled=module.params["enabled"]),
@@ -871,7 +985,7 @@ def create_policy(module, array, all_squash):
if created.status_code == 200:
changed = True
if module.params["quota_limit"]:
- quota = _human_to_bytes(module.params["quota_limit"])
+ quota = human_to_bytes(module.params["quota_limit"])
rules = flasharray.PolicyrulequotapostRules(
enforced=module.params["quota_enforced"],
quota_limit=quota,
@@ -924,16 +1038,15 @@ def create_policy(module, array, all_squash):
def update_policy(module, array, api_version, all_squash):
"""Update an existing policy including add/remove rules"""
- changed = (
- changed_dir
- ) = (
- changed_rule
- ) = changed_enable = changed_quota = changed_member = changed_user_map = False
+ changed = changed_dir = changed_rule = changed_enable = changed_quota = (
+ changed_member
+ ) = changed_user_map = changed_abe = changed_nfs = False
if module.params["policy"] == "nfs":
+ current_policy = list(
+ array.get_policies_nfs(names=[module.params["name"]]).items
+ )[0]
try:
- current_enabled = list(
- array.get_policies_nfs(names=[module.params["name"]]).items
- )[0].enabled
+ current_enabled = current_policy.enabled
if USER_MAP_VERSION in api_version:
current_user_map = list(
array.get_policies_nfs(names=[module.params["name"]]).items
@@ -944,6 +1057,23 @@ def update_policy(module, array, api_version, all_squash):
module.params["name"]
)
)
+ if module.params["nfs_version"] and sorted(
+ module.params["nfs_version"]
+ ) != sorted(getattr(current_policy, "nfs_version", [])):
+ changed_nfs = True
+ if not module.check_mode:
+ res = array.patch_policies_nfs(
+ names=[module.params["name"]],
+ policy=flasharray.PolicyNfsPatch(
+ nfs_version=module.params["nfs_version"]
+ ),
+ )
+ if res.status_code != 200:
+ module.exit_json(
+ msg="Failed to change NFS version for NFS policy {0}".format(
+ module.params["name"]
+ )
+ )
if (
module.params["user_mapping"]
and current_user_map != module.params["user_mapping"]
@@ -988,20 +1118,79 @@ def update_policy(module, array, api_version, all_squash):
rule_name = rules[rule].name
break
if not rule_name:
- if all_squash:
- rules = flasharray.PolicyrulenfsclientpostRules(
- permission=module.params["nfs_permission"],
- client=module.params["client"],
- anongid=module.params["anongid"],
- anonuid=module.params["anonuid"],
- access=module.params["nfs_access"],
- )
+ if LooseVersion(NFS_VERSION) > LooseVersion(
+ array.get_rest_version()
+ ):
+ if all_squash:
+ rules = flasharray.PolicyrulenfsclientpostRules(
+ permission=module.params["nfs_permission"],
+ client=module.params["client"],
+ anongid=module.params["anongid"],
+ anonuid=module.params["anonuid"],
+ access=module.params["nfs_access"],
+ )
+ else:
+ rules = flasharray.PolicyrulenfsclientpostRules(
+ permission=module.params["nfs_permission"],
+ client=module.params["client"],
+ access=module.params["nfs_access"],
+ nfs_version=module.params["nfs_version"],
+ )
+ elif (
+ LooseVersion(SECURITY_VERSION)
+ > LooseVersion(array.get_rest_version())
+ <= LooseVersion(NFS_VERSION)
+ ):
+ if all_squash:
+ rules = flasharray.PolicyrulenfsclientpostRules(
+ permission=module.params["nfs_permission"],
+ client=module.params["client"],
+ anongid=module.params["anongid"],
+ anonuid=module.params["anonuid"],
+ access=module.params["nfs_access"],
+ nfs_version=module.params["nfs_version"],
+ )
+ else:
+ rules = flasharray.PolicyrulenfsclientpostRules(
+ permission=module.params["nfs_permission"],
+ client=module.params["client"],
+ access=module.params["nfs_access"],
+ )
else:
- rules = flasharray.PolicyrulenfsclientpostRules(
- permission=module.params["nfs_permission"],
- client=module.params["client"],
- access=module.params["nfs_access"],
- )
+ if module.params["security"]:
+ if all_squash:
+ rules = flasharray.PolicyrulenfsclientpostRules(
+ permission=module.params["nfs_permission"],
+ client=module.params["client"],
+ anongid=module.params["anongid"],
+ anonuid=module.params["anonuid"],
+ access=module.params["nfs_access"],
+ nfs_version=module.params["nfs_version"],
+ security=module.params["security"],
+ )
+ else:
+ rules = flasharray.PolicyrulenfsclientpostRules(
+ permission=module.params["nfs_permission"],
+ client=module.params["client"],
+ access=module.params["nfs_access"],
+ security=module.params["security"],
+ )
+ else:
+ if all_squash:
+ rules = flasharray.PolicyrulenfsclientpostRules(
+ permission=module.params["nfs_permission"],
+ client=module.params["client"],
+ anongid=module.params["anongid"],
+ anonuid=module.params["anonuid"],
+ access=module.params["nfs_access"],
+ nfs_version=module.params["nfs_version"],
+ )
+ else:
+ rules = flasharray.PolicyrulenfsclientpostRules(
+ permission=module.params["nfs_permission"],
+ client=module.params["client"],
+ access=module.params["nfs_access"],
+ )
rule = flasharray.PolicyRuleNfsClientPost(rules=[rules])
changed_rule = True
if not module.check_mode:
@@ -1044,15 +1233,38 @@ def update_policy(module, array, api_version, all_squash):
)
elif module.params["policy"] == "smb":
try:
- current_enabled = list(
- array.get_policies_smb(names=[module.params["name"]]).items
- )[0].enabled
+ current = list(array.get_policies_smb(names=[module.params["name"]]).items)[
+ 0
+ ]
+ current_enabled = current.enabled
+ current_access_based_enumeration = current.access_based_enumeration_enabled
except Exception:
module.fail_json(
msg="Incorrect policy type specified for existing policy {0}".format(
module.params["name"]
)
)
+ if (
+ "access_based_enumeration" in module.params
+ and current_access_based_enumeration
+ != module.params["access_based_enumeration"]
+ ):
+ changed_abe = True
+ if not module.check_mode:
+ res = array.patch_policies_smb(
+ names=[module.params["name"]],
+ policy=flasharray.PolicySmbPatch(
+ access_based_enumeration_enabled=module.params[
+ "access_based_enumeration"
+ ]
+ ),
+ )
+ if res.status_code != 200:
+ module.exit_json(
+ msg="Failed to enable/disable Access based enueration for SMB policy {0}".format(
+ module.params["name"]
+ )
+ )
if current_enabled != module.params["enabled"]:
changed_enable = True
if not module.check_mode:
@@ -1116,12 +1328,10 @@ def update_policy(module, array, api_version, all_squash):
)
)
elif module.params["policy"] == "snapshot":
- if HAS_PACKAGING:
- suffix_enabled = version.parse(array.get_rest_version()) >= version.parse(
- MIN_SUFFIX_API_VERSION
- )
- else:
- suffix_enabled = False
+ suffix_enabled = bool(
+ LooseVersion(array.get_rest_version())
+ >= LooseVersion(MIN_SUFFIX_API_VERSION)
+ )
try:
current_enabled = list(
array.get_policies_snapshot(names=[module.params["name"]]).items
@@ -1223,7 +1433,7 @@ def update_policy(module, array, api_version, all_squash):
)
if suffix_enabled:
rules = flasharray.PolicyrulesnapshotpostRules(
- at=_convert_to_millisecs(module.params["snap_at"]),
+ at=convert_to_millisecs(module.params["snap_at"]),
client_name=module.params["snap_client_name"],
every=module.params["snap_every"] * 60000,
keep_for=module.params["snap_keep_for"] * 60000,
@@ -1231,7 +1441,7 @@ def update_policy(module, array, api_version, all_squash):
)
else:
rules = flasharray.PolicyrulesnapshotpostRules(
- at=_convert_to_millisecs(module.params["snap_at"]),
+ at=convert_to_millisecs(module.params["snap_at"]),
client_name=module.params["snap_client_name"],
every=module.params["snap_every"] * 60000,
keep_for=module.params["snap_keep_for"] * 60000,
@@ -1276,7 +1486,7 @@ def update_policy(module, array, api_version, all_squash):
)
if suffix_enabled:
rules = flasharray.PolicyrulesnapshotpostRules(
- at=_convert_to_millisecs(module.params["snap_at"]),
+ at=convert_to_millisecs(module.params["snap_at"]),
client_name=module.params["snap_client_name"],
every=module.params["snap_every"] * 60000,
keep_for=module.params["snap_keep_for"] * 60000,
@@ -1284,7 +1494,7 @@ def update_policy(module, array, api_version, all_squash):
)
else:
rules = flasharray.PolicyrulesnapshotpostRules(
- at=_convert_to_millisecs(module.params["snap_at"]),
+ at=convert_to_millisecs(module.params["snap_at"]),
client_name=module.params["snap_client_name"],
every=module.params["snap_every"] * 60000,
keep_for=module.params["snap_keep_for"] * 60000,
@@ -1317,7 +1527,69 @@ def update_policy(module, array, api_version, all_squash):
rule_created.errors[err_no].message,
)
)
- else:
+ elif module.params["policy"] == "autodir":
+ try:
+ current_enabled = list(
+ array.get_policies_autodir(names=[module.params["name"]]).items
+ )[0].enabled
+ except Exception:
+ module.fail_json(
+ msg="Incorrect policy type specified for existing policy {0}".format(
+ module.params["name"]
+ )
+ )
+ if current_enabled != module.params["enabled"]:
+ changed_enable = True
+ if not module.check_mode:
+ res = array.patch_policies_autodir(
+ names=[module.params["name"]],
+ policy=flasharray.PolicyPatch(enabled=module.params["enabled"]),
+ )
+ if res.status_code != 200:
+ module.exit_json(
+ msg="Failed to enable/disable autodir policy {0}".format(
+ module.params["name"]
+ )
+ )
+ if module.params["directory"]:
+ dirs = []
+ new_dirs = []
+ current_dirs = list(
+ array.get_directories_policies_autodir(
+ policy_names=[module.params["name"]]
+ ).items
+ )
+ if current_dirs:
+ for current_dir in range(0, len(current_dirs)):
+ dirs.append(current_dirs[current_dir].member.name)
+ for new_dir in range(0, len(module.params["directory"])):
+ if module.params["directory"][new_dir] not in dirs:
+ changed_dir = True
+ new_dirs.append(module.params["directory"][new_dir])
+ else:
+ new_dirs = module.params["directory"]
+ if new_dirs:
+ policies = flasharray.DirectoryPolicyPost(
+ policies=[
+ flasharray.DirectorypolicypostPolicies(
+ policy=flasharray.Reference(name=module.params["name"])
+ )
+ ]
+ )
+ changed_dir = True
+ for add_dir in range(0, len(new_dirs)):
+ if not module.check_mode:
+ directory_added = array.post_directories_policies_autodir(
+ member_names=[new_dirs[add_dir]], policies=policies
+ )
+ if directory_added.status_code != 200:
+ module.fail_json(
+ msg="Failed to add new directory to Autodir policy {0}. Error: {1}".format(
+ module.params["name"],
+ directory_added.errors[0].message,
+ )
+ )
+ else: # quota
current_enabled = list(
array.get_policies_quota(names=[module.params["name"]]).items
)[0].enabled
@@ -1419,7 +1691,7 @@ def update_policy(module, array, api_version, all_squash):
)
)
if module.params["quota_limit"]:
- quota = _human_to_bytes(module.params["quota_limit"])
+ quota = human_to_bytes(module.params["quota_limit"])
current_rules = list(
array.get_policies_quota_rules(
policy_names=[module.params["name"]]
@@ -1502,6 +1774,8 @@ def update_policy(module, array, api_version, all_squash):
or changed_member
or changed_dir
or changed_user_map
+ or changed_abe
+ or changed_nfs
):
changed = True
module.exit_json(changed=changed)
@@ -1519,7 +1793,9 @@ def main():
),
nfs_permission=dict(type="str", default="rw", choices=["rw", "ro"]),
policy=dict(
- type="str", required=True, choices=["nfs", "smb", "snapshot", "quota"]
+ type="str",
+ required=True,
+ choices=["nfs", "smb", "snapshot", "quota", "autodir"],
),
name=dict(type="str", required=True),
rename=dict(type="str"),
@@ -1540,8 +1816,19 @@ def main():
quota_notifications=dict(
type="list", elements="str", choices=["user", "group"]
),
- user_mapping=dict(type="bool"),
+ user_mapping=dict(type="bool", default=True),
directory=dict(type="list", elements="str"),
+ nfs_version=dict(
+ type="list",
+ elements="str",
+ choices=["nfsv3", "nfsv4"],
+ ),
+ security=dict(
+ type="list",
+ elements="str",
+ choices=["auth_sys", "krb5", "krb5i", "krb5p"],
+ ),
+ access_based_enumeration=dict(type="bool", default=False),
)
)
@@ -1565,6 +1852,11 @@ def main():
msg="FlashArray REST version not supportedi for directory quotas. "
"Minimum version required: {0}".format(MIN_QUOTA_API_VERSION)
)
+ if module.params["policy"] == "autodir" and AUTODIR_VERSION not in api_version:
+ module.fail_json(
+ msg="FlashArray REST version not supported for autodir policies. "
+ "Minimum version required: {0}".format(AUTODIR_VERSION)
+ )
array = get_array(module)
state = module.params["state"]
if module.params["quota_notifications"]:
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_proxy.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_proxy.py
index 37dd7ac6a..c97615ced 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_proxy.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_proxy.py
@@ -30,6 +30,13 @@ options:
default: present
type: str
choices: [ absent, present ]
+ protocol:
+ description:
+ - The proxy protocol.
+ choices: [http, https ]
+ default: https
+ type: str
+ version_added: '1.20.0'
host:
description:
- The proxy host name.
@@ -87,7 +94,11 @@ def create_proxy(module, array):
current_proxy = array.get(proxy=True)
if current_proxy is not None:
new_proxy = (
- "https://" + module.params["host"] + ":" + str(module.params["port"])
+ module.params["protocol"]
+ + "://"
+ + module.params["host"]
+ + ":"
+ + str(module.params["port"])
)
if new_proxy != current_proxy["proxy"]:
changed = True
@@ -105,6 +116,7 @@ def main():
argument_spec.update(
dict(
state=dict(type="str", default="present", choices=["absent", "present"]),
+ protocol=dict(type="str", default="https", choices=["http", "https"]),
host=dict(type="str"),
port=dict(type="int"),
)
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_ra.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_ra.py
index 4899b0797..19c192828 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_ra.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_ra.py
@@ -30,8 +30,8 @@ options:
- When set to I(enable) the RA port can be exposed using the
I(debug) module.
type: str
- default: enable
- choices: [ enable, disable ]
+ default: present
+ choices: [ enable, disable, absent, present ]
extends_documentation_fragment:
- purestorage.flasharray.purestorage.fa
"""
@@ -58,43 +58,75 @@ RETURN = r"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
- get_system,
+ get_array,
purefa_argument_spec,
)
+HAS_PURESTORAGE = True
+try:
+ from pypureclient.flasharray import SupportPatch
+except ImportError:
+ HAS_PURESTORAGE = False
+
def enable_ra(module, array):
"""Enable Remote Assist"""
changed = False
ra_facts = {}
- if not array.get_remote_assist_status()["status"] in ["connected", "enabled"]:
+ if not list(array.get_support().items)[0].remote_assist_status in [
+ "connected",
+ "connecting",
+ "enabled",
+ ]:
changed = True
if not module.check_mode:
- try:
- ra_data = array.enable_remote_assist()
- ra_facts["fa_ra"] = {"name": ra_data["name"], "port": ra_data["port"]}
- except Exception:
- module.fail_json(msg="Enabling Remote Assist failed")
+ res = array.patch_support(support=SupportPatch(remote_assist_active=True))
+ if res.status_code == 200:
+ ra_data = list(res.items)[0]
+ ra_facts["fa_ra"] = {
+ "name": ra_data.remote_assist_paths[0].component_name,
+ "port": None,
+ }
+ else:
+ module.fail_json(
+ msg="Enabling Remote Assist failed. Error: {0}".format(
+ res.errors[0].message
+ )
+ )
else:
- if not module.check_mode:
- try:
- ra_data = array.get_remote_assist_status()
- ra_facts["fa_ra"] = {"name": ra_data["name"], "port": ra_data["port"]}
- except Exception:
- module.fail_json(msg="Getting Remote Assist failed")
+ res = array.get_support()
+ if res.status_code == 200:
+ ra_data = list(res.items)[0]
+ ra_facts["fa_ra"] = {
+ "name": ra_data.remote_assist_paths[0].component_name,
+ "port": None,
+ }
+ else:
+ module.fail_json(
+ msg="Getting Remote Assist failed. Error: {0}".format(
+ res.errors[0].message
+ )
+ )
module.exit_json(changed=changed, ra_info=ra_facts)
def disable_ra(module, array):
"""Disable Remote Assist"""
changed = False
- if array.get_remote_assist_status()["status"] in ["connected", "enabled"]:
+ if list(array.get_support().items)[0].remote_assist_status in [
+ "connected",
+ "connecting",
+ "enabled",
+ ]:
changed = True
if not module.check_mode:
- try:
- array.disable_remote_assist()
- except Exception:
- module.fail_json(msg="Disabling Remote Assist failed")
+ res = array.patch_support(support=SupportPatch(remote_assist_active=False))
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Disabling Remote Assist failed. Error: {0}".format(
+ res.errors[0].message
+ )
+ )
module.exit_json(changed=changed)
@@ -102,15 +134,21 @@ def main():
argument_spec = purefa_argument_spec()
argument_spec.update(
dict(
- state=dict(type="str", default="enable", choices=["enable", "disable"]),
+ state=dict(
+ type="str",
+ default="present",
+ choices=["enable", "disable", "absent", "present"],
+ ),
)
)
module = AnsibleModule(argument_spec, supports_check_mode=True)
+ if not HAS_PURESTORAGE:
+ module.fail_json(msg="py-pure-client sdk is required for this module")
- array = get_system(module)
+ array = get_array(module)
- if module.params["state"] == "enable":
+ if module.params["state"] in ["enable", "present"]:
enable_ra(module, array)
else:
disable_ra(module, array)
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_saml.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_saml.py
index 9d5fc7443..3acf3f748 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_saml.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_saml.py
@@ -121,10 +121,12 @@ except ImportError:
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
- get_system,
get_array,
purefa_argument_spec,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
MIN_REQUIRED_API_VERSION = "2.11"
@@ -310,15 +312,14 @@ def main():
if not HAS_PURESTORAGE:
module.fail_json(msg="py-pure-client sdk is required for this module")
- array = get_system(module)
- api_version = array._list_available_rest_versions()
+ array = get_array(module)
+ api_version = array.get_rest_version()
- if MIN_REQUIRED_API_VERSION not in api_version:
+ if LooseVersion(MIN_REQUIRED_API_VERSION) > LooseVersion(api_version):
module.fail_json(
msg="FlashArray REST version not supported. "
"Minimum version required: {0}".format(MIN_REQUIRED_API_VERSION)
)
- array = get_array(module)
state = module.params["state"]
try:
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_smis.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_smis.py
index f752cb950..b92c65bcb 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_smis.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_smis.py
@@ -65,10 +65,12 @@ except ImportError:
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
- get_system,
get_array,
purefa_argument_spec,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
MIN_REQUIRED_API_VERSION = "2.2"
@@ -115,15 +117,14 @@ def main():
if not HAS_PURESTORAGE:
module.fail_json(msg="py-pure-client sdk is required for this module")
- array = get_system(module)
- api_version = array._list_available_rest_versions()
+ array = get_array(module)
+ api_version = array.get_rest_version()
- if MIN_REQUIRED_API_VERSION not in api_version:
+ if LooseVersion(MIN_REQUIRED_API_VERSION) > LooseVersion(api_version):
module.fail_json(
msg="FlashArray REST version not supported. "
"Minimum version required: {0}".format(MIN_REQUIRED_API_VERSION)
)
- array = get_array(module)
update_smis(module, array)
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_snap.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_snap.py
index db567a398..1b3207878 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_snap.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_snap.py
@@ -32,7 +32,6 @@ options:
suffix:
description:
- Suffix of snapshot name.
- - Not used during creation if I(offload) is provided.
type: str
target:
description:
@@ -51,7 +50,6 @@ options:
- Target can be either another FlashArray or an Offload Target
- This is only applicable for creation, deletion and eradication of snapshots
- I(state) of I(copy) is not supported.
- - I(suffix) is not supported for offload snapshots.
type: str
state:
description:
@@ -71,6 +69,12 @@ options:
- If set to false, allow destruction/eradication of snapshots not in use by replication
type: bool
default: false
+ throttle:
+ description:
+ - If set to true, allows snapshot to fail if array health is not optimal.
+ type: bool
+ default: false
+ version_added: '1.21.0'
extends_documentation_fragment:
- purestorage.flasharray.purestorage.fa
"""
@@ -173,6 +177,8 @@ from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa impo
from datetime import datetime
GET_SEND_API = "2.4"
+THROTTLE_API = "2.25"
+SNAPSHOT_SUFFIX_API = "2.28"
def _check_offload(module, array):
@@ -258,10 +264,14 @@ def get_deleted_snapshot(module, array, arrayv6):
def get_snapshot(module, array):
- """Return Snapshot or None"""
+ """Return True if snapshot exists, False otherwise"""
try:
snapname = module.params["name"] + "." + module.params["suffix"]
- for snaps in array.get_volume(module.params["name"], snap=True, pending=False):
+ name = module.params["name"]
+ if len(name.split(":")) == 2:
+ # API 1.x raises exception if name is a remote snap
+ name = module.params["name"] + "*"
+ for snaps in array.get_volume(name, snap=True, pending=False):
if snaps["name"] == snapname:
return True
except Exception:
@@ -271,12 +281,18 @@ def get_snapshot(module, array):
def create_snapshot(module, array, arrayv6):
"""Create Snapshot"""
changed = False
+ api_version = array._list_available_rest_versions()
if module.params["offload"]:
- module.params["suffix"] = None
+ if SNAPSHOT_SUFFIX_API not in api_version:
+ module.params["suffix"] = None
changed = True
if not module.check_mode:
res = arrayv6.post_remote_volume_snapshots(
- source_names=[module.params["name"]], on=module.params["offload"]
+ source_names=[module.params["name"]],
+ on=module.params["offload"],
+ remote_volume_snapshot=flasharray.RemoteVolumeSnapshotPost(
+ suffix=module.params["suffix"]
+ ),
)
if res.status_code != 200:
module.fail_json(
@@ -285,21 +301,37 @@ def create_snapshot(module, array, arrayv6):
)
)
else:
- remote_snap = list(res.items)[0].name
- module.params["suffix"] = remote_snap.split(".")[1]
+ if SNAPSHOT_SUFFIX_API not in api_version:
+ remote_snap = list(res.items)[0].name
+ module.params["suffix"] = remote_snap.split(".")[1]
else:
changed = True
if not module.check_mode:
- try:
- array.create_snapshot(
- module.params["name"], suffix=module.params["suffix"]
+ if THROTTLE_API in api_version:
+ res = arrayv6.post_volume_snapshots(
+ allow_throttle=module.params["throttle"],
+ volume_snapshot=flasharray.VolumeSnapshotPost(
+ suffix=module.params["suffix"]
+ ),
+ source_names=[module.params["name"]],
)
- except Exception:
- module.fail_json(
- msg="Failed to create snapshot for volume {0}".format(
- module.params["name"]
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Failed to create snapshot for volume {0}. Error: {1}".format(
+ module.params["name"], res.errors[0].message
+ )
+ )
+ else:
+ try:
+ array.create_snapshot(
+ module.params["name"], suffix=module.params["suffix"]
+ )
+ except Exception:
+ module.fail_json(
+ msg="Failed to create snapshot for volume {0}".format(
+ module.params["name"]
+ )
)
- )
module.exit_json(changed=changed, suffix=module.params["suffix"])
@@ -518,6 +550,7 @@ def main():
suffix=dict(type="str"),
target=dict(type="str"),
offload=dict(type="str"),
+ throttle=dict(type="bool", default=False),
ignore_repl=dict(type="bool", default=False),
overwrite=dict(type="bool", default=False),
eradicate=dict(type="bool", default=False),
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_snmp_agent.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_snmp_agent.py
index b9dc8ca94..c3ecb2e64 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_snmp_agent.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_snmp_agent.py
@@ -120,10 +120,12 @@ except ImportError:
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
- get_system,
get_array,
purefa_argument_spec,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
MIN_REQUIRED_API_VERSION = "2.1"
@@ -238,10 +240,10 @@ def main():
supports_check_mode=True,
)
- array = get_system(module)
- api_version = array._list_available_rest_versions()
+ array = get_array(module)
+ api_version = array.get_rest_version()
- if MIN_REQUIRED_API_VERSION not in api_version:
+ if LooseVersion(MIN_REQUIRED_API_VERSION) > LooseVersion(api_version):
module.fail_json(
msg="FlashArray REST version not supported. "
"Minimum version required: {0}".format(MIN_REQUIRED_API_VERSION)
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_sso.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_sso.py
index c1199215f..404a2c044 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_sso.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_sso.py
@@ -66,10 +66,12 @@ except ImportError:
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
- get_system,
get_array,
purefa_argument_spec,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
SSO_API_VERSION = "2.2"
@@ -88,11 +90,10 @@ def main():
module.fail_json(msg="py-pure-client sdk is required for this module")
state = module.params["state"]
- array = get_system(module)
- api_version = array._list_available_rest_versions()
+ array = get_array(module)
+ api_version = array.get_rest_version()
changed = False
- if SSO_API_VERSION in api_version:
- array = get_array(module)
+ if LooseVersion(SSO_API_VERSION) <= LooseVersion(api_version):
current_sso = list(array.get_admins_settings().items)[0].single_sign_on_enabled
if (state == "present" and not current_sso) or (
state == "absent" and current_sso
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_subnet.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_subnet.py
index efce8db9e..84083c522 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_subnet.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_subnet.py
@@ -97,7 +97,7 @@ RETURN = """
"""
try:
- from netaddr import IPNetwork
+ from netaddr import IPNetwork, valid_ipv4, valid_ipv6
HAS_NETADDR = True
except ImportError:
@@ -130,22 +130,38 @@ def update_subnet(module, array, subnet):
"prefix": subnet["prefix"],
"gateway": subnet["gateway"],
}
+ address = str(subnet["prefix"].split("/", 1)[0])
+ if not current_state["vlan"]:
+ current_state["vlan"] = 0
+ if not current_state["gateway"]:
+ if valid_ipv4(address):
+ current_state["gateway"] = "0.0.0.0"
+ elif valid_ipv6(address):
+ current_state["gateway"] = "::"
+ else:
+ module.fail_json(msg="Prefix address is not valid IPv4 or IPv6")
+
if not module.params["prefix"]:
prefix = subnet["prefix"]
else:
- if module.params["gateway"] and module.params["gateway"] not in IPNetwork(
- module.params["prefix"]
- ):
- module.fail_json(msg="Gateway and subnet are not compatible.")
- elif (
- not module.params["gateway"]
- and subnet["gateway"]
- and subnet["gateway"] not in IPNetwork(module.params["prefix"])
+ if module.params["gateway"] and not (
+ module.params["gateway"] in ["0.0.0.0", "::"]
):
- module.fail_json(msg="Gateway and subnet are not compatible.")
+ if module.params["gateway"] and module.params["gateway"] not in IPNetwork(
+ module.params["prefix"]
+ ):
+ module.fail_json(msg="Gateway and subnet are not compatible.")
+ elif (
+ not module.params["gateway"]
+ and subnet["gateway"]
+ and subnet["gateway"] not in IPNetwork(module.params["prefix"])
+ ):
+ module.fail_json(msg="Gateway and subnet are not compatible.")
prefix = module.params["prefix"]
if not module.params["vlan"]:
vlan = subnet["vlan"]
+ if not vlan:
+ vlan = 0
else:
if not 0 <= module.params["vlan"] <= 4094:
module.fail_json(
@@ -165,8 +181,11 @@ def update_subnet(module, array, subnet):
if not module.params["gateway"]:
gateway = subnet["gateway"]
else:
- if module.params["gateway"] not in IPNetwork(prefix):
- module.fail_json(msg="Gateway and subnet are not compatible.")
+ if module.params["gateway"] and not (
+ module.params["gateway"] in ["0.0.0.0", "::"]
+ ):
+ if module.params["gateway"] not in IPNetwork(prefix):
+ module.fail_json(msg="Gateway and subnet are not compatible.")
gateway = module.params["gateway"]
new_state = {"prefix": prefix, "mtu": mtu, "gateway": gateway, "vlan": vlan}
if new_state != current_state:
@@ -214,10 +233,13 @@ def create_subnet(module, array):
if not module.params["prefix"]:
module.fail_json(msg="Prefix required when creating subnet.")
else:
- if module.params["gateway"] and module.params["gateway"] not in IPNetwork(
- module.params["prefix"]
+ if module.params["gateway"] and not (
+ module.params["gateway"] in ["0.0.0.0", "::"]
):
- module.fail_json(msg="Gateway and subnet are not compatible.")
+ if module.params["gateway"] and module.params["gateway"] not in IPNetwork(
+ module.params["prefix"]
+ ):
+ module.fail_json(msg="Gateway and subnet are not compatible.")
prefix = module.params["prefix"]
if module.params["vlan"]:
if not 0 <= module.params["vlan"] <= 4094:
@@ -235,7 +257,7 @@ def create_subnet(module, array):
)
else:
mtu = module.params["mtu"]
- if module.params["gateway"]:
+ if module.params["gateway"] and not (module.params["gateway"] in ["0.0.0.0", "::"]):
if module.params["gateway"] not in IPNetwork(prefix):
module.fail_json(msg="Gateway and subnet are not compatible.")
gateway = module.params["gateway"]
@@ -313,6 +335,10 @@ def main():
state = module.params["state"]
array = get_system(module)
subnet = _get_subnet(module, array)
+ if module.params["prefix"]:
+ module.params["prefix"] = module.params["prefix"].strip("[]")
+ if module.params["gateway"]:
+ module.params["gateway"] = module.params["gateway"].strip("[]")
if state == "present" and not subnet:
create_subnet(module, array)
if state == "present" and subnet:
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_syslog.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_syslog.py
index adb385ca4..20b3104fe 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_syslog.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_syslog.py
@@ -21,14 +21,13 @@ version_added: '1.0.0'
short_description: Configure Pure Storage FlashArray syslog settings
description:
- Configure syslog configuration for Pure Storage FlashArrays.
-- Add or delete an individual syslog server to the existing
- list of serves.
+- Manage individual syslog servers.
author:
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
options:
state:
description:
- - Create or delete syslog servers configuration
+ - Create, update or delete syslog servers configuration
default: present
type: str
choices: [ absent, present ]
@@ -55,14 +54,14 @@ options:
description:
- A user-specified name.
The name must be locally unique and cannot be changed.
- - Only applicable with FlashArrays running Purity//FA 6.0 or higher.
type: str
+ required: true
extends_documentation_fragment:
- purestorage.flasharray.purestorage.fa
"""
EXAMPLES = r"""
-- name: Delete exisitng syslog server entries
+- name: Delete existing syslog server entry
purestorage.flasharray.purefa_syslog:
address: syslog1.com
protocol: tcp
@@ -70,13 +69,23 @@ EXAMPLES = r"""
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
-- name: Set array syslog servers
+- name: Add syslog server entry
purestorage.flasharray.purefa_syslog:
state: present
address: syslog1.com
+ port: 8081
protocol: udp
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
+
+- name: Update syslog server entry
+ purestorage.flasharray.purefa_syslog:
+ state: present
+ address: syslog1.com
+ port: 8081
+ protocol: tcp
+ fa_url: 10.10.10.2
+ api_token: e31060a7-21fc-e277-6240-25983c6c4592
"""
RETURN = r"""
@@ -85,7 +94,7 @@ RETURN = r"""
HAS_PURESTORAGE = True
try:
- from pypureclient import flasharray
+ from pypureclient.flasharray import SyslogServer
except ImportError:
HAS_PURESTORAGE = False
@@ -93,90 +102,71 @@ except ImportError:
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
get_array,
- get_system,
purefa_argument_spec,
)
-SYSLOG_NAME_API = "2.4"
-
-
def delete_syslog(module, array):
"""Delete Syslog Server"""
- changed = False
+ changed = True
+ if not module.check_mode:
+ res = array.delete_syslog_servers(names=[module.params["name"]])
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Failed to remove syslog server {0}. Error: {1}".format(
+ module.params["name"], res.errors[0].message
+ )
+ )
+ module.exit_json(changed=changed)
+
+
+def add_syslog(module, array):
+ """Add Syslog Server"""
+ changed = True
noport_address = module.params["protocol"] + "://" + module.params["address"]
if module.params["port"]:
full_address = noport_address + ":" + module.params["port"]
else:
full_address = noport_address
-
- address_list = array.get(syslogserver=True)["syslogserver"]
-
- if address_list:
- for address in range(0, len(address_list)):
- if address_list[address] == full_address:
- del address_list[address]
- changed = True
- if not module.check_mode:
- try:
- array.set(syslogserver=address_list)
- break
- except Exception:
- module.fail_json(
- msg="Failed to remove syslog server: {0}".format(
- full_address
- )
- )
-
+ if not module.check_mode:
+ res = array.post_syslog_servers(
+ names=[module.params["name"]],
+ syslog_server=SyslogServer(name=module.params["name"], uri=full_address),
+ )
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Adding syslog server {0} failed. Error: {1}".format(
+ module.params["name"], res.errors[0].message
+ )
+ )
module.exit_json(changed=changed)
-def add_syslog(module, array, arrayv6):
- """Add Syslog Server"""
+def update_syslog(module, array):
+ """Update Syslog Server"""
changed = False
+ syslog_config = list(array.get_syslog_servers(names=[module.params["name"]]).items)[
+ 0
+ ]
noport_address = module.params["protocol"] + "://" + module.params["address"]
if module.params["port"]:
full_address = noport_address + ":" + module.params["port"]
else:
full_address = noport_address
-
- address_list = array.get(syslogserver=True)["syslogserver"]
- exists = False
-
- if address_list:
- for address in range(0, len(address_list)):
- if address_list[address] == full_address:
- exists = True
- break
- if not exists:
- if arrayv6 and module.params["name"]:
- changed = True
- if not module.check_mode:
- res = arrayv6.post_syslog_servers(
- names=[module.params["name"]],
- syslog_server=flasharray.SyslogServer(
- name=module.params["name"], uri=full_address
- ),
+ if full_address != syslog_config.uri:
+ changed = True
+ res = array.patch_syslog_servers(
+ names=[module.params["name"]],
+ syslog_server=SyslogServer(uri=full_address),
+ )
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Updating syslog server {0} failed. Error: {1}".format(
+ module.params["name"], res.errors[0].message
)
- if res.status_code != 200:
- module.fail_json(
- msg="Adding syslog server {0} failed. Error: {1}".format(
- module.params["name"], res.errors[0].message
- )
- )
- else:
- changed = True
- if not module.check_mode:
- try:
- address_list.append(full_address)
- array.set(syslogserver=address_list)
- except Exception:
- module.fail_json(
- msg="Failed to add syslog server: {0}".format(full_address)
- )
-
+ )
module.exit_json(changed=changed)
@@ -187,29 +177,30 @@ def main():
address=dict(type="str", required=True),
protocol=dict(type="str", choices=["tcp", "tls", "udp"], required=True),
port=dict(type="str"),
- name=dict(type="str"),
+ name=dict(type="str", required=True),
state=dict(type="str", default="present", choices=["absent", "present"]),
)
)
module = AnsibleModule(argument_spec, supports_check_mode=True)
- array = get_system(module)
+ array = get_array(module)
- if module.params["name"] and not HAS_PURESTORAGE:
+ if not HAS_PURESTORAGE:
module.fail_json(msg="py-pure-client sdk is required for this module")
- api_version = array._list_available_rest_versions()
-
- if SYSLOG_NAME_API in api_version and module.params["name"]:
- arrayv6 = get_array(module)
+ res = array.get_syslog_servers(names=[module.params["name"]])
+ if res.status_code == 200:
+ exists = True
else:
- arrayv6 = None
+ exists = False
- if module.params["state"] == "absent":
+ if module.params["state"] == "absent" and exists:
delete_syslog(module, array)
- else:
- add_syslog(module, array, arrayv6)
+ elif module.params["state"] == "present" and not exists:
+ add_syslog(module, array)
+ elif module.params["state"] == "present" and exists:
+ update_syslog(module, array)
module.exit_json(changed=False)
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_syslog_settings.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_syslog_settings.py
index fce6dffa3..735930e08 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_syslog_settings.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_syslog_settings.py
@@ -76,10 +76,12 @@ except ImportError:
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
- get_system,
get_array,
purefa_argument_spec,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.version import (
+ LooseVersion,
+)
MIN_REQUIRED_API_VERSION = "2.9"
@@ -106,15 +108,14 @@ def main():
if not HAS_PURESTORAGE:
module.fail_json(msg="py-pure-client sdk is required for this module")
- array = get_system(module)
- api_version = array._list_available_rest_versions()
+ array = get_array(module)
+ api_version = array.get_rest_version()
- if MIN_REQUIRED_API_VERSION not in api_version:
+ if LooseVersion(MIN_REQUIRED_API_VERSION) > LooseVersion(api_version):
module.fail_json(
msg="Purity//FA version not supported. Minimum version required: 6.2.0"
)
- array = get_array(module)
changed = cert_change = False
if module.params["ca_certificate"] and len(module.params["ca_certificate"]) > 3000:
module.fail_json(msg="Certificate exceeds 3000 characters")
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_token.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_token.py
index fa66fe308..f7acb8889 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_token.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_token.py
@@ -89,13 +89,22 @@ from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
get_array,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.common import (
+ convert_time_to_millisecs,
+)
from os import environ
import platform
-VERSION = 1.0
+VERSION = 1.5
USER_AGENT_BASE = "Ansible_token"
TIMEOUT_API_VERSION = "2.2"
+HAS_DISTRO = True
+try:
+ import distro
+except ImportError:
+ HAS_DISTRO = False
+
HAS_PURESTORAGE = True
try:
from purestorage import purestorage
@@ -103,30 +112,22 @@ except ImportError:
HAS_PURESTORAGE = False
-def _convert_time_to_millisecs(timeout):
- if timeout[-1:].lower() not in ["w", "d", "h", "m", "s"]:
- return 0
- try:
- if timeout[-1:].lower() == "w":
- return int(timeout[:-1]) * 7 * 86400000
- elif timeout[-1:].lower() == "d":
- return int(timeout[:-1]) * 86400000
- elif timeout[-1:].lower() == "h":
- return int(timeout[:-1]) * 3600000
- elif timeout[-1:].lower() == "m":
- return int(timeout[:-1]) * 60000
- except Exception:
- return 0
-
-
def get_session(module):
"""Return System Object or Fail"""
- user_agent = "%(base)s %(class)s/%(version)s (%(platform)s)" % {
- "base": USER_AGENT_BASE,
- "class": __name__,
- "version": VERSION,
- "platform": platform.platform(),
- }
+ if HAS_DISTRO:
+ user_agent = "%(base)s %(class)s/%(version)s (%(platform)s)" % {
+ "base": USER_AGENT_BASE,
+ "class": __name__,
+ "version": VERSION,
+ "platform": distro.name(pretty=True),
+ }
+ else:
+ user_agent = "%(base)s %(class)s/%(version)s (%(platform)s)" % {
+ "base": USER_AGENT_BASE,
+ "class": __name__,
+ "version": VERSION,
+ "platform": platform.platform(),
+ }
array_name = module.params["fa_url"]
username = module.params["username"]
@@ -205,7 +206,7 @@ def main():
):
module.params["api_token"] = api_token
array6 = get_array(module)
- ttl = _convert_time_to_millisecs(module.params["timeout"])
+ ttl = convert_time_to_millisecs(module.params["timeout"])
if ttl != 0:
changed = True
array6.delete_admins_api_tokens(names=[username])
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_vg.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_vg.py
index febb0d5a2..3720ee7cd 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_vg.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_vg.py
@@ -99,6 +99,11 @@ options:
choices: [ 0, 10 ]
default: 0
version_added: '1.13.0'
+ rename:
+ description:
+ - Value to rename the specified volume group to
+ type: str
+ version_added: '1.22.0'
extends_documentation_fragment:
- purestorage.flasharray.purestorage.fa
"""
@@ -160,6 +165,13 @@ EXAMPLES = r"""
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: absent
+
+- name: Rename volume group foo to bar
+ purestorage.flasharray.purefa_vg:
+ name: foo
+ rename: bar
+ fa_url: 10.10.10.2
+ api_token: e31060a7-21fc-e277-6240-25983c6c4592
"""
RETURN = r"""
@@ -177,6 +189,10 @@ from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa impo
get_system,
purefa_argument_spec,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.common import (
+ human_to_bytes,
+ human_to_real,
+)
VGROUP_API_VERSION = "1.13"
@@ -185,52 +201,15 @@ MULTI_VG_VERSION = "2.2"
PRIORITY_API_VERSION = "2.11"
-def human_to_bytes(size):
- """Given a human-readable byte string (e.g. 2G, 30M),
- return the number of bytes. Will return 0 if the argument has
- unexpected form.
- """
- bytes = size[:-1]
- unit = size[-1].upper()
- if bytes.isdigit():
- bytes = int(bytes)
- if unit == "P":
- bytes *= 1125899906842624
- elif unit == "T":
- bytes *= 1099511627776
- elif unit == "G":
- bytes *= 1073741824
- elif unit == "M":
- bytes *= 1048576
- elif unit == "K":
- bytes *= 1024
- else:
- bytes = 0
- else:
- bytes = 0
- return bytes
-
-
-def human_to_real(iops):
- """Given a human-readable IOPs string (e.g. 2K, 30M),
- return the real number. Will return 0 if the argument has
- unexpected form.
- """
- digit = iops[:-1]
- unit = iops[-1].upper()
- if unit.isdigit():
- digit = iops
- elif digit.isdigit():
- digit = int(digit)
- if unit == "M":
- digit *= 1000000
- elif unit == "K":
- digit *= 1000
- else:
- digit = 0
- else:
- digit = 0
- return digit
+def rename_exists(module, array):
+ """Determine if rename target already exists"""
+ exists = False
+ new_name = module.params["rename"]
+ for vgroup in array.list_vgroups():
+ if vgroup["name"].casefold() == new_name.casefold():
+ exists = True
+ break
+ return exists
def get_multi_vgroups(module, destroyed=False):
@@ -272,6 +251,25 @@ def get_vgroup(module, array):
return vgroup
+def rename_vgroup(module, array):
+ changed = True
+ if not rename_exists(module, array):
+ try:
+ if not module.check_mode:
+ array.rename_vgroup(module.params["name"], module.params["rename"])
+ except Exception:
+ module.fail_json(
+ msg="Rename to {0} failed.".format(module.params["rename"])
+ )
+ else:
+ module.warn(
+ "Rename failed. Volume Group {0} already exists.".format(
+ module.params["rename"]
+ )
+ )
+ module.exit_json(changed=changed)
+
+
def make_vgroup(module, array):
"""Create Volume Group"""
changed = True
@@ -630,6 +628,7 @@ def main():
priority_operator=dict(type="str", choices=["+", "-"], default="+"),
priority_value=dict(type="int", choices=[0, 10], default=0),
eradicate=dict(type="bool", default=False),
+ rename=dict(type="str"),
)
)
@@ -673,6 +672,8 @@ def main():
eradicate_vgroup(module, array)
elif not vgroup and not xvgroup and state == "present":
make_vgroup(module, array)
+ elif state == "present" and vgroup and module.params["rename"] and not xvgroup:
+ rename_vgroup(module, array)
elif vgroup and state == "present":
update_vgroup(module, array)
elif vgroup is None and state == "absent":
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_vnc.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_vnc.py
index 48e154c77..f9dd627a3 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_vnc.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_vnc.py
@@ -32,7 +32,7 @@ options:
choices: [ present, absent ]
name:
description:
- - Name od app
+ - Name of app
type: str
required: true
extends_documentation_fragment:
@@ -80,44 +80,65 @@ vnc:
type: str
"""
+HAS_PURESTORAGE = True
+try:
+ from pypureclient.flasharray import App
+except ImportError:
+ HAS_PURESTORAGE = False
+
+
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
- get_system,
+ get_array,
purefa_argument_spec,
)
-MIN_REQUIRED_API_VERSION = "1.17"
-
def enable_vnc(module, array, app):
"""Enable VNC port"""
changed = False
vnc_fact = []
- if not app["vnc_enabled"]:
- try:
- if not module.check_mode:
- array.enable_app_vnc(module.params["name"])
- vnc_fact = array.get_app_node(module.params["name"])
- changed = True
- except Exception:
- module.fail_json(
- msg="Enabling VNC for {0} failed".format(module.params["name"])
+ if not app.vnc_enabled:
+ changed = True
+ if not module.check_mode:
+ res = array.patch_apps(
+ names=[module.params["name"]], app=App(vnc_enabled=True)
)
+ if res.status_code == 200:
+ vnc_nodes = list(
+ array.get_apps_nodes(app_names=[module.params["name"]]).items
+ )[0]
+ vnc_fact = {
+ "status": vnc_nodes.status,
+ "index": vnc_nodes.index,
+ "version": vnc_nodes.version,
+ "vnc": vnc_nodes.vnc,
+ "name": module.params["name"],
+ }
+ else:
+ module.fail_json(
+ msg="Enabling VNC for {0} failed. Error: {1}".format(
+ module.params["name"], res.errors[0].message
+ )
+ )
module.exit_json(changed=changed, vnc=vnc_fact)
def disable_vnc(module, array, app):
"""Disable VNC port"""
changed = False
- if app["vnc_enabled"]:
- try:
- if not module.check_mode:
- array.disable_app_vnc(module.params["name"])
- changed = True
- except Exception:
- module.fail_json(
- msg="Disabling VNC for {0} failed".format(module.params["name"])
+ if app.vnc_enabled:
+ changed = True
+ if not module.check_mode:
+ res = array.patch_apps(
+ names=[module.params["name"]], app=App(vnc_enabled=False)
)
+ if res.status_code != 200:
+ module.fail_json(
+ msg="Disabling VNC for {0} failed. Error: {1}".format(
+ module.params["name"], res.errors[0].message
+ )
+ )
module.exit_json(changed=changed)
@@ -132,21 +153,18 @@ def main():
module = AnsibleModule(argument_spec, supports_check_mode=True)
- array = get_system(module)
- api_version = array._list_available_rest_versions()
+ if not HAS_PURESTORAGE:
+ module.fail_json(msg="py-pure-client sdk is required for this module")
- if MIN_REQUIRED_API_VERSION not in api_version:
- module.fail_json(
- msg="FlashArray REST version not supported. "
- "Minimum version required: {0}".format(MIN_REQUIRED_API_VERSION)
- )
- try:
- app = array.get_app(module.params["name"])
- except Exception:
+ array = get_array(module)
+
+ res = array.get_apps(names=[module.params["name"]])
+ if res.status_code != 200:
module.fail_json(
msg="Selected application {0} does not exist".format(module.params["name"])
)
- if not app["enabled"]:
+ app = list(res.items)[0]
+ if not app.enabled:
module.fail_json(
msg="Application {0} is not enabled".format(module.params["name"])
)
diff --git a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_volume.py b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_volume.py
index c3c92f6d4..877af7f74 100644
--- a/ansible_collections/purestorage/flasharray/plugins/modules/purefa_volume.py
+++ b/ansible_collections/purestorage/flasharray/plugins/modules/purefa_volume.py
@@ -86,6 +86,7 @@ options:
See associated descriptions
- Only supported from Purity//FA v6.0.0 and higher
type: str
+ default: ""
bw_qos:
description:
- Bandwidth limit for volume in M or G units.
@@ -326,6 +327,10 @@ from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa impo
get_system,
purefa_argument_spec,
)
+from ansible_collections.purestorage.flasharray.plugins.module_utils.common import (
+ human_to_bytes,
+ human_to_real,
+)
QOS_API_VERSION = "1.14"
@@ -402,54 +407,6 @@ def get_pgroup(module, array):
return pgroup
-def human_to_bytes(size):
- """Given a human-readable byte string (e.g. 2G, 30M),
- return the number of bytes. Will return 0 if the argument has
- unexpected form.
- """
- bytes = size[:-1]
- unit = size[-1].upper()
- if bytes.isdigit():
- bytes = int(bytes)
- if unit == "P":
- bytes *= 1125899906842624
- elif unit == "T":
- bytes *= 1099511627776
- elif unit == "G":
- bytes *= 1073741824
- elif unit == "M":
- bytes *= 1048576
- elif unit == "K":
- bytes *= 1024
- else:
- bytes = 0
- else:
- bytes = 0
- return bytes
-
-
-def human_to_real(iops):
- """Given a human-readable IOPs string (e.g. 2K, 30M),
- return the real number. Will return 0 if the argument has
- unexpected form.
- """
- digit = iops[:-1]
- unit = iops[-1].upper()
- if unit.isdigit():
- digit = iops
- elif digit.isdigit():
- digit = int(digit)
- if unit == "M":
- digit *= 1000000
- elif unit == "K":
- digit *= 1000
- else:
- digit = 0
- else:
- digit = 0
- return digit
-
-
def get_multi_volumes(module, destroyed=False):
"""Return True is all volumes exist or None"""
names = []
@@ -1553,7 +1510,7 @@ def main():
count=dict(type="int"),
start=dict(type="int", default=0),
digits=dict(type="int", default=1),
- suffix=dict(type="str"),
+ suffix=dict(type="str", default=""),
priority_operator=dict(type="str", choices=["+", "-", "="]),
priority_value=dict(type="int", choices=[-10, 0, 10]),
size=dict(type="str"),