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