Extend modprobe files functionality

Support full set of options defined in

  man modprobe.d

Change-Id: I3d30b6bc261ef308ae6afd963f13fda1e4b22c0d
diff --git a/README.rst b/README.rst
index 1086777..986ec72 100644
--- a/README.rst
+++ b/README.rst
@@ -439,6 +439,10 @@
 ``/etc/modprobe.d/nf_conntrack.conf`` file with line
 ``options nf_conntrack hashsize=262144``:
 
+'option' can be a mapping (with 'enabled' and 'value' keys) or a scalar.
+
+Example for 'scalar' option value:
+
 .. code-block:: yaml
 
     linux:
@@ -449,6 +453,115 @@
               option:
                 hashsize: 262144
 
+Example for 'mapping' option value:
+
+.. code-block:: yaml
+
+    linux:
+      system:
+        kernel:
+          module:
+            nf_conntrack:
+              option:
+                hashsize:
+                  enabled: true
+                  value: 262144
+
+NOTE: 'enabled' key is optional and is True by default.
+
+Blacklist a module:
+
+.. code-block:: yaml
+
+    linux:
+      system:
+        kernel:
+          module:
+            nf_conntrack:
+              blacklist: true
+
+A module can have a number of aliases, wildcards are allowed.
+Define an alias for a module:
+
+.. code-block:: yaml
+
+    linux:
+      system:
+        kernel:
+          module:
+            nf_conntrack:
+              alias:
+                nfct:
+                  enabled: true
+                "nf_conn*":
+                  enabled: true
+
+NOTE: 'enabled' key is mandatory as there are no other keys exist.
+
+Execute custom command instead of 'insmod' when inserting a module:
+
+.. code-block:: yaml
+
+    linux:
+      system:
+        kernel:
+          module:
+            nf_conntrack:
+              install:
+                enabled: true
+                command: /bin/true
+
+NOTE: 'enabled' key is optional and is True by default.
+
+Execute custom command instead of 'rmmod' when removing a module:
+
+.. code-block:: yaml
+
+    linux:
+      system:
+        kernel:
+          module:
+            nf_conntrack:
+              remove:
+                enabled: true
+                command: /bin/true
+
+NOTE: 'enabled' key is optional and is True by default.
+
+Define module dependencies:
+
+.. code-block:: yaml
+
+    linux:
+      system:
+        kernel:
+          module:
+            nf_conntrack:
+              softdep:
+                pre:
+                  1:
+                    enabled: true
+                    value: a
+                  2:
+                    enabled: true
+                    value: b
+                  3:
+                    enabled: true
+                    value: c
+                post:
+                  1:
+                    enabled: true
+                    value: x
+                  2:
+                    enabled: true
+                    value: y
+                  3:
+                    enabled: true
+                    value: z
+
+NOTE: 'enabled' key is optional and is True by default.
+
+
 Install specific kernel version and ensure all other kernel packages are
 not present. Also install extra modules and headers for this kernel:
 
diff --git a/linux/files/modprobe.conf.jinja b/linux/files/modprobe.conf.jinja
index 8dcd782..a70ab6d 100644
--- a/linux/files/modprobe.conf.jinja
+++ b/linux/files/modprobe.conf.jinja
@@ -1,7 +1,51 @@
-{% if module_content.get('blacklist', false) -%}
+{%- from "linux/map.jinja" import system with context -%}
+# This file is managed by Salt, do not edit.
+{%- set module_content = system.kernel.module.get(module_name) %}
+{%- if module_content.get('blacklist', false) %}
 blacklist {{ module_name }}
-{%- else -%}
-
-options {{ module_name }}{% for option, value in module_content.get('option', {}) | dictsort %} {{ option }}={{ value }}{% endfor %}
-
+{%- endif %}
+{%- for alias, params in module_content.get('alias', {}) | dictsort %}
+  {%- if params.get('enabled', true) %}
+alias {{ alias }} {{ module_name }}
+  {%- endif %}
+{%- endfor %}
+{%- set options = [] %}
+{%- for option, params in module_content.get('option', {}) | dictsort %}
+  {%- if params is mapping %}
+    {%- if params.get('enabled', true) and params.value is defined %}
+      {%- do options.append(option ~ '=' ~ params.value) %}
+    {%- endif %}
+  {%- else %}
+    {%- do options.append(option ~ '=' ~ params) %}
+  {%- endif %}
+{%- endfor %}
+{%- if options | length > 0 %}
+options {{ module_name }} {{ options | join(' ')}}
+{%- endif %}
+{%- if module_content.install is defined %}
+  {%- if module_content.install.get('enabled', true) and module_content.install.command is defined %}
+install {{ module_name }} {{ module_content.install.command }}
+  {%- endif %}
+{%- endif %}
+{%- if module_content.remove is defined %}
+  {%- if module_content.remove.get('enabled', true) and module_content.remove.command is defined %}
+remove {{ module_name }} {{ module_content.remove.command }}
+  {%- endif %}
+{%- endif %}
+{%- if module_content.softdep is defined %}
+  {%- set pre = [] %}
+  {%- set post = [] %}
+  {%- for pos, params in module_content.softdep.get('pre', {}) | dictsort %}
+    {%- if params.get('enabled', true) and params.value is defined %}
+      {%- do pre.append(params.value) %}
+    {%- endif %}
+  {%- endfor %}
+  {%- for pos, params in module_content.softdep.get('post', {}) | dictsort %}
+    {%- if params.get('enabled', true) and params.value is defined %}
+      {%- do post.append(params.value) %}
+    {%- endif %}
+  {%- endfor %}
+  {%- if pre | length + post | length > 0 %}
+softdep {{ module_name }}{% if pre | length > 0 %} pre: {{ pre | join(' ') }}{% endif %}{% if post | length > 0 %} post: {{ post | join(' ') }}{% endif %}
+  {%- endif %}
 {%- endif %}
diff --git a/linux/system/kernel.sls b/linux/system/kernel.sls
index 0f50a19..e6111c5 100644
--- a/linux/system/kernel.sls
+++ b/linux/system/kernel.sls
@@ -56,7 +56,16 @@
 
 {%- endfor %}
 
-{%- for module_name, module_content in system.kernel.get('module', {}).items() %}
+{%- if system.kernel.module is defined %}
+
+modprobe_d_directory:
+  file.directory:
+    - name: /etc/modprobe.d
+    - user: root
+    - group: root
+    - mode: 755
+
+  {%- for module_name in system.kernel.module %}
 
 /etc/modprobe.d/{{ module_name }}.conf:
   file.managed:
@@ -66,10 +75,12 @@
     - template: jinja
     - source: salt://linux/files/modprobe.conf.jinja
     - defaults:
-       module_content: {{ module_content }}
        module_name: {{ module_name }}
+    - require:
+      - file: modprobe_d_directory
 
-{%- endfor %}
+  {%- endfor %}
+{%- endif %}
 
 {%- for sysctl_name, sysctl_value in system.kernel.get('sysctl', {}).items() %}
 
diff --git a/tests/pillar/system.sls b/tests/pillar/system.sls
index f032df0..555a0cd 100644
--- a/tests/pillar/system.sls
+++ b/tests/pillar/system.sls
@@ -25,6 +25,55 @@
       boot_options:
         - pti=off
         - spectre_v2=auto
+      module:
+        module_1:
+          install:
+            command: /bin/true
+          remove:
+            enabled: false
+            command: /bin/false
+        module_2:
+          install:
+            enabled: false
+            command: /bin/false
+          remove:
+            command: /bin/true
+        module_3:
+          blacklist: true
+        module_4:
+          blacklist: false
+          alias:
+            "module*":
+              enabled: true
+            "module_*":
+              enabled: false
+        module_5:
+          softdep:
+            pre:
+              1:
+                value: module_1
+              2:
+                value: module_2
+                enabled: false
+            post:
+              1:
+                value: module_3
+              2:
+                value: module_4
+                enabled: false
+        module_6:
+          option:
+            opt_1: 111
+            opt_2: 222
+        module_7:
+          option:
+            opt_3:
+              value: 333
+            opt_4:
+              enabled: true
+              value: 444
+            opt_5:
+              enabled: false
     cgroup:
       group:
         group_1: