When calling Ansible modules frequently with similar parameters, module_defaults can save on typing and, almost more importantly, improve on clarity by defining default values for modules I use in a play.

Let’s look at a small example in which I want to clear records for a host from a dynamic DNS server and then add it in again. In all invocations of the nsupdate module I would need to specify key name, algorithm, server, etc., but through the use of module_defaults I can set default values and no longer have to specify these repetitively on individual tasks.

- hosts: all
  vars_files:
     dns-params.yml
  module_defaults:
     community.general.nsupdate:
        key_algorithm: "hmac-sha256"
        key_name: "{{ tsig_key_name }}"
        key_secret: "{{ tsig_key_secret }}"
        server: "{{ dns_server }}"
        protocol: "tcp"
        ttl: 60
        zone: "example.org"

  tasks:
  - name: Clean old records from DNS
    community.general.nsupdate:
      record: "www.example.org."
      state: absent

  - name: Add IPv4 addresses to DNS
    community.general.nsupdate:
      record: "www.example.org."
      type: "A"
      value: "192.0.2.43"
      state: present

What I wasn’t aware of and learned today via Ton, is that the module defaults don’t have to be statically specified; they can also be provided via a lookup plugin. I whipped up a simple example to illustrate this.

The lookup plugin (md.py) returns an array containing a single dictionary of values. All keys returned must be valid parameters for the module I intend to use these with. For example, if params contained a key called package, the module would fail as “package” is not (currently) a valid parameter for copy.

from ansible.plugins.lookup import LookupBase

class LookupModule(LookupBase):
    def run(self, terms, variables=None, **kwargs):

        params = { 
            "content" : "Rijsttafel",
            "mode" : "0400",
        }

        return [ params ]

The playbook shows how this plugin is used to feed module_defaults for the copy module, and it then invokes copy twice:

- hosts: localhost
  module_defaults:
      copy: "{{ lookup('md') }}"
  tasks:
  - copy: content="Hello" dest="a" mode=0444
  - copy: dest="b"

The result: two files with distinct permissions and content:

$ ls -l ?
-r--r--r--  1 jpm  staff   5 Oct  5 18:32 a
-r--------  1 jpm  staff  10 Oct  5 18:32 b

$ cat a
Hello

$ cat b
Rijsttafel

The second file b has permissions 0400 and content “Rijsttafel” set from the lookup, and both have these defaults overridden for file a.

The small lookup plugin above doesn’t use terms or kwargs but easily could to return, say, a different class of values, etc.