1#!/usr/local/bin/python3.8
2# Copyright: Ansible Project
3# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
4
5from __future__ import absolute_import, division, print_function
6__metaclass__ = type
7
8
9DOCUMENTATION = r'''
10module: route53_info
11short_description: Retrieves route53 details using AWS methods
12version_added: 1.0.0
13description:
14    - Gets various details related to Route53 zone, record set or health check details.
15    - This module was called C(route53_facts) before Ansible 2.9. The usage did not change.
16options:
17  query:
18    description:
19      - Specifies the query action to take.
20    required: True
21    choices: [
22            'change',
23            'checker_ip_range',
24            'health_check',
25            'hosted_zone',
26            'record_sets',
27            'reusable_delegation_set',
28            ]
29    type: str
30  change_id:
31    description:
32      - The ID of the change batch request.
33      - The value that you specify here is the value that
34        ChangeResourceRecordSets returned in the Id element
35        when you submitted the request.
36      - Required if I(query=change).
37    required: false
38    type: str
39  hosted_zone_id:
40    description:
41      - The Hosted Zone ID of the DNS zone.
42      - Required if I(query) is set to I(hosted_zone) and I(hosted_zone_method) is set to I(details).
43      - Required if I(query) is set to I(record_sets).
44    required: false
45    type: str
46  max_items:
47    description:
48      - Maximum number of items to return for various get/list requests.
49    required: false
50    type: str
51  next_marker:
52    description:
53      - "Some requests such as list_command: hosted_zones will return a maximum
54        number of entries - EG 100 or the number specified by I(max_items).
55        If the number of entries exceeds this maximum another request can be sent
56        using the NextMarker entry from the first response to get the next page
57        of results."
58    required: false
59    type: str
60  delegation_set_id:
61    description:
62      - The DNS Zone delegation set ID.
63    required: false
64    type: str
65  start_record_name:
66    description:
67      - "The first name in the lexicographic ordering of domain names that you want
68        the list_command: record_sets to start listing from."
69    required: false
70    type: str
71  type:
72    description:
73      - The type of DNS record.
74    required: false
75    choices: [ 'A', 'CNAME', 'MX', 'AAAA', 'TXT', 'PTR', 'SRV', 'SPF', 'CAA', 'NS' ]
76    type: str
77  dns_name:
78    description:
79      - The first name in the lexicographic ordering of domain names that you want
80        the list_command to start listing from.
81    required: false
82    type: str
83  resource_id:
84    description:
85      - The ID/s of the specified resource/s.
86      - Required if I(query=health_check) and I(health_check_method=tags).
87      - Required if I(query=hosted_zone) and I(hosted_zone_method=tags).
88    required: false
89    aliases: ['resource_ids']
90    type: list
91    elements: str
92  health_check_id:
93    description:
94      - The ID of the health check.
95      - Required if C(query) is set to C(health_check) and
96        C(health_check_method) is set to C(details) or C(status) or C(failure_reason).
97    required: false
98    type: str
99  hosted_zone_method:
100    description:
101      - "This is used in conjunction with query: hosted_zone.
102        It allows for listing details, counts or tags of various
103        hosted zone details."
104    required: false
105    choices: [
106        'details',
107        'list',
108        'list_by_name',
109        'count',
110        'tags',
111        ]
112    default: 'list'
113    type: str
114  health_check_method:
115    description:
116      - "This is used in conjunction with query: health_check.
117        It allows for listing details, counts or tags of various
118        health check details."
119    required: false
120    choices: [
121        'list',
122        'details',
123        'status',
124        'failure_reason',
125        'count',
126        'tags',
127        ]
128    default: 'list'
129    type: str
130author: Karen Cheng (@Etherdaemon)
131extends_documentation_fragment:
132- amazon.aws.aws
133- amazon.aws.ec2
134
135'''
136
137EXAMPLES = r'''
138# Simple example of listing all hosted zones
139- name: List all hosted zones
140  community.aws.route53_info:
141    query: hosted_zone
142  register: hosted_zones
143
144# Getting a count of hosted zones
145- name: Return a count of all hosted zones
146  community.aws.route53_info:
147    query: hosted_zone
148    hosted_zone_method: count
149  register: hosted_zone_count
150
151- name: List the first 20 resource record sets in a given hosted zone
152  community.aws.route53_info:
153    profile: account_name
154    query: record_sets
155    hosted_zone_id: ZZZ1111112222
156    max_items: 20
157  register: record_sets
158
159- name: List first 20 health checks
160  community.aws.route53_info:
161    query: health_check
162    health_check_method: list
163    max_items: 20
164  register: health_checks
165
166- name: Get health check last failure_reason
167  community.aws.route53_info:
168    query: health_check
169    health_check_method: failure_reason
170    health_check_id: 00000000-1111-2222-3333-12345678abcd
171  register: health_check_failure_reason
172
173- name: Retrieve reusable delegation set details
174  community.aws.route53_info:
175    query: reusable_delegation_set
176    delegation_set_id: delegation id
177  register: delegation_sets
178
179- name: setup of example for using next_marker
180  community.aws.route53_info:
181    query: hosted_zone
182    max_items: 1
183  register: first_info
184
185- name: example for using next_marker
186  community.aws.route53_info:
187    query: hosted_zone
188    next_marker: "{{ first_info.NextMarker }}"
189    max_items: 1
190  when: "{{ 'NextMarker' in first_info }}"
191
192- name: retrieve host entries starting with host1.workshop.test.io
193  block:
194    - name: grab zone id
195      community.aws.route53_zone:
196        zone: "test.io"
197      register: AWSINFO
198
199    - name: grab Route53 record information
200      community.aws.route53_info:
201        type: A
202        query: record_sets
203        hosted_zone_id: "{{ AWSINFO.zone_id }}"
204        start_record_name: "host1.workshop.test.io"
205      register: RECORDS
206'''
207
208try:
209    import botocore
210except ImportError:
211    pass  # Handled by AnsibleAWSModule
212
213from ansible.module_utils._text import to_native
214
215from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule
216
217
218def get_hosted_zone(client, module):
219    params = dict()
220
221    if module.params.get('hosted_zone_id'):
222        params['Id'] = module.params.get('hosted_zone_id')
223    else:
224        module.fail_json(msg="Hosted Zone Id is required")
225
226    return client.get_hosted_zone(**params)
227
228
229def reusable_delegation_set_details(client, module):
230    params = dict()
231    if not module.params.get('delegation_set_id'):
232        if module.params.get('max_items'):
233            params['MaxItems'] = module.params.get('max_items')
234
235        if module.params.get('next_marker'):
236            params['Marker'] = module.params.get('next_marker')
237
238        results = client.list_reusable_delegation_sets(**params)
239    else:
240        params['DelegationSetId'] = module.params.get('delegation_set_id')
241        results = client.get_reusable_delegation_set(**params)
242
243    return results
244
245
246def list_hosted_zones(client, module):
247    params = dict()
248
249    if module.params.get('max_items'):
250        params['MaxItems'] = module.params.get('max_items')
251
252    if module.params.get('next_marker'):
253        params['Marker'] = module.params.get('next_marker')
254
255    if module.params.get('delegation_set_id'):
256        params['DelegationSetId'] = module.params.get('delegation_set_id')
257
258    paginator = client.get_paginator('list_hosted_zones')
259    zones = paginator.paginate(**params).build_full_result()['HostedZones']
260    return {
261        "HostedZones": zones,
262        "list": zones,
263    }
264
265
266def list_hosted_zones_by_name(client, module):
267    params = dict()
268
269    if module.params.get('hosted_zone_id'):
270        params['HostedZoneId'] = module.params.get('hosted_zone_id')
271
272    if module.params.get('dns_name'):
273        params['DNSName'] = module.params.get('dns_name')
274
275    if module.params.get('max_items'):
276        params['MaxItems'] = module.params.get('max_items')
277
278    return client.list_hosted_zones_by_name(**params)
279
280
281def change_details(client, module):
282    params = dict()
283
284    if module.params.get('change_id'):
285        params['Id'] = module.params.get('change_id')
286    else:
287        module.fail_json(msg="change_id is required")
288
289    results = client.get_change(**params)
290    return results
291
292
293def checker_ip_range_details(client, module):
294    return client.get_checker_ip_ranges()
295
296
297def get_count(client, module):
298    if module.params.get('query') == 'health_check':
299        results = client.get_health_check_count()
300    else:
301        results = client.get_hosted_zone_count()
302
303    return results
304
305
306def get_health_check(client, module):
307    params = dict()
308
309    if not module.params.get('health_check_id'):
310        module.fail_json(msg="health_check_id is required")
311    else:
312        params['HealthCheckId'] = module.params.get('health_check_id')
313
314    if module.params.get('health_check_method') == 'details':
315        results = client.get_health_check(**params)
316    elif module.params.get('health_check_method') == 'failure_reason':
317        results = client.get_health_check_last_failure_reason(**params)
318    elif module.params.get('health_check_method') == 'status':
319        results = client.get_health_check_status(**params)
320
321    return results
322
323
324def get_resource_tags(client, module):
325    params = dict()
326
327    if module.params.get('resource_id'):
328        params['ResourceIds'] = module.params.get('resource_id')
329    else:
330        module.fail_json(msg="resource_id or resource_ids is required")
331
332    if module.params.get('query') == 'health_check':
333        params['ResourceType'] = 'healthcheck'
334    else:
335        params['ResourceType'] = 'hostedzone'
336
337    return client.list_tags_for_resources(**params)
338
339
340def list_health_checks(client, module):
341    params = dict()
342
343    if module.params.get('max_items'):
344        params['MaxItems'] = module.params.get('max_items')
345
346    if module.params.get('next_marker'):
347        params['Marker'] = module.params.get('next_marker')
348
349    paginator = client.get_paginator('list_health_checks')
350    health_checks = paginator.paginate(**params).build_full_result()['HealthChecks']
351    return {
352        "HealthChecks": health_checks,
353        "list": health_checks,
354    }
355
356
357def record_sets_details(client, module):
358    params = dict()
359
360    if module.params.get('hosted_zone_id'):
361        params['HostedZoneId'] = module.params.get('hosted_zone_id')
362    else:
363        module.fail_json(msg="Hosted Zone Id is required")
364
365    if module.params.get('max_items'):
366        params['MaxItems'] = module.params.get('max_items')
367
368    if module.params.get('start_record_name'):
369        params['StartRecordName'] = module.params.get('start_record_name')
370
371    if module.params.get('type') and not module.params.get('start_record_name'):
372        module.fail_json(msg="start_record_name must be specified if type is set")
373    elif module.params.get('type'):
374        params['StartRecordType'] = module.params.get('type')
375
376    paginator = client.get_paginator('list_resource_record_sets')
377    record_sets = paginator.paginate(**params).build_full_result()['ResourceRecordSets']
378    return {
379        "ResourceRecordSets": record_sets,
380        "list": record_sets,
381    }
382
383
384def health_check_details(client, module):
385    health_check_invocations = {
386        'list': list_health_checks,
387        'details': get_health_check,
388        'status': get_health_check,
389        'failure_reason': get_health_check,
390        'count': get_count,
391        'tags': get_resource_tags,
392    }
393
394    results = health_check_invocations[module.params.get('health_check_method')](client, module)
395    return results
396
397
398def hosted_zone_details(client, module):
399    hosted_zone_invocations = {
400        'details': get_hosted_zone,
401        'list': list_hosted_zones,
402        'list_by_name': list_hosted_zones_by_name,
403        'count': get_count,
404        'tags': get_resource_tags,
405    }
406
407    results = hosted_zone_invocations[module.params.get('hosted_zone_method')](client, module)
408    return results
409
410
411def main():
412    argument_spec = dict(
413        query=dict(choices=[
414            'change',
415            'checker_ip_range',
416            'health_check',
417            'hosted_zone',
418            'record_sets',
419            'reusable_delegation_set',
420        ], required=True),
421        change_id=dict(),
422        hosted_zone_id=dict(),
423        max_items=dict(),
424        next_marker=dict(),
425        delegation_set_id=dict(),
426        start_record_name=dict(),
427        type=dict(choices=[
428            'A', 'CNAME', 'MX', 'AAAA', 'TXT', 'PTR', 'SRV', 'SPF', 'CAA', 'NS'
429        ]),
430        dns_name=dict(),
431        resource_id=dict(type='list', aliases=['resource_ids'], elements='str'),
432        health_check_id=dict(),
433        hosted_zone_method=dict(choices=[
434            'details',
435            'list',
436            'list_by_name',
437            'count',
438            'tags'
439        ], default='list'),
440        health_check_method=dict(choices=[
441            'list',
442            'details',
443            'status',
444            'failure_reason',
445            'count',
446            'tags',
447        ], default='list'),
448    )
449
450    module = AnsibleAWSModule(
451        argument_spec=argument_spec,
452        supports_check_mode=True,
453        mutually_exclusive=[
454            ['hosted_zone_method', 'health_check_method'],
455        ],
456        check_boto3=False,
457    )
458    if module._name == 'route53_facts':
459        module.deprecate("The 'route53_facts' module has been renamed to 'route53_info'", date='2021-12-01', collection_name='community.aws')
460
461    try:
462        route53 = module.client('route53')
463    except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
464        module.fail_json_aws(e, msg='Failed to connect to AWS')
465
466    invocations = {
467        'change': change_details,
468        'checker_ip_range': checker_ip_range_details,
469        'health_check': health_check_details,
470        'hosted_zone': hosted_zone_details,
471        'record_sets': record_sets_details,
472        'reusable_delegation_set': reusable_delegation_set_details,
473    }
474
475    results = dict(changed=False)
476    try:
477        results = invocations[module.params.get('query')](route53, module)
478    except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
479        module.fail_json(msg=to_native(e))
480
481    module.exit_json(**results)
482
483
484if __name__ == '__main__':
485    main()
486