Repository: ansible/ansible

Issue:
Add support to create nested groups using group_by
For instance using the syntax below:

```
    - name: create dynamic active / passive groups
      group_by:
        key: "tiers-production-app-mysql-replication-master-{{ 'active' if hostvars[inventory_hostname]['ansible_eth1_1'] is defined else 'passive' }}"
        parent: 'tiers-production-app-mysql-replication-master'
```



Discussion:
@tersmitten Greetings! Thanks for taking the time to open this issue. In order for the community to handle your issue effectively, we need a bit more information. 

Here are the items we could not find in your description:
- issue type
- ansible version
- component name

Please set the description of this issue with this template:
https://raw.githubusercontent.com/ansible/ansible/devel/.github/ISSUE_TEMPLATE.md

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: issue_missing_data --->

Resolving patch:
diff --git a/lib/ansible/modules/inventory/group_by.py b/lib/ansible/modules/inventory/group_by.py
--- a/lib/ansible/modules/inventory/group_by.py
+++ b/lib/ansible/modules/inventory/group_by.py
@@ -33,6 +33,12 @@
     description:
     - The variables whose values will be used as groups
     required: true
+  parents:
+    description:
+    - The list of the parent groups
+    required: false
+    default: "all"
+    version_added: "2.4"
 author: "Jeroen Hoekx (@jhoekx)"
 notes:
   - Spaces in group names are converted to dashes '-'.
@@ -47,4 +53,11 @@
 # Create groups like 'kvm-host'
 - group_by:
     key: virt_{{ ansible_virtualization_type }}_{{ ansible_virtualization_role }}
+
+# Create nested groups
+- group_by:
+    key: el{{ ansible_distribution_major_version }}-{{ ansible_architecture }}
+    parents:
+      - el{{ ansible_distribution_major_version }}
+
 '''
diff --git a/lib/ansible/plugins/action/group_by.py b/lib/ansible/plugins/action/group_by.py
--- a/lib/ansible/plugins/action/group_by.py
+++ b/lib/ansible/plugins/action/group_by.py
@@ -18,6 +18,7 @@
 __metaclass__ = type
 
 from ansible.plugins.action import ActionBase
+from ansible.module_utils.six import string_types
 
 
 class ActionModule(ActionBase):
@@ -38,8 +39,11 @@ def run(self, tmp=None, task_vars=None):
             return result
 
         group_name = self._task.args.get('key')
-        group_name = group_name.replace(' ', '-')
+        parent_groups = self._task.args.get('parents', ['all'])
+        if isinstance(parent_groups, string_types):
+            parent_groups = [parent_groups]
 
         result['changed'] = False
-        result['add_group'] = group_name
+        result['add_group'] = group_name.replace(' ', '-')
+        result['parent_groups'] = [name.replace(' ', '-') for name in parent_groups]
         return result
diff --git a/lib/ansible/plugins/strategy/__init__.py b/lib/ansible/plugins/strategy/__init__.py
--- a/lib/ansible/plugins/strategy/__init__.py
+++ b/lib/ansible/plugins/strategy/__init__.py
@@ -632,12 +632,17 @@ def _add_group(self, host, result_item):
         # host object from the master inventory
         real_host = self._inventory.hosts[host.name]
         group_name = result_item.get('add_group')
+        parent_group_names = result_item.get('parent_groups', [])
 
-        if group_name not in self._inventory.groups:
-            # create the new group and add it to inventory
-            self._inventory.add_group(group_name)
-            changed = True
+        for name in [group_name] + parent_group_names:
+            if name not in self._inventory.groups:
+                # create the new group and add it to inventory
+                self._inventory.add_group(name)
+                changed = True
         group = self._inventory.groups[group_name]
+        for parent_group_name in parent_group_names:
+            parent_group = self._inventory.groups[parent_group_name]
+            parent_group.add_child_group(group)
 
         if real_host.name not in group.get_hosts():
             group.add_host(real_host)


Test patch:
diff --git a/test/integration/targets/group_by/test_group_by.yml b/test/integration/targets/group_by/test_group_by.yml
--- a/test/integration/targets/group_by/test_group_by.yml
+++ b/test/integration/targets/group_by/test_group_by.yml
@@ -185,3 +185,21 @@
   tasks:
     - name: check group_vars variable overrides for camelus
       assert: { that: ["uno == 1", "dos == 'two'", "tres == 3"] }
+
+- name: Nested group validation
+  hosts: lama
+  gather_facts: false
+  tasks:
+    - name: group by genus with parent
+      group_by: key=vicugna-{{ genus }} parents=vicugna
+    - name: check group_vars variable overrides for vicugna-lama
+      assert: { that: ["uno == 1", "dos == 2", "tres == 'three'"] }
+
+    - name: group by genus with nonexistent parent
+      group_by:
+        key: "{{ genus }}"
+        parents:
+          - oxydactylus
+          - stenomylus
+    - name: check parent groups
+      assert: { that: ["'oxydactylus' in group_names", "'stenomylus' in group_names"] }