Repository: ansible/ansible

Issue:
feature request: Allow module defaults to be set in config file
##### ISSUE TYPE
Feature Idea

##### COMPONENT NAME
core

##### ANSIBLE VERSION
2.0

##### CONFIGURATION


##### OS / ENVIRONMENT


##### SUMMARY
A user should be able to set defaults for a module in a config file.

EG) 
in ~/.ansible.cfg

```
[apt]
cache_valid_time=360
```


##### STEPS TO REPRODUCE


##### EXPECTED RESULTS


##### ACTUAL RESULTS





Discussion:
i would go more for

[apt]
cache_valid_time=360 

since it is an ini file and all that ....

Good call.

though  .... ini is not a good format for complex defaults ... now i'm thinking a:
module_defaults:

```
apt:
  cache_valid_time: 360
  name:
     - mysql-server
     - php-fpm
     - lighttpd
```

yes, taken the apt example a bit too far ...

Possibly a separate yaml file that is referenced from ansible.cfg?


Resolving patch:
diff --git a/lib/ansible/executor/task_executor.py b/lib/ansible/executor/task_executor.py
--- a/lib/ansible/executor/task_executor.py
+++ b/lib/ansible/executor/task_executor.py
@@ -521,6 +521,18 @@ def _execute(self, variables=None):
         # get handler
         self._handler = self._get_action_handler(connection=self._connection, templar=templar)
 
+        # Apply default params for action/module, if present
+        # These are collected as a list of dicts, so we need to merge them
+        module_defaults = {}
+        for default in self._task.module_defaults:
+            module_defaults.update(default)
+        if module_defaults:
+            module_defaults = templar.template(module_defaults)
+        if self._task.action in module_defaults:
+            tmp_args = module_defaults[self._task.action].copy()
+            tmp_args.update(self._task.args)
+            self._task.args = tmp_args
+
         # And filter out any fields which were set to default(omit), and got the omit token value
         omit_token = variables.get('omit')
         if omit_token is not None:
diff --git a/lib/ansible/playbook/base.py b/lib/ansible/playbook/base.py
--- a/lib/ansible/playbook/base.py
+++ b/lib/ansible/playbook/base.py
@@ -152,6 +152,9 @@ class Base(with_metaclass(BaseMeta, object)):
     # variables
     _vars = FieldAttribute(isa='dict', priority=100, inherit=False)
 
+    # module default params
+    _module_defaults = FieldAttribute(isa='list', extend=True, prepend=True)
+
     # flags and misc. settings
     _environment = FieldAttribute(isa='list', extend=True, prepend=True)
     _no_log = FieldAttribute(isa='bool')


Test patch:
diff --git a/test/integration/targets/module_defaults/aliases b/test/integration/targets/module_defaults/aliases
new file mode 100644
diff --git a/test/integration/targets/module_defaults/tasks/main.yml b/test/integration/targets/module_defaults/tasks/main.yml
new file mode 100644
--- /dev/null
+++ b/test/integration/targets/module_defaults/tasks/main.yml
@@ -0,0 +1,89 @@
+- name: main block
+  vars:
+    test_file: /tmp/ansible-test.module_defaults.foo
+  module_defaults:
+    debug:
+      msg: test default
+    file:
+      path: '{{ test_file }}'
+  block:
+    - debug:
+      register: foo
+
+    - name: test that 'debug' task used default 'msg' param
+      assert:
+        that: foo.msg == "test default"
+
+    - name: remove test file
+      file:
+        state: absent
+
+    - name: touch test file
+      file:
+        state: touch
+
+    - name: stat test file
+      stat:
+        path: '{{ test_file }}'
+      register: foo
+
+    - name: check that test file exists
+      assert:
+        that: foo.stat.exists
+
+    - name: remove test file
+      file:
+        state: absent
+
+    - name: test that module defaults from parent are inherited and merged
+      module_defaults:
+        # Meaningless values to make sure that 'module_defaults' gets
+        # evaluated for this block
+        foo:
+          bar: baz
+      block:
+      - debug:
+        register: foo
+
+      - assert:
+          that: foo.msg == "test default"
+
+    - name: test that we can override module defaults inherited from parent
+      module_defaults:
+        debug:
+          msg: "different test message"
+      block:
+      - debug:
+        register: foo
+
+      - assert:
+          that: foo.msg == "different test message"
+
+    - name: test that module defaults inherited from parent can be removed
+      module_defaults:
+        debug: {}
+      block:
+      - debug:
+        register: foo
+
+      - assert:
+          that:
+            foo.msg == "Hello world!"
+
+    - name: test that module defaults can be overridden by module params
+      block:
+      - debug:
+          msg: another test message
+        register: foo
+
+      - assert:
+          that:
+            foo.msg == "another test message"
+
+      - debug:
+          msg: '{{ omit }}'
+        register: foo
+
+      - assert:
+          that:
+            foo.msg == "Hello world!"