community.general icon indicating copy to clipboard operation
community.general copied to clipboard

lookup('dig', arg1) should be able to process lists

Open opoplawski opened this issue 3 years ago • 12 comments

SUMMARY

lookup('dig', arg) should be able to process arguments that are lists to names to lookup.

ISSUE TYPE
  • Feature Idea
COMPONENT NAME

lookup dig

ADDITIONAL INFORMATION

See also https://github.com/ansible/ansible/issues/20470

There was a suggestion there (https://github.com/ansible/ansible/issues/20470#issuecomment-443220060) that you could do:

lookup('dig', *list)

but that does not work for me - it only returns the IP of the last item in the list.

opoplawski avatar Sep 03 '20 21:09 opoplawski

Files identified in the description:

If these files are inaccurate, please update the component name section of the description or use the !component bot command.

click here for bot help

ansibullbot avatar Sep 03 '20 21:09 ansibullbot

cc @jpmens click here for bot help

ansibullbot avatar Sep 03 '20 21:09 ansibullbot

@opoplawski Following works for me -

---
- hosts: localhost
  vars:
    servers:
    - google.com
    - yahoo.com
  tasks:
    - debug:
        msg: "{{ lookup('dig', *servers) }}"
ok: [localhost] => {
    "msg": "74.6.231.21,74.6.143.26,98.137.11.163,74.6.143.25,98.137.11.164,74.6.231.20"
}

needs_info

Akasurde avatar Jun 11 '21 13:06 Akasurde

The code only resolves one DNS name, so the last one wins: https://github.com/ansible-collections/community.general/blob/main/plugins/lookup/dig.py#L311 This is consistent with the result @Akasurde shows, all six IP addresses resolve to Yahoo servers (host -t NS yahoo.com only gives me five nameservers though), and the four nameservers google.com is using have different IPs.

felixfontein avatar Jun 11 '21 20:06 felixfontein

Correct.

Akasurde avatar Jun 12 '21 06:06 Akasurde

@opoplawski This issue is waiting for your response. Please respond or the issue will be closed.

click here for bot help

ansibullbot avatar Sep 13 '21 19:09 ansibullbot

I think this is still a valid request.

opoplawski avatar Sep 13 '21 19:09 opoplawski

why not loop: "{{ servers }}"?

russoz avatar Oct 03 '21 11:10 russoz

Just lost an hour wondering why I was not getting the correct results. The documentation explicitly states that this module will resolve the domain_(s)_ provided: https://github.com/ansible-collections/community.general/blob/5470ea30dce806dc6c9d766baa57a9ea57aabcd2/plugins/lookup/dig.py#L29-L31

https://github.com/ansible/ansible/issues/20470#issuecomment-443220060 suggests that this is a regression and that it worked before.

bendem avatar Sep 12 '22 11:09 bendem

It was never intended by me that dig be able to query multiple domains, and the description clearly states

The dig lookup runs queries against DNS servers to retrieve DNS records for a specific name

I think the (s) in domains for the _terms parameter is a typo.

jpmens avatar Sep 12 '22 11:09 jpmens

I'd be happy to take a stab at this. I'm guessing we want to keep the pre-processing phase so that all options (qtype=, etc) apply to previous terms as well as following ones (as is currently the case). Special care has to be taken if multiple qtype are specified (i.e. 'google.com', 'yahoo.com/TXT').

Maybe it should be an error to specify the same option twice to avoid the surprise where 'qtype=TXT', 'google.com', 'qtype=A', 'yahoo.com' doesn't error, but doesn't do what one would expect just reading it.

Here is a basic (absolutely untested) patch:

diff --git a/plugins/lookup/dig.py b/plugins/lookup/dig.py
--- plugins/lookup/dig.py
+++ plugins/lookup/dig.py
@@ -280,9 +280,9 @@
         myres = dns.resolver.Resolver(configure=True)
         edns_size = 4096
         myres.use_edns(0, ednsflags=dns.flags.DO, payload=edns_size)
 
-        domain = None
+        domains = []
         qtype = 'A'
         flat = True
         fail_on_error = False
         rdclass = dns.rdataclass.from_text('IN')
@@ -330,61 +330,67 @@
 
             if '/' in t:
                 try:
                     domain, qtype = t.split('/')
+                    domains.append((domain, qtype))
                 except Exception:
-                    domain = t
+                    domains.append(t)
             else:
-                domain = t
+                domains.append(t)
 
         # print "--- domain = {0} qtype={1} rdclass={2}".format(domain, qtype, rdclass)
 
         ret = []
 
-        if qtype.upper() == 'PTR':
+        for domain in domains:
+            current_qtype = qtype
+            if isinstance(domain, tuple):
+                domain, current_qtype = domain
+
+            if current_qtype.upper() == 'PTR':
+                try:
+                    n = dns.reversename.from_address(domain)
+                    domain = n.to_text()
+                except dns.exception.SyntaxError:
+                    pass
+                except Exception as e:
+                    raise AnsibleError("dns.reversename unhandled exception %s" % to_native(e))
+
             try:
-                n = dns.reversename.from_address(domain)
-                domain = n.to_text()
-            except dns.exception.SyntaxError:
-                pass
-            except Exception as e:
-                raise AnsibleError("dns.reversename unhandled exception %s" % to_native(e))
+                answers = myres.query(domain, current_qtype, rdclass=rdclass)
+                for rdata in answers:
+                    s = rdata.to_text()
+                    if current_qtype.upper() == 'TXT':
+                        s = s[1:-1]  # Strip outside quotes on TXT rdata
 
-        try:
-            answers = myres.query(domain, qtype, rdclass=rdclass)
-            for rdata in answers:
-                s = rdata.to_text()
-                if qtype.upper() == 'TXT':
-                    s = s[1:-1]  # Strip outside quotes on TXT rdata
+                    if flat:
+                        ret.append(s)
+                    else:
+                        try:
+                            rd = make_rdata_dict(rdata)
+                            rd['owner'] = answers.canonical_name.to_text()
+                            rd['type'] = dns.rdatatype.to_text(rdata.rdtype)
+                            rd['ttl'] = answers.rrset.ttl
+                            rd['class'] = dns.rdataclass.to_text(rdata.rdclass)
 
-                if flat:
-                    ret.append(s)
-                else:
-                    try:
-                        rd = make_rdata_dict(rdata)
-                        rd['owner'] = answers.canonical_name.to_text()
-                        rd['type'] = dns.rdatatype.to_text(rdata.rdtype)
-                        rd['ttl'] = answers.rrset.ttl
-                        rd['class'] = dns.rdataclass.to_text(rdata.rdclass)
+                            ret.append(rd)
+                        except Exception as err:
+                            if fail_on_error:
+                                raise AnsibleError("Lookup failed: %s" % str(err))
+                            ret.append(str(err))
 
-                        ret.append(rd)
-                    except Exception as err:
-                        if fail_on_error:
-                            raise AnsibleError("Lookup failed: %s" % str(err))
-                        ret.append(str(err))
+            except dns.resolver.NXDOMAIN as err:
+                if fail_on_error:
+                    raise AnsibleError("Lookup failed: %s" % str(err))
+                ret.append('NXDOMAIN')
+            except dns.resolver.NoAnswer as err:
+                if fail_on_error:
+                    raise AnsibleError("Lookup failed: %s" % str(err))
+                ret.append("")
+            except dns.resolver.Timeout as err:
+                if fail_on_error:
+                    raise AnsibleError("Lookup failed: %s" % str(err))
+                ret.append('')
+            except dns.exception.DNSException as err:
+                raise AnsibleError("dns.resolver unhandled exception %s" % to_native(err))
 
-        except dns.resolver.NXDOMAIN as err:
-            if fail_on_error:
-                raise AnsibleError("Lookup failed: %s" % str(err))
-            ret.append('NXDOMAIN')
-        except dns.resolver.NoAnswer as err:
-            if fail_on_error:
-                raise AnsibleError("Lookup failed: %s" % str(err))
-            ret.append("")
-        except dns.resolver.Timeout as err:
-            if fail_on_error:
-                raise AnsibleError("Lookup failed: %s" % str(err))
-            ret.append('')
-        except dns.exception.DNSException as err:
-            raise AnsibleError("dns.resolver unhandled exception %s" % to_native(err))
-
         return ret

bendem avatar Sep 12 '22 12:09 bendem

I'd also like to re-iterate that this wouldn't be required if there was another way to apply a lookup to a list (being able to write somelist | map('lookup', 'dig') would avoid having to solve this over and over again).

bendem avatar Sep 12 '22 12:09 bendem