diff --git a/lib/ansible/modules/cloud/amazon/cloudfront_facts.py b/lib/ansible/modules/cloud/amazon/cloudfront_facts.py new file mode 100644 index 00000000000..ca59b9f7b27 --- /dev/null +++ b/lib/ansible/modules/cloud/amazon/cloudfront_facts.py @@ -0,0 +1,520 @@ +#!/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 . + +ANSIBLE_METADATA = {'status': ['preview'], + 'version': '1.0'} + +DOCUMENTATION = ''' +--- +module: cloudfront_facts +short_description: Obtain facts about an AWS CloudFront distribution +description: + - Gets information about an AWS CloudFront distribution +requirements: + - boto3 >= 1.0.0 + - python >= 2.6 +version_added: "2.3" +author: Willem van Ketwich (@wilvk) +options: + distribution_id: + description: + - The id of the CloudFront distribution. Used with distribution, distribution_config, invalidation, streaming_distribution, streaming_distribution_config, list_invalidations. + required: false + invalidation_id: + description: + - The id of the invalidation to get information about. Used with invalidation. + required: false + origin_access_identity_id: + description: + - The id of the cloudfront origin access identity to get information about. + required: false + web_acl_id: + description: + - Used with list_distributions_by_web_acl_id. + required: false + domain_name_alias: + description: + - Can be used instead of distribution_id - uses the aliased CNAME for the cloudfront distribution to get the distribution id where required. + required: false + all_lists: + description: + - Get all cloudfront lists that do not require parameters. + required: false + default: false + origin_access_identity: + description: + - Get information about an origin access identity. Requires origin_access_identity_id to be specified. + required: false + default: false + origin_access_identity_config: + description: + - Get the configuration information about an origin access identity. Requires origin_access_identity_id to be specified. + required: false + default: false + distribution: + description: + - Get information about a distribution. Requires distribution_id or domain_name_alias to be specified. + required: false + default: false + distribution_config: + description: + - Get the configuration information about a distribution. Requires distribution_id or domain_name_alias to be specified. + required: false + default: false + invalidation: + description: + - Get information about an invalidation. Requires invalidation_id to be specified. + required: false + default: false + streaming_distribution: + description: + - Get information about a specified RTMP distribution. Requires distribution_id or domain_name_alias to be specified. + required: false + default: false + streaming_distribution_configuration: + description: + - Get the configuration information about a specified RTMP distribution. Requires distribution_id or domain_name_alias to be specified. + required: false + default: false + list_origin_access_identities: + description: + - Get a list of cloudfront origin access identities. Requires origin_access_identity_id to be set. + required: false + default: false + list_distributions: + description: + - Get a list of cloudfront distributions. + required: false + default: false + list_distributions_by_web_acl_id: + description: + - Get a list of distributions using web acl id as a filter. Requires web_acl_id to be set. + required: false + default: false + list_invalidations: + description: + - Get a list of invalidations. Requires distribution_id or domain_name_alias to be specified. + required: false + default: false + list_streaming_distributions: + description: + - Get a list of streaming distributions. + required: false + default: false + +extends_documentation_fragment: + - aws + - ec2 +''' + +EXAMPLES = ''' +# Note: These examples do not set authentication details, see the AWS Guide for details. + +# Get information about a distribution +- cloudfront_facts: + distribution: true + distribution_id: my-cloudfront-distribution-id + +# Get information about a distribution using the CNAME of the cloudfront distribution. +- cloudfront_facts: + distribution: true + domain_name_alias: www.my-website.com + +# Facts are published in ansible_facts['cloudfront'][] +- debug: + msg: '{{ ansible_facts['cloudfront']['my-cloudfront-distribution-id'] }}' + +- debug: + msg: '{{ ansible_facts['cloudfront']['www.my-website.com'] }}' + +# Get all information about an invalidation for a distribution. +- cloudfront_facts: + invalidation: true + distribution_id: my-cloudfront-distribution-id + invalidation_id: my-cloudfront-invalidation-id + +# Get all information about a cloudfront origin access identity. +- cloudfront_facts: + origin_access_identity: true + origin_access_identity_id: my-cloudfront-origin-access-identity-id + +# Get all information about lists not requiring parameters (ie. list_origin_access_identities, list_distributions, list_streaming_distributions) +- cloudfront_facts: + all_lists: true +''' + +RETURN = ''' +origin_access_identity: + description: Describes the origin access identity information. Requires origin_access_identity_id to be set. + returned: only if origin_access_identity is true + type: dict +origin_access_identity_configuration: + description: Describes the origin access identity information configuration information. Requires origin_access_identity_id to be set. + returned: only if origin_access_identity_configuration is true + type: dict +distribution: + description: Facts about a cloudfront distribution. Requires distribution_id or domain_name_alias to be specified. Requires origin_access_identity_id to be set. + returned: only if distribution is true + type: dict +distribution_config: + description: Facts about a cloudfront distribution's config. Requires distribution_id or domain_name_alias to be specified. + returned: only if distribution_config is true + type: dict +invalidation: + description: Describes the invalidation information for the distribution. Requires invalidation_id to be specified and either distribution_id or domain_name_alias. + returned: only if invalidation is true + type: dict +streaming_distribution: + description: Describes the streaming information for the distribution. Requires distribution_id or domain_name_alias to be specified. + returned: only if streaming_distribution is true + type: dict +streaming_distribution_configuration: + description: Describes the streaming configuration information for the distribution. Requires distribution_id or domain_name_alias to be specified. + returned: only if streaming_distribution_configuration is true + type: dict +''' + +try: + import boto3 + import botocore + HAS_BOTO3 = True +except ImportError: + HAS_BOTO3 = False + +from ansible.module_utils.ec2 import get_aws_connection_info +from ansible.module_utils.ec2 import ec2_argument_spec +from ansible.module_utils.ec2 import boto3_conn +from ansible.module_utils.basic import AnsibleModule +from functools import partial +import json +import traceback + +class CloudFrontServiceManager: + """Handles CloudFront Services""" + + def __init__(self, module): + self.module = module + + try: + region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module, boto3=True) + self.client = boto3_conn(module, conn_type='client', + resource='cloudfront', region=region, + endpoint=ec2_url, **aws_connect_kwargs) + except botocore.exceptions.NoRegionError: + self.module.fail_json(msg="Region must be specified as a parameter, in AWS_DEFAULT_REGION environment variable or in boto configuration file") + except Exception as e: + self.module.fail_json(msg="Can't establish connection - " + str(e), exception=traceback.format_exc(e)) + + def get_distribution(self, distribution_id): + try: + func = partial(self.client.get_distribution,Id=distribution_id) + return self.paginated_response(func) + except Exception as e: + self.module.fail_json(msg="Error describing distribution - " + str(e), exception=traceback.format_exc(e)) + + def get_distribution_config(self, distribution_id): + try: + func = partial(self.client.get_distribution_config,Id=distribution_id) + return self.paginated_response(func) + except Exception as e: + self.module.fail_json(msg="Error describing distribution configuration - " + str(e), exception=traceback.format_exec(e)) + + def get_origin_access_identity(self, origin_access_identity_id): + try: + func = partial(self.client.get_cloud_front_origin_access_identity,Id=origin_access_identity_id) + return self.paginated_response(func) + except Exception as e: + self.module.fail_json(msg="Error describing origin access identity - " + str(e), exception=traceback.format_exc(e)) + + def get_origin_access_identity_config(self, origin_access_identity_id): + try: + func = partial(self.client.get_cloud_front_origin_access_identity_config,Id=origin_access_identity_id) + return self.paginated_response(func) + except Exception as e: + self.module.fail_json(msg="Error describing origin access identity configuration - " + str(e), exception=traceback.format_exc(e)) + + def get_invalidation(self, distribution_id, invalidation_id): + try: + func = partial(self.client.get_invalidation,DistributionId=distribution_id,Id=invalidation_id) + return self.paginated_response(func) + except Exception as e: + self.module.fail_json(msg="Error describing invalidation - " + str(e), exception=traceback.format_exc(e)) + + def get_streaming_distribution(self, distribution_id): + try: + func = partial(self.client.get_streaming_distribution,Id=distribution_id) + return self.paginated_response(func) + except Exception as e: + self.module.fail_json(msg="Error describing streaming distribution - " + str(e), exception=traceback.format_exc(e)) + + def get_streaming_distribution_config(self, distribution_id): + try: + func = partial(self.client.get_streaming_distribution_config,Id=distribution_id) + return self.paginated_response(func) + except Exception as e: + self.module.fail_json(msg="Error describing streaming distribution - " + str(e), exception=traceback.format_exc(e)) + + def list_origin_access_identities(self): + try: + func = partial(self.client.list_cloud_front_origin_access_identities) + return self.paginated_response(func, 'CloudFrontOriginAccessIdentityList')['Items'] + except Exception as e: + self.module.fail_json(msg="Error listing cloud front origin access identities = " + str(e), exception=traceback.format_exc(e)) + + def list_distributions(self, keyed=True): + try: + func = partial(self.client.list_distributions) + distribution_list = self.paginated_response(func, 'DistributionList')['Items'] + if not keyed: + return distribution_list + return self.keyed_list_helper(distribution_list) + except Exception as e: + self.module.fail_json(msg="Error listing distributions = " + str(e), exception=traceback.format_exc(e)) + + def list_distributions_by_web_acl_id(self, web_acl_id): + try: + func = partial(self.client.list_distributions_by_web_acl_id, WebAclId=web_acl_id) + distributions = self.paginated_response(func, 'DistributionList')['Items'] + return self.keyed_list_helper(distributions) + except Exception as e: + self.module.fail_json(msg="Error listing distributions by web acl id = " + str(e), exception=traceback.format_exc(e)) + + def list_invalidations(self, distribution_id): + try: + func = partial(self.client.list_invalidations, DistributionId=distribution_id) + return self.paginated_response(func, 'InvalidationList')['Items'] + except Exception as e: + self.module.fail_json(msg="Error listing invalidations = " + str(e), exception=traceback.format_exc(e)) + + def list_streaming_distributions(self): + try: + func = partial(self.client.list_streaming_distributions) + streaming_distributions = self.paginated_response(func, 'StreamingDistributionList')['Items'] + return self.keyed_list_helper(streaming_distributions) + except Exception as e: + self.module.fail_json(msg="Error listing streaming distributions = " + str(e), exception=traceback.format_exc(e)) + + def get_distribution_id_from_domain_name(self, domain_name): + try: + distribution_id = "" + distributions = self.list_distributions(False) + for dist in distributions: + for alias in dist['Aliases']['Items']: + if str(alias).lower() == domain_name.lower(): + distribution_id = str(dist['Id']) + break + return distribution_id + except Exception as e: + self.module.fail_json(msg="Error getting distribution id from domain name = " + str(e), exception=traceback.format_exc(e)) + + def get_aliases_from_distribution_id(self, distribution_id): + aliases = [] + try: + distributions = self.list_distributions(False) + for dist in distributions: + if dist['Id'] == distribution_id: + for alias in dist['Aliases']['Items']: + aliases.append(alias) + break + return aliases + except Exception as e: + self.module.fail_json(msg="Error getting list of aliases from distribution_id = " + str(e), exception=traceback.format_exc(e)) + + def paginated_response(self, func, result_key=""): + ''' + Returns expanded response for paginated operations. + The 'result_key' is used to define the concatenated results that are combined from each paginated response. + ''' + args = dict() + results = dict() + loop = True + while loop: + response = func(**args) + if result_key == "": + result = response + result.pop('ResponseMetadata', None) + else: + result = response.get(result_key) + results.update(result) + args['NextToken'] = response.get('NextToken') + loop = args['NextToken'] is not None + return results + + def keyed_list_helper(self, list_to_key): + keyed_list = dict() + for item in list_to_key: + aliases = item['Aliases']['Items'] + distribution_id = item['Id'] + keyed_list.update({distribution_id: item}) + for alias in aliases: + keyed_list.update({alias: item}) + return keyed_list + +def main(): + argument_spec = ec2_argument_spec() + argument_spec.update(dict( + distribution_id=dict(required=False, type='str'), + invalidation_id=dict(required=False, type='str'), + origin_access_identity_id=dict(required=False, type='str'), + domain_name_alias=dict(required=False, type='str'), + all_lists=dict(required=False, default=False, type='bool'), + distribution=dict(required=False, default=False, type='bool'), + distribution_config=dict(required=False, default=False, type='bool'), + origin_access_identity=dict(required=False, default=False, type='bool'), + origin_access_identity_config=dict(required=False, default=False, type='bool'), + invalidation=dict(required=False, default=False, type='bool'), + streaming_distribution=dict(required=False, default=False, type='bool'), + streaming_distribution_config=dict(required=False, default=False, type='bool'), + list_origin_access_identities=dict(required=False, default=False, type='bool'), + list_distributions=dict(required=False, default=False, type='bool'), + list_distributions_by_web_acl_id=dict(required=False, default=False, type='bool'), + list_invalidations=dict(required=False, default=False, type='bool'), + list_streaming_distributions=dict(required=False, default=False, type='bool') + )) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=False) + + if not HAS_BOTO3: + module.fail_json(msg='boto3 is required.') + + service_mgr = CloudFrontServiceManager(module) + + distribution_id = module.params.get('distribution_id') + invalidation_id = module.params.get('invalidation_id') + origin_access_identity_id = module.params.get('origin_access_identity_id') + web_acl_id = module.params.get('web_acl_id') + domain_name_alias = module.params.get('domain_name_alias') + + all_lists = module.params.get('all_lists') + + distribution = module.params.get('distribution') + distribution_config = module.params.get('distribution_config') + origin_access_identity = module.params.get('origin_access_identity') + origin_access_identity_config = module.params.get('origin_access_identity_config') + invalidation = module.params.get('invalidation') + streaming_distribution = module.params.get('streaming_distribution') + streaming_distribution_config = module.params.get('streaming_distribution_config') + + list_origin_access_identities = module.params.get('list_origin_access_identities') + list_distributions = module.params.get('list_distributions') + list_distributions_by_web_acl_id = module.params.get('list_distributions_by_web_acl_id'); + list_invalidations = module.params.get('list_invalidations') + list_streaming_distributions = module.params.get('list_streaming_distributions') + + aliases = [] + + require_distribution_id = (distribution or distribution_config or invalidation or + streaming_distribution or streaming_distribution_config or list_invalidations) + + # set default to list_distributions if no option specified + list_distributions = list_distributions or not (distribution or distribution_config or + origin_access_identity or origin_access_identity_config or invalidation or + streaming_distribution or streaming_distribution_config or list_origin_access_identities or + list_distributions_by_web_acl_id or list_invalidations or list_streaming_distributions) + + # validations + if require_distribution_id and distribution_id is None and domain_name_alias is None: + module.fail_json(msg='Error distribution_id or domain_name_alias have not been specified.') + if (invalidation and invalidation_id is None): + module.fail_json(msg='Error invalidation_id has not been specified.') + if (origin_access_identity or origin_access_identity_config) and origin_access_identity_id is None: + module.fail_json(msg='Error origin_access_identity_id has not been specified.') + if list_distributions_by_web_acl_id and web_acl_id is None: + module.fail_json(msg='Error web_acl_id has not been specified.') + + # get distribution id from domain name alias + if require_distribution_id and distribution_id is None: + distribution_id = service_mgr.get_distribution_id_from_domain_name(domain_name_alias) + if not distribution_id: + module.fail_json(msg='Error unable to source a distribution id from domain_name_alias') + + # set appropriate cloudfront id + if distribution_id and not list_invalidations: + result = { 'cloudfront': { distribution_id: {} } } + aliases = service_mgr.get_aliases_from_distribution_id(distribution_id) + for alias in aliases: + result['cloudfront'].update( { alias: {} } ) + if invalidation_id: + result['cloudfront'].update( { invalidation_id: {} } ) + facts = result['cloudfront'] + elif list_invalidations: + result = { 'cloudfront': { 'invalidations': {} } } + facts = result['cloudfront']['invalidations'] + aliases = service_mgr.get_aliases_from_distribution_id(distribution_id) + for alias in aliases: + result['cloudfront']['invalidations'].update( { alias: {} } ) + elif origin_access_identity_id: + result = { 'cloudfront': { origin_access_identity_id: {} } } + facts = result['cloudfront'][origin_access_identity_id] + elif web_acl_id: + result = { 'cloudfront': { web_acl_id: {} } } + facts = result['cloudfront'][web_acl_id] + else: + result = { 'cloudfront': {} } + facts = result['cloudfront'] + + # get details based on options + if distribution: + distribution_details = service_mgr.get_distribution(distribution_id) + facts[distribution_id].update(distribution_details) + for alias in aliases: + facts[alias].update(distribution_details) + if distribution_config: + distribution_config_details = service_mgr.get_distribution_config(distribution_id) + facts[distribution_id].update(distribution_config_details) + for alias in aliases: + facts[alias].update(distribution_config_details) + if origin_access_identity: + facts[origin_access_identity_id].update(service_mgr.get_origin_access_identity(origin_access_identity_id)) + if origin_access_identity_config: + facts[origin_access_identity_id].update(service_mgr.get_origin_access_identity_config(origin_access_identity_id)) + if invalidation: + invalidation = service_mgr.get_invalidation(distribution_id, invalidation_id) + facts[invalidation_id].update(invalidation) + facts[distribution_id].update(invalidation) + for alias in aliases: + facts[alias].update(invalidation) + if streaming_distribution: + streaming_distribution_details = service_mgr.get_streaming_distribution(distribution_id) + facts[distribution_id].update(streaming_distribution_details) + for alias in aliases: + facts[alias].update(streaming_distribution_details) + if streaming_distribution_config: + streaming_distribution_config_details = service_mgr.get_streaming_distribution_config(distribution_id) + facts[distribution_id].update(streaming_distribution_config_details) + for alias in aliases: + facts[alias].update(streaming_distribution_config_details) + if list_invalidations: + invalidations = service_mgr.list_invalidations(distribution_id) + facts[distribution_id].update(invalidations) + for alias in aliases: + facts[alias].update(invalidations) + + # get list based on options + if all_lists or list_origin_access_identities: + facts['origin_access_identities'] = service_mgr.list_origin_access_identities() + if all_lists or list_distributions: + facts['distributions'] = service_mgr.list_distributions() + if all_lists or list_streaming_distributions: + facts['streaming_distributions'] = service_mgr.list_streaming_distributions() + if list_distributions_by_web_acl_id: + facts['distributions_by_web_acl_id'] = service_mgr.list_distributions_by_web_acl_id(web_acl_id) + + result['changed'] = False + module.exit_json(msg="Retrieved cloudfront facts.", ansible_facts=result) + +if __name__ == '__main__': + main()