└── unifi_controller_facts ├── inventory ├── test.yml ├── README.md └── library └── unifi_controller_facts.py /unifi_controller_facts/inventory: -------------------------------------------------------------------------------- 1 | localhost ansible_connection=local 2 | -------------------------------------------------------------------------------- /unifi_controller_facts/test.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | - name: UniFi Controller Facts Ansible Module Test 4 | hosts: all 5 | gather_facts: false 6 | 7 | tasks: 8 | - name: List online clients 9 | unifi_controller_facts: 10 | controller_baseURL: "https://192.168.1.224:8443" 11 | controller_username: "admin" 12 | controller_password: "changeme" 13 | controller_site: "default" 14 | query: list_online_clients 15 | register: returnedData 16 | 17 | - name: debug 18 | debug: 19 | msg: "{{ returnedData }}" 20 | -------------------------------------------------------------------------------- /unifi_controller_facts/README.md: -------------------------------------------------------------------------------- 1 | # unifi_controller_facts - UniFi Controller Facts Ansible Module 2 | 3 | This project is to extend the UniFi Controller API and consume it via a native Ansible module. 4 | There are a few difficulties with this sort of thing, first being the lack of UniFi Controller API documentation. It's pretty much non-existant. This project is largely based on this project: [https://github.com/Art-of-WiFi/UniFi-API-client/], this is basically a Python port of that client, you may notice many similarities. 5 | 6 | ## What does this do? 7 | This module provides a simple, yet needed extension of the UniFi Controller API. With this module, you can query your UniFi Controller and gather facts and information. This module doesn't have many actions (read: none), the primary purpose is to gather information from the UniFi Controller. In order to manipulate your UniFi Controller, you'll need my unifi_controller Ansible module (coming soon). 8 | 9 | ## Instructions 10 | 1) Download, fork, obtain this source code somehow 11 | 2) Load it onto a machine that has Ansible installed 12 | 3) Note the test.yml file for examples on how to use the module 13 | 14 | ## Example 15 | 16 | ```yaml 17 | - name: Get User List 18 | unifi_controller_facts: 19 | controller_baseURL: "https://127.0.0.1:8443" 20 | controller_username: "admin" 21 | controller_password: "changeme" 22 | controller_site: "default" 23 | query: list_users 24 | register: returnedData 25 | ``` 26 | 27 | ## Available queries 28 | * **Clients** 29 | * list_online_clients 30 | * list_clients 31 | * list_guests 32 | * list_users 33 | * list_user_groups 34 | * stat_all_users 35 | * stat_authorizations 36 | * stat_sessions 37 | * **Devices** 38 | * list_devices 39 | * list_wlan_goups 40 | * list_rouge_access_points 41 | * list_known_rogue_access_points 42 | * list_tags 43 | * **Stats** 44 | * five_minute_site_stats 45 | * hourly_site_stats 46 | * daily_site_stats 47 | * all_site_stats 48 | * five_minute_access_point_stats 49 | * hourly_access_point_stats 50 | * daily_access_point_stats 51 | * five_minute_site_dashboard_metrics 52 | * hourly_site_dashboard_metrics 53 | * site_health_metrics 54 | * port_forwarding_stats 55 | * dpi_stats 56 | * **Hotspot** 57 | * stat_vouchers 58 | * stat_payments 59 | * list_hotspot_operators 60 | * **Configuration** 61 | * list_sites 62 | * sysinfo 63 | * list_site_settings 64 | * list_admins_for_current_site 65 | * list_wlan_configuration 66 | * list_current_channels 67 | * list_voip_extensions 68 | * list_network_configuration 69 | * list_port_configuration 70 | * list_port_forwarding_rules 71 | * list_firewall_groups 72 | * dynamic_dns_configuration 73 | * list_country_codes 74 | * list_auto_backups 75 | * list_radius_profiles 76 | * list_radius_accounts 77 | * **Messages** 78 | * list_alarms 79 | * list_events 80 | 81 | 82 | ## Known Issues 83 | * Documentation in embedded in the module script. It should be copied and compiled externally probably a bit better... 84 | * There are a few untested functions that I don't have the current capacity to fully develop/test, being that I lack a USG, and only have 2 UAPs. If anyone would like to contribute code to support the UniFi switches, and cameras, etc, or maybe donate a device, that'd be much appreciated. 85 | * There are also a few functions that seem to have been broken/made unavailable in the version of the UniFi Controller I operate (my setup isn't that old, I've only tested the 5.8.x line) 86 | 87 | ## License 88 | GNU GPLv3 89 | 90 | ## Credits 91 | * Based off of this project, so many thanks to the {wo}men who provided the groundwork https://github.com/Art-of-WiFi/UniFi-API-client/ 92 | * Ansible module was based off this starting point: https://blog.toast38coza.me/custom-ansible-module-hello-world/ 93 | -------------------------------------------------------------------------------- /unifi_controller_facts/library/unifi_controller_facts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | DOCUMENTATION = ''' 5 | --- 6 | module: unifi_controller_facts 7 | short_description: Manage your UniFi Controllers with Ansible 8 | author: "Ken Moini (@kenmoini)" 9 | ''' 10 | 11 | EXAMPLES = ''' 12 | - name: Get User List 13 | unifi_controller_facts: 14 | controller_baseURL: "https://127.0.0.1:8443" 15 | controller_username: "admin" 16 | controller_password: "changeme" 17 | controller_site: "default" 18 | query: list_users 19 | register: returndData 20 | 21 | - name: List online clients 22 | unifi_controller_facts: 23 | controller_baseURL: "https://192.168.1.224:8443" 24 | controller_username: "admin" 25 | controller_password: "changeme" 26 | controller_site: "default" 27 | query: list_online_clients 28 | register: returndData 29 | ''' 30 | 31 | from ansible.module_utils.basic import * 32 | import json 33 | import requests 34 | import time 35 | 36 | s = requests.session() 37 | 38 | # --------------------------------------------------------------------------------------------------------------------- 39 | # Function: unifi_login 40 | # --------------------------------------------------------------------------------------------------------------------- 41 | # Logs in the user, establishes the cookie 42 | # required parameter = (str) The hostname and port of the target controller 43 | # required parameter = (str) The username to authenticate as 44 | # required parameter = (str) The password to authenticate with 45 | # 46 | # Returns 47 | # dict( 48 | # "status_code" => (int) The HTTP Status Code returned, 49 | # "data" => (json) Returned JSON from login attempt 50 | # ) 51 | # --------------------------------------------------------------------------------------------------------------------- 52 | def unifi_login(data): 53 | s.headers.update({'referer': data['controller_baseURL'] + "/login"}) 54 | l = s.post(data['controller_baseURL'] + "/api/login", json.dumps({"username":data["controller_username"], "password":data["controller_password"]}), verify=False) 55 | return {"status_code": l.status_code, "data": l.json()} 56 | # --------------------------------------------------------------------------------------------------------------------- 57 | # Function: unifi_logout 58 | # --------------------------------------------------------------------------------------------------------------------- 59 | # Logs the user out, destroys the session 60 | # required parameter = (str) The hostname and port of the target controller 61 | # 62 | # Returns 63 | # stdObject 64 | # --------------------------------------------------------------------------------------------------------------------- 65 | def unifi_logout(controller_baseURL): 66 | l = s.get(controller_baseURL + "/logout") 67 | return l 68 | # --------------------------------------------------------------------------------------------------------------------- 69 | # Function: process_response 70 | # --------------------------------------------------------------------------------------------------------------------- 71 | # Process response returned by API commands 72 | # --------------------------------------------------------------------------------------------------------------------- 73 | def process_response(response_json): 74 | decoded_response = json.loads(response_json.text) 75 | if decoded_response['meta']['rc'] == "ok": 76 | if isinstance(decoded_response['data'], list): 77 | return False, True, {"status": response_json.status_code, "data": response_json.text} 78 | else: 79 | return False, True, {"status": response_json.status_code, "data": "SUCCESS"} 80 | else: 81 | return True, False, {"status": response_json.status_code, "data": response_json.text} 82 | 83 | # --------------------------------------------------------------------------------------------------------------------- 84 | # Function: process_response_boolean 85 | # --------------------------------------------------------------------------------------------------------------------- 86 | # Process response returned by API commands, returns SUCCESS if the response was just a boolean 87 | # --------------------------------------------------------------------------------------------------------------------- 88 | def process_response_boolean(response_json): 89 | decoded_response = json.loads(response_json.text) 90 | if decoded_response['meta']['rc'] == "ok": 91 | return False, True, {"status": response_json.status_code, "data": "SUCCESS"} 92 | else: 93 | return True, False, {"status": response_json.status_code, "data": response_json.text} 94 | 95 | # --------------------------------------------------------------------------------------------------------------------- 96 | # Function: list_online_clients - List online client device(s) 97 | # --------------------------------------------------------------------------------------------------------------------- 98 | # returns an array of online client device objects, or in case of a single device request, returns a single client device object 99 | # optional parameter = the MAC address of a single online client device for which the call must be made 100 | # --------------------------------------------------------------------------------------------------------------------- 101 | def list_online_clients(data): 102 | if data['client_mac'] is not None: 103 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/sta/" + data['client_mac'].strip(), verify=False) 104 | else: 105 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/sta/", verify=False) 106 | return process_response(responseData) 107 | 108 | # --------------------------------------------------------------------------------------------------------------------- 109 | # Function: list_guests - List guest devices [UNTESTED] 110 | # --------------------------------------------------------------------------------------------------------------------- 111 | # returns an array of guest device objects with valid access 112 | # optional parameter = time frame in hours to go back to list guests with valid access (default = 24*365 hours) 113 | # --------------------------------------------------------------------------------------------------------------------- 114 | def list_guests(data): 115 | if data['since'] is not None: 116 | within = int(data['since']) 117 | else: 118 | within = 8760 #In hours, Default: 1yr 24*365 119 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/guest", params={"within": within}, verify=False) 120 | return process_response(responseData) 121 | 122 | # --------------------------------------------------------------------------------------------------------------------- 123 | # Function: list_users - List client devices 124 | # --------------------------------------------------------------------------------------------------------------------- 125 | # returns an array of known client device objects 126 | # --------------------------------------------------------------------------------------------------------------------- 127 | def list_users(data): 128 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/list/user", verify=False) 129 | return process_response(responseData) 130 | 131 | # --------------------------------------------------------------------------------------------------------------------- 132 | # Function: list_user_groups - List user groups 133 | # --------------------------------------------------------------------------------------------------------------------- 134 | # returns an array of user group objects 135 | # --------------------------------------------------------------------------------------------------------------------- 136 | def list_user_groups(data): 137 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/list/usergroup", verify=False) 138 | return process_response(responseData) 139 | 140 | # --------------------------------------------------------------------------------------------------------------------- 141 | # Function: stat_all_users - List all client devices ever connected to the site 142 | # --------------------------------------------------------------------------------------------------------------------- 143 | # returns an array of client device objects 144 | # optional parameter = hours to go back (default is 8760 hours or 1 year) 145 | # 146 | # NOTES: 147 | # - is only used to select clients that were online within that period, 148 | # the returned stats per client are all-time totals, irrespective of the value of 149 | # --------------------------------------------------------------------------------------------------------------------- 150 | def stat_all_users(data): 151 | if data['since'] is not None: 152 | within = int(data['since']) 153 | else: 154 | within = 8760 #In hours, Default: 1yr 24*365 155 | paramsToSend = {"within": within, "type": "all", "conn": "all"} # type: all/user/guest, conn: all/wired/wireless 156 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/alluser", params=paramsToSend, verify=False) 157 | return process_response(responseData) 158 | 159 | # --------------------------------------------------------------------------------------------------------------------- 160 | # Function: stat_authorizations - Show all authorizations 161 | # --------------------------------------------------------------------------------------------------------------------- 162 | # returns an array of authorization objects 163 | # optional parameter = Unix timestamp in seconds 164 | # optional parameter = Unix timestamp in seconds 165 | # 166 | # NOTES: 167 | # - defaults to the past 7*24 hours 168 | # --------------------------------------------------------------------------------------------------------------------- 169 | def stat_authorizations(data): 170 | if data['end_epoch'] is not None: 171 | end = int(data['end_epoch']) 172 | else: 173 | end = int(time.time()) 174 | if data['start_epoch'] is not None: 175 | start = int(data['start_epoch']) 176 | else: 177 | start = int(end - (7*24*3600)) 178 | paramsToSend = {"start": start, "end": end} 179 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/authorization", params=paramsToSend, verify=False) 180 | return process_response(responseData) 181 | 182 | # --------------------------------------------------------------------------------------------------------------------- 183 | # Function: stat_sessions - Show all login sessions 184 | # --------------------------------------------------------------------------------------------------------------------- 185 | # returns an array of login session objects for all devices or a single device 186 | # optional parameter = Unix timestamp in seconds 187 | # optional parameter = Unix timestamp in seconds 188 | # optional parameter = client MAC address to return sessions for (can only be used when start and end are also provided) 189 | # optional parameter = client type to return sessions for, can be 'all', 'guest' or 'user'; default value is 'all' 190 | # 191 | # NOTES: 192 | # - defaults to the past 7*24 hours 193 | # --------------------------------------------------------------------------------------------------------------------- 194 | def stat_sessions(data): 195 | if data['end_epoch'] is not None: 196 | end = int(data['end_epoch']) 197 | else: 198 | end = int(time.time()) 199 | if data['start_epoch'] is not None: 200 | start = int(data['start_epoch']) 201 | else: 202 | start = int(end - (7*24*3600)) 203 | if data['client_mac'] is not None: 204 | paramsToSend = { "start": start, "end": end, "type": "all", "mac": data['client_mac'].strip() } 205 | else: 206 | paramsToSend = { "start": start, "end": end, "type": "all" } 207 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/session", params=paramsToSend, verify=False) 208 | return process_response(responseData) 209 | 210 | # --------------------------------------------------------------------------------------------------------------------- 211 | # Function: list_devices - List access points and other devices under management of the controller (USW and/or USG devices) 212 | # --------------------------------------------------------------------------------------------------------------------- 213 | # returns an array of known device objects (or a single device when using the parameter) 214 | # optional parameter = the MAC address of a single device for which the call must be made 215 | # --------------------------------------------------------------------------------------------------------------------- 216 | def list_devices(data): 217 | if data['device_mac'] is not None: 218 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/device/" + data['client_mac'].strip(), verify=False) 219 | else: 220 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/device/", verify=False) 221 | return process_response(responseData) 222 | 223 | # --------------------------------------------------------------------------------------------------------------------- 224 | # Function: list_wlan_groups -List wlan_groups 225 | # --------------------------------------------------------------------------------------------------------------------- 226 | # returns an array containing known wlan_groups 227 | # --------------------------------------------------------------------------------------------------------------------- 228 | def list_wlan_groups(data): 229 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/list/wlangroup", verify=False) 230 | return process_response(responseData) 231 | 232 | # --------------------------------------------------------------------------------------------------------------------- 233 | # Function: list_rouge_access_points - List rogue/neighboring access points 234 | # --------------------------------------------------------------------------------------------------------------------- 235 | # returns an array of rogue/neighboring access point objects 236 | # optional parameter = hours to go back to list discovered "rogue" access points (default = 24 hours) 237 | # --------------------------------------------------------------------------------------------------------------------- 238 | def list_rouge_access_points(data): 239 | if data['since'] is not None: 240 | within = int(data['since']) 241 | else: 242 | within = 24 #In hours, Default: 24 243 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/rogueap", params={"within": within}, verify=False) 244 | return process_response(responseData) 245 | 246 | # --------------------------------------------------------------------------------------------------------------------- 247 | # Function: list_known_rogue_access_points - List known rogue access points 248 | # --------------------------------------------------------------------------------------------------------------------- 249 | # returns an array of known rogue access point objects 250 | # --------------------------------------------------------------------------------------------------------------------- 251 | def list_known_rogue_access_points(data): 252 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/rest/rogueknown", verify=False) 253 | return process_response(responseData) 254 | 255 | # --------------------------------------------------------------------------------------------------------------------- 256 | # Function: list_tags - List (device) tags (using REST) 257 | # --------------------------------------------------------------------------------------------------------------------- 258 | # returns an array of known device tag objects 259 | # 260 | # NOTES: this endpoint was introduced with controller versions 5.5.X 261 | # --------------------------------------------------------------------------------------------------------------------- 262 | def list_tags(data): 263 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/rest/tag", verify=False) 264 | return process_response(responseData) 265 | 266 | # --------------------------------------------------------------------------------------------------------------------- 267 | # Function: five_minute_site_stats - 5 minutes site stats method [UNTESTED] 268 | # --------------------------------------------------------------------------------------------------------------------- 269 | # returns an array of 5-minute stats objects for the current site 270 | # optional parameter = Unix timestamp in milliseconds 271 | # optional parameter = Unix timestamp in milliseconds 272 | # 273 | # NOTES: 274 | # - defaults to the past 12 hours 275 | # - this function/method is only supported on controller versions 5.5.* and later 276 | # - make sure that the retention policy for 5 minutes stats is set to the correct value in 277 | # the controller settings 278 | # --------------------------------------------------------------------------------------------------------------------- 279 | def five_minute_site_stats(data): 280 | if data['end_epoch'] is not None: 281 | end = int(data['end_epoch']) 282 | else: 283 | end = int(time.time() * 1000) #Supa future 284 | if data['start_epoch'] is not None: 285 | start = int(data['start_epoch']) 286 | else: 287 | start = int(end - (12*3600*1000)) 288 | paramsToSend = {"attrs": ['bytes', 'wan-tx_bytes', 'wan-rx_bytes', 'wlan_bytes', 'num_sta', 'lan-num_sta', 'wlan-num_sta', 'time'], "start": start, "end": end} 289 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/report/5minutes.site", params=paramsToSend, verify=False) 290 | return process_response(responseData) 291 | 292 | # --------------------------------------------------------------------------------------------------------------------- 293 | # Function: hourly_site_stats - Hourly site stats [UNTESTED] 294 | # --------------------------------------------------------------------------------------------------------------------- 295 | # returns an array of hourly stats objects for the current site 296 | # optional parameter = Unix timestamp in milliseconds 297 | # optional parameter = Unix timestamp in milliseconds 298 | # 299 | # NOTES: 300 | # - defaults to the past 7*24 hours 301 | # - "bytes" are no longer returned with controller version 4.9.1 and later 302 | # --------------------------------------------------------------------------------------------------------------------- 303 | def hourly_site_stats(data): 304 | if data['end_epoch'] is not None: 305 | end = int(data['end_epoch']) 306 | else: 307 | end = int(time.time() * 1000) #Supa future 308 | if data['start_epoch'] is not None: 309 | start = int(data['start_epoch']) 310 | else: 311 | start = int(end - (7*24*3600*1000)) 312 | paramsToSend = {"attrs": ['bytes', 'wan-tx_bytes', 'wan-rx_bytes', 'wlan_bytes', 'num_sta', 'lan-num_sta', 'wlan-num_sta', 'time'], "start": start, "end": end} 313 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/report/hourly.site", params=paramsToSend, verify=False) 314 | return process_response(responseData) 315 | 316 | # --------------------------------------------------------------------------------------------------------------------- 317 | # Function: daily_site_stats - Daily site stats [UNTESTED] 318 | # --------------------------------------------------------------------------------------------------------------------- 319 | # returns an array of hourly stats objects for the current site 320 | # optional parameter = Unix timestamp in milliseconds 321 | # optional parameter = Unix timestamp in milliseconds 322 | # 323 | # NOTES: 324 | # - defaults to the past 7*24 hours 325 | # - "bytes" are no longer returned with controller version 4.9.1 and later 326 | # --------------------------------------------------------------------------------------------------------------------- 327 | def daily_site_stats(data): 328 | if data['end_epoch'] is not None: 329 | end = int(data['end_epoch']) 330 | else: 331 | end = int(time.time() * 1000) #Supa future 332 | if data['start_epoch'] is not None: 333 | start = int(data['start_epoch']) 334 | else: 335 | start = int(end - (52*7*24*3600*1000)) 336 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/report/daily.site", verify=False) 337 | return process_response(responseData) 338 | 339 | # --------------------------------------------------------------------------------------------------------------------- 340 | # Function: all_sites_stats - List sites stats 341 | # --------------------------------------------------------------------------------------------------------------------- 342 | # returns statistics for all sites hosted on this controller 343 | # 344 | # NOTES: this endpoint was introduced with controller version 5.2.9 345 | # --------------------------------------------------------------------------------------------------------------------- 346 | def all_sites_stats(data): 347 | responseData = s.get(data['controller_baseURL'] + "/api/stat/sites", verify=False) 348 | return process_response(responseData) 349 | 350 | # --------------------------------------------------------------------------------------------------------------------- 351 | # Function: five_minute_access_point_stats - 5 minutes stats method for a single access point or all access points [UNTESTED] 352 | # --------------------------------------------------------------------------------------------------------------------- 353 | # returns an array of 5-minute stats objects 354 | # optional parameter = Unix timestamp in milliseconds 355 | # optional parameter = Unix timestamp in milliseconds 356 | # optional parameter = AP MAC address to return stats for 357 | # 358 | # NOTES: 359 | # - defaults to the past 12 hours 360 | # - this function/method is only supported on controller versions 5.5.* and later 361 | # - make sure that the retention policy for 5 minutes stats is set to the correct value in 362 | # the controller settings 363 | # --------------------------------------------------------------------------------------------------------------------- 364 | def five_minute_access_point_stats(data): 365 | if data['end_epoch'] is not None: 366 | end = int(data['end_epoch']) 367 | else: 368 | end = int(time.time() * 1000) #Supa future 369 | if data['start_epoch'] is not None: 370 | start = int(data['start_epoch']) 371 | else: 372 | start = int(end - (12*3600*1000)) 373 | if data['device_mac'] is not None: 374 | paramsToSend = { "start": start, "end": end, "attrs": ['bytes', 'num_sta', 'time'], "mac": data['device_mac'].strip() } 375 | else: 376 | paramsToSend = { "start": start, "end": end, "attrs": ['bytes', 'num_sta', 'time'] } 377 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/report/5minutes.ap", params=paramsToSend, verify=False) 378 | return process_response(responseData) 379 | 380 | # --------------------------------------------------------------------------------------------------------------------- 381 | # Function: hourly_access_point_stats - Hourly stats method for a single access point or all access points [UNTESTED] 382 | # --------------------------------------------------------------------------------------------------------------------- 383 | # returns an array of hourly stats objects 384 | # optional parameter = Unix timestamp in milliseconds 385 | # optional parameter = Unix timestamp in milliseconds 386 | # optional parameter = AP MAC address to return stats for 387 | # 388 | # NOTES: 389 | # - defaults to the past 7*24 hours 390 | # - UniFi controller does not keep these stats longer than 5 hours with versions < 4.6.6 391 | # --------------------------------------------------------------------------------------------------------------------- 392 | def hourly_access_point_stats(data): 393 | if data['end_epoch'] is not None: 394 | end = int(data['end_epoch']) 395 | else: 396 | end = int(time.time() * 1000) #Supa future 397 | if data['start_epoch'] is not None: 398 | start = int(data['start_epoch']) 399 | else: 400 | start = int(end - (7*24*3600*1000)) 401 | if data['device_mac'] is not None: 402 | paramsToSend = { "start": start, "end": end, "attrs": ['bytes', 'num_sta', 'time'], "mac": data['device_mac'].strip() } 403 | else: 404 | paramsToSend = { "start": start, "end": end, "attrs": ['bytes', 'num_sta', 'time'] } 405 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/report/hourly.ap", params=paramsToSend, verify=False) 406 | return process_response(responseData) 407 | 408 | # --------------------------------------------------------------------------------------------------------------------- 409 | # Function: daily_access_point_stats - Daily stats method for a single access point or all access points [UNTESTED] 410 | # --------------------------------------------------------------------------------------------------------------------- 411 | # returns an array of daily stats objects 412 | # optional parameter = Unix timestamp in milliseconds 413 | # optional parameter = Unix timestamp in milliseconds 414 | # optional parameter = AP MAC address to return stats for 415 | # 416 | # NOTES: 417 | # - defaults to the past 7*24 hours 418 | # - UniFi controller does not keep these stats longer than 5 hours with versions < 4.6.6 419 | # --------------------------------------------------------------------------------------------------------------------- 420 | def daily_access_point_stats(data): 421 | if data['end_epoch'] is not None: 422 | end = int(data['end_epoch']) 423 | else: 424 | end = int(time.time() * 1000) #Supa future 425 | if data['start_epoch'] is not None: 426 | start = int(data['start_epoch']) 427 | else: 428 | start = int(end - (7*24*3600*1000)) 429 | if data['device_mac'] is not None: 430 | paramsToSend = { "start": start, "end": end, "attrs": ['bytes', 'num_sta', 'time'], "mac": data['device_mac'].strip() } 431 | else: 432 | paramsToSend = { "start": start, "end": end, "attrs": ['bytes', 'num_sta', 'time'] } 433 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/report/daily.ap", params=paramsToSend, verify=False) 434 | return process_response(responseData) 435 | 436 | # --------------------------------------------------------------------------------------------------------------------- 437 | # Function five_minute_site_dashboard_metrics - List dashboard metrics from the last 5 minutes 438 | # --------------------------------------------------------------------------------------------------------------------- 439 | # returns an array of dashboard metric objects (available since controller version 4.9.1.alpha) 440 | # --------------------------------------------------------------------------------------------------------------------- 441 | def five_minute_site_dashboard_metrics(data): 442 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/dashboard?scale=5minutes", verify=False) 443 | return process_response(responseData) 444 | 445 | # --------------------------------------------------------------------------------------------------------------------- 446 | # Function hourly_site_dashboard_metrics - List dashboard metrics from the last hour 447 | # --------------------------------------------------------------------------------------------------------------------- 448 | # returns an array of dashboard metric objects (available since controller version 4.9.1.alpha) 449 | # --------------------------------------------------------------------------------------------------------------------- 450 | def hourly_site_dashboard_metrics(data): 451 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/dashboard", verify=False) 452 | return process_response(responseData) 453 | 454 | # --------------------------------------------------------------------------------------------------------------------- 455 | # Function: site_health_metrics - List health metrics 456 | # --------------------------------------------------------------------------------------------------------------------- 457 | # returns an array of health metric objects 458 | # --------------------------------------------------------------------------------------------------------------------- 459 | def site_health_metrics(data): 460 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/health", verify=False) 461 | return process_response(responseData) 462 | 463 | # --------------------------------------------------------------------------------------------------------------------- 464 | # Function: port_forwarding_stats - List port forwarding stats 465 | # --------------------------------------------------------------------------------------------------------------------- 466 | # returns an array of port forwarding stats 467 | # --------------------------------------------------------------------------------------------------------------------- 468 | def port_forwarding_stats(data): 469 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/portforward", verify=False) 470 | return process_response(responseData) 471 | 472 | # --------------------------------------------------------------------------------------------------------------------- 473 | # Function: dpi_stats - List DPI stats 474 | # --------------------------------------------------------------------------------------------------------------------- 475 | # returns an array of DPI stats 476 | # --------------------------------------------------------------------------------------------------------------------- 477 | def dpi_stats(data): 478 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/dpi", verify=False) 479 | return process_response(responseData) 480 | 481 | # --------------------------------------------------------------------------------------------------------------------- 482 | # Function: stat_vouchers - List vouchers 483 | # --------------------------------------------------------------------------------------------------------------------- 484 | # returns an array of hotspot voucher objects 485 | # optional parameter = Unix timestamp in seconds 486 | # --------------------------------------------------------------------------------------------------------------------- 487 | def stat_vouchers(data): 488 | if data['created_time'] is not None: 489 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/voucher", params={"created_time": data['created_time']}, verify=False) 490 | else: 491 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/voucher", verify=False) 492 | return process_response(responseData) 493 | 494 | # --------------------------------------------------------------------------------------------------------------------- 495 | # Function: stat_payments - List payments [UNTESTED] 496 | # --------------------------------------------------------------------------------------------------------------------- 497 | # returns an array of hotspot payments 498 | # optional parameter = number of hours to go back to fetch payments 499 | # --------------------------------------------------------------------------------------------------------------------- 500 | def stat_payments(data): 501 | if data['since'] is not None: 502 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/payment?within=" + int(data['since']), verify=False) 503 | else: 504 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/payment", verify=False) 505 | return process_response(responseData) 506 | 507 | # --------------------------------------------------------------------------------------------------------------------- 508 | # Function: list_hotspot_operators - List hotspot operators (using REST) [UNTESTED] 509 | # --------------------------------------------------------------------------------------------------------------------- 510 | # returns an array of hotspot operators 511 | # --------------------------------------------------------------------------------------------------------------------- 512 | def list_hotspot_operators(data): 513 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/rest/hotspotop", verify=False) 514 | return process_response(responseData) 515 | 516 | # --------------------------------------------------------------------------------------------------------------------- 517 | # Function: list_sites - List sites on this controller 518 | # --------------------------------------------------------------------------------------------------------------------- 519 | # returns a list sites hosted on this controller with some details 520 | # --------------------------------------------------------------------------------------------------------------------- 521 | def list_sites(data): 522 | responseData = s.get(data['controller_baseURL'] + "/api/self/sites", verify=False) 523 | return process_response(responseData) 524 | 525 | # --------------------------------------------------------------------------------------------------------------------- 526 | # Function: sysinfo - Show sysinfo 527 | # --------------------------------------------------------------------------------------------------------------------- 528 | # returns an array of known sysinfo data 529 | # --------------------------------------------------------------------------------------------------------------------- 530 | def sysinfo(data): 531 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/sysinfo", verify=False) 532 | return process_response(responseData) 533 | 534 | # --------------------------------------------------------------------------------------------------------------------- 535 | # Function: list_site_settings - List site settings 536 | # --------------------------------------------------------------------------------------------------------------------- 537 | # returns an array of site configuration settings 538 | # --------------------------------------------------------------------------------------------------------------------- 539 | def list_site_settings(data): 540 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/get/setting", verify=False) 541 | return process_response(responseData) 542 | 543 | # --------------------------------------------------------------------------------------------------------------------- 544 | # Function: list_admins_for_current_site - List admins for current site [404 ERROR] 545 | # --------------------------------------------------------------------------------------------------------------------- 546 | # returns an array containing administrator objects for selected site 547 | # --------------------------------------------------------------------------------------------------------------------- 548 | def list_admins_for_current_site(data): 549 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/cmd/sitemgr", params={"cmd": "get-admins"}, verify=False) 550 | return process_response(responseData) 551 | 552 | # --------------------------------------------------------------------------------------------------------------------- 553 | # Function: list_admins_for_all_sites - List admins across all sites on this controller 554 | # --------------------------------------------------------------------------------------------------------------------- 555 | # returns an array containing administrator objects for all sites 556 | # --------------------------------------------------------------------------------------------------------------------- 557 | def list_admins_for_all_sites(data): 558 | responseData = s.get(data['controller_baseURL'] + "/api/stat/admin", verify=False) 559 | return process_response(responseData) 560 | 561 | # --------------------------------------------------------------------------------------------------------------------- 562 | # Function: list_wlan_configuration - List wlan settings (using REST) 563 | # --------------------------------------------------------------------------------------------------------------------- 564 | # returns an array of wireless networks and their settings, or an array containing a single wireless network when using 565 | # the parameter 566 | # optional parameter = 24 char string; _id of the wlan to fetch the settings for 567 | # --------------------------------------------------------------------------------------------------------------------- 568 | def list_wlan_configuration(data): 569 | if data['wlan_id'] is not None: 570 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/rest/wlanconf/" + str(data['wlan_id'].strip()), verify=False) 571 | else: 572 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/rest/wlanconf/", verify=False) 573 | return process_response(responseData) 574 | 575 | # --------------------------------------------------------------------------------------------------------------------- 576 | # Function: list_current_channels - List current channels 577 | # --------------------------------------------------------------------------------------------------------------------- 578 | # returns an array of currently allowed channels 579 | # --------------------------------------------------------------------------------------------------------------------- 580 | def list_current_channels(data): 581 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/current-channel", verify=False) 582 | return process_response(responseData) 583 | 584 | # --------------------------------------------------------------------------------------------------------------------- 585 | # Function: list_voip_extensions - List VoIP Extensions [400 ERROR] 586 | # --------------------------------------------------------------------------------------------------------------------- 587 | # returns an array of VoIP extensions 588 | # --------------------------------------------------------------------------------------------------------------------- 589 | def list_voip_extensions(data): 590 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/list/extension", verify=False) 591 | return process_response(responseData) 592 | 593 | # --------------------------------------------------------------------------------------------------------------------- 594 | # Function: list_network_configuration - List network settings (using REST) 595 | # --------------------------------------------------------------------------------------------------------------------- 596 | # returns an array of (non-wireless) networks and their settings 597 | # optional parameter = string; network id to get specific network data for 598 | # --------------------------------------------------------------------------------------------------------------------- 599 | def list_network_configuration(data): 600 | if data['network_id'] is not None: 601 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/rest/networkconf/" + str(data['network_id'].strip()), verify=False) 602 | else: 603 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/rest/networkconf/", verify=False) 604 | return process_response(responseData) 605 | 606 | # --------------------------------------------------------------------------------------------------------------------- 607 | # Function: list_port_configuration - List port configurations [UNTESTED 608 | # --------------------------------------------------------------------------------------------------------------------- 609 | # returns an array of port configurations 610 | # --------------------------------------------------------------------------------------------------------------------- 611 | def list_port_configuration(data): 612 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/list/portconf", verify=False) 613 | return process_response(responseData) 614 | 615 | # --------------------------------------------------------------------------------------------------------------------- 616 | # Function: list_port_forwarding_rules - List port forwarding settings [UNTESTED] 617 | # --------------------------------------------------------------------------------------------------------------------- 618 | # returns an array of port forwarding settings 619 | # --------------------------------------------------------------------------------------------------------------------- 620 | def list_port_forwarding_rules(data): 621 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/list/portforward", verify=False) 622 | return process_response(responseData) 623 | 624 | # --------------------------------------------------------------------------------------------------------------------- 625 | # Function: list_firewall_groups - List firewall groups (using REST) [UNTESTED 626 | # --------------------------------------------------------------------------------------------------------------------- 627 | # returns an array containing the current firewall groups on success 628 | # --------------------------------------------------------------------------------------------------------------------- 629 | def list_firewall_groups(data): 630 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/rest/firewallgroup", verify=False) 631 | return process_response(responseData) 632 | 633 | # --------------------------------------------------------------------------------------------------------------------- 634 | # Function: dynamic_dns_configuration - List dynamic DNS settings [UNTESTED] 635 | # --------------------------------------------------------------------------------------------------------------------- 636 | # returns an array of dynamic DNS settings 637 | # --------------------------------------------------------------------------------------------------------------------- 638 | def dynamic_dns_configuration(data): 639 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/list/dynamicdns", verify=False) 640 | return process_response(responseData) 641 | 642 | # --------------------------------------------------------------------------------------------------------------------- 643 | # Function: list_country_codes - List country codes 644 | # --------------------------------------------------------------------------------------------------------------------- 645 | # returns an array of available country codes 646 | # 647 | # NOTES: 648 | # these codes following the ISO standard: 649 | # https://en.wikipedia.org/wiki/ISO_3166-1_numeric 650 | # --------------------------------------------------------------------------------------------------------------------- 651 | def list_country_codes(data): 652 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/ccode", verify=False) 653 | return process_response(responseData) 654 | 655 | # --------------------------------------------------------------------------------------------------------------------- 656 | # Function: list_auto_backups - List auto backups [ERROR 404] 657 | # --------------------------------------------------------------------------------------------------------------------- 658 | # return an array containing objects with backup details on success 659 | # --------------------------------------------------------------------------------------------------------------------- 660 | def list_auto_backups(data): 661 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/cmd/backup", params={"cmd": "list-backups"}, verify=False) 662 | return process_response(responseData) 663 | 664 | # --------------------------------------------------------------------------------------------------------------------- 665 | # Function: list_radius_profiles - List Radius profiles (using REST) [UNTESTED] 666 | # --------------------------------------------------------------------------------------------------------------------- 667 | # returns an array of objects containing all Radius profiles for the current site 668 | # 669 | # NOTES: 670 | # - this function/method is only supported on controller versions 5.5.19 and later 671 | # --------------------------------------------------------------------------------------------------------------------- 672 | def list_radius_profiles(data): 673 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/rest/radiusprofile", verify=False) 674 | return process_response(responseData) 675 | 676 | # --------------------------------------------------------------------------------------------------------------------- 677 | # Function: list_radius_accounts - List Radius user accounts (using REST) [UNTESTED] 678 | # --------------------------------------------------------------------------------------------------------------------- 679 | # returns an array of objects containing all Radius accounts for the current site 680 | # 681 | # NOTES: 682 | # - this function/method is only supported on controller versions 5.5.19 and later 683 | # --------------------------------------------------------------------------------------------------------------------- 684 | def list_radius_accounts(data): 685 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/rest/account", verify=False) 686 | return process_response(responseData) 687 | 688 | # --------------------------------------------------------------------------------------------------------------------- 689 | # Function: list_alarms - List alarms [UNTESTED] 690 | # --------------------------------------------------------------------------------------------------------------------- 691 | # returns an array of known alarms 692 | # --------------------------------------------------------------------------------------------------------------------- 693 | def list_alarms(data): 694 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/list/alarm", verify=False) 695 | return process_response(responseData) 696 | 697 | # --------------------------------------------------------------------------------------------------------------------- 698 | # Function: list_events - List events [UNTESTED] 699 | # --------------------------------------------------------------------------------------------------------------------- 700 | # returns an array of known events 701 | # optional parameter = hours to go back, default value is 720 hours 702 | # optional parameter = which event number to start with (useful for paging of results), default value is 0 703 | # optional parameter = number of events to return, default value is 3000 704 | # --------------------------------------------------------------------------------------------------------------------- 705 | def list_events(data): 706 | if data['since'] is not None: 707 | within = int(data['since']) 708 | else: 709 | start = 720 #In hours, Default: 24 710 | if data['start_num'] is not None: 711 | start = int(data['start_num']) 712 | else: 713 | within = 720 #In hours, Default: 24 714 | if data['limit_num'] is not None: 715 | limit = int(data['limit_num']) 716 | else: 717 | limit = 720 #In hours, Default: 24 718 | paramsToSend = {'_sort': "-time", "within": within, 'type': None, '_start': start, '_limit': limit} 719 | responseData = s.get(data['controller_baseURL'] + "/api/s/" + data['controller_site'] + "/stat/event", params=paramsToSend, verify=False) 720 | return process_response(responseData) 721 | 722 | def main(): 723 | 724 | fields = { 725 | "controller_username": {"required": True, "type": "str"}, 726 | "controller_password": {"required": True, "type": "str", "no_log": True}, 727 | "controller_baseURL": {"required": True, "type": "str"}, 728 | "controller_site": {"required": False, "type": "str", "default": "default"}, 729 | "query": { 730 | "default": "list_sites", 731 | "type": 'str', 732 | "choices": ['list_clients', 'list_online_clients', 'list_guests', 'list_users', 'list_user_groups', 'stat_all_users', 'stat_authorizations', 'stat_sessions', 'list_devices', 'list_wlan_groups', 'list_rouge_access_points', 'list_known_rogue_access_points', 'list_tags', 'five_minute_site_stats', 'hourly_site_stats', 'daily_site_stats', 'all_sites_stats', 'five_minute_access_point_stats', 'hourly_access_point_stats', 'daily_access_point_stats', 'five_minute_site_dashboard_metrics', 'hourly_site_dashboard_metrics', 'site_health_metrics', 'port_forwarding_stats', 'dpi_stats', 'stat_vouchers', 'stat_payments', 'list_hotspot_operators', 'list_sites', 'sysinfo', 'list_site_settings', 'list_admins_for_current_site', 'list_admins_for_all_sites', 'list_wlan_configuration', 'list_current_channels', 'list_voip_extensions', 'list_network_configuration', 'list_port_configuration', 'list_port_forwarding_rules', 'list_firewall_groups', 'dynamic_dns_configuration', 'list_country_codes', 'list_auto_backups', 'list_radius_profiles', 'list_radius_accounts', 'list_alarms', 'list_events'] 733 | }, 734 | "hourly_timeframe": {"required": False, "type": "int", "default": "8760"}, 735 | "since": {"required": False, "type": "int", "default": None}, 736 | "start_num": {"required": False, "type": "int", "default": None}, 737 | "limit_num": {"required": False, "type": "int", "default": None}, 738 | "start_epoch": {"required": False, "type": "int", "default": None}, 739 | "end_epoch": {"required": False, "type": "int", "default": None}, 740 | "created_time": {"required": False, "type": "int", "default": None}, 741 | "device_mac": {"required": False, "type": "str", "default": None}, 742 | "client_mac": {"required": False, "type": "str", "default": None}, 743 | "network_id": {"required": False, "type": "str", "default": None}, 744 | "wlan_id": {"required": False, "type": "str", "default": None}, 745 | } 746 | 747 | choice_map = { 748 | 'list_clients': list_online_clients, 749 | 'list_online_clients': list_online_clients, 750 | 'list_guests': list_guests, 751 | 'list_users': list_users, 752 | 'list_user_groups': list_user_groups, 753 | 'stat_all_users': stat_all_users, 754 | 'stat_authorizations': stat_authorizations, 755 | 'stat_sessions': stat_sessions, 756 | 'list_devices': list_devices, 757 | 'list_wlan_groups': list_wlan_groups, 758 | 'list_rouge_access_points': list_rouge_access_points, 759 | 'list_known_rogue_access_points': list_known_rogue_access_points, 760 | 'list_tags': list_tags, 761 | 'five_minute_site_stats': five_minute_site_stats, 762 | 'hourly_site_stats': hourly_site_stats, 763 | 'daily_site_stats': daily_site_stats, 764 | 'all_sites_stats': all_sites_stats, 765 | 'five_minute_access_point_stats': five_minute_access_point_stats, 766 | 'hourly_access_point_stats': hourly_access_point_stats, 767 | 'daily_access_point_stats': daily_access_point_stats, 768 | 'five_minute_site_dashboard_metrics': five_minute_site_dashboard_metrics, 769 | 'hourly_site_dashboard_metrics': hourly_site_dashboard_metrics, 770 | 'site_health_metrics': site_health_metrics, 771 | 'port_forwarding_stats': port_forwarding_stats, 772 | 'dpi_stats': dpi_stats, 773 | 'stat_vouchers': stat_vouchers, 774 | 'stat_payments': stat_payments, 775 | 'list_hotspot_operators': list_hotspot_operators, 776 | 'list_sites': list_sites, 777 | 'sysinfo': sysinfo, 778 | 'list_site_settings': list_site_settings, 779 | 'list_admins_for_current_site': list_admins_for_current_site, 780 | 'list_admins_for_all_sites': list_admins_for_all_sites, 781 | 'list_wlan_configuration': list_wlan_configuration, 782 | 'list_current_channels': list_current_channels, 783 | 'list_voip_extensions': list_voip_extensions, 784 | 'list_network_configuration': list_network_configuration, 785 | 'list_port_configuration': list_port_configuration, 786 | 'list_port_forwarding_rules': list_port_forwarding_rules, 787 | 'list_firewall_groups': list_firewall_groups, 788 | 'dynamic_dns_configuration': dynamic_dns_configuration, 789 | 'list_country_codes': list_country_codes, 790 | 'list_auto_backups': list_auto_backups, 791 | 'list_radius_profiles': list_radius_profiles, 792 | 'list_radius_accounts': list_radius_accounts, 793 | 'list_alarms': list_alarms, 794 | 'list_events': list_events 795 | } 796 | 797 | module = AnsibleModule(argument_spec=fields, supports_check_mode=False) 798 | 799 | fireLogin = unifi_login({'controller_baseURL': module.params['controller_baseURL'], "controller_username": module.params['controller_username'], "controller_password": module.params['controller_password']}) 800 | if fireLogin['status_code'] == 200: 801 | is_error, has_changed, result = choice_map.get(module.params['query'])(module.params) 802 | else: 803 | res = {"status": fireLogin['status_code'], "data": fireLogin['data']} 804 | is_error, has_changed, result = (True, False, res) 805 | 806 | if not is_error: 807 | module.exit_json(changed=has_changed, meta=result) 808 | else: 809 | module.fail_json(msg="Error", meta=result) 810 | 811 | 812 | if __name__ == '__main__': 813 | main() 814 | --------------------------------------------------------------------------------