1#!/usr/local/bin/python3.8
2#
3# Copyright (c) 2021 Paul Aiton, < @paultaiton >
4#
5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
6
7from __future__ import absolute_import, division, print_function
8__metaclass__ = type
9
10
11DOCUMENTATION = '''
12---
13module: azure_rm_managementgroup_info
14
15version_added: "1.5.0"
16
17short_description: Get Azure Management Group facts
18
19description:
20    - Get facts for a specific Management Group or all Management Groups.
21
22options:
23    name:
24        description:
25            - Limit results to a specific management group by name.
26            - Mutually exclusive with I(id).
27        aliases:
28            - management_group_name
29        type: str
30    id:
31        description:
32            - Limit results to a specific management group by id.
33            - Mutually exclusive with I(name).
34        type: str
35    flatten:
36        description:
37            - If c(True) then child management_groups and subscriptions will be copied to the root
38              of the management_groups and subscriptions return list respectively.
39            - By default c(False), child elements will only apear in the nested complex.
40            - Option only matters when I(children) is c(True), and will otherwise be silently ignored.
41        type: bool
42        default: False
43    children:
44        description:
45            - If c(False), then only I(name) or I(id) group will be fetched, or only the list of root groups.
46            - If c(True), then the children groups will also be returned.
47        type: bool
48        default: False
49    recurse:
50        description:
51            - By default, c(False), only the direct children are returned if I(children) is c(True).
52            - If c(True), then all descendants of the heirarchy are returned.
53            - Option only matters when I(children) is c(True), and will otherwise be silently ignored.
54        type: bool
55        default: False
56
57notes:
58    - azure_rm_managementgroup_info - The roles assigned to the principal executing the playbook will determine what is
59      a root management_group. You may also be able to request the details of a parent management group, but unable to
60      fetch that group. It is highly recommended that if I(children) is set c(True) that specific management groups are
61      requested since a list of all groups will require an additional Azure API call for each returned group.
62
63seealso:
64    - module: azure_rm_subscription_info
65      description: module to look up more in depth information on subscriptions; for example tags.
66    - module: azure_rm_roleassignment_info
67      description: module to look up RBAC role assignments, which can use management group id as scope.
68
69extends_documentation_fragment:
70    - azure.azcollection.azure
71
72author:
73    - Paul Aiton (@paultaiton)
74'''
75
76EXAMPLES = '''
77- name: Get facts for all root management groups for authenticated principal
78  azure_rm_managementgroup_info:
79
80- name: Get facts for one management group by id with direct children
81  azure_rm_managementgroup_info:
82    id: /providers/Microsoft.Management/managementGroups/contoso-group
83    children: True
84
85- name: Get facts for one management group by name with all children, flattened into top list
86  azure_rm_managementgroup_info:
87    name: "contoso-group"
88    children: True
89    recurse: True
90    flatten: True
91'''
92
93RETURN = '''
94management_groups:
95    description:
96        - List of Management Group dicts.
97    returned: always
98    type: list
99    contains:
100        display_name:
101            description: Management Group display name.
102            returned: always
103            type: str
104            sample: "My Management Group"
105        id:
106            description: Management Group fully qualified id.
107            returned: always
108            type: str
109            sample: "/providers/Microsoft.Management/managementGroups/group-name"
110        name:
111            description: Management Group display name.
112            returned: always
113            type: str
114            sample: group-name
115        tenant_id:
116            description: Management Group tenant id
117            returned: always
118            type: str
119            sample: "00000000-0000-0000-0000-000000000000"
120        type:
121            description: Management Group type
122            returned: always
123            type: str
124            sample: "/providers/Microsoft.Management/managementGroups"
125        children:
126            description: Child management groups or subscriptions.
127            returned: if I(children) is c(True)
128            type: list
129            sample: Nested list of children. Same as top groups, but without tenant_id.
130subscriptions:
131    description:
132        - List of subscription objects.
133    returned: if I(children) and I(flatten) are both c(True)
134    type: list
135    contains:
136        display_name:
137            description: subscription display name.
138            returned: always
139            type: str
140            sample: "some-subscription-name"
141        id:
142            description: subscription fully qualified id.
143            returned: always
144            type: str
145            sample: "/subscriptions/00000000-0000-0000-0000-feedc0ffee000000"
146        subscription_id:
147            description: subscription guid.
148            returned: always
149            type: str
150            sample: "00000000-0000-0000-0000-feedc0ffee000000"
151        type:
152            description: Management Group type
153            returned: always
154            type: str
155            sample: "/subscriptions"
156'''
157
158try:
159    from msrestazure.azure_exceptions import CloudError
160except Exception:
161    # This is handled in azure_rm_common
162    pass
163
164from ansible_collections.azure.azcollection.plugins.module_utils.azure_rm_common import AzureRMModuleBase
165
166
167class AzureRMManagementGroupInfo(AzureRMModuleBase):
168    def __init__(self):
169        self.module_arg_spec = dict(
170            children=dict(type='bool', default=False),
171            flatten=dict(type='bool', default=False),
172            id=dict(type='str'),
173            name=dict(type='str', aliases=['management_group_name']),
174            recurse=dict(type='bool', default=False)
175        )
176
177        self.results = dict(
178            changed=False,
179            management_groups=[]
180        )
181
182        self.children = None
183        self.flatten = None
184        self.id = None
185        self.name = None
186        self.recurse = None
187
188        mutually_exclusive = [['name', 'id']]
189
190        super(AzureRMManagementGroupInfo, self).__init__(self.module_arg_spec,
191                                                         supports_check_mode=True,
192                                                         supports_tags=False,
193                                                         mutually_exclusive=mutually_exclusive,
194                                                         facts_module=True)
195
196    def exec_module(self, **kwargs):
197        for key in self.module_arg_spec:
198            setattr(self, key, kwargs[key])
199
200        response = []
201
202        if self.name or self.id:
203            response = [self.get_item()]
204        else:
205            response = self.list_items()
206
207        if self.flatten and self.children:
208            self.results['subscriptions'] = []
209            for group in response:
210                new_groups = []
211                new_subscriptions = []
212                self.results['management_groups'].append(group)
213                new_groups, new_subscriptions = self.flatten_group(group)
214                self.results['management_groups'] += new_groups
215                self.results['subscriptions'] += new_subscriptions
216        else:
217            self.results['management_groups'] = response
218
219        return self.results
220
221    def get_item(self, mg_name=None):
222        if not mg_name:
223            # The parameter to SDK's management_groups.get(group_id) is not correct,
224            # it only works with a bare name value, and not the fqid.
225            if self.id and not self.name:
226                mg_name = self.id.split('/')[-1]
227            else:
228                mg_name = self.name
229
230        expand = 'children' if self.children else None
231        try:
232            response = self.management_groups_client.management_groups.get(group_id=mg_name,
233                                                                           expand=expand,
234                                                                           recurse=self.recurse)
235        except CloudError:
236            self.log('No Management group {0} found.'.format(mg_name))
237            response = None
238
239        return self.to_dict(response)
240
241    def list_items(self):
242        self.log('List all management groups.')
243
244        results = []
245        response = []
246
247        try:
248            response = self.management_groups_client.management_groups.list()
249        except CloudError:
250            self.log('No Management groups found.')
251            pass  # default to response of an empty list
252
253        if self.children:
254            # list method cannot return children, so we must iterate over root management groups to
255            # get each one individually.
256            results = [self.get_item(mg_name=item.name) for item in response]
257        else:
258            results = [self.to_dict(item) for item in response]
259
260        return results
261
262    def to_dict(self, azure_object):
263        if azure_object.type == '/providers/Microsoft.Management/managementGroups':
264            return_dict = dict(
265                display_name=azure_object.display_name,
266                id=azure_object.id,
267                name=azure_object.name,
268                type=azure_object.type
269            )
270
271            # If group has no children, then property will be set to None type.
272            # We want an empty list so that it can be used in loops without issue.
273            if self.children and azure_object.as_dict().get('children'):
274                return_dict['children'] = [self.to_dict(item) for item in azure_object.children]
275            elif self.children:
276                return_dict['children'] = []
277
278            if azure_object.as_dict().get('details', {}).get('parent'):
279                parent_dict = azure_object.as_dict().get('details', {}).get('parent')
280                return_dict['parent'] = dict(
281                    display_name=parent_dict.get('display_name'),
282                    id=parent_dict.get('id'),
283                    name=parent_dict.get('name')
284                )
285
286        elif azure_object.type == 'asdfasdf/subscriptions':
287            return_dict = dict(
288                display_name=azure_object.display_name,
289                id=azure_object.id,
290                subscription_id=azure_object.name,
291                type=azure_object.type
292            )
293        else:
294            # In theory if the Azure API is updated to include another child type of management groups,
295            # the code here will prevent an exception. But there should be logic added in an update to take
296            # care of a new child type of management groups.
297            return_dict = dict(
298                state="This is an unknown and unexpected object. "
299                      + "You should report this as a bug to the ansible-collection/azcollection "
300                      + "project on github. Please include the object type in your issue report, "
301                      + "and @ the authors of this module. ",
302                type=azure_object.as_dict().get('type', None)
303            )
304
305        if azure_object.as_dict().get('tenant_id'):
306            return_dict['tenant_id'] = azure_object.tenant_id
307
308        return return_dict
309
310    def flatten_group(self, management_group):
311        management_group_list = []
312        subscription_list = []
313        if management_group.get('children'):
314            for child in management_group.get('children', []):
315                if child.get('type') == '/providers/Microsoft.Management/managementGroups':
316                    management_group_list.append(child)
317                    new_groups, new_subscriptions = self.flatten_group(child)
318                    management_group_list += new_groups
319                    subscription_list += new_subscriptions
320                elif child.get('type') == '/subscriptions':
321                    subscription_list.append(child)
322        return management_group_list, subscription_list
323
324
325def main():
326    AzureRMManagementGroupInfo()
327
328
329if __name__ == '__main__':
330    main()
331