centralcli 1.7__tar.gz → 1.8__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.
Files changed (49) hide show
  1. {centralcli-1.7 → centralcli-1.8}/PKG-INFO +1 -1
  2. {centralcli-1.7 → centralcli-1.8}/centralcli/central.py +43 -18
  3. {centralcli-1.7 → centralcli-1.8}/centralcli/cleaner.py +17 -0
  4. {centralcli-1.7 → centralcli-1.8}/centralcli/cli.py +2 -5
  5. {centralcli-1.7 → centralcli-1.8}/centralcli/clicommon.py +1 -1
  6. {centralcli-1.7 → centralcli-1.8}/centralcli/clishow.py +46 -72
  7. centralcli-1.8/centralcli/clishowtshoot.py +174 -0
  8. {centralcli-1.7 → centralcli-1.8}/centralcli/clitshoot.py +101 -5
  9. {centralcli-1.7 → centralcli-1.8}/centralcli/cliupgrade.py +1 -0
  10. {centralcli-1.7 → centralcli-1.8}/centralcli/constants.py +34 -1
  11. {centralcli-1.7 → centralcli-1.8}/pyproject.toml +1 -1
  12. {centralcli-1.7 → centralcli-1.8}/README.md +0 -0
  13. {centralcli-1.7 → centralcli-1.8}/centralcli/__init__.py +0 -0
  14. {centralcli-1.7 → centralcli-1.8}/centralcli/boilerplate/README.md +0 -0
  15. {centralcli-1.7 → centralcli-1.8}/centralcli/boilerplate/allcalls.py +0 -0
  16. {centralcli-1.7 → centralcli-1.8}/centralcli/boilerplate/configuration.py +0 -0
  17. {centralcli-1.7 → centralcli-1.8}/centralcli/boilerplate/firmware.py +0 -0
  18. {centralcli-1.7 → centralcli-1.8}/centralcli/boilerplate/guest.py +0 -0
  19. {centralcli-1.7 → centralcli-1.8}/centralcli/boilerplate/wlan.py +0 -0
  20. {centralcli-1.7 → centralcli-1.8}/centralcli/caas.py +0 -0
  21. {centralcli-1.7 → centralcli-1.8}/centralcli/cache.py +0 -0
  22. {centralcli-1.7 → centralcli-1.8}/centralcli/cliadd.py +0 -0
  23. {centralcli-1.7 → centralcli-1.8}/centralcli/cliassign.py +0 -0
  24. {centralcli-1.7 → centralcli-1.8}/centralcli/clibatch.py +0 -0
  25. {centralcli-1.7 → centralcli-1.8}/centralcli/clicaas.py +0 -0
  26. {centralcli-1.7 → centralcli-1.8}/centralcli/cliclone.py +0 -0
  27. {centralcli-1.7 → centralcli-1.8}/centralcli/clidel.py +0 -0
  28. {centralcli-1.7 → centralcli-1.8}/centralcli/clirefresh.py +0 -0
  29. {centralcli-1.7 → centralcli-1.8}/centralcli/clirename.py +0 -0
  30. {centralcli-1.7 → centralcli-1.8}/centralcli/clishowbranch.py +0 -0
  31. {centralcli-1.7 → centralcli-1.8}/centralcli/clishowfirmware.py +0 -0
  32. {centralcli-1.7 → centralcli-1.8}/centralcli/clishowospf.py +0 -0
  33. {centralcli-1.7 → centralcli-1.8}/centralcli/clishowwids.py +0 -0
  34. {centralcli-1.7 → centralcli-1.8}/centralcli/clitest.py +0 -0
  35. {centralcli-1.7 → centralcli-1.8}/centralcli/cliunassign.py +0 -0
  36. {centralcli-1.7 → centralcli-1.8}/centralcli/cliupdate.py +0 -0
  37. {centralcli-1.7 → centralcli-1.8}/centralcli/config.py +0 -0
  38. {centralcli-1.7 → centralcli-1.8}/centralcli/exceptions.py +0 -0
  39. {centralcli-1.7 → centralcli-1.8}/centralcli/logger.py +0 -0
  40. {centralcli-1.7 → centralcli-1.8}/centralcli/models.py +0 -0
  41. {centralcli-1.7 → centralcli-1.8}/centralcli/render.py +0 -0
  42. {centralcli-1.7 → centralcli-1.8}/centralcli/response.py +0 -0
  43. {centralcli-1.7 → centralcli-1.8}/centralcli/setup.py +0 -0
  44. {centralcli-1.7 → centralcli-1.8}/centralcli/static/favicon.ico +0 -0
  45. {centralcli-1.7 → centralcli-1.8}/centralcli/strings.py +0 -0
  46. {centralcli-1.7 → centralcli-1.8}/centralcli/utils.py +0 -0
  47. {centralcli-1.7 → centralcli-1.8}/centralcli/vscodeargs.py +0 -0
  48. {centralcli-1.7 → centralcli-1.8}/centralcli/wh_proxy.py +0 -0
  49. {centralcli-1.7 → centralcli-1.8}/centralcli/wh_proxy_service.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: centralcli
3
- Version: 1.7
3
+ Version: 1.8
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
@@ -198,19 +198,28 @@ class CentralApi(Session):
198
198
  url = "/monitoring/v1/aps"
199
199
  return await self.get(url)
200
200
 
201
- async def get_swarms(self, group: str = None, status: str = None,
202
- public_ip_address: str = None, fields: str = None,
203
- calculate_total: bool = None,
204
- swarm_name: str = None, offset: int = 0, limit: int = 100) -> Response:
201
+ async def get_swarms(
202
+ self,
203
+ group: str = None,
204
+ status: str = None,
205
+ public_ip_address: str = None,
206
+ fields: str = None,
207
+ calculate_total: bool = None,
208
+ sort: str = None,
209
+ swarm_name: str = None,
210
+ offset: int = 0,
211
+ limit: int = 100,
212
+ ) -> Response:
205
213
  """List Swarms.
206
214
 
207
215
  Args:
208
216
  group (str, optional): Filter by group name
209
217
  status (str, optional): Filter by Swarm status
210
218
  public_ip_address (str, optional): Filter by public ip address
211
- fields (str, optional): Comma separated list of fields to be returned. Valid fields are
212
- status, ip_address, public_ip_address, firmware_version
219
+ fields (str, optional): Comma separated list of fields to be returned
220
+ Valid fields are: status, ip_address, public_ip_address, firmware_version
213
221
  calculate_total (bool, optional): Whether to calculate total Swarms
222
+ sort (str, optional): Sort parameter may be one of +swarm_id, -swarm_id
214
223
  swarm_name (str, optional): Filter by swarm name
215
224
  offset (int, optional): Pagination offset Defaults to 0.
216
225
  limit (int, optional): Pagination limit. Default is 100 and max is 1000 Defaults to 100.
@@ -226,11 +235,14 @@ class CentralApi(Session):
226
235
  'public_ip_address': public_ip_address,
227
236
  'fields': fields,
228
237
  'calculate_total': calculate_total,
238
+ 'sort': sort,
229
239
  'swarm_name': swarm_name,
230
240
  'offset': offset,
231
- 'limit': limit,
241
+ 'limit': limit
232
242
  }
233
243
 
244
+ params = utils.strip_none(params)
245
+
234
246
  return await self.get(url, params=params)
235
247
 
236
248
  async def get_swarm_details(self, swarm_id: str) -> Response:
@@ -246,15 +258,6 @@ class CentralApi(Session):
246
258
 
247
259
  return await self.get(url)
248
260
 
249
- # async def get_swarms_by_group(self, group: str) -> Response:
250
- # url = "/monitoring/v1/swarms"
251
- # params = {"group": group}
252
- # return await self.get(url, params=params)
253
-
254
- # async def get_swarm_details(self, swarm_id: str) -> Response:
255
- # url = f"/monitoring/v1/swarms/{swarm_id}"
256
- # return await self.get(url)
257
-
258
261
  async def get_clients(
259
262
  self,
260
263
  *args: Tuple[str],
@@ -2063,11 +2066,33 @@ class CentralApi(Session):
2063
2066
 
2064
2067
  return await self.get(url)
2065
2068
 
2066
- async def get_ts_commands(self, dev_type: Literal['iap', 'mas', 'switch', 'controller']) -> Response:
2069
+ # async def get_ts_commands(self, dev_type: Literal['iap', 'mas', 'switch', 'controller']) -> Response:
2070
+ # url = "/troubleshooting/v1/commands"
2071
+ # params = {"device_type": dev_type}
2072
+ # return await self.get(url, params=params)
2073
+
2074
+ async def get_ts_commands(
2075
+ self,
2076
+ device_type: Literal['iap', 'mas', 'switch', 'controller', 'cx'],
2077
+ ) -> Response:
2078
+ """List Troubleshooting Commands.
2079
+
2080
+ Args:
2081
+ device_type (str): Specify one of "IAP" for swarm, "MAS" for MAS switches, "SWITCH" for
2082
+ aruba switches,"CX" for CX switches, "CONTROLLER" for controllers respectively.
2083
+
2084
+ Returns:
2085
+ Response: CentralAPI Response object
2086
+ """
2067
2087
  url = "/troubleshooting/v1/commands"
2068
- params = {"device_type": dev_type}
2088
+
2089
+ params = {
2090
+ 'device_type': device_type
2091
+ }
2092
+
2069
2093
  return await self.get(url, params=params)
2070
2094
 
2095
+
2071
2096
  async def start_ts_session(
2072
2097
  self,
2073
2098
  serial: str,
@@ -195,6 +195,7 @@ _short_key = {
195
195
  "events_details": "details",
196
196
  "associated_device_count": "devices",
197
197
  "label_id": "id",
198
+ "command_id": "id",
198
199
  "label_name": "name",
199
200
  # "acknowledged": "ack",
200
201
  "acknowledged_by": "ack by",
@@ -1272,3 +1273,19 @@ def show_interfaces(data: Union[List[dict], dict],) -> Union[List[dict], dict]:
1272
1273
  }
1273
1274
 
1274
1275
  return strip_no_value(data)
1276
+
1277
+ def show_ts_commands(data: Union[List[dict], dict],) -> Union[List[dict], dict]:
1278
+ key_order = [
1279
+ "command_id",
1280
+ "category",
1281
+ "command",
1282
+ ]
1283
+ strip_keys = [
1284
+ "summary"
1285
+ ]
1286
+ # data = simple_kv_formatter(data)
1287
+ data = [
1288
+ dict(short_value(k, d.get(k),) for k in key_order if k not in strip_keys) for d in data if "arguments" not in d.keys()
1289
+ ]
1290
+
1291
+ return data
@@ -819,16 +819,13 @@ def all_commands_callback(ctx: typer.Context, update_cache: bool):
819
819
  pass
820
820
 
821
821
 
822
- # TODO See if possible to do all 3 version options as one typer.Option (--version -v -V)
823
822
  @app.callback()
824
823
  def callback(
825
824
  # ctx: typer.Context,``
826
- version: bool = typer.Option(False, "--version", is_flag=True, help="Show current cencli version, and latest available version."),
827
- _version: bool = typer.Option(False, "-V", is_flag=True, help="Show current cencli version, and latest available version.", case_sensitive=False, hidden=False),
828
- __version: bool = typer.Option(False, "-v", is_flag=True, hidden=True),
825
+ version: bool = typer.Option(False, "--version", "-V", "-v", case_sensitive=False, is_flag=True, help="Show current cencli version, and latest available version."),
829
826
  debug: bool = typer.Option(False, "--debug", is_flag=True, envvar="ARUBACLI_DEBUG", help="Enable Additional Debug Logging",),
830
827
  # callback=all_commands_callback),
831
- debugv: bool = typer.Option(False, "--debugv", is_flag=True, help="Enable Verbose Debug Logging",),
828
+ debugv: bool = typer.Option(False, "--debugv", is_flag=True, help="Enable Verbose Debug Logging", hidden=True,),
832
829
  # callback=all_commands_callback),
833
830
  default: bool = typer.Option(
834
831
  False,
@@ -176,10 +176,10 @@ class CLICommon:
176
176
 
177
177
  current = pkg_resources.get_distribution('centralcli').version
178
178
  resp = self.central.request(self.central.get, "https://pypi.org/pypi/centralcli/json")
179
- latest = max(resp.output["releases"])
180
179
  if not resp:
181
180
  print(current)
182
181
  else:
182
+ latest = max(resp.output["releases"])
183
183
  msg = "[bold bright_green]centralcli[/] "
184
184
  msg += 'A CLI app for interacting with Aruba Central Cloud Management Platform.\n'
185
185
  msg += f'Brought to you by [cyan]{resp.output["info"]["author"]}[/]\n\n'
@@ -20,12 +20,12 @@ except (ImportError, ModuleNotFoundError):
20
20
 
21
21
  # Detect if called from pypi installed package or via cloned github repo (development)
22
22
  try:
23
- from centralcli import Response, cleaner, clishowfirmware, clishowwids, clishowbranch, clishowospf, caas, cli, utils, config
23
+ from centralcli import Response, cleaner, clishowfirmware, clishowwids, clishowbranch, clishowospf, clishowtshoot, caas, cli, utils, config
24
24
  except (ImportError, ModuleNotFoundError) as e:
25
25
  pkg_dir = Path(__file__).absolute().parent
26
26
  if pkg_dir.name == "centralcli":
27
27
  sys.path.insert(0, str(pkg_dir.parent))
28
- from centralcli import Response, cleaner, clishowfirmware, clishowwids, clishowbranch, clishowospf, caas, cli, utils, config
28
+ from centralcli import Response, cleaner, clishowfirmware, clishowwids, clishowbranch, clishowospf, clishowtshoot, caas, cli, utils, config
29
29
  else:
30
30
  print(pkg_dir.parts)
31
31
  raise e
@@ -41,6 +41,7 @@ app.add_typer(clishowfirmware.app, name="firmware")
41
41
  app.add_typer(clishowwids.app, name="wids")
42
42
  app.add_typer(clishowbranch.app, name="branch")
43
43
  app.add_typer(clishowospf.app, name="ospf")
44
+ app.add_typer(clishowtshoot.app, name="tshoot")
44
45
 
45
46
  tty = utils.tty
46
47
  iden_meta = IdenMetaVars()
@@ -395,6 +396,49 @@ def aps(
395
396
  do_table=do_table)
396
397
 
397
398
 
399
+ @app.command(short_help="Show Swarms (IAP Clusters)")
400
+ def swarms(
401
+ group: str = typer.Option(None, metavar="<Device Group>", help="Filter by Group", autocompletion=cli.cache.group_completion, show_default=False,),
402
+ status: StatusOptions = typer.Option(None, metavar="[up|down]", help="Filter by swarm status", show_default=False,),
403
+ state: StatusOptions = typer.Option(None, hidden=True), # alias for status
404
+ up: bool = typer.Option(False, "--up", help="Filter by swarms that are Up", show_default=False),
405
+ down: bool = typer.Option(False, "--down", help="Filter by swarms that are Down", show_default=False),
406
+ pub_ip: str = typer.Option(None, metavar="<Public IP Address>", help="Filter by swarm Public IP", show_default=False,),
407
+ name: str = typer.Option(None, "--name", help="Filter by swarm/cluster name", show_default=False,),
408
+ # do_stats: bool = typer.Option(False, "--stats", is_flag=True, help="Show device statistics"),
409
+ sort_by: str = typer.Option(None, "--sort", help="Field to sort by", rich_help_panel="Formatting", show_default=False,),
410
+ reverse: bool = typer.Option(False, "-r", is_flag=True, help="Sort in descending order", rich_help_panel="Formatting"),
411
+ do_json: bool = typer.Option(False, "--json", is_flag=True, help="Output in JSON", rich_help_panel="Formatting"),
412
+ do_yaml: bool = typer.Option(False, "--yaml", is_flag=True, help="Output in YAML", rich_help_panel="Formatting"),
413
+ do_csv: bool = typer.Option(False, "--csv", is_flag=True, help="Output in CSV", rich_help_panel="Formatting"),
414
+ do_table: bool = typer.Option(False, "--table", is_flag=True, help="Output in table format", rich_help_panel="Formatting"),
415
+ outfile: Path = typer.Option(None, "--out", help="Output to file (and terminal)", writable=True, rich_help_panel="Common Options", show_default=False,),
416
+ pager: bool = typer.Option(False, "--pager", help="Enable Paged Output", rich_help_panel="Common Options"),
417
+ update_cache: bool = typer.Option(False, "-U", hidden=True, rich_help_panel="Common Options"), # Force Update of cli.cache for testing
418
+ default: bool = typer.Option(False, "-d", is_flag=True, help="Use default central account", show_default=False, rich_help_panel="Common Options"),
419
+ debug: bool = typer.Option(False, "--debug", envvar="ARUBACLI_DEBUG", help="Enable Additional Debug Logging",),
420
+ account: str = typer.Option(
421
+ "central_info",
422
+ envvar="ARUBACLI_ACCOUNT",
423
+ help="The Aruba Central Account to use (must be defined in the config)",
424
+ autocompletion=cli.cache.account_completion,
425
+ ),
426
+ ) -> None:
427
+ """
428
+ [cyan]Show Swarms (IAP Clusters)[/]
429
+ """
430
+ if down:
431
+ status = "Down"
432
+ elif up:
433
+ status = "Up"
434
+ else:
435
+ status = status or state
436
+
437
+ resp = cli.central.request(cli.central.get_swarms, group=group, status=status, public_ip_address=pub_ip, swarm_name=name)
438
+ tablefmt = cli.get_format(do_json=do_json, do_yaml=do_yaml, do_csv=do_csv, do_table=do_table, default="yaml")
439
+ cli.display_results(resp, tablefmt=tablefmt, pager=pager, outfile=outfile, sort_by=sort_by, reverse=reverse, cleaner=cleaner.show_interfaces)
440
+
441
+
398
442
  @app.command(short_help="Show switches/details")
399
443
  def switches(
400
444
  args: List[str] = typer.Argument(None, metavar=iden_meta.dev, autocompletion=cli.cache.dev_completion),
@@ -1654,76 +1698,6 @@ def roaming(
1654
1698
  cli.display_results(resp, title=f"Roaming history for {mac.cols}", tablefmt="rich", cleaner=cleaner.get_client_roaming_history)
1655
1699
 
1656
1700
 
1657
- @app.command(short_help="Show Troubleshooting output")
1658
- def tshoot(
1659
- device: str = typer.Argument(
1660
- ...,
1661
- metavar=iden_meta.dev,
1662
- help="Aruba Central Device",
1663
- autocompletion=cli.cache.dev_completion,
1664
- ),
1665
- session_id: str = typer.Argument(
1666
- None,
1667
- help="The troubleshooting session id.",
1668
- ),
1669
- verbose2: bool = typer.Option(
1670
- False,
1671
- "-vv",
1672
- help="Show raw response (no formatting but still honors --yaml, --csv ... if provided)",
1673
- show_default=False,
1674
- ),
1675
- pager: bool = typer.Option(False, "--pager", help="Enable Paged Output",),
1676
- default: bool = typer.Option(
1677
- False, "-d",
1678
- is_flag=True,
1679
- help="Use default central account",
1680
- show_default=False,
1681
- ),
1682
- debug: bool = typer.Option(
1683
- False,
1684
- "--debug",
1685
- envvar="ARUBACLI_DEBUG",
1686
- help="Enable Additional Debug Logging",
1687
- show_default=False,
1688
- ),
1689
- account: str = typer.Option(
1690
- "central_info",
1691
- envvar="ARUBACLI_ACCOUNT",
1692
- help="The Aruba Central Account to use (must be defined in the config)",
1693
- autocompletion=cli.cache.account_completion,
1694
- ),
1695
- ) -> None:
1696
- """Show Troubleshooting results from an existing session.
1697
-
1698
- Use cencli tshoot ... to start a troubleshooting session.
1699
-
1700
- """
1701
- central = cli.central
1702
- con = Console(emoji=False)
1703
- dev = cli.cache.get_dev_identifier(device)
1704
-
1705
- # Fetch session ID if not provided
1706
- if not session_id:
1707
- resp = central.request(central.get_ts_session_id, dev.serial)
1708
- if resp.ok and "session_id" in resp.output:
1709
- session_id = resp.output["session_id"]
1710
- else:
1711
- print(f"No session id provided, unable to find active session id for {dev.name}")
1712
- cli.display_results(resp)
1713
- raise typer.Exit(1)
1714
-
1715
- title = f"Troubleshooting output for {dev.name} session {session_id}"
1716
- resp = central.request(central.get_ts_output, dev.serial, session_id=session_id)
1717
- if not resp or resp.output.get("status", "") != "COMPLETED":
1718
- cli.display_results(resp, title=title, tablefmt="rich",)
1719
- elif verbose2:
1720
- cli.display_results(resp)
1721
- else:
1722
- con.print(resp)
1723
- con.print(f"\n {resp.rl}")
1724
-
1725
-
1726
-
1727
1701
  def show_logs_cencli_callback(ctx: typer.Context, cencli: bool):
1728
1702
  if ctx.resilient_parsing: # tab completion, return without validating
1729
1703
  return cencli
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ import typer
5
+ import time
6
+ import pendulum
7
+ import asyncio
8
+ import sys
9
+ from typing import List, Union
10
+ from pathlib import Path
11
+ from rich import print
12
+ from rich.console import Console
13
+
14
+ try:
15
+ import psutil
16
+ hook_enabled = True
17
+ except (ImportError, ModuleNotFoundError):
18
+ hook_enabled = False
19
+
20
+
21
+ # Detect if called from pypi installed package or via cloned github repo (development)
22
+ try:
23
+ from centralcli import cleaner, cli, utils
24
+ except (ImportError, ModuleNotFoundError) as e:
25
+ pkg_dir = Path(__file__).absolute().parent
26
+ if pkg_dir.name == "centralcli":
27
+ sys.path.insert(0, str(pkg_dir.parent))
28
+ from centralcli import cleaner, cli, utils
29
+ else:
30
+ print(pkg_dir.parts)
31
+ raise e
32
+
33
+ from centralcli.constants import (
34
+ IdenMetaVars, DevTypes, SortTsCmdOptions, TSDevTypes, lib_to_api # noqa
35
+ )
36
+
37
+ app = typer.Typer()
38
+
39
+ tty = utils.tty
40
+ iden_meta = IdenMetaVars()
41
+
42
+
43
+ @app.command(short_help="Show Troubleshooting output")
44
+ def results(
45
+ device: str = typer.Argument(
46
+ ...,
47
+ metavar=iden_meta.dev,
48
+ help="Aruba Central Device",
49
+ autocompletion=cli.cache.dev_completion,
50
+ ),
51
+ session_id: str = typer.Argument(
52
+ None,
53
+ help="The troubleshooting session id.",
54
+ ),
55
+ verbose2: bool = typer.Option(
56
+ False,
57
+ "-vv",
58
+ help="Show raw response (no formatting but still honors --yaml, --csv ... if provided)",
59
+ show_default=False,
60
+ ),
61
+ pager: bool = typer.Option(False, "--pager", help="Enable Paged Output",),
62
+ default: bool = typer.Option(
63
+ False, "-d",
64
+ is_flag=True,
65
+ help="Use default central account",
66
+ show_default=False,
67
+ ),
68
+ debug: bool = typer.Option(
69
+ False,
70
+ "--debug",
71
+ envvar="ARUBACLI_DEBUG",
72
+ help="Enable Additional Debug Logging",
73
+ show_default=False,
74
+ ),
75
+ account: str = typer.Option(
76
+ "central_info",
77
+ envvar="ARUBACLI_ACCOUNT",
78
+ help="The Aruba Central Account to use (must be defined in the config)",
79
+ autocompletion=cli.cache.account_completion,
80
+ ),
81
+ ) -> None:
82
+ """
83
+ [cyan]Show Troubleshooting results from an existing session.[/]
84
+
85
+ Use [cyan]cencli tshoot...[/] to start a troubleshooting session.
86
+
87
+ """
88
+ central = cli.central
89
+ con = Console(emoji=False)
90
+ dev = cli.cache.get_dev_identifier(device)
91
+
92
+ # Fetch session ID if not provided
93
+ if not session_id:
94
+ resp = central.request(central.get_ts_session_id, dev.serial)
95
+ if resp.ok and "session_id" in resp.output:
96
+ session_id = resp.output["session_id"]
97
+ else:
98
+ print(f"No session id provided, unable to find active session id for {dev.name}")
99
+ cli.display_results(resp)
100
+ raise typer.Exit(1)
101
+
102
+ title = f"Troubleshooting output for {dev.name} session {session_id}"
103
+ resp = central.request(central.get_ts_output, dev.serial, session_id=session_id)
104
+ if not resp or resp.output.get("status", "") != "COMPLETED":
105
+ cli.display_results(resp, title=title, tablefmt="rich",)
106
+ elif verbose2:
107
+ cli.display_results(resp)
108
+ else:
109
+ con.print(resp)
110
+ con.print(f"\n {resp.rl}")
111
+
112
+
113
+ @app.command(short_help="Show available troubleshooting commands")
114
+ def commands(
115
+ device_type: TSDevTypes = typer.Argument(..., metavar=iden_meta.dev_types_w_mas, show_default=False,),
116
+ sort_by: SortTsCmdOptions = typer.Option("id", "--sort",),
117
+ reverse: bool = typer.Option(False, "-r", help="Reverse output order", show_default=False,),
118
+ verbose2: bool = typer.Option(
119
+ False,
120
+ "-vv",
121
+ help="Show raw response (no formatting but still honors --yaml, --csv ... if provided)",
122
+ show_default=False,
123
+ ),
124
+ pager: bool = typer.Option(False, "--pager", help="Enable Paged Output",),
125
+ default: bool = typer.Option(
126
+ False, "-d",
127
+ is_flag=True,
128
+ help="Use default central account",
129
+ show_default=False,
130
+ ),
131
+ debug: bool = typer.Option(
132
+ False,
133
+ "--debug",
134
+ envvar="ARUBACLI_DEBUG",
135
+ help="Enable Additional Debug Logging",
136
+ show_default=False,
137
+ ),
138
+ account: str = typer.Option(
139
+ "central_info",
140
+ envvar="ARUBACLI_ACCOUNT",
141
+ help="The Aruba Central Account to use (must be defined in the config)",
142
+ autocompletion=cli.cache.account_completion,
143
+ ),
144
+ ) -> None:
145
+ """
146
+ [cyan]Show available troubleshooting commands for a given device type.[/]
147
+
148
+ Use [cyan]cencli tshoot...[/] to start a troubleshooting session.
149
+
150
+ """
151
+ if sort_by == "id":
152
+ sort_by = None
153
+ central = cli.central
154
+ con = Console(emoji=False)
155
+ dev_type = lib_to_api("tshoot", device_type)
156
+
157
+ resp = central.request(central.get_ts_commands, device_type=dev_type,)
158
+ cli.display_results(resp, tablefmt="rich", sort_by=sort_by, reverse=reverse, cleaner=cleaner.show_ts_commands)
159
+ # con.print(f"\n {resp.rl}")
160
+
161
+
162
+
163
+
164
+
165
+ @app.callback()
166
+ def callback():
167
+ """
168
+ Show troubleshooting session details or available commands
169
+ """
170
+ pass
171
+
172
+
173
+ if __name__ == "__main__":
174
+ app()
@@ -4,25 +4,34 @@
4
4
  import sys
5
5
  from pathlib import Path
6
6
  from time import sleep
7
+ from typing import List
7
8
 
8
9
  import typer
9
10
  from rich import print
10
11
  from rich.console import Console
11
12
  from rich.progress import track
12
13
 
14
+ try:
15
+ from fuzzywuzzy import process # type: ignore noqa
16
+ FUZZ = True
17
+ except Exception:
18
+ FUZZ = False
19
+ pass
20
+
13
21
  # Detect if called from pypi installed package or via cloned github repo (development)
14
22
  try:
15
- from centralcli import cli, utils
23
+ from centralcli import cli, utils, cleaner, render
16
24
  except (ImportError, ModuleNotFoundError) as e:
17
25
  pkg_dir = Path(__file__).absolute().parent
18
26
  if pkg_dir.name == "centralcli":
19
27
  sys.path.insert(0, str(pkg_dir.parent))
20
- from centralcli import cli, utils
28
+ from centralcli import cli, utils, cleaner, render
21
29
  else:
22
30
  print(pkg_dir.parts)
23
31
  raise e
24
32
 
25
33
  from centralcli.constants import IdenMetaVars, lib_to_api
34
+ from centralcli.cache import CentralObject
26
35
 
27
36
  app = typer.Typer()
28
37
 
@@ -30,11 +39,10 @@ tty = utils.tty
30
39
  iden_meta = IdenMetaVars()
31
40
 
32
41
 
33
- # TODO AP only completion
34
42
  @app.command(short_help="Show AP Overlay details")
35
43
  def ap_overlay(
36
- device: str = typer.Argument(None, metavar=iden_meta.dev, autocompletion=cli.cache.dev_ap_completion),
37
- outfile: Path = typer.Option(None, "--out", help="Output to file (and terminal)", writable=True),
44
+ device: str = typer.Argument(..., metavar=iden_meta.dev, autocompletion=cli.cache.dev_ap_completion, show_default=False,),
45
+ outfile: Path = typer.Option(None, "--out", help="Output to file (and terminal)", writable=True, show_default=False,),
38
46
  pager: bool = typer.Option(False, "--pager", help="Enable Paged Output"),
39
47
  default: bool = typer.Option(False, "-d", is_flag=True, help="Use default central account", show_default=False,),
40
48
  debug: bool = typer.Option(False, "--debug", envvar="ARUBACLI_DEBUG", help="Enable Additional Debug Logging",),
@@ -43,6 +51,13 @@ def ap_overlay(
43
51
  help="The Aruba Central Account to use (must be defined in the config)",
44
52
  autocompletion=cli.cache.account_completion),
45
53
  ):
54
+ """
55
+ [cyan]Returns the output of the following commands useful in troubleshooting overlay AP / gateway tunnels.[/]
56
+
57
+ [cyan]-[/] show ata endpoint
58
+ [cyan]-[/] show show overlay tunnel config
59
+ [cyan]-[/] show overlay ssid-cluster status
60
+ """
46
61
  console = Console(emoji=False)
47
62
  dev = cli.cache.get_dev_identifier(device, dev_type=("ap"))
48
63
  commands = [201, 203, 218]
@@ -144,6 +159,87 @@ def ping(
144
159
  break
145
160
 
146
161
 
162
+ def ts_send_command(device: CentralObject, cmd: str, outfile: Path, pager: bool,) -> None:
163
+ """Helper command to send troubleshooting output (user provides command) and print results
164
+
165
+ Args:
166
+ device (CentralObject): Device Object
167
+ cmd (str): User provided command
168
+ outfile (Path): Optional output to file
169
+ pager (bool): Optional Use Pager
170
+ """
171
+ console = Console(emoji=False)
172
+ dev = cli.cache.get_dev_identifier(device)
173
+ dev_type = lib_to_api("tshoot", dev.type)
174
+ if len(cmd) == 1:
175
+ cmd = cmd[0].split()
176
+ cmd = " ".join(cmd)
177
+ cmd = cmd.replace(" ", " ").strip().lower()
178
+ resp = cli.central.request(cli.central.get_ts_commands, dev_type)
179
+ if not resp:
180
+ print('[bright_red]Unable to get troubleshooting command list')
181
+ cli.display_results(resp)
182
+ else:
183
+ cmd_list = resp.output
184
+ cmd_id = [c["command_id"] for c in cmd_list if c["command"].strip() == cmd]
185
+ if not cmd_id:
186
+ if FUZZ:
187
+ fuzz_match, fuzz_confidence = process.extract(cmd, [c["command"].strip() for c in cmd_list], limit=1)[0]
188
+ print(f"[bright_red]{cmd}[/] is not a valid troubleshooting command (supported by API) for {dev.type}.")
189
+ confirm_str = render.rich_capture(f"Did you mean [green3]{fuzz_match}[/]?")
190
+ if fuzz_confidence >= 70 and typer.confirm(confirm_str):
191
+ cmd_id = [c["command_id"] for c in cmd_list if c["command"].strip() == fuzz_match]
192
+
193
+ if not cmd_id:
194
+ caption = f'[bright_red]Error[/]: [cyan]{cmd}[/] not found in available troubleshooting commands for {dev.type}. See available commands above.'
195
+ cli.display_results(resp, tablefmt="rich", caption=caption, title=f"Available troubleshooting commands for {dev.type}", cleaner=cleaner.show_ts_commands)
196
+ else:
197
+ resp = cli.central.request(cli.central.start_ts_session, dev.serial, dev_type=dev_type, commands=cmd_id)
198
+ cli.display_results(resp, tablefmt="action", exit_on_fail=True)
199
+
200
+ complete = False
201
+ while not complete:
202
+ for x in range(3):
203
+ with console.status("Waiting for Troubleshooting Response..."):
204
+ sleep(10)
205
+ ts_resp = cli.central.request(cli.central.get_ts_output, dev.serial, resp.session_id)
206
+
207
+ if ts_resp.output.get("status", "") == "COMPLETED":
208
+ print(ts_resp.output["output"])
209
+ complete = True
210
+ break
211
+ else:
212
+ print(f'{ts_resp.output.get("message", " . ").split(".")[0]}. [cyan]Waiting...[/]')
213
+
214
+
215
+ if not complete:
216
+ print(f'[dark_orange3]WARNING[/] Central is still waiting on response from [cyan]{dev.name}[/]')
217
+ if not typer.confirm("Continue to wait/retry?"):
218
+ cli.display_results(ts_resp, tablefmt="action", pager=pager, outfile=outfile)
219
+ break
220
+
221
+ @app.command(short_help="Send troubleshooting command to a device")
222
+ def command(
223
+ device: str = typer.Argument(..., metavar=iden_meta.dev, autocompletion=cli.cache.dev_completion,),
224
+ cmd: List[str] = typer.Argument(..., help="command to send to switch, must be supported by API."),
225
+ outfile: Path = typer.Option(None, "--out", help="Output to file (and terminal)", writable=True, show_default=False,),
226
+ pager: bool = typer.Option(False, "--pager", help="Enable Paged Output"),
227
+ default: bool = typer.Option(False, "-d", is_flag=True, help="Use default central account", show_default=False,),
228
+ debug: bool = typer.Option(False, "--debug", envvar="ARUBACLI_DEBUG", help="Enable Additional Debug Logging",),
229
+ account: str = typer.Option("central_info",
230
+ envvar="ARUBACLI_ACCOUNT",
231
+ help="The Aruba Central Account to use (must be defined in the config)",
232
+ autocompletion=cli.cache.account_completion),
233
+ ):
234
+ """
235
+ [cyan]Send a user provided troubleshooting commands to a device and wait for results.[/]
236
+
237
+ Returns response (from device) to troubleshooting commands.
238
+ Commands must be supported by the API, use [cyan]show tshoot commands <dev-type>[/] to see available commands
239
+ """
240
+ ts_send_command(device, cmd, outfile, pager)
241
+
242
+
147
243
  @app.callback()
148
244
  def callback():
149
245
  """
@@ -179,6 +179,7 @@ def swarm(
179
179
  ...,
180
180
  metavar=iden.dev,
181
181
  help=f"Upgrade will be performed on the cluster the AP belongs to.",
182
+ autocompletion=cli.cache.dev_ap_completion,
182
183
  ),
183
184
  version: str = typer.Argument(None, help="Version to upgrade to",),
184
185
  at: datetime = typer.Option(
@@ -31,6 +31,16 @@ class DevTypes(str, Enum):
31
31
  gw = "gw"
32
32
 
33
33
 
34
+ class TSDevTypes(str, Enum):
35
+ ap = "ap"
36
+ sw = "sw"
37
+ switch = "switch"
38
+ cx = "cx"
39
+ gateway = "gateway"
40
+ gw = "gw"
41
+ mas = "mas"
42
+
43
+
34
44
  class SendConfigDevIdens(str, Enum):
35
45
  ap = "ap"
36
46
  gw = "gw"
@@ -47,6 +57,7 @@ class ShowInventoryArgs(str, Enum):
47
57
  others = "others"
48
58
 
49
59
 
60
+ # TODO can move to lib_to_api class below
50
61
  SHOWINVENTORY_LIB_TO_API = {
51
62
  "all": "all",
52
63
  "ap": "all_ap",
@@ -126,6 +137,7 @@ STRIP_KEYS = [
126
137
  "interfaces",
127
138
  "areas",
128
139
  "lsas",
140
+ "commands",
129
141
  ]
130
142
 
131
143
 
@@ -376,6 +388,14 @@ class ArgToWhat:
376
388
  def _init_test(self):
377
389
  self.webhooks = self.webhook = "webhook"
378
390
 
391
+ def _init_tshoot(self):
392
+ self.ap = self.aps = self.iap = "ap"
393
+ self.gateway = self.gateways = self.gw = "gateway"
394
+ self.switch = self.switch = self.switches = "switch"
395
+ self.cx = "cx"
396
+ self.mas = "mas"
397
+
398
+
379
399
  def _init_clone(self):
380
400
  self.group = self.groups = "group"
381
401
 
@@ -406,6 +426,10 @@ APIMethodType = Literal[
406
426
  "site",
407
427
  "monitoring",
408
428
  "event",
429
+ "template",
430
+ "firmware",
431
+ "event",
432
+ "tshoot"
409
433
  ]
410
434
 
411
435
 
@@ -471,9 +495,12 @@ class LibToAPI:
471
495
  }
472
496
  self.tshoot_to_api = {
473
497
  "gw": "CONTROLLER",
498
+ "gateway": "CONTROLLER",
474
499
  "ap": "IAP",
475
500
  "cx": "CX",
476
- "sw": "SWITCH"
501
+ "sw": "SWITCH",
502
+ "switch": "SWITCH",
503
+ "mas": "MAS"
477
504
  }
478
505
 
479
506
  def __call__(self, method: APIMethodType, key: str, default: str = None) -> str:
@@ -724,6 +751,11 @@ class SortOspfDatabaseOptions(str, Enum):
724
751
  route_tag = "route_tag"
725
752
 
726
753
 
754
+ class SortTsCmdOptions(str, Enum):
755
+ command_id = "id"
756
+ category = "category"
757
+
758
+
727
759
  class StatusOptions(str, Enum):
728
760
  up = "up"
729
761
  down = "down"
@@ -766,6 +798,7 @@ class IdenMetaVars:
766
798
  self.dev_words = f"Optional Identifying Attribute: {self.dev}"
767
799
  self.generic_dev_types = "[ap|gw|switch]"
768
800
  self.dev_types = "[ap|gw|cx|sw]"
801
+ self.dev_types_w_mas = "[ap|gw|cx|sw|mas]"
769
802
  self.group_or_dev = f"device {self.dev.upper()} | group [GROUP]"
770
803
  self.group_dev_cencli = f"{self.dev.upper().replace(']', '|GROUPNAME|cencli]')}"
771
804
  self.group_or_dev_or_site = "[DEVICE|\"all\"|GROUP|SITE]"
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "centralcli"
3
- version = "1.7"
3
+ version = "1.8"
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