diff options
Diffstat (limited to 'plugins/modules/vyos_firewall_global.py')
| -rw-r--r-- | plugins/modules/vyos_firewall_global.py | 156 |
1 files changed, 72 insertions, 84 deletions
diff --git a/plugins/modules/vyos_firewall_global.py b/plugins/modules/vyos_firewall_global.py index 66cdabc..337f364 100644 --- a/plugins/modules/vyos_firewall_global.py +++ b/plugins/modules/vyos_firewall_global.py @@ -181,21 +181,30 @@ gathered: type: dict saved: description: Whether the config was saved after changes. - returned: when changes are applied + returned: when changed type: bool response: description: Raw API response. - returned: when changes are applied + returned: always type: dict """ from ansible.module_utils.basic import AnsibleModule -from ansible_collections.vyos.rest.plugins.module_utils.vyos import VyOSModule +from ansible_collections.vyos.rest.plugins.module_utils.vyos import ( + VyOSModule, + autoclean, + dict_op, + from_device, + normalize_have, +) _BASE = ["firewall", "group"] -# Map argspec key -> API key, value key +# argspec_key -> (device_key, member_key). A genuinely minimal, unavoidable +# mapping: VyOS's 5 group types have different kebab-case device names and +# different member-list field names (address/network/port/interface), none +# of which is a mechanical snake<->kebab transform of the other. _GROUP_TYPES = { "address_group": ("address-group", "address"), "network_group": ("network-group", "network"), @@ -204,103 +213,81 @@ _GROUP_TYPES = { "ipv6_network_group": ("ipv6-network-group", "network"), } +# Each of the 5 device_keys above is itself a tag node (keyed by group +# name) that can collapse to a bare string for a single group with no +# other config. The member fields (address/network/port/interface) are +# NOT tag nodes -- confirmed against vyos-1x (leafNode with <multi/>) -- +# they're plain multi-value leaves, so dict_op's own native list handling +# applies to them directly; no reshaping needed. +_TAG_KEYS = {device_key for device_key, _member_key in _GROUP_TYPES.values()} -def _parse_group_type(raw, val_key): - """Parse a group dict from API raw data.""" - if not raw or not isinstance(raw, dict): - return [] - result = [] - for name, data in sorted(raw.items()): - entry = {"name": name} - data = data or {} - if data.get("description"): - entry["description"] = data["description"] - val = data.get(val_key) - if val is not None: - if isinstance(val, list): - entry[val_key.replace("-", "_")] = val - elif isinstance(val, str): - entry[val_key.replace("-", "_")] = [val] - elif isinstance(val, dict): - entry[val_key.replace("-", "_")] = list(val.keys()) - result.append(entry) - return result +def _group_to_device(g, member_key): + entry = autoclean({k: v for k, v in g.items() if k not in ("name", member_key)}) + members = g.get(member_key) + if members: + entry[member_key] = [str(m) for m in members] + return entry -def get_running_config(vyos): - raw = vyos.get_config(_BASE) - if not raw or not isinstance(raw, dict): - return {} - result = {"group": {}} - for arg_key, (api_key, val_key) in _GROUP_TYPES.items(): - groups = _parse_group_type(raw.get(api_key), val_key) - if groups: - result["group"][arg_key] = groups +def _groups_to_device(groups, member_key): + return {g["name"]: _group_to_device(g, member_key) for g in groups or []} - if not result["group"]: - return {} - return result +def _want_to_device(config): + group = (config or {}).get("group") or {} + want = {} + for arg_key, (device_key, member_key) in _GROUP_TYPES.items(): + groups = group.get(arg_key) or [] + if groups: + want[device_key] = _groups_to_device(groups, member_key) + return want -def _group_cmds(arg_key, groups, have_groups, state): - cmds = [] - api_key, val_key = _GROUP_TYPES[arg_key] - have_map = {g["name"]: g for g in (have_groups or [])} - want_map = {g["name"]: g for g in (groups or [])} - if state == "replaced": - for name in set(have_map) - set(want_map): - cmds.append(("delete", _BASE + [api_key, name])) +def _group_from_device(name, data, member_key): + data = dict(data or {}) + members = data.pop(member_key, None) + entry = {"name": name, **from_device(data)} + if members is not None: + member_list = [members] if isinstance(members, str) else members + entry[member_key] = sorted(str(m) for m in member_list) + return entry - for name, group in want_map.items(): - have_group = have_map.get(name, {}) - gbase = _BASE + [api_key, name] - if group.get("description") and group["description"] != have_group.get("description"): - cmds.append(("set", gbase + ["description", group["description"]])) +def _groups_from_device(raw_groups, member_key): + if not raw_groups or not isinstance(raw_groups, dict): + return [] + return [_group_from_device(name, data, member_key) for name, data in sorted(raw_groups.items())] - # normalize val_key for argspec (underscores) - arg_val_key = val_key.replace("-", "_") - want_vals = set(group.get(arg_val_key) or []) - have_vals = set(have_group.get(arg_val_key) or []) - for val in want_vals - have_vals: - cmds.append(("set", gbase + [val_key, val])) +def get_running_config(vyos): + return vyos.get_config(_BASE) or {} - if state == "replaced": - for val in have_vals - want_vals: - cmds.append(("delete", gbase + [val_key, val])) - return cmds +def _device_to_argspec(raw): + if not raw or not isinstance(raw, dict): + return {} + group = {} + for arg_key, (device_key, member_key) in _GROUP_TYPES.items(): + groups = _groups_from_device(raw.get(device_key), member_key) + if groups: + group[arg_key] = groups + return {"group": group} if group else {} -def build_commands(config, have, state): - cmds = [] +def build_commands(config, raw_have, state): + raw_have = raw_have or {} + want = _want_to_device(config) + norm_have = normalize_have(raw_have, _TAG_KEYS) if state == "deleted": - if have: - cmds.append(("delete", _BASE)) - return cmds + return [("delete", _BASE)] if raw_have else [] + commands = [] if state == "replaced": - # Check if anything differs - would_set = build_commands(config, {}, "merged") - have_set = build_commands(have, {}, "merged") - if would_set == have_set: - return [] - - config = config or {} - want_group = config.get("group") or {} - have_group = have.get("group") or {} - - for arg_key in _GROUP_TYPES: - want_groups = want_group.get(arg_key) or [] - have_groups = have_group.get(arg_key) or [] - if want_groups or (state == "replaced" and have_groups): - cmds += _group_cmds(arg_key, want_groups, have_groups, state) - - return cmds + commands += dict_op(want, norm_have, _BASE, op="purge") + commands += dict_op(want, norm_have, _BASE, op="set") + return commands ARGUMENT_SPEC = dict( @@ -373,12 +360,13 @@ def main(): state = module.params["state"] config = module.params.get("config") or {} - have = get_running_config(vyos) + raw_have = get_running_config(vyos) + have = _device_to_argspec(raw_have) if state == "gathered": module.exit_json(changed=False, gathered=have) - commands = build_commands(config, have, state) + commands = build_commands(config, raw_have, state) if module.check_mode: module.exit_json(changed=bool(commands), commands=commands, before=have) @@ -389,7 +377,7 @@ def main(): module.exit_json( changed=True, before=have, - after=get_running_config(vyos), + after=_device_to_argspec(get_running_config(vyos)), commands=commands, saved=saved, response=response, |
