async def show_config(self, group: str, dev_mac: str = None) -> Response: show_url = "/caasapi/v1/showcommand" url = f"{show_url}/object/committed?group_name={group}" if dev_mac: mac = utils.Mac(dev_mac) if mac: url = f"{url}/{mac.url}" else: log.error( f"{dev_mac} does not appear to be a valid MAC address.", show=True) raise typer.Exit(1) return await self.central.get(url)
def kick( device: str = typer.Argument( ..., metavar=iden.dev, autocompletion=lambda incomplete: ["all", *[m for m in cli.cache.dev_completion(incomplete)]] ), what: KickArgs = typer.Argument(...,), who: str = typer.Argument(None, help="[<mac>|<wlan/ssid>]",), yes: bool = typer.Option(False, "-Y", help="Bypass confirmation prompts - Assume Yes"), yes_: bool = typer.Option(False, "-y", hidden=True), debug: bool = typer.Option(False, "--debug", envvar="ARUBACLI_DEBUG", help="Enable Additional Debug Logging",), default: bool = typer.Option(False, "-d", is_flag=True, help="Use default central account", show_default=False,), account: str = typer.Option("central_info", envvar="ARUBACLI_ACCOUNT", help="The Aruba Central Account to use (must be defined in the config)", autocompletion=cli.cache.account_completion), ) -> None: yes = yes_ if yes_ else yes if device in ["all", "mac", "wlan"]: typer.secho(f"Missing device parameter required before keyword {device}", fg="red") raise typer.Exit(1) dev = cli.cache.get_dev_identifier(device) if what == "mac": if not who: typer.secho("Missing argument <mac address>", fg="red") raise typer.Exit(1) mac = utils.Mac(who) who = mac.cols if not mac: typer.secho(f"{mac.orig} does not appear to be a valid mac address", fg="red") raise typer.Exit(1) _who = f" {who}" if who else " " if yes or typer.confirm(typer.style(f"Please Confirm: kick {what}{_who} on {dev.name}", fg="cyan")): resp = cli.central.request( cli.central.kick_users, dev.serial, kick_all=True if what == "all" else False, mac=None if what != "mac" else mac.cols, ssid=None if what != "wlan" else who, ) typer.secho(str(resp), fg="green" if resp else "red") else: raise typer.Abort()
def get_dev_identifier( self, query_str: Union[str, List[str], tuple], dev_type: str = None, ret_field: str = "serial", retry: bool = True, multi_ok: bool = True, ) -> CentralObject: # TODO dev_type currently not passed in or handled identifier for show switches would also # try to match APs ... & (self.Q.type == dev_type) # TODO refactor to single test function usable by all identifier methods 1 search with a more involved test if isinstance(query_str, (list, tuple)): query_str = " ".join(query_str) match = None for _ in range(0, 2 if retry else 1): # Try exact match match = self.DevDB.search( (self.Q.name == query_str) | (self.Q.ip.test(lambda v: v.split("/")[0] == query_str)) | (self.Q.mac == utils.Mac(query_str).cols) | (self.Q.serial == query_str)) # retry with case insensitive name match if no match with original query if not match: match = self.DevDB.search( (self.Q.name.test(lambda v: v.lower() == query_str.lower()) ) | self.Q.mac.test(lambda v: v.lower() == utils.Mac( query_str).cols.lower()) | self.Q.serial.test( lambda v: v.lower() == query_str.lower())) # retry name match swapping - for _ and _ for - if not match: if "-" in query_str: match = self.DevDB.search( self.Q.name.test(lambda v: v.lower() == query_str. lower().replace("-", "_"))) elif "_" in query_str: match = self.DevDB.search( self.Q.name.test(lambda v: v.lower() == query_str. lower().replace("_", "-"))) # Last Chance try to match name if it startswith provided value if not match: match = self.DevDB.search( self.Q.name.test( lambda v: v.lower().startswith(query_str.lower())) | self.Q.serial.test( lambda v: v.lower().startswith(query_str.lower())) | self.Q.mac.test(lambda v: v.lower().startswith( utils.Mac(query_str).cols.lower()))) if retry and not match and self.central.get_all_devicesv2 not in self.updated: typer.secho( f"No Match Found for {query_str}, Updating Device Cachce", fg="red") self.check_fresh(refresh=True, dev_db=True) if match: break all_match = None if dev_type: all_match = match match = [ d for d in match if d["type"].lower() in "".join( dev_type[0:len(d["type"])]).lower() ] if match: if len(match) > 1: match = self.handle_multi_match(match, query_str=query_str, multi_ok=multi_ok) return CentralObject("dev", match) elif retry: log.error( f"Unable to gather device {ret_field} from provided identifier {query_str}", show=True) if all_match: all_match = all_match[-1] log.error( f"The Following device matched {all_match.get('name')} excluded as {all_match.get('type')} != {dev_type}", show=True, ) raise typer.Abort()
def do_lldp_rename(fstr: str, **kwargs) -> Response: br = cli.central.BatchRequest resp = cli.central.request(cli.central.get_devices, "aps", status="Up", **kwargs) if not resp: cli.display_results(resp, exit_on_fail=True) elif not resp.output: filters = ", ".join([f"{k}: {v}" for k, v in kwargs.items()]) resp.output = { "description": "API called was successful but returned no results.", "error": f"No Up APs found matching provided filters ({filters})." } resp.ok = False cli.display_results(resp, exit_on_fail=True) _all_aps = utils.listify(resp.output) _keys = ["name", "mac", "model"] ap_dict = {d["serial"]: {k: d[k] for k in d if k in _keys} for d in _all_aps} fstr_to_key = { "h": "neighborHostName", "m": "mac", "p": "remotePort", "M": "model", "S": "site", } req_list, name_list, shown_promt = [], [], False if ap_dict: lldp_reqs = [br(cli.central.get_ap_lldp_neighbor, ap) for ap in ap_dict] lldp_resp = cli.central.batch_request(lldp_reqs) if not all(r.ok for r in lldp_resp): log.error("Error occured while gathering lldp neighbor info", show=True) cli.display_results(lldp_resp, exit_on_fail=True) lldp_dict = {d.output[-1]["serial"]: {k: v for k, v in d.output[-1].items()} for d in lldp_resp} ap_dict = { ser: { **val, "neighborHostName": lldp_dict[ser]["neighborHostName"], "remotePort": lldp_dict[ser]["remotePort"], "site": None if "%S" not in fstr else cli.cache.get_dev_identifier(lldp_dict[ser]["neighborSerial"]) } for ser, val in ap_dict.items() } for ap in ap_dict: ap_dict[ap]["mac"] = utils.Mac(ap_dict[ap]["mac"]).clean # _lldp = cli.central.request(cli.central.get_ap_lldp_neighbor, ap) # if _lldp: # ap_dict[ap]["neighborHostName"] = _lldp.output[-1]["neighborHostName"] # ap_dict[ap]["remotePort"] = _lldp.output[-1]["remotePort"] while True: st = 0 x = '' try: for idx, c in enumerate(fstr): if not idx >= st: continue if c == '%': if fstr[idx + 1] not in fstr_to_key.keys(): _e1 = typer.style( f"Invalid source specifier ({fstr[idx + 1]}) in format string {fstr}: ", fg="red" ) _e2 = "Valid values:\n{}".format( ", ".join(fstr_to_key.keys()) ) typer.echo(f"{_e1}\n{_e2}") raise KeyError(f"{fstr[idx + 1]} is not valid") _src = ap_dict[ap][fstr_to_key[fstr[idx + 1]]] if fstr[idx + 2] != "[": if fstr[idx + 2] == "%" or fstr[idx + 3] == "%": x = f'{x}{_src}' st = idx + 2 elif fstr[idx + 2:idx + 4] == "((": # +3 should also be ( _from = fstr[idx + 4] _to = fstr[idx + 6] if not fstr[idx + 5] == ",": typer.secho( f"expected a comma at character {idx + 1 + 5} but found {fstr[idx + 5]}\n" "will try to proceed.", fg="bright_Red" ) if not fstr[idx + 7:idx + 9] == "))": typer.secho( f"expected a )) at characters {idx + 1 + 7}-{idx + 1 + 8} " f"but found {fstr[idx + 7]}{fstr[idx + 8]}\n" "will try to proceed.", fg="bright_Red" ) x = f'{x}{_src.replace(_from, _to)}' st = idx + 9 else: try: fi = _get_full_int(fstr[idx + 3:]) x = f'{x}{_src.split(fstr[idx + 2])[fi.i]}' st = idx + 3 + len(fi) except IndexError: _e1 = ", ".join(_src.split(fstr[idx + 2])) _e2 = len(_src.split(fstr[idx + 2])) typer.secho( f"\nCan't use segment {fi.o} of '{_e1}'\n" f" It only has {_e2} segments.\n", fg="red" ) raise else: # +2 is '[' if fstr[idx + 3] == "-": try: fi = _get_full_int(fstr[idx + 4:]) x = f'{x}{"".join(_src[-fi.o:])}' st = idx + 4 + len(fi) + 1 # +1 for closing ']' except IndexError: typer.secho( f"Can't extract the final {fi.o} characters from {_src}" f"It's only {len(_src)} characters." ) raise else: # +2 is '[' +3: should be int [1:4] fi = _get_full_int(fstr[idx + 3:]) fi2 = _get_full_int(fstr[idx + 3 + len(fi) + 1:]) # +1 for expected ':' if len(_src[slice(fi.i, fi2.o)]) < fi2.o - fi.i: _e1 = typer.style( f"\n{fstr} wants to take characters " f"\n{fi.o} through {fi2.o}" f"\n\"from {_src}\" (slice ends at character {len(_src[slice(fi.i, fi2.o)])}).", fg="red" ) if not shown_promt and typer.confirm( f"{_e1}" f"\n\nResult will be \"" f"{typer.style(''.join(_src[slice(fi.i, fi2.o)]), fg='bright_green')}\"" " for this segment." "\nOK to continue?" ): shown_promt = True x = f'{x}{"".join(_src[slice(fi.i, fi2.o)])}' st = idx + 3 + len(fi) + len(fi2) + 2 # +2 for : and ] else: raise typer.Abort() else: x = f'{x}{c}' req_list += [cli.central.BatchRequest(cli.central.update_ap_settings, (ap, x))] name_list += [f" {x}"] break except typer.Abort: fstr = _lldp_rename_get_fstr() except Exception as e: log.exception(f"LLDP rename exception while parsing {fstr}\n{e}", show=log.DEBUG) typer.secho(f"\nThere Appears to be a problem with {fstr}: {e.__class__.__name__}", fg="red") if typer.confirm("Do you want to edit the fomat string and try again?", abort="True"): fstr = _lldp_rename_get_fstr() typer.secho(f"Resulting AP names based on '{fstr}':", fg="bright_green") if len(name_list) <= 6: typer.echo("\n".join(name_list)) else: typer.echo("\n".join( [ *name_list[0:3], "...", *name_list[-3:] ] ) ) if typer.confirm("Proceed with AP Rename?", abort=True): return cli.central.batch_request(req_list)
def do_lldp_rename(fstr: str, **kwargs) -> Response: need_lldp = False if "%h" not in fstr and "%p" not in fstr else True # TODO get all APs then filter down after, stash down aps for easy subsequent call resp = cli.central.request(cli.central.get_devices, "aps", status="Up", **kwargs) if not resp: cli.display_results(resp, exit_on_fail=True) elif not resp.output: filters = ", ".join([f"{k}: {v}" for k, v in kwargs.items()]) resp.output = { "description": "API called was successful but returned no results.", "error": f"No Up APs found matching provided filters ({filters})." } resp.ok = False cli.display_results(resp, exit_on_fail=True) _all_aps = utils.listify(resp.output) _keys = ["name", "macaddr", "model", "site", "serial"] ap_dict = {d["serial"]: {k if k != "macaddr" else "mac": d[k] for k in d if k in _keys} for d in _all_aps} fstr_to_key = { "h": "neighborHostName", "m": "mac", "p": "remotePort", "M": "model", "S": "site", "s": "serial" } req_list, name_list, shown_promt = [], [], False if not ap_dict: log.error("Something went wrong, no ap_dict provided or empty", show=True) raise typer.Exit(1) num_calls = len(ap_dict) * 3 if need_lldp else len(ap_dict) * 2 if len(ap_dict) > 5: _warn = "\n\n[blink bright_red blink]WARNING[reset]" if need_lldp: _warn = f"{_warn} Format provided requires details about the upstream switch.\n" _warn = f"{_warn} This automation will result in [cyan]{num_calls}[/] API calls. 3 per AP.\n" _warn = f"{_warn} 1 to gather details about the upstream switch\n" else: _warn = f"{_warn} This automation will result in [cyan]{num_calls}[/] API calls, 1 for each AP.\n" _warn = f"{_warn} 1 to get the aps current settings (all settings need to be provided during the update, only the name changes).\n" _warn = f"{_warn} 1 to Update the settings / rename the AP.\n" _warn = f"{_warn}\n Current daily quota: [bright_green]{resp.rl.remain_day}[/] calls remaining\n" print(_warn) if resp.rl.remain_day < num_calls: print(f" {resp.rl}") print(f" More calls required {num_calls} than what's remaining in daily quota {resp.rl.remain_day}.") typer.confirm("Proceed:", abort=True) if need_lldp: ap_dict = _get_lldp_dict(ap_dict) # TODO refactor and use a template string or j2 something should already exist for this stuff. for ap in ap_dict: ap_dict[ap]["mac"] = utils.Mac(ap_dict[ap]["mac"]).clean while True: st = 0 x = '' try: for idx, c in enumerate(fstr): if not idx >= st: continue if c == '%': if fstr[idx + 1] not in fstr_to_key.keys(): _e1 = typer.style( f"Invalid source specifier ({fstr[idx + 1]}) in format string {fstr}: ", fg="red" ) _e2 = "Valid values:\n{}".format( ", ".join(fstr_to_key.keys()) ) typer.echo(f"{_e1}\n{_e2}") raise KeyError(f"{fstr[idx + 1]} is not valid") _src = ap_dict[ap][fstr_to_key[fstr[idx + 1]]] if fstr[idx + 2] != "[": if fstr[idx + 2] == "%" or fstr[idx + 3] == "%": x = f'{x}{_src}' st = idx + 2 elif fstr[idx + 2:idx + 4] == "((": # +3 should also be ( _from = fstr[idx + 4] _to = fstr[idx + 6] if not fstr[idx + 5] == ",": typer.secho( f"expected a comma at character {idx + 1 + 5} but found {fstr[idx + 5]}\n" "will try to proceed.", fg="bright_red" ) if not fstr[idx + 7:idx + 9] == "))": typer.secho( f"expected a )) at characters {idx + 1 + 7}-{idx + 1 + 8} " f"but found {fstr[idx + 7]}{fstr[idx + 8]}\n" "will try to proceed.", fg="bright_red" ) x = f'{x}{_src.replace(_from, _to)}' st = idx + 9 else: try: fi = _get_full_int(fstr[idx + 3:]) x = f'{x}{_src.split(fstr[idx + 2])[fi.i]}' st = idx + 3 + len(fi) except IndexError: _e1 = ", ".join(_src.split(fstr[idx + 2])) _e2 = len(_src.split(fstr[idx + 2])) typer.secho( f"\nCan't use segment {fi.o} of '{_e1}'\n" f" It only has {_e2} segments.\n", fg="red" ) raise else: # +2 is '[' if fstr[idx + 3] == "-": try: fi = _get_full_int(fstr[idx + 4:]) x = f'{x}{"".join(_src[-fi.o:])}' st = idx + 4 + len(fi) + 1 # +1 for closing ']' except IndexError: typer.secho( f"Can't extract the final {fi.o} characters from {_src}" f"It's only {len(_src)} characters." ) raise else: # +2 is '[' +3: should be int [1:4] fi = _get_full_int(fstr[idx + 3:]) fi2 = _get_full_int(fstr[idx + 3 + len(fi) + 1:]) # +1 for expected ':' if len(_src[slice(fi.i, fi2.o)]) < fi2.o - fi.i: _e1 = typer.style( f"\n{fstr} wants to take characters " f"\n{fi.o} through {fi2.o}" f"\n\"from {_src}\" (slice ends at character {len(_src[slice(fi.i, fi2.o)])}).", fg="red" ) if not shown_promt and typer.confirm( f"{_e1}" f"\n\nResult will be \"" f"{typer.style(''.join(_src[slice(fi.i, fi2.o)]), fg='bright_green')}\"" " for this segment." "\nOK to continue?" ): shown_promt = True x = f'{x}{"".join(_src[slice(fi.i, fi2.o)])}' st = idx + 3 + len(fi) + len(fi2) + 2 # +2 for : and ] else: raise typer.Abort() else: x = f'{x}{c}' req_list += [cli.central.BatchRequest(cli.central.update_ap_settings, (ap, x))] name_list += [f" {x}"] break except typer.Abort: fstr = _lldp_rename_get_fstr() except Exception as e: log.exception(f"LLDP rename exception while parsing {fstr}\n{e}", show=log.DEBUG) print(f"\nThere Appears to be a problem with [red]{fstr}[/]: {e.__class__.__name__}") if typer.confirm("Do you want to edit the fomat string and try again?", abort=True): fstr = _lldp_rename_get_fstr() print(f"[bright_green]Resulting AP names based on '{fstr}':") if len(name_list) <= 6: typer.echo("\n".join(name_list)) else: typer.echo("\n".join( [ *name_list[0:3], " ...", *name_list[-3:] ] ) ) if typer.confirm("Proceed with AP Rename?", abort=True): return cli.central.batch_request(req_list)