1#!/usr/bin/python 2# -*- coding: utf-8 -*- 3 4# (c) 2019, Simon Dodsley (simon@purestorage.com) 5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 7from __future__ import absolute_import, division, print_function 8__metaclass__ = type 9 10ANSIBLE_METADATA = {'metadata_version': '1.1', 11 'status': ['preview'], 12 'supported_by': 'community'} 13 14DOCUMENTATION = r''' 15--- 16module: purefa_offload 17version_added: '2.8' 18short_description: Create, modify and delete NFS or S3 offload targets 19description: 20- Create, modify and delete NFS or S3 offload targets. 21- Only supported on Purity v5.2.0 or higher. 22- You must have a correctly configured offload network for offload to work. 23author: 24- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com> 25options: 26 state: 27 description: 28 - Define state of offload 29 default: present 30 choices: [ absent, present ] 31 type: str 32 name: 33 description: 34 - The name of the offload target 35 required: true 36 type: str 37 protocol: 38 description: 39 - Define which protocol the offload engine uses 40 default: nfs 41 choices: [ nfs, s3 ] 42 type: str 43 address: 44 description: 45 - The IP or FQDN address of the NFS server 46 type: str 47 share: 48 description: 49 - NFS export on the NFS server 50 type: str 51 options: 52 description: 53 - Additional mount options for the NFS share 54 - Supported mount options include I(port), I(rsize), 55 I(wsize), I(nfsvers), and I(tcp) or I(udp) 56 required: false 57 default: "" 58 type: str 59 access_key: 60 description: 61 - Access Key ID of the S3 target 62 type: str 63 bucket: 64 description: 65 - Name of the bucket for the S3 target 66 type: str 67 secret: 68 description: 69 - Secret Access Key for the S3 target 70 type: str 71 initialize: 72 description: 73 - Define whether to initialize the S3 bucket 74 type: bool 75 default: true 76 77extends_documentation_fragment: 78- purestorage.fa 79''' 80 81EXAMPLES = r''' 82- name: Create NFS offload target 83 purefa_offload: 84 name: nfs-offload 85 protocol: nfs 86 address: 10.21.200.4 87 share: "/offload_target" 88 fa_url: 10.10.10.2 89 api_token: e31060a7-21fc-e277-6240-25983c6c4592 90 91- name: Create S3 offload target 92 purefa_offload: 93 name: s3-offload 94 protocol: s3 95 access_key: "3794fb12c6204e19195f" 96 bucket: offload-bucket 97 secret: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" 98 fa_url: 10.10.10.2 99 api_token: e31060a7-21fc-e277-6240-25983c6c4592 100 101- name: Delete offload target 102 purefa_offload: 103 name: nfs-offload 104 protocol: nfs 105 state: absent 106 fa_url: 10.10.10.2 107 api_token: e31060a7-21fc-e277-6240-25983c6c4592 108 109 110''' 111 112RETURN = r''' 113''' 114 115import re 116from distutils.version import LooseVersion 117 118from ansible.module_utils.basic import AnsibleModule 119from ansible.module_utils.pure import get_system, purefa_argument_spec 120 121MIN_REQUIRED_API_VERSION = '1.16' 122REGEX_TARGET_NAME = re.compile(r"^[a-zA-Z0-9\-]*$") 123 124 125def get_target(module, array): 126 """Return target or None""" 127 try: 128 return array.get_offload(module.params['name']) 129 except Exception: 130 return None 131 132 133def create_offload(module, array): 134 """Create offload target""" 135 changed = False 136 # First check if the offload network interface is there and enabled 137 try: 138 if not array.get_network_interface('@offload.data')['enabled']: 139 module.fail_json(msg='Offload Network interface not enabled. Please resolve.') 140 except Exception: 141 module.fail_json(msg='Offload Network interface not correctly configured. Please resolve.') 142 if module.params['protocol'] == 'nfs': 143 try: 144 array.connect_nfs_offload(module.params['name'], 145 mount_point=module.params['share'], 146 address=module.params['address'], 147 mount_options=module.params['options']) 148 changed = True 149 except Exception: 150 module.fail_json(msg='Failed to create NFS offload {0}. ' 151 'Please perform diagnostic checks.'.format(module.params['name'])) 152 if module.params['protocol'] == 's3': 153 try: 154 array.connect_s3_offload(module.params['name'], 155 access_key_id=module.params['access_key'], 156 secret_access_key=module.params['secret'], 157 bucket=module.params['bucket'], 158 initialize=module.params['initialize']) 159 changed = True 160 except Exception: 161 module.fail_json(msg='Failed to create S3 offload {0}. ' 162 'Please perform diagnostic checks.'.format(module.params['name'])) 163 module.exit_json(changed=changed) 164 165 166def update_offload(module, array): 167 """Update offload target""" 168 changed = False 169 module.exit_json(changed=changed) 170 171 172def delete_offload(module, array): 173 """Delete offload target""" 174 changed = False 175 if module.params['protocol'] == 'nfs': 176 try: 177 array.disconnect_nfs_offload(module.params['name']) 178 changed = True 179 except Exception: 180 module.fail_json(msg='Failed to delete NFS offload {0}.'.format(module.params['name'])) 181 if module.params['protocol'] == 's3': 182 try: 183 array.disconnect_nfs_offload(module.params['name']) 184 changed = True 185 except Exception: 186 module.fail_json(msg='Failed to delete S3 offload {0}.'.format(module.params['name'])) 187 module.exit_json(changed=changed) 188 189 190def main(): 191 argument_spec = purefa_argument_spec() 192 argument_spec.update(dict( 193 state=dict(type='str', default='present', choices=['present', 'absent']), 194 protocol=dict(type='str', default='nfs', choices=['nfs', 's3']), 195 name=dict(type='str', required=True), 196 initialize=dict(default=True, type='bool'), 197 access_key=dict(type='str'), 198 secret=dict(type='str', no_log=True), 199 bucket=dict(type='str'), 200 share=dict(type='str'), 201 address=dict(type='str'), 202 options=dict(type='str', default=''), 203 )) 204 205 required_if = [] 206 207 if argument_spec['state'] == "present": 208 required_if = [ 209 ('protocol', 'nfs', ['address', 'share']), 210 ('protocol', 's3', ['access_key', 'secret', 'bucket']) 211 ] 212 213 module = AnsibleModule(argument_spec, 214 required_if=required_if, 215 supports_check_mode=False) 216 217 array = get_system(module) 218 api_version = array._list_available_rest_versions() 219 220 if MIN_REQUIRED_API_VERSION not in api_version: 221 module.fail_json(msg='FlashArray REST version not supported. ' 222 'Minimum version required: {0}'.format(MIN_REQUIRED_API_VERSION)) 223 224 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: 225 module.fail_json(msg='Target name invalid. ' 226 'Target name must be between 1 and 56 characters (alphanumeric and -) in length ' 227 'and begin and end with a letter or number. The name must include at least one letter.') 228 if module.params['protocol'] == "s3": 229 if not re.match(r"^[a-z0-9][a-z0-9.\-]*[a-z0-9]$", module.params['bucket']) or len(module.params['bucket']) > 63: 230 module.fail_json(msg='Bucket name invalid. ' 231 'Bucket name must be between 3 and 63 characters ' 232 '(ilowercase, alphanumeric, dash or period) in length ' 233 'and begin and end with a letter or number.') 234 235 apps = array.list_apps() 236 app_version = 0 237 all_good = False 238 for app in range(0, len(apps)): 239 if apps[app]['name'] == 'offload': 240 if (apps[app]['enabled'] and 241 apps[app]['status'] == 'healthy' and 242 LooseVersion(apps[app]['version']) >= LooseVersion('5.2.0')): 243 all_good = True 244 app_version = apps[app]['version'] 245 break 246 247 if not all_good: 248 module.fail_json(msg='Correct Offload app not installed or incorrectly configured') 249 else: 250 if LooseVersion(array.get()['version']) != LooseVersion(app_version): 251 module.fail_json(msg='Offload app version must match Purity version. Please upgrade.') 252 253 target = get_target(module, array) 254 if module.params['state'] == 'present' and not target: 255 target_count = len(array.list_offload()) 256 # Currently only 1 offload target is supported 257 # TODO: (SD) when more targets supported add in REST version check as well 258 if target_count != 0: 259 module.fail_json(msg='Currently only 1 Offload Target is supported.') 260 create_offload(module, array) 261 elif module.params['state'] == 'present' and target: 262 update_offload(module, array) 263 elif module.params['state'] == 'absent' and target: 264 delete_offload(module, array) 265 266 module.exit_json(changed=False) 267 268 269if __name__ == '__main__': 270 main() 271