diff options
Diffstat (limited to 'ansible_collections/purestorage/fusion/plugins')
30 files changed, 0 insertions, 6673 deletions
diff --git a/ansible_collections/purestorage/fusion/plugins/doc_fragments/purestorage.py b/ansible_collections/purestorage/fusion/plugins/doc_fragments/purestorage.py deleted file mode 100644 index a2f933161..000000000 --- a/ansible_collections/purestorage/fusion/plugins/doc_fragments/purestorage.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright: (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 - - -class ModuleDocFragment(object): - # Standard Pure Storage documentation fragment - DOCUMENTATION = r""" -options: - - See separate platform section for more details -requirements: - - See separate platform section for more details -notes: - - Ansible modules are available for the following Pure Storage products: FlashArray, FlashBlade, Pure1, Fusion -""" - - # Documentation fragment for Fusion - FUSION = r""" -options: - private_key_file: - aliases: [ key_file ] - description: - - Path to the private key file - - Defaults to the set environment variable under FUSION_PRIVATE_KEY_FILE. - type: str - private_key_password: - description: - - Password of the encrypted private key file - type: str - issuer_id: - aliases: [ app_id ] - description: - - Application ID from Pure1 Registration page - - eg. pure1:apikey:dssf2331sd - - Defaults to the set environment variable under FUSION_ISSUER_ID - type: str - access_token: - description: - - Access token for Fusion Service - - Defaults to the set environment variable under FUSION_ACCESS_TOKEN - type: str -notes: - - This module requires the I(purefusion) Python library - - You must set C(FUSION_ISSUER_ID) and C(FUSION_PRIVATE_KEY_FILE) environment variables - if I(issuer_id) and I(private_key_file) arguments are not passed to the module directly - - If you want to use access token for authentication, you must use C(FUSION_ACCESS_TOKEN) environment variable - if I(access_token) argument is not passed to the module directly -requirements: - - python >= 3.8 - - purefusion -""" diff --git a/ansible_collections/purestorage/fusion/plugins/inventory/__init__.py b/ansible_collections/purestorage/fusion/plugins/inventory/__init__.py deleted file mode 100644 index e69de29bb..000000000 --- a/ansible_collections/purestorage/fusion/plugins/inventory/__init__.py +++ /dev/null diff --git a/ansible_collections/purestorage/fusion/plugins/module_utils/errors.py b/ansible_collections/purestorage/fusion/plugins/module_utils/errors.py deleted file mode 100644 index f3d574edc..000000000 --- a/ansible_collections/purestorage/fusion/plugins/module_utils/errors.py +++ /dev/null @@ -1,291 +0,0 @@ -# -*- coding: utf-8 -*- - -# (c) 2023, Jan Kodera (jkodera@purestorage.com) -# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -try: - import fusion as purefusion - import urllib3 -except ImportError: - pass - -import sys -import json -import re -import traceback as trace - - -class OperationException(Exception): - """Raised if an asynchronous Operation fails.""" - - def __init__(self, op, http_error=None): - self._op = op - self._http_error = http_error - - @property - def op(self): - return self._op - - @property - def http_error(self): - return self._http_error - - -def _get_verbosity(module): - # verbosity is a private member and Ansible does not really allow - # providing extra information only if the user wants it due to ideological - # reasons, so extract it as carefully as possible and assume non-verbose - # if something fails - try: - if module._verbosity is not None and isinstance(module._verbosity, int): - return module._verbosity - except Exception: - pass - return 0 - - -def _extract_rest_call_site(traceback): - # extracts first function in traceback that comes from 'fusion.api.*_api*', - # converts its name from something like 'get_volume' to 'Get volume' and returns - while traceback: - try: - frame = traceback.tb_frame - func_name = ( - frame.f_code.co_name - ) # contains function name, e.g. 'get_volume' - mod_path = frame.f_globals[ - "__name__" - ] # contains module path, e.g. 'fusion.api.volumes_api' - path_segments = mod_path.split(".") - if ( - path_segments[0] == "fusion" - and path_segments[1] == "api" - and "_api" in path_segments[2] - ): - call_site = func_name.replace("_", " ").capitalize() - return call_site - except Exception: - pass - traceback = traceback.tb_next - return None - - -class DetailsPrinter: - def __init__(self, target): - self._target = target - self._parenthesed = False - - def append(self, what): - if not self._parenthesed: - self._target += " (" - self._parenthesed = True - else: - self._target += ", " - - self._target += what - - def finish(self): - if self._parenthesed: - self._target += ")" - return self._target - - -def format_fusion_api_exception(exception, traceback=None): - """Formats `fusion.rest.ApiException` into a simple short form, suitable - for Ansible error output. Returns a (message: str, body: dict) tuple.""" - message = None - code = None - resource_name = None - request_id = None - body = None - call_site = _extract_rest_call_site(traceback) - try: - body = json.loads(exception.body) - request_id = body.get("request_id", None) - error = body["error"] - message = error.get("message") - code = error.get("pure_code") - if not code: - code = exception.status - if not code: - code = error.get("http_code") - resource_name = error["details"]["name"] - except Exception: - pass - - output = "" - if call_site: - output += "'{0}' failed".format(call_site) - else: - output += "request failed" - - if message: - output += ", {0}".format(message.replace('"', "'")) - - details = DetailsPrinter(output) - if resource_name: - details.append("resource: '{0}'".format(resource_name)) - if code: - details.append("code: '{0}'".format(code)) - if request_id: - details.append("request id: '{0}'".format(request_id)) - output = details.finish() - - return (output, body) - - -def format_failed_fusion_operation_exception(exception): - """Formats failed `fusion.Operation` into a simple short form, suitable - for Ansible error output. Returns a (message: str, body: dict) tuple.""" - op = exception.op - http_error = exception.http_error - if op.status != "Failed" and not http_error: - raise ValueError( - "BUG: can only format Operation exception with .status == Failed or http_error != None" - ) - - message = None - code = None - operation_name = None - operation_id = None - - try: - if op.status == "Failed": - operation_id = op.id - error = op.error - message = error.message - code = error.pure_code - if not code: - code = error.http_code - operation_name = op.request_type - except Exception: - pass - - output = "" - if operation_name: - # converts e.g. 'CreateVolume' to 'Create volume' - operation_name = re.sub("(.)([A-Z][a-z]+)", r"\1 \2", operation_name) - operation_name = re.sub( - "([a-z0-9])([A-Z])", r"\1 \2", operation_name - ).capitalize() - output += "{0}: ".format(operation_name) - output += "operation failed" - - if message: - output += ", {0}".format(message.replace('"', "'")) - - details = DetailsPrinter(output) - if code: - details.append("code: '{0}'".format(code)) - if operation_id: - details.append("operation id: '{0}'".format(operation_id)) - if http_error: - details.append("HTTP error: '{0}'".format(str(http_error).replace('"', "'"))) - - output = details.finish() - - return output - - -def format_http_exception(exception, traceback): - """Formats failed `urllib3.exceptions` exceptions into a simple short form, - suitable for Ansible error output. Returns a `str`.""" - # urllib3 exceptions hide all details in a formatted message so all we - # can do is append the REST call that caused this - output = "" - call_site = _extract_rest_call_site(traceback) - if call_site: - output += "'{0}': ".format(call_site) - output += "HTTP request failed via " - - inner = exception - while True: - try: - e = inner.reason - if e and isinstance(e, urllib3.exceptions.HTTPError): - inner = e - continue - break - except Exception: - break - - if inner != exception: - output += "'{0}'/'{1}'".format(type(inner).__name__, type(exception).__name__) - else: - output += "'{0}'".format(type(exception).__name__) - - output += " - {0}".format(str(exception).replace('"', "'")) - - return output - - -def _handle_api_exception( - module, - exception, - traceback, - verbosity, -): - (error_message, body) = format_fusion_api_exception(exception, traceback) - - if verbosity > 1: - module.fail_json(msg=error_message, call_details=body, traceback=str(traceback)) - elif verbosity > 0: - module.fail_json(msg=error_message, call_details=body) - else: - module.fail_json(msg=error_message) - - -def _handle_operation_exception(module, exception, traceback, verbosity): - op = exception.op - - error_message = format_failed_fusion_operation_exception(exception) - - if verbosity > 1: - module.fail_json( - msg=error_message, op_details=op.to_dict(), traceback=str(traceback) - ) - elif verbosity > 0: - module.fail_json(msg=error_message, op_details=op.to_dict()) - else: - module.fail_json(msg=error_message) - - -def _handle_http_exception(module, exception, traceback, verbosity): - error_message = format_http_exception(exception, traceback) - - if verbosity > 1: - module.fail_json(msg=error_message, traceback=trace.format_exception(exception)) - else: - module.fail_json(msg=error_message) - - -def _except_hook_callback(module, original_hook, type, value, traceback): - verbosity = _get_verbosity(module) - if type == purefusion.rest.ApiException: - _handle_api_exception( - module, - value, - traceback, - verbosity, - ) - elif type == OperationException: - _handle_operation_exception(module, value, traceback, verbosity) - elif issubclass(type, urllib3.exceptions.HTTPError): - _handle_http_exception(module, value, traceback, verbosity) - - # if we bubbled here the handlers were not able to process the exception - original_hook(type, value, traceback) - - -def install_fusion_exception_hook(module): - """Installs a hook that catches `purefusion.rest.ApiException` and - `OperationException` and produces simpler and nicer error messages - for Ansible output.""" - original_hook = sys.excepthook - sys.excepthook = lambda type, value, traceback: _except_hook_callback( - module, original_hook, type, value, traceback - ) diff --git a/ansible_collections/purestorage/fusion/plugins/module_utils/fusion.py b/ansible_collections/purestorage/fusion/plugins/module_utils/fusion.py deleted file mode 100644 index 74b5f0e91..000000000 --- a/ansible_collections/purestorage/fusion/plugins/module_utils/fusion.py +++ /dev/null @@ -1,183 +0,0 @@ -# -*- coding: utf-8 -*- - -# This code is part of Ansible, but is an independent component. -# This particular file snippet, and this file snippet only, is BSD licensed. -# Modules you write using this snippet, which is embedded dynamically by Ansible -# still belong to the author of the module, and may assign their own license -# to the complete work. -# -# Copyright (c), Simon Dodsley <simon@purestorage.com>,2021 -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without modification, -# are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -try: - import fusion -except ImportError: - pass - -from os import environ -from urllib.parse import urljoin -import platform - -TOKEN_EXCHANGE_URL = "https://api.pure1.purestorage.com/oauth2/1.0/token" -VERSION = 1.0 -USER_AGENT_BASE = "Ansible" - -PARAM_ISSUER_ID = "issuer_id" -PARAM_PRIVATE_KEY_FILE = "private_key_file" -PARAM_PRIVATE_KEY_PASSWORD = "private_key_password" -PARAM_ACCESS_TOKEN = "access_token" -ENV_ISSUER_ID = "FUSION_ISSUER_ID" -ENV_API_HOST = "FUSION_API_HOST" -ENV_PRIVATE_KEY_FILE = "FUSION_PRIVATE_KEY_FILE" -ENV_TOKEN_ENDPOINT = "FUSION_TOKEN_ENDPOINT" -ENV_ACCESS_TOKEN = "FUSION_ACCESS_TOKEN" - -# will be deprecated in 2.0.0 -PARAM_APP_ID = "app_id" # replaced by PARAM_ISSUER_ID -PARAM_KEY_FILE = "key_file" # replaced by PARAM_PRIVATE_KEY_FILE -ENV_APP_ID = "FUSION_APP_ID" # replaced by ENV_ISSUER_ID -ENV_HOST = "FUSION_HOST" # replaced by ENV_API_HOST -DEP_VER = "2.0.0" -BASE_PATH = "/api/1.1" - - -def _env_deprecation_warning(module, old_env, new_env, vers): - if old_env in environ: - if new_env in environ: - module.warn( - f"{old_env} env variable is ignored because {new_env} is specified." - f" {old_env} env variable is deprecated and will be removed in version {vers}" - f" Please use {new_env} env variable only." - ) - else: - module.warn( - f"{old_env} env variable is deprecated and will be removed in version {vers}" - f" Please use {new_env} env variable instead." - ) - - -def _param_deprecation_warning(module, old_param, new_param, vers): - if old_param in module.params: - module.warn( - f"{old_param} parameter is deprecated and will be removed in version {vers}" - f" Please use {new_param} parameter instead." - f" Don't use both parameters simultaneously." - ) - - -def get_fusion(module): - """Return System Object or Fail""" - # deprecation warnings - _param_deprecation_warning(module, PARAM_APP_ID, PARAM_ISSUER_ID, DEP_VER) - _param_deprecation_warning(module, PARAM_KEY_FILE, PARAM_PRIVATE_KEY_FILE, DEP_VER) - _env_deprecation_warning(module, ENV_APP_ID, ENV_ISSUER_ID, DEP_VER) - _env_deprecation_warning(module, ENV_HOST, ENV_API_HOST, DEP_VER) - - user_agent = "%(base)s %(class)s/%(version)s (%(platform)s)" % { - "base": USER_AGENT_BASE, - "class": __name__, - "version": VERSION, - "platform": platform.platform(), - } - - issuer_id = module.params[PARAM_ISSUER_ID] - access_token = module.params[PARAM_ACCESS_TOKEN] - private_key_file = module.params[PARAM_PRIVATE_KEY_FILE] - private_key_password = module.params[PARAM_PRIVATE_KEY_PASSWORD] - - if private_key_password is not None: - module.fail_on_missing_params([PARAM_PRIVATE_KEY_FILE]) - - config = fusion.Configuration() - if ENV_API_HOST in environ or ENV_HOST in environ: - host_url = environ.get(ENV_API_HOST, environ.get(ENV_HOST)) - config.host = urljoin(host_url, BASE_PATH) - config.token_endpoint = environ.get(ENV_TOKEN_ENDPOINT, config.token_endpoint) - - if access_token is not None: - config.access_token = access_token - elif issuer_id is not None and private_key_file is not None: - config.issuer_id = issuer_id - config.private_key_file = private_key_file - if private_key_password is not None: - config.private_key_password = private_key_password - elif ENV_ACCESS_TOKEN in environ: - config.access_token = environ.get(ENV_ACCESS_TOKEN) - elif ( - ENV_ISSUER_ID in environ or ENV_APP_ID in environ - ) and ENV_PRIVATE_KEY_FILE in environ: - config.issuer_id = environ.get(ENV_ISSUER_ID, environ.get(ENV_APP_ID)) - config.private_key_file = environ.get(ENV_PRIVATE_KEY_FILE) - else: - module.fail_json( - msg=f"You must set either {ENV_ISSUER_ID} and {ENV_PRIVATE_KEY_FILE} or {ENV_ACCESS_TOKEN} environment variables. " - f"Or module arguments either {PARAM_ISSUER_ID} and {PARAM_PRIVATE_KEY_FILE} or {PARAM_ACCESS_TOKEN}" - ) - - try: - client = fusion.ApiClient(config) - client.set_default_header("User-Agent", user_agent) - api_instance = fusion.DefaultApi(client) - api_instance.get_version() - except Exception as err: - module.fail_json(msg="Fusion authentication failed: {0}".format(err)) - - return client - - -def fusion_argument_spec(): - """Return standard base dictionary used for the argument_spec argument in AnsibleModule""" - - return { - PARAM_ISSUER_ID: { - "no_log": True, - "aliases": [PARAM_APP_ID], - "deprecated_aliases": [ - { - "name": PARAM_APP_ID, - "version": DEP_VER, - "collection_name": "purefusion.fusion", - } - ], - }, - PARAM_PRIVATE_KEY_FILE: { - "no_log": False, - "aliases": [PARAM_KEY_FILE], - "deprecated_aliases": [ - { - "name": PARAM_KEY_FILE, - "version": DEP_VER, - "collection_name": "purefusion.fusion", - } - ], - }, - PARAM_PRIVATE_KEY_PASSWORD: { - "no_log": True, - }, - PARAM_ACCESS_TOKEN: { - "no_log": True, - }, - } diff --git a/ansible_collections/purestorage/fusion/plugins/module_utils/getters.py b/ansible_collections/purestorage/fusion/plugins/module_utils/getters.py deleted file mode 100644 index 535de76ba..000000000 --- a/ansible_collections/purestorage/fusion/plugins/module_utils/getters.py +++ /dev/null @@ -1,99 +0,0 @@ -# -*- coding: utf-8 -*- - -# (c) 2023, Daniel Turecek (dturecek@purestorage.com) -# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -try: - import fusion as purefusion -except ImportError: - pass - - -def get_array(module, fusion, array_name=None): - """Return Array or None""" - array_api_instance = purefusion.ArraysApi(fusion) - try: - if array_name is None: - array_name = module.params["array"] - - return array_api_instance.get_array( - array_name=array_name, - availability_zone_name=module.params["availability_zone"], - region_name=module.params["region"], - ) - except purefusion.rest.ApiException: - return None - - -def get_az(module, fusion, availability_zone_name=None): - """Get Availability Zone or None""" - az_api_instance = purefusion.AvailabilityZonesApi(fusion) - try: - if availability_zone_name is None: - availability_zone_name = module.params["availability_zone"] - - return az_api_instance.get_availability_zone( - region_name=module.params["region"], - availability_zone_name=availability_zone_name, - ) - except purefusion.rest.ApiException: - return None - - -def get_region(module, fusion, region_name=None): - """Get Region or None""" - region_api_instance = purefusion.RegionsApi(fusion) - try: - if region_name is None: - region_name = module.params["region"] - - return region_api_instance.get_region( - region_name=region_name, - ) - except purefusion.rest.ApiException: - return None - - -def get_ss(module, fusion, storage_service_name=None): - """Return Storage Service or None""" - ss_api_instance = purefusion.StorageServicesApi(fusion) - try: - if storage_service_name is None: - storage_service_name = module.params["storage_service"] - - return ss_api_instance.get_storage_service( - storage_service_name=storage_service_name - ) - except purefusion.rest.ApiException: - return None - - -def get_tenant(module, fusion, tenant_name=None): - """Return Tenant or None""" - api_instance = purefusion.TenantsApi(fusion) - try: - if tenant_name is None: - tenant_name = module.params["tenant"] - - return api_instance.get_tenant(tenant_name=tenant_name) - except purefusion.rest.ApiException: - return None - - -def get_ts(module, fusion, tenant_space_name=None): - """Tenant Space or None""" - ts_api_instance = purefusion.TenantSpacesApi(fusion) - try: - if tenant_space_name is None: - tenant_space_name = module.params["tenant_space"] - - return ts_api_instance.get_tenant_space( - tenant_name=module.params["tenant"], - tenant_space_name=tenant_space_name, - ) - except purefusion.rest.ApiException: - return None diff --git a/ansible_collections/purestorage/fusion/plugins/module_utils/networking.py b/ansible_collections/purestorage/fusion/plugins/module_utils/networking.py deleted file mode 100644 index a00d8200a..000000000 --- a/ansible_collections/purestorage/fusion/plugins/module_utils/networking.py +++ /dev/null @@ -1,76 +0,0 @@ -# -*- coding: utf-8 -*- - -# (c) 2023, Jan Kodera (jkodera@purestorage.com) -# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -import ipaddress - -# while regexes are hard to maintain, they are used anyways for few reasons: -# a) REST backend accepts fairly restricted input and we need to match that input instead of all -# the esoteric extra forms various packages are usually capable of parsing (like dotted-decimal -# subnet masks, octal octets, hexadecimal octets, zero-extended addresses etc.) -# b) manually written parsing routines are usually complex to write, verify and think about -import re - -# IPv4 octet regex part, matches only simple decimal 0-255 without leading zeroes -_octet = ( - "((?:[0-9])|" # matches 0-9 - "(?:[1-9][0-9])|" # matches 10-99 - "(?:1[0-9][0-9])|" # matches 100-199 - "(?:2[0-4][0-9])|" # matches 200-249 - "(?:25[0-5]))" # matches 250-255 -) - -# IPv4 subnet mask regex part, matches decimal 8-32 -_subnet_mask = ( - "((?:[8-9])|" # matches 8-9 - "(?:[1-2][0-9])|" # matches 10-29 - "(?:3[0-2]))" # matches 30-32 -) - -# matches IPv4 addresses -_addr_pattern = re.compile(r"^{octet}\.{octet}\.{octet}\.{octet}$".format(octet=_octet)) -# matches IPv4 networks in CIDR format, i.e. addresses in the form 'a.b.c.d/e' -_cidr_pattern = re.compile( - r"^{octet}\.{octet}\.{octet}\.{octet}\/{0}$".format(_subnet_mask, octet=_octet) -) - - -def is_valid_network(addr): - """Returns True if `addr` is IPv4 address/submask in bit CIDR notation, False otherwise.""" - match = re.match(_cidr_pattern, addr) - if match is None: - return False - for i in range(4): - if int(match.group(i + 1)) > 255: - return False - mask = int(match.group(5)) - if mask < 8 or mask > 32: - return False - return True - - -def is_valid_address(addr): - """Returns True if `addr` is a valid IPv4 address, False otherwise. Does not support - octal/hex notations.""" - match = re.match(_addr_pattern, addr) - if match is None: - return False - for i in range(4): - if int(match.group(i + 1)) > 255: - return False - return True - - -def is_address_in_network(addr, network): - """Returns True if `addr` and `network` are a valid IPv4 address and - IPv4 network respectively and if `addr` is in `network`, False otherwise.""" - if not is_valid_address(addr) or not is_valid_network(network): - return False - parsed_addr = ipaddress.ip_address(addr) - parsed_net = ipaddress.ip_network(network) - return parsed_addr in parsed_net diff --git a/ansible_collections/purestorage/fusion/plugins/module_utils/operations.py b/ansible_collections/purestorage/fusion/plugins/module_utils/operations.py deleted file mode 100644 index dc80aefe3..000000000 --- a/ansible_collections/purestorage/fusion/plugins/module_utils/operations.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- - -# (c) 2023, Jan Kodera (jkodera@purestorage.com) -# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -import time -import math - -try: - import fusion as purefusion - from urllib3.exceptions import HTTPError -except ImportError: - pass - -from ansible_collections.purestorage.fusion.plugins.module_utils.errors import ( - OperationException, -) - - -def await_operation(fusion, operation, fail_playbook_if_operation_fails=True): - """ - Waits for given operation to finish. - Throws an exception by default if the operation fails. - """ - op_api = purefusion.OperationsApi(fusion) - operation_get = None - while True: - try: - operation_get = op_api.get_operation(operation.id) - if operation_get.status == "Succeeded": - return operation_get - if operation_get.status == "Failed": - if fail_playbook_if_operation_fails: - raise OperationException(operation_get) - return operation_get - except HTTPError as err: - raise OperationException(operation, http_error=err) - time.sleep(int(math.ceil(operation_get.retry_in / 1000))) diff --git a/ansible_collections/purestorage/fusion/plugins/module_utils/parsing.py b/ansible_collections/purestorage/fusion/plugins/module_utils/parsing.py deleted file mode 100644 index 1bcb8b812..000000000 --- a/ansible_collections/purestorage/fusion/plugins/module_utils/parsing.py +++ /dev/null @@ -1,75 +0,0 @@ -# -*- coding: utf-8 -*- - -# (c) 2023, Jan Kodera (jkodera@purestorage.com) -# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function -import re - -__metaclass__ = type - -METRIC_SUFFIXES = ["K", "M", "G", "T", "P"] - -duration_pattern = re.compile( - r"^((?P<Y>\d+)Y)?((?P<W>\d+)W)?((?P<D>\d+)D)?(((?P<H>\d+)H)?((?P<M>\d+)M)?)?$" -) -duration_transformation = { - "Y": 365 * 24 * 60, - "W": 7 * 24 * 60, - "D": 24 * 60, - "H": 60, - "M": 1, -} - - -def parse_number_with_metric_suffix(module, number, factor=1024): - """Given a human-readable string (e.g. 2G, 30M, 400), - return the resolved integer. - Will call `module.fail_json()` for invalid inputs. - """ - try: - stripped_num = number.strip() - if stripped_num[-1].isdigit(): - return int(stripped_num) - # has unit prefix - result = float(stripped_num[:-1]) - suffix = stripped_num[-1].upper() - factor_count = METRIC_SUFFIXES.index(suffix) + 1 - for _i in range(0, factor_count): - result = result * float(factor) - return int(result) - except Exception: - module.fail_json( - msg="'{0}' is not a valid number, use '400', '1K', '2M', ...".format(number) - ) - return 0 - - -def parse_duration(period): - if period.isdigit(): - return int(period) - - match = duration_pattern.match(period.upper()) - if not match or period == "": - raise ValueError("Invalid format") - - durations = { - "Y": int(match.group("Y")) if match.group("Y") else 0, - "W": int(match.group("W")) if match.group("W") else 0, - "D": int(match.group("D")) if match.group("D") else 0, - "H": int(match.group("H")) if match.group("H") else 0, - "M": int(match.group("M")) if match.group("M") else 0, - } - return sum(value * duration_transformation[key] for key, value in durations.items()) - - -def parse_minutes(module, period): - try: - return parse_duration(period) - except ValueError: - module.fail_json( - msg=( - "'{0}' is not a valid time period, use combination of data units (Y,W,D,H,M)" - "e.g. 4W3D5H, 5D8H5M, 3D, 5W, 1Y5W..." - ).format(period) - ) diff --git a/ansible_collections/purestorage/fusion/plugins/module_utils/prerequisites.py b/ansible_collections/purestorage/fusion/plugins/module_utils/prerequisites.py deleted file mode 100644 index db00a9c6f..000000000 --- a/ansible_collections/purestorage/fusion/plugins/module_utils/prerequisites.py +++ /dev/null @@ -1,162 +0,0 @@ -# -*- coding: utf-8 -*- - -# (c) 2023, Jan Kodera (jkodera@purestorage.com) -# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -import re -import importlib -import importlib.metadata - -# This file exists because Ansible currently cannot declare dependencies on Python modules. -# see https://github.com/ansible/ansible/issues/62733 for more info about lack of req support - -############################# - -# 'module_name, package_name, version_requirements' triplets -DEPENDENCIES = [ - ("fusion", "purefusion", ">=1.0.11,<2.0"), - ("urllib3", "urllib3", None), -] - -############################# - - -def _parse_version(val): - """ - Parse a package version. - Takes in either MAJOR.MINOR or MAJOR.MINOR.PATCH form. PATCH - can have additional suffixes, e.g. '-prerelease', 'a1', ... - - :param val: a string representation of the package version - :returns: tuple of ints (MAJOR, MINOR, PATCH) or None if not parsed - """ - # regexes for this were really ugly - try: - parts = val.split(".") - if len(parts) < 2 or len(parts) > 3: - return None - major = int(parts[0]) - minor = int(parts[1]) - if len(parts) > 2: - patch = re.match(r"^\d+", parts[2]) - patch = int(patch.group(0)) - else: - patch = None - return (major, minor, patch) - except Exception: - return None - - -# returns list of tuples [(COMPARATOR, (MAJOR, MINOR, PATCH)),...] -def _parse_version_requirements(val): - """ - Parse package requirements. - - :param val: a string in the form ">=1.0.11,<2.0" - :returns: list of tuples in the form [(">=", (1, 0, 11)), ("<", (2, 0, None))] or None if not parsed - """ - reqs = [] - try: - parts = val.split(",") - for part in parts: - match = re.match(r"\s*(>=|<=|==|=|<|>|!=)\s*([^\s]+)", part) - op = match.group(1) - ver = match.group(2) - ver_tuple = _parse_version(ver) - if not ver_tuple: - raise ValueError("invalid version {0}".format(ver)) - reqs.append((op, ver_tuple)) - return reqs - except Exception as e: - raise ValueError("invalid version requirement '{0}' {1}".format(val, e)) - - -def _compare_version(op, ver, req): - """ - Compare two versions. - - :param op: a string, one of comparators ">=", "<=", "=", "==", ">" or "<" - :param ver: version tuple in _parse_version() return form - :param req: version tuple in _parse_version() return form - :returns: True if ver 'op' req; False otherwise - """ - - def _cmp(a, b): - return (a > b) - (a < b) - - major = _cmp(ver[0], req[0]) - minor = _cmp(ver[1], req[1]) - patch = None - if req[2] is not None: - patch = _cmp(ver[2] or 0, req[2]) - result = { - ">=": major > 0 or (major == 0 and (minor > 0 or patch is None or patch >= 0)), - "<=": major < 0 or (major == 0 and (minor < 0 or patch is None or patch <= 0)), - ">": major > 0 - or (major == 0 and (minor > 0 or patch is not None and patch > 0)), - "<": major < 0 - or (major == 0 and (minor < 0 or patch is not None and patch < 0)), - "=": major == 0 and minor == 0 and (patch is None or patch == 0), - "==": major == 0 and minor == 0 and (patch is None or patch == 0), - "!=": major != 0 or minor != 0 or (patch is not None and patch != 0), - }.get(op) - return result - - -def _version_satisfied(version, requirements): - """ - Checks whether version matches given version requirements. - - :param version: a string, in input form to _parse_version() - :param requirements: as string, in input form to _parse_version_requirements() - :returns: True if 'version' matches 'requirements'; False otherwise - """ - - version = _parse_version(version) - requirements = _parse_version_requirements(requirements) - for req in requirements: - if not _compare_version(req[0], version, req[1]): - return False - return True - - -# poor helper to work around the fact Ansible is unable to manage python dependencies -def _check_import(ansible_module, module, package=None, version_requirements=None): - """ - Tries to import a module and optionally validates its package version. - Calls AnsibleModule.fail_json() if not satisfied. - - :param ansible_module: an AnsibleModule instance - :param module: a string with module name to try to import - :param package: a string, package to check version for; must be specified with 'version_requirements' - :param version_requirements: a string, version requirements for 'package' - """ - try: - importlib.import_module(module) - except ImportError: - ansible_module.fail_json( - msg="Error: Python package '{0}' required and missing".format(module) - ) - - if package and version_requirements: - # silently ignore version checks and hope for the best if we can't fetch - # the package version since we can't know how the user installs packages - try: - version = importlib.metadata.version(package) - if version and not _version_satisfied(version, version_requirements): - ansible_module.fail_json( - msg="Error: Python package '{0}' version '{1}' does not satisfy requirements '{2}'".format( - module, version, version_requirements - ) - ) - except Exception: - pass # ignore package loads - - -def check_dependencies(ansible_module): - for module, package, version_requirements in DEPENDENCIES: - _check_import(ansible_module, module, package, version_requirements) diff --git a/ansible_collections/purestorage/fusion/plugins/module_utils/snapshots.py b/ansible_collections/purestorage/fusion/plugins/module_utils/snapshots.py deleted file mode 100644 index ed34c1c0e..000000000 --- a/ansible_collections/purestorage/fusion/plugins/module_utils/snapshots.py +++ /dev/null @@ -1,29 +0,0 @@ -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -try: - import fusion as purefusion -except ImportError: - pass - -from ansible_collections.purestorage.fusion.plugins.module_utils.operations import ( - await_operation, -) - - -def delete_snapshot(fusion, snap, snapshots_api): - patch = purefusion.SnapshotPatch(destroyed=purefusion.NullableBoolean(True)) - op = snapshots_api.update_snapshot( - body=patch, - tenant_name=snap.tenant.name, - tenant_space_name=snap.tenant_space.name, - snapshot_name=snap.name, - ) - await_operation(fusion, op) - op = snapshots_api.delete_snapshot( - tenant_name=snap.tenant.name, - tenant_space_name=snap.tenant_space.name, - snapshot_name=snap.name, - ) - await_operation(fusion, op) diff --git a/ansible_collections/purestorage/fusion/plugins/module_utils/startup.py b/ansible_collections/purestorage/fusion/plugins/module_utils/startup.py deleted file mode 100644 index 55d7f11a2..000000000 --- a/ansible_collections/purestorage/fusion/plugins/module_utils/startup.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- - -# (c) 2023, Jan Kodera (jkodera@purestorage.com) -# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -from ansible_collections.purestorage.fusion.plugins.module_utils.errors import ( - install_fusion_exception_hook, -) - -from ansible_collections.purestorage.fusion.plugins.module_utils.prerequisites import ( - check_dependencies, -) - -from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import ( - get_fusion, -) - - -def setup_fusion(module): - check_dependencies(module) - install_fusion_exception_hook(module) - return get_fusion(module) diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_api_client.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_api_client.py deleted file mode 100644 index 42254338f..000000000 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_api_client.py +++ /dev/null @@ -1,140 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2022, Simon Dodsley (simon@purestorage.com) -# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -DOCUMENTATION = r""" ---- -module: fusion_api_client -version_added: '1.0.0' -short_description: Manage API clients in Pure Storage Fusion -description: -- Create or delete an API Client in Pure Storage Fusion. -author: -- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com> -notes: -- Supports C(check mode). -options: - name: - description: - - The name of the client. - type: str - required: true - state: - description: - - Define whether the client should exist or not. - default: present - choices: [ present, absent ] - type: str - public_key: - description: - - The API clients PEM formatted (Base64 encoded) RSA public key. - - Include the C(—–BEGIN PUBLIC KEY—–) and C(—–END PUBLIC KEY—–) lines. - type: str - required: true -extends_documentation_fragment: -- purestorage.fusion.purestorage.fusion -""" - -EXAMPLES = r""" -- name: Create new API client foo - purestorage.fusion.fusion_api_client: - name: "foo client" - public_key: "{{lookup('file', 'public_pem_file') }}" - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" -""" - -RETURN = r""" -""" - -try: - import fusion as purefusion -except ImportError: - pass - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import ( - fusion_argument_spec, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.startup import ( - setup_fusion, -) - - -def get_client_id(module, fusion): - """Get API Client ID, or None if not available""" - id_api_instance = purefusion.IdentityManagerApi(fusion) - try: - clients = id_api_instance.list_api_clients() - for client in clients: - if ( - client.public_key == module.params["public_key"] - and client.display_name == module.params["name"] - ): - return client.id - return None - except purefusion.rest.ApiException: - return None - - -def delete_client(module, fusion, client_id): - """Delete API Client""" - id_api_instance = purefusion.IdentityManagerApi(fusion) - - changed = True - if not module.check_mode: - id_api_instance.delete_api_client(api_client_id=client_id) - module.exit_json(changed=changed) - - -def create_client(module, fusion): - """Create API Client""" - - id_api_instance = purefusion.IdentityManagerApi(fusion) - - changed = True - id = None - if not module.check_mode: - client = purefusion.APIClientPost( - public_key=module.params["public_key"], - display_name=module.params["name"], - ) - res = id_api_instance.create_api_client(client) - id = res.id - module.exit_json(changed=changed, id=id) - - -def main(): - """Main code""" - argument_spec = fusion_argument_spec() - argument_spec.update( - dict( - name=dict(type="str", required=True), - public_key=dict(type="str", required=True), - state=dict(type="str", default="present", choices=["present", "absent"]), - ) - ) - - module = AnsibleModule(argument_spec, supports_check_mode=True) - fusion = setup_fusion(module) - - state = module.params["state"] - client_id = get_client_id(module, fusion) - if client_id is None and state == "present": - create_client(module, fusion) - elif client_id is not None and state == "absent": - delete_client(module, fusion, client_id) - if client_id is not None: - module.exit_json(changed=False, id=client_id) - - module.exit_json(changed=False) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_array.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_array.py deleted file mode 100644 index ec94d616f..000000000 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_array.py +++ /dev/null @@ -1,281 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2022, Simon Dodsley (simon@purestorage.com) -# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -DOCUMENTATION = r""" ---- -module: fusion_array -version_added: '1.0.0' -short_description: Manage arrays in Pure Storage Fusion -description: -- Create or delete an array in Pure Storage Fusion. -author: -- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com> -notes: -- Supports C(check mode). -options: - name: - description: - - The name of the array. - type: str - required: true - state: - description: - - Define whether the array should exist or not. - default: present - choices: [ present, absent ] - type: str - display_name: - description: - - The human name of the array. - - If not provided, defaults to I(name). - type: str - region: - description: - - The region the AZ is in. - type: str - required: true - availability_zone: - aliases: [ az ] - description: - - The availability zone the array is located in. - type: str - required: true - hardware_type: - description: - - Hardware type to which the storage class applies. - choices: [ flash-array-x, flash-array-c, flash-array-x-optane, flash-array-xl ] - type: str - host_name: - description: - - Management IP address of the array, or FQDN. - type: str - appliance_id: - description: - - Appliance ID of the array. - type: str - apartment_id: - description: - - The Apartment ID of the Array. - type: str - maintenance_mode: - description: - - "Switch the array into maintenance mode or back. - Array in maintenance mode can have placement groups migrated out but not in. - Intended use cases are for example safe decommissioning or to prevent use - of an array that has not yet been fully configured." - type: bool - unavailable_mode: - description: - - "Switch the array into unavailable mode or back. - Fusion tries to exclude unavailable arrays from virtually any operation it - can. This is to prevent stalling operations in case of e.g. a networking - failure. As of the moment arrays have to be marked unavailable manually." - type: bool -extends_documentation_fragment: -- purestorage.fusion.purestorage.fusion -""" - -EXAMPLES = r""" -- name: Create new array foo - purestorage.fusion.fusion_array: - name: foo - az: zone_1 - region: region1 - hardware_type: flash-array-x - host_name: foo_array - display_name: "foo array" - appliance_id: 1227571-198887878-35016350232000707 - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" -""" - -RETURN = r""" -""" - -try: - import fusion as purefusion -except ImportError: - pass - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import ( - fusion_argument_spec, -) - -from ansible_collections.purestorage.fusion.plugins.module_utils import getters -from ansible_collections.purestorage.fusion.plugins.module_utils.operations import ( - await_operation, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.startup import ( - setup_fusion, -) - - -def get_array(module, fusion): - """Return Array or None""" - return getters.get_array(module, fusion, array_name=module.params["name"]) - - -def create_array(module, fusion): - """Create Array""" - - array_api_instance = purefusion.ArraysApi(fusion) - id = None - - if not module.check_mode: - if not module.params["display_name"]: - display_name = module.params["name"] - else: - display_name = module.params["display_name"] - array = purefusion.ArrayPost( - hardware_type=module.params["hardware_type"], - display_name=display_name, - host_name=module.params["host_name"], - name=module.params["name"], - appliance_id=module.params["appliance_id"], - apartment_id=module.params["apartment_id"], - ) - res = array_api_instance.create_array( - array, - availability_zone_name=module.params["availability_zone"], - region_name=module.params["region"], - ) - res_op = await_operation(fusion, res) - id = res_op.result.resource.id - - return True, id - - -def update_array(module, fusion): - """Update Array""" - array = get_array(module, fusion) - patches = [] - if ( - module.params["display_name"] - and module.params["display_name"] != array.display_name - ): - patch = purefusion.ArrayPatch( - display_name=purefusion.NullableString(module.params["display_name"]), - ) - patches.append(patch) - - if module.params["host_name"] and module.params["host_name"] != array.host_name: - patch = purefusion.ArrayPatch( - host_name=purefusion.NullableString(module.params["host_name"]) - ) - patches.append(patch) - - if ( - module.params["maintenance_mode"] is not None - and module.params["maintenance_mode"] != array.maintenance_mode - ): - patch = purefusion.ArrayPatch( - maintenance_mode=purefusion.NullableBoolean( - module.params["maintenance_mode"] - ) - ) - patches.append(patch) - if ( - module.params["unavailable_mode"] is not None - and module.params["unavailable_mode"] != array.unavailable_mode - ): - patch = purefusion.ArrayPatch( - unavailable_mode=purefusion.NullableBoolean( - module.params["unavailable_mode"] - ) - ) - patches.append(patch) - - if not module.check_mode: - array_api_instance = purefusion.ArraysApi(fusion) - for patch in patches: - op = array_api_instance.update_array( - patch, - availability_zone_name=module.params["availability_zone"], - region_name=module.params["region"], - array_name=module.params["name"], - ) - await_operation(fusion, op) - - changed = len(patches) != 0 - return changed - - -def delete_array(module, fusion): - """Delete Array - not currently available""" - array_api_instance = purefusion.ArraysApi(fusion) - if not module.check_mode: - res = array_api_instance.delete_array( - region_name=module.params["region"], - availability_zone_name=module.params["availability_zone"], - array_name=module.params["name"], - ) - await_operation(fusion, res) - return True - - -def main(): - """Main code""" - argument_spec = fusion_argument_spec() - argument_spec.update( - dict( - name=dict(type="str", required=True), - availability_zone=dict(type="str", required=True, aliases=["az"]), - display_name=dict(type="str"), - region=dict(type="str", required=True), - apartment_id=dict(type="str"), - appliance_id=dict(type="str"), - host_name=dict(type="str"), - hardware_type=dict( - type="str", - choices=[ - "flash-array-x", - "flash-array-c", - "flash-array-x-optane", - "flash-array-xl", - ], - ), - maintenance_mode=dict(type="bool"), - unavailable_mode=dict(type="bool"), - state=dict(type="str", default="present", choices=["present", "absent"]), - ) - ) - - module = AnsibleModule(argument_spec, supports_check_mode=True) - fusion = setup_fusion(module) - - state = module.params["state"] - array = get_array(module, fusion) - - changed = False - id = None - if array is not None: - id = array.id - - if not array and state == "present": - module.fail_on_missing_params(["hardware_type", "host_name", "appliance_id"]) - changed, id = create_array(module, fusion) - update_array( - module, fusion - ) # update is run to set properties which cannot be set on creation and instead use defaults - elif array and state == "present": - changed = update_array(module, fusion) - elif array and state == "absent": - changed = changed | delete_array(module, fusion) - module.exit_json(changed=changed) - - if id is not None: - module.exit_json(changed=changed, id=id) - - module.exit_json(changed=changed) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_az.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_az.py deleted file mode 100644 index b4a493861..000000000 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_az.py +++ /dev/null @@ -1,162 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2022, Simon Dodsley (simon@purestorage.com) -# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -DOCUMENTATION = r""" ---- -module: fusion_az -version_added: '1.0.0' -short_description: Create Availability Zones in Pure Storage Fusion -description: -- Manage an Availability Zone in Pure Storage Fusion. -author: -- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com> -notes: -- Supports C(check mode). -options: - name: - description: - - The name of the Availability Zone. - type: str - required: true - state: - description: - - Define whether the Availability Zone should exist or not. - default: present - choices: [ present, absent ] - type: str - display_name: - description: - - The human name of the Availability Zone. - - If not provided, defaults to I(name). - type: str - region: - description: - - Region within which the AZ is created. - type: str - required: true -extends_documentation_fragment: -- purestorage.fusion.purestorage.fusion -""" - -EXAMPLES = r""" -- name: Create new AZ foo - purestorage.fusion.fusion_az: - name: foo - display_name: "foo AZ" - region: region1 - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" - -- name: Delete AZ foo - purestorage.fusion.fusion_az: - name: foo - state: absent - region: region1 - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" -""" - -RETURN = r""" -""" - -try: - import fusion as purefusion -except ImportError: - pass - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import ( - fusion_argument_spec, -) - -from ansible_collections.purestorage.fusion.plugins.module_utils import getters -from ansible_collections.purestorage.fusion.plugins.module_utils.operations import ( - await_operation, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.startup import ( - setup_fusion, -) - - -def get_az(module, fusion): - """Get Availability Zone or None""" - return getters.get_az(module, fusion, availability_zone_name=module.params["name"]) - - -def delete_az(module, fusion): - """Delete Availability Zone""" - - az_api_instance = purefusion.AvailabilityZonesApi(fusion) - - changed = True - if not module.check_mode: - op = az_api_instance.delete_availability_zone( - region_name=module.params["region"], - availability_zone_name=module.params["name"], - ) - await_operation(fusion, op) - - module.exit_json(changed=changed) - - -def create_az(module, fusion): - """Create Availability Zone""" - - az_api_instance = purefusion.AvailabilityZonesApi(fusion) - - changed = True - id = None - if not module.check_mode: - if not module.params["display_name"]: - display_name = module.params["name"] - else: - display_name = module.params["display_name"] - - azone = purefusion.AvailabilityZonePost( - name=module.params["name"], - display_name=display_name, - ) - op = az_api_instance.create_availability_zone( - azone, region_name=module.params["region"] - ) - res_op = await_operation(fusion, op) - id = res_op.result.resource.id - - module.exit_json(changed=changed, id=id) - - -def main(): - """Main code""" - argument_spec = fusion_argument_spec() - argument_spec.update( - dict( - name=dict(type="str", required=True), - display_name=dict(type="str"), - region=dict(type="str", required=True), - state=dict(type="str", default="present", choices=["present", "absent"]), - ) - ) - - module = AnsibleModule(argument_spec, supports_check_mode=True) - fusion = setup_fusion(module) - - state = module.params["state"] - azone = get_az(module, fusion) - - if not azone and state == "present": - create_az(module, fusion) - elif azone and state == "absent": - delete_az(module, fusion) - - module.exit_json(changed=False) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_hap.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_hap.py deleted file mode 100644 index c4df0af49..000000000 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_hap.py +++ /dev/null @@ -1,314 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# Copyright: (c) 2022, Simon Dodsley (simon@purestorage.com) -# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -DOCUMENTATION = r""" ---- -module: fusion_hap -version_added: '1.0.0' -short_description: Manage host access policies in Pure Storage Fusion -description: -- Create or delete host access policies in Pure Storage Fusion. -author: -- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com> -notes: -- Supports C(check mode). -- Setting passwords is not an idempotent action. -- Only iSCSI transport is currently supported. -- iSCSI CHAP is not yet supported. -options: - name: - description: - - The name of the host access policy. - type: str - required: true - display_name: - description: - - The human name of the host access policy. - type: str - state: - description: - - Define whether the host access policy should exist or not. - - When removing host access policy all connected volumes must - have been previously disconnected. - type: str - default: present - choices: [ absent, present ] - wwns: - type: list - elements: str - description: - - CURRENTLY NOT SUPPORTED. - - List of wwns for the host access policy. - iqn: - type: str - description: - - IQN for the host access policy. - nqn: - type: str - description: - - CURRENTLY NOT SUPPORTED. - - NQN for the host access policy. - personality: - type: str - description: - - Define which operating system the host is. - default: linux - choices: ['linux', 'windows', 'hpux', 'vms', 'aix', 'esxi', 'solaris', 'hitachi-vsp', 'oracle-vm-server'] - target_user: - type: str - description: - - CURRENTLY NOT SUPPORTED. - - Sets the target user name for CHAP authentication. - - Required with I(target_password). - - To clear the username/password pair use C(clear) as the password. - target_password: - type: str - description: - - CURRENTLY NOT SUPPORTED. - - Sets the target password for CHAP authentication. - - Password length between 12 and 255 characters. - - To clear the username/password pair use C(clear) as the password. - host_user: - type: str - description: - - CURRENTLY NOT SUPPORTED. - - Sets the host user name for CHAP authentication. - - Required with I(host_password). - - To clear the username/password pair use C(clear) as the password. - host_password: - type: str - description: - - CURRENTLY NOT SUPPORTED. - - Sets the host password for CHAP authentication. - - Password length between 12 and 255 characters. - - To clear the username/password pair use C(clear) as the password. -extends_documentation_fragment: -- purestorage.fusion.purestorage.fusion -""" - -EXAMPLES = r""" -- name: Create new AIX host access policy - purestorage.fusion.fusion_hap: - name: foo - personality: aix - iqn: "iqn.2005-03.com.RedHat:linux-host1" - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" - -- name: Delete host access policy - purestorage.fusion.fusion_hap: - name: foo - state: absent - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" -""" - -RETURN = r""" -""" - -try: - import fusion as purefusion -except ImportError: - pass - -import re -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import ( - fusion_argument_spec, -) - -from ansible_collections.purestorage.fusion.plugins.module_utils.startup import ( - setup_fusion, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.operations import ( - await_operation, -) - - -def _check_iqn(module, fusion): - hap_api_instance = purefusion.HostAccessPoliciesApi(fusion) - hosts = hap_api_instance.list_host_access_policies().items - for host in hosts: - if host.iqn == module.params["iqn"] and host.name != module.params["name"]: - module.fail_json( - msg="Supplied IQN {0} already used by host access policy {1}".format( - module.params["iqn"], host.name - ) - ) - - -def get_host(module, fusion): - """Return host or None""" - hap_api_instance = purefusion.HostAccessPoliciesApi(fusion) - try: - return hap_api_instance.get_host_access_policy( - host_access_policy_name=module.params["name"] - ) - except purefusion.rest.ApiException: - return None - - -def create_hap(module, fusion): - """Create a new host access policy""" - hap_api_instance = purefusion.HostAccessPoliciesApi(fusion) - changed = True - if not module.check_mode: - display_name = module.params["display_name"] or module.params["name"] - - op = hap_api_instance.create_host_access_policy( - purefusion.HostAccessPoliciesPost( - iqn=module.params["iqn"], - personality=module.params["personality"], - name=module.params["name"], - display_name=display_name, - ) - ) - res_op = await_operation(fusion, op) - id = res_op.result.resource.id - - module.exit_json(changed=changed, id=id) - - -def delete_hap(module, fusion): - """Delete a Host Access Policy""" - hap_api_instance = purefusion.HostAccessPoliciesApi(fusion) - changed = True - if not module.check_mode: - op = hap_api_instance.delete_host_access_policy( - host_access_policy_name=module.params["name"] - ) - await_operation(fusion, op) - module.exit_json(changed=changed) - - -def main(): - argument_spec = fusion_argument_spec() - argument_spec.update( - dict( - name=dict(type="str", required=True), - state=dict(type="str", default="present", choices=["absent", "present"]), - nqn=dict( - type="str", - removed_in_version="2.0.0", - removed_from_collection="purestorage.fusion", - ), - iqn=dict(type="str"), - wwns=dict( - type="list", - elements="str", - removed_in_version="2.0.0", - removed_from_collection="purestorage.fusion", - ), - host_password=dict( - type="str", - removed_in_version="2.0.0", - removed_from_collection="purestorage.fusion", - no_log=True, - ), - host_user=dict( - type="str", - removed_in_version="2.0.0", - removed_from_collection="purestorage.fusion", - ), - target_password=dict( - type="str", - removed_in_version="2.0.0", - removed_from_collection="purestorage.fusion", - no_log=True, - ), - target_user=dict( - type="str", - removed_in_version="2.0.0", - removed_from_collection="purestorage.fusion", - ), - display_name=dict(type="str"), - personality=dict( - type="str", - default="linux", - choices=[ - "linux", - "windows", - "hpux", - "vms", - "aix", - "esxi", - "solaris", - "hitachi-vsp", - "oracle-vm-server", - ], - ), - ) - ) - - required_if = [["state", "present", ["personality", "iqn"]]] - - module = AnsibleModule( - argument_spec, - supports_check_mode=True, - required_if=required_if, - ) - fusion = setup_fusion(module) - - if module.params["nqn"]: - module.warn( - "`nqn` parameter is deprecated and will be removed in version 2.0.0" - ) - if module.params["wwns"]: - module.warn( - "`wwns` parameter is deprecated and will be removed in version 2.0.0" - ) - if module.params["host_password"]: - module.warn( - "`host_password` parameter is deprecated and will be removed in version 2.0.0" - ) - if module.params["host_user"]: - module.warn( - "`host_user` parameter is deprecated and will be removed in version 2.0.0" - ) - if module.params["target_password"]: - module.warn( - "`target_password` parameter is deprecated and will be removed in version 2.0.0" - ) - if module.params["target_user"]: - module.warn( - "`target_user` parameter is deprecated and will be removed in version 2.0.0" - ) - - hap_pattern = re.compile("^[a-zA-Z0-9]([a-zA-Z0-9-_]{0,61}[a-zA-Z0-9])?$") - iqn_pattern = re.compile( - r"^iqn\.\d{4}-\d{2}((?<!-)\.(?!-)[a-zA-Z0-9\-]+){1,63}(?<!-)(?<!\.)(:(?!:)[^,\s'\"]+)?$" - ) - - if not hap_pattern.match(module.params["name"]): - module.fail_json( - msg="Host Access Policy {0} does not conform to naming convention".format( - module.params["name"] - ) - ) - - if module.params["iqn"] is not None and not iqn_pattern.match(module.params["iqn"]): - module.fail_json( - msg="IQN {0} is not a valid iSCSI IQN".format(module.params["name"]) - ) - - state = module.params["state"] - host = get_host(module, fusion) - _check_iqn(module, fusion) - - if host is None and state == "present": - create_hap(module, fusion) - elif host is not None and state == "absent": - delete_hap(module, fusion) - - module.exit_json(changed=False) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_hw.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_hw.py deleted file mode 100644 index 31d313e9d..000000000 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_hw.py +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2022, Simon Dodsley (simon@purestorage.com) -# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -DOCUMENTATION = r""" ---- -module: fusion_hw -version_added: '1.0.0' -deprecated: - removed_at_date: "2023-08-09" - why: Hardware type cannot be modified in Pure Storage Fusion - alternative: there's no alternative as this functionality has never worked before -short_description: Create hardware types in Pure Storage Fusion -description: -- Create a hardware type in Pure Storage Fusion. -author: -- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com> -notes: -- Supports C(check mode). -options: - name: - description: - - The name of the hardware type. - type: str - state: - description: - - Define whether the hardware type should exist or not. - - Currently there is no mechanism to delete a hardware type. - default: present - choices: [ present ] - type: str - display_name: - description: - - The human name of the hardware type. - - If not provided, defaults to I(name). - type: str - media_type: - description: - - Volume size limit in M, G, T or P units. - type: str - array_type: - description: - - The array type for the hardware type. - choices: [ FA//X, FA//C ] - type: str -extends_documentation_fragment: -- purestorage.fusion.purestorage.fusion -""" - -# this module does nothing, thus no example is provided -EXAMPLES = r""" -""" - -RETURN = r""" -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import ( - fusion_argument_spec, -) - - -def main(): - """Main code""" - argument_spec = fusion_argument_spec() - argument_spec.update( - dict( - name=dict(type="str"), - display_name=dict(type="str"), - array_type=dict(type="str", choices=["FA//X", "FA//C"]), - media_type=dict(type="str"), - state=dict(type="str", default="present", choices=["present"]), - ) - ) - - module = AnsibleModule(argument_spec, supports_check_mode=True) - - module.exit_json(changed=False) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_info.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_info.py deleted file mode 100644 index be019d3d2..000000000 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_info.py +++ /dev/null @@ -1,1130 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2021, Simon Dodsley (simon@purestorage.com), Andrej Pajtas (apajtas@purestorage.com) -# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -DOCUMENTATION = r""" ---- -module: fusion_info -version_added: '1.0.0' -short_description: Collect information from Pure Fusion -description: - - Collect information from a Pure Fusion environment. - - By default, the module will collect basic - information including counts for arrays, availability_zones, volumes, snapshots - . Fleet capacity and data reduction rates are also provided. - - Additional information can be collected based on the configured set of arguments. -author: - - Pure Storage ansible Team (@sdodsley) <pure-ansible-team@purestorage.com> -notes: -- Supports C(check mode). -options: - gather_subset: - description: - - When supplied, this argument will define the information to be collected. - Possible values for this include all, minimum, roles, users, arrays, hardware_types, - volumes, host_access_policies, storage_classes, protection_policies, placement_groups, - network_interfaces, availability_zones, network_interface_groups, storage_endpoints, - snapshots, regions, storage_services, tenants, tenant_spaces, network_interface_groups and api_clients. - type: list - elements: str - required: false - default: minimum -extends_documentation_fragment: - - purestorage.fusion.purestorage.fusion -""" - -EXAMPLES = r""" -- name: Collect default set of information - purestorage.fusion.fusion_info: - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" - register: fusion_info - -- name: Show default information - ansible.builtin.debug: - msg: "{{ fusion_info['fusion_info']['default'] }}" - -- name: Collect all information - purestorage.fusion.fusion_info: - gather_subset: - - all - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" - -- name: Show all information - ansible.builtin.debug: - msg: "{{ fusion_info['fusion_info'] }}" -""" - -RETURN = r""" -fusion_info: - description: Returns the information collected from Fusion - returned: always - type: dict -""" - -try: - import fusion as purefusion -except ImportError: - pass - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import ( - fusion_argument_spec, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.startup import ( - setup_fusion, -) -import time -import http - - -def _convert_microseconds(micros): - seconds = (micros / 1000) % 60 - minutes = (micros / (1000 * 60)) % 60 - hours = (micros / (1000 * 60 * 60)) % 24 - return seconds, minutes, hours - - -def _api_permission_denied_handler(name): - """Return decorator which catches #403 errors""" - - def inner(func): - def wrapper(module, fusion, *args, **kwargs): - try: - return func(module, fusion, *args, **kwargs) - except purefusion.rest.ApiException as exc: - if exc.status == http.HTTPStatus.FORBIDDEN: - module.warn(f"Cannot get [{name} dict], reason: Permission denied") - return None - else: - # other exceptions will be handled by our exception hook - raise exc - - return wrapper - - return inner - - -def generate_default_dict(module, fusion): - def warning_api_exception(name): - module.warn(f"Cannot get {name} in [default dict], reason: Permission denied") - - def warning_argument_none(name, requirement): - module.warn( - f"Cannot get {name} in [default dict], reason: Required argument `{requirement}` not available." - ) - - # All values are independent on each other - if getting one value fails, we will show warning and continue. - # That's also the reason why there's so many nested for loops repeating all over again. - version = None - users_num = None - protection_policies_num = None - host_access_policies_num = None - hardware_types_num = None - storage_services = None - storage_services_num = None - tenants = None - tenants_num = None - regions = None - regions_num = None - roles = None - roles_num = None - storage_classes_num = None - role_assignments_num = None - tenant_spaces_num = None - volumes_num = None - placement_groups_num = None - snapshots_num = None - availability_zones_num = None - arrays_num = None - network_interfaces_num = None - network_interface_groups_num = None - storage_endpoints_num = None - - try: - version = purefusion.DefaultApi(fusion).get_version().version - except purefusion.rest.ApiException as exc: - if exc.status == http.HTTPStatus.FORBIDDEN: - warning_api_exception("API version") - else: - # other exceptions will be handled by our exception hook - raise exc - - try: - users_num = len(purefusion.IdentityManagerApi(fusion).list_users()) - except purefusion.rest.ApiException as exc: - if exc.status == http.HTTPStatus.FORBIDDEN: - warning_api_exception("Users") - else: - # other exceptions will be handled by our exception hook - raise exc - - try: - protection_policies_num = len( - purefusion.ProtectionPoliciesApi(fusion).list_protection_policies().items - ) - except purefusion.rest.ApiException as exc: - if exc.status == http.HTTPStatus.FORBIDDEN: - warning_api_exception("Protection Policies") - else: - # other exceptions will be handled by our exception hook - raise exc - - try: - host_access_policies_num = len( - purefusion.HostAccessPoliciesApi(fusion).list_host_access_policies().items - ) - except purefusion.rest.ApiException as exc: - if exc.status == http.HTTPStatus.FORBIDDEN: - warning_api_exception("Host Access Policies") - else: - # other exceptions will be handled by our exception hook - raise exc - - try: - hardware_types_num = len( - purefusion.HardwareTypesApi(fusion).list_hardware_types().items - ) - except purefusion.rest.ApiException as exc: - if exc.status == http.HTTPStatus.FORBIDDEN: - warning_api_exception("Hardware Types") - else: - # other exceptions will be handled by our exception hook - raise exc - - try: - storage_services = purefusion.StorageServicesApi(fusion).list_storage_services() - storage_services_num = len(storage_services.items) - except purefusion.rest.ApiException as exc: - if exc.status == http.HTTPStatus.FORBIDDEN: - warning_api_exception("Storage Services") - else: - # other exceptions will be handled by our exception hook - raise exc - - try: - tenants = purefusion.TenantsApi(fusion).list_tenants() - tenants_num = len(tenants.items) - except purefusion.rest.ApiException as exc: - if exc.status == http.HTTPStatus.FORBIDDEN: - warning_api_exception("Tenants") - else: - # other exceptions will be handled by our exception hook - raise exc - - try: - regions = purefusion.RegionsApi(fusion).list_regions() - regions_num = len(regions.items) - except purefusion.rest.ApiException as exc: - if exc.status == http.HTTPStatus.FORBIDDEN: - warning_api_exception("Regions") - else: - # other exceptions will be handled by our exception hook - raise exc - - try: - roles = purefusion.RolesApi(fusion).list_roles() - roles_num = len(roles) - except purefusion.rest.ApiException as exc: - if exc.status == http.HTTPStatus.FORBIDDEN: - warning_api_exception("Roles") - else: - # other exceptions will be handled by our exception hook - raise exc - - if storage_services is not None: - try: - storage_class_api_instance = purefusion.StorageClassesApi(fusion) - storage_classes_num = sum( - len( - storage_class_api_instance.list_storage_classes( - storage_service_name=storage_service.name - ).items - ) - for storage_service in storage_services.items - ) - except purefusion.rest.ApiException as exc: - if exc.status == http.HTTPStatus.FORBIDDEN: - warning_api_exception("Storage Classes") - else: - # other exceptions will be handled by our exception hook - raise exc - else: - warning_argument_none("Storage Classes", "storage_services") - - if roles is not None: - try: - role_assign_api_instance = purefusion.RoleAssignmentsApi(fusion) - role_assignments_num = sum( - len(role_assign_api_instance.list_role_assignments(role_name=role.name)) - for role in roles - ) - except purefusion.rest.ApiException as exc: - if exc.status == http.HTTPStatus.FORBIDDEN: - warning_api_exception("Role Assignments") - else: - # other exceptions will be handled by our exception hook - raise exc - else: - warning_argument_none("Role Assignments", "roles") - - if tenants is not None: - tenantspace_api_instance = purefusion.TenantSpacesApi(fusion) - - try: - tenant_spaces_num = sum( - len( - tenantspace_api_instance.list_tenant_spaces( - tenant_name=tenant.name - ).items - ) - for tenant in tenants.items - ) - except purefusion.rest.ApiException as exc: - if exc.status == http.HTTPStatus.FORBIDDEN: - warning_api_exception("Tenant Spaces") - else: - # other exceptions will be handled by our exception hook - raise exc - - try: - vol_api_instance = purefusion.VolumesApi(fusion) - volumes_num = sum( - len( - vol_api_instance.list_volumes( - tenant_name=tenant.name, - tenant_space_name=tenant_space.name, - ).items - ) - for tenant in tenants.items - for tenant_space in tenantspace_api_instance.list_tenant_spaces( - tenant_name=tenant.name - ).items - ) - except purefusion.rest.ApiException as exc: - if exc.status == http.HTTPStatus.FORBIDDEN: - warning_api_exception("Volumes") - else: - # other exceptions will be handled by our exception hook - raise exc - - try: - plgrp_api_instance = purefusion.PlacementGroupsApi(fusion) - placement_groups_num = sum( - len( - plgrp_api_instance.list_placement_groups( - tenant_name=tenant.name, - tenant_space_name=tenant_space.name, - ).items - ) - for tenant in tenants.items - for tenant_space in tenantspace_api_instance.list_tenant_spaces( - tenant_name=tenant.name - ).items - ) - except purefusion.rest.ApiException as exc: - if exc.status == http.HTTPStatus.FORBIDDEN: - warning_api_exception("Placement Groups") - else: - # other exceptions will be handled by our exception hook - raise exc - - try: - snapshot_api_instance = purefusion.SnapshotsApi(fusion) - snapshots_num = sum( - len( - snapshot_api_instance.list_snapshots( - tenant_name=tenant.name, - tenant_space_name=tenant_space.name, - ).items - ) - for tenant in tenants.items - for tenant_space in tenantspace_api_instance.list_tenant_spaces( - tenant_name=tenant.name - ).items - ) - except purefusion.rest.ApiException as exc: - if exc.status == http.HTTPStatus.FORBIDDEN: - warning_api_exception("Snapshots") - else: - # other exceptions will be handled by our exception hook - raise exc - else: - warning_argument_none("Tenant Spaces", "tenants") - warning_argument_none("Volumes", "tenants") - warning_argument_none("Placement Groups", "tenants") - warning_argument_none("Snapshots", "tenants") - - if regions is not None: - az_api_instance = purefusion.AvailabilityZonesApi(fusion) - - try: - availability_zones_num = sum( - len( - az_api_instance.list_availability_zones( - region_name=region.name - ).items - ) - for region in regions.items - ) - except purefusion.rest.ApiException as exc: - if exc.status == http.HTTPStatus.FORBIDDEN: - warning_api_exception("Availability Zones") - else: - # other exceptions will be handled by our exception hook - raise exc - - try: - arrays_api_instance = purefusion.ArraysApi(fusion) - arrays_num = sum( - len( - arrays_api_instance.list_arrays( - availability_zone_name=availability_zone.name, - region_name=region.name, - ).items - ) - for region in regions.items - for availability_zone in az_api_instance.list_availability_zones( - region_name=region.name - ).items - ) - except purefusion.rest.ApiException as exc: - if exc.status == http.HTTPStatus.FORBIDDEN: - warning_api_exception("Arrays") - else: - # other exceptions will be handled by our exception hook - raise exc - - try: - nig_api_instance = purefusion.NetworkInterfaceGroupsApi(fusion) - network_interface_groups_num = sum( - len( - nig_api_instance.list_network_interface_groups( - availability_zone_name=availability_zone.name, - region_name=region.name, - ).items - ) - for region in regions.items - for availability_zone in az_api_instance.list_availability_zones( - region_name=region.name - ).items - ) - except purefusion.rest.ApiException as exc: - if exc.status == http.HTTPStatus.FORBIDDEN: - warning_api_exception("Network Interface Groups") - else: - # other exceptions will be handled by our exception hook - raise exc - - try: - send_api_instance = purefusion.StorageEndpointsApi(fusion) - storage_endpoints_num = sum( - len( - send_api_instance.list_storage_endpoints( - availability_zone_name=availability_zone.name, - region_name=region.name, - ).items - ) - for region in regions.items - for availability_zone in az_api_instance.list_availability_zones( - region_name=region.name - ).items - ) - except purefusion.rest.ApiException as exc: - if exc.status == http.HTTPStatus.FORBIDDEN: - warning_api_exception("Storage Endpoints") - else: - # other exceptions will be handled by our exception hook - raise exc - - try: - nic_api_instance = purefusion.NetworkInterfacesApi(fusion) - network_interfaces_num = sum( - len( - nic_api_instance.list_network_interfaces( - availability_zone_name=availability_zone.name, - region_name=region.name, - array_name=array_detail.name, - ).items - ) - for region in regions.items - for availability_zone in az_api_instance.list_availability_zones( - region_name=region.name - ).items - for array_detail in arrays_api_instance.list_arrays( - availability_zone_name=availability_zone.name, - region_name=region.name, - ).items - ) - except purefusion.rest.ApiException as exc: - if exc.status == http.HTTPStatus.FORBIDDEN: - warning_api_exception("Network Interfaces") - else: - # other exceptions will be handled by our exception hook - raise exc - else: - warning_argument_none("Availability Zones", "regions") - warning_argument_none("Network Interfaces", "regions") - warning_argument_none("Network Interface Groups", "regions") - warning_argument_none("Storage Endpoints", "regions") - warning_argument_none("Arrays", "regions") - - return { - "version": version, - "users": users_num, - "protection_policies": protection_policies_num, - "host_access_policies": host_access_policies_num, - "hardware_types": hardware_types_num, - "storage_services": storage_services_num, - "tenants": tenants_num, - "regions": regions_num, - "storage_classes": storage_classes_num, - "roles": roles_num, - "role_assignments": role_assignments_num, - "tenant_spaces": tenant_spaces_num, - "volumes": volumes_num, - "placement_groups": placement_groups_num, - "snapshots": snapshots_num, - "availability_zones": availability_zones_num, - "arrays": arrays_num, - "network_interfaces": network_interfaces_num, - "network_interface_groups": network_interface_groups_num, - "storage_endpoints": storage_endpoints_num, - } - - -@_api_permission_denied_handler("network_interfaces") -def generate_nics_dict(module, fusion): - nics_info = {} - nic_api_instance = purefusion.NetworkInterfacesApi(fusion) - arrays_api_instance = purefusion.ArraysApi(fusion) - az_api_instance = purefusion.AvailabilityZonesApi(fusion) - regions_api_instance = purefusion.RegionsApi(fusion) - regions = regions_api_instance.list_regions() - for region in regions.items: - azs = az_api_instance.list_availability_zones(region_name=region.name) - for az in azs.items: - array_details = arrays_api_instance.list_arrays( - availability_zone_name=az.name, - region_name=region.name, - ) - for array_detail in array_details.items: - array_name = az.name + "/" + array_detail.name - nics_info[array_name] = {} - nics = nic_api_instance.list_network_interfaces( - availability_zone_name=az.name, - region_name=region.name, - array_name=array_detail.name, - ) - - for nic in nics.items: - nics_info[array_name][nic.name] = { - "enabled": nic.enabled, - "display_name": nic.display_name, - "interface_type": nic.interface_type, - "services": nic.services, - "max_speed": nic.max_speed, - "vlan": nic.eth.vlan, - "address": nic.eth.address, - "mac_address": nic.eth.mac_address, - "gateway": nic.eth.gateway, - "mtu": nic.eth.mtu, - "network_interface_group": nic.network_interface_group.name, - "availability_zone": nic.availability_zone.name, - } - return nics_info - - -@_api_permission_denied_handler("host_access_policies") -def generate_hap_dict(module, fusion): - hap_info = {} - api_instance = purefusion.HostAccessPoliciesApi(fusion) - hosts = api_instance.list_host_access_policies() - for host in hosts.items: - name = host.name - hap_info[name] = { - "personality": host.personality, - "display_name": host.display_name, - "iqn": host.iqn, - } - return hap_info - - -@_api_permission_denied_handler("arrays") -def generate_array_dict(module, fusion): - array_info = {} - array_api_instance = purefusion.ArraysApi(fusion) - az_api_instance = purefusion.AvailabilityZonesApi(fusion) - regions_api_instance = purefusion.RegionsApi(fusion) - regions = regions_api_instance.list_regions() - for region in regions.items: - azs = az_api_instance.list_availability_zones(region_name=region.name) - for az in azs.items: - arrays = array_api_instance.list_arrays( - availability_zone_name=az.name, - region_name=region.name, - ) - for array in arrays.items: - array_name = array.name - array_space = array_api_instance.get_array_space( - availability_zone_name=az.name, - array_name=array_name, - region_name=region.name, - ) - array_perf = array_api_instance.get_array_performance( - availability_zone_name=az.name, - array_name=array_name, - region_name=region.name, - ) - array_info[array_name] = { - "region": region.name, - "availability_zone": az.name, - "host_name": array.host_name, - "maintenance_mode": array.maintenance_mode, - "unavailable_mode": array.unavailable_mode, - "display_name": array.display_name, - "hardware_type": array.hardware_type.name, - "appliance_id": array.appliance_id, - "apartment_id": getattr(array, "apartment_id", None), - "space": { - "total_physical_space": array_space.total_physical_space, - }, - "performance": { - "read_bandwidth": array_perf.read_bandwidth, - "read_latency_us": array_perf.read_latency_us, - "reads_per_sec": array_perf.reads_per_sec, - "write_bandwidth": array_perf.write_bandwidth, - "write_latency_us": array_perf.write_latency_us, - "writes_per_sec": array_perf.writes_per_sec, - }, - } - return array_info - - -@_api_permission_denied_handler("placement_groups") -def generate_pg_dict(module, fusion): - pg_info = {} - tenant_api_instance = purefusion.TenantsApi(fusion) - tenantspace_api_instance = purefusion.TenantSpacesApi(fusion) - pg_api_instance = purefusion.PlacementGroupsApi(fusion) - tenants = tenant_api_instance.list_tenants() - for tenant in tenants.items: - tenant_spaces = tenantspace_api_instance.list_tenant_spaces( - tenant_name=tenant.name - ).items - for tenant_space in tenant_spaces: - groups = pg_api_instance.list_placement_groups( - tenant_name=tenant.name, - tenant_space_name=tenant_space.name, - ) - for group in groups.items: - group_name = tenant.name + "/" + tenant_space.name + "/" + group.name - pg_info[group_name] = { - "tenant": group.tenant.name, - "display_name": group.display_name, - "placement_engine": group.placement_engine, - "tenant_space": group.tenant_space.name, - "az": group.availability_zone.name, - "array": getattr(group.array, "name", None), - } - return pg_info - - -@_api_permission_denied_handler("tenant_spaces") -def generate_ts_dict(module, fusion): - ts_info = {} - tenant_api_instance = purefusion.TenantsApi(fusion) - tenantspace_api_instance = purefusion.TenantSpacesApi(fusion) - tenants = tenant_api_instance.list_tenants() - for tenant in tenants.items: - tenant_spaces = tenantspace_api_instance.list_tenant_spaces( - tenant_name=tenant.name - ).items - for tenant_space in tenant_spaces: - ts_name = tenant.name + "/" + tenant_space.name - ts_info[ts_name] = { - "tenant": tenant.name, - "display_name": tenant_space.display_name, - } - return ts_info - - -@_api_permission_denied_handler("protection_policies") -def generate_pp_dict(module, fusion): - pp_info = {} - api_instance = purefusion.ProtectionPoliciesApi(fusion) - policies = api_instance.list_protection_policies() - for policy in policies.items: - policy_name = policy.name - pp_info[policy_name] = { - "objectives": policy.objectives, - } - return pp_info - - -@_api_permission_denied_handler("tenants") -def generate_tenant_dict(module, fusion): - tenants_api_instance = purefusion.TenantsApi(fusion) - return { - tenant.name: { - "display_name": tenant.display_name, - } - for tenant in tenants_api_instance.list_tenants().items - } - - -@_api_permission_denied_handler("regions") -def generate_regions_dict(module, fusion): - regions_api_instance = purefusion.RegionsApi(fusion) - return { - region.name: { - "display_name": region.display_name, - } - for region in regions_api_instance.list_regions().items - } - - -@_api_permission_denied_handler("availability_zones") -def generate_zones_dict(module, fusion): - zones_info = {} - az_api_instance = purefusion.AvailabilityZonesApi(fusion) - regions_api_instance = purefusion.RegionsApi(fusion) - regions = regions_api_instance.list_regions() - for region in regions.items: - zones = az_api_instance.list_availability_zones(region_name=region.name) - for zone in zones.items: - az_name = zone.name - zones_info[az_name] = { - "display_name": zone.display_name, - "region": zone.region.name, - } - return zones_info - - -@_api_permission_denied_handler("role_assignments") -def generate_ras_dict(module, fusion): - ras_info = {} - ras_api_instance = purefusion.RoleAssignmentsApi(fusion) - role_api_instance = purefusion.RolesApi(fusion) - roles = role_api_instance.list_roles() - for role in roles: - ras = ras_api_instance.list_role_assignments(role_name=role.name) - for assignment in ras: - name = assignment.name - ras_info[name] = { - "display_name": assignment.display_name, - "role": assignment.role.name, - "scope": assignment.scope.name, - } - return ras_info - - -@_api_permission_denied_handler("roles") -def generate_roles_dict(module, fusion): - roles_info = {} - api_instance = purefusion.RolesApi(fusion) - roles = api_instance.list_roles() - for role in roles: - name = role.name - roles_info[name] = { - "display_name": role.display_name, - "scopes": role.assignable_scopes, - } - return roles_info - - -@_api_permission_denied_handler("api_clients") -def generate_api_client_dict(module, fusion): - client_info = {} - api_instance = purefusion.IdentityManagerApi(fusion) - clients = api_instance.list_api_clients() - for client in clients: - client_info[client.name] = { - "display_name": client.display_name, - "issuer": client.issuer, - "public_key": client.public_key, - "creator_id": client.creator_id, - "last_key_update": time.strftime( - "%a, %d %b %Y %H:%M:%S %Z", - time.localtime(client.last_key_update / 1000), - ), - "last_used": time.strftime( - "%a, %d %b %Y %H:%M:%S %Z", - time.localtime(client.last_used / 1000), - ), - } - return client_info - - -@_api_permission_denied_handler("users") -def generate_users_dict(module, fusion): - users_info = {} - api_instance = purefusion.IdentityManagerApi(fusion) - users = api_instance.list_users() - for user in users: - users_info[user.name] = { - "display_name": user.display_name, - "email": user.email, - "id": user.id, - } - return users_info - - -@_api_permission_denied_handler("hardware_types") -def generate_hardware_types_dict(module, fusion): - hardware_info = {} - api_instance = purefusion.HardwareTypesApi(fusion) - hw_types = api_instance.list_hardware_types() - for hw_type in hw_types.items: - hardware_info[hw_type.name] = { - "array_type": hw_type.array_type, - "display_name": hw_type.display_name, - "media_type": hw_type.media_type, - } - return hardware_info - - -@_api_permission_denied_handler("storage_classes") -def generate_sc_dict(module, fusion): - sc_info = {} - ss_api_instance = purefusion.StorageServicesApi(fusion) - sc_api_instance = purefusion.StorageClassesApi(fusion) - services = ss_api_instance.list_storage_services() - for service in services.items: - classes = sc_api_instance.list_storage_classes( - storage_service_name=service.name, - ) - for s_class in classes.items: - sc_info[s_class.name] = { - "bandwidth_limit": getattr(s_class, "bandwidth_limit", None), - "iops_limit": getattr(s_class, "iops_limit", None), - "size_limit": getattr(s_class, "size_limit", None), - "display_name": s_class.display_name, - "storage_service": service.name, - } - return sc_info - - -@_api_permission_denied_handler("storage_services") -def generate_storserv_dict(module, fusion): - ss_dict = {} - ss_api_instance = purefusion.StorageServicesApi(fusion) - services = ss_api_instance.list_storage_services() - for service in services.items: - ss_dict[service.name] = { - "display_name": service.display_name, - "hardware_types": None, - } - # can be None if we don't have permission to see this - if service.hardware_types is not None: - ss_dict[service.name]["hardware_types"] = [] - for hwtype in service.hardware_types: - ss_dict[service.name]["hardware_types"].append(hwtype.name) - return ss_dict - - -@_api_permission_denied_handler("storage_endpoints") -def generate_se_dict(module, fusion): - se_dict = {} - se_api_instance = purefusion.StorageEndpointsApi(fusion) - az_api_instance = purefusion.AvailabilityZonesApi(fusion) - regions_api_instance = purefusion.RegionsApi(fusion) - regions = regions_api_instance.list_regions() - for region in regions.items: - azs = az_api_instance.list_availability_zones(region_name=region.name) - for az in azs.items: - endpoints = se_api_instance.list_storage_endpoints( - region_name=region.name, - availability_zone_name=az.name, - ) - for endpoint in endpoints.items: - name = region.name + "/" + az.name + "/" + endpoint.name - se_dict[name] = { - "display_name": endpoint.display_name, - "endpoint_type": endpoint.endpoint_type, - "iscsi_interfaces": [], - } - for iface in endpoint.iscsi.discovery_interfaces: - dct = { - "address": iface.address, - "gateway": iface.gateway, - "mtu": iface.mtu, - "network_interface_groups": None, - } - if iface.network_interface_groups is not None: - dct["network_interface_groups"] = [ - nig.name for nig in iface.network_interface_groups - ] - se_dict[name]["iscsi_interfaces"].append(dct) - return se_dict - - -@_api_permission_denied_handler("network_interface_groups") -def generate_nigs_dict(module, fusion): - nigs_dict = {} - nig_api_instance = purefusion.NetworkInterfaceGroupsApi(fusion) - az_api_instance = purefusion.AvailabilityZonesApi(fusion) - regions_api_instance = purefusion.RegionsApi(fusion) - regions = regions_api_instance.list_regions() - for region in regions.items: - azs = az_api_instance.list_availability_zones(region_name=region.name) - for az in azs.items: - nigs = nig_api_instance.list_network_interface_groups( - region_name=region.name, - availability_zone_name=az.name, - ) - for nig in nigs.items: - name = region.name + "/" + az.name + "/" + nig.name - nigs_dict[name] = { - "display_name": nig.display_name, - "gateway": nig.eth.gateway, - "prefix": nig.eth.prefix, - "mtu": nig.eth.mtu, - } - return nigs_dict - - -@_api_permission_denied_handler("snapshots") -def generate_snap_dicts(module, fusion): - snap_dict = {} - vsnap_dict = {} - tenant_api_instance = purefusion.TenantsApi(fusion) - tenantspace_api_instance = purefusion.TenantSpacesApi(fusion) - snap_api_instance = purefusion.SnapshotsApi(fusion) - vsnap_api_instance = purefusion.VolumeSnapshotsApi(fusion) - tenants = tenant_api_instance.list_tenants() - for tenant in tenants.items: - tenant_spaces = tenantspace_api_instance.list_tenant_spaces( - tenant_name=tenant.name - ).items - for tenant_space in tenant_spaces: - snaps = snap_api_instance.list_snapshots( - tenant_name=tenant.name, - tenant_space_name=tenant_space.name, - ) - for snap in snaps.items: - snap_name = tenant.name + "/" + tenant_space.name + "/" + snap.name - secs, mins, hours = _convert_microseconds(snap.time_remaining) - snap_dict[snap_name] = { - "display_name": snap.display_name, - "protection_policy": snap.protection_policy, - "time_remaining": "{0} hours, {1} mins, {2} secs".format( - int(hours), int(mins), int(secs) - ), - "volume_snapshots_link": snap.volume_snapshots_link, - } - vsnaps = vsnap_api_instance.list_volume_snapshots( - tenant_name=tenant.name, - tenant_space_name=tenant_space.name, - snapshot_name=snap.name, - ) - for vsnap in vsnaps.items: - vsnap_name = ( - tenant.name - + "/" - + tenant_space.name - + "/" - + snap.name - + "/" - + vsnap.name - ) - secs, mins, hours = _convert_microseconds(vsnap.time_remaining) - vsnap_dict[vsnap_name] = { - "size": vsnap.size, - "display_name": vsnap.display_name, - "protection_policy": vsnap.protection_policy, - "serial_number": vsnap.serial_number, - "created_at": time.strftime( - "%a, %d %b %Y %H:%M:%S %Z", - time.localtime(vsnap.created_at / 1000), - ), - "time_remaining": "{0} hours, {1} mins, {2} secs".format( - int(hours), int(mins), int(secs) - ), - "placement_group": vsnap.placement_group.name, - } - return snap_dict, vsnap_dict - - -@_api_permission_denied_handler("volumes") -def generate_volumes_dict(module, fusion): - volume_info = {} - - tenant_api_instance = purefusion.TenantsApi(fusion) - vol_api_instance = purefusion.VolumesApi(fusion) - tenant_space_api_instance = purefusion.TenantSpacesApi(fusion) - - tenants = tenant_api_instance.list_tenants() - for tenant in tenants.items: - tenant_spaces = tenant_space_api_instance.list_tenant_spaces( - tenant_name=tenant.name - ).items - for tenant_space in tenant_spaces: - volumes = vol_api_instance.list_volumes( - tenant_name=tenant.name, - tenant_space_name=tenant_space.name, - ) - for volume in volumes.items: - vol_name = tenant.name + "/" + tenant_space.name + "/" + volume.name - volume_info[vol_name] = { - "tenant": tenant.name, - "tenant_space": tenant_space.name, - "name": volume.name, - "size": volume.size, - "display_name": volume.display_name, - "placement_group": volume.placement_group.name, - "source_volume_snapshot": getattr( - volume.source_volume_snapshot, "name", None - ), - "protection_policy": getattr( - volume.protection_policy, "name", None - ), - "storage_class": volume.storage_class.name, - "serial_number": volume.serial_number, - "target": {}, - "array": getattr(volume.array, "name", None), - } - - volume_info[vol_name]["target"] = { - "iscsi": { - "addresses": volume.target.iscsi.addresses, - "iqn": volume.target.iscsi.iqn, - }, - "nvme": { - "addresses": None, - "nqn": None, - }, - "fc": { - "addresses": None, - "wwns": None, - }, - } - return volume_info - - -def main(): - argument_spec = fusion_argument_spec() - argument_spec.update( - dict(gather_subset=dict(default="minimum", type="list", elements="str")) - ) - - module = AnsibleModule(argument_spec, supports_check_mode=True) - - # will handle all errors (except #403 which should be handled in code) - fusion = setup_fusion(module) - - subset = [test.lower() for test in module.params["gather_subset"]] - valid_subsets = ( - "all", - "minimum", - "roles", - "users", - "placements", - "arrays", - "hardware_types", - "volumes", - "hosts", - "storage_classes", - "protection_policies", - "placement_groups", - "interfaces", - "zones", - "nigs", - "storage_endpoints", - "snapshots", - "storage_services", - "tenants", - "tenant_spaces", - "network_interface_groups", - "api_clients", - "availability_zones", - "host_access_policies", - "network_interfaces", - "regions", - ) - for option in subset: - if option not in valid_subsets: - module.fail_json( - msg=f"value gather_subset must be one or more of: {','.join(valid_subsets)}, got: {','.join(subset)}\nvalue {option} is not allowed" - ) - - info = {} - - if "minimum" in subset or "all" in subset: - info["default"] = generate_default_dict(module, fusion) - if "hardware_types" in subset or "all" in subset: - info["hardware_types"] = generate_hardware_types_dict(module, fusion) - if "users" in subset or "all" in subset: - info["users"] = generate_users_dict(module, fusion) - if "regions" in subset or "all" in subset: - info["regions"] = generate_regions_dict(module, fusion) - if "availability_zones" in subset or "all" in subset or "zones" in subset: - info["availability_zones"] = generate_zones_dict(module, fusion) - if "zones" in subset: - module.warn( - "The 'zones' subset is deprecated and will be removed in the version 2.0.0\nUse 'availability_zones' subset instead." - ) - if "roles" in subset or "all" in subset: - info["roles"] = generate_roles_dict(module, fusion) - info["role_assignments"] = generate_ras_dict(module, fusion) - if "storage_services" in subset or "all" in subset: - info["storage_services"] = generate_storserv_dict(module, fusion) - if "volumes" in subset or "all" in subset: - info["volumes"] = generate_volumes_dict(module, fusion) - if "protection_policies" in subset or "all" in subset: - info["protection_policies"] = generate_pp_dict(module, fusion) - if "placement_groups" in subset or "all" in subset or "placements" in subset: - info["placement_groups"] = generate_pg_dict(module, fusion) - if "placements" in subset: - module.warn( - "The 'placements' subset is deprecated and will be removed in the version 1.7.0" - ) - if "storage_classes" in subset or "all" in subset: - info["storage_classes"] = generate_sc_dict(module, fusion) - if "network_interfaces" in subset or "all" in subset or "interfaces" in subset: - info["network_interfaces"] = generate_nics_dict(module, fusion) - if "interfaces" in subset: - module.warn( - "The 'interfaces' subset is deprecated and will be removed in the version 2.0.0\nUse 'network_interfaces' subset instead." - ) - if "host_access_policies" in subset or "all" in subset or "hosts" in subset: - info["host_access_policies"] = generate_hap_dict(module, fusion) - if "hosts" in subset: - module.warn( - "The 'hosts' subset is deprecated and will be removed in the version 2.0.0\nUse 'host_access_policies' subset instead." - ) - if "arrays" in subset or "all" in subset: - info["arrays"] = generate_array_dict(module, fusion) - if "tenants" in subset or "all" in subset: - info["tenants"] = generate_tenant_dict(module, fusion) - if "tenant_spaces" in subset or "all" in subset: - info["tenant_spaces"] = generate_ts_dict(module, fusion) - if "storage_endpoints" in subset or "all" in subset: - info["storage_endpoints"] = generate_se_dict(module, fusion) - if "api_clients" in subset or "all" in subset: - info["api_clients"] = generate_api_client_dict(module, fusion) - if "network_interface_groups" in subset or "all" in subset or "nigs" in subset: - info["network_interface_groups"] = generate_nigs_dict(module, fusion) - if "nigs" in subset: - module.warn( - "The 'nigs' subset is deprecated and will be removed in the version 1.7.0" - ) - if "snapshots" in subset or "all" in subset: - snap_dicts = generate_snap_dicts(module, fusion) - if snap_dicts is not None: - info["snapshots"], info["volume_snapshots"] = snap_dicts - else: - info["snapshots"], info["volume_snapshots"] = None, None - - module.exit_json(changed=False, fusion_info=info) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_ni.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_ni.py deleted file mode 100644 index 82c896fac..000000000 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_ni.py +++ /dev/null @@ -1,245 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2023, Andrej Pajtas (apajtas@purestorage.com) -# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -DOCUMENTATION = r""" ---- -module: fusion_ni -version_added: '1.0.0' -short_description: Manage network interfaces in Pure Storage Fusion -description: -- Update parameters of network interfaces in Pure Storage Fusion. -notes: -- Supports C(check_mode). -author: -- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com> -options: - name: - description: - - The name of the network interface. - type: str - required: true - display_name: - description: - - The human name of the network interface. - - If not provided, defaults to I(name). - type: str - region: - description: - - The name of the region the availability zone is in. - type: str - required: true - availability_zone: - aliases: [ az ] - description: - - The name of the availability zone for the network interface. - type: str - required: true - array: - description: - - The name of the array the network interface belongs to. - type: str - required: true - eth: - description: - - The IP address associated with the network interface. - - IP address must include a CIDR notation. - - Only IPv4 is supported at the moment. - - Required together with `network_interface_group` parameter. - type: str - enabled: - description: - - True if network interface is in use. - type: bool - network_interface_group: - description: - - The name of the network interface group this network interface belongs to. - type: str -extends_documentation_fragment: -- purestorage.fusion.purestorage.fusion -""" - -EXAMPLES = r""" -- name: Patch network interface - purestorage.fusion.fusion_ni: - name: foo - region: us-west - availability_zone: bar - array: array0 - eth: 10.21.200.124/24 - enabled: true - network_interface_group: subnet-0 - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" -""" - -RETURN = r""" -""" - -try: - import fusion as purefusion -except ImportError: - pass - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import ( - fusion_argument_spec, -) - -from ansible_collections.purestorage.fusion.plugins.module_utils.getters import ( - get_array, - get_az, - get_region, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.networking import ( - is_valid_network, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.startup import ( - setup_fusion, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.operations import ( - await_operation, -) - - -def get_ni(module, fusion): - """Get Network Interface or None""" - ni_api_instance = purefusion.NetworkInterfacesApi(fusion) - try: - return ni_api_instance.get_network_interface( - region_name=module.params["region"], - availability_zone_name=module.params["availability_zone"], - array_name=module.params["array"], - net_intf_name=module.params["name"], - ) - except purefusion.rest.ApiException: - return None - - -def update_ni(module, fusion, ni): - """Update Network Interface""" - ni_api_instance = purefusion.NetworkInterfacesApi(fusion) - - patches = [] - if ( - module.params["display_name"] - and module.params["display_name"] != ni.display_name - ): - patch = purefusion.NetworkInterfacePatch( - display_name=purefusion.NullableString(module.params["display_name"]), - ) - patches.append(patch) - - if module.params["enabled"] is not None and module.params["enabled"] != ni.enabled: - patch = purefusion.NetworkInterfacePatch( - enabled=purefusion.NullableBoolean(module.params["enabled"]), - ) - patches.append(patch) - - if ( - module.params["network_interface_group"] - and module.params["network_interface_group"] != ni.network_interface_group - ): - if module.params["eth"] and module.params["eth"] != ni.eth: - patch = purefusion.NetworkInterfacePatch( - eth=purefusion.NetworkInterfacePatchEth( - purefusion.NullableString(module.params["eth"]) - ), - network_interface_group=purefusion.NullableString( - module.params["network_interface_group"] - ), - ) - else: - patch = purefusion.NetworkInterfacePatch( - network_interface_group=purefusion.NullableString( - module.params["network_interface_group"] - ), - ) - patches.append(patch) - id = None - if not module.check_mode: - for patch in patches: - op = ni_api_instance.update_network_interface( - patch, - region_name=module.params["region"], - availability_zone_name=module.params["availability_zone"], - array_name=module.params["array"], - net_intf_name=module.params["name"], - ) - res_op = await_operation(fusion, op) - id = res_op.result.resource.id - - changed = len(patches) != 0 - - module.exit_json(changed=changed, id=id) - - -def main(): - """Main code""" - argument_spec = fusion_argument_spec() - argument_spec.update( - dict( - name=dict(type="str", required=True), - display_name=dict(type="str"), - region=dict(type="str", required=True), - availability_zone=dict(type="str", required=True, aliases=["az"]), - array=dict(type="str", required=True), - eth=dict(type="str"), - enabled=dict(type="bool"), - network_interface_group=dict(type="str"), - ) - ) - - required_by = { - "eth": "network_interface_group", - } - - module = AnsibleModule( - argument_spec, - supports_check_mode=True, - required_by=required_by, - ) - - fusion = setup_fusion(module) - - if module.params["eth"] and not is_valid_network(module.params["eth"]): - module.fail_json( - msg="`eth` '{0}' is not a valid address in CIDR notation".format( - module.params["eth"] - ) - ) - - if not get_region(module, fusion): - module.fail_json( - msg="Region {0} does not exist.".format(module.params["region"]) - ) - - if not get_az(module, fusion): - module.fail_json( - msg="Availability Zone {0} does not exist.".format( - module.params["availability_zone"] - ) - ) - - if not get_array(module, fusion): - module.fail_json(msg="Array {0} does not exist.".format(module.params["array"])) - - ni = get_ni(module, fusion) - if not ni: - module.fail_json( - msg="Network Interface {0} does not exist".format(module.params["name"]) - ) - - update_ni(module, fusion, ni) - - module.exit_json(changed=False) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_nig.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_nig.py deleted file mode 100644 index d40b813b9..000000000 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_nig.py +++ /dev/null @@ -1,276 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2022, Simon Dodsley (simon@purestorage.com) -# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -DOCUMENTATION = r""" ---- -module: fusion_nig -version_added: '1.0.0' -short_description: Manage Network Interface Groups in Pure Storage Fusion -description: -- Create, delete and modify network interface groups in Pure Storage Fusion. -- Currently this only supports a single tenant subnet per tenant network -author: -- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com> -notes: -- Supports C(check mode). -options: - name: - description: - - The name of the network interface group. - type: str - required: true - display_name: - description: - - The human name of the network interface group. - - If not provided, defaults to I(name). - type: str - state: - description: - - Define whether the network interface group should exist or not. - type: str - default: present - choices: [ absent, present ] - availability_zone: - aliases: [ az ] - description: - - The name of the availability zone for the network interface group. - type: str - required: true - region: - description: - - Region for the network interface group. - type: str - required: true - gateway: - description: - - "Address of the subnet gateway. - Currently must be a valid IPv4 address." - type: str - mtu: - description: - - MTU setting for the subnet. - default: 1500 - type: int - group_type: - description: - - The type of network interface group. - type: str - default: eth - choices: [ eth ] - prefix: - description: - - "Network prefix in CIDR notation. - Required to create a new network interface group. - Currently only IPv4 addresses with subnet mask are supported." - type: str -extends_documentation_fragment: -- purestorage.fusion.purestorage.fusion -""" - -EXAMPLES = r""" -- name: Create new network interface group foo in AZ bar - purestorage.fusion.fusion_nig: - name: foo - availability_zone: bar - region: region1 - mtu: 9000 - gateway: 10.21.200.1 - prefix: 10.21.200.0/24 - state: present - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" - -- name: Delete network interface group foo in AZ bar - purestorage.fusion.fusion_nig: - name: foo - availability_zone: bar - region: region1 - state: absent - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" -""" - -RETURN = r""" -""" - -try: - import fusion as purefusion -except ImportError: - pass - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import ( - fusion_argument_spec, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.networking import ( - is_valid_address, - is_valid_network, - is_address_in_network, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.startup import ( - setup_fusion, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.operations import ( - await_operation, -) - - -def get_nig(module, fusion): - """Check Network Interface Group""" - nig_api_instance = purefusion.NetworkInterfaceGroupsApi(fusion) - try: - return nig_api_instance.get_network_interface_group( - availability_zone_name=module.params["availability_zone"], - region_name=module.params["region"], - network_interface_group_name=module.params["name"], - ) - except purefusion.rest.ApiException: - return None - - -def create_nig(module, fusion): - """Create Network Interface Group""" - - nig_api_instance = purefusion.NetworkInterfaceGroupsApi(fusion) - - changed = False - if module.params["gateway"] and not is_address_in_network( - module.params["gateway"], module.params["prefix"] - ): - module.fail_json(msg="`gateway` must be an address in subnet `prefix`") - - id = None - if not module.check_mode: - display_name = module.params["display_name"] or module.params["name"] - if module.params["group_type"] == "eth": - if module.params["gateway"]: - eth = purefusion.NetworkInterfaceGroupEthPost( - prefix=module.params["prefix"], - gateway=module.params["gateway"], - mtu=module.params["mtu"], - ) - else: - eth = purefusion.NetworkInterfaceGroupEthPost( - prefix=module.params["prefix"], - mtu=module.params["mtu"], - ) - nig = purefusion.NetworkInterfaceGroupPost( - group_type="eth", - eth=eth, - name=module.params["name"], - display_name=display_name, - ) - op = nig_api_instance.create_network_interface_group( - nig, - availability_zone_name=module.params["availability_zone"], - region_name=module.params["region"], - ) - res_op = await_operation(fusion, op) - id = res_op.result.resource.id - changed = True - else: - # to prevent future unintended error - module.warn(f"group_type={module.params['group_type']} is not implemented") - - module.exit_json(changed=changed, id=id) - - -def delete_nig(module, fusion): - """Delete Network Interface Group""" - changed = True - nig_api_instance = purefusion.NetworkInterfaceGroupsApi(fusion) - if not module.check_mode: - op = nig_api_instance.delete_network_interface_group( - availability_zone_name=module.params["availability_zone"], - region_name=module.params["region"], - network_interface_group_name=module.params["name"], - ) - await_operation(fusion, op) - module.exit_json(changed=changed) - - -def update_nig(module, fusion, nig): - """Update Network Interface Group""" - - nifg_api_instance = purefusion.NetworkInterfaceGroupsApi(fusion) - patches = [] - if ( - module.params["display_name"] - and module.params["display_name"] != nig.display_name - ): - patch = purefusion.NetworkInterfaceGroupPatch( - display_name=purefusion.NullableString(module.params["display_name"]), - ) - patches.append(patch) - - if not module.check_mode: - for patch in patches: - op = nifg_api_instance.update_network_interface_group( - patch, - availability_zone_name=module.params["availability_zone"], - region_name=module.params["region"], - network_interface_group_name=module.params["name"], - ) - await_operation(fusion, op) - - changed = len(patches) != 0 - - module.exit_json(changed=changed, id=nig.id) - - -def main(): - """Main code""" - argument_spec = fusion_argument_spec() - argument_spec.update( - dict( - name=dict(type="str", required=True), - display_name=dict(type="str"), - availability_zone=dict(type="str", required=True, aliases=["az"]), - region=dict(type="str", required=True), - prefix=dict(type="str"), - gateway=dict(type="str"), - mtu=dict(type="int", default=1500), - group_type=dict(type="str", default="eth", choices=["eth"]), - state=dict(type="str", default="present", choices=["absent", "present"]), - ) - ) - - module = AnsibleModule(argument_spec, supports_check_mode=True) - fusion = setup_fusion(module) - - state = module.params["state"] - if module.params["prefix"] and not is_valid_network(module.params["prefix"]): - module.fail_json( - msg="`prefix` '{0}' is not a valid address in CIDR notation".format( - module.params["prefix"] - ) - ) - if module.params["gateway"] and not is_valid_address(module.params["gateway"]): - module.fail_json( - msg="`gateway` '{0}' is not a valid address".format( - module.params["gateway"] - ) - ) - - nig = get_nig(module, fusion) - - if state == "present" and not nig: - module.fail_on_missing_params(["prefix"]) - create_nig(module, fusion) - elif state == "present" and nig: - update_nig(module, fusion, nig) - elif state == "absent" and nig: - delete_nig(module, fusion) - - module.exit_json(changed=False) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_pg.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_pg.py deleted file mode 100644 index 6d6f0eb94..000000000 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_pg.py +++ /dev/null @@ -1,307 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2022, Simon Dodsley (simon@purestorage.com) -# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -DOCUMENTATION = r""" ---- -module: fusion_pg -version_added: '1.0.0' -short_description: Manage placement groups in Pure Storage Fusion -description: -- Create, update or delete a placement groups in Pure Storage Fusion. -author: -- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com> -notes: -- Supports C(check mode). -options: - name: - description: - - The name of the placement group. - type: str - required: true - display_name: - description: - - The human name of the placement group. - - If not provided, defaults to I(name). - type: str - state: - description: - - Define whether the placement group should exist or not. - type: str - default: present - choices: [ absent, present ] - destroy_snapshots_on_delete: - description: - - "Before deleting placement group, snapshots within the placement group will be deleted." - - "If `false` then any snapshots will need to be deleted as a separate step before removing the placement group." - type: bool - tenant: - description: - - The name of the tenant. - type: str - required: true - tenant_space: - description: - - The name of the tenant space. - type: str - required: true - region: - description: - - The name of the region the availability zone is in. - type: str - availability_zone: - aliases: [ az ] - description: - - The name of the availability zone the placement group is in. - type: str - storage_service: - description: - - The name of the storage service to create the placement group for. - type: str - array: - description: - - "Array to place the placement group to. Changing it (i.e. manual migration) - is an elevated operation." - type: str - placement_engine: - description: - - For workload placement recommendations from Pure1 Meta, use C(pure1meta). - - Please note that this might increase volume creation time. - type: str - choices: [ heuristics, pure1meta ] -extends_documentation_fragment: -- purestorage.fusion.purestorage.fusion -""" - -EXAMPLES = r""" -- name: Create new placement group named foo - purestorage.fusion.fusion_pg: - name: foo - tenant: test - tenant_space: space_1 - availability_zone: az1 - region: region1 - storage_service: storage_service_1 - state: present - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" - -- name: Delete placement group foo - purestorage.fusion.fusion_pg: - name: foo - tenant: test - tenant_space: space_1 - state: absent - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" -""" - -RETURN = r""" -""" - -try: - import fusion as purefusion -except ImportError: - pass - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import ( - fusion_argument_spec, -) - -from ansible_collections.purestorage.fusion.plugins.module_utils.startup import ( - setup_fusion, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.operations import ( - await_operation, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.snapshots import ( - delete_snapshot, -) - - -def get_pg(module, fusion): - """Return Placement Group or None""" - pg_api_instance = purefusion.PlacementGroupsApi(fusion) - try: - return pg_api_instance.get_placement_group( - tenant_name=module.params["tenant"], - tenant_space_name=module.params["tenant_space"], - placement_group_name=module.params["name"], - ) - except purefusion.rest.ApiException: - return None - - -def create_pg(module, fusion): - """Create Placement Group""" - - pg_api_instance = purefusion.PlacementGroupsApi(fusion) - - if not module.check_mode: - if not module.params["display_name"]: - display_name = module.params["name"] - else: - display_name = module.params["display_name"] - group = purefusion.PlacementGroupPost( - availability_zone=module.params["availability_zone"], - name=module.params["name"], - display_name=display_name, - region=module.params["region"], - storage_service=module.params["storage_service"], - ) - op = pg_api_instance.create_placement_group( - group, - tenant_name=module.params["tenant"], - tenant_space_name=module.params["tenant_space"], - ) - res_op = await_operation(fusion, op) - id = res_op.result.resource.id - - return True, id - - -def update_display_name(module, fusion, patches, pg): - if not module.params["display_name"]: - return - if module.params["display_name"] == pg.display_name: - return - patch = purefusion.PlacementGroupPatch( - display_name=purefusion.NullableString(module.params["display_name"]), - ) - patches.append(patch) - - -def update_array(module, fusion, patches, pg): - if not module.params["array"]: - return - if not pg.array: - module.warn( - "cannot see placement group array, probably missing required permissions to change it" - ) - return - if pg.array.name == module.params["array"]: - return - - patch = purefusion.PlacementGroupPatch( - array=purefusion.NullableString(module.params["array"]), - ) - patches.append(patch) - - -def update_pg(module, fusion, pg): - """Update Placement Group""" - - pg_api_instance = purefusion.PlacementGroupsApi(fusion) - patches = [] - - update_display_name(module, fusion, patches, pg) - update_array(module, fusion, patches, pg) - - if not module.check_mode: - for patch in patches: - op = pg_api_instance.update_placement_group( - patch, - tenant_name=module.params["tenant"], - tenant_space_name=module.params["tenant_space"], - placement_group_name=module.params["name"], - ) - await_operation(fusion, op) - - changed = len(patches) != 0 - return changed - - -def delete_pg(module, fusion): - """Delete Placement Group""" - pg_api_instance = purefusion.PlacementGroupsApi(fusion) - if not module.check_mode: - if module.params["destroy_snapshots_on_delete"]: - snapshots_api = purefusion.SnapshotsApi(fusion) - snapshots = snapshots_api.list_snapshots( - placement_group=module.params["name"], - tenant_name=module.params["tenant"], - tenant_space_name=module.params["tenant_space"], - ) - for snap in snapshots.items: - delete_snapshot(fusion, snap, snapshots_api) - - op = pg_api_instance.delete_placement_group( - placement_group_name=module.params["name"], - tenant_name=module.params["tenant"], - tenant_space_name=module.params["tenant_space"], - ) - await_operation(fusion, op) - - return True - - -def main(): - """Main code""" - argument_spec = fusion_argument_spec() - argument_spec.update( - dict( - name=dict(type="str", required=True), - destroy_snapshots_on_delete=dict(type="bool"), - display_name=dict(type="str"), - tenant=dict(type="str", required=True), - tenant_space=dict(type="str", required=True), - region=dict(type="str"), - availability_zone=dict(type="str", aliases=["az"]), - storage_service=dict(type="str"), - state=dict(type="str", default="present", choices=["absent", "present"]), - array=dict(type="str"), - placement_engine=dict( - type="str", - choices=["heuristics", "pure1meta"], - removed_in_version="2.0.0", - removed_from_collection="purestorage.fusion", - ), - ) - ) - - module = AnsibleModule(argument_spec, supports_check_mode=True) - fusion = setup_fusion(module) - - if module.params["placement_engine"]: - module.warn("placement_engine parameter will be deprecated in version 2.0.0") - - changed = False - - state = module.params["state"] - pgroup = get_pg(module, fusion) - - id = None - if pgroup is not None: - id = pgroup.id - - if state == "present" and not pgroup: - module.fail_on_missing_params( - ["region", "availability_zone", "storage_service"] - ) - changed, id = create_pg(module, fusion) or changed - if module.params["array"]: - # changing placement requires additional update - pgroup = get_pg(module, fusion) - changedUpdate = update_pg(module, fusion, pgroup) - changed = changed | changedUpdate - elif state == "present" and pgroup: - changed = update_pg(module, fusion, pgroup) or changed - elif state == "absent" and pgroup: - changed = delete_pg(module, fusion) or changed - module.exit_json(changed=changed) - - if id is not None: - module.exit_json(changed=changed, id=id) - - module.exit_json(changed=changed) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_pp.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_pp.py deleted file mode 100644 index 216209d84..000000000 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_pp.py +++ /dev/null @@ -1,208 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2022, Simon Dodsley (simon@purestorage.com) -# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -DOCUMENTATION = r""" ---- -module: fusion_pp -version_added: '1.0.0' -short_description: Manage protection policies in Pure Storage Fusion -description: -- Manage protection policies in Pure Storage Fusion. -author: -- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com> -notes: -- Supports C(check mode). -options: - name: - description: - - The name of the protection policy. - type: str - required: true - state: - description: - - Define whether the protection policy should exist or not. - default: present - choices: [ present, absent ] - type: str - destroy_snapshots_on_delete: - description: - - "Before deleting protection policy, snapshots within the protection policy will be deleted." - - "If `false` then any snapshots will need to be deleted as a separate step before removing the protection policy." - type: bool - display_name: - description: - - The human name of the protection policy. - - If not provided, defaults to I(name). - type: str - local_rpo: - description: - - Recovery Point Objective for snapshots. - - Minimum value is 10 minutes. - - Value can be provided as m(inutes), h(ours), - d(ays), w(eeks), or y(ears). - - If no unit is provided, minutes are assumed. - type: str - local_retention: - description: - - Retention Duration for periodic snapshots. - - Minimum value is 10 minutes. - - Value can be provided as m(inutes), h(ours), - d(ays), w(eeks), or y(ears). - - If no unit is provided, minutes are assumed. - type: str -extends_documentation_fragment: -- purestorage.fusion.purestorage.fusion -""" - -EXAMPLES = r""" -- name: Create new protection policy foo - purestorage.fusion.fusion_pp: - name: foo - local_rpo: 10 - local_retention: 4d - display_name: "foo pp" - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" - -- name: Delete protection policy foo - purestorage.fusion.fusion_pp: - name: foo - state: absent - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" -""" - -RETURN = r""" -""" - -try: - import fusion as purefusion -except ImportError: - pass - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import ( - fusion_argument_spec, -) - -from ansible_collections.purestorage.fusion.plugins.module_utils.parsing import ( - parse_minutes, -) - -from ansible_collections.purestorage.fusion.plugins.module_utils.startup import ( - setup_fusion, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.operations import ( - await_operation, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.snapshots import ( - delete_snapshot, -) - - -def get_pp(module, fusion): - """Return Protection Policy or None""" - pp_api_instance = purefusion.ProtectionPoliciesApi(fusion) - try: - return pp_api_instance.get_protection_policy( - protection_policy_name=module.params["name"] - ) - except purefusion.rest.ApiException: - return None - - -def create_pp(module, fusion): - """Create Protection Policy""" - - pp_api_instance = purefusion.ProtectionPoliciesApi(fusion) - local_rpo = parse_minutes(module, module.params["local_rpo"]) - local_retention = parse_minutes(module, module.params["local_retention"]) - if local_retention < 10: - module.fail_json(msg="Local Retention must be a minimum of 10 minutes") - if local_rpo < 10: - module.fail_json(msg="Local RPO must be a minimum of 10 minutes") - - changed = True - id = None - if not module.check_mode: - if not module.params["display_name"]: - display_name = module.params["name"] - else: - display_name = module.params["display_name"] - op = pp_api_instance.create_protection_policy( - purefusion.ProtectionPolicyPost( - name=module.params["name"], - display_name=display_name, - objectives=[ - purefusion.RPO(type="RPO", rpo="PT" + str(local_rpo) + "M"), - purefusion.Retention( - type="Retention", after="PT" + str(local_retention) + "M" - ), - ], - ) - ) - res_op = await_operation(fusion, op) - id = res_op.result.resource.id - - module.exit_json(changed=changed, id=id) - - -def delete_pp(module, fusion): - """Delete Protection Policy""" - pp_api_instance = purefusion.ProtectionPoliciesApi(fusion) - changed = True - if not module.check_mode: - if module.params["destroy_snapshots_on_delete"]: - protection_policy = get_pp(module, fusion) - snapshots_api = purefusion.SnapshotsApi(fusion) - snapshots = snapshots_api.query_snapshots( - protection_policy_id=protection_policy.id - ) - for snap in snapshots.items: - delete_snapshot(fusion, snap, snapshots_api) - - op = pp_api_instance.delete_protection_policy( - protection_policy_name=module.params["name"], - ) - await_operation(fusion, op) - - module.exit_json(changed=changed) - - -def main(): - """Main code""" - argument_spec = fusion_argument_spec() - argument_spec.update( - dict( - name=dict(type="str", required=True), - destroy_snapshots_on_delete=dict(type="bool"), - display_name=dict(type="str"), - local_rpo=dict(type="str"), - local_retention=dict(type="str"), - state=dict(type="str", default="present", choices=["present", "absent"]), - ) - ) - module = AnsibleModule(argument_spec, supports_check_mode=True) - fusion = setup_fusion(module) - - state = module.params["state"] - policy = get_pp(module, fusion) - - if not policy and state == "present": - module.fail_on_missing_params(["local_rpo", "local_retention"]) - create_pp(module, fusion) - elif policy and state == "absent": - delete_pp(module, fusion) - - module.exit_json(changed=False) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_ra.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_ra.py deleted file mode 100644 index c2ae2d5cf..000000000 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_ra.py +++ /dev/null @@ -1,284 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2022, Simon Dodsley (simon@purestorage.com) -# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -DOCUMENTATION = r""" ---- -module: fusion_ra -version_added: '1.0.0' -short_description: Manage role assignments in Pure Storage Fusion -description: -- Create or delete a storage class in Pure Storage Fusion. -author: -- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com> -notes: -- Supports C(check mode). -options: - role: - description: - - The name of the role to be assigned/unassigned. - type: str - required: true - state: - description: - - Define whether the role assingment should exist or not. - type: str - default: present - choices: [ absent, present ] - user: - description: - - The username to assign the role to. - - Currently this only supports the Pure1 App ID. - - This should be provide in the same format as I(issuer_id). - type: str - principal: - description: - - The unique ID of the principal (User or API Client) to assign to the role. - type: str - api_client_key: - description: - - The issuer ID of the API client to assign the role to. - type: str - scope: - description: - - The level to which the role is assigned. - choices: [ organization, tenant, tenant_space ] - default: organization - type: str - tenant: - description: - - The name of the tenant the user has the role applied to. - - Must be provided if I(scope) is set to either C(tenant) or C(tenant_space). - type: str - tenant_space: - description: - - The name of the tenant_space the user has the role applied to. - - Must be provided if I(scope) is set to C(tenant_space). - type: str -extends_documentation_fragment: -- purestorage.fusion.purestorage.fusion -""" - -EXAMPLES = r""" -- name: Assign role foo to user in tenant bar - purestorage.fusion.fusion_ra: - name: foo - user: key_name - tenant: bar - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" - -- name: Delete role foo from user in tenant bar - purestorage.fusion.fusion_ra: - name: foo - user: key_name - tenant: bar - state: absent - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" -""" - -RETURN = r""" -""" - -try: - import fusion as purefusion -except ImportError: - pass - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import ( - fusion_argument_spec, -) - -from ansible_collections.purestorage.fusion.plugins.module_utils.operations import ( - await_operation, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.startup import ( - setup_fusion, -) - - -def get_principal(module, fusion): - if module.params["principal"]: - return module.params["principal"] - if module.params["user"]: - principal = user_to_principal(fusion, module.params["user"]) - if not principal: - module.fail_json( - msg="User {0} does not exist".format(module.params["user"]) - ) - return principal - if module.params["api_client_key"]: - principal = apiclient_to_principal(fusion, module.params["api_client_key"]) - if not principal: - module.fail_json( - msg="API Client with key {0} does not exist".format( - module.params["api_client_key"] - ) - ) - return principal - - -def user_to_principal(fusion, user_id): - """Given a human-readable Fusion user, such as a Pure 1 App ID - return the associated principal - """ - id_api_instance = purefusion.IdentityManagerApi(fusion) - users = id_api_instance.list_users() - for user in users: - if user.name == user_id: - return user.id - return None - - -def apiclient_to_principal(fusion, api_client_key): - """Given an API client issuer ID, such as "pure1:apikey:123xXxyYyzYzASDF", - return the associated principal - """ - id_api_instance = purefusion.IdentityManagerApi(fusion) - api_clients = id_api_instance.list_users(name=api_client_key) - if len(api_clients) > 0: - return api_clients[0].id - return None - - -def get_scope(params): - """Given a scope type and associated tenant - and tenant_space, return the scope_link - """ - scope_link = None - if params["scope"] == "organization": - scope_link = "/" - elif params["scope"] == "tenant": - scope_link = "/tenants/" + params["tenant"] - elif params["scope"] == "tenant_space": - scope_link = ( - "/tenants/" + params["tenant"] + "/tenant-spaces/" + params["tenant_space"] - ) - return scope_link - - -def get_ra(module, fusion): - """Return Role Assignment or None""" - ra_api_instance = purefusion.RoleAssignmentsApi(fusion) - try: - principal = get_principal(module, fusion) - assignments = ra_api_instance.list_role_assignments( - role_name=module.params["role"], - principal=principal, - ) - for assign in assignments: - scope = get_scope(module.params) - if assign.scope.self_link == scope: - return assign - return None - except purefusion.rest.ApiException: - return None - - -def create_ra(module, fusion): - """Create Role Assignment""" - - ra_api_instance = purefusion.RoleAssignmentsApi(fusion) - - changed = True - id = None - if not module.check_mode: - principal = get_principal(module, fusion) - scope = get_scope(module.params) - assignment = purefusion.RoleAssignmentPost(scope=scope, principal=principal) - op = ra_api_instance.create_role_assignment( - assignment, role_name=module.params["role"] - ) - res_op = await_operation(fusion, op) - id = res_op.result.resource.id - - module.exit_json(changed=changed, id=id) - - -def delete_ra(module, fusion): - """Delete Role Assignment""" - changed = True - ra_api_instance = purefusion.RoleAssignmentsApi(fusion) - if not module.check_mode: - ra_name = get_ra(module, fusion).name - op = ra_api_instance.delete_role_assignment( - role_name=module.params["role"], role_assignment_name=ra_name - ) - await_operation(fusion, op) - - module.exit_json(changed=changed) - - -def main(): - """Main code""" - argument_spec = fusion_argument_spec() - argument_spec.update( - dict( - api_client_key=dict(type="str", no_log=True), - principal=dict(type="str"), - role=dict( - type="str", - required=True, - deprecated_aliases=[ - dict( - name="name", - date="2023-07-26", - collection_name="purefusion.fusion", - ) - ], - ), - scope=dict( - type="str", - default="organization", - choices=["organization", "tenant", "tenant_space"], - ), - state=dict(type="str", default="present", choices=["present", "absent"]), - tenant=dict(type="str"), - tenant_space=dict(type="str"), - user=dict(type="str"), - ) - ) - - required_if = [ - ["scope", "tenant", ["tenant"]], - ["scope", "tenant_space", ["tenant", "tenant_space"]], - ] - mutually_exclusive = [ - ("user", "principal", "api_client_key"), - ] - required_one_of = [ - ("user", "principal", "api_client_key"), - ] - - module = AnsibleModule( - argument_spec, - required_if=required_if, - supports_check_mode=True, - mutually_exclusive=mutually_exclusive, - required_one_of=required_one_of, - ) - fusion = setup_fusion(module) - - state = module.params["state"] - role_assignment = get_ra(module, fusion) - - if not role_assignment and state == "present": - create_ra(module, fusion) - elif role_assignment and state == "absent": - delete_ra(module, fusion) - else: - module.exit_json(changed=False) - - module.exit_json(changed=False) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_region.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_region.py deleted file mode 100644 index de40e7dc2..000000000 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_region.py +++ /dev/null @@ -1,182 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2022, Simon Dodsley (simon@purestorage.com) -# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -DOCUMENTATION = r""" ---- -module: fusion_region -version_added: '1.1.0' -short_description: Manage Regions in Pure Storage Fusion -description: -- Manage regions in Pure Storage Fusion. -author: -- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com> -notes: -- Supports C(check mode). -options: - name: - description: - - The name of the Region. - type: str - required: true - state: - description: - - Define whether the Region should exist or not. - default: present - choices: [ present, absent ] - type: str - display_name: - description: - - The human name of the Region. - - If not provided, defaults to I(name). - type: str -extends_documentation_fragment: -- purestorage.fusion.purestorage.fusion -""" - -EXAMPLES = r""" -- name: Create new region foo - purestorage.fusion.fusion_region: - name: foo - display_name: "foo Region" - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" - -- name: Update region foo - purestorage.fusion.fusion_region: - name: foo - display_name: "new foo Region" - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" - -- name: Delete region foo - purestorage.fusion.fusion_region: - name: foo - state: absent - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" -""" - -RETURN = r""" -""" - -try: - import fusion as purefusion -except ImportError: - pass - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import ( - fusion_argument_spec, -) - -from ansible_collections.purestorage.fusion.plugins.module_utils.operations import ( - await_operation, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.startup import ( - setup_fusion, -) -from ansible_collections.purestorage.fusion.plugins.module_utils import getters - - -def get_region(module, fusion): - """Get Region or None""" - return getters.get_region(module, fusion, module.params["name"]) - - -def create_region(module, fusion): - """Create Region""" - - reg_api_instance = purefusion.RegionsApi(fusion) - - changed = True - id = None - if not module.check_mode: - if not module.params["display_name"]: - display_name = module.params["name"] - else: - display_name = module.params["display_name"] - region = purefusion.RegionPost( - name=module.params["name"], - display_name=display_name, - ) - op = reg_api_instance.create_region(region) - res_op = await_operation(fusion, op) - id = res_op.result.resource.id - - module.exit_json(changed=changed, id=id) - - -def delete_region(module, fusion): - """Delete Region""" - - reg_api_instance = purefusion.RegionsApi(fusion) - - changed = True - if not module.check_mode: - op = reg_api_instance.delete_region(region_name=module.params["name"]) - await_operation(fusion, op) - - module.exit_json(changed=changed) - - -def update_region(module, fusion, region): - """Update Region settings""" - changed = False - reg_api_instance = purefusion.RegionsApi(fusion) - - if ( - module.params["display_name"] - and module.params["display_name"] != region.display_name - ): - changed = True - if not module.check_mode: - reg = purefusion.RegionPatch( - display_name=purefusion.NullableString(module.params["display_name"]) - ) - op = reg_api_instance.update_region( - reg, - region_name=module.params["name"], - ) - await_operation(fusion, op) - - module.exit_json(changed=changed, id=region.id) - - -def main(): - """Main code""" - argument_spec = fusion_argument_spec() - argument_spec.update( - dict( - name=dict(type="str", required=True), - display_name=dict(type="str"), - state=dict(type="str", default="present", choices=["present", "absent"]), - ) - ) - - module = AnsibleModule(argument_spec, supports_check_mode=True) - fusion = setup_fusion(module) - - state = module.params["state"] - region = get_region(module, fusion) - - if not region and state == "present": - create_region(module, fusion) - elif region and state == "present": - update_region(module, fusion, region) - elif region and state == "absent": - delete_region(module, fusion) - else: - module.exit_json(changed=False) - - module.exit_json(changed=False) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_sc.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_sc.py deleted file mode 100644 index 59fc0025e..000000000 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_sc.py +++ /dev/null @@ -1,257 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2022, Simon Dodsley (simon@purestorage.com) -# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -DOCUMENTATION = r""" ---- -module: fusion_sc -version_added: '1.0.0' -short_description: Manage storage classes in Pure Storage Fusion -description: -- Manage a storage class in Pure Storage Fusion. -notes: -- Supports C(check_mode). -- It is not currently possible to update bw_limit or - iops_limit after a storage class has been created. -author: -- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com> -options: - name: - description: - - The name of the storage class. - type: str - required: true - state: - description: - - Define whether the storage class should exist or not. - default: present - choices: [ present, absent ] - type: str - display_name: - description: - - The human name of the storage class. - - If not provided, defaults to I(name). - type: str - size_limit: - description: - - Volume size limit in M, G, T or P units. - - Must be between 1MB and 4PB. - - If not provided at creation, this will default to 4PB. - type: str - bw_limit: - description: - - The bandwidth limit in M or G units. - M will set MB/s. - G will set GB/s. - - Must be between 1MB/s and 512GB/s. - - If not provided at creation, this will default to 512GB/s. - type: str - iops_limit: - description: - - The IOPs limit - use value or K or M. - K will mean 1000. - M will mean 1000000. - - Must be between 100 and 100000000. - - If not provided at creation, this will default to 100000000. - type: str - storage_service: - description: - - Storage service to which the storage class belongs. - type: str - required: true -extends_documentation_fragment: -- purestorage.fusion.purestorage.fusion -""" - -EXAMPLES = r""" -- name: Create new storage class foo - purestorage.fusion.fusion_sc: - name: foo - size_limit: 100G - iops_limit: 100000 - bw_limit: 25M - storage_service: service1 - display_name: "test class" - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" - -- name: Update storage class (only display_name change is supported) - purestorage.fusion.fusion_sc: - name: foo - display_name: "main class" - storage_service: service1 - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" - -- name: Delete storage class - purestorage.fusion.fusion_sc: - name: foo - storage_service: service1 - state: absent - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" -""" - -RETURN = r""" -""" - -try: - import fusion as purefusion -except ImportError: - pass - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import ( - fusion_argument_spec, -) - -from ansible_collections.purestorage.fusion.plugins.module_utils.parsing import ( - parse_number_with_metric_suffix, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.startup import ( - setup_fusion, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.operations import ( - await_operation, -) - - -def get_sc(module, fusion): - """Return Storage Class or None""" - sc_api_instance = purefusion.StorageClassesApi(fusion) - try: - return sc_api_instance.get_storage_class( - storage_class_name=module.params["name"], - storage_service_name=module.params["storage_service"], - ) - except purefusion.rest.ApiException: - return None - - -def create_sc(module, fusion): - """Create Storage Class""" - - sc_api_instance = purefusion.StorageClassesApi(fusion) - - if not module.params["size_limit"]: - module.params["size_limit"] = "4P" - if not module.params["iops_limit"]: - module.params["iops_limit"] = "100000000" - if not module.params["bw_limit"]: - module.params["bw_limit"] = "512G" - size_limit = parse_number_with_metric_suffix(module, module.params["size_limit"]) - iops_limit = int( - parse_number_with_metric_suffix( - module, module.params["iops_limit"], factor=1000 - ) - ) - bw_limit = parse_number_with_metric_suffix(module, module.params["bw_limit"]) - if bw_limit < 1048576 or bw_limit > 549755813888: # 1MB/s to 512GB/s - module.fail_json(msg="Bandwidth limit is not within the required range") - if iops_limit < 100 or iops_limit > 100_000_000: - module.fail_json(msg="IOPs limit is not within the required range") - if size_limit < 1048576 or size_limit > 4503599627370496: # 1MB to 4PB - module.fail_json(msg="Size limit is not within the required range") - - changed = True - id = None - if not module.check_mode: - if not module.params["display_name"]: - display_name = module.params["name"] - else: - display_name = module.params["display_name"] - s_class = purefusion.StorageClassPost( - name=module.params["name"], - size_limit=size_limit, - iops_limit=iops_limit, - bandwidth_limit=bw_limit, - display_name=display_name, - ) - op = sc_api_instance.create_storage_class( - s_class, storage_service_name=module.params["storage_service"] - ) - res_op = await_operation(fusion, op) - id = res_op.result.resource.id - - module.exit_json(changed=changed, id=id) - - -def update_sc(module, fusion, s_class): - """Update Storage Class settings""" - changed = False - sc_api_instance = purefusion.StorageClassesApi(fusion) - - if ( - module.params["display_name"] - and module.params["display_name"] != s_class.display_name - ): - changed = True - if not module.check_mode: - sclass = purefusion.StorageClassPatch( - display_name=purefusion.NullableString(module.params["display_name"]) - ) - op = sc_api_instance.update_storage_class( - sclass, - storage_service_name=module.params["storage_service"], - storage_class_name=module.params["name"], - ) - await_operation(fusion, op) - - module.exit_json(changed=changed, id=s_class.id) - - -def delete_sc(module, fusion): - """Delete Storage Class""" - sc_api_instance = purefusion.StorageClassesApi(fusion) - changed = True - if not module.check_mode: - op = sc_api_instance.delete_storage_class( - storage_class_name=module.params["name"], - storage_service_name=module.params["storage_service"], - ) - await_operation(fusion, op) - - module.exit_json(changed=changed) - - -def main(): - """Main code""" - argument_spec = fusion_argument_spec() - argument_spec.update( - dict( - name=dict(type="str", required=True), - display_name=dict(type="str"), - iops_limit=dict(type="str"), - bw_limit=dict(type="str"), - size_limit=dict(type="str"), - storage_service=dict(type="str", required=True), - state=dict(type="str", default="present", choices=["present", "absent"]), - ) - ) - - module = AnsibleModule(argument_spec, supports_check_mode=True) - fusion = setup_fusion(module) - - state = module.params["state"] - s_class = get_sc(module, fusion) - - if not s_class and state == "present": - create_sc(module, fusion) - elif s_class and state == "present": - update_sc(module, fusion, s_class) - elif s_class and state == "absent": - delete_sc(module, fusion) - else: - module.exit_json(changed=False) - - module.exit_json(changed=False) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_se.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_se.py deleted file mode 100644 index 3a191a166..000000000 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_se.py +++ /dev/null @@ -1,508 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2023, Simon Dodsley (simon@purestorage.com), Jan Kodera (jkodera@purestorage.com) -# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -DOCUMENTATION = r""" ---- -module: fusion_se -version_added: '1.0.0' -short_description: Manage storage endpoints in Pure Storage Fusion -description: -- Create or delete storage endpoints in Pure Storage Fusion. -notes: -- Supports C(check_mode). -author: -- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com> -options: - name: - description: - - The name of the storage endpoint. - type: str - required: true - display_name: - description: - - The human name of the storage endpoint. - - If not provided, defaults to I(name). - type: str - state: - description: - - Define whether the storage endpoint should exist or not. - type: str - default: present - choices: [ absent, present ] - region: - description: - - The name of the region the availability zone is in - type: str - required: true - availability_zone: - aliases: [ az ] - description: - - The name of the availability zone for the storage endpoint. - type: str - required: true - endpoint_type: - description: - - "DEPRECATED: Will be removed in version 2.0.0" - - Type of the storage endpoint. Only iSCSI is available at the moment. - type: str - iscsi: - description: - - List of discovery interfaces. - type: list - elements: dict - suboptions: - address: - description: - - IP address to be used in the subnet of the storage endpoint. - - IP address must include a CIDR notation. - - Only IPv4 is supported at the moment. - type: str - gateway: - description: - - Address of the subnet gateway. - type: str - network_interface_groups: - description: - - List of network interface groups to assign to the address. - type: list - elements: str - cbs_azure_iscsi: - description: - - CBS Azure iSCSI - type: dict - suboptions: - storage_endpoint_collection_identity: - description: - - The Storage Endpoint Collection Identity which belongs to the Azure entities. - type: str - load_balancer: - description: - - The Load Balancer id which gives permissions to CBS array applications to modify the Load Balancer. - type: str - load_balancer_addresses: - description: - - The IPv4 addresses of the Load Balancer. - type: list - elements: str - network_interface_groups: - description: - - "DEPRECATED: Will be removed in version 2.0.0" - - List of network interface groups to assign to the storage endpoints. - type: list - elements: str - addresses: - description: - - "DEPRECATED: Will be removed in version 2.0.0" - - List of IP addresses to be used in the subnet of the storage endpoint. - - IP addresses must include a CIDR notation. - - Only IPv4 is supported at the moment. - type: list - elements: str - gateway: - description: - - "DEPRECATED: Will be removed in version 2.0.0" - - Address of the subnet gateway. - - Currently this must be provided. - type: str - -extends_documentation_fragment: -- purestorage.fusion.purestorage.fusion -""" - -EXAMPLES = r""" -- name: Create new storage endpoint foo in AZ bar - purestorage.fusion.fusion_se: - name: foo - availability_zone: bar - region: us-west - iscsi: - - address: 10.21.200.124/24 - gateway: 10.21.200.1 - network_interface_groups: - - subnet-0 - - address: 10.21.200.36/24 - gateway: 10.21.200.2 - network_interface_groups: - - subnet-0 - - subnet-1 - state: present - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" - -- name: Create new CBS storage endpoint foo in AZ bar - purestorage.fusion.fusion_se: - name: foo - availability_zone: bar - region: us-west - cbs_azure_iscsi: - storage_endpoint_collection_identity: "/subscriptions/sub/resourcegroups/sec/providers/ms/userAssignedIdentities/secId" - load_balancer: "/subscriptions/sub/resourcegroups/sec/providers/ms/loadBalancers/sec-lb" - load_balancer_addresses: - - 10.21.200.1 - - 10.21.200.2 - state: present - app_id: key_name - key_file: "az-admin-private-key.pem" - -- name: Delete storage endpoint foo in AZ bar - purestorage.fusion.fusion_se: - name: foo - availability_zone: bar - region: us-west - state: absent - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" - -- name: (DEPRECATED) Create new storage endpoint foo in AZ bar - purestorage.fusion.fusion_se: - name: foo - availability_zone: bar - gateway: 10.21.200.1 - region: us-west - addresses: - - 10.21.200.124/24 - - 10.21.200.36/24 - network_interface_groups: - - subnet-0 - state: present - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" -""" - -RETURN = r""" -""" - -try: - import fusion as purefusion -except ImportError: - pass - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import ( - fusion_argument_spec, -) - -from ansible_collections.purestorage.fusion.plugins.module_utils.networking import ( - is_valid_network, - is_valid_address, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.startup import ( - setup_fusion, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.operations import ( - await_operation, -) - - -####################################################################### -# DEPRECATED CODE SECTION STARTS - - -def create_se_old(module, fusion): - """Create Storage Endpoint""" - - se_api_instance = purefusion.StorageEndpointsApi(fusion) - - changed = True - - if not module.check_mode: - if not module.params["display_name"]: - display_name = module.params["name"] - else: - display_name = module.params["display_name"] - ifaces = [] - for address in module.params["addresses"]: - if module.params["gateway"]: - iface = purefusion.StorageEndpointIscsiDiscoveryInterfacePost( - address=address, - gateway=module.params["gateway"], - network_interface_groups=module.params["network_interface_groups"], - ) - else: - iface = purefusion.StorageEndpointIscsiDiscoveryInterfacePost( - address=address, - network_interface_groups=module.params["network_interface_groups"], - ) - ifaces.append(iface) - op = purefusion.StorageEndpointPost( - endpoint_type="iscsi", - iscsi=purefusion.StorageEndpointIscsiPost( - discovery_interfaces=ifaces, - ), - name=module.params["name"], - display_name=display_name, - ) - op = se_api_instance.create_storage_endpoint( - op, - region_name=module.params["region"], - availability_zone_name=module.params["availability_zone"], - ) - await_operation(fusion, op) - - module.exit_json(changed=changed) - - -# DEPRECATED CODE SECTION ENDS -####################################################################### - - -def get_se(module, fusion): - """Storage Endpoint or None""" - se_api_instance = purefusion.StorageEndpointsApi(fusion) - try: - return se_api_instance.get_storage_endpoint( - region_name=module.params["region"], - storage_endpoint_name=module.params["name"], - availability_zone_name=module.params["availability_zone"], - ) - except purefusion.rest.ApiException: - return None - - -def create_se(module, fusion): - """Create Storage Endpoint""" - se_api_instance = purefusion.StorageEndpointsApi(fusion) - id = None - if not module.check_mode: - endpoint_type = None - - iscsi = None - if module.params["iscsi"] is not None: - iscsi = purefusion.StorageEndpointIscsiPost( - discovery_interfaces=[ - purefusion.StorageEndpointIscsiDiscoveryInterfacePost(**endpoint) - for endpoint in module.params["iscsi"] - ] - ) - endpoint_type = "iscsi" - - cbs_azure_iscsi = None - if module.params["cbs_azure_iscsi"] is not None: - cbs_azure_iscsi = purefusion.StorageEndpointCbsAzureIscsiPost( - storage_endpoint_collection_identity=module.params["cbs_azure_iscsi"][ - "storage_endpoint_collection_identity" - ], - load_balancer=module.params["cbs_azure_iscsi"]["load_balancer"], - load_balancer_addresses=module.params["cbs_azure_iscsi"][ - "load_balancer_addresses" - ], - ) - endpoint_type = "cbs-azure-iscsi" - - op = se_api_instance.create_storage_endpoint( - purefusion.StorageEndpointPost( - name=module.params["name"], - display_name=module.params["display_name"] or module.params["name"], - endpoint_type=endpoint_type, - iscsi=iscsi, - cbs_azure_iscsi=cbs_azure_iscsi, - ), - region_name=module.params["region"], - availability_zone_name=module.params["availability_zone"], - ) - res_op = await_operation(fusion, op) - id = res_op.result.resource.id - - module.exit_json(changed=True, id=id) - - -def delete_se(module, fusion): - """Delete Storage Endpoint""" - se_api_instance = purefusion.StorageEndpointsApi(fusion) - if not module.check_mode: - op = se_api_instance.delete_storage_endpoint( - region_name=module.params["region"], - availability_zone_name=module.params["availability_zone"], - storage_endpoint_name=module.params["name"], - ) - await_operation(fusion, op) - module.exit_json(changed=True) - - -def update_se(module, fusion, se): - """Update Storage Endpoint""" - - se_api_instance = purefusion.StorageEndpointsApi(fusion) - patches = [] - if ( - module.params["display_name"] - and module.params["display_name"] != se.display_name - ): - patch = purefusion.StorageEndpointPatch( - display_name=purefusion.NullableString(module.params["display_name"]), - ) - patches.append(patch) - - if not module.check_mode: - for patch in patches: - op = se_api_instance.update_storage_endpoint( - patch, - region_name=module.params["region"], - availability_zone_name=module.params["availability_zone"], - storage_endpoint_name=module.params["name"], - ) - await_operation(fusion, op) - - changed = len(patches) != 0 - - module.exit_json(changed=changed, id=se.id) - - -def main(): - """Main code""" - argument_spec = fusion_argument_spec() - argument_spec.update( - dict( - name=dict(type="str", required=True), - display_name=dict(type="str"), - region=dict(type="str", required=True), - availability_zone=dict(type="str", required=True, aliases=["az"]), - iscsi=dict( - type="list", - elements="dict", - options=dict( - address=dict(type="str"), - gateway=dict(type="str"), - network_interface_groups=dict(type="list", elements="str"), - ), - ), - cbs_azure_iscsi=dict( - type="dict", - options=dict( - storage_endpoint_collection_identity=dict(type="str"), - load_balancer=dict(type="str"), - load_balancer_addresses=dict(type="list", elements="str"), - ), - ), - state=dict(type="str", default="present", choices=["absent", "present"]), - # deprecated, will be removed in 2.0.0 - endpoint_type=dict( - type="str", - removed_in_version="2.0.0", - removed_from_collection="purestorage.fusion", - ), - addresses=dict( - type="list", - elements="str", - removed_in_version="2.0.0", - removed_from_collection="purestorage.fusion", - ), - gateway=dict( - type="str", - removed_in_version="2.0.0", - removed_from_collection="purestorage.fusion", - ), - network_interface_groups=dict( - type="list", - elements="str", - removed_in_version="2.0.0", - removed_from_collection="purestorage.fusion", - ), - ) - ) - - mutually_exclusive = [ - ("iscsi", "cbs_azure_iscsi"), - # can not use both deprecated and new fields at the same time - ("iscsi", "cbs_azure_iscsi", "addresses"), - ("iscsi", "cbs_azure_iscsi", "gateway"), - ("iscsi", "cbs_azure_iscsi", "network_interface_groups"), - ] - - module = AnsibleModule( - argument_spec, - mutually_exclusive=mutually_exclusive, - supports_check_mode=True, - ) - fusion = setup_fusion(module) - - state = module.params["state"] - - if module.params["endpoint_type"] is not None: - module.warn( - "'endpoint_type' parameter is deprecated and will be removed in the version 2.0" - ) - - deprecated_parameters = {"addresses", "gateway", "network_interface_groups"} - used_deprecated_parameters = [ - key - for key in list(deprecated_parameters & module.params.keys()) - if module.params[key] is not None - ] - - if len(used_deprecated_parameters) > 0: - # user uses deprecated module interface - for param_name in used_deprecated_parameters: - module.warn( - f"'{param_name}' parameter is deprecated and will be removed in the version 2.0" - ) - - if module.params["addresses"]: - for address in module.params["addresses"]: - if not is_valid_network(address): - module.fail_json( - msg=f"'{address}' is not a valid address in CIDR notation" - ) - - sendp = get_se(module, fusion) - - if state == "present" and not sendp: - module.fail_on_missing_params(["addresses"]) - if not (module.params["addresses"]): - module.fail_json( - msg="At least one entry in 'addresses' is required to create new storage endpoint" - ) - create_se_old(module, fusion) - elif state == "present" and sendp: - update_se(module, fusion, sendp) - elif state == "absent" and sendp: - delete_se(module, fusion) - else: - # user uses new module interface - if module.params["iscsi"] is not None: - for endpoint in module.params["iscsi"]: - address = endpoint["address"] - if not is_valid_network(address): - module.fail_json( - msg=f"'{address}' is not a valid address in CIDR notation" - ) - gateway = endpoint["gateway"] - if not is_valid_address(gateway): - module.fail_json( - msg=f"'{gateway}' is not a valid IPv4 address notation" - ) - if module.params["cbs_azure_iscsi"] is not None: - for address in module.params["cbs_azure_iscsi"]["load_balancer_addresses"]: - if not is_valid_address(address): - module.fail_json( - msg=f"'{address}' is not a valid IPv4 address notation" - ) - - sendp = get_se(module, fusion) - - if state == "present" and not sendp: - if ( - module.params["iscsi"] is None - and module.params["cbs_azure_iscsi"] is None - ): - module.fail_json( - msg="either 'iscsi' or `cbs_azure_iscsi` parameter is required when creating storage endpoint" - ) - create_se(module, fusion) - elif state == "present" and sendp: - update_se(module, fusion, sendp) - elif state == "absent" and sendp: - delete_se(module, fusion) - - module.exit_json(changed=False) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_ss.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_ss.py deleted file mode 100644 index 4e6388249..000000000 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_ss.py +++ /dev/null @@ -1,211 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2022, Simon Dodsley (simon@purestorage.com) -# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -DOCUMENTATION = r""" ---- -module: fusion_ss -version_added: '1.0.0' -short_description: Manage storage services in Pure Storage Fusion -description: -- Manage a storage services in Pure Storage Fusion. -author: -- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com> -notes: -- Supports C(check mode). -options: - name: - description: - - The name of the storage service. - type: str - required: true - state: - description: - - Define whether the storage service should exist or not. - default: present - choices: [ present, absent ] - type: str - display_name: - description: - - The human name of the storage service. - - If not provided, defaults to I(name). - type: str - hardware_types: - description: - - Hardware types to which the storage service applies. - type: list - elements: str - choices: [ flash-array-x, flash-array-c, flash-array-x-optane, flash-array-xl ] -extends_documentation_fragment: -- purestorage.fusion.purestorage.fusion -""" - -EXAMPLES = r""" -- name: Create new storage service foo - purestorage.fusion.fusion_ss: - name: foo - hardware_types: - - flash-array-x - - flash-array-x-optane - display_name: "test class" - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" - -- name: Update storage service - purestorage.fusion.fusion_ss: - name: foo - display_name: "main class" - hardware_types: - - flash-array-c - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" - -- name: Delete storage service - purestorage.fusion.fusion_ss: - name: foo - state: absent - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" -""" - -RETURN = r""" -""" - -try: - import fusion as purefusion -except ImportError: - pass - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import ( - fusion_argument_spec, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.startup import ( - setup_fusion, -) -from ansible_collections.purestorage.fusion.plugins.module_utils import getters -from ansible_collections.purestorage.fusion.plugins.module_utils.operations import ( - await_operation, -) - - -def get_ss(module, fusion): - """Return Storage Service or None""" - return getters.get_ss(module, fusion, storage_service_name=module.params["name"]) - - -def create_ss(module, fusion): - """Create Storage Service""" - - ss_api_instance = purefusion.StorageServicesApi(fusion) - - changed = True - id = None - if not module.check_mode: - if not module.params["display_name"]: - display_name = module.params["name"] - else: - display_name = module.params["display_name"] - s_service = purefusion.StorageServicePost( - name=module.params["name"], - display_name=display_name, - hardware_types=module.params["hardware_types"], - ) - op = ss_api_instance.create_storage_service(s_service) - res_op = await_operation(fusion, op) - id = res_op.result.resource.id - - module.exit_json(changed=changed, id=id) - - -def delete_ss(module, fusion): - """Delete Storage Service""" - - ss_api_instance = purefusion.StorageServicesApi(fusion) - - changed = True - if not module.check_mode: - op = ss_api_instance.delete_storage_service( - storage_service_name=module.params["name"] - ) - await_operation(fusion, op) - - module.exit_json(changed=changed) - - -def update_ss(module, fusion, ss): - """Update Storage Service""" - - ss_api_instance = purefusion.StorageServicesApi(fusion) - patches = [] - if ( - module.params["display_name"] - and module.params["display_name"] != ss.display_name - ): - patch = purefusion.StorageServicePatch( - display_name=purefusion.NullableString(module.params["display_name"]), - ) - patches.append(patch) - - id = None - if not module.check_mode: - for patch in patches: - op = ss_api_instance.update_storage_service( - patch, - storage_service_name=module.params["name"], - ) - await_operation(fusion, op) - - changed = len(patches) != 0 - - module.exit_json(changed=changed, id=ss.id) - - -def main(): - """Main code""" - argument_spec = fusion_argument_spec() - argument_spec.update( - dict( - name=dict(type="str", required=True), - display_name=dict(type="str"), - hardware_types=dict( - type="list", - elements="str", - choices=[ - "flash-array-x", - "flash-array-c", - "flash-array-x-optane", - "flash-array-xl", - ], - ), - state=dict(type="str", default="present", choices=["present", "absent"]), - ) - ) - - module = AnsibleModule(argument_spec, supports_check_mode=True) - fusion = setup_fusion(module) - - state = module.params["state"] - s_service = get_ss(module, fusion) - - if not s_service and state == "present": - module.fail_on_missing_params(["hardware_types"]) - create_ss(module, fusion) - elif s_service and state == "present": - update_ss(module, fusion, s_service) - elif s_service and state == "absent": - delete_ss(module, fusion) - else: - module.exit_json(changed=False) - - module.exit_json(changed=False) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_tenant.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_tenant.py deleted file mode 100644 index 85224a6c5..000000000 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_tenant.py +++ /dev/null @@ -1,171 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2022, Simon Dodsley (simon@purestorage.com) -# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -DOCUMENTATION = r""" ---- -module: fusion_tenant -version_added: '1.0.0' -short_description: Manage tenants in Pure Storage Fusion -description: -- Create,delete or update a tenant in Pure Storage Fusion. -author: -- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com> -notes: -- Supports C(check mode). -options: - name: - description: - - The name of the tenant. - type: str - required: true - state: - description: - - Define whether the tenant should exist or not. - default: present - choices: [ present, absent ] - type: str - display_name: - description: - - The human name of the tenant. - - If not provided, defaults to I(name). - type: str -extends_documentation_fragment: -- purestorage.fusion.purestorage.fusion -""" - -EXAMPLES = r""" -- name: Create new tenat foo - purestorage.fusion.fusion_tenant: - name: foo - display_name: "tenant foo" - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" - -- name: Delete tenat foo - purestorage.fusion.fusion_tenant: - name: foo - state: absent - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" -""" - -RETURN = r""" -""" - -try: - import fusion as purefusion -except ImportError: - pass - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import ( - fusion_argument_spec, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.startup import ( - setup_fusion, -) -from ansible_collections.purestorage.fusion.plugins.module_utils import getters -from ansible_collections.purestorage.fusion.plugins.module_utils.operations import ( - await_operation, -) - - -def get_tenant(module, fusion): - """Return Tenant or None""" - return getters.get_tenant(module, fusion, tenant_name=module.params["name"]) - - -def create_tenant(module, fusion): - """Create Tenant""" - - api_instance = purefusion.TenantsApi(fusion) - changed = True - id = None - if not module.check_mode: - if not module.params["display_name"]: - display_name = module.params["name"] - else: - display_name = module.params["display_name"] - tenant = purefusion.TenantPost( - name=module.params["name"], - display_name=display_name, - ) - op = api_instance.create_tenant(tenant) - res_op = await_operation(fusion, op) - id = res_op.result.resource.id - - module.exit_json(changed=changed, id=id) - - -def update_tenant(module, fusion, tenant): - """Update Tenant settings""" - changed = False - api_instance = purefusion.TenantsApi(fusion) - - if ( - module.params["display_name"] - and module.params["display_name"] != tenant.display_name - ): - changed = True - if not module.check_mode: - new_tenant = purefusion.TenantPatch( - display_name=purefusion.NullableString(module.params["display_name"]), - ) - op = api_instance.update_tenant( - new_tenant, - tenant_name=module.params["name"], - ) - await_operation(fusion, op) - - module.exit_json(changed=changed, id=tenant.id) - - -def delete_tenant(module, fusion): - """Delete Tenant""" - changed = True - api_instance = purefusion.TenantsApi(fusion) - if not module.check_mode: - op = api_instance.delete_tenant(tenant_name=module.params["name"]) - await_operation(fusion, op) - - module.exit_json(changed=changed) - - -def main(): - """Main code""" - argument_spec = fusion_argument_spec() - argument_spec.update( - dict( - name=dict(type="str", required=True), - display_name=dict(type="str"), - state=dict(type="str", default="present", choices=["present", "absent"]), - ) - ) - - module = AnsibleModule(argument_spec, supports_check_mode=True) - fusion = setup_fusion(module) - - state = module.params["state"] - tenant = get_tenant(module, fusion) - - if not tenant and state == "present": - create_tenant(module, fusion) - elif tenant and state == "present": - update_tenant(module, fusion, tenant) - elif tenant and state == "absent": - delete_tenant(module, fusion) - else: - module.exit_json(changed=False) - - module.exit_json(changed=False) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_tn.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_tn.py deleted file mode 100644 index 717b1e46f..000000000 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_tn.py +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2022, Simon Dodsley (simon@purestorage.com) -# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -DOCUMENTATION = r""" ---- -module: fusion_tn -version_added: '1.0.0' -deprecated: - removed_at_date: "2023-07-26" - why: Tenant Networks were removed as a concept in Pure Storage Fusion - alternative: most of the functionality can be replicated using M(purestorage.fusion.fusion_se) and M(purestorage.fusion.fusion_nig) -short_description: Manage tenant networks in Pure Storage Fusion -description: -- Create or delete tenant networks in Pure Storage Fusion. -notes: -- Supports C(check_mode). -- Currently this only supports a single tenant subnet per tenant network. -author: -- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com> -options: - name: - description: - - The name of the tenant network. - type: str - display_name: - description: - - The human name of the tenant network. - - If not provided, defaults to I(name). - type: str - state: - description: - - Define whether the tenant network should exist or not. - type: str - default: present - choices: [ absent, present ] - region: - description: - - The name of the region the availability zone is in - type: str - availability_zone: - aliases: [ az ] - description: - - The name of the availability zone for the tenant network. - type: str - provider_subnets: - description: - - List of provider subnets to assign to the tenant networks subnet. - type: list - elements: str - addresses: - description: - - List of IP addresses to be used in the subnet of the tenant network. - - IP addresses must include a CIDR notation. - - IPv4 and IPv6 are fully supported. - type: list - elements: str - gateway: - description: - - Address of the subnet gateway. - - Currently this must be provided. - type: str - mtu: - description: - - MTU setting for the subnet. - default: 1500 - type: int - prefix: - description: - - Network prefix in CIDR format. - - This will be deprecated soon. - type: str -extends_documentation_fragment: -- purestorage.fusion.purestorage.fusion -""" - -# this module does nothing, thus no example is provided -EXAMPLES = r""" -""" - -RETURN = r""" -""" - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import ( - fusion_argument_spec, -) - - -def main(): - """Main code""" - argument_spec = fusion_argument_spec() - argument_spec.update( - dict( - name=dict(type="str"), - region=dict(type="str"), - display_name=dict(type="str"), - availability_zone=dict(type="str", aliases=["az"]), - prefix=dict(type="str"), - gateway=dict(type="str"), - mtu=dict(type="int", default=1500), - provider_subnets=dict(type="list", elements="str"), - addresses=dict(type="list", elements="str"), - state=dict(type="str", default="present", choices=["absent", "present"]), - ) - ) - module = AnsibleModule(argument_spec, supports_check_mode=True) - module.warn( - "This module is deprecated, doesn't work, and will be removed in the version 2.0." - " Please, use purestorage.fusion.fusion_se and purestorage.fusion.fusion_nig instead." - ) - module.exit_json(changed=False) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_ts.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_ts.py deleted file mode 100644 index ac60476bc..000000000 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_ts.py +++ /dev/null @@ -1,189 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2022, Simon Dodsley (simon@purestorage.com) -# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -DOCUMENTATION = r""" ---- -module: fusion_ts -version_added: '1.0.0' -short_description: Manage tenant spaces in Pure Storage Fusion -description: -- Create, update or delete a tenant spaces in Pure Storage Fusion. -author: -- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com> -notes: -- Supports C(check mode). -options: - name: - description: - - The name of the tenant space. - type: str - required: true - display_name: - description: - - The human name of the tenant space. - - If not provided, defaults to I(name). - type: str - state: - description: - - Define whether the tenant space should exist or not. - type: str - default: present - choices: [ absent, present ] - tenant: - description: - - The name of the tenant. - type: str - required: true -extends_documentation_fragment: -- purestorage.fusion.purestorage.fusion -""" - -EXAMPLES = r""" -- name: Create new tenant space foo for tenant bar - purestorage.fusion.fusion_ts: - name: foo - tenant: bar - state: present - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" - -- name: Delete tenant space foo in tenant bar - purestorage.fusion.fusion_ts: - name: foo - tenant: bar - state: absent - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" -""" - -RETURN = r""" -""" - -try: - import fusion as purefusion -except ImportError: - pass - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import ( - fusion_argument_spec, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.startup import ( - setup_fusion, -) -from ansible_collections.purestorage.fusion.plugins.module_utils import getters -from ansible_collections.purestorage.fusion.plugins.module_utils.operations import ( - await_operation, -) - - -def get_ts(module, fusion): - """Tenant Space or None""" - return getters.get_ts(module, fusion, tenant_space_name=module.params["name"]) - - -def create_ts(module, fusion): - """Create Tenant Space""" - - ts_api_instance = purefusion.TenantSpacesApi(fusion) - - changed = True - id = None - if not module.check_mode: - if not module.params["display_name"]: - display_name = module.params["name"] - else: - display_name = module.params["display_name"] - tspace = purefusion.TenantSpacePost( - name=module.params["name"], - display_name=display_name, - ) - op = ts_api_instance.create_tenant_space( - tspace, - tenant_name=module.params["tenant"], - ) - res_op = await_operation(fusion, op) - id = res_op.result.resource.id - - module.exit_json(changed=changed, id=id) - - -def update_ts(module, fusion, ts): - """Update Tenant Space""" - - ts_api_instance = purefusion.TenantSpacesApi(fusion) - patches = [] - if ( - module.params["display_name"] - and module.params["display_name"] != ts.display_name - ): - patch = purefusion.TenantSpacePatch( - display_name=purefusion.NullableString(module.params["display_name"]), - ) - patches.append(patch) - - if not module.check_mode: - for patch in patches: - op = ts_api_instance.update_tenant_space( - patch, - tenant_name=module.params["tenant"], - tenant_space_name=module.params["name"], - ) - await_operation(fusion, op) - - changed = len(patches) != 0 - - module.exit_json(changed=changed, id=ts.id) - - -def delete_ts(module, fusion): - """Delete Tenant Space""" - changed = True - ts_api_instance = purefusion.TenantSpacesApi(fusion) - if not module.check_mode: - op = ts_api_instance.delete_tenant_space( - tenant_name=module.params["tenant"], - tenant_space_name=module.params["name"], - ) - await_operation(fusion, op) - - module.exit_json(changed=changed) - - -def main(): - """Main code""" - argument_spec = fusion_argument_spec() - argument_spec.update( - dict( - name=dict(type="str", required=True), - display_name=dict(type="str"), - tenant=dict(type="str", required=True), - state=dict(type="str", default="present", choices=["absent", "present"]), - ) - ) - - module = AnsibleModule(argument_spec, supports_check_mode=True) - fusion = setup_fusion(module) - - state = module.params["state"] - tspace = get_ts(module, fusion) - - if state == "present" and not tspace: - create_ts(module, fusion) - elif state == "present" and tspace: - update_ts(module, fusion, tspace) - elif state == "absent" and tspace: - delete_ts(module, fusion) - - module.exit_json(changed=False) - - -if __name__ == "__main__": - main() diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_volume.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_volume.py deleted file mode 100644 index 38dee8650..000000000 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_volume.py +++ /dev/null @@ -1,559 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- - -# (c) 2023, Simon Dodsley (simon@purestorage.com), Jan Kodera (jkodera@purestorage.com) -# GNU General Public License v3.0+ (see COPYING.GPLv3 or https://www.gnu.org/licenses/gpl-3.0.txt) - -from __future__ import absolute_import, division, print_function - -__metaclass__ = type - -DOCUMENTATION = r""" ---- -module: fusion_volume -version_added: '1.0.0' -short_description: Manage volumes in Pure Storage Fusion -description: -- Create, update or delete a volume in Pure Storage Fusion. -author: -- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com> -notes: -- Supports C(check mode). -options: - name: - description: - - The name of the volume. - type: str - required: true - display_name: - description: - - The human name of the volume. - - If not provided, defaults to I(name). - type: str - state: - description: - - Define whether the volume should exist or not. - type: str - default: present - choices: [ absent, present ] - tenant: - description: - - The name of the tenant. - type: str - required: true - tenant_space: - description: - - The name of the tenant space. - type: str - required: true - eradicate: - description: - - "Wipes the volume instead of a soft delete if true. Must be used with `state: absent`." - type: bool - default: false - size: - description: - - Volume size in M, G, T or P units. - type: str - storage_class: - description: - - The name of the storage class. - type: str - placement_group: - description: - - The name of the placement group. - type: str - protection_policy: - description: - - The name of the protection policy. - type: str - host_access_policies: - description: - - 'A list of host access policies to connect the volume to. - To clear, assign empty list: host_access_policies: []' - type: list - elements: str - source_volume: - description: - - The source volume name. It must live within the same tenant space. - Cannot be used together with `source_snapshot` or `source_volume_snapshot`. - type: str - source_snapshot: - description: - - The source snapshot name. It must live within the same tenant space. - Cannot be used together with `source_volume`. - type: str - source_volume_snapshot: - description: - - The source volume snapshot name. It must live within the same tenant space. - Cannot be used together with `source_volume`. - type: str - rename: - description: - - New name for volume. - type: str -extends_documentation_fragment: -- purestorage.fusion.purestorage.fusion -""" - -EXAMPLES = r""" -- name: Create new volume named foo in storage_class fred - purestorage.fusion.fusion_volume: - name: foo - storage_class: fred - placement_group: pg - size: 1T - tenant: test - tenant_space: space_1 - state: present - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" - -- name: Create new volume based on a volume from the same tenant space - purestorage.fusion.fusion_volume: - name: foo - storage_class: fred - placement_group: pg - tenant: test - tenant_space: space_1 - state: present - source_volume: "original_volume_name" - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" - -- name: Create new volume based on a volume snapshot from the same tenant space - purestorage.fusion.fusion_volume: - name: foo - storage_class: fred - placement_group: pg - tenant: test - tenant_space: space_1 - state: present - source_snapshot: "snap" - source_volume_snapshot: "vol_snap" - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" - -- name: Extend the size of an existing volume named foo - purestorage.fusion.fusion_volume: - name: foo - size: 2T - tenant: test - tenant_space: space_1 - state: present - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" - -- name: Delete volume named foo - purestorage.fusion.fusion_volume: - name: foo - tenant: test - tenant_space: space_1 - state: absent - issuer_id: key_name - private_key_file: "az-admin-private-key.pem" -""" - -RETURN = r""" -""" - -from ansible_collections.purestorage.fusion.plugins.module_utils.operations import ( - await_operation, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.startup import ( - setup_fusion, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.parsing import ( - parse_number_with_metric_suffix, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import ( - fusion_argument_spec, -) -from ansible.module_utils.basic import AnsibleModule - -try: - import fusion as purefusion -except ImportError: - pass - - -def get_volume(module, fusion): - """Return Volume or None""" - volume_api_instance = purefusion.VolumesApi(fusion) - try: - return volume_api_instance.get_volume( - tenant_name=module.params["tenant"], - tenant_space_name=module.params["tenant_space"], - volume_name=module.params["name"], - ) - except purefusion.rest.ApiException: - return None - - -def get_wanted_haps(module): - """Return set of host access policies to assign""" - if not module.params["host_access_policies"]: - return set() - # looks like yaml parsing can leave in some spaces if coma-delimited .so strip() the names - return set([hap.strip() for hap in module.params["host_access_policies"]]) - - -def extract_current_haps(volume): - """Return set of host access policies that volume currently has""" - if not volume.host_access_policies: - return set() - return set([hap.name for hap in volume.host_access_policies]) - - -def create_volume(module, fusion): - """Create Volume""" - id = None - if not module.check_mode: - display_name = module.params["display_name"] or module.params["name"] - volume_api_instance = purefusion.VolumesApi(fusion) - source_link = get_source_link_from_parameters(module.params) - volume = purefusion.VolumePost( - size=None # when cloning a volume, size is not required - if source_link - else parse_number_with_metric_suffix(module, module.params["size"]), - storage_class=module.params["storage_class"], - placement_group=module.params["placement_group"], - name=module.params["name"], - display_name=display_name, - protection_policy=module.params["protection_policy"], - source_link=source_link, - ) - op = volume_api_instance.create_volume( - volume, - tenant_name=module.params["tenant"], - tenant_space_name=module.params["tenant_space"], - ) - res_op = await_operation(fusion, op) - id = res_op.result.resource.id - return True, id - - -def update_host_access_policies(module, current, patches): - wanted = module.params - # 'wanted[...] is not None' to differentiate between empty list and no list - if wanted["host_access_policies"] is not None: - current_haps = extract_current_haps(current) - wanted_haps = get_wanted_haps(module) - if wanted_haps != current_haps: - patch = purefusion.VolumePatch( - host_access_policies=purefusion.NullableString(",".join(wanted_haps)) - ) - patches.append(patch) - - -def update_destroyed(module, current, patches): - wanted = module.params - destroyed = wanted["state"] != "present" - if destroyed != current.destroyed: - patch = purefusion.VolumePatch(destroyed=purefusion.NullableBoolean(destroyed)) - patches.append(patch) - if destroyed and not module.params["eradicate"]: - module.warn( - ( - "Volume '{0}' is being soft deleted to prevent data loss, " - "if you want to wipe it immediately to reclaim used space, add 'eradicate: true'" - ).format(current.name) - ) - - -def update_display_name(module, current, patches): - wanted = module.params - if wanted["display_name"] and wanted["display_name"] != current.display_name: - patch = purefusion.VolumePatch( - display_name=purefusion.NullableString(wanted["display_name"]) - ) - patches.append(patch) - - -def update_storage_class(module, current, patches): - wanted = module.params - if ( - wanted["storage_class"] - and wanted["storage_class"] != current.storage_class.name - ): - patch = purefusion.VolumePatch( - storage_class=purefusion.NullableString(wanted["storage_class"]) - ) - patches.append(patch) - - -def update_placement_group(module, current, patches): - wanted = module.params - if ( - wanted["placement_group"] - and wanted["placement_group"] != current.placement_group.name - ): - patch = purefusion.VolumePatch( - placement_group=purefusion.NullableString(wanted["placement_group"]) - ) - patches.append(patch) - - -def update_size(module, current, patches): - wanted = module.params - if wanted["size"]: - wanted_size = parse_number_with_metric_suffix(module, wanted["size"]) - if wanted_size != current.size: - patch = purefusion.VolumePatch(size=purefusion.NullableSize(wanted_size)) - patches.append(patch) - - -def update_protection_policy(module, current, patches): - wanted = module.params - current_policy = current.protection_policy.name if current.protection_policy else "" - if ( - wanted["protection_policy"] is not None - and wanted["protection_policy"] != current_policy - ): - patch = purefusion.VolumePatch( - protection_policy=purefusion.NullableString(wanted["protection_policy"]) - ) - patches.append(patch) - - -def update_source_link(module, fusion, current, patches): - source_link = get_source_link_from_parameters(module.params) - if source_link is not None and ( - current.source is None or current.source.self_link != source_link - ): - patch = purefusion.VolumePatch( - source_link=purefusion.NullableString(source_link) - ) - patches.append(patch) - - -def apply_patches(module, fusion, patches): - volume_api_instance = purefusion.VolumesApi(fusion) - for patch in patches: - op = volume_api_instance.update_volume( - patch, - volume_name=module.params["name"], - tenant_name=module.params["tenant"], - tenant_space_name=module.params["tenant_space"], - ) - await_operation(fusion, op) - - -def update_volume(module, fusion): - """Update Volume size, placement group, protection policy, storage class, HAPs""" - current = get_volume(module, fusion) - patches = [] - - if not current: - # cannot update nonexistent volume - # Note for check mode: the reasons this codepath is ran in check mode - # is to catch any argument errors and to compute 'changed'. Basically - # all argument checks are kept in validate_arguments() to filter the - # first part. The second part MAY diverge flow from the real run here if - # create_volume() created the volume and update was then run to update - # its properties. HOWEVER we don't really care in that case because - # create_volume() already sets 'changed' to true, so any 'changed' - # result from update_volume() would not change it. - return False - - # volumes with 'destroyed' flag are kinda special because we can't change - # most of their properties while in this state, so we need to set it last - # and unset it first if changed, respectively - if module.params["state"] == "present": - update_destroyed(module, current, patches) - update_size(module, current, patches) - update_protection_policy(module, current, patches) - update_display_name(module, current, patches) - update_storage_class(module, current, patches) - update_placement_group(module, current, patches) - update_host_access_policies(module, current, patches) - update_source_link(module, fusion, current, patches) - elif module.params["state"] == "absent" and not current.destroyed: - update_size(module, current, patches) - update_protection_policy(module, current, patches) - update_display_name(module, current, patches) - update_storage_class(module, current, patches) - update_placement_group(module, current, patches) - update_host_access_policies(module, current, patches) - update_source_link(module, fusion, current, patches) - update_destroyed(module, current, patches) - - if not module.check_mode: - apply_patches(module, fusion, patches) - - changed = len(patches) != 0 - return changed - - -def eradicate_volume(module, fusion): - """Eradicate Volume""" - current = get_volume(module, fusion) - if module.check_mode: - return current or module.params["state"] == "present" - if not current: - return False - - # update_volume() should be called before eradicate=True and it should - # ensure the volume is destroyed and HAPs are unassigned - if not current.destroyed or current.host_access_policies: - module.fail_json( - msg="BUG: inconsistent state, eradicate_volume() cannot be called with current.destroyed=False or any host_access_policies" - ) - - volume_api_instance = purefusion.VolumesApi(fusion) - op = volume_api_instance.delete_volume( - volume_name=module.params["name"], - tenant_name=module.params["tenant"], - tenant_space_name=module.params["tenant_space"], - ) - await_operation(fusion, op) - - return True - - -def get_source_link_from_parameters(params): - tenant = params["tenant"] - tenant_space = params["tenant_space"] - volume = params["source_volume"] - snapshot = params["source_snapshot"] - volume_snapshot = params["source_volume_snapshot"] - if ( - tenant is None or tenant_space is None - ): # should not happen as those parameters are always required by the ansible module - return None - if volume is not None: - return f"/tenants/{tenant}/tenant-spaces/{tenant_space}/volumes/{volume}" - if snapshot is not None and volume_snapshot is not None: - return f"/tenants/{tenant}/tenant-spaces/{tenant_space}/snapshots/{snapshot}/volume-snapshots/{volume_snapshot}" - return None - - -def validate_arguments(module, volume): - """Validates most argument conditions and possible unacceptable argument combinations""" - state = module.params["state"] - - if state == "present" and not volume: - module.fail_on_missing_params(["placement_group", "storage_class"]) - - if ( - module.params["size"] is None - and module.params["source_volume"] is None - and module.params["source_snapshot"] is None - ): - module.fail_json( - msg="Either `size`, `source_volume` or `source_snapshot` parameter is required when creating a volume." - ) - - if module.params["state"] == "absent" and ( - module.params["host_access_policies"] - or ( - module.params["host_access_policies"] is None - and volume - and volume.host_access_policies - ) - ): - module.fail_json( - msg=( - "Volume must have no host access policies when destroyed, either revert the delete " - "by setting 'state: present' or remove all HAPs by 'host_access_policies: []'" - ) - ) - - if state == "present" and module.params["eradicate"]: - module.fail_json( - msg="'eradicate: true' cannot be used together with 'state: present'" - ) - - if module.params["size"] is not None: - size = parse_number_with_metric_suffix(module, module.params["size"]) - if size < 1048576 or size > 4503599627370496: # 1MB to 4PB - module.fail_json( - msg="Size is not within the required range, size must be between 1MB and 4PB" - ) - - -def main(): - """Main code""" - argument_spec = fusion_argument_spec() - deprecated_hosts = dict( - name="hosts", date="2023-07-26", collection_name="purefusion.fusion" - ) - argument_spec.update( - dict( - name=dict(type="str", required=True), - display_name=dict(type="str"), - rename=dict( - type="str", - removed_at_date="2023-07-26", - removed_from_collection="purestorage.fusion", - ), - tenant=dict(type="str", required=True), - tenant_space=dict(type="str", required=True), - placement_group=dict(type="str"), - storage_class=dict(type="str"), - protection_policy=dict(type="str"), - host_access_policies=dict( - type="list", elements="str", deprecated_aliases=[deprecated_hosts] - ), - eradicate=dict(type="bool", default=False), - state=dict(type="str", default="present", choices=["absent", "present"]), - size=dict(type="str"), - source_volume=dict(type="str"), - source_snapshot=dict(type="str"), - source_volume_snapshot=dict(type="str"), - ) - ) - - required_by = { - "placement_group": "storage_class", - } - - mutually_exclusive = [ - # a new volume cannot be based on a volume and a snapshot at the same time - # also, when cloning a volume, size of original volume is used - ("source_volume", "source_snapshot", "size"), - ] - - required_together = [ - # when creating a volume from snapshot, we need to know both snapshot name and snapshot volume name - ("source_snapshot", "source_volume_snapshot"), - ] - - module = AnsibleModule( - argument_spec, - required_by=required_by, - mutually_exclusive=mutually_exclusive, - required_together=required_together, - supports_check_mode=True, - ) - fusion = setup_fusion(module) - - state = module.params["state"] - - volume = get_volume(module, fusion) - - validate_arguments(module, volume) - - if state == "absent" and not volume: - module.exit_json(changed=False) - - changed = False - id = None - if volume is not None: - id = volume.id - if state == "present" and not volume: - changed, id = create_volume(module, fusion) - # volume might exist even if soft-deleted, so we still have to update it - changed = changed | update_volume(module, fusion) - if module.params["eradicate"]: - changed = changed | eradicate_volume(module, fusion) - module.exit_json(changed=changed) - - if id is not None: - module.exit_json(changed=changed, id=id) - - module.exit_json(changed=changed) - - -if __name__ == "__main__": - main() |