Repository URL to install this package:
|
Version:
6.0.0 ▾
|
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2021, Simon Dodsley (simon@purestorage.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.1",
"status": ["preview"],
"supported_by": "community",
}
DOCUMENTATION = r"""
---
module: purefa_dirsnap
version_added: '1.9.0'
short_description: Manage FlashArray File System Directory Snapshots
description:
- Create/Delete FlashArray File System directory snapshots
- A full snapshot name is constructed in the form of DIR.CLIENT_NAME.SUFFIX
where DIR is the managed directory name, CLIENT_NAME is the client name,
and SUFFIX is the suffix.
- The client visible snapshot name is CLIENT_NAME.SUFFIX.
author:
- Pure Storage Ansible Team (@sdodsley) <pure-ansible-team@purestorage.com>
options:
name:
description:
- Name of the directory to snapshot
type: str
required: true
state:
description:
- Define whether the directory snapshot should exist or not.
default: present
choices: [ absent, present ]
type: str
filesystem:
description:
- Name of the filesystem the directory links to.
type: str
required: true
eradicate:
description:
- Define whether to eradicate the snapshot on delete or leave in trash
type: bool
default: false
client:
description:
- The client name portion of the client visible snapshot name
type: str
required: true
suffix:
description:
- Snapshot suffix to use
type: str
new_client:
description:
- The new client name when performing a rename
type: str
version_added: '1.12.0'
new_suffix:
description:
- The new suffix when performing a rename
type: str
version_added: '1.12.0'
rename:
description:
- Whether to rename a directory snapshot
- The snapshot client name and suffix can be changed
- Required with I(new_client) ans I(new_suffix)
type: bool
default: false
version_added: '1.12.0'
keep_for:
description:
- Retention period, after which snapshots will be eradicated
- Specify in seconds. Range 300 - 31536000 (5 minutes to 1 year)
- Value of 0 will set no retention period.
- If not specified on create will default to 0 (no retention period)
type: int
extends_documentation_fragment:
- purestorage.flasharray.purestorage.fa
"""
EXAMPLES = r"""
- name: Create a snapshot direcotry foo in filesysten bar for client test with suffix test
purestorage.flasharray.purefa_dirsnap:
name: foo
filesystem: bar
client: test
suffix: test
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Update retention time for a snapshot foo:bar.client.test
purestorage.flasharray.purefa_dirsnap:
name: foo
filesystem: bar
client: client
suffix: test
keep_for: 300 # 5 minutes
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Delete snapshot foo:bar.client.test
purestorage.flasharray.purefa_dirsnap:
name: foo
filesystem: bar
client: client
suffix: test
state: absent
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Recover deleted snapshot foo:bar.client.test
purestorage.flasharray.purefa_dirsnap:
name: foo
filesystem: bar
client: client
suffix: test
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Delete and eradicate snapshot foo:bar.client.test
purestorage.flasharray.purefa_dirsnap:
name: foo
filesystem: bar
client: client
suffix: test
state: absent
eradicate: true
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Eradicate deleted snapshot foo:bar.client.test
purestorage.flasharray.purefa_dirsnap:
name: foo
filesystem: bar
client: client
suffix: test
eradicate: true
state: absent
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
- name: Rename snapshot
purestorage.flasharray.purefa_dirsnap:
name: foo
filesystem: bar
client: client
suffix: test
rename: true
new_client: client2
new_suffix: test2
fa_url: 10.10.10.2
api_token: e31060a7-21fc-e277-6240-25983c6c4592
"""
RETURN = r"""
"""
HAS_PURESTORAGE = True
try:
from pypureclient.flasharray import DirectorySnapshotPost, DirectorySnapshotPatch
except ImportError:
HAS_PURESTORAGE = False
import re
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.purestorage.flasharray.plugins.module_utils.purefa import (
get_system,
get_array,
purefa_argument_spec,
)
MIN_REQUIRED_API_VERSION = "2.2"
MIN_RENAME_API_VERSION = "2.10"
def eradicate_snap(module, array):
"""Eradicate a filesystem snapshot"""
changed = True
if not module.check_mode:
snapname = (
module.params["filesystem"]
+ ":"
+ module.params["name"]
+ "."
+ module.params["client"]
+ "."
+ module.params["suffix"]
)
res = array.delete_directory_snapshots(names=[snapname])
if res.status_code != 200:
module.fail_json(
msg="Failed to eradicate filesystem snapshot {0}. Error: {1}".format(
snapname, res.errors[0].message
)
)
module.exit_json(changed=changed)
def delete_snap(module, array):
"""Delete a filesystem snapshot"""
changed = True
if not module.check_mode:
snapname = (
module.params["filesystem"]
+ ":"
+ module.params["name"]
+ "."
+ module.params["client"]
+ "."
+ module.params["suffix"]
)
directory_snapshot = DirectorySnapshotPatch(destroyed=True)
res = array.patch_directory_snapshots(
names=[snapname], directory_snapshot=directory_snapshot
)
if res.status_code != 200:
module.fail_json(
msg="Failed to delete filesystem snapshot {0}. Error: {1}".format(
snapname, res.errors[0].message
)
)
if module.params["eradicate"]:
eradicate_snap(module, array)
module.exit_json(changed=changed)
def update_snap(module, array, snap_detail):
"""Update a filesystem snapshot retention time"""
changed = True
snapname = (
module.params["filesystem"]
+ ":"
+ module.params["name"]
+ "."
+ module.params["client"]
+ "."
+ module.params["suffix"]
)
if module.params["rename"]:
if not module.params["new_client"]:
new_client = module.params["client"]
else:
new_client = module.params["new_client"]
if not module.params["new_suffix"]:
new_suffix = module.params["suffix"]
else:
new_suffix = module.params["new_suffix"]
new_snapname = (
module.params["filesystem"]
+ ":"
+ module.params["name"]
+ "."
+ new_client
+ "."
+ new_suffix
)
directory_snapshot = DirectorySnapshotPatch(
client_name=new_client, suffix=new_suffix
)
if not module.check_mode:
res = array.patch_directory_snapshots(
names=[snapname], directory_snapshot=directory_snapshot
)
if res.status_code != 200:
module.fail_json(
msg="Failed to rename snapshot {0}. Error: {1}".format(
snapname, res.errors[0].message
)
)
else:
snapname = new_snapname
if not module.params["keep_for"] or module.params["keep_for"] == 0:
keep_for = 0
elif 300 <= module.params["keep_for"] <= 31536000:
keep_for = module.params["keep_for"] * 1000
else:
module.fail_json(msg="keep_for not in range of 300 - 31536000")
if not module.check_mode:
if snap_detail.destroyed:
directory_snapshot = DirectorySnapshotPatch(destroyed=False)
res = array.patch_directory_snapshots(
names=[snapname], directory_snapshot=directory_snapshot
)
if res.status_code != 200:
module.fail_json(
msg="Failed to recover snapshot {0}. Error: {1}".format(
snapname, res.errors[0].message
)
)
directory_snapshot = DirectorySnapshotPatch(keep_for=keep_for)
if snap_detail.time_remaining == 0 and keep_for != 0:
res = array.patch_directory_snapshots(
names=[snapname], directory_snapshot=directory_snapshot
)
if res.status_code != 200:
module.fail_json(
msg="Failed to retention time for snapshot {0}. Error: {1}".format(
snapname, res.errors[0].message
)
)
elif snap_detail.time_remaining > 0:
if module.params["rename"] and module.params["keep_for"]:
res = array.patch_directory_snapshots(
names=[snapname], directory_snapshot=directory_snapshot
)
if res.status_code != 200:
module.fail_json(
msg="Failed to retention time for renamed snapshot {0}. Error: {1}".format(
snapname, res.errors[0].message
)
)
module.exit_json(changed=changed)
def create_snap(module, array):
"""Create a filesystem snapshot"""
changed = True
if not module.check_mode:
if not module.params["keep_for"] or module.params["keep_for"] == 0:
keep_for = 0
elif 300 <= module.params["keep_for"] <= 31536000:
keep_for = module.params["keep_for"] * 1000
else:
module.fail_json(msg="keep_for not in range of 300 - 31536000")
directory = module.params["filesystem"] + ":" + module.params["name"]
if module.params["suffix"]:
directory_snapshot = DirectorySnapshotPost(
client_name=module.params["client"],
keep_for=keep_for,
suffix=module.params["suffix"],
)
else:
directory_snapshot = DirectorySnapshotPost(
client_name=module.params["client"], keep_for=keep_for
)
res = array.post_directory_snapshots(
source_names=[directory], directory_snapshot=directory_snapshot
)
if res.status_code != 200:
module.fail_json(
msg="Failed to create client {0} snapshot for {1}. Error: {2}".format(
module.params["client"], directory, res.errors[0].message
)
)
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"]),
filesystem=dict(type="str", required=True),
name=dict(type="str", required=True),
eradicate=dict(type="bool", default=False),
client=dict(type="str", required=True),
suffix=dict(type="str"),
rename=dict(type="bool", default=False),
new_client=dict(type="str"),
new_suffix=dict(type="str"),
keep_for=dict(type="int"),
)
)
required_if = [["state", "absent", ["suffix"]]]
module = AnsibleModule(
argument_spec, required_if=required_if, supports_check_mode=True
)
if module.params["rename"]:
if not module.params["new_client"] and not module.params["new_suffix"]:
module.fail_json(msg="Rename requires one of: new_client, new_suffix")
if not HAS_PURESTORAGE:
module.fail_json(msg="py-pure-client sdk is required for this module")
client_pattern = re.compile(
"^(?=.*[a-zA-Z-])[a-zA-Z0-9]([a-zA-Z0-9-]{0,56}[a-zA-Z0-9])?$"
)
suffix_pattern = re.compile(
"^(?=.*[a-zA-Z-])[a-zA-Z0-9]([a-zA-Z0-9-]{0,63}[a-zA-Z0-9])?$"
)
if module.params["suffix"]:
if not suffix_pattern.match(module.params["suffix"]):
module.fail_json(
msg="Suffix name {0} does not conform to the suffix name rules.".format(
module.params["suffix"]
)
)
if module.params["new_suffix"]:
if not suffix_pattern.match(module.params["new_suffix"]):
module.fail_json(
msg="Suffix rename {0} does not conform to the suffix name rules.".format(
module.params["new_suffix"]
)
)
if module.params["client"]:
if not client_pattern.match(module.params["client"]):
module.fail_json(
msg="Client name {0} does not conform to the client name rules.".format(
module.params["client"]
)
)
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 module.params["rename"] and MIN_RENAME_API_VERSION not in api_version:
module.fail_json(
msg="Directory snapshot rename not supported. "
"Minimum Purity//FA version required: 6.2.1"
)
array = get_array(module)
state = module.params["state"]
snapshot_root = module.params["filesystem"] + ":" + module.params["name"]
if bool(
array.get_directories(
filter='name="' + snapshot_root + '"', total_item_count=True
).total_item_count
== 0
):
module.fail_json(msg="Directory {0} does not exist.".format(snapshot_root))
snap_exists = False
if module.params["suffix"]:
snap_detail = array.get_directory_snapshots(
filter="name='"
+ snapshot_root
+ "."
+ module.params["client"]
+ "."
+ module.params["suffix"]
+ "'",
total_item_count=True,
)
if bool(snap_detail.status_code == 200):
snap_exists = bool(snap_detail.total_item_count != 0)
if snap_exists:
snap_facts = list(snap_detail.items)[0]
if state == "present" and not snap_exists:
create_snap(module, array)
elif state == "present" and snap_exists and module.params["suffix"]:
update_snap(module, array, snap_facts)
elif state == "absent" and snap_exists and not snap_facts.destroyed:
delete_snap(module, array)
elif (
state == "absent"
and snap_exists
and snap_facts.destroyed
and module.params["eradicate"]
):
eradicate_snap(module, array)
else:
module.exit_json(changed=False)
module.exit_json(changed=False)
if __name__ == "__main__":
main()