centralcli 4.0.2__tar.gz → 4.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {centralcli-4.0.2 → centralcli-4.2.0}/PKG-INFO +1 -1
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/__init__.py +56 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/caas.py +0 -2
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/cache.py +85 -29
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/central.py +115 -94
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/cleaner.py +80 -14
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/cli.py +80 -51
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/cliadd.py +7 -27
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/clibatch.py +94 -37
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/clicommon.py +14 -2
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/clidel.py +1 -2
- centralcli-4.2.0/centralcli/cliexport.py +60 -0
- centralcli-4.2.0/centralcli/clioptions.py +11 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/clirename.py +1 -4
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/clishow.py +221 -219
- centralcli-4.2.0/centralcli/clishowaudit.py +203 -0
- centralcli-4.2.0/centralcli/clishowcloudauth.py +95 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/clitshoot.py +3 -1
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/cliupdate.py +1 -1
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/config.py +6 -4
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/constants.py +6 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/logger.py +1 -1
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/models.py +34 -1
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/response.py +36 -18
- {centralcli-4.0.2 → centralcli-4.2.0}/pyproject.toml +1 -1
- {centralcli-4.0.2 → centralcli-4.2.0}/LICENSE +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/README.md +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/boilerplate/README.md +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/boilerplate/allcalls.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/cliassign.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/clicaas.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/cliclone.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/clidelfirmware.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/clikick.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/clirefresh.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/cliset.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/clisetfirmware.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/clishowbranch.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/clishowfirmware.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/clishowospf.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/clishowoverlay.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/clishowtshoot.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/clishowwids.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/clitest.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/cliunassign.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/cliupgrade.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/exceptions.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/objects.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/render.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/setup.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/static/favicon.ico +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/strings.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/utils.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/vscodeargs.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/wh2snow.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/wh_proxy.py +0 -0
- {centralcli-4.0.2 → centralcli-4.2.0}/centralcli/wh_proxy_service.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: centralcli
|
|
3
|
-
Version: 4.0
|
|
3
|
+
Version: 4.2.0
|
|
4
4
|
Summary: A CLI for interacting with Aruba Central (Cloud Management Platform). Facilitates bulk imports, exports, reporting. A handy tool if you have devices managed by Aruba Central.
|
|
5
5
|
Home-page: https://github.com/Pack3tL0ss/central-api-cli
|
|
6
6
|
License: MIT
|
|
@@ -8,6 +8,7 @@ import os
|
|
|
8
8
|
import typer
|
|
9
9
|
|
|
10
10
|
from pathlib import Path
|
|
11
|
+
from typing import Iterable, List
|
|
11
12
|
import sys
|
|
12
13
|
|
|
13
14
|
import click
|
|
@@ -63,6 +64,61 @@ log = MyLogger(log_file, debug=config.debug, show=config.debug, verbose=config.d
|
|
|
63
64
|
log.debug(f"{__name__} __init__ calling script: {_calling_script}, base_dir: {config.base_dir}")
|
|
64
65
|
log.debugv(f"config attributes: {json.dumps({k: str(v) for k, v in config.__dict__.items()}, indent=4)}")
|
|
65
66
|
|
|
67
|
+
|
|
68
|
+
# HACK completion has gotten jacked up. typer calls click.utils._expand_args in Windows which was added in click 8, but completion is broken in click 8 so cencli is pinned to 7.1.2 until I can investigate further
|
|
69
|
+
# This hack manually adds the _expand_args functionality to click.utils which is pinned to 7.1.2 Otherwise an exception is thrown on Windows.
|
|
70
|
+
if os.name == "nt": # pragma: no cover
|
|
71
|
+
def _expand_args(
|
|
72
|
+
args: Iterable[str],
|
|
73
|
+
*,
|
|
74
|
+
user: bool = True,
|
|
75
|
+
env: bool = True,
|
|
76
|
+
glob_recursive: bool = True,
|
|
77
|
+
) -> List[str]:
|
|
78
|
+
"""Simulate Unix shell expansion with Python functions.
|
|
79
|
+
|
|
80
|
+
See :func:`glob.glob`, :func:`os.path.expanduser`, and
|
|
81
|
+
:func:`os.path.expandvars`.
|
|
82
|
+
|
|
83
|
+
This is intended for use on Windows, where the shell does not do any
|
|
84
|
+
expansion. It may not exactly match what a Unix shell would do.
|
|
85
|
+
|
|
86
|
+
:param args: List of command line arguments to expand.
|
|
87
|
+
:param user: Expand user home directory.
|
|
88
|
+
:param env: Expand environment variables.
|
|
89
|
+
:param glob_recursive: ``**`` matches directories recursively.
|
|
90
|
+
|
|
91
|
+
:meta private:
|
|
92
|
+
"""
|
|
93
|
+
from glob import glob
|
|
94
|
+
import re
|
|
95
|
+
|
|
96
|
+
out = []
|
|
97
|
+
|
|
98
|
+
for arg in args:
|
|
99
|
+
if user:
|
|
100
|
+
arg = os.path.expanduser(arg)
|
|
101
|
+
|
|
102
|
+
if env:
|
|
103
|
+
arg = os.path.expandvars(arg)
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
matches = glob(arg, recursive=glob_recursive)
|
|
107
|
+
except re.error:
|
|
108
|
+
matches = []
|
|
109
|
+
|
|
110
|
+
if not matches:
|
|
111
|
+
out.append(arg)
|
|
112
|
+
else:
|
|
113
|
+
out.extend(matches)
|
|
114
|
+
|
|
115
|
+
return out
|
|
116
|
+
|
|
117
|
+
import click
|
|
118
|
+
if not hasattr(click.utils, "_expand_args"):
|
|
119
|
+
click.utils._expand_args = _expand_args
|
|
120
|
+
|
|
121
|
+
|
|
66
122
|
from pycentral.base import ArubaCentralBase
|
|
67
123
|
from .utils import Utils
|
|
68
124
|
utils = Utils()
|
|
@@ -128,8 +128,6 @@ class BuildCLI:
|
|
|
128
128
|
common = self.data[dev]["_common"]
|
|
129
129
|
vlans = self.data[dev]["vlans"]
|
|
130
130
|
_pretty_name = typer.style(common.get('hostname', dev), fg="bright_green")
|
|
131
|
-
# print(f"Verifying {_pretty_name} is in Group {common['group']}...", end='')
|
|
132
|
-
# group_devs = self.session.get_gateways_by_group(self.data[dev]["_common"]["group"])
|
|
133
131
|
resp = self.central.request(self.central.get_devices, "gateways")
|
|
134
132
|
gateways = resp.output
|
|
135
133
|
self.dev_info = [_dev for _dev in gateways if _dev.get('mac', '').lower() == dev.lower()]
|
|
@@ -590,7 +590,7 @@ class Cache:
|
|
|
590
590
|
incomplete: str,
|
|
591
591
|
args: List[str] = [],
|
|
592
592
|
) -> Generator[Tuple[str, str], None, None] | None:
|
|
593
|
-
"""Device completion for returning matches that are
|
|
593
|
+
"""Device completion for returning matches that are switches (AOS-SW or CX)
|
|
594
594
|
|
|
595
595
|
Args:
|
|
596
596
|
incomplete (str): The last partial or full command before completion invoked.
|
|
@@ -617,6 +617,40 @@ class Cache:
|
|
|
617
617
|
for m in out:
|
|
618
618
|
yield m
|
|
619
619
|
|
|
620
|
+
# TODO this is not used until get_dev_identifier is refactored to support type vs generic_type
|
|
621
|
+
# TODO Also args is not being sent by typer, would need to use ctx.params.values()
|
|
622
|
+
# def dev_cx_completion(
|
|
623
|
+
# self,
|
|
624
|
+
# incomplete: str,
|
|
625
|
+
# args: List[str] = [],
|
|
626
|
+
# ) -> Generator[Tuple[str, str], None, None] | None:
|
|
627
|
+
# """Device completion for returning matches that are CX switches
|
|
628
|
+
|
|
629
|
+
# Args:
|
|
630
|
+
# incomplete (str): The last partial or full command before completion invoked.
|
|
631
|
+
# args (List[str], optional): The previous arguments/commands on CLI. Defaults to [].
|
|
632
|
+
|
|
633
|
+
# Yields:
|
|
634
|
+
# Generator[Tuple[str, str], None, None]: Name and help_text for the device, or
|
|
635
|
+
# Returns None if config is invalid
|
|
636
|
+
# """
|
|
637
|
+
# # Prevents exception during completion when config missing or invalid
|
|
638
|
+
# if not config.valid:
|
|
639
|
+
# err_console.print(":warning: Invalid config")
|
|
640
|
+
# return
|
|
641
|
+
|
|
642
|
+
# match = self.get_dev_identifier(incomplete, dev_type=["cx"], completion=True)
|
|
643
|
+
|
|
644
|
+
# out = []
|
|
645
|
+
# if match:
|
|
646
|
+
# out = [
|
|
647
|
+
# tuple([m.name, m.help_text]) for m in sorted(match, key=lambda i: i.name)
|
|
648
|
+
# if m.name not in args
|
|
649
|
+
# ]
|
|
650
|
+
|
|
651
|
+
# for m in out:
|
|
652
|
+
# yield m
|
|
653
|
+
|
|
620
654
|
def dev_kwarg_completion(
|
|
621
655
|
self,
|
|
622
656
|
ctx: typer.Context,
|
|
@@ -810,6 +844,7 @@ class Cache:
|
|
|
810
844
|
|
|
811
845
|
def dev_ap_gw_completion(
|
|
812
846
|
self,
|
|
847
|
+
ctx: typer.Context,
|
|
813
848
|
incomplete: str,
|
|
814
849
|
args: List[str] = None,
|
|
815
850
|
) -> Generator[Tuple[str, str], None, None] | None:
|
|
@@ -828,6 +863,10 @@ class Cache:
|
|
|
828
863
|
err_console.print(":warning: Invalid config")
|
|
829
864
|
return
|
|
830
865
|
|
|
866
|
+
# Prevents device completion for cencli show config cencli
|
|
867
|
+
if ctx.command_path == "cencli show config" and ctx.params.get("group_dev", "") == "cencli":
|
|
868
|
+
return
|
|
869
|
+
|
|
831
870
|
dev_types = ["ap", "gw"]
|
|
832
871
|
_match = self.get_dev_identifier(incomplete, dev_type=dev_types, completion=True)
|
|
833
872
|
|
|
@@ -912,6 +951,7 @@ class Cache:
|
|
|
912
951
|
# works in BASH and powershell
|
|
913
952
|
def group_dev_ap_gw_completion(
|
|
914
953
|
self,
|
|
954
|
+
ctx: typer.Context,
|
|
915
955
|
incomplete: str,
|
|
916
956
|
args: List[str] = None,
|
|
917
957
|
) -> Generator[Tuple[str, str], None, None] | None:
|
|
@@ -945,12 +985,16 @@ class Cache:
|
|
|
945
985
|
out += [tuple([m.name, m.help_text])]
|
|
946
986
|
# out += [tuple([m.name if " " not in m.name else f"'{m.name}'", m.help_text])] # FIXME completion for names with spaces is now broken, used to work. Change in completion behavior
|
|
947
987
|
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
988
|
+
if args:
|
|
989
|
+
if " ".join(args).lower() == "show config" and "cencli".lower().startswith(incomplete):
|
|
990
|
+
out += [("cencli", "show cencli configuration")]
|
|
991
|
+
if " ".join(args).lower() == "update config" and "cencli".lower().startswith(incomplete):
|
|
992
|
+
out += [("cencli", "update cencli configuration")]
|
|
993
|
+
elif ctx.command_path == "cencli show config" and ctx.params.get("group_dev") is None: # typer not sending args fix
|
|
994
|
+
if "cencli".lower().startswith(incomplete):
|
|
995
|
+
out += [("cencli", "update cencli configuration")]
|
|
996
|
+
|
|
997
|
+
|
|
954
998
|
|
|
955
999
|
# partial completion by serial: out appears to have list with expected tuple but does
|
|
956
1000
|
# not appear in zsh
|
|
@@ -1160,8 +1204,8 @@ class Cache:
|
|
|
1160
1204
|
out += [tuple([c.name, f'{c.ip}|{c.mac} type: {c.type} connected to: {c.connected_name} ~ {c.connected_port} (fail-safe match)'])]
|
|
1161
1205
|
|
|
1162
1206
|
|
|
1163
|
-
for c in out:
|
|
1164
|
-
yield c
|
|
1207
|
+
for c in out: # TODO completion behavior has changed. This works-around issue bash doesn't complete past 00: and zsh treats each octet as a dev name when : is used.
|
|
1208
|
+
yield c[0].replace(":", "-"), c[1]
|
|
1165
1209
|
|
|
1166
1210
|
def event_completion(
|
|
1167
1211
|
self,
|
|
@@ -1183,9 +1227,17 @@ class Cache:
|
|
|
1183
1227
|
err_console.print(":warning: Invalid config")
|
|
1184
1228
|
return
|
|
1185
1229
|
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1230
|
+
if incomplete == "":
|
|
1231
|
+
out = [("cencli", "Show cencli logs"), *[(x['id'], f"{x['id']}|{x['device'].split('Group:')[0].rstrip()}") for x in self.events]]
|
|
1232
|
+
for m in out:
|
|
1233
|
+
yield m[0], m[1]
|
|
1234
|
+
|
|
1235
|
+
elif "cencli".startswith(incomplete.lower()):
|
|
1236
|
+
yield "cencli", "Show cencli logs"
|
|
1237
|
+
else:
|
|
1238
|
+
for event in self.events:
|
|
1239
|
+
if event["id"].startswith(incomplete):
|
|
1240
|
+
yield event["id"], f"{event['id']}|{event['device'].split('Group:')[0].rstrip()}"
|
|
1189
1241
|
|
|
1190
1242
|
# TODO add support for zip code city state etc.
|
|
1191
1243
|
def site_completion(
|
|
@@ -1356,6 +1408,7 @@ class Cache:
|
|
|
1356
1408
|
for m in out:
|
|
1357
1409
|
yield m
|
|
1358
1410
|
|
|
1411
|
+
# TODO double check not used and remove
|
|
1359
1412
|
def completion(
|
|
1360
1413
|
self,
|
|
1361
1414
|
incomplete: str,
|
|
@@ -1428,6 +1481,21 @@ class Cache:
|
|
|
1428
1481
|
|
|
1429
1482
|
return len(ret) == len(data)
|
|
1430
1483
|
|
|
1484
|
+
async def get_swack_ids(self, resp: Response, update_data: List[dict]) -> List[dict]:
|
|
1485
|
+
# TODO these likely cause more delay on large accounts as it's pulling from raw, make more efficient or loop for both aps/switches in the same loop
|
|
1486
|
+
# add swarm_ids for APs to cache (AOS 8 IAP and AP individual Upgrade)
|
|
1487
|
+
if "aps" in resp.raw.get("aps", [{}])[0]:
|
|
1488
|
+
_swarm_ids = {d["serial"]: d["swarm_id"] or d["serial"] for d in resp.raw["aps"][0]["aps"]}
|
|
1489
|
+
update_data = [d if not d["type"] == "ap" or d["serial"] not in _swarm_ids else {**d, **{"swack_id": _swarm_ids[d["serial"]]}} for d in update_data]
|
|
1490
|
+
|
|
1491
|
+
# add stack_ids for switches to cache
|
|
1492
|
+
if "switches" in resp.raw.get("switches", [{}])[0]:
|
|
1493
|
+
_stack_ids = {d["serial"]: d["stack_id"] for d in resp.raw["switches"][0]["switches"]}
|
|
1494
|
+
update_data = [d if d["type"] not in ["cx", "sw"] or d["serial"] not in _stack_ids else {**d, **{"swack_id": _stack_ids[d["serial"]]}} for d in update_data]
|
|
1495
|
+
# FIXME need to update everything that uses AP swarm_id to use swack_id (swarm/stack id)
|
|
1496
|
+
|
|
1497
|
+
return update_data
|
|
1498
|
+
|
|
1431
1499
|
# FIXME handle no devices in Central yet exception 837 --> cleaner.py 498
|
|
1432
1500
|
# TODO if we are updating inventory we only need to get those devices types
|
|
1433
1501
|
async def update_dev_db(self, data: Union[str, List[str], List[dict]] = None, remove: bool = False) -> Union[List[int], Response]:
|
|
@@ -1487,20 +1555,8 @@ class Cache:
|
|
|
1487
1555
|
if resp.ok:
|
|
1488
1556
|
if resp.output:
|
|
1489
1557
|
_update_data = utils.listify(resp.output)
|
|
1490
|
-
_update_data = cleaner.get_devices(_update_data)
|
|
1491
|
-
|
|
1492
|
-
# TODO these likely cause more delay on large accounts as it's pulling from raw, make more efficient or loop for both aps/switches in the same loop
|
|
1493
|
-
# add swarm_ids for APs to cache (AOS 8 IAP and AP individual Upgrade)
|
|
1494
|
-
if "aps" in resp.raw.get("aps", [{}])[0]:
|
|
1495
|
-
_swarm_ids = {d["serial"]: d["swarm_id"] or d["serial"] for d in resp.raw["aps"][0]["aps"]}
|
|
1496
|
-
_update_data = [d if not d["type"] == "ap" or d["serial"] not in _swarm_ids else {**d, **{"swack_id": _swarm_ids[d["serial"]]}} for d in _update_data]
|
|
1497
|
-
|
|
1498
|
-
# add stack_ids for switches to cache
|
|
1499
|
-
if "switches" in resp.raw.get("switches", [{}])[0]:
|
|
1500
|
-
_stack_ids = {d["serial"]: d["stack_id"] for d in resp.raw["switches"][0]["switches"]}
|
|
1501
|
-
_update_data = [d if d["type"] not in ["cx", "sw"] or d["serial"] not in _stack_ids else {**d, **{"swack_id": _stack_ids[d["serial"]]}} for d in _update_data]
|
|
1502
|
-
# FIXME need to update everything that uses AP swarm_id to use swack_id (swarm/stack id)
|
|
1503
|
-
|
|
1558
|
+
_update_data = cleaner.get_devices(_update_data, cache=True)
|
|
1559
|
+
_update_data = await self.get_swack_ids(resp, _update_data)
|
|
1504
1560
|
|
|
1505
1561
|
self.DevDB.truncate()
|
|
1506
1562
|
update_res = self.DevDB.insert_multiple(_update_data)
|
|
@@ -2580,6 +2636,7 @@ class Cache:
|
|
|
2580
2636
|
else:
|
|
2581
2637
|
return None
|
|
2582
2638
|
|
|
2639
|
+
# cencli completion can be removed.. Moved to get_event_identifier
|
|
2583
2640
|
def get_log_identifier(self, query: str) -> str:
|
|
2584
2641
|
if "audit_trail" in query:
|
|
2585
2642
|
return query
|
|
@@ -2587,7 +2644,6 @@ class Cache:
|
|
|
2587
2644
|
return ["cencli", *[x["id"] for x in self.logs]]
|
|
2588
2645
|
|
|
2589
2646
|
try:
|
|
2590
|
-
|
|
2591
2647
|
if "cencli".startswith(query.lower()):
|
|
2592
2648
|
return ["cencli"]
|
|
2593
2649
|
|
|
@@ -2622,7 +2678,7 @@ class Cache:
|
|
|
2622
2678
|
return match[-1]["details"]
|
|
2623
2679
|
|
|
2624
2680
|
except ValueError as e:
|
|
2625
|
-
log.error(f"Exception in get_event_identifier {e.__class__.__name__}")
|
|
2681
|
+
log.error(f"Exception in get_event_identifier {e.__class__.__name__}", show=True)
|
|
2626
2682
|
log.exception(e)
|
|
2627
|
-
print(f"[bright_red]Exception[/] in get_event_identifier [dark_orange4]{e.__class__.__name__}[/]")
|
|
2683
|
+
# print(f"[bright_red]Exception[/] in get_event_identifier [dark_orange4]{e.__class__.__name__}[/]")
|
|
2628
2684
|
raise typer.Exit(1)
|
|
@@ -5,6 +5,8 @@ from __future__ import annotations
|
|
|
5
5
|
import base64
|
|
6
6
|
import json
|
|
7
7
|
import time
|
|
8
|
+
import tablib
|
|
9
|
+
import yaml
|
|
8
10
|
from asyncio.proactor_events import _ProactorBasePipeTransport
|
|
9
11
|
from datetime import datetime, timedelta
|
|
10
12
|
from enum import Enum
|
|
@@ -1042,7 +1044,7 @@ class CentralApi(Session):
|
|
|
1042
1044
|
|
|
1043
1045
|
# TODO cleanup the way raw is combined, see show wids all.
|
|
1044
1046
|
# TODO add full kwargs and type-hints
|
|
1045
|
-
async def get_all_devicesv2(self, **kwargs) -> Response:
|
|
1047
|
+
async def get_all_devicesv2(self, calculate_client_count: bool = True, **kwargs) -> Response:
|
|
1046
1048
|
"""Get all devices from Aruba Central
|
|
1047
1049
|
|
|
1048
1050
|
Returns:
|
|
@@ -1058,7 +1060,7 @@ class CentralApi(Session):
|
|
|
1058
1060
|
}
|
|
1059
1061
|
_output = {}
|
|
1060
1062
|
|
|
1061
|
-
reqs = [self.BatchRequest(self.get_devices, dev_type, **kwargs) for dev_type in dev_types]
|
|
1063
|
+
reqs = [self.BatchRequest(self.get_devices, dev_type, calculate_client_count=calculate_client_count, **kwargs) for dev_type in dev_types]
|
|
1062
1064
|
res = await self._batch_request(reqs)
|
|
1063
1065
|
_failure_idxs = [idx for idx, r in enumerate(res) if not r]
|
|
1064
1066
|
if _failure_idxs:
|
|
@@ -1075,7 +1077,7 @@ class CentralApi(Session):
|
|
|
1075
1077
|
|
|
1076
1078
|
resp = res[-1]
|
|
1077
1079
|
# TODO pass raw JSON use pydantic models in cleaner for non-verbose, verbose, --clients --stats outputs
|
|
1078
|
-
if
|
|
1080
|
+
if calculate_client_count:
|
|
1079
1081
|
_output = {k: [{"client_count": inner.get("client_count", "-"), **inner} for inner in utils.listify(v)] for k, v in zip(dev_types, [r.output for r in res]) if v}
|
|
1080
1082
|
else:
|
|
1081
1083
|
_output = {k: utils.listify(v) for k, v in zip(dev_types, [r.output for r in res]) if v}
|
|
@@ -1105,7 +1107,9 @@ class CentralApi(Session):
|
|
|
1105
1107
|
|
|
1106
1108
|
return resp
|
|
1107
1109
|
|
|
1108
|
-
# API-FLAW aos-sw always shows VLAN as 1 (allowed_vlans represents the PVID for access)
|
|
1110
|
+
# API-FLAW aos-sw always shows VLAN as 1 (allowed_vlans represents the PVID for an access port, include all VLANs on a trunk port, no indication of native)
|
|
1111
|
+
# API-FLAW aos-sw always shows mode as access, cx does as well, but has vlan_mode which is accurate
|
|
1112
|
+
# API-FLAW neither show interface name/description
|
|
1109
1113
|
async def get_switch_ports(self, serial: str, slot: str = None, aos_sw: bool = False) -> Response:
|
|
1110
1114
|
"""Switch Ports Details.
|
|
1111
1115
|
|
|
@@ -1113,7 +1117,7 @@ class CentralApi(Session):
|
|
|
1113
1117
|
serial (str): Serial number of switch to be queried
|
|
1114
1118
|
slot (str, optional): Slot name of the ports to be queried {For chassis type switches
|
|
1115
1119
|
only}.
|
|
1116
|
-
aos_sw (bool, optional): Device is ArubaOS-Switch. Defaults to False
|
|
1120
|
+
aos_sw (bool, optional): Device is ArubaOS-Switch. Defaults to False (indicating CX switch)
|
|
1117
1121
|
|
|
1118
1122
|
Returns:
|
|
1119
1123
|
Response: CentralAPI Response object
|
|
@@ -1322,7 +1326,7 @@ class CentralApi(Session):
|
|
|
1322
1326
|
show_resource_details: bool = False,
|
|
1323
1327
|
cluster_id: str = None,
|
|
1324
1328
|
model: str = None,
|
|
1325
|
-
calculate_client_count: bool =
|
|
1329
|
+
calculate_client_count: bool = True,
|
|
1326
1330
|
calculate_ssid_count: bool = False,
|
|
1327
1331
|
macaddr: str = None,
|
|
1328
1332
|
public_ip_address: str = None,
|
|
@@ -1558,20 +1562,9 @@ class CentralApi(Session):
|
|
|
1558
1562
|
|
|
1559
1563
|
return await self.get(url)
|
|
1560
1564
|
|
|
1561
|
-
# DELME not used should be safe to remove after verification
|
|
1562
|
-
async def get_gateways_by_group(self, group):
|
|
1563
|
-
url = "/monitoring/v1/mobility_controllers" if config.is_cop else "/monitoring/v1/gateways"
|
|
1564
|
-
params = {"group": group}
|
|
1565
|
-
return await self.get(url, params=params)
|
|
1566
|
-
|
|
1567
1565
|
async def get_group_for_dev_by_serial(self, serial_num):
|
|
1568
1566
|
return await self.get(f"/configuration/v1/devices/{serial_num}/group")
|
|
1569
1567
|
|
|
1570
|
-
# async def get_dhcp_client_info_by_gw(self, serial_num):
|
|
1571
|
-
# url = f"/monitoring/v1/mobility_controllers/{serial_num}/dhcp_clients"
|
|
1572
|
-
# params = {"reservation": False}
|
|
1573
|
-
# return await self.get(url, params=params)
|
|
1574
|
-
|
|
1575
1568
|
async def get_vlan_info_by_gw(self, serial_num):
|
|
1576
1569
|
return await self.get(f"/monitoring/v1/mobility_controllers/{serial_num}/vlan")
|
|
1577
1570
|
|
|
@@ -1584,11 +1577,6 @@ class CentralApi(Session):
|
|
|
1584
1577
|
url = f"/monitoring/v1/mobility_controllers/{serial_num}/uplinks/tunnel_stats"
|
|
1585
1578
|
return await self.get(url)
|
|
1586
1579
|
|
|
1587
|
-
async def get_uplink_state_by_group(self, group: str) -> Response:
|
|
1588
|
-
url = "/monitoring/v1/mobility_controllers/uplinks/distribution"
|
|
1589
|
-
params = {"group": group}
|
|
1590
|
-
return await self.get(url, params)
|
|
1591
|
-
|
|
1592
1580
|
# TODO move cleaner
|
|
1593
1581
|
async def get_all_sites(
|
|
1594
1582
|
self,
|
|
@@ -1626,12 +1614,15 @@ class CentralApi(Session):
|
|
|
1626
1614
|
event_description: str = None,
|
|
1627
1615
|
event_type: str = None,
|
|
1628
1616
|
fields: str = None,
|
|
1629
|
-
calculate_total: bool =
|
|
1617
|
+
calculate_total: bool = True,
|
|
1630
1618
|
offset: int = 0,
|
|
1631
1619
|
limit: int = 1000,
|
|
1632
1620
|
) -> Response:
|
|
1633
1621
|
"""List Events. v2
|
|
1634
1622
|
|
|
1623
|
+
Endpoint allows a max of 10000 records to be retrieved. The sum of offset + limit can not
|
|
1624
|
+
exceed 10,000
|
|
1625
|
+
|
|
1635
1626
|
Args:
|
|
1636
1627
|
group (str, optional): Filter by group name
|
|
1637
1628
|
swarm_id (str, optional): Filter by Swarm ID
|
|
@@ -1655,7 +1646,7 @@ class CentralApi(Session):
|
|
|
1655
1646
|
event_type (str, optional): Filter by event type
|
|
1656
1647
|
fields (str, optional): Comma separated list of fields to be returned. Valid fields are
|
|
1657
1648
|
number, level
|
|
1658
|
-
calculate_total (bool, optional): Whether to calculate total events
|
|
1649
|
+
calculate_total (bool, optional): Whether to calculate total events. Defaults to True.
|
|
1659
1650
|
offset (int, optional): Pagination offset Defaults to 0.
|
|
1660
1651
|
limit (int, optional): Pagination limit. Default is 100 and max is 1000 Defaults to 1000.
|
|
1661
1652
|
|
|
@@ -1668,8 +1659,8 @@ class CentralApi(Session):
|
|
|
1668
1659
|
"group": group,
|
|
1669
1660
|
"swarm_id": swarm_id,
|
|
1670
1661
|
"label": label,
|
|
1671
|
-
"
|
|
1672
|
-
"
|
|
1662
|
+
"from_timestamp": from_ts,
|
|
1663
|
+
"to_timestamp": to_ts,
|
|
1673
1664
|
'macaddr': macaddr,
|
|
1674
1665
|
'bssid': bssid,
|
|
1675
1666
|
'device_mac': device_mac,
|
|
@@ -1682,7 +1673,7 @@ class CentralApi(Session):
|
|
|
1682
1673
|
'event_description': event_description,
|
|
1683
1674
|
'event_type': event_type,
|
|
1684
1675
|
'fields': fields,
|
|
1685
|
-
'calculate_total': calculate_total,
|
|
1676
|
+
'calculate_total': str(calculate_total),
|
|
1686
1677
|
"offset": offset,
|
|
1687
1678
|
"limit": limit,
|
|
1688
1679
|
}
|
|
@@ -1902,29 +1893,6 @@ class CentralApi(Session):
|
|
|
1902
1893
|
else:
|
|
1903
1894
|
return Response(error="Missing Required Parameters")
|
|
1904
1895
|
|
|
1905
|
-
async def update_ssh_creds(self, device_serial: str, username: str, password: str) -> Response:
|
|
1906
|
-
"""Set Username, password required for establishing SSH connection to switch.
|
|
1907
|
-
|
|
1908
|
-
This method only applies to switches
|
|
1909
|
-
|
|
1910
|
-
Args:
|
|
1911
|
-
device_serial (str): Serial number of the switch.
|
|
1912
|
-
username (str): SSH username
|
|
1913
|
-
password (str): SSH password
|
|
1914
|
-
|
|
1915
|
-
Returns:
|
|
1916
|
-
Response: CentralAPI Response object
|
|
1917
|
-
Successful Response body (Response.output): "Success"
|
|
1918
|
-
"""
|
|
1919
|
-
url = f"/configuration/v1/devices/{device_serial}/ssh_connection"
|
|
1920
|
-
|
|
1921
|
-
json_data = {
|
|
1922
|
-
'username': username,
|
|
1923
|
-
'password': password
|
|
1924
|
-
}
|
|
1925
|
-
|
|
1926
|
-
return await self.post(url, json_data=json_data)
|
|
1927
|
-
|
|
1928
1896
|
async def get_task_status(
|
|
1929
1897
|
self,
|
|
1930
1898
|
task_id: str,
|
|
@@ -2021,24 +1989,6 @@ class CentralApi(Session):
|
|
|
2021
1989
|
|
|
2022
1990
|
return await self.get(url)
|
|
2023
1991
|
|
|
2024
|
-
async def get_controller_vlans(self, serial: str) -> Response:
|
|
2025
|
-
"""Get Mobility Controllers VLAN details.
|
|
2026
|
-
|
|
2027
|
-
Args:
|
|
2028
|
-
serial (str): Serial number of mobility controller to be queried
|
|
2029
|
-
|
|
2030
|
-
Returns:
|
|
2031
|
-
Response: CentralAPI Response object
|
|
2032
|
-
"""
|
|
2033
|
-
url = f"/monitoring/v1/mobility_controllers/{serial}/vlan"
|
|
2034
|
-
|
|
2035
|
-
return await self.get(url)
|
|
2036
|
-
|
|
2037
|
-
# async def get_ts_commands(self, dev_type: Literal['iap', 'mas', 'switch', 'controller']) -> Response:
|
|
2038
|
-
# url = "/troubleshooting/v1/commands"
|
|
2039
|
-
# params = {"device_type": dev_type}
|
|
2040
|
-
# return await self.get(url, params=params)
|
|
2041
|
-
|
|
2042
1992
|
async def get_ts_commands(
|
|
2043
1993
|
self,
|
|
2044
1994
|
device_type: Literal['iap', 'mas', 'switch', 'controller', 'cx'],
|
|
@@ -2196,6 +2146,56 @@ class CentralApi(Session):
|
|
|
2196
2146
|
|
|
2197
2147
|
return await self.get(url)
|
|
2198
2148
|
|
|
2149
|
+
async def get_switch_neighbors_v1(
|
|
2150
|
+
self,
|
|
2151
|
+
serial: str,
|
|
2152
|
+
) -> Response:
|
|
2153
|
+
"""Get lldp device neighbor info for CX switch.
|
|
2154
|
+
|
|
2155
|
+
If used on AOS-SW will only return neighbors that are CX switches
|
|
2156
|
+
|
|
2157
|
+
Args:
|
|
2158
|
+
serial (str): id of the switch
|
|
2159
|
+
|
|
2160
|
+
Returns:
|
|
2161
|
+
Response: CentralAPI Response object
|
|
2162
|
+
"""
|
|
2163
|
+
url = f"/monitoring/v1/cx_switches/{serial}/neighbors"
|
|
2164
|
+
|
|
2165
|
+
return await self.get(url)
|
|
2166
|
+
|
|
2167
|
+
async def get_cx_switch_stack_neighbors_v1(
|
|
2168
|
+
self,
|
|
2169
|
+
stack_id: str,
|
|
2170
|
+
) -> Response:
|
|
2171
|
+
"""Get lldp device neighbor info for CX switch stack.
|
|
2172
|
+
|
|
2173
|
+
Args:
|
|
2174
|
+
stack_id (str): Filter by stack_id
|
|
2175
|
+
|
|
2176
|
+
Returns:
|
|
2177
|
+
Response: CentralAPI Response object
|
|
2178
|
+
"""
|
|
2179
|
+
url = f"/monitoring/v1/cx_switch_stacks/{stack_id}/neighbors"
|
|
2180
|
+
|
|
2181
|
+
return await self.get(url)
|
|
2182
|
+
|
|
2183
|
+
async def get_switch_vsx_detail(
|
|
2184
|
+
self,
|
|
2185
|
+
serial: str,
|
|
2186
|
+
) -> Response:
|
|
2187
|
+
"""Get switch vsx info for CX switch.
|
|
2188
|
+
|
|
2189
|
+
Args:
|
|
2190
|
+
serial (str): Filter by switch serial
|
|
2191
|
+
|
|
2192
|
+
Returns:
|
|
2193
|
+
Response: CentralAPI Response object
|
|
2194
|
+
"""
|
|
2195
|
+
url = f"/monitoring/v1/cx_switches/{serial}/vsx"
|
|
2196
|
+
|
|
2197
|
+
return await self.get(url)
|
|
2198
|
+
|
|
2199
2199
|
async def do_multi_group_snapshot(
|
|
2200
2200
|
self,
|
|
2201
2201
|
backup_name: str,
|
|
@@ -3159,7 +3159,7 @@ class CentralApi(Session):
|
|
|
3159
3159
|
self,
|
|
3160
3160
|
swarm_id: str,
|
|
3161
3161
|
command: Literal[
|
|
3162
|
-
"
|
|
3162
|
+
"reboot",
|
|
3163
3163
|
"erase_configuration",
|
|
3164
3164
|
]
|
|
3165
3165
|
) -> Response:
|
|
@@ -3168,11 +3168,14 @@ class CentralApi(Session):
|
|
|
3168
3168
|
Args:
|
|
3169
3169
|
swarm_id (str): Swarm ID of device
|
|
3170
3170
|
command (str): Command mentioned in the description that is to be executed
|
|
3171
|
-
valid: '
|
|
3171
|
+
valid: 'reboot', 'erase_configuration'
|
|
3172
3172
|
|
|
3173
3173
|
Returns:
|
|
3174
3174
|
Response: CentralAPI Response object
|
|
3175
3175
|
"""
|
|
3176
|
+
if command == "reboot":
|
|
3177
|
+
command = "reboot_swarm"
|
|
3178
|
+
|
|
3176
3179
|
url = f"/device_management/v1/swarm/{swarm_id}/action/{command}"
|
|
3177
3180
|
|
|
3178
3181
|
return await self.post(url)
|
|
@@ -4146,29 +4149,6 @@ class CentralApi(Session):
|
|
|
4146
4149
|
|
|
4147
4150
|
return await self.delete(url)
|
|
4148
4151
|
|
|
4149
|
-
# TODO may remove this would show the device in the group, but didn't behave as expected (device was not in device list)
|
|
4150
|
-
async def assign_devices_to_group(self, group: str, serial_nums: Union[List[str], str]) -> Response:
|
|
4151
|
-
"""Assign devices to pre-provisioned group.
|
|
4152
|
-
|
|
4153
|
-
// Used indirectly by add device (when group option provided) //
|
|
4154
|
-
|
|
4155
|
-
Args:
|
|
4156
|
-
group (str): Group name
|
|
4157
|
-
serials (List[str]|str): Device serial number or list of device serial numbers.
|
|
4158
|
-
|
|
4159
|
-
Returns:
|
|
4160
|
-
Response: CentralAPI Response object
|
|
4161
|
-
"""
|
|
4162
|
-
url = "/device_management/v1/group/assign"
|
|
4163
|
-
serial_nums = utils.listify(serial_nums)
|
|
4164
|
-
|
|
4165
|
-
json_data = {
|
|
4166
|
-
'serials': serial_nums,
|
|
4167
|
-
'group': group
|
|
4168
|
-
}
|
|
4169
|
-
|
|
4170
|
-
return await self.post(url, json_data=json_data)
|
|
4171
|
-
|
|
4172
4152
|
async def preprovision_device_to_group(
|
|
4173
4153
|
self,
|
|
4174
4154
|
group_name: str,
|
|
@@ -5634,6 +5614,44 @@ class CentralApi(Session):
|
|
|
5634
5614
|
url = "/keymgmt/v1/health"
|
|
5635
5615
|
return await self.get(url)
|
|
5636
5616
|
|
|
5617
|
+
async def cloudauth_get_registered_macs(
|
|
5618
|
+
self,
|
|
5619
|
+
search: str = None,
|
|
5620
|
+
sort: str = None,
|
|
5621
|
+
filename: str = None,
|
|
5622
|
+
) -> Response:
|
|
5623
|
+
"""Fetch all Mac Registrations as a CSV file.
|
|
5624
|
+
|
|
5625
|
+
Args:
|
|
5626
|
+
search (str, optional): Filter the Mac Registrations by Mac Address and Client Name.
|
|
5627
|
+
Does a 'contains' match.
|
|
5628
|
+
sort (str, optional): Sort order Valid Values: +name, -name, +display_name,
|
|
5629
|
+
-display_name
|
|
5630
|
+
filename (str, optional): Suggest a file name for the downloading file via content
|
|
5631
|
+
disposition header.
|
|
5632
|
+
|
|
5633
|
+
Returns:
|
|
5634
|
+
Response: CentralAPI Response object
|
|
5635
|
+
"""
|
|
5636
|
+
url = "/cloudauth/api/v3/bulk/mac"
|
|
5637
|
+
|
|
5638
|
+
params = {
|
|
5639
|
+
'search': search,
|
|
5640
|
+
'sort': sort,
|
|
5641
|
+
'filename': filename
|
|
5642
|
+
}
|
|
5643
|
+
|
|
5644
|
+
resp = await self.get(url, params=params)
|
|
5645
|
+
|
|
5646
|
+
if resp:
|
|
5647
|
+
try:
|
|
5648
|
+
ds = tablib.Dataset().load(resp.output)
|
|
5649
|
+
resp.output = yaml.load(ds.json, Loader=yaml.SafeLoader)
|
|
5650
|
+
except Exception as e:
|
|
5651
|
+
log.error(f"cloudauth_get_registered_macs caught {e.__class__.__name__} trying to convert csv return from API to dict.", caption=True)
|
|
5652
|
+
|
|
5653
|
+
return resp
|
|
5654
|
+
|
|
5637
5655
|
async def get_user_accounts(
|
|
5638
5656
|
self,
|
|
5639
5657
|
app_name: str = None,
|
|
@@ -5672,7 +5690,10 @@ class CentralApi(Session):
|
|
|
5672
5690
|
'limit': limit
|
|
5673
5691
|
}
|
|
5674
5692
|
|
|
5675
|
-
|
|
5693
|
+
# TODO this needs a fair amount of massaging to turn into a command, it's nested dicts
|
|
5694
|
+
# example response in private vscode dir.
|
|
5695
|
+
resp = await self.get(url, params=params)
|
|
5696
|
+
return resp
|
|
5676
5697
|
|
|
5677
5698
|
if __name__ == "__main__":
|
|
5678
5699
|
pass
|