summaryrefslogtreecommitdiff
path: root/lib/ansible/modules/extras/cloud/amazon/efs_facts.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ansible/modules/extras/cloud/amazon/efs_facts.py')
-rw-r--r--lib/ansible/modules/extras/cloud/amazon/efs_facts.py377
1 files changed, 377 insertions, 0 deletions
diff --git a/lib/ansible/modules/extras/cloud/amazon/efs_facts.py b/lib/ansible/modules/extras/cloud/amazon/efs_facts.py
new file mode 100644
index 0000000000..1720ec5d80
--- /dev/null
+++ b/lib/ansible/modules/extras/cloud/amazon/efs_facts.py
@@ -0,0 +1,377 @@
+#!/usr/bin/python
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+DOCUMENTATION = '''
+---
+module: efs_facts
+short_description: Get information about Amazon EFS file systems
+description:
+ - Module searches Amazon EFS file systems
+version_added: "2.2"
+requirements: [ boto3 ]
+author:
+ - "Ryan Sydnor (@ryansydnor)"
+options:
+ name:
+ description:
+ - Creation Token of Amazon EFS file system.
+ required: false
+ default: None
+ id:
+ description:
+ - ID of Amazon EFS.
+ required: false
+ default: None
+ tags:
+ description:
+ - |
+ List of tags of Amazon EFS. Should be defined as dictionary
+ required: false
+ default: None
+ targets:
+ description:
+ - |
+ List of mounted targets. It should be a list of dictionaries, every dictionary should include next attributes:
+ - SubnetId - Mandatory. The ID of the subnet to add the mount target in.
+ - IpAddress - Optional. A valid IPv4 address within the address range of the specified subnet.
+ - SecurityGroups - Optional. List of security group IDs, of the form "sg-xxxxxxxx". These must be for the same VPC as subnet specified.
+ required: false
+ default: None
+extends_documentation_fragment:
+ - aws
+'''
+
+EXAMPLES = '''
+# find all existing efs
+- efs_facts:
+ register: result
+
+- efs_facts:
+ name: myTestNameTag
+
+- efs_facts:
+ id: fs-1234abcd
+
+# Searching all EFS instances with tag Name = 'myTestNameTag', in subnet 'subnet-1a2b3c4d' and with security group 'sg-4d3c2b1a'
+- efs_facts:
+ tags:
+ name: myTestNameTag
+ targets:
+ - subnet-1a2b3c4d
+ - sg-4d3c2b1a
+'''
+
+RETURN = '''
+creation_time:
+ description: timestamp of creation date
+ returned:
+ type: datetime
+ sample: 2015-11-16 07:30:57-05:00
+creation_token:
+ description: EFS creation token
+ returned:
+ type: UUID
+ sample: console-88609e04-9a0e-4a2e-912c-feaa99509961
+file_system_id:
+ description: ID of the file system
+ returned:
+ type: unique ID
+ sample: fs-xxxxxxxx
+life_cycle_state:
+ description: state of the EFS file system
+ returned:
+ type: str
+ sample: creating, available, deleting, deleted
+mount_point:
+ description: url of file system
+ returned:
+ type: str
+ sample: .fs-xxxxxxxx.efs.us-west-2.amazonaws.com:/
+mount_targets:
+ description: list of mount targets
+ returned:
+ type: list of dicts
+ sample:
+ [
+ {
+ "file_system_id": "fs-a7ad440e",
+ "ip_address": "172.31.17.173",
+ "life_cycle_state": "available",
+ "mount_target_id": "fsmt-d8907871",
+ "network_interface_id": "eni-6e387e26",
+ "owner_id": "740748460359",
+ "security_groups": [
+ "sg-a30b22c6"
+ ],
+ "subnet_id": "subnet-e265c895"
+ },
+ ...
+ ]
+name:
+ description: name of the file system
+ returned:
+ type: str
+ sample: my-efs
+number_of_mount_targets:
+ description: the number of targets mounted
+ returned:
+ type: int
+ sample: 3
+owner_id:
+ description: AWS account ID of EFS owner
+ returned:
+ type: str
+ sample: XXXXXXXXXXXX
+size_in_bytes:
+ description: size of the file system in bytes as of a timestamp
+ returned:
+ type: dict
+ sample:
+ {
+ "timestamp": "2015-12-21 13:59:59-05:00",
+ "value": 12288
+ }
+performance_mode:
+ description: performance mode of the file system
+ returned:
+ type: str
+ sample: "generalPurpose"
+tags:
+ description: tags on the efs instance
+ returned:
+ type: dict
+ sample:
+ {
+ "name": "my-efs",
+ "key": "Value"
+ }
+
+'''
+
+
+from time import sleep
+from collections import defaultdict
+
+try:
+ from botocore.exceptions import ClientError
+ import boto3
+ HAS_BOTO3 = True
+except ImportError as e:
+ HAS_BOTO3 = False
+
+class EFSConnection(object):
+ STATE_CREATING = 'creating'
+ STATE_AVAILABLE = 'available'
+ STATE_DELETING = 'deleting'
+ STATE_DELETED = 'deleted'
+
+ def __init__(self, module, region, **aws_connect_params):
+ try:
+ self.connection = boto3_conn(module, conn_type='client',
+ resource='efs', region=region,
+ **aws_connect_params)
+ except Exception as e:
+ module.fail_json(msg="Failed to connect to AWS: %s" % str(e))
+
+ self.region = region
+
+ def get_file_systems(self, **kwargs):
+ """
+ Returns generator of file systems including all attributes of FS
+ """
+ items = iterate_all(
+ 'FileSystems',
+ self.connection.describe_file_systems,
+ **kwargs
+ )
+ for item in items:
+ item['CreationTime'] = str(item['CreationTime'])
+ """
+ Suffix of network path to be used as NFS device for mount. More detail here:
+ http://docs.aws.amazon.com/efs/latest/ug/gs-step-three-connect-to-ec2-instance.html
+ """
+ item['MountPoint'] = '.%s.efs.%s.amazonaws.com:/' % (item['FileSystemId'], self.region)
+ if 'Timestamp' in item['SizeInBytes']:
+ item['SizeInBytes']['Timestamp'] = str(item['SizeInBytes']['Timestamp'])
+ if item['LifeCycleState'] == self.STATE_AVAILABLE:
+ item['Tags'] = self.get_tags(FileSystemId=item['FileSystemId'])
+ item['MountTargets'] = list(self.get_mount_targets(FileSystemId=item['FileSystemId']))
+ else:
+ item['Tags'] = {}
+ item['MountTargets'] = []
+ yield item
+
+ def get_tags(self, **kwargs):
+ """
+ Returns tag list for selected instance of EFS
+ """
+ tags = iterate_all(
+ 'Tags',
+ self.connection.describe_tags,
+ **kwargs
+ )
+ return dict((tag['Key'], tag['Value']) for tag in tags)
+
+ def get_mount_targets(self, **kwargs):
+ """
+ Returns mount targets for selected instance of EFS
+ """
+ targets = iterate_all(
+ 'MountTargets',
+ self.connection.describe_mount_targets,
+ **kwargs
+ )
+ for target in targets:
+ if target['LifeCycleState'] == self.STATE_AVAILABLE:
+ target['SecurityGroups'] = list(self.get_security_groups(
+ MountTargetId=target['MountTargetId']
+ ))
+ else:
+ target['SecurityGroups'] = []
+ yield target
+
+ def get_security_groups(self, **kwargs):
+ """
+ Returns security groups for selected instance of EFS
+ """
+ return iterate_all(
+ 'SecurityGroups',
+ self.connection.describe_mount_target_security_groups,
+ **kwargs
+ )
+
+
+def iterate_all(attr, map_method, **kwargs):
+ """
+ Method creates iterator from boto result set
+ """
+ args = dict((key, value) for (key, value) in kwargs.items() if value is not None)
+ wait = 1
+ while True:
+ try:
+ data = map_method(**args)
+ for elm in data[attr]:
+ yield elm
+ if 'NextMarker' in data:
+ args['Marker'] = data['Nextmarker']
+ continue
+ break
+ except ClientError as e:
+ if e.response['Error']['Code'] == "ThrottlingException" and wait < 600:
+ sleep(wait)
+ wait = wait * 2
+ continue
+
+
+def prefix_to_attr(attr_id):
+ """
+ Helper method to convert ID prefix to mount target attribute
+ """
+ attr_by_prefix = {
+ 'fsmt-': 'MountTargetId',
+ 'subnet-': 'SubnetId',
+ 'eni-': 'NetworkInterfaceId',
+ 'sg-': 'SecurityGroups'
+ }
+ prefix = first_or_default(filter(
+ lambda pref: str(attr_id).startswith(pref),
+ attr_by_prefix.keys()
+ ))
+ if prefix:
+ return attr_by_prefix[prefix]
+ return 'IpAddress'
+
+def first_or_default(items, default=None):
+ """
+ Helper method to fetch first element of list (if exists)
+ """
+ for item in items:
+ return item
+ return default
+
+def has_tags(available, required):
+ """
+ Helper method to determine if tag requested already exists
+ """
+ for key, value in required.items():
+ if key not in available or value != available[key]:
+ return False
+ return True
+
+def has_targets(available, required):
+ """
+ Helper method to determine if mount tager requested already exists
+ """
+ grouped = group_list_of_dict(available)
+ for (value, field) in required:
+ if field not in grouped or value not in grouped[field]:
+ return False
+ return True
+
+def group_list_of_dict(array):
+ """
+ Helper method to group list of dict to dict with all possible values
+ """
+ result = defaultdict(list)
+ for item in array:
+ for key, value in item.items():
+ result[key] += value if isinstance(value, list) else [value]
+ return result
+
+
+def main():
+ """
+ Module action handler
+ """
+ argument_spec = ec2_argument_spec()
+ argument_spec.update(dict(
+ id=dict(required=False, type='str', default=None),
+ name=dict(required=False, type='str', default=None),
+ tags=dict(required=False, type="dict", default={}),
+ targets=dict(required=False, type="list", default=[])
+ ))
+
+ module = AnsibleModule(argument_spec=argument_spec)
+
+ if not HAS_BOTO3:
+ module.fail_json(msg='boto3 required for this module')
+
+ region, _, aws_connect_params = get_aws_connection_info(module, boto3=True)
+ connection = EFSConnection(module, region, **aws_connect_params)
+
+ name = module.params.get('name')
+ fs_id = module.params.get('id')
+ tags = module.params.get('tags')
+ targets = module.params.get('targets')
+
+ file_systems_info = connection.get_file_systems(FileSystemId=fs_id, CreationToken=name)
+
+ if tags:
+ file_systems_info = filter(lambda item: has_tags(item['Tags'], tags), file_systems_info)
+
+ if targets:
+ targets = [(item, prefix_to_attr(item)) for item in targets]
+ file_systems_info = filter(lambda item:
+ has_targets(item['MountTargets'], targets), file_systems_info)
+
+ file_systems_info = [camel_dict_to_snake_dict(x) for x in file_systems_info]
+ module.exit_json(changed=False, ansible_facts={'efs': file_systems_info})
+
+from ansible.module_utils.basic import *
+from ansible.module_utils.ec2 import *
+
+if __name__ == '__main__':
+ main()