예제 #1
0
    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)
예제 #2
0
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()
예제 #3
0
    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()
예제 #4
0
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)
예제 #5
0
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)