centralcli 2.3.0__tar.gz → 2.3.1__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-2.3.0 → centralcli-2.3.1}/PKG-INFO +1 -1
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/central.py +85 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/cliassign.py +21 -10
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/clibatch.py +149 -27
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/clishow.py +8 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/cliunassign.py +15 -2
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/config.py +5 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/constants.py +1 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/strings.py +2 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/pyproject.toml +1 -1
- {centralcli-2.3.0 → centralcli-2.3.1}/LICENSE +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/README.md +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/__init__.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/boilerplate/README.md +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/boilerplate/allcalls.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/caas.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/cache.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/cleaner.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/cli.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/cliadd.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/clicaas.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/cliclone.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/clicommon.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/clidel.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/clikick.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/clirefresh.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/clirename.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/clishowbranch.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/clishowfirmware.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/clishowospf.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/clishowoverlay.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/clishowtshoot.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/clishowwids.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/clitest.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/clitshoot.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/cliupdate.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/cliupgrade.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/exceptions.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/logger.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/models.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/objects.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/render.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/response.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/setup.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/static/favicon.ico +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/utils.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/vscodeargs.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/wh2snow.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/wh_proxy.py +0 -0
- {centralcli-2.3.0 → centralcli-2.3.1}/centralcli/wh_proxy_service.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: centralcli
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.1
|
|
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
|
|
@@ -4028,6 +4028,25 @@ class CentralApi(Session):
|
|
|
4028
4028
|
else:
|
|
4029
4029
|
return await self.post(url, json_data=json_data)
|
|
4030
4030
|
|
|
4031
|
+
async def cop_delete_device_from_inventory(
|
|
4032
|
+
self,
|
|
4033
|
+
devices: List[str] = None,
|
|
4034
|
+
) -> Response:
|
|
4035
|
+
"""Delete devices using Serial number. Only applies to CoP deployments.
|
|
4036
|
+
|
|
4037
|
+
Args:
|
|
4038
|
+
devices (list, optional): List of devices to be deleted from
|
|
4039
|
+
GreenLake inventory. Only applies to CoP
|
|
4040
|
+
|
|
4041
|
+
Returns:
|
|
4042
|
+
Response: CentralAPI Response object
|
|
4043
|
+
"""
|
|
4044
|
+
url = "/platform/device_inventory/v1/devices"
|
|
4045
|
+
|
|
4046
|
+
devices = [{"serial": serial} for serial in devices]
|
|
4047
|
+
|
|
4048
|
+
return await self.delete(url, json_data=devices)
|
|
4049
|
+
|
|
4031
4050
|
# TODO maybe helper method to delete_device that calls these
|
|
4032
4051
|
async def delete_gateway(
|
|
4033
4052
|
self,
|
|
@@ -5477,6 +5496,72 @@ class CentralApi(Session):
|
|
|
5477
5496
|
|
|
5478
5497
|
return await self.get(url)
|
|
5479
5498
|
|
|
5499
|
+
|
|
5500
|
+
async def get_auto_subscribe(
|
|
5501
|
+
self,
|
|
5502
|
+
) -> Response:
|
|
5503
|
+
"""Get the services which have auto subscribe enabled.
|
|
5504
|
+
|
|
5505
|
+
Returns:
|
|
5506
|
+
Response: CentralAPI Response object
|
|
5507
|
+
"""
|
|
5508
|
+
url = "/platform/licensing/v1/customer/settings/autolicense"
|
|
5509
|
+
|
|
5510
|
+
return await self.get(url)
|
|
5511
|
+
|
|
5512
|
+
async def enable_auto_subscribe(
|
|
5513
|
+
self,
|
|
5514
|
+
services: List[str] | str,
|
|
5515
|
+
) -> Response:
|
|
5516
|
+
"""Standalone Customer API: Assign licenses to all devices and enable auto subscribe for
|
|
5517
|
+
given services.
|
|
5518
|
+
|
|
5519
|
+
Args:
|
|
5520
|
+
services (List[str]): list of services e.g. ['pa', 'ucc', foundation_ap,
|
|
5521
|
+
advanced_switch_6200, foundation_70XX etc ...]. Check
|
|
5522
|
+
/platform/licensing/v1/services/config API response to know the list of supported
|
|
5523
|
+
services.
|
|
5524
|
+
|
|
5525
|
+
Returns:
|
|
5526
|
+
Response: CentralAPI Response object
|
|
5527
|
+
"""
|
|
5528
|
+
url = "/platform/licensing/v1/customer/settings/autolicense"
|
|
5529
|
+
|
|
5530
|
+
if isinstance(services, str):
|
|
5531
|
+
services = [services]
|
|
5532
|
+
|
|
5533
|
+
json_data = {
|
|
5534
|
+
'services': services
|
|
5535
|
+
}
|
|
5536
|
+
|
|
5537
|
+
return await self.post(url, json_data=json_data)
|
|
5538
|
+
|
|
5539
|
+
async def disable_auto_subscribe(
|
|
5540
|
+
self,
|
|
5541
|
+
services: List[str] | str,
|
|
5542
|
+
) -> Response:
|
|
5543
|
+
"""Standalone Customer API: Disable auto licensing for given services.
|
|
5544
|
+
|
|
5545
|
+
Args:
|
|
5546
|
+
services (List[str]): list of services e.g. ['pa', 'ucc', foundation_ap,
|
|
5547
|
+
advanced_switch_6200, foundation_70XX etc ...]. Check
|
|
5548
|
+
/platform/licensing/v1/services/config API response to know the list of supported
|
|
5549
|
+
services.
|
|
5550
|
+
|
|
5551
|
+
Returns:
|
|
5552
|
+
Response: CentralAPI Response object
|
|
5553
|
+
"""
|
|
5554
|
+
url = "/platform/licensing/v1/customer/settings/autolicense"
|
|
5555
|
+
|
|
5556
|
+
if isinstance(services, str):
|
|
5557
|
+
services = [services]
|
|
5558
|
+
|
|
5559
|
+
json_data = {
|
|
5560
|
+
'services': services
|
|
5561
|
+
}
|
|
5562
|
+
|
|
5563
|
+
return await self.delete(url, json_data=json_data)
|
|
5564
|
+
|
|
5480
5565
|
# // -- Not used by commands yet. undocumented kms api -- //
|
|
5481
5566
|
async def kms_get_synced_aps(self, mac: str) -> Response:
|
|
5482
5567
|
url = f"/keymgmt/v1/syncedaplist/{mac}"
|
|
@@ -24,10 +24,10 @@ except (ImportError, ModuleNotFoundError) as e:
|
|
|
24
24
|
app = typer.Typer()
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
@app.command(
|
|
27
|
+
@app.command()
|
|
28
28
|
def license(
|
|
29
29
|
license: cli.cache.LicenseTypes = typer.Argument(..., show_default=False),
|
|
30
|
-
serial_nums: List[str] = typer.Argument(..., show_default=False),
|
|
30
|
+
serial_nums: List[str] = typer.Argument(..., help="device serial numbers or 'auto' to enable auto-subscribe.", show_default=False),
|
|
31
31
|
yes: bool = typer.Option(False, "-Y", "-y", help="Bypass confirmation prompts - Assume Yes"),
|
|
32
32
|
debug: bool = typer.Option(False, "--debug", envvar="ARUBACLI_DEBUG", help="Enable Additional Debug Logging",),
|
|
33
33
|
default: bool = typer.Option(False, "-d", is_flag=True, help="Use default central account", show_default=False,),
|
|
@@ -36,7 +36,7 @@ def license(
|
|
|
36
36
|
help="The Aruba Central Account to use (must be defined in the config)",
|
|
37
37
|
autocompletion=cli.cache.account_completion),
|
|
38
38
|
) -> None:
|
|
39
|
-
"""Assign Licenses to devices by serial number.
|
|
39
|
+
"""Assign Licenses to devices by serial number(s) or enable auto-subscribe for the license type.
|
|
40
40
|
|
|
41
41
|
Device must already be added to Central. Use '[cyan]cencli show inventory[/]' to see devices that have been added.
|
|
42
42
|
Use '--license' option with '[cyan]cencli add device ...[/]' to add device and assign license in one command.
|
|
@@ -44,16 +44,27 @@ def license(
|
|
|
44
44
|
# devices = [cli.cache.get_dev_identifier(dev) for dev in devices]
|
|
45
45
|
|
|
46
46
|
# TODO add confirmation method builder to output class
|
|
47
|
-
|
|
48
|
-
if
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
do_auto = True if "auto" in [s.lower() for s in serial_nums] else False
|
|
48
|
+
if do_auto:
|
|
49
|
+
_msg = f"Enable Auto-assignment of [bright_green]{license}[/bright_green] to applicable devices."
|
|
50
|
+
if len(serial_nums) > 1:
|
|
51
|
+
print(f'[cyan]auto[/] keyword provided remaining entries will be [bright_red]ignored[/]')
|
|
51
52
|
else:
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
_msg = f"Assign [bright_green]{license}[/bright_green] to"
|
|
54
|
+
if len(serial_nums) > 1:
|
|
55
|
+
_dev_msg = '\n '.join([f'[cyan]{dev}[/]' for dev in serial_nums])
|
|
56
|
+
_msg = f"{_msg}:\n {_dev_msg}"
|
|
57
|
+
else:
|
|
58
|
+
dev = serial_nums[0]
|
|
59
|
+
_msg = f"{_msg} [cyan]{dev}[/]"
|
|
60
|
+
|
|
54
61
|
print(_msg)
|
|
55
62
|
if yes or typer.confirm("\nProceed?"):
|
|
56
|
-
|
|
63
|
+
if not do_auto:
|
|
64
|
+
resp = cli.central.request(cli.central.assign_licenses, serial_nums, services=license.name)
|
|
65
|
+
else:
|
|
66
|
+
resp = cli.central.request(cli.central.enable_auto_subscribe, services=license.name)
|
|
67
|
+
|
|
57
68
|
cli.display_results(resp, tablefmt="action")
|
|
58
69
|
# TODO cache update similar to batch unsubscribe
|
|
59
70
|
|
|
@@ -1011,6 +1011,11 @@ def batch_delete_devices(data: Union[list, dict], *, ui_only: bool = False, yes:
|
|
|
1011
1011
|
br(cli.central.unarchive_devices, (inv_del_serials,)),
|
|
1012
1012
|
]
|
|
1013
1013
|
|
|
1014
|
+
# cop only delete devices from GreenLake inventory
|
|
1015
|
+
cop_del_reqs = [] if not inv_del_serials or not config.is_cop else [
|
|
1016
|
+
br(cli.central.cop_delete_device_from_inventory, inv_del_serials)
|
|
1017
|
+
]
|
|
1018
|
+
|
|
1014
1019
|
# build reqs to remove devs from monit views. Down devs now, Up devs delayed to allow time to disc.
|
|
1015
1020
|
mon_del_reqs, delayed_mon_del_reqs = [], []
|
|
1016
1021
|
for dev_type, _devs in zip(["ap", "switch", "gateway"], [aps, switches, gws]):
|
|
@@ -1034,7 +1039,7 @@ def batch_delete_devices(data: Union[list, dict], *, ui_only: bool = False, yes:
|
|
|
1034
1039
|
print("")
|
|
1035
1040
|
|
|
1036
1041
|
# None of the provided devices were found in cache or inventory
|
|
1037
|
-
if not [*reqs, *mon_del_reqs, *delayed_mon_del_reqs]:
|
|
1042
|
+
if not [*reqs, *mon_del_reqs, *delayed_mon_del_reqs, *cop_del_reqs]:
|
|
1038
1043
|
print("Everything is as it should be, nothing to do.")
|
|
1039
1044
|
raise typer.Exit(0)
|
|
1040
1045
|
|
|
@@ -1043,7 +1048,7 @@ def batch_delete_devices(data: Union[list, dict], *, ui_only: bool = False, yes:
|
|
|
1043
1048
|
if len(cache_devs) > 1:
|
|
1044
1049
|
_msg += "\n".join([f" {d.summary_text}" for d in cache_devs[1:]])
|
|
1045
1050
|
|
|
1046
|
-
_total_reqs = len([*reqs, *mon_del_reqs, *delayed_mon_del_reqs]) if not ui_only else len(mon_del_reqs)
|
|
1051
|
+
_total_reqs = len([*reqs, *cop_del_reqs, *mon_del_reqs, *delayed_mon_del_reqs]) if not ui_only else len(mon_del_reqs)
|
|
1047
1052
|
|
|
1048
1053
|
if ui_only:
|
|
1049
1054
|
if delayed_mon_del_reqs:
|
|
@@ -1066,7 +1071,7 @@ def batch_delete_devices(data: Union[list, dict], *, ui_only: bool = False, yes:
|
|
|
1066
1071
|
console.print("[italic]Cache has not been updated, [cyan]cencli show all -v[/ cyan] will result in a full cache update.[/ italic]")
|
|
1067
1072
|
cli.display_results(batch_resp, exit_on_fail=True, caption="Re-run command to perform remaining actions.")
|
|
1068
1073
|
|
|
1069
|
-
if not delayed_mon_del_reqs:
|
|
1074
|
+
if not delayed_mon_del_reqs and not cop_del_reqs:
|
|
1070
1075
|
# if all reqs OK cache is updated by deleting specific items, otherwise it's a full cache refresh
|
|
1071
1076
|
all_ok = True if all(r.ok for r in batch_resp) else False
|
|
1072
1077
|
|
|
@@ -1096,33 +1101,47 @@ def batch_delete_devices(data: Union[list, dict], *, ui_only: bool = False, yes:
|
|
|
1096
1101
|
cli.display_results(batch_resp, tablefmt="action")
|
|
1097
1102
|
raise typer.Exit(0)
|
|
1098
1103
|
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
_del_resp = cli.central.batch_request(del_reqs_try, continue_on_fail=True)
|
|
1110
|
-
if _try == 3:
|
|
1111
|
-
if not all([r.ok for r in _del_resp]):
|
|
1112
|
-
print("\n[dark_orange]:warning:[/] Retries exceeded. Devices still remain Up in central and cannot be deleted. This command can be re-ran once they have disconnected.")
|
|
1113
|
-
del_resp += _del_resp
|
|
1114
|
-
else:
|
|
1115
|
-
del_resp += [r for r in _del_resp if r.ok or isinstance(r.output, dict) and r.output.get("error_code", "") != "0007"]
|
|
1104
|
+
elif delayed_mon_del_reqs:
|
|
1105
|
+
del_resp = []
|
|
1106
|
+
del_reqs_try = delayed_mon_del_reqs.copy()
|
|
1107
|
+
_delay = 10 if not switches else 30 # switches take longer to drop off
|
|
1108
|
+
for _try in range(4):
|
|
1109
|
+
_word = "more " if _try > 0 else ""
|
|
1110
|
+
_prefix = "" if _try == 0 else f"\[Attempt {_try + 1}] "
|
|
1111
|
+
_delay -= (5 * _try) # reduce delay by 5 secs for each request
|
|
1112
|
+
for _ in track(range(_delay), description=f"{_prefix}[green]Allowing {_word}time for devices to disconnect."):
|
|
1113
|
+
sleep(1)
|
|
1116
1114
|
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1115
|
+
_del_resp = cli.central.batch_request(del_reqs_try, continue_on_fail=True)
|
|
1116
|
+
if _try == 3:
|
|
1117
|
+
if not all([r.ok for r in _del_resp]):
|
|
1118
|
+
print("\n[dark_orange]:warning:[/] Retries exceeded. Devices still remain Up in central and cannot be deleted. This command can be re-ran once they have disconnected.")
|
|
1119
|
+
del_resp += _del_resp
|
|
1120
|
+
else:
|
|
1121
|
+
del_resp += [r for r in _del_resp if r.ok or isinstance(r.output, dict) and r.output.get("error_code", "") != "0007"]
|
|
1122
1122
|
|
|
1123
|
-
|
|
1123
|
+
del_reqs_try = [del_reqs_try[idx] for idx, r in enumerate(_del_resp) if not r.ok and isinstance(r.output, dict) and r.output.get("error_code", "") == "0007"]
|
|
1124
|
+
if del_reqs_try:
|
|
1125
|
+
print(f"{len(del_reqs_try)} out of {len(*mon_del_reqs, *delayed_mon_del_reqs)} device{'s are' if len(del_reqs_try) > 1 else ' is'} still [bright_green]Up[/] in Central")
|
|
1126
|
+
else:
|
|
1127
|
+
break
|
|
1128
|
+
|
|
1129
|
+
batch_resp += del_resp or _del_resp
|
|
1130
|
+
|
|
1131
|
+
# On COP delete devices from GreenLake inventory (only available on CoP)
|
|
1132
|
+
# TODO test against a cop system
|
|
1133
|
+
# TODO add to cencli delete device ...
|
|
1134
|
+
if cop_del_reqs:
|
|
1135
|
+
cop_del_resp = cli.central.batch_request(cop_del_reqs)
|
|
1136
|
+
if not all(r.ok for r in cop_del_resp):
|
|
1137
|
+
print("[bright_red]Errors occured during CoP GreenLake delete")
|
|
1138
|
+
cli.display_results(cop_del_resp, tablefmt="action")
|
|
1139
|
+
else:
|
|
1140
|
+
# display results (below) with results of previous calls
|
|
1141
|
+
batch_resp += cop_del_resp
|
|
1124
1142
|
|
|
1125
1143
|
# TODO need to update cache after ui-only delete
|
|
1144
|
+
# TODO need to improve logic throughout and update inventory cache
|
|
1126
1145
|
if batch_resp:
|
|
1127
1146
|
with console.status("Performing cache updates..."):
|
|
1128
1147
|
db_updates = []
|
|
@@ -1804,7 +1823,7 @@ def move(
|
|
|
1804
1823
|
elif not import_file:
|
|
1805
1824
|
_msg = [
|
|
1806
1825
|
"Usage: cencli batch move [OPTIONS] WHAT:[devices] IMPORT_FILE",
|
|
1807
|
-
"Try 'cencli batch
|
|
1826
|
+
"Try 'cencli batch move ?' for help.",
|
|
1808
1827
|
"",
|
|
1809
1828
|
"Error: One of 'IMPORT_FILE' or --example should be provided.",
|
|
1810
1829
|
]
|
|
@@ -1814,6 +1833,109 @@ def move(
|
|
|
1814
1833
|
resp = batch_move_devices(import_file, yes=yes, do_group=do_group, do_site=do_site, do_label=do_label)
|
|
1815
1834
|
cli.display_results(resp, tablefmt="action")
|
|
1816
1835
|
|
|
1836
|
+
|
|
1837
|
+
@app.command()
|
|
1838
|
+
def archive(
|
|
1839
|
+
import_file: Path = typer.Argument(None, exists=True, show_default=False,),
|
|
1840
|
+
show_example: bool = typer.Option(False, "--example", help="Show Example import file format.", show_default=False),
|
|
1841
|
+
yes: bool = typer.Option(False, "-Y", "-y", help="Bypass confirmation prompts - Assume Yes"),
|
|
1842
|
+
default: bool = typer.Option(
|
|
1843
|
+
False, "-d", is_flag=True, help="Use default central account", show_default=False,
|
|
1844
|
+
callback=cli.default_callback,
|
|
1845
|
+
),
|
|
1846
|
+
debug: bool = typer.Option(
|
|
1847
|
+
False, "--debug", envvar="ARUBACLI_DEBUG", help="Enable Additional Debug Logging",
|
|
1848
|
+
),
|
|
1849
|
+
account: str = typer.Option(
|
|
1850
|
+
"central_info",
|
|
1851
|
+
envvar="ARUBACLI_ACCOUNT",
|
|
1852
|
+
help="The Aruba Central Account to use (must be defined in the config)",
|
|
1853
|
+
),
|
|
1854
|
+
) -> None:
|
|
1855
|
+
"""Batch archive devices based on import data from file.
|
|
1856
|
+
|
|
1857
|
+
This will archive the devices in GreenLake
|
|
1858
|
+
"""
|
|
1859
|
+
if show_example:
|
|
1860
|
+
print(examples.archive)
|
|
1861
|
+
return
|
|
1862
|
+
|
|
1863
|
+
elif not import_file:
|
|
1864
|
+
_msg = [
|
|
1865
|
+
"Usage: cencli batch archive [OPTIONS] WHAT:[devices] IMPORT_FILE",
|
|
1866
|
+
"Try 'cencli batch archive ?' for help.",
|
|
1867
|
+
"",
|
|
1868
|
+
"Error: One of 'IMPORT_FILE' or --example should be provided.",
|
|
1869
|
+
]
|
|
1870
|
+
print("\n".join(_msg))
|
|
1871
|
+
raise typer.Exit(1)
|
|
1872
|
+
else:
|
|
1873
|
+
data = config.get_file_data(import_file, text_ok=True)
|
|
1874
|
+
if data and isinstance(data, list):
|
|
1875
|
+
if all([isinstance(x, dict) for x in data]):
|
|
1876
|
+
serials = [x.get("serial") or x.get("serial_num") for x in data]
|
|
1877
|
+
elif all(isinstance(x, str) for x in data):
|
|
1878
|
+
serials = data if not data[0].lower().startswith("serial") else data[1:]
|
|
1879
|
+
else:
|
|
1880
|
+
print(f"[bright_red]Error[/] Unexpected data structure returned from {import_file.name}")
|
|
1881
|
+
print("Use [cyan]cencli batch archive --example[/] to see expected format.")
|
|
1882
|
+
raise typer.Exit(1)
|
|
1883
|
+
|
|
1884
|
+
res = cli.central.request(cli.central.archive_devices, (serials,))
|
|
1885
|
+
cli.display_results(res)
|
|
1886
|
+
|
|
1887
|
+
|
|
1888
|
+
@app.command()
|
|
1889
|
+
def unarchive(
|
|
1890
|
+
import_file: Path = typer.Argument(None, exists=True, show_default=False,),
|
|
1891
|
+
show_example: bool = typer.Option(False, "--example", help="Show Example import file format.", show_default=False),
|
|
1892
|
+
yes: bool = typer.Option(False, "-Y", "-y", help="Bypass confirmation prompts - Assume Yes"),
|
|
1893
|
+
default: bool = typer.Option(
|
|
1894
|
+
False, "-d", is_flag=True, help="Use default central account", show_default=False,
|
|
1895
|
+
callback=cli.default_callback,
|
|
1896
|
+
),
|
|
1897
|
+
debug: bool = typer.Option(
|
|
1898
|
+
False, "--debug", envvar="ARUBACLI_DEBUG", help="Enable Additional Debug Logging",
|
|
1899
|
+
),
|
|
1900
|
+
account: str = typer.Option(
|
|
1901
|
+
"central_info",
|
|
1902
|
+
envvar="ARUBACLI_ACCOUNT",
|
|
1903
|
+
help="The Aruba Central Account to use (must be defined in the config)",
|
|
1904
|
+
),
|
|
1905
|
+
) -> None:
|
|
1906
|
+
"""Batch unarchive devices based on import data from file.
|
|
1907
|
+
|
|
1908
|
+
This will unarchive the devices (previously archived) in GreenLake
|
|
1909
|
+
"""
|
|
1910
|
+
if show_example:
|
|
1911
|
+
print(examples.unarchive)
|
|
1912
|
+
return
|
|
1913
|
+
|
|
1914
|
+
elif not import_file:
|
|
1915
|
+
_msg = [
|
|
1916
|
+
"Usage: cencli batch unarchive [OPTIONS] WHAT:[devices] IMPORT_FILE",
|
|
1917
|
+
"Try 'cencli batch unarchive ?' for help.",
|
|
1918
|
+
"",
|
|
1919
|
+
"Error: One of 'IMPORT_FILE' or --example should be provided.",
|
|
1920
|
+
]
|
|
1921
|
+
print("\n".join(_msg))
|
|
1922
|
+
raise typer.Exit(1)
|
|
1923
|
+
else:
|
|
1924
|
+
data = config.get_file_data(import_file, text_ok=True)
|
|
1925
|
+
if data and isinstance(data, list):
|
|
1926
|
+
if all([isinstance(x, dict) for x in data]):
|
|
1927
|
+
serials = [x.get("serial") or x.get("serial_num") for x in data]
|
|
1928
|
+
elif all(isinstance(x, str) for x in data):
|
|
1929
|
+
serials = data if not data[0].lower().startswith("serial") else data[1:]
|
|
1930
|
+
else:
|
|
1931
|
+
print(f"[bright_red]Error[/] Unexpected data structure returned from {import_file.name}")
|
|
1932
|
+
print("Use [cyan]cencli batch unarchive --example[/] to see expected format.")
|
|
1933
|
+
raise typer.Exit(1)
|
|
1934
|
+
|
|
1935
|
+
res = cli.central.request(cli.central.unarchive_devices, (serials,))
|
|
1936
|
+
cli.display_results(res)
|
|
1937
|
+
|
|
1938
|
+
|
|
1817
1939
|
@app.callback()
|
|
1818
1940
|
def callback():
|
|
1819
1941
|
"""
|
|
@@ -580,6 +580,13 @@ def subscription(
|
|
|
580
580
|
title = "Subscription Details"
|
|
581
581
|
_cleaner = cleaner.get_subscriptions
|
|
582
582
|
set_width_cols = {"name": 40}
|
|
583
|
+
elif what == "auto":
|
|
584
|
+
resp = cli.central.request(cli.central.get_auto_subscribe)
|
|
585
|
+
if resp and "services" in resp.output:
|
|
586
|
+
resp.output = resp.output["services"]
|
|
587
|
+
title = "Services with auto-subscribe enabled"
|
|
588
|
+
_cleaner = None
|
|
589
|
+
set_width_cols = None
|
|
583
590
|
elif what == "stats":
|
|
584
591
|
resp = cli.central.request(cli.central.get_subscription_stats)
|
|
585
592
|
title = "Subscription Stats"
|
|
@@ -603,6 +610,7 @@ def subscription(
|
|
|
603
610
|
set_width_cols=set_width_cols
|
|
604
611
|
)
|
|
605
612
|
|
|
613
|
+
|
|
606
614
|
# TODO need sort_by enum
|
|
607
615
|
@app.command(short_help="Show Swarms (IAP Clusters)")
|
|
608
616
|
def swarms(
|
|
@@ -27,10 +27,10 @@ iden = IdenMetaVars()
|
|
|
27
27
|
app = typer.Typer()
|
|
28
28
|
|
|
29
29
|
|
|
30
|
-
@app.command(
|
|
30
|
+
@app.command()
|
|
31
31
|
def license(
|
|
32
32
|
license: cli.cache.LicenseTypes = typer.Argument(..., help="License type to unassign from device(s).", show_default=False),
|
|
33
|
-
devices: List[str] = typer.Argument(..., metavar=iden.dev_many, autocompletion=cli.cache.dev_completion),
|
|
33
|
+
devices: List[str] = typer.Argument(..., help="device serial numbers or 'auto' to disable auto-subscribe.", metavar=iden.dev_many, autocompletion=cli.cache.dev_completion),
|
|
34
34
|
yes: bool = typer.Option(False, "-Y", "-y", help="Bypass confirmation prompts - Assume Yes"),
|
|
35
35
|
debug: bool = typer.Option(False, "--debug", envvar="ARUBACLI_DEBUG", help="Enable Additional Debug Logging",),
|
|
36
36
|
default: bool = typer.Option(False, "-d", is_flag=True, help="Use default central account", show_default=False,),
|
|
@@ -39,6 +39,19 @@ def license(
|
|
|
39
39
|
help="The Aruba Central Account to use (must be defined in the config)",
|
|
40
40
|
autocompletion=cli.cache.account_completion),
|
|
41
41
|
) -> None:
|
|
42
|
+
"""Unssign Licenses from devices by serial number(s) or disable auto-subscribe for the license type.
|
|
43
|
+
"""
|
|
44
|
+
do_auto = True if "auto" in [s.lower() for s in devices] else False
|
|
45
|
+
if do_auto:
|
|
46
|
+
_msg = f"Disable Auto-assignment of [bright_green]{license}[/bright_green] to applicable devices."
|
|
47
|
+
if len(devices) > 1:
|
|
48
|
+
print(f'[cyan]auto[/] keyword provided remaining entries will be [bright_red]ignored[/]')
|
|
49
|
+
print(_msg)
|
|
50
|
+
if yes or typer.confirm("\nProceed?"):
|
|
51
|
+
resp = cli.central.request(cli.central.disable_auto_subscribe, services=license.name)
|
|
52
|
+
cli.display_results(resp, tablefmt="action")
|
|
53
|
+
return
|
|
54
|
+
|
|
42
55
|
try:
|
|
43
56
|
devices: CentralObject = [cli.cache.get_dev_identifier(dev) for dev in devices]
|
|
44
57
|
except typer.Exit: # allows un-assignment of devices that never checked into Central
|
|
@@ -279,6 +279,7 @@ class Config:
|
|
|
279
279
|
self.debugv: bool = self.data.get("debugv", False)
|
|
280
280
|
self.sanitize: bool = self.data.get("sanitize", False)
|
|
281
281
|
self.account = self.get_account_from_args()
|
|
282
|
+
self.base_url = self.data.get(self.account, {}).get("base_url")
|
|
282
283
|
try:
|
|
283
284
|
self.webhook = WebHook(**self.data.get(self.account, {}).get("webhook", {}))
|
|
284
285
|
except ValidationError:
|
|
@@ -319,6 +320,10 @@ class Config:
|
|
|
319
320
|
else:
|
|
320
321
|
return self.data.get("central_info", {}).get(item, default)
|
|
321
322
|
|
|
323
|
+
@property
|
|
324
|
+
def is_cop(self):
|
|
325
|
+
return False if self.base_url.endswith("arubanetworks.com") else True
|
|
326
|
+
|
|
322
327
|
# not used but may be handy
|
|
323
328
|
@property
|
|
324
329
|
def tokens(self):
|
|
@@ -352,6 +352,8 @@ class ImportExamples:
|
|
|
352
352
|
self.deploy = clibatch_deploy
|
|
353
353
|
self.subscribe = clibatch_subscribe
|
|
354
354
|
self.unsubscribe = clibatch_unsubscribe
|
|
355
|
+
self.archive = clibatch_unsubscribe.replace("unsubscribe", "archive")
|
|
356
|
+
self.unarchive = clibatch_unsubscribe.replace("unsubscribe", "unarchive")
|
|
355
357
|
self.move_devices = clibatch_move_devices
|
|
356
358
|
|
|
357
359
|
def __getattr__(self, key: str):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "centralcli"
|
|
3
|
-
version = "2.3.
|
|
3
|
+
version = "2.3.1"
|
|
4
4
|
description = "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
|
license = "MIT"
|
|
6
6
|
authors = ["Wade Wells (Pack3tL0ss) <wade@consolepi.org>"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|