From 38c0b581c3c6df3e0ba57cf2178508aaddcf2d40 Mon Sep 17 00:00:00 2001
From: Ganesh Nalawade <ganesh634@gmail.com>
Date: Tue, 15 Aug 2017 19:09:44 +0530
Subject: [PATCH] Handle common argument in aggregate parameter for vyos module
 (#28182)

* Handle common agrument in aggregate parameter for vyos module

*  Add supoort to set parameter in aggregate to it's respctive
   top level argument if value not provided in aggregate.
*  Aggregate argument spec validation
*  Documentation for aggregate

* Fix unit test failure
---
 .../network/interface/net_interface.py        | 18 +++++
 .../modules/network/interface/net_linkagg.py  | 12 ++++
 .../network/interface/net_lldp_interface.py   | 14 ++++
 .../network/layer3/net_l3_interface.py        | 13 ++++
 .../network/routing/net_static_route.py       |  7 ++
 .../modules/network/system/net_user.py        |  3 +
 .../modules/network/vyos/vyos_interface.py    | 68 +++++++++++--------
 .../modules/network/vyos/vyos_l3_interface.py | 53 ++++++++++-----
 .../modules/network/vyos/vyos_linkagg.py      | 52 +++++++++-----
 lib/ansible/modules/network/vyos/vyos_lldp.py |  1 -
 .../network/vyos/vyos_lldp_interface.py       | 53 +++++++++++----
 .../modules/network/vyos/vyos_logging.py      | 62 +++++++++++------
 .../modules/network/vyos/vyos_static_route.py | 57 +++++++++++-----
 lib/ansible/modules/network/vyos/vyos_user.py | 37 +++++-----
 .../net_interface/tests/vyos/basic.yaml       | 20 +++---
 .../vyos_interface/tests/cli/basic.yaml       | 20 +++---
 .../targets/vyos_linkagg/tests/cli/basic.yaml |  8 +--
 .../vyos_lldp_interface/tests/cli/basic.yaml  | 20 +++---
 18 files changed, 353 insertions(+), 165 deletions(-)

diff --git a/lib/ansible/modules/network/interface/net_interface.py b/lib/ansible/modules/network/interface/net_interface.py
index 34fb6d5314e..19592d8349a 100644
--- a/lib/ansible/modules/network/interface/net_interface.py
+++ b/lib/ansible/modules/network/interface/net_interface.py
@@ -87,6 +87,24 @@ EXAMPLES = """
     name: ge-0/0/1
     description: test-interface
     enabled: False
+
+- name: Create interface using aggregate
+  net_interface:
+    aggregate:
+      - name: ge-0/0/1
+        description: test-interface-1
+      - name: ge-0/0/2
+        description: test-interface-2
+    speed: 1g
+    duplex: full
+    mtu: 512
+
+- name: Delete interface using aggregate
+  junos_interface:
+    aggregate:
+      - name: ge-0/0/1
+      - name: ge-0/0/2
+    state: absent
 """
 
 RETURN = """
diff --git a/lib/ansible/modules/network/interface/net_linkagg.py b/lib/ansible/modules/network/interface/net_linkagg.py
index 52cc545cf68..007b58badd2 100644
--- a/lib/ansible/modules/network/interface/net_linkagg.py
+++ b/lib/ansible/modules/network/interface/net_linkagg.py
@@ -70,6 +70,18 @@ EXAMPLES = """
     name: bond0
     state: absent
 
+- name: Create aggregate of linkagg definitions
+  net_linkagg:
+    aggregate:
+        - { name: bond0, members: [eth1] }
+        - { name: bond1, members: [eth2] }
+
+- name: Remove aggregate of linkagg definitions
+  net_linkagg:
+    aggregate:
+      - name: bond0
+      - name: bond1
+    state: absent
 """
 
 RETURN = """
diff --git a/lib/ansible/modules/network/interface/net_lldp_interface.py b/lib/ansible/modules/network/interface/net_lldp_interface.py
index 5f27a8f68d4..562eddf1933 100644
--- a/lib/ansible/modules/network/interface/net_lldp_interface.py
+++ b/lib/ansible/modules/network/interface/net_lldp_interface.py
@@ -58,6 +58,20 @@ EXAMPLES = """
   net_lldp_interface:
     name: eth1
     state: absent
+
+- name: Create aggregate of LLDP interface configurations
+  net_lldp_interface:
+    aggregate:
+    - name: eth1
+    - name: eth2
+    state: present
+
+- name: Delete aggregate of LLDP interface configurations
+  net_lldp_interface:
+    aggregate:
+    - name: eth1
+    - name: eth2
+    state: absent
 """
 
 RETURN = """
diff --git a/lib/ansible/modules/network/layer3/net_l3_interface.py b/lib/ansible/modules/network/layer3/net_l3_interface.py
index 976a316bac1..93e3fd7eae6 100644
--- a/lib/ansible/modules/network/layer3/net_l3_interface.py
+++ b/lib/ansible/modules/network/layer3/net_l3_interface.py
@@ -55,6 +55,19 @@ EXAMPLES = """
   net_l3_interface:
     name: eth0
     state: absent
+
+- name: Set IP addresses on aggregate
+  net_l3_interface:
+    aggregate:
+      - { name: eth1, ipv4: 192.168.2.10/24 }
+      - { name: eth2, ipv4: 192.168.3.10/24, ipv6: "fd5d:12c9:2201:1::1/64" }
+
+- name: Remove IP addresses on aggregate
+  net_l3_interface:
+    aggregate:
+      - { name: eth1, ipv4: 192.168.2.10/24 }
+      - { name: eth2, ipv4: 192.168.3.10/24, ipv6: "fd5d:12c9:2201:1::1/64" }
+    state: absent
 """
 
 RETURN = """
diff --git a/lib/ansible/modules/network/routing/net_static_route.py b/lib/ansible/modules/network/routing/net_static_route.py
index 79924b6d1e0..e6c32ac17a7 100644
--- a/lib/ansible/modules/network/routing/net_static_route.py
+++ b/lib/ansible/modules/network/routing/net_static_route.py
@@ -70,6 +70,13 @@ EXAMPLES = """
     aggregate:
       - { prefix: 192.168.2.0, mask 255.255.255.0, next_hop: 10.0.0.1 }
       - { prefix: 192.168.3.0, mask 255.255.255.0, next_hop: 10.0.2.1 }
+
+- name: Remove static route collections
+  net_static_route:
+    aggregate:
+      - { prefix: 172.24.1.0/24, next_hop: 192.168.42.64 }
+      - { prefix: 172.24.3.0/24, next_hop: 192.168.42.64 }
+    state: absent
 """
 
 RETURN = """
diff --git a/lib/ansible/modules/network/system/net_user.py b/lib/ansible/modules/network/system/net_user.py
index 96f601c8c97..9ba7c8872cd 100644
--- a/lib/ansible/modules/network/system/net_user.py
+++ b/lib/ansible/modules/network/system/net_user.py
@@ -98,9 +98,11 @@ EXAMPLES = """
     name: ansible
     sshkey: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
     state: present
+
 - name: remove all users except admin
   net_user:
     purge: yes
+
 - name: set multiple users to privilege level 15
   net_user:
     aggregate:
@@ -108,6 +110,7 @@ EXAMPLES = """
       - name: netend
     privilege: 15
     state: present
+
 - name: Change Password for User netop
   net_user:
     name: netop
diff --git a/lib/ansible/modules/network/vyos/vyos_interface.py b/lib/ansible/modules/network/vyos/vyos_interface.py
index 212346af0bf..3a6d336de9e 100644
--- a/lib/ansible/modules/network/vyos/vyos_interface.py
+++ b/lib/ansible/modules/network/vyos/vyos_interface.py
@@ -93,6 +93,26 @@ EXAMPLES = """
     speed: 100
     mtu: 256
     duplex: full
+
+- name: Set interface using aggregate
+  vyos_interface:
+    aggregate:
+      - { name: eth1, description: test-interface-1,  speed: 100, duplex: half, mtu: 512}
+      - { name: eth2, description: test-interface-2,  speed: 1000, duplex: full, mtu: 256}
+
+- name: Disable interface on aggregate
+  net_interface:
+    aggregate:
+      - name: eth1
+      - name: eth2
+    enabled: False
+
+- name: Delete interface using aggregate
+  net_interface:
+    aggregate:
+      - name: eth1
+      - name: eth2
+    state: absent
 """
 
 RETURN = """
@@ -108,12 +128,13 @@ commands:
 """
 import re
 
+from copy import deepcopy
 from time import sleep
 
 from ansible.module_utils._text import to_text
 from ansible.module_utils.basic import AnsibleModule
 from ansible.module_utils.connection import exec_command
-from ansible.module_utils.network_common import conditional
+from ansible.module_utils.network_common import conditional, remove_default_spec
 from ansible.module_utils.vyos import load_config, get_config
 from ansible.module_utils.vyos import vyos_argument_spec, check_args
 
@@ -213,40 +234,19 @@ def map_config_to_obj(module):
 
 def map_params_to_obj(module):
     obj = []
-
-    params = ['speed', 'description', 'duplex', 'mtu']
     aggregate = module.params.get('aggregate')
-
     if aggregate:
-        for c in aggregate:
-            d = c.copy()
-            if 'name' not in d:
-                module.fail_json(msg="missing required arguments: %s" % 'name')
-
-            for item in params:
-                if item not in d:
-                    d[item] = None
-
-            if d.get('description') is None:
-                d['description'] = DEFAULT_DESCRIPTION
-
-            if not d.get('state'):
-                d['state'] = module.params['state']
-
-            if d.get('enabled') is None:
-                d['enabled'] = module.params['enabled']
+        for item in aggregate:
+            for key in item:
+                if item.get(key) is None:
+                    item[key] = module.params[key]
 
+            d = item.copy()
             if d['enabled']:
                 d['disable'] = False
             else:
                 d['disable'] = True
 
-            if d.get('delay') is None:
-                d['delay'] = module.params['delay']
-
-            if d.get('speed'):
-                d['speed'] = str(d['speed'])
-
             obj.append(d)
     else:
         params = {
@@ -300,7 +300,7 @@ def check_declarative_intent_params(module, want, result):
 def main():
     """ main entry point for module execution
     """
-    argument_spec = dict(
+    element_spec = dict(
         name=dict(),
         description=dict(default=DEFAULT_DESCRIPTION),
         speed=dict(),
@@ -308,11 +308,21 @@ def main():
         duplex=dict(choices=['full', 'half', 'auto']),
         enabled=dict(default=True, type='bool'),
         delay=dict(default=10, type='int'),
-        aggregate=dict(type='list'),
         state=dict(default='present',
                    choices=['present', 'absent', 'up', 'down'])
     )
 
+    aggregate_spec = deepcopy(element_spec)
+    aggregate_spec['name'] = dict(required=True)
+
+    # remove default in aggregate spec, to handle common arguments
+    remove_default_spec(aggregate_spec)
+
+    argument_spec = dict(
+        aggregate=dict(type='list', elements='dict', options=aggregate_spec),
+    )
+
+    argument_spec.update(element_spec)
     argument_spec.update(vyos_argument_spec)
 
     required_one_of = [['name', 'aggregate']]
diff --git a/lib/ansible/modules/network/vyos/vyos_l3_interface.py b/lib/ansible/modules/network/vyos/vyos_l3_interface.py
index 38125d20627..54d1ad262fb 100644
--- a/lib/ansible/modules/network/vyos/vyos_l3_interface.py
+++ b/lib/ansible/modules/network/vyos/vyos_l3_interface.py
@@ -45,10 +45,6 @@ options:
       - IPv6 of the L3 interface.
   aggregate:
     description: List of L3 interfaces definitions
-  purge:
-    description:
-      - Purge L3 interfaces not defined in the aggregate parameter.
-    default: no
   state:
     description:
       - State of the L3 interface configuration.
@@ -66,6 +62,19 @@ EXAMPLES = """
   vyos_l3_interface:
     name: eth0
     state: absent
+
+- name: Set IP addresses on aggregate
+  vyos_l3_interface:
+    aggregate:
+      - { name: eth1, ipv4: 192.168.2.10/24 }
+      - { name: eth2, ipv4: 192.168.3.10/24, ipv6: "fd5d:12c9:2201:1::1/64" }
+
+- name: Remove IP addresses on aggregate
+  vyos_l3_interface:
+    aggregate:
+      - { name: eth1, ipv4: 192.168.2.10/24 }
+      - { name: eth2, ipv4: 192.168.3.10/24, ipv6: "fd5d:12c9:2201:1::1/64" }
+    state: absent
 """
 
 RETURN = """
@@ -76,7 +85,10 @@ commands:
   sample:
     - set interfaces ethernet eth0 address '192.168.0.1/24'
 """
+from copy import deepcopy
+
 from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.network_common import remove_default_spec
 from ansible.module_utils.vyos import load_config, run_commands
 from ansible.module_utils.vyos import vyos_argument_spec, check_args
 
@@ -153,18 +165,14 @@ def map_config_to_obj(module):
 def map_params_to_obj(module):
     obj = []
 
-    if 'aggregate' in module.params and module.params['aggregate']:
-        for c in module.params['aggregate']:
-            d = c.copy()
+    aggregate = module.params.get('aggregate')
+    if aggregate:
+        for item in aggregate:
+            for key in item:
+                if item.get(key) is None:
+                    item[key] = module.params[key]
 
-            if 'ipv4' not in d:
-                d['ipv4'] = None
-            if 'ipv6' not in d:
-                d['ipv6'] = None
-            if 'state' not in d:
-                d['state'] = module.params['state']
-
-            obj.append(d)
+            obj.append(item.copy())
     else:
         obj.append({
             'name': module.params['name'],
@@ -179,16 +187,25 @@ def map_params_to_obj(module):
 def main():
     """ main entry point for module execution
     """
-    argument_spec = dict(
+    element_spec = dict(
         name=dict(),
         ipv4=dict(),
         ipv6=dict(),
-        aggregate=dict(type='list'),
-        purge=dict(default=False, type='bool'),
         state=dict(default='present',
                    choices=['present', 'absent'])
     )
 
+    aggregate_spec = deepcopy(element_spec)
+    aggregate_spec['name'] = dict(required=True)
+
+    # remove default in aggregate spec, to handle common arguments
+    remove_default_spec(aggregate_spec)
+
+    argument_spec = dict(
+        aggregate=dict(type='list', elements='dict', options=aggregate_spec),
+    )
+
+    argument_spec.update(element_spec)
     argument_spec.update(vyos_argument_spec)
 
     required_one_of = [['name', 'aggregate']]
diff --git a/lib/ansible/modules/network/vyos/vyos_linkagg.py b/lib/ansible/modules/network/vyos/vyos_linkagg.py
index b2ca325ea32..9972a9cdffb 100644
--- a/lib/ansible/modules/network/vyos/vyos_linkagg.py
+++ b/lib/ansible/modules/network/vyos/vyos_linkagg.py
@@ -49,10 +49,6 @@ options:
       - List of members of the link aggregation group.
   aggregate:
     description: List of link aggregation definitions.
-  purge:
-    description:
-      - Purge link aggregation groups not defined in the aggregates parameter.
-    default: no
   state:
     description:
       - State of the link aggregation group.
@@ -73,6 +69,18 @@ EXAMPLES = """
     name: bond0
     state: absent
 
+- name: Create aggregate of linkagg definitions
+  vyos_linkagg:
+    aggregate:
+        - { name: bond0, members: [eth1] }
+        - { name: bond1, members: [eth2] }
+
+- name: Remove aggregate of linkagg definitions
+  vyos_linkagg:
+    aggregate:
+      - name: bond0
+      - name: bond1
+    state: absent
 """
 
 RETURN = """
@@ -85,7 +93,10 @@ commands:
     - set interfaces ethernet eth0 bond-group 'bond0'
     - set interfaces ethernet eth1 bond-group 'bond0'
 """
+from copy import deepcopy
+
 from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.network_common import remove_default_spec
 from ansible.module_utils.vyos import load_config, run_commands
 from ansible.module_utils.vyos import vyos_argument_spec, check_args
 
@@ -173,17 +184,14 @@ def map_config_to_obj(module):
 
 def map_params_to_obj(module):
     obj = []
+    aggregate = module.params.get('aggregate')
+    if aggregate:
+        for item in aggregate:
+            for key in item:
+                if item.get(key) is None:
+                    item[key] = module.params[key]
 
-    if 'aggregate' in module.params and module.params['aggregate']:
-        for c in module.params['aggregate']:
-            d = c.copy()
-
-            if 'state' not in d:
-                d['state'] = module.params['state']
-            if 'mode' not in d:
-                d['mode'] = module.params['mode']
-
-            obj.append(d)
+            obj.append(item.copy())
     else:
         obj.append({
             'name': module.params['name'],
@@ -198,25 +206,35 @@ def map_params_to_obj(module):
 def main():
     """ main entry point for module execution
     """
-    argument_spec = dict(
+    element_spec = dict(
         name=dict(),
         mode=dict(choices=['802.3ad', 'active-backup', 'broadcast',
                            'round-robin', 'transmit-load-balance',
                            'adaptive-load-balance', 'xor-hash', 'on'],
                   default='802.3ad'),
         members=dict(type='list'),
-        aggregate=dict(type='list'),
-        purge=dict(default=False, type='bool'),
         state=dict(default='present',
                    choices=['present', 'absent', 'up', 'down'])
     )
 
+    aggregate_spec = deepcopy(element_spec)
+    aggregate_spec['name'] = dict(required=True)
+
+    # remove default in aggregate spec, to handle common arguments
+    remove_default_spec(aggregate_spec)
+
+    argument_spec = dict(
+        aggregate=dict(type='list', elements='dict', options=aggregate_spec),
+    )
+
+    argument_spec.update(element_spec)
     argument_spec.update(vyos_argument_spec)
 
     required_one_of = [['name', 'aggregate']]
     mutually_exclusive = [['name', 'aggregate']]
     module = AnsibleModule(argument_spec=argument_spec,
                            required_one_of=required_one_of,
+                           mutually_exclusive=mutually_exclusive,
                            supports_check_mode=True)
 
     warnings = list()
diff --git a/lib/ansible/modules/network/vyos/vyos_lldp.py b/lib/ansible/modules/network/vyos/vyos_lldp.py
index 8292633f552..09eb6f7ee52 100644
--- a/lib/ansible/modules/network/vyos/vyos_lldp.py
+++ b/lib/ansible/modules/network/vyos/vyos_lldp.py
@@ -78,7 +78,6 @@ def main():
     """
     argument_spec = dict(
         interfaces=dict(type='list'),
-        purge=dict(default=False, type='bool'),
         state=dict(default='present',
                    choices=['present', 'absent',
                             'enabled', 'disabled'])
diff --git a/lib/ansible/modules/network/vyos/vyos_lldp_interface.py b/lib/ansible/modules/network/vyos/vyos_lldp_interface.py
index bab7d75fff5..a19d8e5b328 100644
--- a/lib/ansible/modules/network/vyos/vyos_lldp_interface.py
+++ b/lib/ansible/modules/network/vyos/vyos_lldp_interface.py
@@ -39,10 +39,6 @@ options:
       - Name of the interface LLDP should be configured on.
   aggregate:
     description: List of interfaces LLDP should be configured on.
-  purge:
-    description:
-      - Purge interfaces not defined in the aggregate parameter.
-    default: no
   state:
     description:
       - State of the LLDP configuration.
@@ -64,7 +60,21 @@ EXAMPLES = """
 
 - name: Disable LLDP globally
   net_lldp_interface:
-    state: lldp
+    state: disabled
+
+- name: Create aggregate of LLDP interface configurations
+  vyos_lldp_interface:
+    aggregate:
+    - name: eth1
+    - name: eth2
+    state: present
+
+- name: Delete aggregate of LLDP interface configurations
+  vyos_lldp_interface:
+    aggregate:
+    - name: eth1
+    - name: eth2
+    state: absent
 """
 
 RETURN = """
@@ -76,7 +86,10 @@ commands:
     - set service lldp eth1
     - set service lldp eth2 disable
 """
+from copy import deepcopy
+
 from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.network_common import remove_default_spec
 from ansible.module_utils.vyos import get_config, load_config
 from ansible.module_utils.vyos import vyos_argument_spec, check_args
 
@@ -142,14 +155,14 @@ def map_config_to_obj(module):
 def map_params_to_obj(module):
     obj = []
 
-    if module.params['aggregate']:
-        for i in module.params['aggregate']:
-            d = i.copy()
+    aggregate = module.params.get('aggregate')
+    if aggregate:
+        for item in aggregate:
+            for key in item:
+                if item.get(key) is None:
+                    item[key] = module.params[key]
 
-            if 'state' not in d:
-                d['state'] = module.params['state']
-
-            obj.append(d)
+            obj.append(item.copy())
     else:
         obj.append({'name': module.params['name'], 'state': module.params['state']})
 
@@ -159,16 +172,26 @@ def map_params_to_obj(module):
 def main():
     """ main entry point for module execution
     """
-    argument_spec = dict(
+    element_spec = dict(
         name=dict(),
-        aggregate=dict(type='list'),
-        purge=dict(default=False, type='bool'),
         state=dict(default='present',
                    choices=['present', 'absent',
                             'enabled', 'disabled'])
     )
 
+    aggregate_spec = deepcopy(element_spec)
+    aggregate_spec['name'] = dict(required=True)
+
+    # remove default in aggregate spec, to handle common arguments
+    remove_default_spec(aggregate_spec)
+
+    argument_spec = dict(
+        aggregate=dict(type='list', elements='dict', options=aggregate_spec),
+    )
+
+    argument_spec.update(element_spec)
     argument_spec.update(vyos_argument_spec)
+
     required_one_of = [['name', 'aggregate']]
     mutually_exclusive = [['name', 'aggregate']]
 
diff --git a/lib/ansible/modules/network/vyos/vyos_logging.py b/lib/ansible/modules/network/vyos/vyos_logging.py
index d1d6241bcd6..0b1298e63d3 100644
--- a/lib/ansible/modules/network/vyos/vyos_logging.py
+++ b/lib/ansible/modules/network/vyos/vyos_logging.py
@@ -50,10 +50,6 @@ options:
       - Set logging severity levels.
   aggregate:
     description: List of logging definitions.
-  purge:
-    description:
-      - Purge logging not defined in the aggregate parameter.
-    default: no
   state:
     description:
       - State of the logging configuration.
@@ -67,16 +63,33 @@ EXAMPLES = """
     dest: console
     facility: all
     level: crit
+
 - name: remove console logging configuration
   vyos_logging:
     dest: console
     state: absent
+
 - name: configure file logging
   vyos_logging:
     dest: file
     name: test
     facility: local3
     level: err
+
+- name: Add logging aggregate
+  vyos_logging:
+    aggregate:
+      - { dest: file, name: test1, facility: all, level: info }
+      - { dest: file, name: test2, facility: news, level: debug }
+    state: present
+
+- name: Remove logging aggregate
+  vyos_logging:
+    aggregate:
+      - { dest: console, facility: all, level: info }
+      - { dest: console, facility: daemon, level: warning }
+      - { dest: file, name: test2, facility: news, level: debug }
+    state: absent
 """
 
 RETURN = """
@@ -90,7 +103,10 @@ commands:
 
 import re
 
+from copy import deepcopy
+
 from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.network_common import remove_default_spec
 from ansible.module_utils.vyos import get_config, load_config
 from ansible.module_utils.vyos import vyos_argument_spec, check_args
 
@@ -160,21 +176,18 @@ def config_to_dict(module):
     return obj
 
 
-def map_params_to_obj(module):
+def map_params_to_obj(module, required_if=None):
     obj = []
 
-    if 'aggregate' in module.params and module.params['aggregate']:
-        for c in module.params['aggregate']:
-            d = c.copy()
-            if d['dest'] not in ('host', 'file', 'user'):
-                d['name'] = None
-            else:
-                pass
+    aggregate = module.params.get('aggregate')
+    if aggregate:
+        for item in aggregate:
+            for key in item:
+                if item.get(key) is None:
+                    item[key] = module.params[key]
 
-            if 'state' not in d:
-                d['state'] = module.params['state']
-
-            obj.append(d)
+            module._check_required_if(required_if, item)
+            obj.append(item.copy())
 
     else:
         if module.params['dest'] not in ('host', 'file', 'user'):
@@ -194,16 +207,25 @@ def map_params_to_obj(module):
 def main():
     """ main entry point for module execution
     """
-    argument_spec = dict(
+    element_spec = dict(
         dest=dict(type='str', choices=['console', 'file', 'global', 'host', 'user']),
         name=dict(type='str'),
         facility=dict(type='str'),
         level=dict(type='str'),
         state=dict(default='present', choices=['present', 'absent']),
-        aggregate=dict(type='list'),
-        purge=dict(default=False, type='bool')
     )
 
+    aggregate_spec = deepcopy(element_spec)
+
+    # remove default in aggregate spec, to handle common arguments
+    remove_default_spec(aggregate_spec)
+
+    argument_spec = dict(
+        aggregate=dict(type='list', elements='dict', options=aggregate_spec),
+    )
+
+    argument_spec.update(element_spec)
+
     argument_spec.update(vyos_argument_spec)
     required_if = [('dest', 'host', ['name', 'facility', 'level']),
                    ('dest', 'file', ['name', 'facility', 'level']),
@@ -221,7 +243,7 @@ def main():
     result = {'changed': False}
     if warnings:
         result['warnings'] = warnings
-    want = map_params_to_obj(module)
+    want = map_params_to_obj(module, required_if=required_if)
     have = config_to_dict(module)
 
     commands = spec_to_commands((want, have), module)
diff --git a/lib/ansible/modules/network/vyos/vyos_static_route.py b/lib/ansible/modules/network/vyos/vyos_static_route.py
index eff7ed1cf60..8a0bf5c7494 100644
--- a/lib/ansible/modules/network/vyos/vyos_static_route.py
+++ b/lib/ansible/modules/network/vyos/vyos_static_route.py
@@ -50,10 +50,6 @@ options:
       - Admin distance of the static route.
   aggregate:
     description: List of static route definitions
-  purge:
-    description:
-      - Purge static routes not defined in the aggregates parameter.
-    default: no
   state:
     description:
       - State of the static route configuration.
@@ -67,22 +63,32 @@ EXAMPLES = """
     prefix: 192.168.2.0
     mask: 24
     next_hop: 10.0.0.1
+
 - name: configure static route prefix/mask
   vyos_static_route:
     prefix: 192.168.2.0/16
     next_hop: 10.0.0.1
+
 - name: remove configuration
   vyos_static_route:
     prefix: 192.168.2.0
     mask: 16
     next_hop: 10.0.0.1
     state: absent
+
 - name: configure aggregates of static routes
   vyos_static_route:
     aggregate:
       - { prefix: 192.168.2.0, mask: 24, next_hop: 10.0.0.1 }
       - { prefix: 192.168.3.0, mask: 16, next_hop: 10.0.2.1 }
       - { prefix: 192.168.3.0/16, next_hop: 10.0.2.1 }
+
+- name: Remove static route collections
+  vyos_static_route:
+    aggregate:
+      - { prefix: 172.24.1.0/24, next_hop: 192.168.42.64 }
+      - { prefix: 172.24.3.0/24, next_hop: 192.168.42.64 }
+    state: absent
 """
 
 RETURN = """
@@ -93,10 +99,12 @@ commands:
   sample:
     - set protocols static route 192.168.2.0/16 next-hop 10.0.0.1
 """
-
 import re
 
+from copy import deepcopy
+
 from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.network_common import remove_default_spec
 from ansible.module_utils.vyos import get_config, load_config
 from ansible.module_utils.vyos import vyos_argument_spec, check_args
 
@@ -156,20 +164,23 @@ def config_to_dict(module):
     return obj
 
 
-def map_params_to_obj(module):
+def map_params_to_obj(module, required_together=None):
     obj = []
+    aggregate = module.params.get('aggregate')
+    if aggregate:
+        for item in aggregate:
+            for key in item:
+                if item.get(key) is None:
+                    item[key] = module.params[key]
 
-    if 'aggregate' in module.params and module.params['aggregate']:
-        for c in module.params['aggregate']:
-            d = c.copy()
+            module._check_required_together(required_together, item)
+            d = item.copy()
             if '/' in d['prefix']:
                 d['mask'] = d['prefix'].split('/')[1]
                 d['prefix'] = d['prefix'].split('/')[0]
 
-            if 'state' not in d:
-                d['state'] = module.params['state']
-            if 'admin_distance' not in d:
-                d['admin_distance'] = str(module.params['admin_distance'])
+            if 'admin_distance' in d:
+                d['admin_distance'] = str(d['admin_distance'])
 
             obj.append(d)
     else:
@@ -197,16 +208,27 @@ def map_params_to_obj(module):
 def main():
     """ main entry point for module execution
     """
-    argument_spec = dict(
+    element_spec = dict(
         prefix=dict(type='str'),
         mask=dict(type='str'),
         next_hop=dict(type='str'),
         admin_distance=dict(type='int'),
-        aggregate=dict(type='list'),
-        purge=dict(type='bool'),
         state=dict(default='present', choices=['present', 'absent'])
     )
 
+    aggregate_spec = deepcopy(element_spec)
+    aggregate_spec['prefix'] = dict(required=True)
+
+    # remove default in aggregate spec, to handle common arguments
+    remove_default_spec(aggregate_spec)
+
+    argument_spec = dict(
+        aggregate=dict(type='list', elements='dict', options=aggregate_spec),
+    )
+
+    argument_spec.update(element_spec)
+    argument_spec.update(vyos_argument_spec)
+
     argument_spec.update(vyos_argument_spec)
     required_one_of = [['aggregate', 'prefix']]
     required_together = [['prefix', 'next_hop']]
@@ -215,6 +237,7 @@ def main():
     module = AnsibleModule(argument_spec=argument_spec,
                            required_one_of=required_one_of,
                            required_together=required_together,
+                           mutually_exclusive=mutually_exclusive,
                            supports_check_mode=True)
 
     warnings = list()
@@ -223,7 +246,7 @@ def main():
     result = {'changed': False}
     if warnings:
         result['warnings'] = warnings
-    want = map_params_to_obj(module)
+    want = map_params_to_obj(module, required_together=required_together)
     have = config_to_dict(module)
 
     commands = spec_to_commands((want, have), module)
diff --git a/lib/ansible/modules/network/vyos/vyos_user.py b/lib/ansible/modules/network/vyos/vyos_user.py
index 333d6bba365..1de6e254956 100644
--- a/lib/ansible/modules/network/vyos/vyos_user.py
+++ b/lib/ansible/modules/network/vyos/vyos_user.py
@@ -127,9 +127,11 @@ commands:
 
 import re
 
+from copy import deepcopy
 from functools import partial
 
 from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.network_common import remove_default_spec
 from ansible.module_utils.vyos import get_config, load_config
 from ansible.module_utils.six import iteritems
 from ansible.module_utils.vyos import vyos_argument_spec, check_args
@@ -215,13 +217,6 @@ def get_param_value(key, item, module):
     if not item.get(key):
         value = module.params[key]
 
-    # if key does exist, do a type check on it to validate it
-    else:
-        value_type = module.argument_spec[key].get('type', 'str')
-        type_checker = module._CHECK_ARGUMENT_TYPES_DISPATCHER[value_type]
-        type_checker(item[key])
-        value = item[key]
-
     # validate the param value (if validator func exists)
     validator = globals().get('validate_%s' % key)
     if all((value, validator)):
@@ -231,21 +226,17 @@ def get_param_value(key, item, module):
 
 
 def map_params_to_obj(module):
-    users = module.params['users']
-    if not users:
+    aggregate = module.params['aggregate']
+    if not aggregate:
         if not module.params['name'] and module.params['purge']:
             return list()
-        elif not module.params['name']:
-            module.fail_json(msg='username is required')
         else:
             aggregate = [{'name': module.params['name']}]
     else:
         aggregate = list()
-        for item in users:
+        for item in aggregate:
             if not isinstance(item, dict):
                 aggregate.append({'name': item})
-            elif 'name' not in item:
-                module.fail_json(msg='name is required')
             else:
                 aggregate.append(item)
 
@@ -278,8 +269,7 @@ def update_objects(want, have):
 def main():
     """ main entry point for module execution
     """
-    argument_spec = dict(
-        users=dict(type='list', aliases=['aggregate']),
+    element_spec = dict(
         name=dict(),
 
         full_name=dict(),
@@ -292,9 +282,20 @@ def main():
         state=dict(default='present', choices=['present', 'absent'])
     )
 
-    argument_spec.update(vyos_argument_spec)
-    mutually_exclusive = [('name', 'users')]
+    aggregate_spec = deepcopy(element_spec)
+    aggregate_spec['name'] = dict(required=True)
 
+    # remove default in aggregate spec, to handle common arguments
+    remove_default_spec(aggregate_spec)
+
+    argument_spec = dict(
+        aggregate=dict(type='list', elements='dict', options=aggregate_spec, aliases=['users']),
+    )
+
+    argument_spec.update(element_spec)
+    argument_spec.update(vyos_argument_spec)
+
+    mutually_exclusive = [('name', 'aggregate')]
     module = AnsibleModule(argument_spec=argument_spec,
                            mutually_exclusive=mutually_exclusive,
                            supports_check_mode=True)
diff --git a/test/integration/targets/net_interface/tests/vyos/basic.yaml b/test/integration/targets/net_interface/tests/vyos/basic.yaml
index 777016964ab..fa7d7f6603e 100644
--- a/test/integration/targets/net_interface/tests/vyos/basic.yaml
+++ b/test/integration/targets/net_interface/tests/vyos/basic.yaml
@@ -138,8 +138,9 @@
 - name: Disable interface on aggregate
   net_interface:
     aggregate:
-      - { name: eth1, description: test-interface-1,  speed: 100, duplex: half, mtu: 512, enabled: False}
-      - { name: eth2, description: test-interface-2,  speed: 1000, duplex: full, mtu: 256, enabled: False}
+      - name: eth1
+      - name: eth2
+    enabled: False
   register: result
 
 - assert:
@@ -151,8 +152,9 @@
 - name: Enable interface on aggregate
   net_interface:
     aggregate:
-      - { name: eth1, description: test-interface-1,  speed: 100, duplex: half, mtu: 512, enabled: True}
-      - { name: eth2, description: test-interface-2,  speed: 1000, duplex: full, mtu: 256, enabled: True}
+      - name: eth1
+      - name: eth2
+    enabled: True
   register: result
 
 - assert:
@@ -164,8 +166,9 @@
 - name: Delete interface aggregate
   net_interface:
     aggregate:
-      - { name: eth1, state: absent}
-      - { name: eth2, state: absent}
+      - name: eth1
+      - name: eth2
+    state: absent
   register: result
 
 - assert:
@@ -177,8 +180,9 @@
 - name: Delete interface aggregate (idempotent)
   net_interface:
     aggregate:
-      - { name: eth1, state: absent}
-      - { name: eth2, state: absent}
+      - name: eth1
+      - name: eth2
+    state: absent
   register: result
 
 - assert:
diff --git a/test/integration/targets/vyos_interface/tests/cli/basic.yaml b/test/integration/targets/vyos_interface/tests/cli/basic.yaml
index a4c3a7cd403..83c2a27b9a4 100644
--- a/test/integration/targets/vyos_interface/tests/cli/basic.yaml
+++ b/test/integration/targets/vyos_interface/tests/cli/basic.yaml
@@ -167,8 +167,9 @@
 - name: Disable interface on aggregate
   vyos_interface:
     aggregate:
-      - { name: eth1, description: test-interface-1, enabled: False}
-      - { name: eth2, description: test-interface-2, enabled: False}
+      - name: eth1
+      - name: eth2
+    enabled: False
   register: result
 
 - assert:
@@ -180,8 +181,9 @@
 - name: Enable interface on aggregate
   vyos_interface:
     aggregate:
-      - { name: eth1, description: test-interface-1, enabled: True}
-      - { name: eth2, description: test-interface-2, enabled: True}
+      - name: eth1
+      - name: eth2
+    enabled: True
   register: result
 
 - assert:
@@ -193,8 +195,9 @@
 - name: Delete interface aggregate
   vyos_interface:
     aggregate:
-      - { name: eth1, state: absent}
-      - { name: eth2, state: absent}
+      - name: eth1
+      - name: eth2
+    state: absent
   register: result
 
 - assert:
@@ -206,8 +209,9 @@
 - name: Delete interface aggregate (idempotent)
   vyos_interface:
     aggregate:
-      - { name: eth1, state: absent}
-      - { name: eth2, state: absent}
+      - name: eth1
+      - name: eth2
+    state: absent
   register: result
 
 - assert:
diff --git a/test/integration/targets/vyos_linkagg/tests/cli/basic.yaml b/test/integration/targets/vyos_linkagg/tests/cli/basic.yaml
index 0b66905de40..27c2df1209c 100644
--- a/test/integration/targets/vyos_linkagg/tests/cli/basic.yaml
+++ b/test/integration/targets/vyos_linkagg/tests/cli/basic.yaml
@@ -156,8 +156,8 @@
 - name: Remove collection of linkagg definitions
   vyos_linkagg:
     aggregate:
-      - { name: bond0 }
-      - { name: bond1 }
+      - name: bond0
+      - name: bond1
     state: absent
   register: result
 
@@ -172,8 +172,8 @@
 - name: Remove collection of linkagg definitions again (idempotent)
   vyos_linkagg:
     aggregate:
-      - { name: bond0 }
-      - { name: bond1 }
+      - name: bond0
+      - name: bond1
     state: absent
   register: result
 
diff --git a/test/integration/targets/vyos_lldp_interface/tests/cli/basic.yaml b/test/integration/targets/vyos_lldp_interface/tests/cli/basic.yaml
index 53c8f4c47a2..089d9b7a982 100644
--- a/test/integration/targets/vyos_lldp_interface/tests/cli/basic.yaml
+++ b/test/integration/targets/vyos_lldp_interface/tests/cli/basic.yaml
@@ -90,8 +90,8 @@
 - name: Create aggregate of LLDP interface configurations
   vyos_lldp_interface:
     aggregate:
-    - { name: eth1 }
-    - { name: eth2 }
+    - name: eth1
+    - name: eth2
     state: present
   register: result
 
@@ -104,8 +104,8 @@
 - name: Create aggregate of LLDP interface configurations again (idempotent)
   vyos_lldp_interface:
     aggregate:
-    - { name: eth1 }
-    - { name: eth2 }
+    - name: eth1
+    - name: eth2
     state: present
   register: result
 
@@ -116,7 +116,7 @@
 - name: Override LLDP interface configuration on aggregate
   vyos_lldp_interface:
     aggregate:
-    - { name: eth1 }
+    - name: eth1
     - { name: eth2, state: disabled }
     state: present
   register: result
@@ -129,7 +129,7 @@
 - name: Override LLDP interface configuration on aggregate again (idempotent)
   vyos_lldp_interface:
     aggregate:
-    - { name: eth1 }
+    - name: eth1
     - { name: eth2, state: disabled }
     state: present
   register: result
@@ -141,8 +141,8 @@
 - name: Delete aggregate of LLDP interface configurations
   vyos_lldp_interface:
     aggregate:
-    - { name: eth1 }
-    - { name: eth2 }
+    - name: eth1
+    - name: eth2
     state: absent
   register: result
 
@@ -155,8 +155,8 @@
 - name: Delete aggregate of LLDP interface configurations (idempotent)
   vyos_lldp_interface:
     aggregate:
-    - { name: eth1 }
-    - { name: eth2 }
+    - name: eth1
+    - name: eth2
     state: absent
   register: result