module

Krikri::PluginHelpers::UfwCommand

UfwCommand - pure logic for building the ufw command line for a rule/state/default/logging change. No I/O here - the plugin itself runs the resulting command and interprets its output.

Rule command shape verified against community.general's actual ufw.py source (the "long format" documented directly in its own comment): ufw [route] [delete | insert NUM] allow|deny|reject|limit [in|out on INTERFACE] [log] [from ADDRESS [port PORT]] [to ADDRESS [port PORT]] [proto protocol] [app application] [comment COMMENT].

Class methods

changed_from_output?(output : String) : Bool

Real ufw prints a line containing "Skipping" (e.g. "Skipping adding existing rule") and exits 0 when a rule command is a no-op - verified against community.general's actual ufw.py source, which checks for exactly this substring (filter_line_that_contains("Skipping", rules_dry)) to decide changed in check mode. Used here as the changed signal for a real (non-dry-run) application too, since replicating ufw's full rule-tuple diffing logic would need actually-working netfilter access to verify - not available in this project's Docker-based compat harness (rootless podman container), so this is source-verified but not further behavior-verified end-to-end the way every other plugin in this codebase has been.

Source
default_command(value : String, direction : String | Nil) : String
Source
exec_failure_msg(stdout : String, stderr : String) : String

Real ufw.py's execute() fails with module.fail_json(msg=err or out, commands=cmds) - stderr wins, stdout is the fallback when the failing command wrote nothing to stderr. Every ufw invocation (pre/post status probes, the state/rule command itself) goes through that helper, so this is the failure message real Ansible shows whenever the ufw CLI exits non-zero

  • e.g. a container without CAP_NET_ADMIN, where even ufw status verbose dies with iptables' "Permission denied (you must be root)". This engine used to read only the probes' stdout and ignore their exit codes entirely, turning that exact situation into a false "Rules updated" success.
Source
logging_command(value : String) : String
Source
resolve_insert(insert : Int32, relative_to_cmd : String, numbered_status : String) : Int32 | Nil

Resolves insert:/insert_relative_to: into the actual absolute ufw insert NUM position, given ufw status numbered's own output. zero (the default) is a pure passthrough - the caller only needs this for the other four values. Algorithm (including the "no ipv4/ipv6 rules yet" fallback positions and the insert-past-the-end-means-no-insert-flag-at-all case) copied field-for-field from community.general's actual ufw.py source, not derived from the docs' prose - it's the one piece of this plugin dense enough that reproducing it from the English description alone would very likely have drifted from the real rule-number arithmetic.

Returns nil if the resolved position would fall past the last existing rule - real ufw rejects an insert number larger than the maximum rule number, so real Ansible drops the insert flag entirely in that case (the rule is just appended normally) instead of sending a command ufw would refuse.

Source
rule_command(params : Hash(String, String), dry_run : Bool = false) : String

params keys read: route, delete, insert, rule, direction, interface, interface_in, interface_out, log, from_ip, from_port, to_ip, to_port, proto, name, comment. dry_run inserts ufw's own --dry-run flag right after the binary name, matching its real position in the "long format" (ufw [--dry-run] [route] ...).

Source
state_command(state : String) : String | Nil
Source