Server IP : 85.214.239.14 / Your IP : 3.138.137.114 Web Server : Apache/2.4.62 (Debian) System : Linux h2886529.stratoserver.net 4.9.0 #1 SMP Tue Jan 9 19:45:01 MSK 2024 x86_64 User : www-data ( 33) PHP Version : 7.4.18 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare, MySQL : OFF | cURL : OFF | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /lib/python3/dist-packages/ansible_collections/community/aws/plugins/modules/ |
Upload File : |
#!/usr/bin/python # Copyright: Ansible Project # 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 DOCUMENTATION = ''' --- module: api_gateway_domain short_description: Manage AWS API Gateway custom domains description: - Manages API Gateway custom domains for API GW Rest APIs. - AWS API Gateway custom domain setups use CloudFront behind the scenes. So you will get a CloudFront distribution as a result, configured to be aliased with your domain. - Prior to release 5.0.0 this module was called C(community.aws.aws_api_gateway_domain). The usage did not change. version_added: '3.3.0' author: - 'Stefan Horning (@stefanhorning)' options: domain_name: description: - Domain name you want to use for your API GW deployment. required: true type: str certificate_arn: description: - AWS Certificate Manger (ACM) TLS certificate ARN. type: str required: true security_policy: description: - Set allowed TLS versions through AWS defined policies. Currently only C(TLS_1_0) and C(TLS_1_2) are available. default: TLS_1_2 choices: ['TLS_1_0', 'TLS_1_2'] type: str endpoint_type: description: - API endpoint configuration for domain. Use EDGE for edge-optimized endpoint, or use C(REGIONAL) or C(PRIVATE). default: EDGE choices: ['EDGE', 'REGIONAL', 'PRIVATE'] type: str domain_mappings: description: - Map your domain base paths to your API GW REST APIs, that you previously created. Use provided ID of the API setup and the release stage. - "domain_mappings should be a list of dictionaries containing three keys: base_path, rest_api_id and stage." - "Example: I([{ base_path: v1, rest_api_id: abc123, stage: production }])" - if you want base path to be just I(/) omit the param completely or set it to empty string. required: true type: list elements: dict state: description: - Create or delete custom domain setup. default: present choices: [ 'present', 'absent' ] type: str extends_documentation_fragment: - amazon.aws.aws - amazon.aws.ec2 - amazon.aws.boto3 notes: - Does not create a DNS entry on Route53, for that use the M(community.aws.route53) module. - Only supports TLS certificates from AWS ACM that can just be referenced by the ARN, while the AWS API still offers (deprecated) options to add own Certificates. ''' EXAMPLES = ''' - name: Setup endpoint for a custom domain for your API Gateway HTTP API community.aws.api_gateway_domain: domain_name: myapi.foobar.com certificate_arn: 'arn:aws:acm:us-east-1:1231123123:certificate/8bd89412-abc123-xxxxx' security_policy: TLS_1_2 endpoint_type: EDGE domain_mappings: - { rest_api_id: abc123, stage: production } state: present register: api_gw_domain_result - name: Create a DNS record for your custom domain on route 53 (using route53 module) community.aws.route53: record: myapi.foobar.com value: "{{ api_gw_domain_result.response.domain.distribution_domain_name }}" type: A alias: true zone: foobar.com alias_hosted_zone_id: "{{ api_gw_domain_result.response.domain.distribution_hosted_zone_id }}" command: create ''' RETURN = ''' response: description: The data returned by create_domain_name (or update and delete) and create_base_path_mapping methods by boto3. returned: success type: dict sample: domain: { domain_name: mydomain.com, certificate_arn: 'arn:aws:acm:xxxxxx', distribution_domain_name: xxxx.cloudfront.net, distribution_hosted_zone_id: ABC123123, endpoint_configuration: { types: ['EDGE'] }, domain_name_status: 'AVAILABLE', security_policy: TLS_1_2, tags: {} } path_mappings: [ { base_path: '(empty)', rest_api_id: 'abcd123', stage: 'production' } ] ''' try: from botocore.exceptions import ClientError, BotoCoreError, EndpointConnectionError except ImportError: pass # caught by imported AnsibleAWSModule import copy from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule, is_boto3_error_code from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict, snake_dict_to_camel_dict def get_domain(module, client): domain_name = module.params.get('domain_name') result = {} try: result['domain'] = get_domain_name(client, domain_name) result['path_mappings'] = get_domain_mappings(client, domain_name) except is_boto3_error_code('NotFoundException'): return None except (ClientError, BotoCoreError, EndpointConnectionError) as e: # pylint: disable=duplicate-except module.fail_json_aws(e, msg="getting API GW domain") return camel_dict_to_snake_dict(result) def create_domain(module, client): path_mappings = module.params.get('domain_mappings', []) domain_name = module.params.get('domain_name') result = {'domain': {}, 'path_mappings': []} try: result['domain'] = create_domain_name( module, client, domain_name, module.params.get('certificate_arn'), module.params.get('endpoint_type'), module.params.get('security_policy') ) for mapping in path_mappings: base_path = mapping.get('base_path', '') rest_api_id = mapping.get('rest_api_id') stage = mapping.get('stage') if rest_api_id is None or stage is None: module.fail_json('Every domain mapping needs a rest_api_id and stage name') result['path_mappings'].append(add_domain_mapping(client, domain_name, base_path, rest_api_id, stage)) except (ClientError, BotoCoreError, EndpointConnectionError) as e: module.fail_json_aws(e, msg="creating API GW domain") return camel_dict_to_snake_dict(result) def update_domain(module, client, existing_domain): domain_name = module.params.get('domain_name') result = existing_domain result['updated'] = False domain = existing_domain.get('domain') # Compare only relevant set of domain arguments. # As get_domain_name gathers all kind of state information that can't be set anyways. # Also this module doesn't support custom TLS cert setup params as they are kind of deprecated already and would increase complexity. existing_domain_settings = { 'certificate_arn': domain.get('certificate_arn'), 'security_policy': domain.get('security_policy'), 'endpoint_type': domain.get('endpoint_configuration').get('types')[0] } specified_domain_settings = { 'certificate_arn': module.params.get('certificate_arn'), 'security_policy': module.params.get('security_policy'), 'endpoint_type': module.params.get('endpoint_type') } if specified_domain_settings != existing_domain_settings: try: result['domain'] = update_domain_name(client, domain_name, **snake_dict_to_camel_dict(specified_domain_settings)) result['updated'] = True except (ClientError, BotoCoreError, EndpointConnectionError) as e: module.fail_json_aws(e, msg="updating API GW domain") existing_mappings = copy.deepcopy(existing_domain.get('path_mappings', [])) # Cleanout `base_path: "(none)"` elements from dicts as those won't match with specified mappings for mapping in existing_mappings: if mapping.get('base_path', 'missing') == '(none)': mapping.pop('base_path') specified_mappings = copy.deepcopy(module.params.get('domain_mappings', [])) # Cleanout `base_path: ""` elements from dicts as those won't match with existing mappings for mapping in specified_mappings: if mapping.get('base_path', 'missing') == '': mapping.pop('base_path') if specified_mappings != existing_mappings: try: # When lists missmatch delete all existing mappings before adding new ones as specified for mapping in existing_domain.get('path_mappings', []): delete_domain_mapping(client, domain_name, mapping['base_path']) for mapping in module.params.get('domain_mappings', []): result['path_mappings'] = add_domain_mapping( client, domain_name, mapping.get('base_path', ''), mapping.get('rest_api_id'), mapping.get('stage') ) result['updated'] = True except (ClientError, BotoCoreError, EndpointConnectionError) as e: module.fail_json_aws(e, msg="updating API GW domain mapping") return camel_dict_to_snake_dict(result) def delete_domain(module, client): domain_name = module.params.get('domain_name') try: result = delete_domain_name(client, domain_name) except (ClientError, BotoCoreError, EndpointConnectionError) as e: module.fail_json_aws(e, msg="deleting API GW domain") return camel_dict_to_snake_dict(result) retry_params = {"delay": 5, "backoff": 1.2} @AWSRetry.jittered_backoff(**retry_params) def get_domain_name(client, domain_name): return client.get_domain_name(domainName=domain_name) @AWSRetry.jittered_backoff(**retry_params) def get_domain_mappings(client, domain_name): return client.get_base_path_mappings(domainName=domain_name, limit=200).get('items', []) @AWSRetry.jittered_backoff(**retry_params) def create_domain_name(module, client, domain_name, certificate_arn, endpoint_type, security_policy): endpoint_configuration = {'types': [endpoint_type]} if endpoint_type == 'EDGE': return client.create_domain_name( domainName=domain_name, certificateArn=certificate_arn, endpointConfiguration=endpoint_configuration, securityPolicy=security_policy ) else: # Use regionalCertificateArn for regional domain deploys return client.create_domain_name( domainName=domain_name, regionalCertificateArn=certificate_arn, endpointConfiguration=endpoint_configuration, securityPolicy=security_policy ) @AWSRetry.jittered_backoff(**retry_params) def add_domain_mapping(client, domain_name, base_path, rest_api_id, stage): return client.create_base_path_mapping(domainName=domain_name, basePath=base_path, restApiId=rest_api_id, stage=stage) @AWSRetry.jittered_backoff(**retry_params) def update_domain_name(client, domain_name, **kwargs): patch_operations = [] for key, value in kwargs.items(): path = "/" + key if key == "endpointType": continue patch_operations.append({"op": "replace", "path": path, "value": value}) return client.update_domain_name(domainName=domain_name, patchOperations=patch_operations) @AWSRetry.jittered_backoff(**retry_params) def delete_domain_name(client, domain_name): return client.delete_domain_name(domainName=domain_name) @AWSRetry.jittered_backoff(**retry_params) def delete_domain_mapping(client, domain_name, base_path): return client.delete_base_path_mapping(domainName=domain_name, basePath=base_path) def main(): argument_spec = dict( domain_name=dict(type='str', required=True), certificate_arn=dict(type='str', required=True), security_policy=dict(type='str', default='TLS_1_2', choices=['TLS_1_0', 'TLS_1_2']), endpoint_type=dict(type='str', default='EDGE', choices=['EDGE', 'REGIONAL', 'PRIVATE']), domain_mappings=dict(type='list', required=True, elements='dict'), state=dict(type='str', default='present', choices=['present', 'absent']) ) module = AnsibleAWSModule( argument_spec=argument_spec, supports_check_mode=False ) client = module.client('apigateway') state = module.params.get('state') changed = False if state == "present": existing_domain = get_domain(module, client) if existing_domain is not None: result = update_domain(module, client, existing_domain) changed = result['updated'] else: result = create_domain(module, client) changed = True if state == "absent": result = delete_domain(module, client) changed = True exit_args = {"changed": changed} if result is not None: exit_args['response'] = result module.exit_json(**exit_args) if __name__ == '__main__': main()