Migrated to purestorage.flasharray

pull/68298/head
Ansible Core Team 5 years ago committed by Matt Martz
parent 7449ec1546
commit d9920706d7

@ -1,172 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2018, Simon Dodsley (simon@purestorage.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: purefa_alert
version_added: '2.9'
short_description: Configure Pure Storage FlashArray alert email settings
description:
- Configure alert email configuration for Pure Storage FlashArrays.
- Add or delete an individual syslog server to the existing
list of serves.
author:
- Simon Dodsley (@sdodsley)
options:
state:
type: str
description:
- Create or delete alert email
default: present
choices: [ absent, present ]
address:
type: str
description:
- Email address (valid format required)
required: true
enabled:
type: bool
default: true
description:
- Set specified email address to be enabled or disabled
extends_documentation_fragment:
- purestorage.fa
'''
EXAMPLES = r'''
- name: Add new email recipient and enable, or enable existing email
purefa_alert:
address: "user@domain.com"
enabled: true
state: present
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Delete existing email recipient
purefa_alert:
state: absent
address: "user@domain.com"
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
'''
RETURN = r'''
'''
import re
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.pure import get_system, purefa_argument_spec
def create_alert(module, array):
"""Create Alert Email"""
changed = False
if not module.check_mode:
try:
array.create_alert_recipient(module.params['address'])
changed = True
except Exception:
module.fail_json(msg='Failed to create alert email: {0}'.format(module.params['address']))
if not module.params['enabled']:
try:
array.disable_alert_recipient(module.params['address'])
except Exception:
module.fail_json(msg='Failed to create alert email: {0}'.format(module.params['address']))
changed = True
module.exit_json(changed=changed)
def enable_alert(module, array):
"""Enable Alert Email"""
changed = False
if not module.check_mode:
try:
array.enable_alert_recipient(module.params['address'])
except Exception:
module.fail_json(msg='Failed to enable alert email: {0}'.format(module.params['address']))
changed = True
module.exit_json(changed=changed)
def disable_alert(module, array):
"""Disable Alert Email"""
changed = False
if not module.check_mode:
try:
array.disable_alert_recipient(module.params['address'])
except Exception:
module.fail_json(msg='Failed to disable alert email: {0}'.format(module.params['address']))
changed = True
module.exit_json(changed=changed)
def delete_alert(module, array):
"""Delete Alert Email"""
changed = False
if module.params['address'] == "flasharray-alerts@purestorage.com":
module.fail_json(msg='Built-in address {0} cannot be deleted.'.format(module.params['address']))
if not module.check_mode:
try:
array.delete_alert_recipient(module.params['address'])
except Exception:
module.fail_json(msg='Failed to delete alert email: {0}'.format(module.params['address']))
changed = True
module.exit_json(changed=changed)
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(dict(
address=dict(type='str', required=True),
enabled=dict(type='bool', default=True),
state=dict(type='str', default='present', choices=['absent', 'present']),
))
module = AnsibleModule(argument_spec,
supports_check_mode=True)
pattern = re.compile(r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
if not pattern.match(module.params['address']):
module.fail_json(msg='Valid email address not provided.')
array = get_system(module)
exists = False
try:
emails = array.list_alert_recipients()
except Exception:
module.fail_json(msg='Failed to get existing email list')
for email in range(0, len(emails)):
if emails[email]['name'] == module.params['address']:
exists = True
enabled = emails[email]['enabled']
break
if module.params['state'] == 'present' and not exists:
create_alert(module, array)
elif module.params['state'] == 'present' and exists and not enabled and module.params['enabled']:
enable_alert(module, array)
elif module.params['state'] == 'present' and exists and enabled and not module.params['enabled']:
disable_alert(module, array)
elif module.params['state'] == 'absent' and exists:
delete_alert(module, array)
module.exit_json(changed=False)
if __name__ == '__main__':
main()

@ -1,90 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2018, Simon Dodsley (simon@purestorage.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: purefa_arrayname
version_added: '2.9'
short_description: Configure Pure Storage FlashArray array name
description:
- Configure name of array for Pure Storage FlashArrays.
- Ideal for Day 0 initial configuration.
author:
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
options:
state:
description: Set the array name
type: str
default: present
choices: [ present ]
name:
description:
- Name of the array. Must conform to correct naming schema.
type: str
required: true
extends_documentation_fragment:
- purestorage.fa
'''
EXAMPLES = r'''
- name: Set new array name
purefa_arrayname:
name: new-array-name
state: present
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
'''
RETURN = r'''
'''
import re
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.pure import get_system, purefa_argument_spec
def update_name(module, array):
"""Change array name"""
changed = False
try:
array.set(name=module.params['name'])
changed = True
except Exception:
module.fail_json(msg='Failed to change array name to {0}'.format(module.params['name']))
module.exit_json(changed=changed)
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(dict(
name=dict(type='str', required=True),
state=dict(type='str', default='present', choices=['present']),
))
module = AnsibleModule(argument_spec,
supports_check_mode=False)
array = get_system(module)
pattern = re.compile("^[a-zA-Z0-9]([a-zA-Z0-9-]{0,54}[a-zA-Z0-9])?$")
if not pattern.match(module.params['name']):
module.fail_json(msg='Array name {0} does not conform to array name rules. See documentation.'.format(module.params['name']))
if module.params['name'] != array.get()['array_name']:
update_name(module, array)
module.exit_json(changed=False)
if __name__ == '__main__':
main()

@ -1,116 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2018, Simon Dodsley (simon@purestorage.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: purefa_banner
version_added: '2.9'
short_description: Configure Pure Storage FlashArray GUI and SSH MOTD message
description:
- Configure MOTD for Pure Storage FlashArrays.
- This will be shown during an SSH or GUI login to the array.
- Multiple line messages can be achieved using \\n.
author:
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
options:
state:
description: Set ot delete the MOTD
default: present
type: str
choices: [ present, absent ]
banner:
description: Banner text, or MOTD, to use
type: str
default: "Welcome to the machine..."
extends_documentation_fragment:
- purestorage.fa
'''
EXAMPLES = r'''
- name: Set new banner text
purefa_banner:
banner: "Banner over\ntwo lines"
state: present
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Delete banner text
purefa_banner:
state: absent
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
'''
RETURN = r'''
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.pure import get_system, purefa_argument_spec
def set_banner(module, array):
"""Set MOTD banner text"""
changed = False
try:
if not module.params['banner']:
module.fail_json(msg='Invalid MOTD banner given')
if not module.check_mode:
array.set(banner=module.params['banner'])
changed = True
except Exception:
module.fail_json(msg='Failed to set MOTD banner text')
module.exit_json(changed=changed)
def delete_banner(module, array):
"""Delete MOTD banner text"""
changed = False
try:
array.set(banner="")
changed = True
except Exception:
module.fail_json(msg='Failed to delete current MOTD banner text')
module.exit_json(changed=changed)
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(dict(
banner=dict(type='str', default="Welcome to the machine..."),
state=dict(type='str', default='present', choices=['present', 'absent']),
))
required_if = [('state', 'present', ['banner'])]
module = AnsibleModule(argument_spec,
required_if=required_if,
supports_check_mode=False)
state = module.params['state']
array = get_system(module)
current_banner = array.get(banner=True)['banner']
# set banner if empty value or value differs
if state == 'present' and (not current_banner or current_banner != module.params['banner']):
set_banner(module, array)
# clear banner if it has a value
elif state == 'absent' and current_banner:
delete_banner(module, array)
module.exit_json(changed=False)
if __name__ == '__main__':
main()

@ -1,155 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2018, Simon Dodsley (simon@purestorage.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: purefa_connect
version_added: '2.9'
short_description: Manage replication connections between two FlashArrays
description:
- Manage array connections to specified target array
author:
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
options:
state:
description:
- Create or delete array connection
default: present
type: str
choices: [ absent, present ]
target_url:
description:
- Management IP address of remote array.
type: str
required: true
target_api:
description:
- API token for target array
type: str
connection:
description: Type of connection between arrays.
type: str
choices: [ sync, async ]
default: async
extends_documentation_fragment:
- purestorage.fa
'''
EXAMPLES = r'''
- name: Create an async connection to remote array
purefa_connect:
target_url: 10.10.10.20
target_api:
connection: async
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Delete connection to remote array
purefa_connect:
state: absent
target_url: 10.10.10.20
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
'''
RETURN = r'''
'''
HAS_PURESTORAGE = True
try:
from purestorage import FlashArray
except ImportError:
HAS_PURESTORAGE = False
import platform
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.pure import get_system, purefa_argument_spec
def _check_connected(module, array):
connected_arrays = array.list_array_connections()
for target in range(0, len(connected_arrays)):
if connected_arrays[target]['management_address'] == module.params['target_url'] and \
connected_arrays[target]['connected']:
return connected_arrays[target]
return None
def break_connection(module, array, target_array):
"""Break connection between arrays"""
changed = True
if not module.check_mode:
source_array = array.get()['array_name']
try:
array.disconnect_array(target_array['array_name'])
except Exception:
module.fail_json(msg="Failed to disconnect {0} from {1}.".format(target_array['array_name'], source_array))
module.exit_json(changed=changed)
def create_connection(module, array):
"""Create connection between arrays"""
changed = True
if not module.check_mode:
remote_array = module.params['target_url']
user_agent = '%(base)s %(class)s/%(version)s (%(platform)s)' % {
'base': 'Ansible',
'class': __name__,
'version': 1.2,
'platform': platform.platform()
}
try:
remote_system = FlashArray(module.params['target_url'],
api_token=module.params['target_api'],
user_agent=user_agent)
connection_key = remote_system.get(connection_key=True)['connection_key']
remote_array = remote_system.get()['array_name']
array.connect_array(module.params['target_url'], connection_key, [module.params['connection']])
except Exception:
module.fail_json(msg="Failed to connect to remote array {0}.".format(remote_array))
module.exit_json(changed=changed)
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(dict(
state=dict(type='str', default='present', choices=['absent', 'present']),
connection=dict(type='str', default='async', choices=['async', 'sync']),
target_url=dict(type='str', required=True),
target_api=dict(type='str'),
))
required_if = [('state', 'present', ['target_api'])]
module = AnsibleModule(argument_spec,
required_if=required_if,
supports_check_mode=True)
if not HAS_PURESTORAGE:
module.fail_json(msg='purestorage sdk is required for this module')
state = module.params['state']
array = get_system(module)
target_array = _check_connected(module, array)
if state == 'present' and target_array is None:
create_connection(module, array)
elif state == 'absent'and target_array is not None:
break_connection(module, array, target_array)
module.exit_json(changed=False)
if __name__ == '__main__':
main()

@ -1,133 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2018, Simon Dodsley (simon@purestorage.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: purefa_dns
version_added: '2.8'
short_description: Configure FlashArray DNS settings
description:
- Set or erase configuration for the DNS settings.
- Nameservers provided will overwrite any existing nameservers.
author:
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
options:
state:
description:
- Set or delete directory service configuration
default: present
type: str
choices: [ absent, present ]
domain:
description:
- Domain suffix to be appended when performing DNS lookups.
type: str
nameservers:
description:
- List of up to 3 unique DNS server IP addresses. These can be
IPv4 or IPv6 - No validation is done of the addresses is performed.
type: list
extends_documentation_fragment:
- purestorage.fa
'''
EXAMPLES = r'''
- name: Delete existing DNS settings
purefa_dns:
state: absent
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Set DNS settings
purefa_dns:
domain: purestorage.com
nameservers:
- 8.8.8.8
- 8.8.4.4
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
'''
RETURN = r'''
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.pure import get_system, purefa_argument_spec
def remove(duplicate):
final_list = []
for num in duplicate:
if num not in final_list:
final_list.append(num)
return final_list
def delete_dns(module, array):
"""Delete DNS settings"""
changed = False
current_dns = array.get_dns()
if current_dns['domain'] == '' and current_dns['nameservers'] == ['']:
module.exit_json(changed=changed)
else:
try:
array.set_dns(domain='', nameservers=[])
changed = True
except Exception:
module.fail_json(msg='Delete DNS settings failed')
module.exit_json(changed=changed)
def create_dns(module, array):
"""Set DNS settings"""
changed = False
current_dns = array.get_dns()
if current_dns['domain'] != module.params['domain'] or sorted(module.params['nameservers']) != sorted(current_dns['nameservers']):
try:
array.set_dns(domain=module.params['domain'],
nameservers=module.params['nameservers'][0:3])
changed = True
except Exception:
module.fail_json(msg='Set DNS settings failed: Check configuration')
module.exit_json(changed=changed)
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(dict(
state=dict(type='str', default='present', choices=['absent', 'present']),
domain=dict(type='str'),
nameservers=dict(type='list'),
))
required_if = [('state', 'present', ['domain', 'nameservers'])]
module = AnsibleModule(argument_spec,
required_if=required_if,
supports_check_mode=False)
state = module.params['state']
array = get_system(module)
if state == 'absent':
delete_dns(module, array)
elif state == 'present':
module.params['nameservers'] = remove(module.params['nameservers'])
create_dns(module, array)
else:
module.exit_json(changed=False)
if __name__ == '__main__':
main()

@ -1,326 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2018, Simon Dodsley (simon@purestorage.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: purefa_ds
version_added: '2.6'
short_description: Configure FlashArray Directory Service
description:
- Set or erase configuration for the directory service. There is no facility
to SSL certificates at this time. Use the FlashArray GUI for this
additional configuration work.
- To modify an existing directory service configuration you must first delete
an existing configuration and then recreate with new settings.
author:
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
options:
state:
type: str
description:
- Create or delete directory service configuration
default: present
choices: [ absent, present ]
enable:
description:
- Whether to enable or disable directory service support.
default: false
type: bool
uri:
type: list
description:
- A list of up to 30 URIs of the directory servers. Each URI must include
the scheme ldap:// or ldaps:// (for LDAP over SSL), a hostname, and a
domain name or IP address. For example, ldap://ad.company.com configures
the directory service with the hostname "ad" in the domain "company.com"
while specifying the unencrypted LDAP protocol.
base_dn:
type: str
description:
- Sets the base of the Distinguished Name (DN) of the directory service
groups. The base should consist of only Domain Components (DCs). The
base_dn will populate with a default value when a URI is entered by
parsing domain components from the URI. The base DN should specify DC=
for each domain component and multiple DCs should be separated by commas.
required: true
bind_password:
type: str
description:
- Sets the password of the bind_user user name account.
bind_user:
type: str
description:
- Sets the user name that can be used to bind to and query the directory.
- For Active Directory, enter the username - often referred to as
sAMAccountName or User Logon Name - of the account that is used to
perform directory lookups.
- For OpenLDAP, enter the full DN of the user.
group_base:
type: str
description:
- Specifies where the configured groups are located in the directory
tree. This field consists of Organizational Units (OUs) that combine
with the base DN attribute and the configured group CNs to complete
the full Distinguished Name of the groups. The group base should
specify OU= for each OU and multiple OUs should be separated by commas.
The order of OUs is important and should get larger in scope from left
to right. Each OU should not exceed 64 characters in length.
- Not Supported from Purity 5.2.0 or higher. Use I(purefa_dsrole) module.
ro_group:
type: str
description:
- Sets the common Name (CN) of the configured directory service group
containing users with read-only privileges on the FlashArray. This
name should be just the Common Name of the group without the CN=
specifier. Common Names should not exceed 64 characters in length.
- Not Supported from Purity 5.2.0 or higher. Use I(purefa_dsrole) module.
sa_group:
type: str
description:
- Sets the common Name (CN) of the configured directory service group
containing administrators with storage-related privileges on the
FlashArray. This name should be just the Common Name of the group
without the CN= specifier. Common Names should not exceed 64
characters in length.
- Not Supported from Purity 5.2.0 or higher. Use I(purefa_dsrole) module.
aa_group:
type: str
description:
- Sets the common Name (CN) of the directory service group containing
administrators with full privileges when managing the FlashArray.
The name should be just the Common Name of the group without the
CN= specifier. Common Names should not exceed 64 characters in length.
- Not Supported from Purity 5.2.0 or higher. Use I(purefa_dsrole) module.
extends_documentation_fragment:
- purestorage.fa
'''
EXAMPLES = r'''
- name: Delete existing directory service
purefa_ds:
state: absent
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Create directory service (disabled) - Pre-5.2.0
purefa_ds:
uri: "ldap://lab.purestorage.com"
base_dn: "DC=lab,DC=purestorage,DC=com"
bind_user: Administrator
bind_password: password
group_base: "OU=Pure-Admin"
ro_group: PureReadOnly
sa_group: PureStorage
aa_group: PureAdmin
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Create directory service (disabled) - 5.2.0 or higher
purefa_ds:
uri: "ldap://lab.purestorage.com"
base_dn: "DC=lab,DC=purestorage,DC=com"
bind_user: Administrator
bind_password: password
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Enable existing directory service
purefa_ds:
enable: true
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Disable existing directory service
purefa_ds:
enable: false
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Create directory service (enabled) - Pre-5.2.0
purefa_ds:
enable: true
uri: "ldap://lab.purestorage.com"
base_dn: "DC=lab,DC=purestorage,DC=com"
bind_user: Administrator
bind_password: password
group_base: "OU=Pure-Admin"
ro_group: PureReadOnly
sa_group: PureStorage
aa_group: PureAdmin
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Create directory service (enabled) - 5.2.0 or higher
purefa_ds:
enable: true
uri: "ldap://lab.purestorage.com"
base_dn: "DC=lab,DC=purestorage,DC=com"
bind_user: Administrator
bind_password: password
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
'''
RETURN = r'''
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.pure import get_system, purefa_argument_spec
DS_ROLE_REQUIRED_API_VERSION = '1.16'
def update_ds(module, array):
"""Update Directory Service"""
changed = False
module.exit_json(changed=changed)
def enable_ds(module, array):
"""Enable Directory Service"""
changed = False
try:
array.enable_directory_service()
changed = True
except Exception:
module.fail_json(msg='Enable Directory Service failed: Check Configuration')
module.exit_json(changed=changed)
def disable_ds(module, array):
"""Disable Directory Service"""
"""Disable Directory Service"""
changed = False
try:
array.disable_directory_service()
changed = True
except Exception:
module.fail_json(msg='Disable Directory Service failed')
module.exit_json(changed=changed)
def delete_ds(module, array):
"""Delete Directory Service"""
changed = False
try:
api_version = array._list_available_rest_versions()
array.set_directory_service(enabled=False)
if DS_ROLE_REQUIRED_API_VERSION in api_version:
array.set_directory_service(uri=[''],
base_dn="",
bind_user="",
bind_password="",
certificate="")
changed = True
else:
array.set_directory_service(uri=[''],
base_dn="",
group_base="",
bind_user="",
bind_password="",
readonly_group="",
storage_admin_group="",
array_admin_group="",
certificate="")
changed = True
except Exception:
module.fail_json(msg='Delete Directory Service failed')
module.exit_json(changed=changed)
def create_ds(module, array):
"""Create Directory Service"""
changed = False
api_version = array._list_available_rest_versions()
if DS_ROLE_REQUIRED_API_VERSION in api_version:
if not module.params['role']:
module.fail_json(msg='At least one role must be configured')
try:
array.set_directory_service(uri=module.params['uri'],
base_dn=module.params['base_dn'],
bind_user=module.params['bind_user'],
bind_password=module.params['bind_password'])
array.set_directory_service(enabled=module.params['enable'])
changed = True
except Exception:
module.fail_json(msg='Create Directory Service failed: Check configuration')
else:
groups_rule = [not module.params['ro_group'],
not module.params['sa_group'],
not module.params['aa_group']]
if all(groups_rule):
module.fail_json(msg='At least one group must be configured')
try:
array.set_directory_service(uri=module.params['uri'],
base_dn=module.params['base_dn'],
group_base=module.params['group_base'],
bind_user=module.params['bind_user'],
bind_password=module.params['bind_password'],
readonly_group=module.params['ro_group'],
storage_admin_group=module.params['sa_group'],
array_admin_group=module.params['aa_group'])
array.set_directory_service(enabled=module.params['enable'])
changed = True
except Exception:
module.fail_json(msg='Create Directory Service failed: Check configuration')
module.exit_json(changed=changed)
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(dict(
uri=dict(type='list'),
state=dict(type='str', default='present', choices=['absent', 'present']),
enable=dict(type='bool', default=False),
bind_password=dict(type='str', no_log=True),
bind_user=dict(type='str'),
base_dn=dict(type='str'),
group_base=dict(type='str'),
ro_group=dict(type='str'),
sa_group=dict(type='str'),
aa_group=dict(type='str'),
))
required_together = [['uri', 'bind_password', 'bind_user',
'base_dn', 'group_base']]
module = AnsibleModule(argument_spec,
required_together=required_together,
supports_check_mode=False)
state = module.params['state']
array = get_system(module)
ds_exists = False
dirserv = array.get_directory_service()
ds_enabled = dirserv['enabled']
if dirserv['base_dn']:
ds_exists = True
if state == 'absent' and ds_exists:
delete_ds(module, array)
elif ds_exists and module.params['enable'] and ds_enabled:
update_ds(module, array)
elif ds_exists and not module.params['enable'] and ds_enabled:
disable_ds(module, array)
elif ds_exists and module.params['enable'] and not ds_enabled:
enable_ds(module, array)
elif not ds_exists and state == 'present':
create_ds(module, array)
else:
module.exit_json(changed=False)
if __name__ == '__main__':
main()

@ -1,164 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2019, Simon Dodsley (simon@purestorage.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: purefa_dsrole
version_added: '2.8'
short_description: Configure FlashArray Directory Service Roles
description:
- Set or erase directory services role configurations.
- Only available for FlashArray running Purity 5.2.0 or higher
author:
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
options:
state:
description:
- Create or delete directory service role
type: str
default: present
choices: [ absent, present ]
role:
description:
- The directory service role to work on
choices: [ array_admin, ops_admin, readonly, storage_admin ]
group_base:
type: str
description:
- Specifies where the configured group is located in the directory
tree. This field consists of Organizational Units (OUs) that combine
with the base DN attribute and the configured group CNs to complete
the full Distinguished Name of the groups. The group base should
specify OU= for each OU and multiple OUs should be separated by commas.
The order of OUs is important and should get larger in scope from left
to right.
- Each OU should not exceed 64 characters in length.
group:
type: str
description:
- Sets the common Name (CN) of the configured directory service group
containing users for the FlashBlade. This name should be just the
Common Name of the group without the CN= specifier.
- Common Names should not exceed 64 characters in length.
extends_documentation_fragment:
- purestorage.fa
'''
EXAMPLES = r'''
- name: Delete existing array_admin directory service role
purefa_dsrole:
role: array_admin
state: absent
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Create array_admin directory service role
purefa_dsrole:
role: array_admin
group_base: "OU=PureGroups,OU=SANManagers"
group: pureadmins
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Update ops_admin directory service role
purefa_dsrole:
role: ops_admin
group_base: "OU=PureGroups"
group: opsgroup
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
'''
RETURN = r'''
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.pure import get_system, purefa_argument_spec
def update_role(module, array):
"""Update Directory Service Role"""
changed = False
role = array.list_directory_services_roles(names=[module.params['role']])
if role['group_base'] != module.params['group_base'] or role['group'] != module.params['group']:
try:
array.set_directory_services_roles(names=[module.params['role']],
group_base=module.params['group_base'],
group=module.params['group'])
changed = True
except Exception:
module.fail_json(msg='Update Directory Service Role {0} failed'.format(module.params['role']))
module.exit_json(changed=changed)
def delete_role(module, array):
"""Delete Directory Service Role"""
changed = False
try:
array.set_directory_services_roles(names=[module.params['role']],
group_base='',
group='')
changed = True
except Exception:
module.fail_json(msg='Delete Directory Service Role {0} failed'.format(module.params['role']))
module.exit_json(changed=changed)
def create_role(module, array):
"""Create Directory Service Role"""
changed = False
try:
array.set_directory_services_roles(names=[module.params['role']],
group_base=module.params['group_base'],
group=module.params['group'])
changed = True
except Exception:
module.fail_json(msg='Create Directory Service Role {0} failed: Check configuration'.format(module.params['role']))
module.exit_json(changed=changed)
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(dict(
role=dict(required=True, type='str', choices=['array_admin', 'ops_admin', 'readonly', 'storage_admin']),
state=dict(type='str', default='present', choices=['absent', 'present']),
group_base=dict(type='str'),
group=dict(type='str'),
))
required_together = [['group', 'group_base']]
module = AnsibleModule(argument_spec,
required_together=required_together,
supports_check_mode=False)
state = module.params['state']
array = get_system(module)
role_configured = False
role = array.list_directory_services_roles(names=[module.params['role']])
if role['group'] is not None:
role_configured = True
if state == 'absent' and role_configured:
delete_role(module, array)
elif role_configured and state == 'present':
update_role(module, array)
elif not role_configured and state == 'present':
create_role(module, array)
else:
module.exit_json(changed=False)
if __name__ == '__main__':
main()

@ -1,263 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2018, Simon Dodsley (simon@purestorage.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: purefa_hg
version_added: '2.4'
short_description: Manage hostgroups on Pure Storage FlashArrays
description:
- Create, delete or modify hostgroups on Pure Storage FlashArrays.
author:
- Pure Storage ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
options:
hostgroup:
description:
- The name of the hostgroup.
type: str
required: true
state:
description:
- Define whether the hostgroup should exist or not.
type: str
default: present
choices: [ absent, present ]
host:
type: list
description:
- List of existing hosts to add to hostgroup.
volume:
type: list
description:
- List of existing volumes to add to hostgroup.
extends_documentation_fragment:
- purestorage.fa
'''
EXAMPLES = r'''
- name: Create empty hostgroup
purefa_hg:
hostgroup: foo
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Add hosts and volumes to existing or new hostgroup
purefa_hg:
hostgroup: foo
host:
- host1
- host2
volume:
- vol1
- vol2
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Delete hosts and volumes from hostgroup
purefa_hg:
hostgroup: foo
host:
- host1
- host2
volume:
- vol1
- vol2
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: absent
# This will disconnect all hosts and volumes in the hostgroup
- name: Delete hostgroup
purefa_hg:
hostgroup: foo
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: absent
- name: Create host group with hosts and volumes
purefa_hg:
hostgroup: bar
host:
- host1
- host2
volume:
- vol1
- vol2
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
'''
RETURN = r'''
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.pure import get_system, purefa_argument_spec
try:
from purestorage import purestorage
HAS_PURESTORAGE = True
except ImportError:
HAS_PURESTORAGE = False
def get_hostgroup(module, array):
hostgroup = None
for host in array.list_hgroups():
if host["name"] == module.params['hostgroup']:
hostgroup = host
break
return hostgroup
def make_hostgroup(module, array):
changed = True
try:
array.create_hgroup(module.params['hostgroup'])
except Exception:
changed = False
if module.params['host']:
array.set_hgroup(module.params['hostgroup'], hostlist=module.params['host'])
if module.params['volume']:
for vol in module.params['volume']:
array.connect_hgroup(module.params['hostgroup'], vol)
module.exit_json(changed=changed)
def update_hostgroup(module, array):
changed = False
hgroup = get_hostgroup(module, array)
volumes = array.list_hgroup_connections(module.params['hostgroup'])
if module.params['state'] == "present":
if module.params['host']:
new_hosts = list(set(module.params['host']).difference(hgroup['hosts']))
if new_hosts:
try:
array.set_hgroup(module.params['hostgroup'], addhostlist=new_hosts)
changed = True
except Exception:
module.fail_josn(msg='Failed to add host(s) to hostgroup')
if module.params['volume']:
if volumes:
current_vols = [vol['vol'] for vol in volumes]
new_volumes = list(set(module.params['volume']).difference(set(current_vols)))
for cvol in new_volumes:
try:
array.connect_hgroup(module.params['hostgroup'], cvol)
changed = True
except Exception:
changed = False
else:
for cvol in module.params['volume']:
try:
array.connect_hgroup(module.params['hostgroup'], cvol)
changed = True
except Exception:
changed = False
else:
if module.params['host']:
old_hosts = list(set(module.params['host']).intersection(hgroup['hosts']))
if old_hosts:
try:
array.set_hgroup(module.params['hostgroup'], remhostlist=old_hosts)
changed = True
except Exception:
changed = False
if module.params['volume']:
old_volumes = list(set(module.params['volume']).difference(set([vol['name'] for vol in volumes])))
for cvol in old_volumes:
try:
array.disconnect_hgroup(module.params['hostgroup'], cvol)
changed = True
except Exception:
changed = False
module.exit_json(changed=changed)
def delete_hostgroup(module, array):
changed = True
try:
vols = array.list_hgroup_connections(module.params['hostgroup'])
for vol in vols:
try:
array.disconnect_hgroup(module.params['hostgroup'], vol["vol"])
except Exception:
changed = False
host = array.get_hgroup(module.params['hostgroup'])
try:
array.set_hgroup(module.params['hostgroup'], remhostlist=host['hosts'])
try:
array.delete_hgroup(module.params['hostgroup'])
except Exception:
changed = False
except Exception:
changed = False
except Exception:
changed = False
module.exit_json(changed=changed)
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(dict(
hostgroup=dict(type='str', required=True),
state=dict(type='str', default='present', choices=['absent', 'present']),
host=dict(type='list'),
volume=dict(type='list'),
))
module = AnsibleModule(argument_spec, supports_check_mode=False)
if not HAS_PURESTORAGE:
module.fail_json(msg='purestorage sdk is required for this module in host')
state = module.params['state']
array = get_system(module)
hostgroup = get_hostgroup(module, array)
if module.params['host']:
try:
for hst in module.params['host']:
array.get_host(hst)
except Exception:
module.fail_json(msg='Host {0} not found'.format(hst))
if module.params['volume']:
try:
for vol in module.params['volume']:
array.get_volume(vol)
except Exception:
module.fail_json(msg='Volume {0} not found'.format(vol))
if hostgroup and state == 'present':
update_hostgroup(module, array)
elif hostgroup and module.params['volume'] and state == 'absent':
update_hostgroup(module, array)
elif hostgroup and module.params['host'] and state == 'absent':
update_hostgroup(module, array)
elif hostgroup and state == 'absent':
delete_hostgroup(module, array)
elif hostgroup is None and state == 'absent':
module.exit_json(changed=False)
else:
make_hostgroup(module, array)
if __name__ == '__main__':
main()

@ -1,476 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Simon Dodsley (simon@purestorage.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: purefa_host
version_added: '2.4'
short_description: Manage hosts on Pure Storage FlashArrays
description:
- Create, delete or modify hosts on Pure Storage FlashArrays.
author:
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
notes:
- If specifying C(lun) option ensure host support requested value
options:
host:
description:
- The name of the host.
type: str
required: true
state:
description:
- Define whether the host should exist or not.
- When removing host all connected volumes will be disconnected.
type: str
default: present
choices: [ absent, present ]
protocol:
description:
- Defines the host connection protocol for volumes.
type: str
default: iscsi
choices: [ fc, iscsi, nvme, mixed ]
wwns:
type: list
description:
- List of wwns of the host if protocol is fc or mixed.
iqn:
type: list
description:
- List of IQNs of the host if protocol is iscsi or mixed.
nqn:
type: list
description:
- List of NQNs of the host if protocol is nvme or mixed.
version_added: '2.8'
volume:
type: str
description:
- Volume name to map to the host.
lun:
description:
- LUN ID to assign to volume for host. Must be unique.
- If not provided the ID will be automatically assigned.
- Range for LUN ID is 1 to 4095.
type: int
version_added: '2.8'
personality:
type: str
description:
- Define which operating system the host is. Recommended for
ActiveCluster integration.
default: ''
choices: ['hpux', 'vms', 'aix', 'esxi', 'solaris', 'hitachi-vsp', 'oracle-vm-server', 'delete', '']
version_added: '2.7'
preferred_array:
type: list
description:
- List of preferred arrays in an ActiveCluster environment.
- To remove existing preferred arrays from the host, specify I(delete).
version_added: '2.9'
extends_documentation_fragment:
- purestorage.fa
'''
EXAMPLES = r'''
- name: Create new AIX host
purefa_host:
host: foo
personality: aix
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Delete host
purefa_host:
host: foo
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: absent
- name: Make host bar with wwn ports
purefa_host:
host: bar
protocol: fc
wwns:
- 00:00:00:00:00:00:00
- 11:11:11:11:11:11:11
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Make host bar with iSCSI ports
purefa_host:
host: bar
protocol: iscsi
iqn:
- iqn.1994-05.com.redhat:7d366003913
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Make host bar with NVMe ports
purefa_host:
host: bar
protocol: nvme
nqn:
- nqn.2014-08.com.vendor:nvme:nvm-subsystem-sn-d78432
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Make mixed protocol host
purefa_host:
host: bar
protocol: mixed
nqn:
- nqn.2014-08.com.vendor:nvme:nvm-subsystem-sn-d78432
iqn:
- iqn.1994-05.com.redhat:7d366003914
wwns:
- 00:00:00:00:00:00:01
- 11:11:11:11:11:11:12
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Map host foo to volume bar as LUN ID 12
purefa_host:
host: foo
volume: bar
lun: 12
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Add preferred arrays to host foo
purefa_host:
host: foo
preferred_array:
- array1
- array2
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Delete preferred arrays from host foo
purefa_host:
host: foo
preferred_array: delete
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
'''
RETURN = r'''
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.pure import get_system, purefa_argument_spec
AC_REQUIRED_API_VERSION = '1.14'
PREFERRED_ARRAY_API_VERSION = '1.15'
NVME_API_VERSION = '1.16'
def _is_cbs(module, array, is_cbs=False):
"""Is the selected array a Cloud Block Store"""
model = ''
ct0_model = array.get_hardware('CT0')['model']
if ct0_model:
model = ct0_model
else:
ct1_model = array.get_hardware('CT1')['model']
model = ct1_model
if 'CBS' in model:
is_cbs = True
return is_cbs
def _set_host_initiators(module, array):
"""Set host initiators."""
if module.params['protocol'] in ['nvme', 'mixed']:
if module.params['nqn']:
try:
array.set_host(module.params['host'],
nqnlist=module.params['nqn'])
except Exception:
module.fail_json(msg='Setting of NVMe NQN failed.')
if module.params['protocol'] in ['iscsi', 'mixed']:
if module.params['iqn']:
try:
array.set_host(module.params['host'],
iqnlist=module.params['iqn'])
except Exception:
module.fail_json(msg='Setting of iSCSI IQN failed.')
if module.params['protocol'] in ['fc', 'mixed']:
if module.params['wwns']:
try:
array.set_host(module.params['host'],
wwnlist=module.params['wwns'])
except Exception:
module.fail_json(msg='Setting of FC WWNs failed.')
def _update_host_initiators(module, array, answer=False):
"""Change host initiator if iscsi or nvme or add new FC WWNs"""
if module.params['protocol'] in ['nvme', 'mixed']:
if module.params['nqn']:
current_nqn = array.get_host(module.params['host'])['nqn']
if current_nqn != module.params['nqn']:
try:
array.set_host(module.params['host'],
nqnlist=module.params['nqn'])
answer = True
except Exception:
module.fail_json(msg='Change of NVMe NQN failed.')
if module.params['protocol'] in ['iscsi', 'mixed']:
if module.params['iqn']:
current_iqn = array.get_host(module.params['host'])['iqn']
if current_iqn != module.params['iqn']:
try:
array.set_host(module.params['host'],
iqnlist=module.params['iqn'])
answer = True
except Exception:
module.fail_json(msg='Change of iSCSI IQN failed.')
if module.params['protocol'] in ['fc', 'mixed']:
if module.params['wwns']:
module.params['wwns'] = [wwn.replace(':', '') for wwn in module.params['wwns']]
module.params['wwns'] = [wwn.upper() for wwn in module.params['wwns']]
current_wwn = array.get_host(module.params['host'])['wwn']
if current_wwn != module.params['wwns']:
try:
array.set_host(module.params['host'],
wwnlist=module.params['wwns'])
answer = True
except Exception:
module.fail_json(msg='FC WWN change failed.')
return answer
def _connect_new_volume(module, array, answer=False):
"""Connect volume to host"""
api_version = array._list_available_rest_versions()
if AC_REQUIRED_API_VERSION in api_version and module.params['lun']:
try:
array.connect_host(module.params['host'],
module.params['volume'],
lun=module.params['lun'])
answer = True
except Exception:
module.fail_json(msg='LUN ID {0} invalid. Check for duplicate LUN IDs.'.format(module.params['lun']))
else:
array.connect_host(module.params['host'], module.params['volume'])
answer = True
return answer
def _set_host_personality(module, array):
"""Set host personality. Only called when supported"""
if module.params['personality'] != 'delete':
array.set_host(module.params['host'],
personality=module.params['personality'])
else:
array.set_host(module.params['host'], personality='')
def _set_preferred_array(module, array):
"""Set preferred array list. Only called when supported"""
if module.params['preferred_array'] != ['delete']:
array.set_host(module.params['host'],
preferred_array=module.params['preferred_array'])
else:
array.set_host(module.params['host'], personality='')
def _update_host_personality(module, array, answer=False):
"""Change host personality. Only called when supported"""
personality = array.get_host(module.params['host'], personality=True)['personality']
if personality is None and module.params['personality'] != 'delete':
try:
array.set_host(module.params['host'],
personality=module.params['personality'])
answer = True
except Exception:
module.fail_json(msg='Personality setting failed.')
if personality is not None:
if module.params['personality'] == 'delete':
try:
array.set_host(module.params['host'], personality='')
answer = True
except Exception:
module.fail_json(msg='Personality deletion failed.')
elif personality != module.params['personality']:
try:
array.set_host(module.params['host'],
personality=module.params['personality'])
answer = True
except Exception:
module.fail_json(msg='Personality change failed.')
return answer
def _update_preferred_array(module, array, answer=False):
"""Update existing preferred array list. Only called when supported"""
preferred_array = array.get_host(module.params['host'], preferred_array=True)['preferred_array']
if preferred_array == [] and module.params['preferred_array'] != ['delete']:
try:
array.set_host(module.params['host'],
preferred_array=module.params['preferred_array'])
answer = True
except Exception:
module.fail_json(msg='Preferred array list creation failed for {0}.'.format(module.params['host']))
elif preferred_array != []:
if module.params['preferred_array'] == ['delete']:
try:
array.set_host(module.params['host'], preferred_array=[])
answer = True
except Exception:
module.fail_json(msg='Preferred array list deletion failed for {0}.'.format(module.params['host']))
elif preferred_array != module.params['preferred_array']:
try:
array.set_host(module.params['host'],
preferred_array=module.params['preferred_array'])
answer = True
except Exception:
module.fail_json(msg='Preferred array list change failed for {0}.'.format(module.params['host']))
return answer
def get_host(module, array):
host = None
for hst in array.list_hosts():
if hst["name"] == module.params['host']:
host = hst
break
return host
def make_host(module, array):
changed = True
if not module.check_mode:
try:
array.create_host(module.params['host'])
except Exception:
module.fail_json(msg='Host {0} creation failed.'.format(module.params['host']))
try:
_set_host_initiators(module, array)
api_version = array._list_available_rest_versions()
if AC_REQUIRED_API_VERSION in api_version and module.params['personality']:
_set_host_personality(module, array)
if PREFERRED_ARRAY_API_VERSION in api_version and module.params['preferred_array']:
_set_preferred_array(module, array)
if module.params['volume']:
if module.params['lun']:
array.connect_host(module.params['host'],
module.params['volume'],
lun=module.params['lun'])
else:
array.connect_host(module.params['host'], module.params['volume'])
except Exception:
module.fail_json(msg='Host {0} configuration failed.'.format(module.params['host']))
module.exit_json(changed=changed)
def update_host(module, array):
changed = True
if not module.check_mode:
init_changed = vol_changed = pers_changed = pref_changed = False
volumes = array.list_host_connections(module.params['host'])
if module.params['iqn'] or module.params['wwns'] or module.params['nqn']:
init_changed = _update_host_initiators(module, array)
if module.params['volume']:
current_vols = [vol['vol'] for vol in volumes]
if not module.params['volume'] in current_vols:
vol_changed = _connect_new_volume(module, array)
api_version = array._list_available_rest_versions()
if AC_REQUIRED_API_VERSION in api_version:
if module.params['personality']:
pers_changed = _update_host_personality(module, array)
if PREFERRED_ARRAY_API_VERSION in api_version:
if module.params['preferred_array']:
pref_changed = _update_preferred_array(module, array)
changed = init_changed or vol_changed or pers_changed or pref_changed
module.exit_json(changed=changed)
def delete_host(module, array):
changed = True
if not module.check_mode:
try:
for vol in array.list_host_connections(module.params['host']):
array.disconnect_host(module.params['host'], vol["vol"])
array.delete_host(module.params['host'])
except Exception:
module.fail_json(msg='Host {0} deletion failed'.format(module.params['host']))
module.exit_json(changed=changed)
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(dict(
host=dict(type='str', required=True),
state=dict(type='str', default='present', choices=['absent', 'present']),
protocol=dict(type='str', default='iscsi', choices=['fc', 'iscsi', 'nvme', 'mixed']),
nqn=dict(type='list'),
iqn=dict(type='list'),
wwns=dict(type='list'),
volume=dict(type='str'),
lun=dict(type='int'),
personality=dict(type='str', default='',
choices=['hpux', 'vms', 'aix', 'esxi', 'solaris',
'hitachi-vsp', 'oracle-vm-server', 'delete', '']),
preferred_array=dict(type='list'),
))
module = AnsibleModule(argument_spec, supports_check_mode=True)
array = get_system(module)
if _is_cbs(module, array) and module.params['wwns'] or module.params['nqn']:
module.fail_json(msg='Cloud block Store only support iSCSI as a protocol')
api_version = array._list_available_rest_versions()
if module.params['nqn'] is not None and NVME_API_VERSION not in api_version:
module.fail_json(msg='NVMe protocol not supported. Please upgrade your array.')
state = module.params['state']
host = get_host(module, array)
if module.params['lun'] and not 1 <= module.params['lun'] <= 4095:
module.fail_json(msg='LUN ID of {0} is out of range (1 to 4095)'.format(module.params['lun']))
if module.params['volume']:
try:
array.get_volume(module.params['volume'])
except Exception:
module.fail_json(msg='Volume {0} not found'.format(module.params['volume']))
if module.params['preferred_array']:
try:
if module.params['preferred_array'] != ['delete']:
all_connected_arrays = array.list_array_connections()
if not all_connected_arrays:
module.fail_json(msg='No target arrays connected to source array. Setting preferred arrays not possible.')
else:
current_arrays = [array.get()['array_name']]
for current_array in range(0, len(all_connected_arrays)):
if all_connected_arrays[current_array]['type'] == "sync-replication":
current_arrays.append(all_connected_arrays[current_array]['array_name'])
for array_to_connect in range(0, len(module.params['preferred_array'])):
if module.params['preferred_array'][array_to_connect] not in current_arrays:
module.fail_json(msg='Array {0} is not a synchronously connected array.'.format(module.params['preferred_array'][array_to_connect]))
except Exception:
module.fail_json(msg='Failed to get existing array connections.')
if host is None and state == 'present':
make_host(module, array)
elif host and state == 'present':
update_host(module, array)
elif host and state == 'absent':
delete_host(module, array)
elif host is None and state == 'absent':
module.exit_json(changed=False)
if __name__ == '__main__':
main()

File diff suppressed because it is too large Load Diff

@ -1,128 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2018, Simon Dodsley (simon@purestorage.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: purefa_ntp
version_added: '2.8'
short_description: Configure Pure Storage FlashArray NTP settings
description:
- Set or erase NTP configuration for Pure Storage FlashArrays.
author:
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
options:
state:
description:
- Create or delete NTP servers configuration
type: str
default: present
choices: [ absent, present ]
ntp_servers:
type: list
description:
- A list of up to 4 alternate NTP servers. These may include IPv4,
IPv6 or FQDNs. Invalid IP addresses will cause the module to fail.
No validation is performed for FQDNs.
- If more than 4 servers are provided, only the first 4 unique
nameservers will be used.
- if no servers are given a default of I(0.pool.ntp.org) will be used.
extends_documentation_fragment:
- purestorage.fa
'''
EXAMPLES = r'''
- name: Delete existing NTP server entries
purefa_ntp:
state: absent
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Set array NTP servers
purefa_ntp:
state: present
ntp_servers:
- "0.pool.ntp.org"
- "1.pool.ntp.org"
- "2.pool.ntp.org"
- "3.pool.ntp.org"
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
'''
RETURN = r'''
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.pure import get_system, purefa_argument_spec
def remove(duplicate):
final_list = []
for num in duplicate:
if num not in final_list:
final_list.append(num)
return final_list
def delete_ntp(module, array):
"""Delete NTP Servers"""
changed = False
if array.get(ntpserver=True)['ntpserver'] != []:
try:
array.set(ntpserver=[])
changed = True
except Exception:
module.fail_json(msg='Deletion of NTP servers failed')
module.exit_json(changed=changed)
def create_ntp(module, array):
"""Set NTP Servers"""
changed = False
if not module.params['ntp_servers']:
module.params['ntp_servers'] = ['0.pool.ntp.org']
try:
array.set(ntpserver=module.params['ntp_servers'][0:4])
changed = True
except Exception:
module.fail_json(msg='Update of NTP servers failed')
module.exit_json(changed=changed)
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(dict(
ntp_servers=dict(type='list'),
state=dict(type='str', default='present', choices=['absent', 'present']),
))
required_if = [['state', 'present', ['ntp_servers']]]
module = AnsibleModule(argument_spec,
required_if=required_if,
supports_check_mode=False)
array = get_system(module)
if module.params['state'] == 'absent':
delete_ntp(module, array)
else:
module.params['ntp_servers'] = remove(module.params['ntp_servers'])
if sorted(array.get(ntpserver=True)['ntpserver']) != sorted(module.params['ntp_servers'][0:4]):
create_ntp(module, array)
module.exit_json(changed=False)
if __name__ == '__main__':
main()

@ -1,270 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2019, Simon Dodsley (simon@purestorage.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: purefa_offload
version_added: '2.8'
short_description: Create, modify and delete NFS or S3 offload targets
description:
- Create, modify and delete NFS or S3 offload targets.
- Only supported on Purity v5.2.0 or higher.
- You must have a correctly configured offload network for offload to work.
author:
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
options:
state:
description:
- Define state of offload
default: present
choices: [ absent, present ]
type: str
name:
description:
- The name of the offload target
required: true
type: str
protocol:
description:
- Define which protocol the offload engine uses
default: nfs
choices: [ nfs, s3 ]
type: str
address:
description:
- The IP or FQDN address of the NFS server
type: str
share:
description:
- NFS export on the NFS server
type: str
options:
description:
- Additional mount options for the NFS share
- Supported mount options include I(port), I(rsize),
I(wsize), I(nfsvers), and I(tcp) or I(udp)
required: false
default: ""
type: str
access_key:
description:
- Access Key ID of the S3 target
type: str
bucket:
description:
- Name of the bucket for the S3 target
type: str
secret:
description:
- Secret Access Key for the S3 target
type: str
initialize:
description:
- Define whether to initialize the S3 bucket
type: bool
default: true
extends_documentation_fragment:
- purestorage.fa
'''
EXAMPLES = r'''
- name: Create NFS offload target
purefa_offload:
name: nfs-offload
protocol: nfs
address: 10.21.200.4
share: "/offload_target"
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Create S3 offload target
purefa_offload:
name: s3-offload
protocol: s3
access_key: "3794fb12c6204e19195f"
bucket: offload-bucket
secret: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Delete offload target
purefa_offload:
name: nfs-offload
protocol: nfs
state: absent
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
'''
RETURN = r'''
'''
import re
from distutils.version import LooseVersion
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.pure import get_system, purefa_argument_spec
MIN_REQUIRED_API_VERSION = '1.16'
REGEX_TARGET_NAME = re.compile(r"^[a-zA-Z0-9\-]*$")
def get_target(module, array):
"""Return target or None"""
try:
return array.get_offload(module.params['name'])
except Exception:
return None
def create_offload(module, array):
"""Create offload target"""
changed = False
# First check if the offload network interface is there and enabled
try:
if not array.get_network_interface('@offload.data')['enabled']:
module.fail_json(msg='Offload Network interface not enabled. Please resolve.')
except Exception:
module.fail_json(msg='Offload Network interface not correctly configured. Please resolve.')
if module.params['protocol'] == 'nfs':
try:
array.connect_nfs_offload(module.params['name'],
mount_point=module.params['share'],
address=module.params['address'],
mount_options=module.params['options'])
changed = True
except Exception:
module.fail_json(msg='Failed to create NFS offload {0}. '
'Please perform diagnostic checks.'.format(module.params['name']))
if module.params['protocol'] == 's3':
try:
array.connect_s3_offload(module.params['name'],
access_key_id=module.params['access_key'],
secret_access_key=module.params['secret'],
bucket=module.params['bucket'],
initialize=module.params['initialize'])
changed = True
except Exception:
module.fail_json(msg='Failed to create S3 offload {0}. '
'Please perform diagnostic checks.'.format(module.params['name']))
module.exit_json(changed=changed)
def update_offload(module, array):
"""Update offload target"""
changed = False
module.exit_json(changed=changed)
def delete_offload(module, array):
"""Delete offload target"""
changed = False
if module.params['protocol'] == 'nfs':
try:
array.disconnect_nfs_offload(module.params['name'])
changed = True
except Exception:
module.fail_json(msg='Failed to delete NFS offload {0}.'.format(module.params['name']))
if module.params['protocol'] == 's3':
try:
array.disconnect_nfs_offload(module.params['name'])
changed = True
except Exception:
module.fail_json(msg='Failed to delete S3 offload {0}.'.format(module.params['name']))
module.exit_json(changed=changed)
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(dict(
state=dict(type='str', default='present', choices=['present', 'absent']),
protocol=dict(type='str', default='nfs', choices=['nfs', 's3']),
name=dict(type='str', required=True),
initialize=dict(default=True, type='bool'),
access_key=dict(type='str'),
secret=dict(type='str', no_log=True),
bucket=dict(type='str'),
share=dict(type='str'),
address=dict(type='str'),
options=dict(type='str', default=''),
))
required_if = []
if argument_spec['state'] == "present":
required_if = [
('protocol', 'nfs', ['address', 'share']),
('protocol', 's3', ['access_key', 'secret', 'bucket'])
]
module = AnsibleModule(argument_spec,
required_if=required_if,
supports_check_mode=False)
array = get_system(module)
api_version = array._list_available_rest_versions()
if MIN_REQUIRED_API_VERSION not in api_version:
module.fail_json(msg='FlashArray REST version not supported. '
'Minimum version required: {0}'.format(MIN_REQUIRED_API_VERSION))
if not re.match(r"^[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9]$", module.params['name']) or len(module.params['name']) > 56:
module.fail_json(msg='Target name invalid. '
'Target name must be between 1 and 56 characters (alphanumeric and -) in length '
'and begin and end with a letter or number. The name must include at least one letter.')
if module.params['protocol'] == "s3":
if not re.match(r"^[a-z0-9][a-z0-9.\-]*[a-z0-9]$", module.params['bucket']) or len(module.params['bucket']) > 63:
module.fail_json(msg='Bucket name invalid. '
'Bucket name must be between 3 and 63 characters '
'(ilowercase, alphanumeric, dash or period) in length '
'and begin and end with a letter or number.')
apps = array.list_apps()
app_version = 0
all_good = False
for app in range(0, len(apps)):
if apps[app]['name'] == 'offload':
if (apps[app]['enabled'] and
apps[app]['status'] == 'healthy' and
LooseVersion(apps[app]['version']) >= LooseVersion('5.2.0')):
all_good = True
app_version = apps[app]['version']
break
if not all_good:
module.fail_json(msg='Correct Offload app not installed or incorrectly configured')
else:
if LooseVersion(array.get()['version']) != LooseVersion(app_version):
module.fail_json(msg='Offload app version must match Purity version. Please upgrade.')
target = get_target(module, array)
if module.params['state'] == 'present' and not target:
target_count = len(array.list_offload())
# Currently only 1 offload target is supported
# TODO: (SD) when more targets supported add in REST version check as well
if target_count != 0:
module.fail_json(msg='Currently only 1 Offload Target is supported.')
create_offload(module, array)
elif module.params['state'] == 'present' and target:
update_offload(module, array)
elif module.params['state'] == 'absent' and target:
delete_offload(module, array)
module.exit_json(changed=False)
if __name__ == '__main__':
main()

@ -1,489 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Simon Dodsley (simon@purestorage.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: purefa_pg
version_added: '2.4'
short_description: Manage protection groups on Pure Storage FlashArrays
description:
- Create, delete or modify protection groups on Pure Storage FlashArrays.
- If a protection group exists and you try to add non-valid types, eg. a host
to a volume protection group the module will ignore the invalid types.
- Protection Groups on Offload targets are supported.
author:
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
options:
pgroup:
description:
- The name of the protection group.
type: str
required: true
state:
description:
- Define whether the protection group should exist or not.
type: str
default: present
choices: [ absent, present ]
volume:
description:
- List of existing volumes to add to protection group.
type: list
host:
description:
- List of existing hosts to add to protection group.
type: list
hostgroup:
description:
- List of existing hostgroups to add to protection group.
type: list
eradicate:
description:
- Define whether to eradicate the protection group on delete and leave in trash.
type : bool
default: 'no'
enabled:
description:
- Define whether to enabled snapshots for the protection group.
type : bool
default: 'yes'
target:
description:
- List of remote arrays or offload target for replication protection group
to connect to.
- Note that all replicated protection groups are asynchronous.
- Target arrays or offload targets must already be connected to the source array.
- Maximum number of targets per Protection Group is 4, assuming your
configuration supports this.
type: list
version_added: '2.8'
extends_documentation_fragment:
- purestorage.fa
'''
EXAMPLES = r'''
- name: Create new local protection group
purefa_pg:
pgroup: foo
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Create new replicated protection group
purefa_pg:
pgroup: foo
target:
- arrayb
- arrayc
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Create new replicated protection group to offload target and remote array
purefa_pg:
pgroup: foo
target:
- offload
- arrayc
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Create new protection group with snapshots disabled
purefa_pg:
pgroup: foo
enabled: false
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Delete protection group
purefa_pg:
pgroup: foo
eradicate: true
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: absent
- name: Eradicate protection group foo on offload target where source array is arrayA
purefa_pg:
pgroup: "arrayA:foo"
target: offload
eradicate: true
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: absent
- name: Create protection group for hostgroups
purefa_pg:
pgroup: bar
hostgroup:
- hg1
- hg2
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Create protection group for hosts
purefa_pg:
pgroup: bar
host:
- host1
- host2
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Create replicated protection group for volumes
purefa_pg:
pgroup: bar
volume:
- vol1
- vol2
target: arrayb
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
'''
RETURN = r'''
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.pure import get_system, purefa_argument_spec
OFFLOAD_API_VERSION = '1.16'
def get_targets(array):
"""Get Offload Targets"""
targets = []
try:
target_details = array.list_offload()
except Exception:
return None
for targetcnt in range(0, len(target_details)):
if target_details[targetcnt]['status'] == "connected":
targets.append(target_details[targetcnt]['name'])
return targets
def get_arrays(array):
""" Get Connected Arrays"""
arrays = []
array_details = array.list_array_connections()
for arraycnt in range(0, len(array_details)):
if array_details[arraycnt]['connected']:
arrays.append(array_details[arraycnt]['array_name'])
return arrays
def get_pending_pgroup(module, array):
""" Get Protection Group"""
pgroup = None
if ":" in module.params['pgroup']:
for pgrp in array.list_pgroups(pending=True, on="*"):
if pgrp["name"] == module.params['pgroup'] and pgrp['time_remaining']:
pgroup = pgrp
break
else:
for pgrp in array.list_pgroups(pending=True):
if pgrp["name"] == module.params['pgroup'] and pgrp['time_remaining']:
pgroup = pgrp
break
return pgroup
def get_pgroup(module, array):
""" Get Protection Group"""
pgroup = None
if ":" in module.params['pgroup']:
for pgrp in array.list_pgroups(on="*"):
if pgrp["name"] == module.params['pgroup']:
pgroup = pgrp
break
else:
for pgrp in array.list_pgroups():
if pgrp["name"] == module.params['pgroup']:
pgroup = pgrp
break
return pgroup
def get_pgroup_sched(module, array):
""" Get Protection Group Schedule"""
pgroup = None
for pgrp in array.list_pgroups(schedule=True):
if pgrp["name"] == module.params['pgroup']:
pgroup = pgrp
break
return pgroup
def check_pg_on_offload(module, array):
""" Check if PG already exists on offload target """
array_name = array.get()['array_name']
remote_pg = array_name + ":" + module.params['pgroup']
targets = get_targets(array)
for target in targets:
remote_pgs = array.list_pgroups(pending=True, on=target)
for rpg in range(0, len(remote_pgs)):
if remote_pg == remote_pgs[rpg]['name']:
return target
return None
def make_pgroup(module, array):
""" Create Protection Group"""
changed = False
if module.params['target']:
api_version = array._list_available_rest_versions()
connected_targets = []
connected_arrays = get_arrays(array)
if OFFLOAD_API_VERSION in api_version:
connected_targets = get_targets(array)
offload_name = check_pg_on_offload(module, array)
if offload_name and offload_name in module.params['target'][0:4]:
module.fail_json(msg='Protection Group {0} already exists on offload target {1}.'.format(module.params['pgroup'], offload_name))
connected_arrays = connected_arrays + connected_targets
if connected_arrays == []:
module.fail_json(msg='No connected targets on source array.')
if set(module.params['target'][0:4]).issubset(connected_arrays):
try:
array.create_pgroup(module.params['pgroup'], targetlist=module.params['target'][0:4])
except Exception:
module.fail_json(msg='Creation of replicated pgroup {0} failed. {1}'.format(module.params['pgroup'], module.params['target'][0:4]))
else:
module.fail_json(msg='Check all selected targets are connected to the source array.')
else:
try:
array.create_pgroup(module.params['pgroup'])
except Exception:
module.fail_json(msg='Creation of pgroup {0} failed.'.format(module.params['pgroup']))
try:
if module.params['target']:
array.set_pgroup(module.params['pgroup'], replicate_enabled=module.params['enabled'])
else:
array.set_pgroup(module.params['pgroup'], snap_enabled=module.params['enabled'])
except Exception:
module.fail_json(msg='Enabling pgroup {0} failed.'.format(module.params['pgroup']))
if module.params['volume']:
try:
array.set_pgroup(module.params['pgroup'], vollist=module.params['volume'])
except Exception:
module.fail_json(msg='Adding volumes to pgroup {0} failed.'.format(module.params['pgroup']))
if module.params['host']:
try:
array.set_pgroup(module.params['pgroup'], hostlist=module.params['host'])
except Exception:
module.fail_json(msg='Adding hosts to pgroup {0} failed.'.format(module.params['pgroup']))
if module.params['hostgroup']:
try:
array.set_pgroup(module.params['pgroup'], hgrouplist=module.params['hostgroup'])
except Exception:
module.fail_json(msg='Adding hostgroups to pgroup {0} failed.'.format(module.params['pgroup']))
changed = True
module.exit_json(changed=changed)
def update_pgroup(module, array):
""" Update Protection Group"""
changed = False
if module.params['target']:
api_version = array._list_available_rest_versions()
connected_targets = []
connected_arrays = get_arrays(array)
if OFFLOAD_API_VERSION in api_version:
connected_targets = get_targets(array)
offload_name = check_pg_on_offload(module, array)
if offload_name and offload_name in module.params['target'][0:4]:
module.fail_json(msg='Protection Group {0} already exists on offload target {1}.'.format(module.params['pgroup'], offload_name))
connected_arrays = connected_arrays + connected_targets
if connected_arrays == []:
module.fail_json(msg='No targets connected to source array.')
current_connects = array.get_pgroup(module.params['pgroup'])['targets']
current_targets = []
if current_connects:
for targetcnt in range(0, len(current_connects)):
current_targets.append(current_connects[targetcnt]['name'])
if set(module.params['target'][0:4]) != set(current_targets):
if not set(module.params['target'][0:4]).issubset(connected_arrays):
module.fail_json(msg='Check all selected targets are connected to the source array.')
try:
array.set_pgroup(module.params['pgroup'], targetlist=module.params['target'][0:4])
changed = True
except Exception:
module.fail_json(msg='Changing targets for pgroup {0} failed.'.format(module.params['pgroup']))
if module.params['target'] and module.params['enabled'] != get_pgroup_sched(module, array)['replicate_enabled']:
try:
array.set_pgroup(module.params['pgroup'], replicate_enabled=module.params['enabled'])
changed = True
except Exception:
module.fail_json(msg='Changing enabled status of pgroup {0} failed.'.format(module.params['pgroup']))
elif not module.params['target'] and module.params['enabled'] != get_pgroup_sched(module, array)['snap_enabled']:
try:
array.set_pgroup(module.params['pgroup'], snap_enabled=module.params['enabled'])
changed = True
except Exception:
module.fail_json(msg='Changing enabled status of pgroup {0} failed.'.format(module.params['pgroup']))
if module.params['volume'] and get_pgroup(module, array)['hosts'] is None and get_pgroup(module, array)['hgroups'] is None:
if get_pgroup(module, array)['volumes'] is None:
try:
array.set_pgroup(module.params['pgroup'], vollist=module.params['volume'])
changed = True
except Exception:
module.fail_json(msg='Adding volumes to pgroup {0} failed.'.format(module.params['pgroup']))
else:
if not all(x in get_pgroup(module, array)['volumes'] for x in module.params['volume']):
try:
array.set_pgroup(module.params['pgroup'], vollist=module.params['volume'])
changed = True
except Exception:
module.fail_json(msg='Changing volumes in pgroup {0} failed.'.format(module.params['pgroup']))
if module.params['host'] and get_pgroup(module, array)['volumes'] is None and get_pgroup(module, array)['hgroups'] is None:
if not get_pgroup(module, array)['hosts'] is None:
try:
array.set_pgroup(module.params['pgroup'], hostlist=module.params['host'])
changed = True
except Exception:
module.fail_json(msg='Adding hosts to pgroup {0} failed.'.format(module.params['pgroup']))
else:
if not all(x in get_pgroup(module, array)['hosts'] for x in module.params['host']):
try:
array.set_pgroup(module.params['pgroup'], hostlist=module.params['host'])
changed = True
except Exception:
module.fail_json(msg='Changing hosts in pgroup {0} failed.'.format(module.params['pgroup']))
if module.params['hostgroup'] and get_pgroup(module, array)['hosts'] is None and get_pgroup(module, array)['volumes'] is None:
if not get_pgroup(module, array)['hgroups'] is None:
try:
array.set_pgroup(module.params['pgroup'], hgrouplist=module.params['hostgroup'])
changed = True
except Exception:
module.fail_json(msg='Adding hostgroups to pgroup {0} failed.'.format(module.params['pgroup']))
else:
if not all(x in get_pgroup(module, array)['hgroups'] for x in module.params['hostgroup']):
try:
array.set_pgroup(module.params['pgroup'], hgrouplist=module.params['hostgroup'])
changed = True
except Exception:
module.fail_json(msg='Changing hostgroups in pgroup {0} failed.'.format(module.params['pgroup']))
module.exit_json(changed=changed)
def eradicate_pgroup(module, array):
""" Eradicate Protection Group"""
changed = False
if ":" in module.params['pgroup']:
try:
target = ''.join(module.params['target'])
array.destroy_pgroup(module.params['pgroup'], on=target, eradicate=True)
changed = True
except Exception:
module.fail_json(msg='Eradicating pgroup {0} failed.'.format(module.params['pgroup']))
else:
try:
array.destroy_pgroup(module.params['pgroup'], eradicate=True)
changed = True
except Exception:
module.fail_json(msg='Eradicating pgroup {0} failed.'.format(module.params['pgroup']))
module.exit_json(changed=changed)
def delete_pgroup(module, array):
""" Delete Protection Group"""
changed = False
if ":" in module.params['pgroup']:
try:
target = ''.join(module.params['target'])
array.destroy_pgroup(module.params['pgroup'], on=target)
changed = True
except Exception:
module.fail_json(msg='Deleting pgroup {0} failed.'.format(module.params['pgroup']))
else:
try:
array.destroy_pgroup(module.params['pgroup'])
changed = True
except Exception:
module.fail_json(msg='Deleting pgroup {0} failed.'.format(module.params['pgroup']))
if module.params['eradicate']:
eradicate_pgroup(module, array)
module.exit_json(changed=changed)
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(dict(
pgroup=dict(type='str', required=True),
state=dict(type='str', default='present', choices=['absent', 'present']),
volume=dict(type='list'),
host=dict(type='list'),
hostgroup=dict(type='list'),
target=dict(type='list'),
eradicate=dict(type='bool', default=False),
enabled=dict(type='bool', default=True),
))
mutually_exclusive = [['volume', 'host', 'hostgroup']]
module = AnsibleModule(argument_spec,
mutually_exclusive=mutually_exclusive,
supports_check_mode=False)
state = module.params['state']
array = get_system(module)
api_version = array._list_available_rest_versions()
if ":" in module.params['pgroup'] and OFFLOAD_API_VERSION not in api_version:
module.fail_json(msg='API version does not support offload protection groups.')
pgroup = get_pgroup(module, array)
xpgroup = get_pending_pgroup(module, array)
if module.params['host']:
try:
for hst in module.params['host']:
array.get_host(hst)
except Exception:
module.fail_json(msg='Host {0} not found'.format(hst))
if module.params['hostgroup']:
try:
for hstg in module.params['hostgroup']:
array.get_hgroup(hstg)
except Exception:
module.fail_json(msg='Hostgroup {0} not found'.format(hstg))
if pgroup and state == 'present':
update_pgroup(module, array)
elif pgroup and state == 'absent':
delete_pgroup(module, array)
elif xpgroup and state == 'absent' and module.params['eradicate']:
eradicate_pgroup(module, array)
elif not pgroup and not xpgroup and state == 'present':
make_pgroup(module, array)
elif pgroup is None and state == 'absent':
module.exit_json(changed=False)
if __name__ == '__main__':
main()

@ -1,294 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2017, Simon Dodsley (simon@purestorage.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: purefa_pgsnap
version_added: '2.6'
short_description: Manage protection group snapshots on Pure Storage FlashArrays
description:
- Create or delete protection group snapshots on Pure Storage FlashArray.
- Recovery of replicated snapshots on the replica target array is enabled.
author:
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
options:
name:
description:
- The name of the source protection group.
type: str
required: true
suffix:
description:
- Suffix of snapshot name.
state:
description:
- Define whether the protection group snapshot should exist or not.
Copy (added in 2.7) will create a full read/write clone of the
snapshot.
type: str
choices: [ absent, present, copy ]
default: present
eradicate:
description:
- Define whether to eradicate the snapshot on delete or leave in trash.
type: bool
default: 'no'
restore:
description:
- Restore a specific volume from a protection group snapshot.
type: str
version_added: 2.7
overwrite:
description:
- Define whether to overwrite the target volume if it already exists.
type: bool
default: 'no'
version_added: 2.8
target:
description:
- Volume to restore a specified volume to.
- If not supplied this will default to the volume defined in I(restore)
type: str
version_added: 2.8
now:
description: Whether to initiate a snapshot of the protection group immediately
type: bool
default: False
version_added: 2.9
apply_retention:
description: Apply retention schedule settings to the snapshot
type: bool
default: False
version_added: 2.9
remote:
description: Force immeadiate snapshot to remote targets
type: bool
default: False
version_added: 2.9
extends_documentation_fragment:
- purestorage.fa
'''
EXAMPLES = r'''
- name: Create protection group snapshot foo.ansible
purefa_pgsnap:
name: foo
suffix: ansible
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: present
- name: Delete and eradicate protection group snapshot named foo.snap
purefa_pgsnap:
name: foo
suffix: snap
eradicate: true
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: absent
- name: Restore volume data from local protection group snapshot named foo.snap to volume data2
purefa_pgsnap:
name: foo
suffix: snap
restore: data
target: data2
overwrite: true
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: copy
- name: Restore remote protection group snapshot arrayA:pgname.snap.data to local copy
purefa_pgsnap:
name: arrayA:pgname
suffix: snap
restore: data
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: copy
- name: Create snapshot of existing pgroup foo with suffix and force immeadiate copy to remote targets
purefa_pgsnap:
name: pgname
suffix: force
now: True
apply_retention: True
remote: True
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: copy
'''
RETURN = r'''
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.pure import get_system, purefa_argument_spec
from datetime import datetime
def get_pgroup(module, array):
"""Return Protection Group or None"""
try:
return array.get_pgroup(module.params['name'])
except Exception:
return None
def get_pgroupvolume(module, array):
"""Return Protection Group Volume or None"""
try:
pgroup = array.get_pgroup(module.params['name'])
for volume in pgroup['volumes']:
if volume == module.params['restore']:
return volume
except Exception:
return None
def get_rpgsnapshot(module, array):
"""Return iReplicated Snapshot or None"""
try:
snapname = module.params['name'] + "." + module.params['suffix'] + "." + module.params['restore']
for snap in array.list_volumes(snap=True):
if snap['name'] == snapname:
return snapname
except Exception:
return None
def get_pgsnapshot(module, array):
"""Return Snapshot (active or deleted) or None"""
try:
snapname = module.params['name'] + "." + module.params['suffix']
for snap in array.get_pgroup(module.params['name'], snap=True, pending=True):
if snap['name'] == snapname:
return snapname
except Exception:
return None
def create_pgsnapshot(module, array):
"""Create Protection Group Snapshot"""
changed = True
if not module.check_mode:
try:
if module.params['now'] and array.get_pgroup(module.params['name'])['targets'] is not None:
array.create_pgroup_snapshot(source=module.params['name'],
suffix=module.params['suffix'],
snap=True,
apply_retention=module.params['apply_retention'],
replicate_now=module.params['remote'])
else:
array.create_pgroup_snapshot(source=module.params['name'],
suffix=module.params['suffix'],
snap=True,
apply_retention=module.params['apply_retention'])
except Exception:
module.fail_json(msg="Snapshot of pgroup {0} failed.".format(module.params['name']))
module.exit_json(changed=changed)
def restore_pgsnapvolume(module, array):
"""Restore a Protection Group Snapshot Volume"""
changed = True
if not module.check_mode:
if ":" in module.params['name']:
if get_rpgsnapshot(module, array)is None:
module.fail_json(msg="Selected restore snapshot {0} does not exist in the Protection Group".format(module.params['restore']))
else:
if get_pgroupvolume(module, array) is None:
module.fail_json(msg="Selected restore volume {0} does not exist in the Protection Group".format(module.params['restore']))
volume = module.params['name'] + "." + module.params['suffix'] + "." + module.params['restore']
try:
array.copy_volume(volume, module.params['target'], overwrite=module.params['overwrite'])
except Exception:
module.fail_json(msg="Failed to restore {0} from pgroup {1}".format(volume, module.params['name']))
module.exit_json(changed=changed)
def update_pgsnapshot(module, array):
"""Update Protection Group Snapshot"""
changed = True
module.exit_json(changed=changed)
def delete_pgsnapshot(module, array):
""" Delete Protection Group Snapshot"""
changed = True
if not module.check_mode:
snapname = module.params['name'] + "." + module.params['suffix']
try:
array.destroy_pgroup(snapname)
if module.params['eradicate']:
try:
array.eradicate_pgroup(snapname)
except Exception:
module.fail_json(msg="Failed to eradicate pgroup {0}".format(snapname))
except Exception:
module.fail_json(msg="Failed to delete pgroup {0}".format(snapname))
module.exit_json(changed=changed)
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(dict(
name=dict(type='str', required=True),
suffix=dict(type='str'),
restore=dict(type='str'),
overwrite=dict(type='bool', default=False),
target=dict(type='str'),
eradicate=dict(type='bool', default=False),
now=dict(type='bool', default=False),
apply_retention=dict(type='bool', default=False),
remote=dict(type='bool', default=False),
state=dict(type='str', default='present', choices=['absent', 'present', 'copy']),
))
required_if = [('state', 'copy', ['suffix', 'restore'])]
module = AnsibleModule(argument_spec,
required_if=required_if,
supports_check_mode=True)
if module.params['suffix'] is None:
suffix = "snap-" + str((datetime.utcnow() - datetime(1970, 1, 1, 0, 0, 0, 0)).total_seconds())
module.params['suffix'] = suffix.replace(".", "")
if not module.params['target'] and module.params['restore']:
module.params['target'] = module.params['restore']
state = module.params['state']
array = get_system(module)
pgroup = get_pgroup(module, array)
if pgroup is None:
module.fail_json(msg="Protection Group {0} does not exist.".format(module.params['name']))
pgsnap = get_pgsnapshot(module, array)
if state == 'copy':
restore_pgsnapvolume(module, array)
elif state == 'present' and not pgsnap:
create_pgsnapshot(module, array)
elif state == 'present' and pgsnap:
update_pgsnapshot(module, array)
elif state == 'absent' and pgsnap:
delete_pgsnapshot(module, array)
elif state == 'absent' and not pgsnap:
module.exit_json(changed=False)
module.exit_json(changed=False)
if __name__ == '__main__':
main()

@ -1,99 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2018, Simon Dodsley (simon@purestorage.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: purefa_phonehome
version_added: '2.9'
short_description: Enable or Disable Pure Storage FlashArray Phonehome
description:
- Enable or Disable Phonehome for a Pure Storage FlashArray.
author:
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
options:
state:
description:
- Define state of phonehome
type: str
default: present
choices: [ present, absent ]
extends_documentation_fragment:
- purestorage.fa
'''
EXAMPLES = r'''
- name: Enable Phonehome
purefa_phonehome:
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Disable Phonehome
purefa_phonehome:
state: disable
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
'''
RETURN = r'''
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.pure import get_system, purefa_argument_spec
def enable_ph(module, array):
"""Enable Remote Assist"""
changed = False
if array.get_phonehome()['phonehome'] != 'enabled':
try:
if not module.check_mode:
array.enable_phonehome()
changed = True
except Exception:
module.fail_json(msg='Enabling Phonehome failed')
module.exit_json(changed=changed)
def disable_ph(module, array):
"""Disable Remote Assist"""
changed = False
if array.get_phonehome()['phonehome'] == 'enabled':
try:
if not module.check_mode:
array.disable_phonehome()
changed = True
except Exception:
module.fail_json(msg='Disabling Remote Assist failed')
module.exit_json(changed=changed)
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(dict(
state=dict(type='str', default='present', choices=['present', 'absent']),
))
module = AnsibleModule(argument_spec,
supports_check_mode=True)
array = get_system(module)
if module.params['state'] == 'present':
enable_ph(module, array)
else:
disable_ph(module, array)
module.exit_json(changed=False)
if __name__ == '__main__':
main()

@ -1,113 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2018, Simon Dodsley (simon@purestorage.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: purefa_ra
version_added: '2.8'
short_description: Enable or Disable Pure Storage FlashArray Remote Assist
description:
- Enable or Disable Remote Assist for a Pure Storage FlashArray.
author:
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
options:
state:
description:
- Define state of remote assist
- When set to I(enable) the RA port can be exposed using the
I(debug) module.
type: str
default: enable
choices: [ enable, disable ]
extends_documentation_fragment:
- purestorage.fa
'''
EXAMPLES = r'''
- name: Enable Remote Assist port
purefa_ra:
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
register: result
- debug:
msg: "Remote Assist: {{ result['ra_info'] }}"
- name: Disable Remote Assist port
purefa_ra:
state: disable
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
'''
RETURN = r'''
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.pure import get_system, purefa_argument_spec
def enable_ra(module, array):
"""Enable Remote Assist"""
changed = False
ra_facts = {}
if array.get_remote_assist_status()['status'] != 'enabled':
try:
ra_data = array.enable_remote_assist()
ra_facts['fa_ra'] = {'name': ra_data['name'],
'port': ra_data['port']}
changed = True
except Exception:
module.fail_json(msg='Enabling Remote Assist failed')
else:
try:
ra_data = array.get_remote_assist_status()
ra_facts['fa_ra'] = {'name': ra_data['name'],
'port': ra_data['port']}
except Exception:
module.fail_json(msg='Getting Remote Assist failed')
module.exit_json(changed=changed, ra_info=ra_facts)
def disable_ra(module, array):
"""Disable Remote Assist"""
changed = False
if array.get_remote_assist_status()['status'] == 'enabled':
try:
array.disable_remote_assist()
changed = True
except Exception:
module.fail_json(msg='Disabling Remote Assist failed')
module.exit_json(changed=changed)
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(dict(
state=dict(type='str', default='enable', choices=['enable', 'disable']),
))
module = AnsibleModule(argument_spec,
supports_check_mode=False)
array = get_system(module)
if module.params['state'] == 'enable':
enable_ra(module, array)
else:
disable_ra(module, array)
module.exit_json(changed=False)
if __name__ == '__main__':
main()

@ -1,150 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2018, Simon Dodsley (simon@purestorage.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: purefa_smtp
version_added: '2.9'
author:
- Pure Storage ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
short_description: Configure FlashArray SMTP settings
description:
- Set or erase configuration for the SMTP settings.
- If username/password are set this will always force a change as there is
no way to see if the password is different from the current SMTP configuration.
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
options:
state:
description:
- Set or delete SMTP configuration
default: present
type: str
choices: [ absent, present ]
password:
description:
- The SMTP password.
type: str
user:
description:
- The SMTP username.
type: str
relay_host:
description:
- IPv4 or IPv6 address or FQDN. A port number may be appended.
type: str
sender_domain:
description:
- Domain name.
type: str
extends_documentation_fragment:
- purestorage.fa
'''
EXAMPLES = r'''
- name: Delete existing SMTP settings
purefa_smtp:
state: absent
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Set SMTP settings
purefa_smtp:
sender_domain: purestorage.com
password: account_password
user: smtp_account
relay_host: 10.2.56.78:2345
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
'''
RETURN = r'''
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.pure import get_system, purefa_argument_spec
def delete_smtp(module, array):
"""Delete SMTP settings"""
changed = True
if not module.check_mode:
try:
array.set_smtp(sender_domain='', username='', password='', relay_host='')
except Exception:
module.fail_json(msg='Delete SMTP settings failed')
module.exit_json(changed=changed)
def create_smtp(module, array):
"""Set SMTP settings"""
changed = True
current_smtp = array.get_smtp()
if not module.check_mode:
if module.params['sender_domain'] and current_smtp['sender_domain'] != module.params['sender_domain']:
try:
array.set_smtp(sender_domain=module.params['sender_domain'])
changed_sender = True
except Exception:
module.fail_json(msg='Set SMTP sender domain failed.')
else:
changed_sender = False
if module.params['relay_host'] and current_smtp['relay_host'] != module.params['relay_host']:
try:
array.set_smtp(relay_host=module.params['relay_host'])
changed_relay = True
except Exception:
module.fail_json(msg='Set SMTP relay host failed.')
else:
changed_relay = False
if module.params['user']:
try:
array.set_smtp(user_name=module.params['user'], password=module.params['password'])
changed_creds = True
except Exception:
module.fail_json(msg='Set SMTP username/password failed.')
else:
changed_creds = False
changed = bool(changed_sender or changed_relay or changed_creds)
module.exit_json(changed=changed)
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(dict(
state=dict(type='str', default='present', choices=['absent', 'present']),
sender_domain=dict(type='str'),
password=dict(type='str', no_log=True),
user=dict(type='str'),
relay_host=dict(type='str'),
))
required_together = [['user', 'password']]
module = AnsibleModule(argument_spec,
required_together=required_together,
supports_check_mode=True)
state = module.params['state']
array = get_system(module)
if state == 'absent':
delete_smtp(module, array)
elif state == 'present':
create_smtp(module, array)
else:
module.exit_json(changed=False)
if __name__ == '__main__':
main()

@ -1,238 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2017, Simon Dodsley (simon@purestorage.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: purefa_snap
version_added: '2.4'
short_description: Manage volume snapshots on Pure Storage FlashArrays
description:
- Create or delete volumes and volume snapshots on Pure Storage FlashArray.
author:
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
options:
name:
description:
- The name of the source volume.
type: str
required: true
suffix:
description:
- Suffix of snapshot name.
type: str
target:
description:
- Name of target volume if creating from snapshot.
type: str
overwrite:
description:
- Define whether to overwrite existing volume when creating from snapshot.
type: bool
default: 'no'
state:
description:
- Define whether the volume snapshot should exist or not.
choices: [ absent, copy, present ]
type: str
default: present
eradicate:
description:
- Define whether to eradicate the snapshot on delete or leave in trash.
type: bool
default: 'no'
extends_documentation_fragment:
- purestorage.fa
'''
EXAMPLES = r'''
- name: Create snapshot foo.ansible
purefa_snap:
name: foo
suffix: ansible
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: present
- name: Create R/W clone foo_clone from snapshot foo.snap
purefa_snap:
name: foo
suffix: snap
target: foo_clone
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: copy
- name: Overwrite existing volume foo_clone with snapshot foo.snap
purefa_snap:
name: foo
suffix: snap
target: foo_clone
overwrite: true
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: copy
- name: Delete and eradicate snapshot named foo.snap
purefa_snap:
name: foo
suffix: snap
eradicate: true
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: absent
'''
RETURN = r'''
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.pure import get_system, purefa_argument_spec
from datetime import datetime
try:
from purestorage import purestorage
HAS_PURESTORAGE = True
except ImportError:
HAS_PURESTORAGE = False
def get_volume(module, array):
"""Return Volume or None"""
try:
return array.get_volume(module.params['name'])
except Exception:
return None
def get_target(module, array):
"""Return Volume or None"""
try:
return array.get_volume(module.params['target'])
except Exception:
return None
def get_snapshot(module, array):
"""Return Snapshot or None"""
try:
snapname = module.params['name'] + "." + module.params['suffix']
for s in array.get_volume(module.params['name'], snap='true'):
if s['name'] == snapname:
return snapname
except Exception:
return None
def create_snapshot(module, array):
"""Create Snapshot"""
changed = True
if not module.check_mode:
try:
array.create_snapshot(module.params['name'], suffix=module.params['suffix'])
except Exception:
changed = False
module.exit_json(changed=changed)
def create_from_snapshot(module, array):
"""Create Volume from Snapshot"""
source = module.params['name'] + "." + module.params['suffix']
tgt = get_target(module, array)
if tgt is None:
changed = True
if not module.check_mode:
array.copy_volume(source,
module.params['target'])
elif tgt is not None and module.params['overwrite']:
changed = True
if not module.check_mode:
array.copy_volume(source,
module.params['target'],
overwrite=module.params['overwrite'])
elif tgt is not None and not module.params['overwrite']:
changed = False
module.exit_json(changed=changed)
def update_snapshot(module, array):
"""Update Snapshot"""
changed = False
module.exit_json(changed=changed)
def delete_snapshot(module, array):
""" Delete Snapshot"""
changed = True
if not module.check_mode:
snapname = module.params['name'] + "." + module.params['suffix']
try:
array.destroy_volume(snapname)
if module.params['eradicate']:
try:
array.eradicate_volume(snapname)
except Exception:
changed = False
except Exception:
changed = False
module.exit_json(changed=changed)
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(dict(
name=dict(type='str', required=True),
suffix=dict(type='str'),
target=dict(type='str'),
overwrite=dict(type='bool', default=False),
eradicate=dict(type='bool', default=False),
state=dict(type='str', default='present', choices=['absent', 'copy', 'present']),
))
required_if = [('state', 'copy', ['target', 'suffix'])]
module = AnsibleModule(argument_spec,
required_if=required_if,
supports_check_mode=True)
if not HAS_PURESTORAGE:
module.fail_json(msg='purestorage sdk is required for this module in volume')
if module.params['suffix'] is None:
suffix = "snap-" + str((datetime.utcnow() - datetime(1970, 1, 1, 0, 0, 0, 0)).total_seconds())
module.params['suffix'] = suffix.replace(".", "")
state = module.params['state']
array = get_system(module)
volume = get_volume(module, array)
target = get_target(module, array)
snap = get_snapshot(module, array)
if state == 'present' and volume and not snap:
create_snapshot(module, array)
elif state == 'present' and volume and snap:
update_snapshot(module, array)
elif state == 'present' and not volume:
update_snapshot(module, array)
elif state == 'copy' and snap:
create_from_snapshot(module, array)
elif state == 'copy' and not snap:
update_snapshot(module, array)
elif state == 'absent' and snap:
delete_snapshot(module, array)
elif state == 'absent' and not snap:
module.exit_json(changed=False)
if __name__ == '__main__':
main()

@ -1,333 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2019, Simon Dodsley (simon@purestorage.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: purefa_snmp
version_added: '2.9'
short_description: Configure FlashArray SNMP Managers
description:
- Manage SNMP managers on a Pure Storage FlashArray.
- Changing of a named SNMP managers version is not supported.
- This module is not idempotent and will always modify an
existing SNMP manager due to hidden parameters that cannot
be compared to the play parameters.
author:
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
options:
name:
description:
- Name of SNMP Manager
required: True
type: str
state:
description:
- Create or delete SNMP manager
type: str
default: present
choices: [ absent, present ]
auth_passphrase:
type: str
description:
- SNMPv3 only. Passphrase of 8 - 32 characters.
auth_protocol:
type: str
description:
- SNMP v3 only. Hash algorithm to use
choices: [ MD5, SHA ]
community:
type: str
description:
- SNMP v2c only. Manager community ID. Between 1 and 32 characters long.
host:
type: str
description:
- IPv4 or IPv6 address or FQDN to send trap messages to.
required: True
user:
type: str
description:
- SNMP v3 only. User ID recognized by the specified SNMP manager.
Must be between 1 and 32 characters.
version:
type: str
description:
- Version of SNMP protocol to use for the manager.
choices: [ v2c, v3 ]
default: v2c
notification:
type: str
description:
- Action to perform on event.
default: trap
choices: [ inform, trap ]
privacy_passphrase:
type: str
description:
- SNMPv3 only. Passphrase to encrypt SNMP messages.
Must be between 8 and 63 non-space ASCII characters.
privacy_protocol:
type: str
description:
- SNMP v3 only. Encryption protocol to use
choices: [ AES, DES ]
extends_documentation_fragment:
- purestorage.fa
'''
EXAMPLES = r'''
- name: Delete existing SNMP manager
purefa_snmp:
name: manager1
state: absent
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Create v2c SNMP manager
purefa_snmp:
name: manager1
community: public
host: 10.21.22.23
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Create v3 SNMP manager
purefa_snmp:
name: manager2
version: v3
auth_protocol: MD5
auth_passphrase: password
host: 10.21.22.23
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Update existing SNMP manager
purefa_snmp:
name: manager1
community: private
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
'''
RETURN = r'''
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.pure import get_system, purefa_argument_spec
def update_manager(module, array):
"""Update SNMP Manager"""
changed = True
if not module.check_mode:
try:
mgr = array.get_snmp_manager(module.params['name'])
except Exception:
module.fail_json(msg="Failed to get current configuration for SNMP manager {0}.".format(module.params['name']))
if mgr['version'] != module.params['version']:
module.fail_json(msg="Changing an SNMP managers version is not supported.")
elif module.params['version'] == "v2c":
try:
array.set_snmp_manager(module.params['name'],
community=module.params['community'],
notification=module.params['notification'],
host=module.params['host']
)
except Exception:
module.fail_json(msg="Failed to update SNMP manager {0}.".format(module.params['name']))
else:
if module.params['auth_protocol'] and module.params['privacy_protocol']:
try:
array.set_snmp_manager(module.params['name'],
auth_passphrase=module.params['auth_passphrase'],
auth_protocol=module.params['auth_protocol'],
privacy_passphrase=module.params['privacy_passphrase'],
privacy_protocol=module.params['privacy_protocol'],
notification=module.params['notification'],
user=module.params['user'],
host=module.params['host']
)
except Exception:
module.fail_json(msg="Failed to update SNMP manager {0}.".format(module.params['name']))
elif module.params['auth_protocol'] and not module.params['privacy_protocol']:
try:
array.set_snmp_manager(module.params['name'],
version=module.params['version'],
auth_passphrase=module.params['auth_passphrase'],
auth_protocol=module.params['auth_protocol'],
notification=module.params['notification'],
user=module.params['user'],
host=module.params['host']
)
except Exception:
module.fail_json(msg="Failed to update SNMP manager {0}.".format(module.params['name']))
elif not module.params['auth_protocol'] and module.params['privacy_protocol']:
try:
array.set_snmp_manager(module.params['name'],
version=module.params['version'],
privacy_passphrase=module.params['privacy_passphrase'],
privacy_protocol=module.params['privacy_protocol'],
notification=module.params['notification'],
user=module.params['user'],
host=module.params['host']
)
except Exception:
module.fail_json(msg="Failed to update SNMP manager {0}.".format(module.params['name']))
elif not module.params['auth_protocol'] and not module.params['privacy_protocol']:
try:
array.set_snmp_manager(module.params['name'],
version=module.params['version'],
notification=module.params['notification'],
user=module.params['user'],
host=module.params['host']
)
except Exception:
module.fail_json(msg="Failed to update SNMP manager {0}.".format(module.params['name']))
else:
module.fail_json(msg="Invalid parameters selected in update. Please raise issue in Ansible GitHub")
module.exit_json(changed=changed)
def delete_manager(module, array):
"""Delete SNMP Manager"""
changed = True
if not module.check_mode:
try:
array.delete_snmp_manager(module.params['name'])
except Exception:
module.fail_json(msg='Delete SNMP manager {0} failed'.format(module.params['name']))
module.exit_json(changed=changed)
def create_manager(module, array):
"""Create SNMP Manager"""
changed = True
if not module.check_mode:
if module.params['version'] == "v2c":
try:
array.create_snmp_manager(module.params['name'],
version=module.params['version'],
community=module.params['community'],
notification=module.params['notification'],
user=module.params['user'],
host=module.params['host']
)
except Exception:
module.fail_json(msg="Failed to create SNMP manager {0}.".format(module.params['name']))
else:
if module.params['auth_protocol'] and module.params['privacy_protocol']:
try:
array.create_snmp_manager(module.params['name'],
version=module.params['version'],
auth_passphrase=module.params['auth_passphrase'],
auth_protocol=module.params['auth_protocol'],
privacy_passphrase=module.params['privacy_passphrase'],
privacy_protocol=module.params['privacy_protocol'],
notification=module.params['notification'],
user=module.params['user'],
host=module.params['host']
)
except Exception:
module.fail_json(msg="Failed to create SNMP manager {0}.".format(module.params['name']))
elif module.params['auth_protocol'] and not module.params['privacy_protocol']:
try:
array.create_snmp_manager(module.params['name'],
version=module.params['version'],
auth_passphrase=module.params['auth_passphrase'],
auth_protocol=module.params['auth_protocol'],
notification=module.params['notification'],
user=module.params['user'],
host=module.params['host']
)
except Exception:
module.fail_json(msg="Failed to create SNMP manager {0}.".format(module.params['name']))
elif not module.params['auth_protocol'] and module.params['privacy_protocol']:
try:
array.create_snmp_manager(module.params['name'],
version=module.params['version'],
privacy_passphrase=module.params['privacy_passphrase'],
privacy_protocol=module.params['privacy_protocol'],
notification=module.params['notification'],
user=module.params['user'],
host=module.params['host']
)
except Exception:
module.fail_json(msg="Failed to create SNMP manager {0}.".format(module.params['name']))
elif not module.params['auth_protocol'] and not module.params['privacy_protocol']:
try:
array.create_snmp_manager(module.params['name'],
version=module.params['version'],
notification=module.params['notification'],
user=module.params['user'],
host=module.params['host']
)
except Exception:
module.fail_json(msg="Failed to create SNMP manager {0}.".format(module.params['name']))
else:
module.fail_json(msg="Invalid parameters selected in create. Please raise issue in Ansible GitHub")
module.exit_json(changed=changed)
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(dict(
name=dict(type='str', required=True),
host=dict(type='str', required=True),
state=dict(type='str', default='present', choices=['absent', 'present']),
user=dict(type='str'),
notification=dict(type='str', choices=['inform', 'trap'], default='trap'),
auth_passphrase=dict(type='str', no_log=True),
auth_protocol=dict(type='str', choices=['MD5', 'SHA']),
privacy_passphrase=dict(type='str', no_log=True),
privacy_protocol=dict(type='str', choices=['AES', 'DES']),
version=dict(type='str', default='v2c', choices=['v2c', 'v3']),
community=dict(type='str'),
))
required_together = [['auth_passphrase', 'auth_protocol'],
['privacy_passphrase', 'privacy_protocol']]
required_if = [['version', 'v2c', ['community', 'host']],
['version', 'v3', ['host', 'user']]]
module = AnsibleModule(argument_spec,
required_together=required_together,
required_if=required_if,
supports_check_mode=True)
state = module.params['state']
array = get_system(module)
mgr_configured = False
mgrs = array.list_snmp_managers()
for mgr in range(0, len(mgrs)):
if mgrs[mgr]['name'] == module.params['name']:
mgr_configured = True
break
if module.params['version'] == "v3":
if module.params['auth_passphrase'] and (8 > len(module.params['auth_passphrase']) > 32):
module.fail_json(msg="auth_password must be between 8 and 32 characters")
if module.params['privacy_passphrase'] and 8 > len(module.params['privacy_passphrase']) > 63:
module.fail_json(msg="privacy_password must be between 8 and 63 characters")
if state == 'absent' and mgr_configured:
delete_manager(module, array)
elif mgr_configured and state == 'present':
update_manager(module, array)
elif not mgr_configured and state == 'present':
create_manager(module, array)
else:
module.exit_json(changed=False)
if __name__ == '__main__':
main()

@ -1,158 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2018, Simon Dodsley (simon@purestorage.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: purefa_syslog
version_added: '2.9'
short_description: Configure Pure Storage FlashArray syslog settings
description:
- Configure syslog configuration for Pure Storage FlashArrays.
- Add or delete an individual syslog server to the existing
list of serves.
author:
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
options:
state:
description:
- Create or delete syslog servers configuration
default: present
type: str
choices: [ absent, present ]
protocol:
description:
- Protocol which server uses
required: true
type: str
choices: [ tcp, tls, udp ]
port:
description:
- Port at which the server is listening. If no port is specified
the system will use 514
type: str
address:
description:
- Syslog server address.
This field supports IPv4, IPv6 or FQDN.
An invalid IP addresses will cause the module to fail.
No validation is performed for FQDNs.
type: str
required: true
extends_documentation_fragment:
- purestorage.fa
'''
EXAMPLES = r'''
- name: Delete existing syslog server entries
purefa_syslog:
address: syslog1.com
protocol: tcp
state: absent
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Set array syslog servers
purefa_syslog:
state: present
address: syslog1.com
protocol: udp
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
'''
RETURN = r'''
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.pure import get_system, purefa_argument_spec
def delete_syslog(module, array):
"""Delete Syslog Server"""
changed = False
noport_address = module.params['protocol'] + "://" + module.params['address']
if module.params['port']:
full_address = noport_address + ":" + module.params['port']
else:
full_address = noport_address
address_list = array.get(syslogserver=True)['syslogserver']
if address_list:
for address in range(0, len(address_list)):
if address_list[address] == full_address:
del address_list[address]
try:
array.set(syslogserver=address_list)
changed = True
break
except Exception:
module.fail_json(msg='Failed to remove syslog server: {0}'.format(full_address))
module.exit_json(changed=changed)
def add_syslog(module, array):
"""Add Syslog Server"""
changed = False
noport_address = module.params['protocol'] + "://" + module.params['address']
if module.params['port']:
full_address = noport_address + ":" + module.params['port']
else:
full_address = noport_address
address_list = array.get(syslogserver=True)['syslogserver']
exists = False
if address_list:
for address in range(0, len(address_list)):
if address_list[address] == full_address:
exists = True
break
if not exists:
try:
address_list.append(full_address)
array.set(syslogserver=address_list)
changed = True
except Exception:
module.fail_json(msg='Failed to add syslog server: {0}'.format(full_address))
module.exit_json(changed=changed)
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(dict(
address=dict(type='str', required=True),
protocol=dict(type='str', choices=['tcp', 'tls', 'udp'], required=True),
port=dict(type='str'),
state=dict(type='str', default='present', choices=['absent', 'present']),
))
module = AnsibleModule(argument_spec,
supports_check_mode=False)
array = get_system(module)
if module.params['state'] == 'absent':
delete_syslog(module, array)
else:
add_syslog(module, array)
module.exit_json(changed=False)
if __name__ == '__main__':
main()

@ -1,222 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2018, Simon Dodsley (simon@purestorage.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: purefa_user
version_added: '2.8'
short_description: Create, modify or delete FlashArray local user account
description:
- Create, modify or delete local users on a Pure Storage FlashArray.
author:
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
options:
state:
description:
- Create, delete or update local user account
default: present
type: str
choices: [ absent, present ]
name:
description:
- The name of the local user account
type: str
role:
description:
- Sets the local user's access level to the array
type: str
choices: [ readonly, storage_admin, array_admin ]
password:
description:
- Password for the local user.
type: str
old_password:
description:
- If changing an existing password, you must provide the old password for security
type: str
api:
description:
- Define whether to create an API token for this user
- Token can be exposed using the I(debug) module
type: bool
default: false
extends_documentation_fragment:
- purestorage.fa
'''
EXAMPLES = r'''
- name: Create new user ansible with API token
purefa_user:
name: ansible
password: apassword
role: storage_admin
api: true
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
register: result
debug:
msg: "API Token: {{ result['user_info']['user_api'] }}"
- name: Change role type for existing user
purefa_user:
name: ansible
role: array_admin
state: update
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Change password type for existing user (NOT IDEMPOTENT)
purefa_user:
name: ansible
password: anewpassword
old_password: apassword
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Change API token for existing user
purefa_user:
name: ansible
api: true
state: update
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
register: result
debug:
msg: "API Token: {{ result['user_info']['user_api'] }}"
'''
RETURN = r'''
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.pure import get_system, purefa_argument_spec
MIN_REQUIRED_API_VERSION = '1.14'
def get_user(module, array):
"""Return Local User Account or None"""
user = None
users = array.list_admins()
for acct in range(0, len(users)):
if users[acct]['name'] == module.params['name']:
user = users[acct]
return user
def create_user(module, array):
"""Create or Update Local User Account"""
changed = False
user = get_user(module, array)
role = module.params['role']
api_changed = False
role_changed = False
passwd_changed = False
user_token = {}
if not user:
try:
if not role:
role = 'readonly'
array.create_admin(module.params['name'], role=role,
password=module.params['password'])
if module.params['api']:
try:
user_token['user_api'] = array.create_api_token(module.params['name'])['api_token']
except Exception:
array.delete_user(module.params['name'])
module.fail_json(msg='Local User {0}: Creation failed'.format(module.params['name']))
changed = True
except Exception:
module.fail_json(msg='Local User {0}: Creation failed'.format(module.params['name']))
else:
if module.params['password'] and not module.params['old_password']:
changed = False
module.exit_json(changed=changed)
if module.params['password'] and module.params['old_password']:
if module.params['old_password'] and (module.params['password'] != module.params['old_password']):
try:
array.set_admin(module.params['name'], password=module.params['password'],
old_password=module.params['old_password'])
passwd_changed = True
except Exception:
module.fail_json(msg='Local User {0}: Password reset failed. '
'Check old password.'.format(module.params['name']))
else:
module.fail_json(msg='Local User Account {0}: Password change failed - '
'Check both old and new passwords'.format(module.params['name']))
if module.params['api']:
try:
if not array.get_api_token(module.params['name'])['api_token'] is None:
array.delete_api_token(module.params['name'])
user_token['user_api'] = array.create_api_token(module.params['name'])['api_token']
api_changed = True
except Exception:
module.fail_json(msg='Local User {0}: API token change failed'.format(module.params['name']))
if module.params['role'] != user['role']:
try:
array.set_admin(module.params['name'], role=module.params['role'])
role_changed = True
except Exception:
module.fail_json(msg='Local User {0}: Role changed failed'.format(module.params['name']))
if passwd_changed or role_changed or api_changed:
changed = True
module.exit_json(changed=changed, user_info=user_token)
def delete_user(module, array):
"""Delete Local User Account"""
changed = False
if get_user(module, array):
try:
array.delete_admin(module.params['name'])
changed = True
except Exception:
module.fail_json(msg='Object Store Account {0}: Deletion failed'.format(module.params['name']))
module.exit_json(changed=changed)
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(dict(
name=dict(required=True, type='str'),
role=dict(type='str', choices=['readonly', 'storage_admin', 'array_admin']),
state=dict(type='str', default='present', choices=['absent', 'present']),
password=dict(type='str', no_log=True),
old_password=dict(type='str', no_log=True),
api=dict(type='bool', default=False),
))
module = AnsibleModule(argument_spec,
supports_check_mode=False)
state = module.params['state']
array = get_system(module)
api_version = array._list_available_rest_versions()
if MIN_REQUIRED_API_VERSION not in api_version:
module.fail_json(msg='FlashArray REST version not supported. '
'Minimum version required: {0}'.format(MIN_REQUIRED_API_VERSION))
if state == 'absent':
delete_user(module, array)
elif state == 'present':
create_user(module, array)
else:
module.exit_json(changed=False)
if __name__ == '__main__':
main()

@ -1,189 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2017, Simon Dodsley (simon@purestorage.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: purefa_vg
version_added: '2.9'
short_description: Manage volume groups on Pure Storage FlashArrays
description:
- Create, delete or modify volume groups on Pure Storage FlashArrays.
author:
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
options:
vgroup:
description:
- The name of the volume group.
type: str
required: true
state:
description:
- Define whether the volume group should exist or not.
type: str
default: present
choices: [ absent, present ]
eradicate:
description:
- Define whether to eradicate the volume group on delete and leave in trash.
type : bool
default: 'no'
extends_documentation_fragment:
- purestorage.fa
'''
EXAMPLES = r'''
- name: Create new volume group
purefa_vg:
vgroup: foo
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Destroy volume group
purefa_vg:
vgroup: foo
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: absent
- name: Recover deleted volume group
purefa_vg:
vgroup: foo
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Destroy and Eradicate volume group
purefa_vg:
vgroup: foo
eradicate: true
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: absent
'''
RETURN = r'''
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.pure import get_system, purefa_argument_spec
VGROUP_API_VERSION = '1.13'
def get_pending_vgroup(module, array):
""" Get Deleted Volume Group"""
vgroup = None
for vgrp in array.list_vgroups(pending=True):
if vgrp["name"] == module.params['vgroup'] and vgrp['time_remaining']:
vgroup = vgrp
break
return vgroup
def get_vgroup(module, array):
""" Get Volume Group"""
vgroup = None
for vgrp in array.list_vgroups():
if vgrp["name"] == module.params['vgroup']:
vgroup = vgrp
break
return vgroup
def make_vgroup(module, array):
""" Create Volume Group"""
changed = True
if not module.check_mode:
try:
array.create_vgroup(module.params['vgroup'])
except Exception:
module.fail_json(msg='creation of volume group {0} failed.'.format(module.params['vgroup']))
module.exit_json(changed=changed)
def recover_vgroup(module, array):
""" Recover Volume Group"""
changed = True
if not module.check_mode:
try:
array.recover_vgroup(module.params['vgroup'])
except Exception:
module.fail_json(msg='Recovery of volume group {0} failed.'.format(module.params['vgroup']))
module.exit_json(changed=changed)
def eradicate_vgroup(module, array):
""" Eradicate Volume Group"""
changed = True
if not module.check_mode:
try:
array.eradicate_vgroup(module.params['vgroup'])
except Exception:
module.fail_json(msg='Eradicating vgroup {0} failed.'.format(module.params['vgroup']))
module.exit_json(changed=changed)
def delete_vgroup(module, array):
""" Delete Volume Group"""
changed = True
if not module.check_mode:
try:
array.destroy_vgroup(module.params['vgroup'])
except Exception:
module.fail_json(msg='Deleting vgroup {0} failed.'.format(module.params['vgroup']))
if module.params['eradicate']:
eradicate_vgroup(module, array)
module.exit_json(changed=changed)
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(dict(
vgroup=dict(type='str', required=True),
state=dict(type='str', default='present', choices=['absent', 'present']),
eradicate=dict(type='bool', default=False),
))
module = AnsibleModule(argument_spec,
supports_check_mode=True)
state = module.params['state']
array = get_system(module)
api_version = array._list_available_rest_versions()
if VGROUP_API_VERSION not in api_version:
module.fail_json(msg='API version does not support volume groups.')
vgroup = get_vgroup(module, array)
xvgroup = get_pending_vgroup(module, array)
if xvgroup and state == 'present':
recover_vgroup(module, array)
elif vgroup and state == 'absent':
delete_vgroup(module, array)
elif xvgroup and state == 'absent' and module.params['eradicate']:
eradicate_vgroup(module, array)
elif not vgroup and not xvgroup and state == 'present':
make_vgroup(module, array)
elif vgroup is None and state == 'absent':
module.exit_json(changed=False)
module.exit_json(changed=False)
if __name__ == '__main__':
main()

@ -1,498 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2018, Simon Dodsley (simon@purestorage.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = r'''
---
module: purefa_volume
version_added: '2.4'
short_description: Manage volumes on Pure Storage FlashArrays
description:
- Create, delete or extend the capacity of a volume on Pure Storage FlashArray.
author:
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
options:
name:
description:
- The name of the volume.
type: str
required: true
target:
description:
- The name of the target volume, if copying.
type: str
state:
description:
- Define whether the volume should exist or not.
default: present
choices: [ absent, present ]
type: str
eradicate:
description:
- Define whether to eradicate the volume on delete or leave in trash.
type: bool
default: 'no'
overwrite:
description:
- Define whether to overwrite a target volume if it already exists.
type: bool
default: 'no'
size:
description:
- Volume size in M, G, T or P units.
type: str
bw_qos:
description:
- Bandwidth limit for volume in M or G units.
M will set MB/s
G will set GB/s
To clear an existing QoS setting use 0 (zero)
version_added: '2.8'
type: str
aliases: [ qos ]
iops_qos:
description:
- IOPs limit for volume - use value or K or M
K will mean 1000
M will mean 1000000
To clear an existing IOPs setting use 0 (zero)
version_added: '2.10'
type: str
extends_documentation_fragment:
- purestorage.fa
'''
EXAMPLES = r'''
- name: Create new volume named foo with a QoS limit
purefa_volume:
name: foo
size: 1T
bw_qos: 58M
iops_qos: 23K
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: present
- name: Extend the size of an existing volume named foo
purefa_volume:
name: foo
size: 2T
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: present
- name: Delete and eradicate volume named foo
purefa_volume:
name: foo
eradicate: yes
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: absent
- name: Create clone of volume bar named foo
purefa_volume:
name: foo
target: bar
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: present
- name: Overwrite volume bar with volume foo
purefa_volume:
name: foo
target: bar
overwrite: yes
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: present
- name: Clear volume QoS from volume foo
purefa_volume:
name: foo
bw_qos: 0
iops_qos: 0
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
state: present
'''
RETURN = r'''
volume:
description: A dictionary describing the changed volume. Only some
attributes below will be returned with various actions.
type: dict
returned: success
contains:
source:
description: Volume name of source volume used for volume copy
type: str
serial:
description: Volume serial number
type: str
sample: '361019ECACE43D83000120A4'
created:
description: Volume creation time
type: str
sample: '2019-03-13T22:49:24Z'
name:
description: Volume name
type: str
size:
description: Volume size in bytes
type: int
bandwidth_limit:
description: Volume bandwidth limit in bytes/sec
type: int
iops_limit:
description: Volume IOPs limit
type: int
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.pure import get_system, purefa_argument_spec
QOS_API_VERSION = "1.14"
VGROUPS_API_VERSION = "1.13"
POD_API_VERSION = "1.13"
IOPS_API_VERSION = "1.17"
def human_to_bytes(size):
"""Given a human-readable byte string (e.g. 2G, 30M),
return the number of bytes. Will return 0 if the argument has
unexpected form.
"""
bytes = size[:-1]
unit = size[-1]
if bytes.isdigit():
bytes = int(bytes)
if unit == 'P':
bytes *= 1125899906842624
elif unit == 'T':
bytes *= 1099511627776
elif unit == 'G':
bytes *= 1073741824
elif unit == 'M':
bytes *= 1048576
elif unit == 'K':
bytes *= 1024
else:
bytes = 0
else:
bytes = 0
return bytes
def human_to_real(iops):
"""Given a human-readable IOPs string (e.g. 2K, 30M),
return the real number. Will return 0 if the argument has
unexpected form.
"""
digit = iops[:-1]
unit = iops[-1]
if digit.isdigit():
digit = int(digit)
if unit == 'M':
digit *= 1000000
elif unit == 'K':
digit *= 1000
else:
digit = 0
else:
digit = 0
return digit
def get_volume(module, array):
"""Return Volume or None"""
try:
return array.get_volume(module.params['name'])
except Exception:
return None
def get_destroyed_volume(module, array):
"""Return Destroyed Volume or None"""
try:
return bool(array.get_volume(module.params['name'], pending=True)['time_remaining'] != '')
except Exception:
return False
def get_target(module, array):
"""Return Volume or None"""
try:
return array.get_volume(module.params['target'])
except Exception:
return None
def check_vgroup(module, array):
"""Check is the requested VG to create volume in exists"""
vg_exists = False
api_version = array._list_available_rest_versions()
if VGROUPS_API_VERSION in api_version:
vg_name = module.params["name"].split("/")[0]
try:
vgs = array.list_vgroups()
except Exception:
module.fail_json(msg="Failed to get volume groups list. Check array.")
for vgroup in range(0, len(vgs)):
if vg_name == vgs[vgroup]['name']:
vg_exists = True
break
else:
module.fail_json(msg="VG volumes are not supported. Please upgrade your FlashArray.")
return vg_exists
def check_pod(module, array):
"""Check is the requested pod to create volume in exists"""
pod_exists = False
api_version = array._list_available_rest_versions()
if POD_API_VERSION in api_version:
pod_name = module.params["name"].split("::")[0]
try:
pods = array.list_pods()
except Exception:
module.fail_json(msg="Failed to get pod list. Check array.")
for pod in range(0, len(pods)):
if pod_name == pods[pod]['name']:
pod_exists = True
break
else:
module.fail_json(msg="Pod volumes are not supported. Please upgrade your FlashArray.")
return pod_exists
def create_volume(module, array):
"""Create Volume"""
changed = True
volfact = []
if not module.check_mode:
if "/" in module.params['name'] and not check_vgroup(module, array):
module.fail_json(msg="Failed to create volume {0}. Volume Group does not exist.".format(module.params["name"]))
if "::" in module.params['name'] and not check_pod(module, array):
module.fail_json(msg="Failed to create volume {0}. Poid does not exist".format(module.params["name"]))
api_version = array._list_available_rest_versions()
if module.params['bw_qos'] or module.params['iops_qos']:
if module.params['bw_qos'] and QOS_API_VERSION in api_version or module.params['iops_qos'] and IOPS_API_VERSION in api_version:
if module.params['bw_qos'] and not module.params['iops_qos']:
if 549755813888 >= int(human_to_bytes(module.params['bw_qos'])) >= 1048576:
try:
volfact = array.create_volume(module.params['name'],
module.params['size'],
bandwidth_limit=module.params['bw_qos'])
except Exception:
module.fail_json(msg='Volume {0} creation failed.'.format(module.params['name']))
else:
module.fail_json(msg='Bandwidth QoS value {0} out of range.'.format(module.params['bw_qos']))
elif module.params['iops_qos'] and not module.params['bw_qos']:
if 100000000 >= int(human_to_real(module.params['iops_qos'])) >= 100:
try:
volfact = array.create_volume(module.params['name'],
module.params['size'],
iops_limit=module.params['iops_qos'])
except Exception:
module.fail_json(msg='Volume {0} creation failed.'.format(module.params['name']))
else:
module.fail_json(msg='IOPs QoS value {0} out of range.'.format(module.params['iops_qos']))
else:
bw_qos_size = int(human_to_bytes(module.params['bw_qos']))
if 100000000 >= int(human_to_real(module.params['iops_qos'])) >= 100 and 549755813888 >= bw_qos_size >= 1048576:
try:
volfact = array.create_volume(module.params['name'],
module.params['size'],
iops_limit=module.params['iops_qos'],
bandwidth_limit=module.params['bw_qos'])
except Exception:
module.fail_json(msg='Volume {0} creation failed.'.format(module.params['name']))
else:
module.fail_json(msg='IOPs or Bandwidth QoS value out of range.')
else:
try:
volfact = array.create_volume(module.params['name'], module.params['size'])
except Exception:
module.fail_json(msg='Volume {0} creation failed.'.format(module.params['name']))
module.exit_json(changed=changed, volume=volfact)
def copy_from_volume(module, array):
"""Create Volume Clone"""
changed = True
volfact = []
if not module.check_mode:
tgt = get_target(module, array)
if tgt is None:
try:
volfact = array.copy_volume(module.params['name'],
module.params['target'])
except Exception:
module.fail_json(msg='Copy volume {0} to volume {1} failed.'.format(module.params['name'],
module.params['target']))
elif tgt is not None and module.params['overwrite']:
try:
volfact = array.copy_volume(module.params['name'],
module.params['target'],
overwrite=module.params['overwrite'])
except Exception:
module.fail_json(msg='Copy volume {0} to volume {1} failed.'.format(module.params['name'],
module.params['target']))
module.exit_json(changed=changed, volume=volfact)
def update_volume(module, array):
"""Update Volume size and/or QoS"""
changed = True
volfact = []
if not module.check_mode:
change = False
api_version = array._list_available_rest_versions()
vol = array.get_volume(module.params['name'])
vol_qos = array.get_volume(module.params['name'], qos=True)
if QOS_API_VERSION in api_version:
if vol_qos['bandwidth_limit'] is None:
vol_qos['bandwidth_limit'] = 0
if IOPS_API_VERSION in api_version:
if vol_qos['iops_limit'] is None:
vol_qos['iops_limit'] = 0
if module.params['size']:
if human_to_bytes(module.params['size']) != vol['size']:
if human_to_bytes(module.params['size']) > vol['size']:
try:
volfact = array.extend_volume(module.params['name'], module.params['size'])
change = True
except Exception:
module.fail_json(msg='Volume {0} resize failed.'.format(module.params['name']))
if module.params['bw_qos'] and QOS_API_VERSION in api_version:
if human_to_bytes(module.params['bw_qos']) != vol_qos['bandwidth_limit']:
if module.params['bw_qos'] == '0':
try:
volfact = array.set_volume(module.params['name'], bandwidth_limit='')
change = True
except Exception:
module.fail_json(msg='Volume {0} Bandwidth QoS removal failed.'.format(module.params['name']))
elif 549755813888 >= int(human_to_bytes(module.params['bw_qos'])) >= 1048576:
try:
volfact = array.set_volume(module.params['name'],
bandwidth_limit=module.params['bw_qos'])
change = True
except Exception:
module.fail_json(msg='Volume {0} Bandwidth QoS change failed.'.format(module.params['name']))
else:
module.fail_json(msg='Bandwidth QoS value {0} out of range.'.format(module.params['bw_qos']))
if module.params['iops_qos'] and IOPS_API_VERSION in api_version:
if human_to_real(module.params['iops_qos']) != vol_qos['iops_limit']:
if module.params['iops_qos'] == '0':
try:
volfact = array.set_volume(module.params['name'], iops_limit='')
change = True
except Exception:
module.fail_json(msg='Volume {0} IOPs QoS removal failed.'.format(module.params['name']))
elif 100000000 >= int(human_to_real(module.params['iops_qos'])) >= 100:
try:
volfact = array.set_volume(module.params['name'],
iops_limit=module.params['iops_qos'])
except Exception:
module.fail_json(msg='Volume {0} IOPs QoS change failed.'.format(module.params['name']))
else:
module.fail_json(msg='Bandwidth QoS value {0} out of range.'.format(module.params['bw_qos']))
module.exit_json(changed=change, volume=volfact)
module.exit_json(changed=changed)
def delete_volume(module, array):
""" Delete Volume"""
changed = True
volfact = []
if not module.check_mode:
try:
array.destroy_volume(module.params['name'])
if module.params['eradicate']:
try:
volfact = array.eradicate_volume(module.params['name'])
except Exception:
module.fail_json(msg='Eradicate volume {0} failed.'.format(module.params['name']))
except Exception:
module.fail_json(msg='Delete volume {0} failed.'.format(module.params['name']))
module.exit_json(changed=changed, volume=volfact)
def eradicate_volume(module, array):
""" Eradicate Deleted Volume"""
changed = True
volfact = []
if not module.check_mode:
if module.params['eradicate']:
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, volume=volfact)
def main():
argument_spec = purefa_argument_spec()
argument_spec.update(dict(
name=dict(type='str', required=True),
target=dict(type='str'),
overwrite=dict(type='bool', default=False),
eradicate=dict(type='bool', default=False),
state=dict(type='str', default='present', choices=['absent', 'present']),
bw_qos=dict(type='str', aliases=['qos']),
iops_qos=dict(type='str'),
size=dict(type='str'),
))
mutually_exclusive = [['size', 'target'], ['qos', 'target']]
module = AnsibleModule(argument_spec,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True)
size = module.params['size']
bw_qos = module.params['bw_qos']
iops_qos = module.params['iops_qos']
state = module.params['state']
array = get_system(module)
volume = get_volume(module, array)
if not volume:
destroyed = get_destroyed_volume(module, array)
target = get_target(module, array)
if state == 'present' and not volume and size:
create_volume(module, array)
elif state == 'present' and volume and (size or bw_qos or iops_qos):
update_volume(module, array)
elif state == 'present' and volume and target:
copy_from_volume(module, array)
elif state == 'present' and volume and not target:
copy_from_volume(module, array)
elif state == 'absent' and volume:
delete_volume(module, array)
elif state == 'absent' and destroyed:
eradicate_volume(module, array)
elif state == 'present' and not volume or not size:
module.exit_json(changed=False)
elif state == 'absent' and not volume:
module.exit_json(changed=False)
if __name__ == '__main__':
main()

@ -993,40 +993,6 @@ lib/ansible/modules/source_control/git.py validate-modules:parameter-type-not-in
lib/ansible/modules/source_control/subversion.py validate-modules:doc-required-mismatch
lib/ansible/modules/source_control/subversion.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/source_control/subversion.py validate-modules:undocumented-parameter
lib/ansible/modules/storage/purestorage/purefa_alert.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/purestorage/purefa_arrayname.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/purestorage/purefa_banner.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/purestorage/purefa_connect.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/purestorage/purefa_dns.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/purestorage/purefa_dns.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/purestorage/purefa_ds.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/purestorage/purefa_ds.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/purestorage/purefa_dsrole.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/purestorage/purefa_dsrole.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/purestorage/purefa_hg.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/purestorage/purefa_hg.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/purestorage/purefa_host.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/purestorage/purefa_host.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/purestorage/purefa_info.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/purestorage/purefa_info.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/purestorage/purefa_info.py validate-modules:return-syntax-error
lib/ansible/modules/storage/purestorage/purefa_ntp.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/purestorage/purefa_ntp.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/purestorage/purefa_offload.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/purestorage/purefa_pg.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/purestorage/purefa_pg.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/purestorage/purefa_pgsnap.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/purestorage/purefa_pgsnap.py validate-modules:parameter-type-not-in-doc
lib/ansible/modules/storage/purestorage/purefa_phonehome.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/purestorage/purefa_ra.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/purestorage/purefa_smtp.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/purestorage/purefa_snap.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/purestorage/purefa_snmp.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/purestorage/purefa_syslog.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/purestorage/purefa_user.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/purestorage/purefa_vg.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/purestorage/purefa_volume.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/purestorage/purefa_volume.py validate-modules:mutually_exclusive-unknown
lib/ansible/modules/storage/purestorage/purefb_ds.py validate-modules:doc-required-mismatch
lib/ansible/modules/storage/purestorage/purefb_ds.py validate-modules:parameter-list-no-elements
lib/ansible/modules/storage/purestorage/purefb_dsrole.py validate-modules:doc-required-mismatch

Loading…
Cancel
Save