How to validate the arguments for a nested dictionary

See: How to validate the arguments for a nested dictionary · Issue #2665 · ansible/ansible-documentation · GitHub

SUMMARY

I can’t find an example in the role argument validation documentation for how to document/validate a dict that contains dicts (nested dict)

For example:

{
“firewalld_zones”: {
“public”: {
“target”: “default”,
“services”: [
“audit”,
“bacula-client”,
“ssh”,
“snmp”,
“ldap”,
“ldaps”,
“nfs”,
“ntp”,
“pop3”,
“pop3s”,
“smtp”,
“smtps”,
“smtp-submission”,
“snmp”,
“snmptrap”
],
“rich_rules”: [
“rule family="ipv4" source address="192.168.1.0/24" port port="22" protocol="tcp" accept”
],
“ports”: [
“80/tcp”
],
“protocols”: [
“tcp”
],
“forward”: false,
“masquerade”: false,
“sources”: [
“192.168.1.0/24”
],
“source_ports”: [
“80/tcp”
],
“interfaces”: [
“ens192”
],
“forward_ports”: [
{
“port”: 443,
“proto”: “tcp”,
“toport”: 8000,
“toaddr”: “10.0.13.0”
}
],
“icmp-block-inversion”: false,
“icmp_blocks”: [
“echo-request”
]
}
}
}


firewalld_zones:
public:
target: default
services:
- audit
- bacula-client
- ssh
- snmp
- ldap
- ldaps
- nfs
- ntp
- pop3
- pop3s
- smtp
- smtps
- smtp-submission
- snmp
- snmptrap
rich_rules:
- rule family=“ipv4” source address=“192.168.1.0/24” port port=“22” protocol=“tcp”
accept
ports:
- 80/tcp
protocols:
- tcp
forward: false
masquerade: false
sources:
- 192.168.1.0/24
source_ports:
- 80/tcp
interfaces:
- ens192
forward_ports:
- port: 443
proto: tcp
toport: 8000
toaddr: 10.0.13.0
icmp-block-inversion: false
icmp_blocks:
- echo-request

The problem I’m having is that the name for the option is a key so it will be different every time but it has options as well.

firewalld_zones:
type: dict
description: ‘A dictionary containing the zones to be managed.’
options:
WHAT_GOES_HERE: (can’t be ‘public’, ‘drop’, etc since those are free-form dictionary keys.
type: dict

Roles — Ansible Community Documentation

First please note that you should format the data as code with three backticks, otherwise it’s pretty much unreadable.

The problem I’m having is that the name for the option is a key so it will be different every time but it has options as well.

There’s no way to validate that with role (and module/plugin) argument spec validation.

The best way is to rearrange the data to not have a list of dictionaries, where name (or something else) is one key in the element dictionaries. Like


firewalld_zones:
  - name: public
    target: default
    services:
    ...
2 Likes

There’s no way to validate that with role (and module/plugin) argument spec validation.

Why exactly can’t it be done if all the dictionary keys are listed in the argument spec? I think I have done this for a few roles… :woman_shrugging:

If the dictionary keys can be anything, they can’t be listed in a static argument spec (for roles or modules). It’s the same limitation described in New role argument spec doesn't support arbitrary keys · Issue #74001 · ansible/ansible · GitHub, the feature has never existed.

Another option could be validating it’s a dictionary in the argument spec:

# meta/argument_specs.yml
argument_specs:
  main:
    firewalld_zones:
      type: dict
      default: {}  # documentation only, matches the value in defaults/main.yml

and use a dynamic argument spec with a validate_argument_spec task in the role entrypoint:

# tasks/main.yml

- name: Validate firewalld_zones
  validate_argument_spec:
    argument_spec: "{{ dynamic_spec }}"
  vars:
    dynamic_spec:
      firewalld_zones:
        type: dict
        options: "{{ dict(firewalld_zones | zip([filewalld_zone_value_spec] * (firewalld_zones | length))) }}"
    filewalld_zone_value_spec:
      type: dict
      options:
        forward:
          type: bool
        forward_ports:
          type: list
          elements: dict
          options:
            port:
              type: int
            proto:
              type: str
              choices:
                - tcp
            toport:
              type: int
            toaddr:
              type: str
        # etc...
1 Like

First please note that you should format the data as code with three backticks, otherwise it’s pretty much unreadable.

I’m aware. I went to all that effort in the Github issue and a lot of good it did me so I was a bit annoyed when I posted it. Not to take it out on you guys :slight_smile:

Good suggestions, thank you! I will look at them all and see what I can do. I thought about literally adding every possible zone, at least the ones that are OOTB but didn’t like that idea. I also thought of just saying it’s a dictionary and leaving it at that but didn’t like that either. I also considered changing the data structure but was hoping to not have to do that because it means I have to go back over the role and change that where it applies. Could be a lot of work. I was not aware of the dynamic argument spec option, I’ll to read up on that.

FWIW I have a role that has a dictionary for Apache VirtualHosts and a argument spec for this dictionary and the dictionaries are validated using this task.

1 Like