Init: mediaserver

This commit is contained in:
2023-02-08 12:13:28 +01:00
parent 848bc9739c
commit f7c23d4ba9
31914 changed files with 6175775 additions and 0 deletions

View File

@@ -0,0 +1,44 @@
# -*- 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:
key_file:
description:
- Path to the private key file
- Defaults to the set environment variable under FUSION_PRIVATE_KEY_FILE.
type: str
app_id:
description:
- Application ID from Pure1 Registration page
- eg. pure1:apikey:dssf2331sd
- Defaults to the set environment variable under FUSION_APP_ID
type: str
notes:
- This module requires the I(purefusion) Python library
- You must set C(FUSION_APP_ID) and C(FUSION_PRIVATE_KEY_FILE) environment variables
if I(app_id) and I(key_file) arguments are not passed to the module directly
requirements:
- python >= 3.5
- purefusion
"""

View File

@@ -0,0 +1,98 @@
# -*- 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
HAS_FUSION = True
try:
import fusion
except ImportError:
HAS_FUSION = False
from os import environ
import platform
TOKEN_EXCHANGE_URL = "https://api.pure1.purestorage.com/oauth2/1.0/token"
VERSION = 1.0
USER_AGENT_BASE = "Ansible"
def get_fusion(module):
"""Return System Object or Fail"""
user_agent = "%(base)s %(class)s/%(version)s (%(platform)s)" % {
"base": USER_AGENT_BASE,
"class": __name__,
"version": VERSION,
"platform": platform.platform(),
}
app_id = module.params["app_id"]
key_file = module.params["key_file"]
if HAS_FUSION:
config = fusion.Configuration()
if app_id and key_file:
try:
config.issuer_id = app_id
config.private_key_file = key_file
client = fusion.ApiClient(config)
client.set_default_header("User-Agent", user_agent)
except Exception:
module.fail_json(msg="Unknown failure. Please contact Pure Support")
elif environ.get("FUSION_APP_ID") and environ.get("FUSION_PRIVATE_KEY_FILE"):
try:
config.issuer_id = environ.get("FUSION_APP_ID")
config.private_key_file = environ.get("FUSION_PRIVATE_KEY_FILE")
client = fusion.ApiClient(config)
client.set_default_header("User-Agent", user_agent)
except Exception:
module.fail_json(msg="Unknown failure. Please contact Pure Support")
else:
module.fail_json(
msg="You must set FUSION_APP_ID and FUSION_PRIVATE_KEY_FILE environment variables "
"or the app_id and key_file module arguments"
)
try:
api_instance = fusion.DefaultApi(client)
api_instance.get_version()
except Exception as err:
module.fail_json(msg="Fusion authentication failed: {0}".format(err))
else:
module.fail_json(msg="fusion SDK is not installed.")
return client
def fusion_argument_spec():
"""Return standard base dictionary used for the argument_spec argument in AnsibleModule"""
return dict(
app_id=dict(no_log=True),
key_file=dict(no_log=False),
)

View File

@@ -0,0 +1,152 @@
#!/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') }}"
app_id: key_name
key_file: "az-admin-private-key.pem"
"""
RETURN = r"""
"""
HAS_FUSION = True
try:
import fusion as purefusion
except ImportError:
HAS_FUSION = False
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import (
get_fusion,
fusion_argument_spec,
)
def get_client(module, fusion):
"""Get API Client - True or False"""
id_api_instance = purefusion.IdentityManagerApi(fusion)
try:
clients = id_api_instance.list_api_clients()
for client in range(0, len(clients)):
if (
clients[client].public_key == module.params["public_key"]
and clients[client].display_name == module.params["name"]
):
return clients[client].id
return False
except purefusion.rest.ApiException:
return False
def delete_client(module, fusion):
"""Delete API Client"""
id_api_instance = purefusion.IdentityManagerApi(fusion)
changed = True
if not module.check_mode:
try:
id_api_instance.delete_api_client(api_client_id=get_client(module, fusion))
except purefusion.rest.ApiException as err:
module.fail_json(
msg="API Client {0} deletion failed.: {1}".format(
module.params["name"], err
)
)
module.exit_json(changed=changed)
def create_client(module, fusion):
"""Create API Client"""
id_api_instance = purefusion.IdentityManagerApi(fusion)
changed = True
if not module.check_mode:
try:
client = purefusion.APIClientPost(
public_key=module.params["public_key"],
display_name=module.params["name"],
)
id_api_instance.create_api_client(client)
except purefusion.rest.ApiException as err:
module.fail_json(
msg="API Client {0} creation failed.: {1}".format(
module.params["name"], err
)
)
module.exit_json(changed=changed)
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 = get_fusion(module)
state = module.params["state"]
client = get_client(module, fusion)
if not client and state == "present":
create_client(module, fusion)
elif client and state == "absent":
delete_client(module, fusion)
else:
module.exit_json(changed=False)
module.exit_json(changed=False)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,293 @@
#!/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 ]
required: true
type: str
host_name:
description:
- Management IP address of the array, or FQDN.
required: true
type: str
appliance_id:
description:
- Appliance ID of the array.
required: true
type: str
maintenance_mode:
description:
- Set the array in maintenance mode or not.
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
hardware_type: bigfast
display_name: "foo array"
appliance_id: 1227571-198887878-35016350232000707
app_id: key_name
key_file: "az-admin-private-key.pem"
"""
RETURN = r"""
"""
HAS_FUSION = True
try:
import fusion as purefusion
except ImportError:
HAS_FUSION = False
import math
import time
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import (
get_fusion,
fusion_argument_spec,
)
def wait_operation_finish(module, op_id, fusion):
"""
wait_operation_finish wait until operation status is Succeeded or Failed. Then returns you that operation.
if the operation takes longer than expected, it will raise an Exception
"""
op_cli = purefusion.OperationsApi(fusion)
while True:
op = op_cli.get_operation(op_id)
if op.status == "Succeeded" or op.status == "Failed":
return op
time.sleep(int(math.ceil(op.retry_in / 1000)))
def wait_operation_succeeded(module, op_id, fusion):
"""
wait_operation_succeeded calls wait_operation_finish and expect the result is succeeded.
if the operation is in other status, it will raise an expection
"""
op = wait_operation_finish(module, op_id, fusion)
if op.status == "Succeeded":
return op
else:
# this is how we handle asynchronous error
# if operation failed, the error field should be set. We can check it by op.error
# op.error uses fusion.models.error.Error
module.fail_json("Operation failed: {0}".format(op.error.message))
def get_array(module, fusion):
"""Return Array or None"""
array_api_instance = purefusion.ArraysApi(fusion)
try:
return array_api_instance.get_array(
array_name=module.params["name"],
availability_zone_name=module.params["availability_zone"],
region_name=module.params["region"],
)
except purefusion.rest.ApiException:
return None
def create_array(module, fusion):
"""Create Array"""
array_api_instance = purefusion.ArraysApi(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"]
try:
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"],
)
res = array_api_instance.create_array(
array,
availability_zone_name=module.params["availability_zone"],
region_name=module.params["region"],
)
wait_operation_succeeded(module, res.id, fusion)
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Array {0} creation failed.: {1}".format(module.params["name"], err)
)
if module.params["maintenance_mode"] is not None:
array = purefusion.ArrayPatch(
maintenance_mode=purefusion.NullableBoolean(
module.params["maintenance_mode"]
),
)
res = array_api_instance.update_array(
array,
availability_zone_name=module.params["availability_zone"],
region_name=module.params["region"],
array_name=module.params["name"],
)
wait_operation_succeeded(module, res.id, fusion)
module.exit_json(changed=changed)
def update_array(module, fusion, array):
"""Update Array"""
array_api_instance = purefusion.ArraysApi(fusion)
changed = False
if (
module.params["display_name"]
and module.params["display_name"] != array.display_name
):
display_name = module.params["display_name"]
changed = True
if not module.check_mode:
array = purefusion.ArrayPatch(
display_name=purefusion.NullableString(display_name),
)
res = array_api_instance.update_array(
array,
availability_zone_name=module.params["availability_zone"],
region_name=module.params["region"],
array_name=module.params["name"],
)
wait_operation_succeeded(module, res.id, fusion)
if module.params["maintenance_mode"] is not None:
if module.params["maintenance_mode"] != array.maintenance_mode:
maint_mode = module.params["maintenance_mode"]
changed = True
if not module.check_mode:
array = purefusion.ArrayPatch(
maintenance_mode=purefusion.NullableBoolean(maint_mode),
)
res = array_api_instance.update_array(
array,
availability_zone_name=module.params["availability_zone"],
region_name=module.params["region"],
array_name=module.params["name"],
)
wait_operation_succeeded(module, res.id, fusion)
module.exit_json(changed=changed)
def delete_array(module, fusion):
"""Delete Array - not currently available"""
array_api_instance = purefusion.ArraysApi(fusion)
changed = True
if not module.check_mode:
try:
res = array_api_instance.delete_array(
region_name=module.params["region"],
availability_zone_name=module.params["availability_zone"],
array_name=module.params["name"],
)
wait_operation_succeeded(module, res.id, fusion)
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Array {0} creation failed.: {1}".format(module.params["name"], err)
)
module.exit_json(changed=changed)
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),
appliance_id=dict(type="str", required=True),
host_name=dict(type="str", required=True),
hardware_type=dict(
type="str",
required=True,
choices=[
"flash-array-x",
"flash-array-c",
"flash-array-x-optane",
"flash-array-xl",
],
),
maintenance_mode=dict(type="bool"),
state=dict(type="str", default="present", choices=["present", "absent"]),
)
)
module = AnsibleModule(argument_spec, supports_check_mode=True)
fusion = get_fusion(module)
state = module.params["state"]
array = get_array(module, fusion)
if not array and state == "present":
create_array(module, fusion)
elif array and state == "present":
update_array(module, fusion, array)
elif array and state == "absent":
delete_array(module, fusion)
else:
module.exit_json(changed=False)
module.exit_json(changed=False)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,186 @@
#!/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
extends_documentation_fragment:
- purestorage.fusion.purestorage.fusion
"""
EXAMPLES = r"""
- name: Create new AZ foo
purestorage.fusion.fusion_az:
name: foo
display_name: "foo AZ"
app_id: key_name
key_file: "az-admin-private-key.pem"
- name: Delete AZ foo
purestorage.fusion.fusion_az:
name: foo
state: absent
app_id: key_name
key_file: "az-admin-private-key.pem"
"""
RETURN = r"""
"""
HAS_FUSION = True
try:
import fusion as purefusion
except ImportError:
HAS_FUSION = False
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import (
get_fusion,
fusion_argument_spec,
)
def get_az(module, fusion):
"""Get Availability Zone or None"""
az_api_instance = purefusion.AvailabilityZonesApi(fusion)
try:
return az_api_instance.get_availability_zone(
availability_zone_name=module.params["name"],
region_name=module.params["region"],
)
except purefusion.rest.ApiException:
return None
def get_region(module, fusion):
"""Get Region or None"""
region_api_instance = purefusion.RegionsApi(fusion)
try:
return region_api_instance.get_region(
region_name=module.params["region"],
)
except purefusion.rest.ApiException:
return None
def delete_az(module, fusion):
"""Delete Availability Zone"""
az_api_instance = purefusion.AvailabilityZonesApi(fusion)
changed = True
if not module.check_mode:
try:
az_api_instance.delete_availability_zone(
region_name=module.params["region"],
availability_zone_name=module.params["name"],
)
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Availability Zone {0} deletion failed.: {1}".format(
module.params["name"], err
)
)
module.exit_json(changed=changed)
def create_az(module, fusion):
"""Create Availability Zone"""
az_api_instance = purefusion.AvailabilityZonesApi(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"]
try:
azone = purefusion.AvailabilityZonePost(
name=module.params["name"],
display_name=display_name,
)
az_api_instance.create_availability_zone(
azone, region_name=module.params["region"]
)
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Availability Zone {0} creation failed.: {1}".format(
module.params["name"], err
)
)
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"),
region=dict(type="str"),
state=dict(type="str", default="present", choices=["present", "absent"]),
)
)
module = AnsibleModule(argument_spec, supports_check_mode=True)
fusion = get_fusion(module)
state = module.params["state"]
azone = get_az(module, fusion)
if not get_region(module, fusion):
module.fail_json(
msg="Region {0} does not exist.".format(module.params["region"])
)
if not azone and state == "present":
create_az(module, fusion)
elif azone and state == "absent":
delete_az(module, fusion)
else:
module.exit_json(changed=False)
module.exit_json(changed=False)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,270 @@
#!/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','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"
app_id: key_name
key_file: "az-admin-private-key.pem"
- name: Delete host access policy
purestorage.flasharray.purefa_host:
name: foo
state: absent
app_id: key_name
key_file: "az-admin-private-key.pem"
"""
RETURN = r"""
"""
HAS_FUSION = True
try:
import fusion as purefusion
except ImportError:
HAS_FUSION = False
import re
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import (
get_fusion,
fusion_argument_spec,
)
def _check_iqn(module, fusion):
hap_api_instance = purefusion.HostAccessPoliciesApi(fusion)
hosts = hap_api_instance.list_host_access_policies().items
for host in range(0, len(hosts)):
if (
hosts[host].iqn == module.params["iqn"]
and hosts[host].name != module.params["name"]
):
module.fail_json(
msg="Supplied IQN {0} already used by host access polivy {1}".format(
module.params["iqn"], hosts[host].name
)
)
def get_host(module, fusion):
"""Return host or None"""
hap_api_instance = purefusion.HostAccessPoliciesApi(fusion)
try:
hap_api_instance.get_host_access_policy(
host_access_policy_name=module.params["name"]
)
return True
except purefusion.rest.ApiException:
return False
def create_hap(module, fusion):
"""Create a new host access policy"""
hap_api_instance = purefusion.HostAccessPoliciesApi(fusion)
changed = True
if not module.check_mode:
try:
hap_api_instance.create_host_access_policy(
purefusion.HostAccessPoliciesPost(
iqn=module.params["iqn"],
personality=module.params["personality"],
name=module.params["name"],
display_name=module.params["display_name"],
)
)
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Host Access Policy {0} creation failed: {1}".format(
module.params["name"], err
)
)
module.exit_json(changed=changed)
def delete_hap(module, fusion):
"""Delete a Host Access Policy"""
hap_api_instance = purefusion.HostAccessPoliciesApi(fusion)
changed = True
if not module.check_mode:
try:
hap_api_instance.delete_host_access_policy(
host_access_policy_name=module.params["name"]
)
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Host Access Policy {0} deletion failed: {1}".format(
module.params["name"], err
)
)
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"),
iqn=dict(type="str"),
wwns=dict(type="list", elements="str"),
host_password=dict(type="str", no_log=True),
host_user=dict(type="str"),
target_password=dict(type="str", no_log=True),
target_user=dict(type="str"),
display_name=dict(type="str"),
personality=dict(
type="str",
default="linux",
choices=[
"linux",
"hpux",
"vms",
"aix",
"esxi",
"solaris",
"hitachi-vsp",
"oracle-vm-server",
],
),
)
)
required_if = [["state", "present", ["personality", "iqn"]]]
required_together = [
["host_password", "host_user"],
["target_password", "target_user"],
]
module = AnsibleModule(
argument_spec,
supports_check_mode=True,
required_together=required_together,
required_if=required_if,
)
fusion = get_fusion(module)
module.params["name"] = module.params["name"].lower()
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}(?<!-)(?<!\.)(:[^:]+)?$"
)
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 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 not host and state == "present":
create_hap(module, fusion)
elif host and state == "absent":
delete_hap(module, fusion)
module.exit_json(changed=False)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,150 @@
#!/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'
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
required: true
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
required: true
array_type:
description:
- The array type for the hardware type.
choices: [ FA//X, FA//C ]
type: str
required: true
extends_documentation_fragment:
- purestorage.fusion.purestorage.fusion
"""
EXAMPLES = r"""
- name: Create new hardware type foo
purestorage.fusion.fusion_hw:
name: foo
array_type: "FA//X"
media_type: NVME
display_name: "NVME arrays"
app_id: key_name
key_file: "az-admin-private-key.pem"
"""
RETURN = r"""
"""
HAS_FUSION = True
try:
import fusion as purefusion
except ImportError:
HAS_FUSION = False
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import (
get_fusion,
fusion_argument_spec,
)
def get_hw(module, fusion):
"""Get Hardware Type or None"""
hw_api_instance = purefusion.HardwareTypesApi(fusion)
try:
return hw_api_instance.get_hardware_type(
hardware_type_name=module.params["name"]
)
except purefusion.rest.ApiException:
return None
def create_hw(module, fusion):
"""Create Hardware Type"""
hw_api_instance = purefusion.HardwareTypesApi(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"]
try:
hw_type = purefusion.HardwareTypePost(
name=module.params["name"],
array_type=module.params["array_type"],
media_type=module.params["media_type"],
display_name=display_name,
)
hw_api_instance.create_hardware_type(hw_type)
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Hardware Type {0} creation failed.: {1}".format(
module.params["name"], err
)
)
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"),
array_type=dict(type="str", choices=["FA//X", "FA//C"], required=True),
media_type=dict(type="str", required=True),
state=dict(type="str", default="present", choices=["present"]),
)
)
module = AnsibleModule(argument_spec, supports_check_mode=True)
fusion = get_fusion(module)
state = module.params["state"]
h_type = get_hw(module, fusion)
if not h_type and state == "present":
create_hw(module, fusion)
else:
module.exit_json(changed=False)
module.exit_json(changed=False)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,919 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2021, 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_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, availabiliy_zones, volunmes, snapshots
. Fleet capacity and data reduction rates are also provided.
- Additional information can be collected based on the configured set of arguements.
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, placements,
arrays, hardware_types, volumes, host, storage_classes, protection_policies,
placement_groups, interfaces, zones, nigs, storage_endpoints, snapshots,
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:
app_id: key_name
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
app_id: key_name
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: complex
"""
HAS_FUSION = True
try:
import fusion as purefusion
except ImportError:
HAS_FUSION = False
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import (
get_fusion,
fusion_argument_spec,
)
import time
def _convertMicroseconds(micros):
seconds = (micros / 1000) % 60
minutes = (micros / (1000 * 60)) % 60
hours = (micros / (1000 * 60 * 60)) % 24
return seconds, minutes, hours
def generate_default_dict(fusion):
default_info = {}
arrays_api_instance = purefusion.ArraysApi(fusion)
az_api_instance = purefusion.AvailabilityZonesApi(fusion)
default_api_instance = purefusion.DefaultApi(fusion)
hw_api_instance = purefusion.HardwareTypesApi(fusion)
host_access_api_instance = purefusion.HostAccessPoliciesApi(fusion)
id_api_instance = purefusion.IdentityManagerApi(fusion)
nic_api_instance = purefusion.NetworkInterfacesApi(fusion)
nig_api_instance = purefusion.NetworkInterfaceGroupsApi(fusion)
plgrp_api_instance = purefusion.PlacementGroupsApi(fusion)
protpol_api_instance = purefusion.ProtectionPoliciesApi(fusion)
regions_api_instance = purefusion.RegionsApi(fusion)
role_assign_api_instance = purefusion.RoleAssignmentsApi(fusion)
roles_api_instance = purefusion.RolesApi(fusion)
snapshot_api_instance = purefusion.SnapshotsApi(fusion)
send_api_instance = purefusion.StorageEndpointsApi(fusion)
storage_srv_api_instance = purefusion.StorageServicesApi(fusion)
storage_class_api_instance = purefusion.StorageClassesApi(fusion)
tenant_api_instance = purefusion.TenantsApi(fusion)
tenantspace_api_instance = purefusion.TenantSpacesApi(fusion)
vol_api_instance = purefusion.VolumesApi(fusion)
default_info["version"] = default_api_instance.get_version().version
storage_services = storage_srv_api_instance.list_storage_services()
default_info["storage_services"] = len(storage_services.items)
sclass = 0
for storserv in range(0, len(storage_services.items)):
sclass = sclass + len(
storage_class_api_instance.list_storage_classes(
storage_service_name=storage_services.items[storserv].name
).items
)
default_info["storage_classes"] = sclass
protection_policies = protpol_api_instance.list_protection_policies()
default_info["protection_policies"] = len(protection_policies.items)
users = id_api_instance.list_users()
default_info["users"] = len(users)
host_access_policies = host_access_api_instance.list_host_access_policies()
default_info["host_access_policies"] = len(host_access_policies.items)
hw_types = hw_api_instance.list_hardware_types()
default_info["hardware_types"] = len(hw_types.items)
tenants = tenant_api_instance.list_tenants()
default_info["tenants"] = len(tenants.items)
tenant_spaces = 0
for tenant in range(0, len(tenants.items)):
tenant_spaces = tenant_spaces + len(
tenantspace_api_instance.list_tenant_spaces(
tenant_name=tenants.items[tenant].name
).items
)
default_info["tenant_spaces"] = tenant_spaces
roles = roles_api_instance.list_roles()
assignments = 0
default_info["roles"] = len(roles)
for role in range(0, len(roles)):
assignments = assignments + len(
role_assign_api_instance.list_role_assignments(role_name=roles[role].name)
)
default_info["role_assignments"] = assignments
regions = regions_api_instance.list_regions()
default_info["regions"] = len(regions.items)
azs = 0
for count in range(0, len(regions.items)):
azs = azs + len(
az_api_instance.list_availability_zones(
region_name=regions.items[count].name
).items
)
default_info["availability_zones"] = azs
arrays = nigs = sendpoints = nics = 0
for count in range(0, len(regions.items)):
azones = az_api_instance.list_availability_zones(
region_name=regions.items[count].name
)
for azone in range(0, len(azones.items)):
array_details = arrays_api_instance.list_arrays(
availability_zone_name=azones.items[azone].name,
region_name=regions.items[count].name,
)
for array_detail in range(0, len(array_details.items)):
nics = nics + len(
nic_api_instance.list_network_interfaces(
availability_zone_name=azones.items[azone].name,
region_name=regions.items[count].name,
array_name=array_details.items[array_detail].name,
).items
)
nigs = nigs + len(
nig_api_instance.list_network_interface_groups(
availability_zone_name=azones.items[azone].name,
region_name=regions.items[count].name,
).items
)
sendpoints = sendpoints + len(
send_api_instance.list_storage_endpoints(
availability_zone_name=azones.items[azone].name,
region_name=regions.items[count].name,
).items
)
arrays = arrays + len(array_details.items)
default_info["appiiances"] = arrays
default_info["network_interfaces"] = nics
default_info["network_interface_groups"] = nigs
volumes = placement_grps = snapshots = 0
for tenant in range(0, len(tenants.items)):
tenant_spaces = tenantspace_api_instance.list_tenant_spaces(
tenant_name=tenants.items[tenant].name
).items
for tenant_space in range(0, len(tenant_spaces)):
volumes = volumes + len(
vol_api_instance.list_volumes(
tenant_name=tenants.items[tenant].name,
tenant_space_name=tenant_spaces[tenant_space].name,
).items
)
placement_grps = placement_grps + len(
plgrp_api_instance.list_placement_groups(
tenant_name=tenants.items[tenant].name,
tenant_space_name=tenant_spaces[tenant_space].name,
).items
)
snapshots = snapshots + len(
snapshot_api_instance.list_snapshots(
tenant_name=tenants.items[tenant].name,
tenant_space_name=tenant_spaces[tenant_space].name,
).items
)
default_info["volumes"] = volumes
default_info["placements_groups"] = placement_grps
default_info["snapshots"] = snapshots
return default_info
def generate_nics_dict(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 range(0, len(regions.items)):
azs = az_api_instance.list_availability_zones(
region_name=regions.items[region].name
)
for count in range(0, len(azs.items)):
array_details = arrays_api_instance.list_arrays(
availability_zone_name=azs.items[count].name,
region_name=regions.items[region].name,
)
for array_detail in range(0, len(array_details.items)):
array_name = (
azs.items[count].name + "/" + array_details.items[array_detail].name
)
nics_info[array_name] = {}
nics = nic_api_instance.list_network_interfaces(
availability_zone_name=azs.items[count].name,
region_name=regions.items[region].name,
array_name=array_details.items[array_detail].name,
)
for nic in range(0, len(nics.items)):
nics_info[array_name][nics.items[nic].name] = {
"enabled": nics.items[nic].enabled,
"display_name": nics.items[nic].display_name,
"interface_type": nics.items[nic].interface_type,
"services": nics.items[nic].services,
"max_speed": nics.items[nic].max_speed,
"vlan": nics.items[nic].eth.vlan,
"address": nics.items[nic].eth.address,
"mac_address": nics.items[nic].eth.mac_address,
"gateway": nics.items[nic].eth.gateway,
"mtu": nics.items[nic].eth.mtu,
"network_interface_group": nics.items[
nic
].network_interface_group.name,
"availability_zone": nics.items[nic].availability_zone.name,
}
return nics_info
def generate_hap_dict(fusion):
hap_info = {}
api_instance = purefusion.HostAccessPoliciesApi(fusion)
hosts = api_instance.list_host_access_policies()
for host in range(0, len(hosts.items)):
name = hosts.items[host].name
hap_info[name] = {
"personality": hosts.items[host].personality,
"display_name": hosts.items[host].display_name,
"iqn": hosts.items[host].iqn,
}
return hap_info
def generate_array_dict(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 range(0, len(regions.items)):
azs = az_api_instance.list_availability_zones(
region_name=regions.items[region].name
)
for az in range(0, len(azs.items)):
arrays = array_api_instance.list_arrays(
availability_zone_name=azs.items[az].name,
region_name=regions.items[region].name,
)
for array in range(0, len(arrays.items)):
array_name = arrays.items[array].name
array_space = array_api_instance.get_array_space(
availability_zone_name=azs.items[az].name,
array_name=array_name,
region_name=regions.items[region].name,
)
array_perf = array_api_instance.get_array_performance(
availability_zone_name=azs.items[az].name,
array_name=array_name,
region_name=regions.items[region].name,
)
array_info[array_name] = {
"region": regions.items[region].name,
"availability_zone": azs.items[az].name,
"host_name": arrays.items[array].host_name,
"maintenance_mode": arrays.items[array].maintenance_mode,
"unavailable_mode": arrays.items[array].unavailable_mode,
"display_name": arrays.items[array].display_name,
"hardware_type": arrays.items[array].hardware_type.name,
"appliance_id": arrays.items[array].appliance_id,
"apartment_id": getattr(arrays.items[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
def generate_pg_dict(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 range(0, len(tenants.items)):
tenant_spaces = tenantspace_api_instance.list_tenant_spaces(
tenant_name=tenants.items[tenant].name
).items
for tenant_space in range(0, len(tenant_spaces)):
groups = pg_api_instance.list_placement_groups(
tenant_name=tenants.items[tenant].name,
tenant_space_name=tenant_spaces[tenant_space].name,
)
for group in range(0, len(groups.items)):
group_name = (
tenants.items[tenant].name
+ "/"
+ tenant_spaces[tenant_space].name
+ "/"
+ groups.items[group].name
)
pg_info[group_name] = {
"tenant": groups.items[group].tenant.name,
"display_name": groups.items[group].display_name,
"placement_engine": groups.items[group].placement_engine,
"tenant_space": groups.items[group].tenant_space.name,
"az": groups.items[group].availability_zone.name,
"array": getattr(groups.items[group].array, "name", None),
}
return pg_info
def generate_placements_dict(fusion):
pl_info = {}
tenant_api_instance = purefusion.TenantsApi(fusion)
tenantspace_api_instance = purefusion.TenantSpacesApi(fusion)
pl_api_instance = purefusion.PlacementsApi(fusion)
tenants = tenant_api_instance.list_tenants()
for tenant in range(0, len(tenants.items)):
tenant_spaces = tenantspace_api_instance.list_tenant_spaces(
tenant_name=tenants.items[tenant].name
).items
for tenant_space in range(0, len(tenant_spaces)):
placements = pl_api_instance.list_placements(
tenant_name=tenants.items[tenant].name,
tenant_space_name=tenant_spaces[tenant_space].name,
)
for placement in range(0, len(placements.items)):
pl_name = (
tenants.items[tenant].name
+ "/"
+ tenant_spaces[tenant_space].name
+ "/"
+ placements.items[placement].name
)
pl_info[pl_name] = {
"tenant": tenants.items[tenant].name,
"tenant_space": tenant_spaces[tenant_space].name,
"display_name": placements.items[placement].display_name,
"placement_group": placements.items[placement].placement_group.name,
"storage_class": placements.items[placement].storage_class.name,
"array": placements.items[placement].array.name,
"protocols": {
"iscsi": {},
"fc": {},
"nvme": {},
},
}
if placements.items[placement].protocols.iscsi:
pl_info[pl_name]["protocols"]["iscsi"] = {
"iqn": placements.items[placement].protocols.iscsi.iqn,
"addresses": placements.items[
placement
].protocols.iscsi.addresses,
}
return pl_info
def generate_ts_dict(fusion):
ts_info = {}
tenant_api_instance = purefusion.TenantsApi(fusion)
tenantspace_api_instance = purefusion.TenantSpacesApi(fusion)
tenants = tenant_api_instance.list_tenants()
for tenant in range(0, len(tenants.items)):
tenant_spaces = tenantspace_api_instance.list_tenant_spaces(
tenant_name=tenants.items[tenant].name
).items
for tenant_space in range(0, len(tenant_spaces)):
ts_name = (
tenants.items[tenant].name + "/" + tenant_spaces[tenant_space].name
)
ts_info[ts_name] = {
"tenant": tenants.items[tenant].name,
"display_name": tenant_spaces[tenant_space].display_name,
}
return ts_info
def generate_pp_dict(fusion):
pp_info = {}
api_instance = purefusion.ProtectionPoliciesApi(fusion)
policies = api_instance.list_protection_policies()
for policy in range(0, len(policies.items)):
policy_name = policies.items[policy].name
pp_info[policy_name] = {
"objectives": policies.items[policy].objectives,
}
return pp_info
def generate_tenant_dict(fusion):
tenant_info = {}
api_instance = purefusion.TenantsApi(fusion)
tenants = api_instance.list_tenants()
for tenant in range(0, len(tenants.items)):
name = tenants.items[tenant].name
tenant_info[name] = {
"display_name": tenants.items[tenant].display_name,
}
return tenant_info
def generate_zones_dict(fusion):
zones_info = {}
az_api_instance = purefusion.AvailabilityZonesApi(fusion)
regions_api_instance = purefusion.RegionsApi(fusion)
regions = regions_api_instance.list_regions()
for region in range(0, len(regions.items)):
zones = az_api_instance.list_availability_zones(
region_name=regions.items[region].name
)
for zone in range(0, len(zones.items)):
az_name = zones.items[zone].name
zones_info[az_name] = {
"display_name": zones.items[zone].display_name,
"region": zones.items[zone].region.name,
}
return zones_info
def generate_ras_dict(fusion):
ras_info = {}
ras_api_instance = purefusion.RoleAssignmentsApi(fusion)
role_api_instance = purefusion.RolesApi(fusion)
roles = role_api_instance.list_roles()
for role in range(0, len(roles)):
ras = ras_api_instance.list_role_assignments(role_name=roles[role].name)
for assignment in range(0, len(ras)):
name = ras[assignment].name
ras_info[name] = {
"display_name": ras[assignment].display_name,
"role": ras[assignment].role.name,
"scope": ras[assignment].scope.name,
}
return ras_info
def generate_roles_dict(fusion):
roles_info = {}
api_instance = purefusion.RolesApi(fusion)
roles = api_instance.list_roles()
for role in range(0, len(roles)):
name = roles[role].name
roles_info[name] = {
"display_name": roles[role].display_name,
"scopes": roles[role].assignable_scopes,
}
return roles_info
def generate_api_client_dict(fusion):
client_info = {}
api_instance = purefusion.IdentityManagerApi(fusion)
clients = api_instance.list_api_clients()
for client in range(0, len(clients)):
name = clients[client].name
client_info[name] = {
"display_name": clients[client].display_name,
"issuer": clients[client].issuer,
"public_key": clients[client].public_key,
"creator_id": clients[client].creator_id,
"last_key_update": time.strftime(
"%a, %d %b %Y %H:%M:%S %Z",
time.localtime(clients[client].last_key_update / 1000),
),
"last_used": time.strftime(
"%a, %d %b %Y %H:%M:%S %Z",
time.localtime(clients[client].last_used / 1000),
),
}
return client_info
def generate_users_dict(fusion):
users_info = {}
api_instance = purefusion.IdentityManagerApi(fusion)
users = api_instance.list_users()
for user in range(0, len(users)):
name = users[user].name
users_info[name] = {
"display_name": users[user].display_name,
"email": users[user].email,
"id": users[user].id,
}
return users_info
def generate_hardware_dict(fusion):
hardware_info = {}
api_instance = purefusion.HardwareTypesApi(fusion)
hw_types = api_instance.list_hardware_types()
for hw_type in range(0, len(hw_types.items)):
type_name = hw_types.items[hw_type].name
hardware_info[type_name] = {
"array_type": hw_types.items[hw_type].array_type,
"display_name": hw_types.items[hw_type].display_name,
"media_type": hw_types.items[hw_type].media_type,
}
return hardware_info
def generate_storageclass_dict(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 range(0, len(services.items)):
classes = sc_api_instance.list_storage_classes(
storage_service_name=services.items[service].name,
)
for s_class in range(0, len(classes.items)):
sc_name = classes.items[s_class].name
sc_info[sc_name] = {
"bandwidth_limit": getattr(
classes.items[s_class], "bandwidth_limit", None
),
"iops_limit": getattr(classes.items[s_class], "iops_limit", None),
"size_limit": getattr(classes.items[s_class], "size_limit", None),
"display_name": classes.items[s_class].display_name,
"storage_service": services.items[service].name,
}
return sc_info
def generate_storserv_dict(fusion):
ss_dict = {}
ss_api_instance = purefusion.StorageServicesApi(fusion)
services = ss_api_instance.list_storage_services()
for service in range(0, len(services.items)):
ss_dict[services.items[service].name] = {
"display_name": services.items[service].display_name,
"hardware_types": [],
}
for hwtype in range(0, len(services.items[service].hardware_types)):
ss_dict[services.items[service].name]["hardware_types"].append(
services.items[service].hardware_types[hwtype].name
)
return ss_dict
def generate_se_dict(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 range(0, len(regions.items)):
azs = az_api_instance.list_availability_zones(
region_name=regions.items[region].name
)
for az in range(0, len(azs.items)):
endpoints = se_api_instance.list_storage_endpoints(
region_name=regions.items[region].name,
availability_zone_name=azs.items[az].name,
)
for endpoint in range(0, len(endpoints.items)):
name = (
regions.items[region].name
+ "/"
+ azs.items[az].name
+ "/"
+ endpoints.items[endpoint].name
)
se_dict[name] = {
"display_name": endpoints.items[endpoint].display_name,
"endpoint_type": endpoints.items[endpoint].endpoint_type,
"iscsi_interfaces": [],
}
for iface in range(
0, len(endpoints.items[endpoint].iscsi.discovery_interfaces)
):
se_dict[name]["iscsi_interfaces"].append(
{
"address": endpoints.items[endpoint]
.iscsi.discovery_interfaces[iface]
.address,
"gateway": endpoints.items[endpoint]
.iscsi.discovery_interfaces[iface]
.gateway,
"mtu": endpoints.items[endpoint]
.iscsi.discovery_interfaces[iface]
.mtu,
"network_interface_group": endpoints.items[endpoint]
.iscsi.discovery_interfaces[iface]
.network_interface_groups[0]
.name,
}
)
return se_dict
def generate_nig_dict(fusion):
nig_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 range(0, len(regions.items)):
azs = az_api_instance.list_availability_zones(
region_name=regions.items[region].name
)
for az in range(0, len(azs.items)):
nigs = nig_api_instance.list_network_interface_groups(
region_name=regions.items[region].name,
availability_zone_name=azs.items[az].name,
)
for nig in range(0, len(nigs.items)):
name = (
regions.items[region].name
+ "/"
+ azs.items[az].name
+ "/"
+ nigs.items[nig].name
)
nig_dict[name] = {
"display_name": nigs.items[nig].display_name,
"gateway": nigs.items[nig].eth.gateway,
"prefix": nigs.items[nig].eth.prefix,
"mtu": nigs.items[nig].eth.mtu,
}
return nig_dict
def generate_snap_dict(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 range(0, len(tenants.items)):
tenant_spaces = tenantspace_api_instance.list_tenant_spaces(
tenant_name=tenants.items[tenant].name
).items
for tenant_space in range(0, len(tenant_spaces)):
snaps = snap_api_instance.list_snapshots(
tenant_name=tenants.items[tenant].name,
tenant_space_name=tenant_spaces[tenant_space].name,
)
for snap in range(0, len(snaps.items)):
snap_name = (
tenants.items[tenant].name
+ "/"
+ tenant_spaces[tenant_space].name
+ "/"
+ snaps.items[snap].name
)
secs, mins, hours = _convertMicroseconds(
snaps.items[snap].time_remaining
)
snap_dict[snap_name] = {
"display_name": snaps.items[snap].display_name,
"protection_policy": snaps.items[snap].protection_policy,
"time_remaining": "{0} hours, {1} mins, {2} secs".format(
int(hours), int(mins), int(secs)
),
"volume_snapshots_link": snaps.items[snap].volume_snapshots_link,
}
vsnaps = vsnap_api_instance.list_volume_snapshots(
tenant_name=tenants.items[tenant].name,
tenant_space_name=tenant_spaces[tenant_space].name,
snapshot_name=snaps.items[snap].name,
)
for vsnap in range(0, len(vsnaps.items)):
vsnap_name = (
tenants.items[tenant].name
+ "/"
+ tenant_spaces[tenant_space].name
+ "/"
+ snaps.items[snap].name
+ "/"
+ vsnaps.items[vsnap].name
)
secs, mins, hours = _convertMicroseconds(
vsnaps.items[vsnap].time_remaining
)
vsnap_dict[vsnap_name] = {
"size": vsnaps.items[vsnap].size,
"display_name": vsnaps.items[vsnap].display_name,
"protection_policy": vsnaps.items[vsnap].protection_policy,
"serial_number": vsnaps.items[vsnap].serial_number,
"created_at": time.strftime(
"%a, %d %b %Y %H:%M:%S %Z",
time.localtime(vsnaps.items[vsnap].created_at / 1000),
),
"time_remaining": "{0} hours, {1} mins, {2} secs".format(
int(hours), int(mins), int(secs)
),
"placement_group": vsnaps.items[vsnap].placement_group.name,
}
return snap_dict, vsnap_dict
def generate_volumes_dict(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 range(0, len(tenants.items)):
tenant_spaces = tenant_space_api_instance.list_tenant_spaces(
tenant_name=tenants.items[tenant].name
).items
for tenant_space in range(0, len(tenant_spaces)):
volumes = vol_api_instance.list_volumes(
tenant_name=tenants.items[tenant].name,
tenant_space_name=tenant_spaces[tenant_space].name,
)
for volume in range(0, len(volumes.items)):
vol_name = (
tenants.items[tenant].name
+ "/"
+ tenant_spaces[tenant_space].name
+ "/"
+ volumes.items[volume].name
)
volume_info[vol_name] = {
"tenant": tenants.items[tenant].name,
"tenant_space": tenant_spaces[tenant_space].name,
"name": volumes.items[volume].name,
"size": volumes.items[volume].size,
"display_name": volumes.items[volume].display_name,
"array": volumes.items[volume].array.name,
"placement_group": volumes.items[volume].placement_group.name,
"source_volume_snapshot": getattr(
volumes.items[volume].source_volume_snapshot, "name", None
),
"protection_policy": getattr(
volumes.items[volume].protection_policy, "name", None
),
"storage_class": volumes.items[volume].storage_class.name,
"serial_number": volumes.items[volume].serial_number,
"target": {},
}
volume_info[vol_name]["target"] = {
"iscsi": {
"addresses": volumes.items[volume].target.iscsi.addresses,
"iqn": volumes.items[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)
if not HAS_FUSION:
module.fail_json(msg="fusion SDK required for this module")
fusion = get_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",
)
subset_test = (test in valid_subsets for test in subset)
if not all(subset_test):
module.fail_json(
msg="value must gather_subset must be one or more of: %s, got: %s"
% (",".join(valid_subsets), ",".join(subset))
)
info = {}
if "minimum" in subset or "all" in subset:
info["default"] = generate_default_dict(fusion)
if "hardware_types" in subset or "all" in subset:
info["hardware"] = generate_hardware_dict(fusion)
if "users" in subset or "all" in subset:
info["users"] = generate_users_dict(fusion)
if "zones" in subset or "all" in subset:
info["zones"] = generate_zones_dict(fusion)
if "roles" in subset or "all" in subset:
info["roles"] = generate_roles_dict(fusion)
info["role_assignments"] = generate_ras_dict(fusion)
if "storage_services" in subset or "all" in subset:
info["storage_services"] = generate_storserv_dict(fusion)
if "volumes" in subset or "all" in subset:
info["volumes"] = generate_volumes_dict(fusion)
if "protection_policies" in subset or "all" in subset:
info["protection_policies"] = generate_pp_dict(fusion)
if "placement_groups" in subset or "all" in subset:
info["placement_groups"] = generate_pg_dict(fusion)
if "storage_classes" in subset or "all" in subset:
info["storageclass"] = generate_storageclass_dict(fusion)
if "interfaces" in subset or "all" in subset:
info["interfaces"] = generate_nics_dict(fusion)
if "hosts" in subset or "all" in subset:
info["hosts"] = generate_hap_dict(fusion)
if "arrays" in subset or "all" in subset:
info["arrays"] = generate_array_dict(fusion)
if "tenants" in subset or "all" in subset:
info["tenants"] = generate_tenant_dict(fusion)
if "tenant_spaces" in subset or "all" in subset:
info["tenant_spaces"] = generate_ts_dict(fusion)
if "storage_endpoints" in subset or "all" in subset:
info["storage_endpoints"] = generate_se_dict(fusion)
if "api_clients" in subset or "all" in subset:
info["api_clients"] = generate_api_client_dict(fusion)
if "nigs" in subset or "all" in subset:
info["network_interface_groups"] = generate_nig_dict(fusion)
if "snapshots" in subset or "all" in subset:
info["snapshots"], info["volume_snapshots"] = generate_snap_dict(fusion)
module.exit_json(changed=False, fusion_info=info)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,279 @@
#!/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.
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 format.
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
app_id: key_name
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
app_id: key_name
key_file: "az-admin-private-key.pem"
"""
RETURN = r"""
"""
HAS_FUSION = True
try:
import fusion as purefusion
except ImportError:
HAS_FUSION = False
try:
from netaddr import IPNetwork
HAS_NETADDR = True
except ImportError:
HAS_NETADDR = False
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import (
get_fusion,
fusion_argument_spec,
)
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 get_az(module, fusion):
"""Availability Zone or None"""
az_api_instance = purefusion.AvailabilityZonesApi(fusion)
try:
return az_api_instance.get_availability_zone(
availability_zone_name=module.params["availability_zone"],
region_name=module.params["region"],
)
except purefusion.rest.ApiException:
return None
def create_nig(module, fusion):
"""Create Network Interface Group"""
nig_api_instance = purefusion.NetworkInterfaceGroupsApi(fusion)
changed = True
if module.params["gateway"] and module.params["gateway"] not in IPNetwork(
module.params["prefix"]
):
module.fail_json(msg="Gateway and subnet prefix are not compatible.")
if not module.check_mode:
if not module.params["display_name"]:
display_name = module.params["name"]
else:
display_name = module.params["display_name"]
try:
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,
)
nig_api_instance.create_network_interface_group(
nig,
availability_zone_name=module.params["availability_zone"],
region_name=module.params["region"],
)
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Network Interface Group {0} creation failed.: {1}".format(
module.params["name"], err
)
)
module.exit_json(changed=changed)
def delete_nig(module, fusion):
"""Delete Network Interface Group"""
changed = True
nig_api_instance = purefusion.NetworkInterfaceGroupsApi(fusion)
if not module.check_mode:
try:
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"],
)
except purefusion.rest.ApiException:
module.fail_json(
msg="Delete Network Interface Group {0} failed.".format(
module.params["name"]
)
)
module.exit_json(changed=changed)
def update_nig(module, fusion, nig):
"""Update Network Interface Group"""
changed = False
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"),
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)
if not HAS_NETADDR:
module.fail_json(msg="netaddr module is required")
state = module.params["state"]
fusion = get_fusion(module)
if module.params["prefix"]:
if "/" not in module.params["prefix"]:
module.fail_json(msg="Prefix must be in a CIDR format")
if 8 > int(module.params["prefix"].split("/")[1]) > 32:
module.fail_json(
msg="An invalid CIDR notation has been provided: {0}".format(
module.params["prefix"]
)
)
if not get_az(module, fusion):
module.fail_json(msg="Availability Zone {0} does not exist")
nig = get_nig(module, fusion)
if state == "present" and not nig:
if not module.params["prefix"]:
module.fail_json(
msg="When creating a new network interface group "
"`prefix` must be provided"
)
create_nig(module, fusion)
elif state == "present" and nig:
# TODO: re-add this when SDK bug fixed
module.exit_json(changed=False)
# update_ps(module, fusion, subnet)
elif state == "absent" and nig:
delete_nig(module, fusion)
module.exit_json(changed=False)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,247 @@
#!/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 ]
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
required: true
availability_zone:
aliases: [ az ]
description:
- The name of the availability zone to create the placement group in.
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 ]
default: heuristics
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
placement_engine: pure1meta
state: present
app_id: key_name
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
app_id: key_name
key_file: "az-admin-private-key.pem"
"""
RETURN = r"""
"""
HAS_FUSION = True
try:
import fusion as purefusion
except ImportError:
HAS_FUSION = False
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import (
get_fusion,
fusion_argument_spec,
)
def get_ts(module, fusion):
"""Tenant Space or None"""
ts_api_instance = purefusion.TenantSpacesApi(fusion)
try:
return ts_api_instance.get_tenant_space(
tenant_name=module.params["tenant"],
tenant_space_name=module.params["tenant_space"],
)
except purefusion.rest.ApiException:
return None
def get_az(module, fusion):
"""Availability Zone or None"""
api_instance = purefusion.AvailabilityZonesApi(fusion)
try:
return api_instance.get_availability_zone(
availability_zone_name=module.params["availability_zone"],
region_name=module.params["region"],
)
except purefusion.rest.ApiException:
return None
def get_tenant(module, fusion):
"""Return Tenant or None"""
api_instance = purefusion.TenantsApi(fusion)
try:
return api_instance.get_tenant(tenant_name=module.params["tenant"])
except purefusion.rest.ApiException:
return None
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)
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"]
try:
group = purefusion.PlacementGroupPost(
placement_engine=module.params["placement_engine"].lower(),
availability_zone=module.params["availability_zone"],
name=module.params["name"],
display_name=display_name,
)
pg_api_instance.create_placement_group(
group,
tenant_name=module.params["tenant"],
tenant_space_name=module.params["tenant_space"],
)
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Placement Group {0} creation failed.: {1}".format(
module.params["name"], err
)
)
module.exit_json(changed=changed)
def delete_pg(module, fusion):
"""Delete Placement Group"""
changed = True
pg_api_instance = purefusion.PlacementGroupsApi(fusion)
if not module.check_mode:
try:
pg_api_instance.delete_placement_group(
placement_group_name=module.params["name"],
tenant_name=module.params["tenant"],
tenant_space_name=module.params["tenant_space"],
)
except purefusion.rest.ApiException:
module.fail_json(
msg="Delete Placement Group {0} failed.".format(module.params["name"])
)
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),
tenant_space=dict(type="str", required=True),
region=dict(type="str", required=True),
availability_zone=dict(type="str", aliases=["az"]),
state=dict(type="str", default="present", choices=["absent", "present"]),
placement_engine=dict(
type="str", default="heuristics", choices=["heuristics", "pure1meta"]
),
)
)
required_if = [["state", "present", ["availability_zone", "placement_engine"]]]
module = AnsibleModule(
argument_spec, required_if=required_if, supports_check_mode=True
)
state = module.params["state"]
fusion = get_fusion(module)
pgroup = get_pg(module, fusion)
if not (
get_az(module, fusion) and get_tenant(module, fusion) and get_ts(module, fusion)
):
module.fail_json(
msg="Please check the values for `availability_zone`, `tenant` "
"and `tenant_space` to ensure they all exit and have appropriate relationships."
)
if state == "present" and not pgroup:
create_pg(module, fusion)
elif state == "absent" and pgroup:
delete_pg(module, fusion)
module.exit_json(changed=False)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,219 @@
#!/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
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.
- Value should be specified in minutes.
- Minimum value is 10 minutes.
type: int
required: true
local_retention:
description:
- Retention Duration for periodic snapshots.
- Minimum value is 1 minute.
- Value can be provided as m(inutes), h(ours),
d(ays), w(eeks), or y(ears).
- If no unit is provided, minutes are assumed.
- Must be between 1MB/s and 512GB/s.
type: str
required: true
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"
app_id: key_name
key_file: "az-admin-private-key.pem"
- name: Create new protection policy foo
purestorage.fusion.fusion_pp:
name: foo
state: absent
app_id: key_name
key_file: "az-admin-private-key.pem"
"""
RETURN = r"""
"""
HAS_FUSION = True
try:
import fusion as purefusion
except ImportError:
HAS_FUSION = False
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import (
get_fusion,
fusion_argument_spec,
)
def human_to_minutes(period):
"""Given a human-readable period (e.g. 2d, 3w),
return the number of minutes. Will return 0 if the argument has
unexpected form.
"""
minutes = period[:-1]
unit = period[-1].lower()
if minutes.isdigit():
minutes = int(minutes)
if unit == "y":
minutes *= 524160
elif unit == "w":
minutes *= 10080
elif unit == "d":
minutes *= 1440
elif unit == "h":
minutes *= 60
else:
minutes = 0
else:
minutes = 0
return minutes
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_retention = human_to_minutes(module.params["local_retention"])
if local_retention < 1:
module.fail_json(msg="Local Retention must be a minimum of 1 minutes")
if module.params["local_rpo"] < 10:
module.fail_json(msg="Local RPO must be a minimum of 10 minutes")
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"]
try:
pp_api_instance.create_protection_policy(
purefusion.ProtectionPolicyPost(
name=module.params["name"],
display_name=display_name,
objectives=[
{
"type": "RPO",
"rpo": "PT" + str(module.params["local_rpo"]) + "M",
},
{
"type": "Retention",
"after": "PT" + str(local_retention) + "M",
},
],
)
)
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Protection Policy {0} creation failed.: {1}".format(
module.params["name"], err
)
)
module.exit_json(changed=changed)
def delete_pp(module, fusion):
"""Delete Protection Policy"""
pp_api_instance = purefusion.ProtectionPoliciesApi(fusion)
changed = True
if not module.check_mode:
try:
pp_api_instance.delete_protection_policy(
protection_policy_name=module.params["name"],
)
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Protection Policy {0} deletion failed.: {1}".format(
module.params["name"], err
)
)
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"),
local_rpo=dict(type="int", required=True),
local_retention=dict(type="str", required=True),
state=dict(type="str", default="present", choices=["present", "absent"]),
)
)
module = AnsibleModule(argument_spec, supports_check_mode=True)
fusion = get_fusion(module)
state = module.params["state"]
policy = get_pp(module, fusion)
if not policy and state == "present":
create_pp(module, fusion)
elif policy and state == "absent":
delete_pp(module, fusion)
else:
module.exit_json(changed=False)
module.exit_json(changed=False)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,270 @@
#!/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:
name:
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(app_id).
required: true
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
app_id: key_name
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
app_id: key_name
key_file: "az-admin-private-key.pem"
"""
RETURN = r"""
"""
HAS_FUSION = True
try:
import fusion as purefusion
except ImportError:
HAS_FUSION = False
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import (
get_fusion,
fusion_argument_spec,
)
def human_to_principal(fusion, user_id):
"""Given a human readable Fusion user, such as a Pure 1 App ID
return the associated principal
"""
principal = None
id_api_instance = purefusion.IdentityManagerApi(fusion)
users = id_api_instance.list_users()
for user in range(0, len(users)):
if users[user].name == user_id:
principal = users[user].id
return principal
def human_to_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_role(module, fusion):
"""Return Role or None"""
role_api_instance = purefusion.RolesApi(fusion)
try:
return role_api_instance.get_role(role_name=module.params["name"])
except purefusion.rest.ApiException:
return None
def get_ra(module, fusion):
"""Return Role Assignment or None"""
ra_api_instance = purefusion.RoleAssignmentsApi(fusion)
try:
assignments = ra_api_instance.list_role_assignments(
role_name=module.params["name"]
)
for assign in range(0, len(assignments)):
principal = human_to_principal(fusion, module.params["user"])
scope = human_to_scope(module.params)
if (
assignments[assign].principal == principal
and assignments[assign].scope.self_link == scope
):
return assignments[assign]
return None
except purefusion.rest.ApiException:
return None
def get_tenant(module, fusion):
"""Return tenant or None"""
t_api_instance = purefusion.TenantsApi(fusion)
try:
return t_api_instance.get_tenant(tenant_name=module.params["tenant"])
except purefusion.rest.ApiException:
return None
def get_ts(module, fusion):
"""Return tenant space or None"""
ts_api_instance = purefusion.TenantSpacesApi(fusion)
try:
return ts_api_instance.get_tenant_space(
tenant_space_name=module.params["tenant_space"],
tenant_name=module.params["tenant"],
)
except purefusion.rest.ApiException:
return None
def create_ra(module, fusion):
"""Create Role Assignment"""
ra_api_instance = purefusion.RoleAssignmentsApi(fusion)
changed = True
if not module.check_mode:
scope = human_to_scope(module.params)
principal = human_to_principal(fusion, module.params["user"])
assignment = purefusion.RoleAssignmentPost(scope=scope, principal=principal)
try:
ra_api_instance.create_role_assignment(
assignment, role_name=module.params["name"]
)
except purefusion.rest.ApiException:
module.fail_json(
msg="{0} level Role Assignment creation for user {1} failed".format(
module.params["scope"], module.params["user"]
)
)
module.exit_json(changed=changed)
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
try:
ra_api_instance.delete_role_assignment(
role_name=module.params["name"], role_assignment_name=ra_name
)
except purefusion.rest.ApiException:
module.fail_json(
msg="{0} level Role Assignment delete for user {1} failed".format(
module.params["scope"], module.params["user"]
)
)
module.exit_json(changed=changed)
def main():
"""Main code"""
argument_spec = fusion_argument_spec()
argument_spec.update(
dict(
name=dict(type="str", required=True),
tenant=dict(type="str"),
tenant_space=dict(type="str"),
user=dict(type="str", required=True),
scope=dict(
type="str",
default="organization",
choices=["organization", "tenant", "tenant_space"],
),
state=dict(type="str", default="present", choices=["present", "absent"]),
)
)
required_if = [
["scope", "tenant", ["tenant"]],
["scope", "tenant_space", ["tenant", "tenant_space"]],
]
module = AnsibleModule(
argument_spec, required_if=required_if, supports_check_mode=True
)
fusion = get_fusion(module)
state = module.params["state"]
if not human_to_principal(fusion, module.params["user"]):
module.fail_json(msg="User {0} does not exist".format(module.params["user"]))
if module.params["tenant"] and not get_tenant(module, fusion):
module.fail_json(
msg="Tenant {0} does not exist".format(module.params["tenant"])
)
if module.params["tenant_space"] and not get_ts(module, fusion):
module.fail_json(
msg="Tenant Space {0} does not exist".format(module.params["tenant_space"])
)
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()

View File

@@ -0,0 +1,196 @@
#!/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"
app_id: key_name
key_file: "az-admin-private-key.pem"
- name: Update region foo
purestorage.fusion.fusion_region:
name: foo
display_name: "new foo Region"
app_id: key_name
key_file: "az-admin-private-key.pem"
- name: Delete region foo
purestorage.fusion.fusion_region:
name: foo
state: absent
app_id: key_name
key_file: "az-admin-private-key.pem"
"""
RETURN = r"""
"""
HAS_FUSION = True
try:
import fusion as purefusion
except ImportError:
HAS_FUSION = False
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import (
get_fusion,
fusion_argument_spec,
)
def get_region(module, fusion):
"""Get Region or None"""
region_api_instance = purefusion.RegionsApi(fusion)
try:
return region_api_instance.get_region(
region_name=module.params["region"],
)
except purefusion.rest.ApiException:
return None
def create_region(module, fusion):
"""Create Region"""
reg_api_instance = purefusion.RegionsApi(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"]
try:
region = purefusion.RegionPost(
name=module.params["name"],
display_name=display_name,
)
reg_api_instance.create_region(region)
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Region {0} creation failed.: {1}".format(
module.params["name"], err
)
)
module.exit_json(changed=changed)
def delete_region(module, fusion):
"""Delete Region"""
reg_api_instance = purefusion.RegionsApi(fusion)
changed = True
if not module.check_mode:
try:
reg_api_instance.delete_region(region_name=module.params["region"])
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Region {0} creation failed.: {1}".format(
module.params["name"], err
)
)
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"])
)
try:
reg_api_instance.update_region(
reg,
region_name=module.params["name"],
)
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Changing region display_name failed: {0}".format(err)
)
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 = get_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()

View File

@@ -0,0 +1,344 @@
#!/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
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"
app_id: key_name
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"
app_id: key_name
key_file: "az-admin-private-key.pem"
- name: Delete storage class
purestorage.fusion.fusion_sc:
name: foo
storage_service: service1
state: absent
app_id: key_name
key_file: "az-admin-private-key.pem"
"""
RETURN = r"""
"""
HAS_FUSION = True
try:
import fusion as purefusion
except ImportError:
HAS_FUSION = False
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import (
get_fusion,
fusion_argument_spec,
)
def human_to_bytes(size):
"""Given a human-readable byte string (e.g. 2G, 30M),
return the number of bytes. Will return 0 if the argument has
unexpected form.
"""
my_bytes = size[:-1]
unit = size[-1].upper()
if my_bytes.isdigit():
my_bytes = int(my_bytes)
if unit == "P":
my_bytes *= 1125899906842624
elif unit == "T":
my_bytes *= 1099511627776
elif unit == "G":
my_bytes *= 1073741824
elif unit == "M":
my_bytes *= 1048576
elif unit == "K":
my_bytes *= 1024
else:
my_bytes = 0
else:
my_bytes = 0
return my_bytes
def human_to_real(iops):
"""Given a human-readable IOPs string (e.g. 2K, 30M),
return the real number. Will return 0 if the argument has
unexpected form.
"""
digit = iops[:-1]
unit = iops[-1].upper()
if unit.isdigit():
digit = iops
elif digit.isdigit():
digit = int(digit)
if unit == "M":
digit *= 1000000
elif unit == "K":
digit *= 1000
else:
digit = 0
else:
digit = 0
return digit
def bytes_to_human(bytes_number):
"""Convert bytes to a human readable string"""
if bytes_number:
labels = ["B", "KB", "MB", "GB", "TB", "PB"]
i = 0
double_bytes = bytes_number
while i < len(labels) and bytes_number >= 1024:
double_bytes = bytes_number / 1024.0
i += 1
bytes_number = bytes_number / 1024
return str(round(double_bytes, 2)) + " " + labels[i]
return None
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 get_ss(module, fusion):
"""Return Storage Service or None"""
ss_api_instance = purefusion.StorageServicesApi(fusion)
try:
return ss_api_instance.get_storage_service(
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 = human_to_bytes(module.params["size_limit"])
iops_limit = int(human_to_real(module.params["iops_limit"]))
bw_limit = human_to_bytes(module.params["bw_limit"])
if bw_limit not in range(1048576, 549755813889): # 1MB/s to 512GB/s
module.fail_json(msg="Bandwidth limit is not within the required range")
if 100 > iops_limit > 10000000:
module.fail_json(msg="IOPs limit is not within the required range")
if size_limit not in range(1048576, 4503599627370497): # 1MB to 4PB
module.fail_json(msg="Size limit is not within the required range")
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"]
try:
s_class = purefusion.StorageClassPost(
name=module.params["name"],
size_limit=size_limit,
iops_limit=iops_limit,
bandwidth_limit=bw_limit,
display_name=display_name,
)
sc_api_instance.create_storage_class(
s_class, storage_service_name=module.params["storage_service"]
)
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Storage Class {0} creation failed.: {1}".format(
module.params["name"], err
)
)
module.exit_json(changed=changed)
def update_sc(module, fusion):
"""Update Storage Class settings"""
changed = False
sc_api_instance = purefusion.StorageClassesApi(fusion)
s_class = sc_api_instance.get_storage_class(
storage_class_name=module.params["name"],
storage_service_name=module.params["storage_service"],
)
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"])
)
try:
sc_api_instance.update_storage_class(
sclass,
storage_service_name=module.params["storage_service"],
storage_class_name=module.params["name"],
)
except purefusion.rest.ApiException as err:
module.fail_json(msg="Changing display_name failed: {0}".format(err))
module.exit_json(changed=changed)
def delete_sc(module, fusion):
"""Delete Storage Class"""
sc_api_instance = purefusion.StorageClassesApi(fusion)
changed = True
if not module.check_mode:
try:
sc_api_instance.delete_storage_class(
storage_class=module.params["name"],
storage_service_name=module.params["storage_service"],
)
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Storage Class {0} deletion failed.: {1}".format(
module.params["name"], err
)
)
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"),
state=dict(type="str", default="present", choices=["present", "absent"]),
)
)
module = AnsibleModule(argument_spec, supports_check_mode=True)
fusion = get_fusion(module)
state = module.params["state"]
s_class = get_sc(module, fusion)
if not s_class and not module.params["storage_service"]:
module.fail_json(
msg="`hardware_type` is required when creating a new Storage Class"
)
if module.params["storage_service"] and not get_ss(module, fusion):
module.fail_json(
msg="Storage Service Type {0} does not exist".format(
module.params["storage_service"]
)
)
if not s_class and state == "present":
create_sc(module, fusion)
elif s_class and state == "present":
update_sc(module, fusion)
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()

View File

@@ -0,0 +1,232 @@
#!/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.
required: true
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_type:
- flash-array-x
- flash-array-x-optane
display_name: "test class"
app_id: key_name
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
app_id: key_name
key_file: "az-admin-private-key.pem"
- name: Delete storage service
purestorage.fusion.fusion_ss:
name: foo
state: absent
app_id: key_name
key_file: "az-admin-private-key.pem"
"""
RETURN = r"""
"""
HAS_FUSION = True
try:
import fusion as purefusion
except ImportError:
HAS_FUSION = False
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import (
get_fusion,
fusion_argument_spec,
)
def get_ss(module, fusion):
"""Return Storage Service or None"""
ss_api_instance = purefusion.StorageServicesApi(fusion)
try:
return ss_api_instance.get_storage_service(
storage_service_name=module.params["name"]
)
except purefusion.rest.ApiException:
return None
def create_ss(module, fusion):
"""Create Storage Service"""
ss_api_instance = purefusion.StorageServicesApi(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"]
try:
s_service = purefusion.StorageServicePost(
name=module.params["name"],
display_name=display_name,
hardware_types=module.params["hardware_types"],
)
ss_api_instance.create_storage_service(s_service)
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Storage Service {0} creation failed.: {1}".format(
module.params["name"], err
)
)
module.exit_json(changed=changed)
def delete_ss(module, fusion):
"""Delete Storage Service"""
ss_api_instance = purefusion.StorageServicesApi(fusion)
changed = True
if not module.check_mode:
try:
ss_api_instance.delete_storage_service(
storage_service_name=module.params["name"]
)
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Storage Service {0} deletion failed.: {1}".format(
module.params["name"], err
)
)
module.exit_json(changed=changed)
def update_ss(module, fusion):
"""Update Storage Service"""
changed = False
ss_api_instance = purefusion.StorageServicesApi(fusion)
s_service = ss_api_instance.get_storage_service(
storage_service_name=module.params["name"],
)
hw_types = []
for hw_type in range(0, len(s_service.hardware_types)):
hw_types.append(s_service.hardware_types[hw_type].name)
if (
module.params["display_name"]
and module.params["display_name"] != s_service.display_name
):
changed = True
display_name = module.params["display_name"]
else:
display_name = s_service.display_name
if changed and not module.check_mode:
sservice = purefusion.StorageServicePatch(
display_name=purefusion.NullableString(display_name),
)
try:
ss_api_instance.update_storage_service(
sservice,
storage_service_name=module.params["name"],
)
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Changing storage service {0} failed. Error: {1}".format(
module.params["name"], err
)
)
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"),
hardware_types=dict(
type="list",
required=True,
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 = get_fusion(module)
state = module.params["state"]
s_service = get_ss(module, fusion)
if not s_service and state == "present":
create_ss(module, fusion)
elif s_service and state == "present":
update_ss(module, fusion)
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()

View File

@@ -0,0 +1,185 @@
#!/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"
app_id: key_name
key_file: "az-admin-private-key.pem"
- name: Delete tenat foo
purestorage.fusion.fusion_tenant:
name: foo
state: absent
app_id: key_name
key_file: "az-admin-private-key.pem"
"""
RETURN = r"""
"""
HAS_FUSION = True
try:
import fusion as purefusion
except ImportError:
HAS_FUSION = False
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import (
get_fusion,
fusion_argument_spec,
)
def get_tenant(module, fusion):
"""Return Tenant or None"""
api_instance = purefusion.TenantsApi(fusion)
try:
return api_instance.get_tenant(tenant_name=module.params["name"])
except purefusion.rest.ApiException:
return None
def create_tenant(module, fusion):
"""Create Tenant"""
api_instance = purefusion.TenantsApi(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"]
try:
tenant = purefusion.TenantPost(
name=module.params["name"],
display_name=display_name,
)
api_instance.create_tenant(tenant)
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Tenant {0} creation failed.: {1}".format(
module.params["name"], err
)
)
module.exit_json(changed=changed)
def update_tenant(module, fusion):
"""Update Tenant settings"""
changed = False
api_instance = purefusion.TenantsApi(fusion)
tenant = api_instance.get_tenant(
tenant_name=module.params["name"],
)
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"]),
)
try:
api_instance.update_tenant(
new_tenant,
tenant_name=module.params["name"],
)
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Changing tenant display_name failed: {0}".format(err)
)
module.exit_json(changed=changed)
def delete_tenant(module, fusion):
"""Delete Tenant"""
changed = True
api_instance = purefusion.TenantsApi(fusion)
if not module.check_mode:
try:
api_instance.delete_tenant(tenant_name=module.params["name"])
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Deleting Tenant {0} failed: {1}".format(module.params["name"], err)
)
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 = get_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)
elif tenant and state == "absent":
delete_tenant(module, fusion)
else:
module.exit_json(changed=False)
module.exit_json(changed=False)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,307 @@
#!/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'
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
required: true
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
required: true
availability_zone:
aliases: [ az ]
description:
- The name of the availability zone for the tenant network.
type: str
required: true
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
"""
EXAMPLES = r"""
- name: Create new tenant network foo in AZ bar
purestorage.fusion.fusion_tn:
name: foo
availability_zone: bar
mtu: 9000
gateway: 10.21.200.1
addresses:
- 10.21.200.124/24
- 10.21.200.36/24
provider_subnets:
- subnet-0
state: present
app_id: key_name
key_file: "az-admin-private-key.pem"
- name: Delete tenant network foo in AZ bar
purestorage.fusion.fusion_tn:
name: foo
availability_zone: bar
state: absent
app_id: key_name
key_file: "az-admin-private-key.pem"
"""
RETURN = r"""
"""
HAS_FUSION = True
try:
import fusion as purefusion
except ImportError:
HAS_FUSION = False
try:
from netaddr import IPNetwork
HAS_NETADDR = True
except ImportError:
HAS_NETADDR = False
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import (
get_fusion,
fusion_argument_spec,
)
def get_ps(module, fusion):
"""Check all Provider Subnets"""
ps_api_instance = purefusion.ProviderSubnetsApi(fusion)
for subnet in range(0, len(module.params["provider_subnets"])):
try:
ps_api_instance.get_provider_subnet(
availability_zone_name=module.params["availability_zone"],
provider_subnet=module.params["provider_subnets"][subnet],
)
except purefusion.rest.ApiException:
return False
return True
def get_az(module, fusion):
"""Availability Zone or None"""
az_api_instance = purefusion.AvailabilityZonesApi(fusion)
try:
return az_api_instance.get_availability_zone(
availability_zone_name=module.params["availability_zone"],
region_name=module.params["region"],
)
except purefusion.rest.ApiException:
return None
def get_tn(module, fusion):
"""Tenant Network or None"""
api_instance = purefusion.TenantNetworksApi(fusion)
try:
return api_instance.get_tenant_network(
tenant_network=module.params["name"],
availability_zone_name=module.params["availability_zone"],
)
except purefusion.rest.ApiException:
return None
def create_tn(module, fusion):
"""Create Tenant Network"""
tn_api_instance = purefusion.TenantNetworksApi(fusion)
changed = True
if module.params["gateway"] and module.params["gateway"] not in IPNetwork(
module.params["prefix"]
):
module.fail_json(msg="Gateway and subnet prefix are not compatible.")
if not module.check_mode:
if not module.params["display_name"]:
display_name = module.params["name"]
else:
display_name = module.params["display_name"]
try:
if module.params["gateway"]:
tsubnet = purefusion.TenantSubnetPost(
prefix=module.params["prefix"],
addresses=module.params["addresses"],
gateway=module.params["gateway"],
mtu=module.params["mtu"],
provider_subnets=module.params["provider_subnets"],
)
else:
tsubnet = purefusion.TenantSubnetPost(
prefix=module.params["prefix"],
addresses=module.params["addresses"],
mtu=module.params["mtu"],
provider_subnets=module.params["provider_subnets"],
)
tnet = purefusion.TenantNetworkPost(
tenant_subnets=[tsubnet],
name=module.params["name"],
display_name=display_name,
)
tn_api_instance.create_tenant_network(
tnet,
availability_zone_name=module.params["availability_zone"],
)
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Tenant Network {0} creation failed.: {1}".format(
module.params["name"], err
)
)
module.exit_json(changed=changed)
def delete_tn(module, fusion):
"""Delete Tenant Network"""
changed = True
tn_api_instance = purefusion.TenantNetworksApi(fusion)
if not module.check_mode:
try:
tn_api_instance.delete_tenant_network(
availability_zone_name=module.params["availability_zone"],
tenant_network=module.params["name"],
)
except purefusion.rest.ApiException:
module.fail_json(
msg="Delete Tenant Network {0} failed.".format(module.params["name"])
)
module.exit_json(changed=changed)
def update_tn(module, fusion, tenant_network):
"""Update Tenant Network"""
changed = False
module.exit_json(changed=changed)
def main():
"""Main code"""
argument_spec = fusion_argument_spec()
argument_spec.update(
dict(
name=dict(type="str", required=True),
region=dict(type="str", required=True),
display_name=dict(type="str"),
availability_zone=dict(type="str", required=True, 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)
if not HAS_NETADDR:
module.fail_json(msg="netaddr module is required")
state = module.params["state"]
fusion = get_fusion(module)
if not get_az(module, fusion):
module.fail_json(msg="Availability Zone {0} does not exist")
if module.params["provider_subnets"] and not get_ps(module, fusion):
module.fail_json(
msg="Not all of the provider subnets exist in the specified AZ"
)
for address in range(0, len(module.params["addresses"])):
if "/" not in module.params["addresses"][address]:
module.fail_json(msg="All addresses must include a CIDR notation")
if 8 > int(module.params["addresses"][address].split("/")[1]) > 32:
module.fail_json(
msg="An invalid CIDR notation has been provided: {0}".format(
module.params["addresses"][address]
)
)
tnet = get_tn(module, fusion)
if state == "present" and not tnet:
if not (
module.params["addresses"]
and module.params["gateway"] # Soon to be optional
and module.params["prefix"] # To be removed soon
and module.params["provider_subnets"]
):
module.fail_json(
msg="When creating a new tenant network, the following "
"parameters must be supplied: `gateway`, `addresses`, `prefix` "
"and `provider_subnets`"
)
create_tn(module, fusion)
elif state == "present" and tnet:
update_tn(module, fusion, tnet)
elif state == "absent" and tnet:
delete_tn(module, fusion)
module.exit_json(changed=False)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,178 @@
#!/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 teanat space foo for tenany bar
purestorage.fusion.fusion_ts:
name: foo
tenant: bar
state: present
app_id: key_name
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
app_id: key_name
key_file: "az-admin-private-key.pem"
"""
RETURN = r"""
"""
HAS_FUSION = True
try:
import fusion as purefusion
except ImportError:
HAS_FUSION = False
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import (
get_fusion,
fusion_argument_spec,
)
def get_ts(module, fusion):
"""Tenant Space or None"""
ts_api_instance = purefusion.TenantSpacesApi(fusion)
try:
return ts_api_instance.get_tenant_space(
tenant_name=module.params["tenant"],
tenant_space_name=module.params["name"],
)
except purefusion.rest.ApiException:
return None
def get_tenant(module, fusion):
"""Return Tenant or None"""
api_instance = purefusion.TenantsApi(fusion)
try:
return api_instance.get_tenant(tenant_name=module.params["tenant"])
except purefusion.rest.ApiException:
return None
def create_ts(module, fusion):
"""Create Tenant Space"""
ts_api_instance = purefusion.TenantSpacesApi(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"]
try:
tspace = purefusion.TenantSpacePost(
name=module.params["name"],
display_name=display_name,
)
ts_api_instance.create_tenant_space(
tspace,
tenant_name=module.params["tenant"],
)
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Tenant Space {0} creation failed.: {1}".format(
module.params["name"], err
)
)
module.exit_json(changed=changed)
def delete_ts(module, fusion):
"""Delete Tenant Space"""
changed = True
ts_api_instance = purefusion.TenantSpacesApi(fusion)
if not module.check_mode:
try:
ts_api_instance.delete_tenant_space(
tenant_name=module.params["tenant"],
tenant_space_name=module.params["name"],
)
except purefusion.rest.ApiException:
module.fail_json(
msg="Delete Tenant Space {0} failed.".format(module.params["name"])
)
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)
state = module.params["state"]
fusion = get_fusion(module)
if not get_tenant(module, fusion):
module.fail_json(msg="Tenant {0} does not exist")
tspace = get_ts(module, fusion)
if state == "present" and not tspace:
create_ts(module, fusion)
elif state == "absent" and tspace:
delete_ts(module, fusion)
module.exit_json(changed=False)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,648 @@
#!/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_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:
- Define whether to eradicate the volume on delete or leave in trash.
type: bool
default: 'no'
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 plcement group.
type: str
protection_policy:
description:
- The name of the protection policy.
type: str
hosts:
description:
- A list of host access policies to connect the volume to.
type: list
elements: 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
size: 1T
tenant: test
tenant_space: space_1
state: present
app_id: key_name
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
app_id: key_name
key_file: "az-admin-private-key.pem"
- name: Rename volume named foo to bar
purestorage.fusion.fusion_volume:
name: foo
rename: bar
tenant: test
tenant_space: space_1
state: absent
app_id: key_name
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
app_id: key_name
key_file: "az-admin-private-key.pem"
"""
RETURN = r"""
"""
HAS_FUSION = True
try:
import fusion as purefusion
except ImportError:
HAS_FUSION = False
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import (
get_fusion,
fusion_argument_spec,
)
def _check_hosts(module, fusion):
current_haps = []
hap_api_instance = purefusion.HostAccessPoliciesApi(fusion)
hosts = hap_api_instance.list_host_access_policies()
for host in range(0, len(hosts.items)):
current_haps.append(hosts.items[host].name)
if not (set(module.params["hosts"]).issubset(set(current_haps))):
module.fail_json(
msg="At least of of the speciied hosts does not currently exist"
)
def _check_target_volume(module, fusion):
vol_api_instance = purefusion.VolumesApi(fusion)
try:
vol_api_instance.get_volume(
tenant_name=module.params["tenant"],
tenant_space_name=module.params["tenant_space"],
volume_name=module.params["rename"],
)
return True
except purefusion.rest.ApiException:
return False
def human_to_bytes(size):
"""Given a human-readable byte string (e.g. 2G, 30M),
return the number of bytes. Will return 0 if the argument has
unexpected form.
"""
my_bytes = size[:-1]
unit = size[-1].upper()
if my_bytes.isdigit():
my_bytes = int(my_bytes)
if unit == "P":
my_bytes *= 1125899906842624
elif unit == "T":
my_bytes *= 1099511627776
elif unit == "G":
my_bytes *= 1073741824
elif unit == "M":
my_bytes *= 1048576
elif unit == "K":
my_bytes *= 1024
else:
my_bytes = 0
else:
my_bytes = 0
return my_bytes
def bytes_to_human(bytes_number):
"""Convert bytes to a human readable string"""
if bytes_number:
labels = ["B", "KB", "MB", "GB", "TB", "PB"]
i = 0
double_bytes = bytes_number
while i < len(labels) and bytes_number >= 1024:
double_bytes = bytes_number / 1024.0
i += 1
bytes_number = bytes_number / 1024
return str(round(double_bytes, 2)) + " " + labels[i]
return None
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_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["storage_class"]
)
except purefusion.rest.ApiException:
return None
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["placement_group"],
)
except purefusion.rest.ApiException:
return None
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["protection_policy"]
)
except purefusion.rest.ApiException:
return None
def get_destroyed_volume(module, fusion):
"""Return Destroyed Volume or None"""
vs_api_instance = purefusion.VolumeSnapshotsApi(fusion)
try:
return vs_api_instance.get_volume_snapshot(
volume_name=module.params["name"],
tenant_name=module.params["tenant"],
tenant_space_name=module.params["tenant_space"],
)
except purefusion.rest.ApiException:
return False
def create_volume(module, fusion):
"""Create Volume"""
sc_api_instance = purefusion.StorageClassesApi(fusion)
vol_api_instance = purefusion.VolumesApi(fusion)
if not module.params["size"]:
module.fail_json(msg="Size for a new volume must be specified")
size = human_to_bytes(module.params["size"])
sc_size_limit = sc_api_instance.get_storage_class(
storage_class_name=module.params["storage_class"]
).size_limit
if size > sc_size_limit:
module.fail_json(
msg="Requested size {0} exceeds the storage class limit of {1}".format(
module.params["size"], bytes_to_human(sc_size_limit)
)
)
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"]
try:
volume = purefusion.VolumePost(
size=size,
storage_class=module.params["storage_class"],
placement_group=module.params["placement_group"],
name=module.params["name"],
display_name=display_name,
)
vol_api_instance.create_volume(
volume,
tenant_name=module.params["tenant"],
tenant_space_name=module.params["tenant_space"],
)
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Volume {0} creation failed.: {1}".format(
module.params["name"], err
)
)
if module.params["hosts"]:
volume = purefusion.VolumePatch(
hosts=purefusion.NullableString(module.params["hosts"])
)
module.exit_json(changed=changed)
def update_volume(module, fusion):
"""Update Volume size, placement group, storage class, HAPs"""
changed = False
sc_api_instance = purefusion.StorageClassesApi(fusion)
vol_api_instance = purefusion.VolumesApi(fusion)
vol = vol_api_instance.get_volume(
tenant_name=module.params["tenant"],
tenant_space_name=module.params["tenant_space"],
volume_name=module.params["name"],
)
hosts = []
if vol.hosts:
for host in range(0, len(vol.hosts)):
hosts.append(vol.hosts[host].name)
current_vol = {
"size": vol.size,
"hosts": list(dict.fromkeys(hosts)),
"placement_group": vol.placement_group.name,
"protection_policy": getattr(vol.protection_policy, "name", None),
"storage_class": vol.storage_class.name,
"display_name": vol.display_name,
}
new_vol = {
"size": vol.size,
"hosts": list(dict.fromkeys(hosts)),
"placement_group": vol.placement_group.name,
"protection_policy": getattr(vol.protection_policy, "name", None),
"storage_class": vol.storage_class.name,
"display_name": vol.display_name,
}
if (
module.params["storage_class"]
and module.params["storage_class"] != current_vol["storage_class"]
):
new_vol["storage_class"] = module.params["storage_class"]
if (
module.params["size"]
and human_to_bytes(module.params["size"]) != current_vol["size"]
):
if human_to_bytes(module.params["size"]) > current_vol["size"]:
new_vol["size"] = human_to_bytes(module.params["size"])
sc_size_limit = sc_api_instance.get_storage_class(
storage_class_name=new_vol["storage_class"]
).size_limit
if new_vol["size"] > sc_size_limit:
module.fail_json(
msg="Volume size {0} exceeds the storage class limit of {1}".format(
new_vol["size"], sc_size_limit
)
)
if not module.params["size"] and module.params["storage_class"]:
sc_size_limit = sc_api_instance.get_storage_class(
storage_class_name=new_vol["storage_class"]
).size_limit
if current_vol["size"] > sc_size_limit:
module.fail_json(
msg="Volume size {0} exceeds the storage class limit of {1}".format(
new_vol["size"], sc_size_limit
)
)
if (
module.params["placement_group"]
and module.params["placement_group"] != current_vol["placement_group"]
):
new_vol["protection_group"] = module.params["placement_group"]
if (
module.params["protection_policy"]
and module.params["protection_policy"] != current_vol["protection_policy"]
):
new_vol["protection_policy"] = module.params["protection_policy"]
if (
module.params["display_name"]
and module.params["display_name"] != current_vol["display_name"]
):
new_vol["display_name"] = module.params["display_name"]
if (new_vol != current_vol) or module.params["hosts"]:
changed = False
if not module.check_mode:
# PATCH is atomic so has to pass or fail, therefore only one item
# can be changed at a time
if new_vol["display_name"] != current_vol["display_name"]:
volume = purefusion.VolumePatch(
display_name=purefusion.NullableString(new_vol["display_name"])
)
try:
res = vol_api_instance.update_volume(
volume,
volume_name=module.params["name"],
tenant_name=module.params["tenant"],
tenant_space_name=module.params["tenant_space"],
)
changed = True
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Changing display_name failed: {0}".format(err)
)
if new_vol["storage_class"] != current_vol["storage_class"]:
volume = purefusion.VolumePatch(
storage_class=purefusion.NullableString(new_vol["storage_class"])
)
try:
res = vol_api_instance.update_volume(
volume,
volume_name=module.params["name"],
tenant_name=module.params["tenant"],
tenant_space_name=module.params["tenant_space"],
)
changed = True
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Changing storage_class failed: {0}".format(err)
)
if new_vol["size"] != current_vol["size"]:
volume = purefusion.VolumePatch(
size=purefusion.NullableSize(new_vol["size"])
)
try:
res = vol_api_instance.update_volume(
volume,
volume_name=module.params["name"],
tenant_name=module.params["tenant"],
tenant_space_name=module.params["tenant_space"],
)
changed = True
except purefusion.rest.ApiException as err:
module.fail_json(msg="Changing size failed: {0}".format(err))
if new_vol["placement_group"] != current_vol["placement_group"]:
volume = purefusion.VolumePatch(
placement_group=purefusion.NullableString(
new_vol["placement_group"]
)
)
try:
res = vol_api_instance.update_volume(
volume,
volume_name=module.params["name"],
tenant_name=module.params["tenant"],
tenant_space_name=module.params["tenant_space"],
)
changed = True
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Changing placement_group failed: {0}".format(err)
)
if new_vol["protection_policy"] != current_vol["protection_policy"]:
volume = purefusion.VolumePatch(
protection_policy=purefusion.NullableString(
new_vol["protection_policy"]
)
)
try:
vol_api_instance.update_volume(
volume,
volume_name=module.params["name"],
tenant_name=module.params["tenant"],
tenant_space_name=module.params["tenant_space"],
)
changed = True
except purefusion.rest.ApiException as err:
module.fail_json(
msg="Changing protection_policy failed: {0}".format(err)
)
if module.params["hosts"]:
if not new_vol["hosts"]:
new_vol["hosts"] = []
for host in module.params["hosts"]:
if module.params["state"] == "absent":
if new_vol["hosts"]:
new_vol["hosts"].remove(host)
else:
new_vol["hosts"].append(host)
new_vol["hosts"] = list(dict.fromkeys(new_vol["hosts"]))
if new_vol["hosts"] != current_vol["hosts"]:
volume = purefusion.VolumePatch(
hosts=purefusion.NullableString(",".join(new_vol["hosts"]))
)
try:
vol_api_instance.update_volume(
volume,
volume_name=module.params["name"],
tenant_name=module.params["tenant"],
tenant_space_name=module.params["tenant_space"],
)
changed = True
except purefusion.rest.ApiException as err:
module.fail_json(msg="Changing hosts failed: {0}".format(err))
module.exit_json(changed=changed)
def delete_volume(module, fusion):
"""Delete Volume"""
changed = True
vol_api_instance = purefusion.VolumesApi(fusion)
if not module.check_mode:
try:
vol_api_instance.delete_volume(
volume_name=module.params["name"],
tenant_name=module.params["tenant"],
tenant_space_name=module.params["tenant_space"],
)
if module.params["eradicate"]:
try:
pass
# eradicate_volume(module, array)
except Exception:
module.fail_json(
msg="Eradicate volume {0} failed.".format(module.params["name"])
)
except purefusion.rest.ApiException:
module.fail_json(
msg="Delete volume {0} failed.".format(module.params["name"])
)
module.exit_json(changed=changed)
def eradicate_volume(module, array):
"""Eradicate Deleted Volume"""
changed = True
if not module.check_mode:
try:
array.eradicate_volume(module.params["name"])
except Exception:
module.fail_json(
msg="Eradication of volume {0} failed".format(module.params["name"])
)
module.exit_json(changed=changed)
def recover_volume(module, array):
"""Recover Deleted Volume"""
changed = True
module.warn("Volume recovery not yet supported")
# if not module.check_mode:
# try:
# array.recover_volume(module.params["name"])
# except Exception:
# module.fail_json(
# msg="Recovery of volume {0} failed".format(module.params["name"])
# )
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"),
rename=dict(type="str"),
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"),
hosts=dict(type="list", elements="str"),
eradicate=dict(type="bool", default=False),
state=dict(type="str", default="present", choices=["absent", "present"]),
size=dict(type="str"),
)
)
module = AnsibleModule(argument_spec, supports_check_mode=True)
size = module.params["size"]
state = module.params["state"]
destroyed = False
fusion = get_fusion(module)
volume = get_volume(module, fusion)
if module.params["rename"] and _check_target_volume(module, fusion):
module.fail_json(
msg="Taerget volume name {0} already exists".format(module.params["rename"])
)
if not volume and not (
module.params["storage_class"] and module.params["placement_group"]
):
module.fail_json(
msg="`storage_class` and `placement_group` are required when creating a new volume"
)
if module.params["hosts"]:
_check_hosts(module, fusion)
if module.params["storage_class"] and not get_sc(module, fusion):
module.fail_json(
msg="Storage Class {0} does not exist".format(
module.params["storage_class"]
)
)
if module.params["placement_group"] and not get_pg(module, fusion):
module.fail_json(
msg="Placement Group {0} does not exist in the provide "
"tenant and tenant name space".format(module.params["placement_group"])
)
if module.params["protection_policy"] and not get_pp(module, fusion):
module.fail_json(
msg="Protection Policy {0} does not exist".format(
module.params["protection_policy"]
)
)
# if not volume:
# destroyed = get_destroyed_volume(module, fusion)
if state == "present" and not volume and not destroyed and size:
create_volume(module, fusion)
elif (state == "present" and volume) or (
state == "absent" and volume and module.params["hosts"]
):
update_volume(module, fusion)
elif state == "absent" and volume and not module.params["hosts"]:
delete_volume(module, fusion)
elif state == "absent" and destroyed:
eradicate_volume(module, fusion)
elif state == "present":
if not volume and not size:
module.fail_json(msg="Size must be specified to create a new volume")
elif state == "absent" and not volume:
module.exit_json(changed=False)
module.exit_json(changed=False)
if __name__ == "__main__":
main()