Repository: ansible/ansible

Issue:
Add support for comments in UFW
##### ISSUE TYPE
 - Feature Idea

##### COMPONENT NAME
ufw

##### ANSIBLE VERSION
```
ansible 2.2.1.0
```

##### SUMMARY
UFW supports comments since version 0.35. Would be great to support this in the ufw module.

Note: This is a resubmission of ansible/ansible-modules-extras#2065 since issues have been moved to the new repo.


Discussion:
cc @ovcharenko @pyykkis
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/lib/ansible/modules/system/ufw.py b/lib/ansible/modules/system/ufw.py
--- a/lib/ansible/modules/system/ufw.py
+++ b/lib/ansible/modules/system/ufw.py
@@ -126,6 +126,11 @@
       - Apply the rule to routed/forwarded packets.
     required: false
     choices: ['yes', 'no']
+  comment:
+    description:
+      - Add a comment to the rule. Requires UFW version >=0.35.
+    required: false
+    version_added: "2.4"
 '''
 
 EXAMPLES = '''
@@ -194,12 +199,13 @@
     - 172.16.0.0/12
     - 192.168.0.0/16
 
-# Deny access to udp port 514 from host 1.2.3.4:
+# Deny access to udp port 514 from host 1.2.3.4 and include a comment:
 - ufw:
     rule: deny
     proto: udp
     src: 1.2.3.4
     port: 514
+    comment: "Block syslog"
 
 # Allow incoming access to eth0 from 1.2.3.5 port 5469 to 1.2.3.4 port 5469
 - ufw:
@@ -250,7 +256,8 @@ def main():
             to_ip     = dict(default='any', aliases=['dest', 'to']),
             to_port   = dict(default=None,  aliases=['port']),
             proto     = dict(default=None,  aliases=['protocol'], choices=['any', 'tcp', 'udp', 'ipv6', 'esp', 'ah']),
-            app       = dict(default=None,  aliases=['name'])
+            app       = dict(default=None,  aliases=['name']),
+            comment   = dict(default=None, type='str')
         ),
         supports_check_mode = True,
         mutually_exclusive = [['app', 'proto', 'logging']]
@@ -267,6 +274,33 @@ def execute(cmd):
         if rc != 0:
             module.fail_json(msg=err or out)
 
+    def ufw_version():
+        """
+        Returns the major and minor version of ufw installed on the system.
+        """
+        rc, out, err = module.run_command("%s --version" % ufw_bin)
+        if rc != 0:
+            module.fail_json(
+                msg="Failed to get ufw version.", rc=rc, out=out, err=err
+            )
+
+        lines = [x for x in out.split('\n') if x.strip() != '']
+        if len(lines) == 0:
+            module.fail_json(msg="Failed to get ufw version.", rc=0, out=out)
+
+        matches = re.search(r'^ufw.+(\d+)\.(\d+)(?:\.(\d+))?.*$', lines[0])
+        if matches is None:
+            module.fail_json(msg="Failed to get ufw version.", rc=0, out=out)
+
+        # Convert version to numbers
+        major = int(matches.group(1))
+        minor = int(matches.group(2))
+        rev   = 0
+        if matches.group(3) is not None:
+            rev = int(matches.group(3))
+
+        return major, minor, rev
+
     params = module.params
 
     # Ensure at least one of the command arguments are given
@@ -284,7 +318,7 @@ def execute(cmd):
 
     # Save the pre state and rules in order to recognize changes
     (_, pre_state, _) = module.run_command(ufw_bin + ' status verbose')
-    (_, pre_rules, _) = module.run_command("grep '^### tuple' /lib/ufw/user*.rules")
+    (_, pre_rules, _) = module.run_command("grep '^### tuple' /lib/ufw/user.rules /lib/ufw/user6.rules /etc/ufw/user.rules /etc/ufw/user6.rules")
 
     # Execute commands
     for (command, value) in commands.items():
@@ -306,7 +340,7 @@ def execute(cmd):
             #
             # ufw [--dry-run] [delete] [insert NUM] [route] allow|deny|reject|limit [in|out on INTERFACE] [log|log-all] \
             #     [from ADDRESS [port PORT]] [to ADDRESS [port PORT]] \
-            #     [proto protocol] [app application]
+            #     [proto protocol] [app application] [comment COMMENT]
             cmd.append([module.boolean(params['delete']), 'delete'])
             cmd.append([module.boolean(params['route']), 'route'])
             cmd.append([params['insert'], "insert %s" % params['insert']])
@@ -322,11 +356,16 @@ def execute(cmd):
                 value = params[key]
                 cmd.append([value, template % (value)])
 
+            ufw_major, ufw_minor, _ = ufw_version()
+            # comment is supported only in ufw version after 0.35
+            if (ufw_major == 0 and ufw_minor >= 35) or ufw_major > 0:
+                cmd.append([params['comment'], "comment '%s'" % params['comment']])
+
             execute(cmd)
 
     # Get the new state
     (_, post_state, _) = module.run_command(ufw_bin + ' status verbose')
-    (_, post_rules, _) = module.run_command("grep '^### tuple' /lib/ufw/user*.rules")
+    (_, post_rules, _) = module.run_command("grep '^### tuple' /lib/ufw/user.rules /lib/ufw/user6.rules /etc/ufw/user.rules /etc/ufw/user6.rules")
     changed = (pre_state != post_state) or (pre_rules != post_rules)
 
     return module.exit_json(changed=changed, commands=cmds, msg=post_state.rstrip())


Test patch: