diff options
Diffstat (limited to 'ansible_collections/purestorage/fusion/plugins')
20 files changed, 310 insertions, 85 deletions
diff --git a/ansible_collections/purestorage/fusion/plugins/module_utils/errors.py b/ansible_collections/purestorage/fusion/plugins/module_utils/errors.py index 0edf364cf..f3d574edc 100644 --- a/ansible_collections/purestorage/fusion/plugins/module_utils/errors.py +++ b/ansible_collections/purestorage/fusion/plugins/module_utils/errors.py @@ -162,7 +162,7 @@ def format_failed_fusion_operation_exception(exception): if not code: code = error.http_code operation_name = op.request_type - except Exception as e: + except Exception: pass output = "" diff --git a/ansible_collections/purestorage/fusion/plugins/module_utils/parsing.py b/ansible_collections/purestorage/fusion/plugins/module_utils/parsing.py index a2cd75245..1bcb8b812 100644 --- a/ansible_collections/purestorage/fusion/plugins/module_utils/parsing.py +++ b/ansible_collections/purestorage/fusion/plugins/module_utils/parsing.py @@ -11,7 +11,7 @@ __metaclass__ = type METRIC_SUFFIXES = ["K", "M", "G", "T", "P"] duration_pattern = re.compile( - r"^((?P<Y>[1-9]\d*)Y)?((?P<W>[1-9]\d*)W)?((?P<D>[1-9]\d*)D)?(((?P<H>[1-9]\d*)H)?((?P<M>[1-9]\d*)M)?)?$" + r"^((?P<Y>\d+)Y)?((?P<W>\d+)W)?((?P<D>\d+)D)?(((?P<H>\d+)H)?((?P<M>\d+)M)?)?$" ) duration_transformation = { "Y": 365 * 24 * 60, diff --git a/ansible_collections/purestorage/fusion/plugins/module_utils/prerequisites.py b/ansible_collections/purestorage/fusion/plugins/module_utils/prerequisites.py index a4edaf341..db00a9c6f 100644 --- a/ansible_collections/purestorage/fusion/plugins/module_utils/prerequisites.py +++ b/ansible_collections/purestorage/fusion/plugins/module_utils/prerequisites.py @@ -136,7 +136,7 @@ def _check_import(ansible_module, module, package=None, version_requirements=Non :param version_requirements: a string, version requirements for 'package' """ try: - mod = importlib.import_module(module) + importlib.import_module(module) except ImportError: ansible_module.fail_json( msg="Error: Python package '{0}' required and missing".format(module) diff --git a/ansible_collections/purestorage/fusion/plugins/module_utils/snapshots.py b/ansible_collections/purestorage/fusion/plugins/module_utils/snapshots.py new file mode 100644 index 000000000..ed34c1c0e --- /dev/null +++ b/ansible_collections/purestorage/fusion/plugins/module_utils/snapshots.py @@ -0,0 +1,29 @@ +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +try: + import fusion as purefusion +except ImportError: + pass + +from ansible_collections.purestorage.fusion.plugins.module_utils.operations import ( + await_operation, +) + + +def delete_snapshot(fusion, snap, snapshots_api): + patch = purefusion.SnapshotPatch(destroyed=purefusion.NullableBoolean(True)) + op = snapshots_api.update_snapshot( + body=patch, + tenant_name=snap.tenant.name, + tenant_space_name=snap.tenant_space.name, + snapshot_name=snap.name, + ) + await_operation(fusion, op) + op = snapshots_api.delete_snapshot( + tenant_name=snap.tenant.name, + tenant_space_name=snap.tenant_space.name, + snapshot_name=snap.name, + ) + await_operation(fusion, op) diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_api_client.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_api_client.py index 39860449d..42254338f 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_api_client.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_api_client.py @@ -99,14 +99,15 @@ def create_client(module, fusion): id_api_instance = purefusion.IdentityManagerApi(fusion) changed = True + id = None if not module.check_mode: client = purefusion.APIClientPost( public_key=module.params["public_key"], display_name=module.params["name"], ) - id_api_instance.create_api_client(client) - - module.exit_json(changed=changed) + res = id_api_instance.create_api_client(client) + id = res.id + module.exit_json(changed=changed, id=id) def main(): @@ -129,8 +130,8 @@ def main(): create_client(module, fusion) elif client_id is not None and state == "absent": delete_client(module, fusion, client_id) - else: - module.exit_json(changed=False) + if client_id is not None: + module.exit_json(changed=False, id=client_id) module.exit_json(changed=False) diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_array.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_array.py index f7933eabe..ec94d616f 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_array.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_array.py @@ -60,6 +60,10 @@ options: description: - Appliance ID of the array. type: str + apartment_id: + description: + - The Apartment ID of the Array. + type: str maintenance_mode: description: - "Switch the array into maintenance mode or back. @@ -123,6 +127,7 @@ def create_array(module, fusion): """Create Array""" array_api_instance = purefusion.ArraysApi(fusion) + id = None if not module.check_mode: if not module.params["display_name"]: @@ -135,14 +140,17 @@ def create_array(module, fusion): host_name=module.params["host_name"], name=module.params["name"], appliance_id=module.params["appliance_id"], + apartment_id=module.params["apartment_id"], ) res = array_api_instance.create_array( array, availability_zone_name=module.params["availability_zone"], region_name=module.params["region"], ) - await_operation(fusion, res) - return True + res_op = await_operation(fusion, res) + id = res_op.result.resource.id + + return True, id def update_array(module, fusion): @@ -222,6 +230,7 @@ def main(): availability_zone=dict(type="str", required=True, aliases=["az"]), display_name=dict(type="str"), region=dict(type="str", required=True), + apartment_id=dict(type="str"), appliance_id=dict(type="str"), host_name=dict(type="str"), hardware_type=dict( @@ -246,17 +255,24 @@ def main(): array = get_array(module, fusion) changed = False + id = None + if array is not None: + id = array.id + if not array and state == "present": module.fail_on_missing_params(["hardware_type", "host_name", "appliance_id"]) - changed = create_array(module, fusion) | update_array( + changed, id = create_array(module, fusion) + update_array( module, fusion ) # update is run to set properties which cannot be set on creation and instead use defaults elif array and state == "present": - changed = changed | update_array(module, fusion) + changed = update_array(module, fusion) elif array and state == "absent": changed = changed | delete_array(module, fusion) - else: - module.exit_json(changed=False) + module.exit_json(changed=changed) + + if id is not None: + module.exit_json(changed=changed, id=id) module.exit_json(changed=changed) diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_az.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_az.py index 02647d397..b4a493861 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_az.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_az.py @@ -112,6 +112,7 @@ def create_az(module, fusion): az_api_instance = purefusion.AvailabilityZonesApi(fusion) changed = True + id = None if not module.check_mode: if not module.params["display_name"]: display_name = module.params["name"] @@ -125,9 +126,10 @@ def create_az(module, fusion): op = az_api_instance.create_availability_zone( azone, region_name=module.params["region"] ) - await_operation(fusion, op) + res_op = await_operation(fusion, op) + id = res_op.result.resource.id - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=id) def main(): @@ -152,8 +154,6 @@ def main(): create_az(module, fusion) elif azone and state == "absent": delete_az(module, fusion) - else: - module.exit_json(changed=False) module.exit_json(changed=False) diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_hap.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_hap.py index 3f45ea2dd..c4df0af49 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_hap.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_hap.py @@ -170,8 +170,10 @@ def create_hap(module, fusion): display_name=display_name, ) ) - await_operation(fusion, op) - module.exit_json(changed=changed) + res_op = await_operation(fusion, op) + id = res_op.result.resource.id + + module.exit_json(changed=changed, id=id) def delete_hap(module, fusion): diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_ni.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_ni.py index 6816ed841..82c896fac 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_ni.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_ni.py @@ -162,7 +162,7 @@ def update_ni(module, fusion, ni): ), ) patches.append(patch) - + id = None if not module.check_mode: for patch in patches: op = ni_api_instance.update_network_interface( @@ -172,11 +172,12 @@ def update_ni(module, fusion, ni): array_name=module.params["array"], net_intf_name=module.params["name"], ) - await_operation(fusion, op) + res_op = await_operation(fusion, op) + id = res_op.result.resource.id changed = len(patches) != 0 - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=id) def main(): diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_nig.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_nig.py index d6056fd5a..d40b813b9 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_nig.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_nig.py @@ -146,6 +146,7 @@ def create_nig(module, fusion): ): module.fail_json(msg="`gateway` must be an address in subnet `prefix`") + id = None if not module.check_mode: display_name = module.params["display_name"] or module.params["name"] if module.params["group_type"] == "eth": @@ -171,13 +172,14 @@ def create_nig(module, fusion): availability_zone_name=module.params["availability_zone"], region_name=module.params["region"], ) - await_operation(fusion, op) + res_op = await_operation(fusion, op) + id = res_op.result.resource.id changed = True else: # to prevent future unintended error module.warn(f"group_type={module.params['group_type']} is not implemented") - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=id) def delete_nig(module, fusion): @@ -220,7 +222,7 @@ def update_nig(module, fusion, nig): changed = len(patches) != 0 - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=nig.id) def main(): diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_pg.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_pg.py index 57843d896..6d6f0eb94 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_pg.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_pg.py @@ -36,6 +36,11 @@ options: type: str default: present choices: [ absent, present ] + destroy_snapshots_on_delete: + description: + - "Before deleting placement group, snapshots within the placement group will be deleted." + - "If `false` then any snapshots will need to be deleted as a separate step before removing the placement group." + type: bool tenant: description: - The name of the tenant. @@ -116,6 +121,9 @@ from ansible_collections.purestorage.fusion.plugins.module_utils.startup import from ansible_collections.purestorage.fusion.plugins.module_utils.operations import ( await_operation, ) +from ansible_collections.purestorage.fusion.plugins.module_utils.snapshots import ( + delete_snapshot, +) def get_pg(module, fusion): @@ -153,9 +161,10 @@ def create_pg(module, fusion): tenant_name=module.params["tenant"], tenant_space_name=module.params["tenant_space"], ) - await_operation(fusion, op) + res_op = await_operation(fusion, op) + id = res_op.result.resource.id - return True + return True, id def update_display_name(module, fusion, patches, pg): @@ -213,6 +222,16 @@ def delete_pg(module, fusion): """Delete Placement Group""" pg_api_instance = purefusion.PlacementGroupsApi(fusion) if not module.check_mode: + if module.params["destroy_snapshots_on_delete"]: + snapshots_api = purefusion.SnapshotsApi(fusion) + snapshots = snapshots_api.list_snapshots( + placement_group=module.params["name"], + tenant_name=module.params["tenant"], + tenant_space_name=module.params["tenant_space"], + ) + for snap in snapshots.items: + delete_snapshot(fusion, snap, snapshots_api) + op = pg_api_instance.delete_placement_group( placement_group_name=module.params["name"], tenant_name=module.params["tenant"], @@ -229,6 +248,7 @@ def main(): argument_spec.update( dict( name=dict(type="str", required=True), + destroy_snapshots_on_delete=dict(type="bool"), display_name=dict(type="str"), tenant=dict(type="str", required=True), tenant_space=dict(type="str", required=True), @@ -257,19 +277,28 @@ def main(): state = module.params["state"] pgroup = get_pg(module, fusion) + id = None + if pgroup is not None: + id = pgroup.id + if state == "present" and not pgroup: module.fail_on_missing_params( ["region", "availability_zone", "storage_service"] ) - changed = create_pg(module, fusion) or changed + changed, id = create_pg(module, fusion) or changed if module.params["array"]: # changing placement requires additional update pgroup = get_pg(module, fusion) - changed = update_pg(module, fusion, pgroup) or changed + changedUpdate = update_pg(module, fusion, pgroup) + changed = changed | changedUpdate elif state == "present" and pgroup: changed = update_pg(module, fusion, pgroup) or changed elif state == "absent" and pgroup: changed = delete_pg(module, fusion) or changed + module.exit_json(changed=changed) + + if id is not None: + module.exit_json(changed=changed, id=id) module.exit_json(changed=changed) diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_pp.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_pp.py index abce9195c..216209d84 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_pp.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_pp.py @@ -31,6 +31,11 @@ options: default: present choices: [ present, absent ] type: str + destroy_snapshots_on_delete: + description: + - "Before deleting protection policy, snapshots within the protection policy will be deleted." + - "If `false` then any snapshots will need to be deleted as a separate step before removing the protection policy." + type: bool display_name: description: - The human name of the protection policy. @@ -39,8 +44,10 @@ options: local_rpo: description: - Recovery Point Objective for snapshots. - - Value should be specified in minutes. - Minimum value is 10 minutes. + - Value can be provided as m(inutes), h(ours), + d(ays), w(eeks), or y(ears). + - If no unit is provided, minutes are assumed. type: str local_retention: description: @@ -95,6 +102,9 @@ from ansible_collections.purestorage.fusion.plugins.module_utils.startup import from ansible_collections.purestorage.fusion.plugins.module_utils.operations import ( await_operation, ) +from ansible_collections.purestorage.fusion.plugins.module_utils.snapshots import ( + delete_snapshot, +) def get_pp(module, fusion): @@ -114,11 +124,13 @@ def create_pp(module, fusion): pp_api_instance = purefusion.ProtectionPoliciesApi(fusion) local_rpo = parse_minutes(module, module.params["local_rpo"]) local_retention = parse_minutes(module, module.params["local_retention"]) - if local_retention < 1: - module.fail_json(msg="Local Retention must be a minimum of 1 minutes") + if local_retention < 10: + module.fail_json(msg="Local Retention must be a minimum of 10 minutes") if local_rpo < 10: module.fail_json(msg="Local RPO must be a minimum of 10 minutes") + changed = True + id = None if not module.check_mode: if not module.params["display_name"]: display_name = module.params["name"] @@ -136,9 +148,10 @@ def create_pp(module, fusion): ], ) ) - await_operation(fusion, op) + res_op = await_operation(fusion, op) + id = res_op.result.resource.id - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=id) def delete_pp(module, fusion): @@ -146,6 +159,15 @@ def delete_pp(module, fusion): pp_api_instance = purefusion.ProtectionPoliciesApi(fusion) changed = True if not module.check_mode: + if module.params["destroy_snapshots_on_delete"]: + protection_policy = get_pp(module, fusion) + snapshots_api = purefusion.SnapshotsApi(fusion) + snapshots = snapshots_api.query_snapshots( + protection_policy_id=protection_policy.id + ) + for snap in snapshots.items: + delete_snapshot(fusion, snap, snapshots_api) + op = pp_api_instance.delete_protection_policy( protection_policy_name=module.params["name"], ) @@ -160,6 +182,7 @@ def main(): argument_spec.update( dict( name=dict(type="str", required=True), + destroy_snapshots_on_delete=dict(type="bool"), display_name=dict(type="str"), local_rpo=dict(type="str"), local_retention=dict(type="str"), @@ -177,8 +200,6 @@ def main(): create_pp(module, fusion) elif policy and state == "absent": delete_pp(module, fusion) - else: - module.exit_json(changed=False) module.exit_json(changed=False) diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_ra.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_ra.py index 7cfc7d866..c2ae2d5cf 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_ra.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_ra.py @@ -43,7 +43,7 @@ options: type: str api_client_key: description: - - The key of API client to assign the role to. + - The issuer ID of the API client to assign the role to. type: str scope: description: @@ -127,7 +127,7 @@ def get_principal(module, fusion): def user_to_principal(fusion, user_id): - """Given a human readable Fusion user, such as a Pure 1 App ID + """Given a human-readable Fusion user, such as a Pure 1 App ID return the associated principal """ id_api_instance = purefusion.IdentityManagerApi(fusion) @@ -139,7 +139,7 @@ def user_to_principal(fusion, user_id): def apiclient_to_principal(fusion, api_client_key): - """Given an API client key, such as "pure1:apikey:123xXxyYyzYzASDF" (also known as issuer_id), + """Given an API client issuer ID, such as "pure1:apikey:123xXxyYyzYzASDF", return the associated principal """ id_api_instance = purefusion.IdentityManagerApi(fusion) @@ -189,6 +189,7 @@ def create_ra(module, fusion): ra_api_instance = purefusion.RoleAssignmentsApi(fusion) changed = True + id = None if not module.check_mode: principal = get_principal(module, fusion) scope = get_scope(module.params) @@ -196,8 +197,10 @@ def create_ra(module, fusion): op = ra_api_instance.create_role_assignment( assignment, role_name=module.params["role"] ) - await_operation(fusion, op) - module.exit_json(changed=changed) + res_op = await_operation(fusion, op) + id = res_op.result.resource.id + + module.exit_json(changed=changed, id=id) def delete_ra(module, fusion): diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_region.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_region.py index fbcbff4b0..de40e7dc2 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_region.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_region.py @@ -96,6 +96,7 @@ def create_region(module, fusion): reg_api_instance = purefusion.RegionsApi(fusion) changed = True + id = None if not module.check_mode: if not module.params["display_name"]: display_name = module.params["name"] @@ -106,9 +107,10 @@ def create_region(module, fusion): display_name=display_name, ) op = reg_api_instance.create_region(region) - await_operation(fusion, op) + res_op = await_operation(fusion, op) + id = res_op.result.resource.id - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=id) def delete_region(module, fusion): @@ -144,7 +146,7 @@ def update_region(module, fusion, region): ) await_operation(fusion, op) - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=region.id) def main(): diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_sc.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_sc.py index 2327b8d48..59fc0025e 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_sc.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_sc.py @@ -160,6 +160,7 @@ def create_sc(module, fusion): module.fail_json(msg="Size limit is not within the required range") changed = True + id = None if not module.check_mode: if not module.params["display_name"]: display_name = module.params["name"] @@ -175,9 +176,10 @@ def create_sc(module, fusion): op = sc_api_instance.create_storage_class( s_class, storage_service_name=module.params["storage_service"] ) - await_operation(fusion, op) + res_op = await_operation(fusion, op) + id = res_op.result.resource.id - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=id) def update_sc(module, fusion, s_class): @@ -201,7 +203,7 @@ def update_sc(module, fusion, s_class): ) await_operation(fusion, op) - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=s_class.id) def delete_sc(module, fusion): diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_se.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_se.py index 9eed4bea0..3a191a166 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_se.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_se.py @@ -269,7 +269,7 @@ def get_se(module, fusion): def create_se(module, fusion): """Create Storage Endpoint""" se_api_instance = purefusion.StorageEndpointsApi(fusion) - + id = None if not module.check_mode: endpoint_type = None @@ -307,9 +307,10 @@ def create_se(module, fusion): region_name=module.params["region"], availability_zone_name=module.params["availability_zone"], ) - await_operation(fusion, op) + res_op = await_operation(fusion, op) + id = res_op.result.resource.id - module.exit_json(changed=True) + module.exit_json(changed=True, id=id) def delete_se(module, fusion): @@ -351,7 +352,7 @@ def update_se(module, fusion, se): changed = len(patches) != 0 - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=se.id) def main(): diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_ss.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_ss.py index 3fdbb07dd..4e6388249 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_ss.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_ss.py @@ -106,6 +106,7 @@ def create_ss(module, fusion): ss_api_instance = purefusion.StorageServicesApi(fusion) changed = True + id = None if not module.check_mode: if not module.params["display_name"]: display_name = module.params["name"] @@ -117,9 +118,10 @@ def create_ss(module, fusion): hardware_types=module.params["hardware_types"], ) op = ss_api_instance.create_storage_service(s_service) - await_operation(fusion, op) + res_op = await_operation(fusion, op) + id = res_op.result.resource.id - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=id) def delete_ss(module, fusion): @@ -151,6 +153,7 @@ def update_ss(module, fusion, ss): ) patches.append(patch) + id = None if not module.check_mode: for patch in patches: op = ss_api_instance.update_storage_service( @@ -161,7 +164,7 @@ def update_ss(module, fusion, ss): changed = len(patches) != 0 - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=ss.id) def main(): diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_tenant.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_tenant.py index 96e890a6b..85224a6c5 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_tenant.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_tenant.py @@ -87,6 +87,7 @@ def create_tenant(module, fusion): api_instance = purefusion.TenantsApi(fusion) changed = True + id = None if not module.check_mode: if not module.params["display_name"]: display_name = module.params["name"] @@ -97,9 +98,10 @@ def create_tenant(module, fusion): display_name=display_name, ) op = api_instance.create_tenant(tenant) - await_operation(fusion, op) + res_op = await_operation(fusion, op) + id = res_op.result.resource.id - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=id) def update_tenant(module, fusion, tenant): @@ -122,7 +124,7 @@ def update_tenant(module, fusion, tenant): ) await_operation(fusion, op) - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=tenant.id) def delete_tenant(module, fusion): diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_ts.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_ts.py index 33fb0187a..ac60476bc 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_ts.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_ts.py @@ -95,6 +95,7 @@ def create_ts(module, fusion): ts_api_instance = purefusion.TenantSpacesApi(fusion) changed = True + id = None if not module.check_mode: if not module.params["display_name"]: display_name = module.params["name"] @@ -108,9 +109,10 @@ def create_ts(module, fusion): tspace, tenant_name=module.params["tenant"], ) - await_operation(fusion, op) + res_op = await_operation(fusion, op) + id = res_op.result.resource.id - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=id) def update_ts(module, fusion, ts): @@ -138,7 +140,7 @@ def update_ts(module, fusion, ts): changed = len(patches) != 0 - module.exit_json(changed=changed) + module.exit_json(changed=changed, id=ts.id) def delete_ts(module, fusion): diff --git a/ansible_collections/purestorage/fusion/plugins/modules/fusion_volume.py b/ansible_collections/purestorage/fusion/plugins/modules/fusion_volume.py index 5b19064f5..38dee8650 100644 --- a/ansible_collections/purestorage/fusion/plugins/modules/fusion_volume.py +++ b/ansible_collections/purestorage/fusion/plugins/modules/fusion_volume.py @@ -73,6 +73,21 @@ options: To clear, assign empty list: host_access_policies: []' type: list elements: str + source_volume: + description: + - The source volume name. It must live within the same tenant space. + Cannot be used together with `source_snapshot` or `source_volume_snapshot`. + type: str + source_snapshot: + description: + - The source snapshot name. It must live within the same tenant space. + Cannot be used together with `source_volume`. + type: str + source_volume_snapshot: + description: + - The source volume snapshot name. It must live within the same tenant space. + Cannot be used together with `source_volume`. + type: str rename: description: - New name for volume. @@ -86,6 +101,7 @@ EXAMPLES = r""" purestorage.fusion.fusion_volume: name: foo storage_class: fred + placement_group: pg size: 1T tenant: test tenant_space: space_1 @@ -93,6 +109,31 @@ EXAMPLES = r""" issuer_id: key_name private_key_file: "az-admin-private-key.pem" +- name: Create new volume based on a volume from the same tenant space + purestorage.fusion.fusion_volume: + name: foo + storage_class: fred + placement_group: pg + tenant: test + tenant_space: space_1 + state: present + source_volume: "original_volume_name" + issuer_id: key_name + private_key_file: "az-admin-private-key.pem" + +- name: Create new volume based on a volume snapshot from the same tenant space + purestorage.fusion.fusion_volume: + name: foo + storage_class: fred + placement_group: pg + tenant: test + tenant_space: space_1 + state: present + source_snapshot: "snap" + source_volume_snapshot: "vol_snap" + issuer_id: key_name + private_key_file: "az-admin-private-key.pem" + - name: Extend the size of an existing volume named foo purestorage.fusion.fusion_volume: name: foo @@ -116,24 +157,24 @@ EXAMPLES = r""" RETURN = r""" """ -try: - import fusion as purefusion -except ImportError: - pass - -from ansible.module_utils.basic import AnsibleModule -from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import ( - fusion_argument_spec, -) -from ansible_collections.purestorage.fusion.plugins.module_utils.parsing import ( - parse_number_with_metric_suffix, +from ansible_collections.purestorage.fusion.plugins.module_utils.operations import ( + await_operation, ) from ansible_collections.purestorage.fusion.plugins.module_utils.startup import ( setup_fusion, ) -from ansible_collections.purestorage.fusion.plugins.module_utils.operations import ( - await_operation, +from ansible_collections.purestorage.fusion.plugins.module_utils.parsing import ( + parse_number_with_metric_suffix, ) +from ansible_collections.purestorage.fusion.plugins.module_utils.fusion import ( + fusion_argument_spec, +) +from ansible.module_utils.basic import AnsibleModule + +try: + import fusion as purefusion +except ImportError: + pass def get_volume(module, fusion): @@ -166,28 +207,30 @@ def extract_current_haps(volume): def create_volume(module, fusion): """Create Volume""" - - size = parse_number_with_metric_suffix(module, module.params["size"]) - + id = None if not module.check_mode: display_name = module.params["display_name"] or module.params["name"] volume_api_instance = purefusion.VolumesApi(fusion) + source_link = get_source_link_from_parameters(module.params) volume = purefusion.VolumePost( - size=size, + size=None # when cloning a volume, size is not required + if source_link + else parse_number_with_metric_suffix(module, module.params["size"]), storage_class=module.params["storage_class"], placement_group=module.params["placement_group"], name=module.params["name"], display_name=display_name, protection_policy=module.params["protection_policy"], + source_link=source_link, ) op = volume_api_instance.create_volume( volume, tenant_name=module.params["tenant"], tenant_space_name=module.params["tenant_space"], ) - await_operation(fusion, op) - - return True + res_op = await_operation(fusion, op) + id = res_op.result.resource.id + return True, id def update_host_access_policies(module, current, patches): @@ -273,6 +316,17 @@ def update_protection_policy(module, current, patches): patches.append(patch) +def update_source_link(module, fusion, current, patches): + source_link = get_source_link_from_parameters(module.params) + if source_link is not None and ( + current.source is None or current.source.self_link != source_link + ): + patch = purefusion.VolumePatch( + source_link=purefusion.NullableString(source_link) + ) + patches.append(patch) + + def apply_patches(module, fusion, patches): volume_api_instance = purefusion.VolumesApi(fusion) for patch in patches: @@ -313,6 +367,7 @@ def update_volume(module, fusion): update_storage_class(module, current, patches) update_placement_group(module, current, patches) update_host_access_policies(module, current, patches) + update_source_link(module, fusion, current, patches) elif module.params["state"] == "absent" and not current.destroyed: update_size(module, current, patches) update_protection_policy(module, current, patches) @@ -320,6 +375,7 @@ def update_volume(module, fusion): update_storage_class(module, current, patches) update_placement_group(module, current, patches) update_host_access_policies(module, current, patches) + update_source_link(module, fusion, current, patches) update_destroyed(module, current, patches) if not module.check_mode: @@ -355,16 +411,46 @@ def eradicate_volume(module, fusion): return True +def get_source_link_from_parameters(params): + tenant = params["tenant"] + tenant_space = params["tenant_space"] + volume = params["source_volume"] + snapshot = params["source_snapshot"] + volume_snapshot = params["source_volume_snapshot"] + if ( + tenant is None or tenant_space is None + ): # should not happen as those parameters are always required by the ansible module + return None + if volume is not None: + return f"/tenants/{tenant}/tenant-spaces/{tenant_space}/volumes/{volume}" + if snapshot is not None and volume_snapshot is not None: + return f"/tenants/{tenant}/tenant-spaces/{tenant_space}/snapshots/{snapshot}/volume-snapshots/{volume_snapshot}" + return None + + def validate_arguments(module, volume): """Validates most argument conditions and possible unacceptable argument combinations""" state = module.params["state"] if state == "present" and not volume: - module.fail_on_missing_params(["placement_group", "storage_class", "size"]) + module.fail_on_missing_params(["placement_group", "storage_class"]) + + if ( + module.params["size"] is None + and module.params["source_volume"] is None + and module.params["source_snapshot"] is None + ): + module.fail_json( + msg="Either `size`, `source_volume` or `source_snapshot` parameter is required when creating a volume." + ) if module.params["state"] == "absent" and ( module.params["host_access_policies"] - or (volume and volume.host_access_policies) + or ( + module.params["host_access_policies"] is None + and volume + and volume.host_access_policies + ) ): module.fail_json( msg=( @@ -378,7 +464,7 @@ def validate_arguments(module, volume): msg="'eradicate: true' cannot be used together with 'state: present'" ) - if module.params["size"]: + if module.params["size"] is not None: size = parse_number_with_metric_suffix(module, module.params["size"]) if size < 1048576 or size > 4503599627370496: # 1MB to 4PB module.fail_json( @@ -412,6 +498,9 @@ def main(): eradicate=dict(type="bool", default=False), state=dict(type="str", default="present", choices=["absent", "present"]), size=dict(type="str"), + source_volume=dict(type="str"), + source_snapshot=dict(type="str"), + source_volume_snapshot=dict(type="str"), ) ) @@ -419,9 +508,22 @@ def main(): "placement_group": "storage_class", } + mutually_exclusive = [ + # a new volume cannot be based on a volume and a snapshot at the same time + # also, when cloning a volume, size of original volume is used + ("source_volume", "source_snapshot", "size"), + ] + + required_together = [ + # when creating a volume from snapshot, we need to know both snapshot name and snapshot volume name + ("source_snapshot", "source_volume_snapshot"), + ] + module = AnsibleModule( argument_spec, required_by=required_by, + mutually_exclusive=mutually_exclusive, + required_together=required_together, supports_check_mode=True, ) fusion = setup_fusion(module) @@ -436,12 +538,19 @@ def main(): module.exit_json(changed=False) changed = False + id = None + if volume is not None: + id = volume.id if state == "present" and not volume: - changed = changed | create_volume(module, fusion) + changed, id = create_volume(module, fusion) # volume might exist even if soft-deleted, so we still have to update it changed = changed | update_volume(module, fusion) if module.params["eradicate"]: changed = changed | eradicate_volume(module, fusion) + module.exit_json(changed=changed) + + if id is not None: + module.exit_json(changed=changed, id=id) module.exit_json(changed=changed) |