Beispiel #1
0
def sites(
    args: List[str] = typer.Argument(None, metavar=iden_meta.site, hidden=False),
    do_json: bool = typer.Option(False, "--json", is_flag=True, help="Output in JSON"),
    do_yaml: bool = typer.Option(False, "--yaml", is_flag=True, help="Output in YAML"),
    do_csv: bool = typer.Option(False, "--csv", is_flag=True, help="Output in CSV"),
    do_table: bool = typer.Option(False, "--table", help="Output in table format",),
    outfile: Path = typer.Option(None, "--out", help="Output to file (and terminal)", writable=True),
    sort_by: SortOptions = typer.Option(None, "--sort"),
    no_pager: bool = typer.Option(False, "--no-pager", help="Disable Paged Output"),
    update_cache: bool = typer.Option(False, "-U", hidden=True),  # Force Update of cli.cache for testing
    default: bool = typer.Option(False, "-d", is_flag=True, help="Use default central account",
                                 callback=cli.default_callback),
    debug: bool = typer.Option(False, "--debug", envvar="ARUBACLI_DEBUG", help="Enable Additional Debug Logging",
                               callback=cli.debug_callback),
    account: str = typer.Option("central_info",
                                envvar="ARUBACLI_ACCOUNT",
                                help="The Aruba Central Account to use (must be defined in the config)",
                                callback=cli.account_name_callback),
):
    central = cli.central
    cli.cache(refresh=update_cache)
    site = None
    if args:
        args = tuple([i for i in args if i != "all"])
        if args:
            site = cli.cache.get_site_identifier(args, multi_ok=True)

    if not site:
        if central.get_all_sites not in cli.cache.updated:
            asyncio.run(cli.cache.update_site_db())
        resp = Response(output=cli.cache.sites)
    else:
        resp = central.request(central.get_site_details, site.id)

    tablefmt = cli.get_format(do_json=do_json, do_yaml=do_yaml, do_csv=do_csv, do_table=do_table)

    cli.display_results(
        resp,
        tablefmt=tablefmt,
        title="Sites" if not args else f"{site.name} site details",
        pager=not no_pager,
        outfile=outfile
    )
Beispiel #2
0
def wlan(
    group: str = typer.Argument(..., metavar="[GROUP NAME|SWARM ID]", autocompletion=cli.cache.group_completion),
    name: str = typer.Argument(..., metavar="[WLAN NAME]", autocompletion=lambda incomplete: tuple(["<WLAN NAME>"])),
    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)",),
) -> None:
    yes = yes_ if yes_ else yes
    group = cli.cache.get_group_identifier(group)
    confirm_1 = typer.style("Please Confirm:", fg="cyan")
    confirm_2 = typer.style("Delete", fg="bright_red")
    confirm_3 = typer.style(f"Group {group.name}, WLAN {name}", fg="cyan")
    if yes or typer.confirm(f"{confirm_1} {confirm_2} {confirm_3}", abort=True):
        resp = cli.central.request(cli.central.delete_wlan, group.name, name)
        cli.display_results(resp, tablefmt="action")
Beispiel #3
0
def nuke(
    device: str = typer.Argument(...,
                                 metavar=iden.dev,
                                 autocompletion=cli.cache.dev_completion),
    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
    dev = cli.cache.get_dev_identifier(device)
    if dev.type != "sw":
        print(
            f"[bright_red]ERROR:[/] This command only applies to AOS-SW (switches), not {dev.type.upper()}"
        )
        raise typer.Exit(1)

    print(
        f"You are about to [bright_red blink]Factory Default[/] [cyan]{dev.name}[/]|[cyan]{dev.serial}[/]"
    )
    if yes or typer.confirm("Proceed?", abort=True):
        resp = cli.central.request(cli.central.send_command_to_device,
                                   dev.serial, 'erase_configuration')
        cli.display_results(resp, tablefmt="action")
Beispiel #4
0
def blink(
    device: str = typer.Argument(
        ...,
        metavar=iden.dev,
        autocompletion=cli.cache.dev_switch_ap_completion),
    action: BlinkArgs = typer.Argument(
        ..., ),  # metavar="Device: [on|off|<# of secs to blink>]"),
    secs: int = typer.Argument(None,
                               metavar="SECONDS",
                               help="Blink for _ seconds."),
    yes: bool = typer.Option(False,
                             "-Y",
                             help="Bypass confirmation prompts - Assume Yes",
                             hidden=True),
    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  # Not using confirmation for blink but will allow -Y
    command = f'blink_led_{action}'
    dev = cli.cache.get_dev_identifier(device, dev_type=["switch", "ap"])
    resp = cli.central.request(cli.central.send_command_to_device,
                               dev.serial,
                               command,
                               duration=secs)
    cli.display_results(resp, tablefmt="action")
Beispiel #5
0
def webhook(
    name: str = typer.Argument(..., ),
    urls: List[str] = typer.Argument(..., help="webhook urls",),
    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",),
    account: str = typer.Option("central_info",
                                envvar="ARUBACLI_ACCOUNT",
                                help="The Aruba Central Account to use (must be defined in the config)",),
) -> None:
    yes = yes_ if yes_ else yes

    print("Adding WebHook: [cyan]{}[/cyan] with urls:\n  {}".format(name, '\n  '.join(urls)))
    if yes or typer.confirm("\nProceed?", abort=True):
        resp = cli.central.request(cli.central.add_webhook, name, urls)

        cli.display_results(resp, tablefmt="action")
        if not resp:
            raise typer.Exit(1)
Beispiel #6
0
def site(
    sites: List[str] = typer.Argument(
        ...,
        help="Site(s) to delete (can provide more than one).",
        autocompletion=cli.cache.site_completion,
    ),
    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)",),
) -> None:
    yes = yes_ if yes_ else yes
    sites = [cli.cache.get_site_identifier(s) for s in sites]

    _del_msg = [
        f"  {typer.style(s.name, fg='reset')}" for s in sites
    ]
    if len(_del_msg) > 7:
        _del_msg = [*_del_msg[0:3], "  ...", *_del_msg[-3:]]
    _del_msg = "\n".join(_del_msg)
    confirm_1 = typer.style("About to", fg="cyan")
    confirm_2 = typer.style("Delete:", fg="bright_red")
    confirm_3 = f'{typer.style(f"Confirm", fg="cyan")} {typer.style(f"delete", fg="red")}'
    confirm_3 = f'{confirm_3} {typer.style(f"{len(sites)} sites?", fg="cyan")}'
    _msg = f"{confirm_1} {confirm_2}\n{_del_msg}\n{confirm_3}"

    if yes or typer.confirm(_msg, abort=True):
        del_list = [s.id for s in sites]
        resp = cli.central.request(cli.central.delete_site, del_list)
        cli.display_results(resp, tablefmt="action")
        if resp:
            cache_del_res = asyncio.run(cli.cache.update_site_db(data=del_list, remove=True))
            if len(cache_del_res) != len(del_list):
                log.warning(
                    f"Attempt to delete entries from Site Cache returned {len(cache_del_res)} "
                    f"but we tried to delete {len(del_list)}",
                    show=True
                )
Beispiel #7
0
def certs(
    name: str = typer.Argument(None, metavar='[certificate name|certificate hash]', hidden=False),
    do_json: bool = typer.Option(False, "--json", is_flag=True, help="Output in JSON"),
    do_yaml: bool = typer.Option(False, "--yaml", is_flag=True, help="Output in YAML"),
    do_csv: bool = typer.Option(False, "--csv", is_flag=True, help="Output in CSV"),
    do_table: bool = typer.Option(False, "--table", help="Output in table format",),
    outfile: Path = typer.Option(None, "--out", help="Output to file (and terminal)", writable=True),
    sort_by: SortOptions = typer.Option(None, "--sort"),
    no_pager: bool = typer.Option(False, "--no-pager", help="Disable Paged Output"),
    default: bool = typer.Option(False, "-d", is_flag=True, help="Use default central account",
                                 callback=cli.default_callback),
    debug: bool = typer.Option(False, "--debug", envvar="ARUBACLI_DEBUG", help="Enable Additional Debug Logging",
                               callback=cli.debug_callback),
    account: str = typer.Option("central_info",
                                envvar="ARUBACLI_ACCOUNT",
                                help="The Aruba Central Account to use (must be defined in the config)",
                                callback=cli.account_name_callback)
) -> None:
    resp = cli.central.request(cli.central.get_certificates, name, callback=cleaner.get_certificates)
    tablefmt = cli.get_format(do_json=do_json, do_yaml=do_yaml, do_csv=do_csv, do_table=do_table, default="rich")

    cli.display_results(resp, tablefmt=tablefmt, pager=not no_pager, outfile=outfile)
Beispiel #8
0
def lldp(
    device: List[str] = typer.Argument(..., metavar=iden_meta.dev, hidden=False),
    do_json: bool = typer.Option(False, "--json", is_flag=True, help="Output in JSON"),
    do_yaml: bool = typer.Option(False, "--yaml", is_flag=True, help="Output in YAML"),
    do_csv: bool = typer.Option(False, "--csv", is_flag=True, help="Output in CSV"),
    do_table: bool = typer.Option(False, "--table", help="Output in table format",),
    outfile: Path = typer.Option(None, "--out", help="Output to file (and terminal)", writable=True),
    sort_by: SortOptions = typer.Option(None, "--sort"),
    no_pager: bool = typer.Option(False, "--no-pager", help="Disable Paged Output"),
    update_cache: bool = typer.Option(False, "-U", hidden=True),  # Force Update of cli.cache for testing
    default: bool = typer.Option(False, "-d", is_flag=True, help="Use default central account",
                                 callback=cli.default_callback),
    debug: bool = typer.Option(False, "--debug", envvar="ARUBACLI_DEBUG", help="Enable Additional Debug Logging",
                               callback=cli.debug_callback),
    account: str = typer.Option("central_info",
                                envvar="ARUBACLI_ACCOUNT",
                                help="The Aruba Central Account to use (must be defined in the config)",
                                callback=cli.account_name_callback)
) -> None:
    central = cli.central
    cli.cache(refresh=update_cache)

    # We take last arg [-1] from list so they can type "neighbor" if they want.
    dev = cli.cache.get_dev_identifier(device[-1], dev_type="ap")
    if dev.type == "ap":
        resp = central.request(central.get_ap_lldp_neighbor, dev.serial)
        tablefmt = cli.get_format(do_json=do_json, do_yaml=do_yaml, do_csv=do_csv, do_table=do_table, default="rich")

        cli.display_results(
            resp,
            tablefmt=tablefmt,
            title="AP lldp neighbor",
            pager=not no_pager,
            outfile=outfile,
            cleaner=cleaner.get_lldp_neighbor,
        )
    else:
        typer.secho(f"This command is currently only valid for APs.  {dev.name} is type: {dev.type}", fg="red")
        raise typer.Exit(1)
Beispiel #9
0
def rename(
    what: RenameArgs = typer.Argument(...,),
    group: str = typer.Argument(..., autocompletion=cli.cache.group_completion),
    new_name: str = typer.Argument(..., autocompletion=lambda incomplete: None),
    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
    group = cli.cache.get_group_identifier(group)
    if yes or typer.confirm(typer.style(f"Please Confirm: rename group {group.name} -> {new_name}", fg="cyan"), abort=True):
        resp = cli.central.request(cli.central.update_group_name, group.name, new_name)

        if not resp and "group already has AOS_10X version set" in resp.output.get("description", ""):
            resp.output["description"] = f"{group.name} is an AOS_10X group, rename only supported on AOS_8X groups. Use clone."

        cli.display_results(resp)
Beispiel #10
0
def group_old(
    group_name: str = typer.Argument(..., autocompletion=cli.cache.group_completion),
    aos_version: int = typer.Argument(None, metavar="[10]", help="Set to 10 to Upgrade group to AOS 10"),
    mos: bool = typer.Option(None, help="Enable monitor only for switches in the group"),
    yes: bool = typer.Option(False, "-Y", help="Bypass confirmation prompts - Assume Yes"),
    yes_: bool = typer.Option(False, "-y", hidden=True),
    update_cache: bool = typer.Option(False, "-U", hidden=True),  # Force Update of cli.cache for testing
    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)",),
) -> None:
    yes = yes_ if yes_ else yes
    cli.cache(refresh=update_cache)
    group = cli.cache.get_group_identifier(group_name)

    _msg = [
        typer.style("Update group", fg="cyan"),
        typer.style(group.name, fg="bright_green"),
    ]
    if aos_version:
        _msg += [
            typer.style("AOS version", fg="cyan"),
            typer.style(str(aos_version), fg="bright_green"),
            typer.style("[Note: AOS10 groups can not be downgraded back to AOS8]", fg="red"),
        ]

    _msg = " ".join(_msg)

    if mos is not None:
        _msg = f'{_msg}{", " if aos_version else ": "}{typer.style("monitor only switch", fg="cyan")}'
        _msg = f'{_msg} {typer.style("enabled" if mos else "disabled", fg="bright_green")}'

    _msg = f'{_msg}{typer.style("?", fg="cyan")}'

    if yes or typer.confirm(_msg, abort=True):
        resp = cli.central.request(cli.central.update_group_properties_v1, group.name, aos_version, monitor_only_switch=mos)
        cli.display_results(resp)
Beispiel #11
0
def _get_lldp_dict(ap_dict: dict) -> dict:
    """Updates provided dict of APs keyed by AP serial number with lldp neighbor info
    """
    br = cli.central.BatchRequest
    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"],
        }
        for ser, val in ap_dict.items()
    }

    return ap_dict
Beispiel #12
0
def _cache(
    args: List[CacheArgs] = typer.Argument(None, hidden=False),
    do_yaml: bool = typer.Option(False, "--yaml", is_flag=True, help="Output in YAML"),
    do_csv: bool = typer.Option(False, "--csv", is_flag=True, help="Output in CSV"),
    do_table: bool = typer.Option(False, "--table", help="Output in table format",),
    outfile: Path = typer.Option(None, "--out", help="Output to file (and terminal)", writable=True),
    no_pager: bool = typer.Option(False, "--no-pager", help="Disable Paged Output"),
    update_cache: bool = typer.Option(False, "-U", hidden=True),  # Force Update of cli.cache for testing
    default: bool = typer.Option(False, "-d", is_flag=True, help="Use default central account",
                                 callback=cli.default_callback),
    debug: bool = typer.Option(False, "--debug", envvar="ARUBACLI_DEBUG", help="Enable Additional Debug Logging",
                               callback=cli.debug_callback),
    account: str = typer.Option("central_info",
                                envvar="ARUBACLI_ACCOUNT",
                                help="The Aruba Central Account to use (must be defined in the config)",
                                callback=cli.account_name_callback),
):
    cli.cache(refresh=update_cache)
    args = ('all',) if not args else args
    for arg in args:
        cache_out = getattr(cli.cache, arg)
        tablefmt = cli.get_format(do_json=None, do_yaml=do_yaml, do_csv=do_csv, do_table=do_table, default="json")
        cli.display_results(data=cache_out, tablefmt=tablefmt, tile=f"Cache {args[-1]}", pager=not no_pager, outfile=outfile)
Beispiel #13
0
def add(
    what: BatchArgs = typer.Argument(...,),
    import_file: Path = typer.Argument(..., exists=True),
    yes: bool = typer.Option(False, "-Y", help="Bypass confirmation prompts - Assume Yes"),
    yes_: bool = typer.Option(False, "-y", hidden=True),
    default: bool = typer.Option(
        False, "-d", is_flag=True, help="Use default central account", show_default=False
    ),
    debug: bool = typer.Option(
        False, "--debug", envvar="ARUBACLI_DEBUG", help="Enable Additional Debug Logging",
    ),
    account: str = typer.Option(
        "central_info",
        envvar="ARUBACLI_ACCOUNT",
        help="The Aruba Central Account to use (must be defined in the config)",
    ),
) -> None:
    """Perform batch Add operations using import data from file."""
    yes = yes_ if yes_ else yes
    if what == "sites":
        resp = batch_add_sites(import_file, yes)
        cli.display_results(resp)
    elif what == "groups":
        batch_add_groups(import_file, yes)
Beispiel #14
0
def variables(
    args: List[str] = typer.Argument(None, metavar=iden_meta.dev, hidden=False),
    do_json: bool = typer.Option(False, "--json", is_flag=True, help="Output in JSON"),
    do_yaml: bool = typer.Option(False, "--yaml", is_flag=True, help="Output in YAML"),
    do_csv: bool = typer.Option(False, "--csv", is_flag=True, help="Output in CSV"),
    do_table: bool = typer.Option(False, "--table", help="Output in table format",),
    outfile: Path = typer.Option(None, "--out", help="Output to file (and terminal)", writable=True),
    sort_by: SortOptions = typer.Option(None, "--sort"),
    no_pager: bool = typer.Option(False, "--no-pager", help="Disable Paged Output"),
    update_cache: bool = typer.Option(False, "-U", hidden=True),  # Force Update of cli.cache for testing
    default: bool = typer.Option(False, "-d", is_flag=True, help="Use default central account",
                                 callback=cli.default_callback),
    debug: bool = typer.Option(False, "--debug", envvar="ARUBACLI_DEBUG", help="Enable Additional Debug Logging",
                               callback=cli.debug_callback),
    account: str = typer.Option("central_info",
                                envvar="ARUBACLI_ACCOUNT",
                                help="The Aruba Central Account to use (must be defined in the config)",
                                callback=cli.account_name_callback)
):
    central = cli.central
    cli.cache(refresh=update_cache)

    if args and args != "all":
        args = cli.cache.get_dev_identifier(args)

    resp = central.request(central.get_variables, () if not args else args.serial)
    tablefmt = cli.get_format(do_json=do_json, do_yaml=do_yaml, do_csv=do_csv, do_table=do_table, default="json")

    cli.display_results(
        resp,
        tablefmt=tablefmt,
        title="Variables" if not args else f"{args.name} Variables",
        pager=not no_pager,
        outfile=outfile,
        sort_by=sort_by
    )
Beispiel #15
0
def rename(
    what: BatchArgs = typer.Argument(...,),
    import_file: Path = typer.Argument(None, metavar="['lldp'|IMPORT FILE PATH]"),  # TODO completion
    lldp: bool = typer.Option(None, help="Automatic AP rename based on lldp info from upstream switch.",),
    ap: str = typer.Option(None, metavar=iden_meta_vars.dev, help="[LLDP rename] Perform on specified AP",),
    label: str = typer.Option(None, help="[LLDP rename] Perform on APs with specified label",),
    group: str = typer.Option(None, help="[LLDP rename] Perform on APs in specified group",),
    site: str = typer.Option(None, metavar=iden_meta_vars.site, help="[LLDP rename] Perform on APs in specified site",),
    model: str = typer.Option(None, help="[LLDP rename] Perform on APs of specified model",),
    yes: bool = typer.Option(False, "-Y", help="Bypass confirmation prompts - Assume Yes"),
    yes_: bool = typer.Option(False, "-y", hidden=True),
    default: bool = typer.Option(False, "-d", is_flag=True, help="Use default central account", show_default=False,),
    debug: bool = typer.Option(
        False, "--debug", envvar="ARUBACLI_DEBUG", help="Enable Additional Debug Logging",
    ),
    account: str = typer.Option(
        "central_info",
        envvar="ARUBACLI_ACCOUNT",
        help="The Aruba Central Account to use (must be defined in the config)",
    ),
) -> None:
    """Perform AP rename in batch from import file or automatically based on LLDP"""
    yes = yes_ if yes_ else yes

    if str(import_file).lower() == "lldp":
        lldp = True
        import_file = None

    if not import_file and not lldp:
        typer.echo("Error: Missing required parameter [IMPORT_FILE|'lldp']")
        raise typer.Exit(1)

    central = cli.central
    if import_file:
        if not import_file.exists():
            typer.secho(f"Error: {import_file} not found.", fg="red")
            raise typer.Exit(1)

        data = config.get_file_data(import_file)

        resp = None
        if what == "aps":
            # transform flat csv struct to Dict[str, Dict[str, str]] {"<AP serial>": {"hostname": "<desired_name>"}}
            if import_file.suffix in [".csv", ".tsv", ".dbf", ".xls", ".xlsx"]:
                if data and len(data.headers) < 3:
                    if "name" in data.headers:
                        data = [{k if k != "name" else "hostname": d[k] for k in d} for d in data.dict]
                        data.headers["hostname"] = data.headers.pop(
                            data.headers.index(data.headers["name"])
                        )
                    data = {
                        i.get("serial", i.get("serial_number", i.get("serial_num", "ERROR"))):
                        {k: v for k, v in i.items() if not k.startswith("serial")} for i in data.dict
                    }

            calls, conf_msg = [], [typer.style("Names Gathered from import:", fg="bright_green")]
            for ap in data:  # serial num
                conf_msg += [f"  {ap}: {data[ap]['hostname']}"]
                calls.append(central.BatchRequest(central.update_ap_settings, (ap,), data[ap]))

            if len(conf_msg) > 6:
                conf_msg = [*conf_msg[0:3], "...", *conf_msg[-3:]]
            typer.echo("\n".join(conf_msg))

            if yes or typer.confirm("Proceed with AP rename?", abort=True):
                resp = central.batch_request(calls)

    elif lldp:
        kwargs = {}
        if group:
            kwargs["group"] = cli.cache.get_group_identifier(group).name
        if ap:
            kwargs["serial"] = cli.cache.get_dev_identifier(ap, dev_type="ap").serial
        if site:
            kwargs["site"] = cli.cache.get_site_identifier(site).name
        if model:
            kwargs["model"] = model
        if label:
            kwargs["label"] = label

        resp = do_lldp_rename(_lldp_rename_get_fstr(), **kwargs)

        cli.display_results(resp, exit_on_fail=True)
Beispiel #16
0
def delete(
    what: BatchDelArgs = typer.Argument(...,),
    import_file: Path = typer.Argument(..., exists=True, readable=True),
    yes: bool = typer.Option(False, "-Y", help="Bypass confirmation prompts - Assume Yes"),
    yes_: bool = typer.Option(False, "-y", hidden=True),
    default: bool = typer.Option(
        False, "-d", is_flag=True, help="Use default central account", show_default=False,
    ),
    debug: bool = typer.Option(
        False, "--debug", envvar="ARUBACLI_DEBUG", help="Enable Additional Debug Logging",
    ),
    account: str = typer.Option(
        "central_info",
        envvar="ARUBACLI_ACCOUNT",
        help="The Aruba Central Account to use (must be defined in the config)",
    ),
) -> None:
    """Perform batch Delete operations using import data from file."""
    yes = yes_ if yes_ else yes
    central = cli.central
    data = config.get_file_data(import_file)
    if hasattr(data, "dict"):  # csv
        data = data.dict

    resp = None
    if what == "sites":
        del_list = []
        _msg_list = []
        for i in data:
            if isinstance(i, str) and isinstance(data[i], dict):
                i = {"site_name": i, **data[i]} if "name" not in i and "site_name" not in i else data[i]

            if "site_id" not in i and "id" not in i:
                if "site_name" in i or "name" in i:
                    _name = i.get("site_name", i.get("name"))
                    _id = cli.cache.get_site_identifier(_name).id
                    found = True
                    _msg_list += [_name]
                    del_list += [_id]
            else:
                found = False
                for key in ["site_id", "id"]:
                    if key in i:
                        del_list += [i[key]]
                        _msg_list += [i.get("site_name", i.get("site", i.get("name", f"id: {i[key]}")))]
                        found = True
                        break

            if not found:
                if i.get("site_name", i.get("site", i.get("name"))):
                    site = cli.cache.get_site_identifier(i.get("site_name", i.get("site", i.get("name"))))
                    _msg_list += [site.name]
                    del_list += [site.id]
                    break
                else:
                    typer.secho("Error getting site ids from import, unable to find required key", fg="red")
                    raise typer.Exit(1)

        if len(_msg_list) > 7:
            _msg_list = [*_msg_list[0:3], "...", *_msg_list[-3:]]
        typer.secho("\nSites to delete:", fg="bright_green")
        typer.echo("\n".join([f"  {m}" for m in _msg_list]))
        if yes or typer.confirm(f"\n{typer.style('Delete', fg='red')} {len(del_list)} sites", abort=True):
            resp = central.request(central.delete_site, del_list)
            if resp:
                cache_del_res = asyncio.run(cli.cache.update_site_db(data=del_list, remove=True))
                if len(cache_del_res) != len(del_list):
                    log.warning(
                        f"Attempt to delete entries from Site Cache returned {len(cache_del_res)} "
                        f"but we tried to delete {len(del_list)} sites.",
                        show=True
                    )

    cli.display_results(resp)
Beispiel #17
0
def add(
    what: BatchArgs = typer.Argument(...,),
    import_file: Path = typer.Argument(..., exists=True),
    yes: bool = typer.Option(False, "-Y", help="Bypass confirmation prompts - Assume Yes"),
    yes_: bool = typer.Option(False, "-y", hidden=True),
    default: bool = typer.Option(
        False, "-d", is_flag=True, help="Use default central account", show_default=False
    ),
    debug: bool = typer.Option(
        False, "--debug", envvar="ARUBACLI_DEBUG", help="Enable Additional Debug Logging",
    ),
    account: str = typer.Option(
        "central_info",
        envvar="ARUBACLI_ACCOUNT",
        help="The Aruba Central Account to use (must be defined in the config)",
    ),
) -> None:
    """Perform batch Add operations using import data from file."""
    yes = yes_ if yes_ else yes
    central = cli.central
    data = config.get_file_data(import_file)

    resp = None
    if what == "sites":
        if import_file.suffix in [".csv", ".tsv", ".dbf", ".xls", ".xlsx"]:
            # TODO Exception handler
            if "address" in str(data.headers) and len(data.headers) > 3:  # address info
                data = [
                    {
                        "site_name": i.get("site_name", i.get("site", i.get("name"))),
                        "site_address": {k: v for k, v in i.items() if k not in ["site", "site_name"]}
                    }
                    for i in data.dict
                ]
            else:  # geoloc
                data = [
                    {
                        "site_name": i.get("site_name", i.get("site", i.get("name"))),
                        "geolocation": {k: v for k, v in i.items() if k not in ["site", "site_name"]}
                    }
                    for i in data.dict
                ]
        site_names = [
            d.get("site_name", "ERROR") for d in data
        ]
        if len(site_names) > 7:
            site_names = [*site_names[0:3], "...", *site_names[-3:]]
        _msg = [
            typer.style("Batch Add Sites:", fg="cyan"),
            typer.style(
                "\n".join([typer.style(f'  {n}', fg="bright_green" if n != "..." else "reset") for n in site_names])
            ),
            typer.style("Proceed with Site Additions?", fg="cyan")
        ]
        _msg = "\n".join(_msg)
        if yes or typer.confirm(_msg, abort=True):
            resp = central.request(central.create_site, site_list=data)
            if resp:
                cache_data = [{k.replace("site_", ""): v for k, v in d.items()} for d in resp.output]
                cache_res = asyncio.run(cli.cache.update_site_db(data=cache_data))
                if len(cache_res) != len(data):
                    log.warning(
                        "Attempted to add entries to Site Cache after batch import.  Cache Response "
                        f"{len(cache_res)} but we added {len(data)} sites.",
                        show=True
                    )

    cli.display_results(resp)
Beispiel #18
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)
Beispiel #19
0
def device(
    kw1: AddGroupArgs = typer.Argument(..., hidden=True, metavar="",),
    val1: str = typer.Argument(..., metavar="serial [SERIAL NUM]", hidden=False, autocompletion=cli.cache.smg_kw_completion),
    kw2: str = typer.Argument(..., hidden=True, metavar="", autocompletion=cli.cache.smg_kw_completion),
    val2: str = typer.Argument(..., metavar="mac [MAC ADDRESS]", hidden=False, autocompletion=cli.cache.smg_kw_completion),
    kw3: str = typer.Argument(None, metavar="", hidden=True, autocompletion=cli.cache.smg_kw_completion),
    val3: str = typer.Argument(None, metavar="group [GROUP]", help="pre-assign device to group",
                               autocompletion=cli.cache.smg_kw_completion),
    kw4: str = typer.Argument(None, metavar="", hidden=True, autocompletion=cli.cache.smg_kw_completion),
    val4: str = typer.Argument(None, metavar="site [SITE]", help="Assign newly added device to site",
                               autocompletion=cli.cache.smg_kw_completion),
    _: str = typer.Argument(None, metavar="", hidden=True, autocompletion=cli.cache.null_completion),
    _group: str = typer.Option(None, "--group", autocompletion=cli.cache.group_completion, hidden=True),
    _site: str = typer.Option(None, "--site", autocompletion=cli.cache.site_completion, hidden=True),
    license: List[LicenseTypes] = typer.Option(None, "--license", help="Assign license subscription(s) to device"),
    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)",),
) -> None:
    yes = yes_ if yes_ else yes
    kwd_vars = [kw1, kw2, kw3, kw4]
    vals = [val1, val2, val3, val4]
    kwargs = {
        "mac": None,
        "serial": None,
        "group": None,
        "site": None,
        "license": license
    }

    if _:
        print("DEVELOPER NOTE Null completion item has value... being ignored.")
    for name, value in zip(kwd_vars, vals):
        if name and name not in kwargs:
            print(f"[bright_red][blink]Error[/bright_red]: {name} is invalid")
            raise typer.Exit(1)
        else:
            kwargs[name] = value

    kwargs["group"] = kwargs["group"] or _group
    kwargs["site"] = kwargs["site"] or _site

    # Error if both serial and mac are not provided
    if not kwargs["mac"] or not kwargs["serial"]:
        print("[bright_red][blink]Error[/bright_red]: both serial number and mac address are required.")
        raise typer.Exit(1)

    api_kwd = {"serial": "serial_num", "mac": "mac_address"}
    kwargs = {api_kwd.get(k, k): v for k, v in kwargs.items() if v}

    _msg = [f"Add device: [bright_green]{kwargs['serial_num']}|{kwargs['mac_address']}[/bright_green]"]
    if "group" in kwargs and kwargs["group"]:
        _group = cli.cache.get_group_identifier(kwargs["group"])
        kwargs["group"] = _group.name
        _msg += [f"\n  Pre-Assign to Group: [bright_green]{kwargs['group']}[/bright_green]"]
    if "site" in kwargs and kwargs["site"]:
        _site = cli.cache.get_site_identifier(kwargs["site"])
        kwargs["site"] = _site.id
        _msg += [f"\n  Assign to Site: [bright_green]{_site.name}[/bright_green]"]
    if "license" in kwargs and kwargs["license"]:
        _lic_msg = [lic._value_ for lic in kwargs["license"]]
        _lic_msg = _lic_msg if len(kwargs["license"]) > 1 else _lic_msg[0]
        _msg += [
            f"\n  Assign License{'s' if len(kwargs['license']) > 1 else ''}: [bright_green]{_lic_msg}[/bright_green]"
        ]
        kwargs["license"] = [lic.replace("-", "_") for lic in kwargs["license"]]

    print("".join(_msg))

    # if yes or typer.confirm(_msg, abort=True):
    if yes or typer.confirm("\nProceed?", abort=True):
        resp = cli.central.request(cli.central.add_devices, **kwargs)
        cli.display_results(resp, tablefmt="action")
Beispiel #20
0
def group(
    group: str = typer.Argument(..., metavar="[GROUP NAME]", autocompletion=cli.cache.group_completion),
    # group_password: str = typer.Argument(
    #     None,
    #     show_default=False,
    #     help="Group password is required. You will be prompted for password if not provided.",
    #     autocompletion=lambda incomplete: incomplete
    # ),
    wired_tg: bool = typer.Option(False, "--wired-tg", help="Manage switch configurations via templates"),
    wlan_tg: bool = typer.Option(False, "--wlan-tg", help="Manage AP configurations via templates"),
    gw_role: GatewayRole = typer.Option(None, metavar="[branch|vpnc|wlan]"),
    aos10: bool = typer.Option(None, "--aos10", is_flag=True, help="Create AOS10 Group (default Instant)", show_default=False),
    microbranch: bool = typer.Option(
        None,
        "--mb",
        is_flag=True,
        help="Configure Group for MicroBranch APs (AOS10 only)",
        show_default=False,
    ),
    ap: bool = typer.Option(None, "--ap", help="Allow APs in group"),
    sw: bool = typer.Option(None, "--sw", help="Allow ArubaOS-SW switches in group."),
    cx: bool = typer.Option(None, "--cx", help="Allow ArubaOS-CX switches in group."),
    gw: bool = typer.Option(None, "--gw", help="Allow gateways in group."),
    mon_only_sw: bool = typer.Option(False, "--mon-only-sw", help="Monitor Only for ArubaOS-SW"),
    mon_only_cx: bool = typer.Option(False, "--mon_only_cx", help="Monitor Only for ArubaOS-CX", hidden=True),
    # ap_user: str = typer.Option("admin", help="Provide user for AP group"),  # TODO build func to update group pass
    # ap_passwd: str = typer.Option(None, help="Provide password for AP group (use single quotes)"),
    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",),
    debugv: bool = typer.Option(
        False, "--debugv",
        envvar="ARUBACLI_VERBOSE_DEBUG",
        help="Enable verbose Debug Logging",
        callback=cli.verbose_debug_callback,
        hidden=True,
    ),
    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)",),
) -> None:
    yes = yes_ if yes_ else yes
    # if not group_password:
    #     group_password = typer.prompt("Group Password", confirmation_prompt=True, hide_input=True,)

    # else:
    #     _msg = f'{_msg}{typer.style(f"?", fg="cyan")}'

    allowed_types = []
    if ap:
        allowed_types += ["ap"]
    if sw:
        allowed_types += ["sw"]
    if cx:
        allowed_types += ["cx"]
    if gw:
        allowed_types += ["gw"]
    if not allowed_types:
        allowed_types = ["ap", "gw", "cx", "sw"]
    _arch = "Instant" if not aos10 else "AOS10"

    # -- // Error on combinations that are not allowed by API \\ --
    if not aos10 and microbranch:
        print(
            f":x: [bright_red]Microbranch is only valid if group is configured as AOS10 group ({color('--aos10')})."
        )
        raise typer.Exit(1)
    if (mon_only_sw or mon_only_cx) and wired_tg:
        print(":x: [bright_red]Error: Monitor only is not valid for template group.")
        raise typer.Exit(1)
    if not [t for t in allowed_types if allowed_types in ["cx", "sw"]] and (mon_only_sw or mon_only_cx):
        print(":x: [bright_red]Error: Monitor only is not valid without '--sw' or '--cx' (Allowed Device Types)")
        raise typer.Exit(1)
    if gw_role and gw_role == "wlan" and not aos10:
        print(":x: [bright_red]WLAN role for Gateways requires the group be configured as AOS10 via --aos10 option.")
        raise typer.Exit(1)
    if all([x is None for x in [ap, sw, cx, gw]]):
        print("[green]No Allowed devices provided. Allowing all device types.")
        print("[reset]  NOTE: Device Types can be added once group is created, but not removed.\n")

    _arch_msg = f"[bright_green]{_arch} "
    _msg = f"[cyan]Create {'' if aos10 is None else _arch_msg}[cyan]group [bright_green]{group}[/bright_green]"
    _msg = f"{_msg}\n    [cyan]Allowed Device Types[/cyan]: [bright_green]{allowed_types}[/bright_green]"

    if wired_tg:
        _msg = f"{_msg}\n    [cyan]switches[/cyan]: [bright_green]Template Group[/bright_green]"
    if wlan_tg:
        _msg = f"{_msg}\n    [cyan]APs[/cyan]: [bright_green]Template Group[/bright_green]"
    if gw_role:
        _msg = f"{_msg}\n    [cyan]Gateway Role[/cyan]: [bright_green]{gw_role}[/bright_green]"
    if microbranch:
        _msg = f"{_msg}\n    [cyan]AP Role[/cyan]: [bright_green]Microbranch[/bright_green]"
    if mon_only_sw:
        _msg = f"{_msg}\n    [cyan]Monitor Only ArubaOS-SW: [bright_green]True[/bright_green]"
    if mon_only_cx:
        _msg = f"{_msg}\n    [cyan]Monitor Only ArubaOS-CX: [bright_green]True[/bright_green]"
    print(f"{_msg}\n")

    if yes or typer.confirm("Proceed with values?"):
        resp = cli.central.request(
            cli.central.create_group,
            group,
            wired_tg=wired_tg,
            wlan_tg=wlan_tg,
            allowed_types=allowed_types,
            aos10=aos10,
            microbranch=microbranch,
            gw_role=gw_role,
            monitor_only_sw=mon_only_sw,
        )
        cli.display_results(resp, tablefmt="action")
        if resp:
            asyncio.run(
                cli.cache.update_group_db({'name': group, 'template group': {'Wired': wired_tg, 'Wireless': wlan_tg}})
            )
        else:
            raise typer.Exit(1)
Beispiel #21
0
def group(
    group: str = typer.Argument(
        ...,
        metavar="[GROUP NAME]",
        help="Upgrade devices by group",
        autocompletion=cli.cache.group_completion,
    ),
    version: str = typer.Argument(
        None,
        help="Version to upgrade to [Default: recommended version]",
        show_default=False,
        autocompletion=lambda incomplete: [
            m for m in [
                ("<firmware version>", "The version of firmware to upgrade to."),
                *[m for m in cli.cache.null_completion(incomplete)]
            ]
        ],
    ),
    at: datetime = typer.Option(
        None,
        help="When to schedule upgrade. format: 'mm/dd/yyyy hh:mm' or 'dd hh:mm' (implies current month) [Default: Now]",
        show_default=False,
        formats=["%m/%d/%Y %H:%M", "%d %H:%M"],
        ),
    dev_type: GenericDevIdens = typer.Option(None, help="Upgrade a specific device type",),
    model: str = typer.Option(None, help="[applies to switches only] Upgrade a specific switch model"),
    reboot: bool = typer.Option(False, "-R", help="Automatically reboot device after firmware download"),
    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)",
                                ),
) -> None:
    yes = yes_ if yes_ else yes
    group = cli.cache.get_group_identifier(group)
    at = None if not at else int(round(at.timestamp()))

    ver_msg = [typer.style("Upgrade", fg="cyan")]
    if dev_type:
        ver_msg += [lib_to_gen_plural(dev_type)]
        dev_type = lib_to_api("upgrade", dev_type)

    if model:
        ver_msg += [f"model {typer.style(f'{model}', fg='bright_green')}"]

    ver_msg += [f"in group {typer.style(f'{group.name}', fg='bright_green')}"]

    if version:
        _version = [f"to {typer.style(version, fg='bright_green')}"]
    else:
        _version = [f"to {typer.style('Recommended version', fg='bright_green')}"]
    ver_msg += _version
    ver_msg = " ".join(ver_msg)

    if reboot:
        ver_msg = f"{ver_msg} and reboot"

    if yes or typer.confirm(
        f"{ver_msg}?",
        abort=True,
    ):
        resp = cli.central.request(
            cli.central.upgrade_firmware,
            scheduled_at=at,
            group=group.name,
            device_type=dev_type,
            firmware_version=version,
            model=model,
            reboot=reboot
        )
        cli.display_results(resp, tablefmt="action")
Beispiel #22
0
def send_cmds(
    kw1: constants.SendCmdArgs = typer.Argument(
        ...,
    ),
    nodes: str = typer.Argument(
        None,
        autocompletion=cache.send_cmds_completion,
        metavar=iden.group_or_dev_or_site,
        # callback=cli.send_cmds_node_callback,
        # is_eager=True,
    ),
    kw2: str = typer.Argument(
        None,
        autocompletion=cache.send_cmds_completion,
        # callback=cli.send_cmds_node_callback,
    ),
    commands: List[str] = typer.Argument(None, callback=cli.send_cmds_node_callback),
    cmd_file: Path = typer.Option(None, help="Path to file containing commands (1 per line) to be sent to device", exists=True),
    # dev_file: Path = typer.Option(None, help="Path to file containing iden for devices to send commands to", exists=True),
    # group: bool = typer.Option(None, help="Send commands to all gateways in a group", autocompletion=cli.cache.group_completion),
    # site: bool = typer.Option(None, help="Send commands to all gateways in a site", autocompletion=cli.cache.site_completion),
    all: bool = typer.Option(False, "-A", help="Send command(s) to all gateways (device level update) when group is provided"),
    yes: bool = typer.Option(False, "-Y", help="Bypass confirmation prompts - Assume Yes"),
    yes_: bool = typer.Option(False, "-y", hidden=True),
    default: bool = typer.Option(False, "-d", is_flag=True, help="Use default central account",
                                 callback=cli.default_callback),
    debug: bool = typer.Option(False, "--debug", envvar="ARUBACLI_DEBUG", help="Enable Additional Debug Logging",
                               callback=cli.debug_callback),
    account: str = typer.Option("central_info",
                                envvar="ARUBACLI_ACCOUNT",
                                help="The Aruba Central Account to use (must be defined in the config)",
                                callback=cli.account_name_callback),
) -> None:
    console = Console(emoji=False)
    yes = yes if yes else yes_
    commands = commands or []
    if kw1 == "group":
        if all:
            g = cache.get_group_identifier(nodes)
            nodes = [cache.CentralObject(d) for d in cache.devices if d["type"] == "gw" and d["group"] == g.name]
            action = f"all devices in {g.name} group."
        else:
            nodes = cache.get_group_identifier(nodes)
            action = f"group level gateway config for {nodes.name} group."
    elif kw1 == "site":
        s = cache.get_group_identifier(nodes)
        nodes = [cache.CentralObject(d) for d in cache.devices if d["type"] == "gw" and d["site"] == s.name]
        action = f"all devices in site: {s.name}"
    elif kw1 == "file":
        dev_file = Path(nodes)
        file_data = config.get_file_data(dev_file, text_ok=True)
        if not file_data:
            print(f"No data parsed from file {dev_file.name}.")
            raise typer.Exit(1)

        if isinstance(file_data, list):
            nodes = [cache.get_identifier(d.strip(), ["dev", "group", "site"], device_type="gw") for d in file_data]
        else:
            devices = file_data.get("devices", file_data.get("gateways"))
            if devices:
                nodes = [cache.get_identifier(d.strip(), ["dev", "group", "site"], device_type="gw") for d in file_data["devices"]]
            elif "groups" in file_data:
                nodes = [cache.CentralObject(d) for d in cache.devices if d["type"] == "gw" and d["group"] in file_data["groups"]]
            elif "sites" in file_data:
                nodes = [cache.CentralObject(d) for d in cache.devices if d["type"] == "gw" and d["site"] in file_data["sites"]]
            else:
                print(f"Expected 'gateways', 'groups', or 'sites' key in {dev_file.name}.")
                raise typer.Exit(1)

            if "cmds" in file_data or "commands" in file_data:
                if commands:
                    print("Providing commands on the command line and in the import file is a strange thing to do.")
                    raise typer.Exit(1)
                commands = file_data.get("cmds", file_data.get("commands"))
    elif kw1 == "device":
        if not isinstance(nodes, str):
            print(f"nodes is of type {type(nodes)} this is unexpected.")

        nodes = [cache.get_identifier(nodes, ["dev"], "gw")]

    if cmd_file:
        if commands:
            print("Providing commands on the command line and in the import file is a strange thing to do.")
            raise typer.Exit(1)
        else:
            commands = [line.rstrip() for line in cmd_file.read_text().splitlines()]

    if not commands:
        print("Error No commands provided")
        raise typer.Exit(1)

    if yes or typer.confirm("\nProceed?", abort=True):
        caasapi = caas.CaasAPI(central=cli.central)
        _reqs = [
            cli.central.BatchRequest(
                caasapi.send_commands,
                n.name if not n.is_dev else n.mac,
                cli_cmds=commands
            )
            for n in utils.listify(nodes)
        ]
        batch_res = cli.central.batch_request(_reqs)
        cli.display_results(batch_res, cleaner=cleaner.parse_caas_response)
Beispiel #23
0
def method(
    method: str = typer.Argument(..., autocompletion=cli.cache.method_test_completion),
    kwargs: List[str] = typer.Argument(None),
    _help: bool = typer.Option(False, "--doc", help="Get details on required args/keyword args for provided method."),
    do_json: bool = typer.Option(False, "--json", is_flag=True, help="Output in JSON", show_default=False),
    do_yaml: bool = typer.Option(False, "--yaml", is_flag=True, help="Output in YAML", show_default=False),
    do_csv: bool = typer.Option(False, "--csv", is_flag=True, help="Output in CSV", show_default=False),
    do_table: bool = typer.Option(False, "--table", is_flag=True, help="Output in Table", show_default=False),
    outfile: Path = typer.Option(None, help="Output to file (and terminal)", writable=True),
    pager: bool = typer.Option(False, help="Enable Paged Output"),
    update_cache: bool = typer.Option(False, "-U", hidden=True),  # Force Update of cache for testing
    default: bool = typer.Option(False, "-d", is_flag=True, help="Use default central account", show_default=False,
                                 callback=cli.default_callback),
    debug: bool = typer.Option(False, "--debug", envvar="ARUBACLI_DEBUG", help="Enable Debug Logging",
                               callback=cli.debug_callback),
    debugv: bool = typer.Option(
        False, "--debugv",
        envvar="ARUBACLI_VERBOSE_DEBUG",
        help="Enable verbose Debug Logging",
        hidden=True,
        callback=cli.verbose_debug_callback,
    ),
    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:
    """Dev testing commands to run CentralApi methods from command line.

    Refer to central.py and the schema generated code in the boilerplate dir
    for available calls.  Tab completion will also return available methods.
    Use --doc to get details on arguments for the provided method.

    Args:
        method (str, optional): CentralAPI method to test.
        kwargs (List[str], optional): list of args kwargs to pass to function.

    format: arg1 arg2 keyword=value keyword2=value
        or  arg1, arg2, keyword = value, keyword2=value

    Examples: cencli test method platform_get_devices all_ap

        Use --doc flag to see documentation for usage of a method.
        cencli test method platform_get_devices --doc

    Displays all attributes of Response object
    """
    # FIXME account only works if method is in central.py
    central = CentralApi(account)
    cli.cache(refresh=update_cache)
    if not hasattr(central, method):
        if account != "central_info":
            print("Testing methods only supports the --account option for methods in central.py")
            raise typer.Exit(1)
        bpdir = Path(__file__).parent / "boilerplate"
        all_calls = [
            importlib.import_module(f"centralcli.{bpdir.name}.{f.stem}") for f in bpdir.iterdir()
            if not f.name.startswith("_") and f.suffix == ".py"
        ]
        for m in all_calls:
            log.debug(f"Looking for {method} in {m.__file__.split('/')[-1]}")
            if hasattr(m.AllCalls(), method):
                central = m.AllCalls()
                break

    if not hasattr(central, method):
        typer.secho(f"{method} does not exist", fg="red")
        raise typer.Exit(1)

    if _help:
        old_ret = "Response: CentralAPI Response object"
        new_ret = "Response from Aruba Central API gateway."
        print(getattr(central, method).__doc__.replace(old_ret, new_ret))
        raise typer.Exit(0)

    kwargs = (
        "~".join(kwargs).replace("'", "").replace('"', '').replace("~=", "=").replace("=~", "=").replace(",~", "~").split("~")
    )
    args = [k if not k.isdigit() else int(k) for k in kwargs if k and "=" not in k]
    kwargs = [k.split("=") for k in kwargs if "=" in k]
    kwargs = {k[0]: k[1] if not k[1].isdigit() else int(k[1]) for k in kwargs}
    for arg in args:
        if isinstance(arg, str):
            if arg.startswith("[") and arg.endswith("]"):
                args[args.index(arg)] = [a if not a.isdigit() else int(a) for a in arg.strip("[]").split(",")]
    for k, v in kwargs.items():
        if isinstance(v, str):
            if v.startswith("[") and v.endswith("]"):
                kwargs[k] = [vv if not vv.isdigit() else int(vv) for vv in v.strip("[]").split(",")]
            if v.lower() in ["true", "false"]:
                kwargs[k] = True if v.lower() == "true" else False

    from rich.console import Console
    c = Console(file=outfile)

    req = (
        f"central.{method}({', '.join(str(a) for a in args)}{', ' if args else ''}"
        f"{', '.join([f'{k}={kwargs[k]}' for k in kwargs]) if kwargs else ''})"
    )

    resp = central.request(getattr(central, method), *args, **kwargs)
    if "should be str" in resp.output and "bool" in resp.output:
        c.log(f"{resp.output}.  LAME!  Converting to str!")
        args = tuple([str(a).lower() if isinstance(a, bool) else a for a in args])
        kwargs = {k: str(v).lower() if isinstance(v, bool) else v for k, v in kwargs.items()}
        resp = central.request(getattr(central, method), *args, **kwargs)

    attrs = {
        k: v for k, v in resp.__dict__.items() if k not in ["output", "raw"] and (log.DEBUG or not k.startswith("_"))
    }

    c.print(req)
    c.print("\n".join([f"  {k}: {v}" for k, v in attrs.items()]))

    tablefmt = cli.get_format(
        do_json, do_yaml, do_csv, do_table, default="yaml"
    )

    if resp.raw and resp.output != resp.raw:
        typer.echo(f"\n{typer.style('CentralCLI Response Output', fg='bright_green')}:")
        cli.display_results(data=resp.output, tablefmt=tablefmt, pager=pager, outfile=outfile)
    if resp.raw:
        typer.echo(f"\n{typer.style('Raw Response Output', fg='bright_green')}:")
        cli.display_results(data=resp.raw, tablefmt="json", pager=pager, outfile=outfile)
Beispiel #24
0
def compliance(
    device_type: ShowFirmwareDevType = typer.Argument(
        ...,
        metavar="[ap|gw|switch]",
    ),
    _group: List[str] = typer.Argument(
        None,
        metavar="[GROUP-NAME]",
        autocompletion=cli.cache.group_completion),
    group_name: str = typer.Option(None,
                                   "--group",
                                   help="Filter by group",
                                   autocompletion=cli.cache.group_completion),
    do_json: bool = typer.Option(False,
                                 "--json",
                                 is_flag=True,
                                 help="Output in JSON"),
    do_yaml: bool = typer.Option(False,
                                 "--yaml",
                                 is_flag=True,
                                 help="Output in YAML"),
    do_csv: bool = typer.Option(False,
                                "--csv",
                                is_flag=True,
                                help="Output in CSV"),
    do_table: bool = typer.Option(
        False,
        "--table",
        help="Output in table format",
    ),
    outfile: Path = typer.Option(None,
                                 "--out",
                                 help="Output to file (and terminal)",
                                 writable=True),
    pager: bool = typer.Option(False, help="Enable Paged Output"),
    update_cache: bool = typer.Option(
        False, "-U", hidden=True),  # Force Update of cli.cache for testing
    default: bool = typer.Option(
        False,
        "-d",
        is_flag=True,
        help="Use default central account",
        show_default=False,
    ),
    debug: bool = typer.Option(
        False,
        "--debug",
        envvar="ARUBACLI_DEBUG",
        help="Enable Additional Debug Logging",
    ),
    account: str = typer.Option(
        "central_info",
        envvar="ARUBACLI_ACCOUNT",
        help="The Aruba Central Account to use (must be defined in the config)",
    ),
):
    central = cli.central
    cli.cache(refresh=update_cache)
    _type_to_name = {
        "AP": "IAP",
        "GATEWAY": "CONTROLLER",
        "GW": "CONTROLLER",
        "SWITCH": "HP"
    }
    # Allows both:
    # show firmware compliance <dev-type> <group iden>
    # show firmware compliance <dev-type> group <group iden>
    if len(_group) > 2:
        typer.echo(
            f"Unknown extra arguments in {[x for x in list(_group)[0:-1] if x.lower() != 'group']}"
        )
        raise typer.Exit(1)
    _group = None if not _group else _group[-1]
    group = _group or group_name
    if group:
        group = cli.cache.get_group_identifier(group).name

    # TODO make device_type optional add 'all' keyword and implied 'all' if no device_type
    #      add macro method to get compliance for all device_types.
    kwargs = {
        'device_type': _type_to_name.get(device_type.upper(), device_type),
        'group': group
    }

    resp = central.request(central.get_firmware_compliance, **kwargs)
    if resp.status == 404 and resp.output.lower() == "not found":
        resp.output = (
            f"Invalid URL or No compliance set for {device_type.lower()} "
            f"{'Globally' if not group else f'in group {group}'}")
        typer.echo(str(resp).replace("404", typer.style("404", fg="red")))
    else:
        tablefmt = cli.get_format(do_json=do_json,
                                  do_yaml=do_yaml,
                                  do_csv=do_csv,
                                  do_table=do_table)

        cli.display_results(
            resp,
            tablefmt=tablefmt,
            title=
            f"{'Global ' if not group else f'{group} '}Firmware Compliance",
            pager=pager,
            outfile=outfile)
Beispiel #25
0
def rename(
    what: BatchArgs = typer.Argument(..., ),
    import_file: Path = typer.Argument(None, exists=True),
    lldp: bool = typer.Option(
        None,
        help="Automatic AP rename based on lldp info from upstream switch.",
    ),
    yes: bool = typer.Option(False,
                             "-Y",
                             help="Bypass confirmation prompts - Assume Yes"),
    yes_: bool = typer.Option(False, "-y", hidden=True),
    default: bool = typer.Option(False,
                                 "-d",
                                 is_flag=True,
                                 help="Use default central account",
                                 callback=cli.default_callback),
    debug: bool = typer.Option(False,
                               "--debug",
                               envvar="ARUBACLI_DEBUG",
                               help="Enable Additional Debug Logging",
                               callback=cli.debug_callback),
    account: str = typer.Option(
        "central_info",
        envvar="ARUBACLI_ACCOUNT",
        help="The Aruba Central Account to use (must be defined in the config)",
        callback=cli.account_name_callback,
    ),
) -> None:
    """Perform AP rename in batch from import file or automatically based on LLDP"""
    central = cli.central
    if import_file:
        data = config.get_file_data(import_file)

        resp = None
        if what == "aps":
            if import_file.suffix in [".csv", ".tsv", ".dbf", ".xls", ".xlsx"]:
                if data and len(data.headers) < 3:
                    if "name" in data.headers:
                        data = [{
                            k if k != "name" else "hostname": d[k]
                            for k in d
                        } for d in data.dict]
                        data.headers["hostname"] = data.headers.pop(
                            data.headers.index(data.headers["name"]))
                    data = {
                        i.get(
                            "serial",
                            i.get("serial_number",
                                  i.get("serial_num", "ERROR"))): {
                                      k: v
                                      for k, v in i.items()
                                      if not k.startswith("serial")
                                  }
                        for i in data.dict
                    }
            calls = []
            for ap in data:
                calls.append(
                    central.BatchRequest(central.update_ap_settings, (ap, ),
                                         data[ap]))

            resp = central.batch_request(calls)

    elif lldp:
        rtxt = typer.style("RESULT: ", fg=typer.colors.BRIGHT_BLUE)
        typer.secho("Rename APs based on LLDP:", fg="bright_green")
        typer.echo(
            "This function will automatically rename APs based on a combination of\n"
            "information from the upstream switch (via LLDP) and from the AP itself.\n\n"
            "Please provide a format string based on these examples:\n"
            "  For the examples: hostname 'SNANTX-IDF3-sw1, AP on port 7\n"
            "                    AP mac aa:bb:cc:dd:ee:ff\n"
            f"{typer.style('Format String Examples:', fg='cyan')}\n"
            "  Upstream switches hostname: \n"
            "      '%h[1:4]%'    will use the first 3 characters of the switches hostname.\n"
            f"         {rtxt} 'SNAN'\n"
            "      '%H-1%'    will split the hostname into parts separating on '-' and use\n"
            f"         the firt segment.  {rtxt} 'SNANTX\n"
            f"      '%p%'    represents the interface.  {rtxt} '7'\n"
            "                   note: an interface in the form 1/1/12 is converted to 1_1_12\n"
            "       '%p/3%    seperates the port string on / and uses the 3rd segment.\n"
            "        '%m% or %m[-4] = last 4 digits of the AP MAC\n"
            "        '%m:1% would split on : and take the 1st segment.\n")
        fstr = typer.prompt("Enter Desired format string:")
        do_lldp_rename(fstr)
    else:
        typer.secho(
            "import file Argument is required if --lldp flag not provided",
            fg="red")

    cli.display_results(resp)
def device(
    device: str = typer.Argument(
        ...,
        metavar="Device: [serial #|name|ip address|mac address]",
        autocompletion=cli.cache.dev_completion,
    ),
    version: str = typer.Argument(
        None,
        help="Version to upgrade to [Default: recommended version]",
        show_default=False,
        autocompletion=lambda incomplete: [
            m for m in [("<firmware version>",
                         "The version of firmware to upgrade to."),
                        *[m for m in cli.cache.null_completion(incomplete)]]
        ],
    ),
    at: datetime = typer.Option(
        None,
        help=
        "When to schedule upgrade. format: 'mm/dd/yyyy_hh:mm' or 'dd_hh:mm' (implies current month) [Default: Now]",
        show_default=False,
        formats=["%m/%d/%Y_%H:%M", "%d_%H:%M"],
    ),
    reboot: bool = typer.Option(
        False,
        "-R",
        help=
        "Automatically reboot device after firmware download (APs will reboot regardless)"
    ),
    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)",
    ),
) -> None:
    yes = yes_ if yes_ else yes
    dev = cli.cache.get_dev_identifier(device)
    if dev.generic_type == "ap":
        reboot = True
    at = None if not at else int(round(at.timestamp()))

    ver_msg = "Recommended version" if not version else version
    ver_msg = f"{ver_msg} and reboot" if reboot else f"{ver_msg} ('-R' not specified, device will not be rebooted)"

    if yes or typer.confirm(
            typer.style(
                f"Upgrade {dev.name} to {ver_msg}?",
                fg="bright_green",
            ),
            abort=True,
    ):
        resp = cli.central.request(cli.central.upgrade_firmware,
                                   scheduled_at=at,
                                   serial=dev.serial,
                                   firmware_version=version,
                                   reboot=reboot)
        cli.display_results(resp, tablefmt="action")
def rogues(
    start: str = typer.Option(None, help="Start time of range to collect logs, format: yyyy-mm-ddThh:mm (24 hour notation)",),
    end: str = typer.Option(None, help="End time of range to collect logs, formnat: yyyy-mm-ddThh:mm (24 hour notation)",),
    past: str = typer.Option(None, help="Collect Logs for last <past>, d=days, h=hours, m=mins i.e.: 3h"),
    group: str = typer.Argument(None, metavar="[GROUP-NAME]", autocompletion=cli.cache.group_completion),
    label: str = typer.Argument(None, metavar="[LABEL]",),  # autocompletion=cli.cache.group_completion),  # TODO cache labels
    site: str = typer.Argument(None, metavar="[SITE-NAME]", autocompletion=cli.cache.site_completion),
    # swarm: str = typer.Argument(None, metavar="[SWARM NAME or ID]", autocompletion=cli.cache.swarm_completion),  # TODO
    sort_by: str = typer.Option(None, "--sort",),  # Uses post formatting field headers
    reverse: bool = typer.Option(
        True, "-r",
        help="Reverse Output order.",
        show_default=False
    ),
    verbose: bool = typer.Option(False, "-v", help="Show raw unformatted response (vertically)"),
    do_json: bool = typer.Option(False, "--json", is_flag=True, help="Output in JSON"),
    do_yaml: bool = typer.Option(False, "--yaml", is_flag=True, help="Output in YAML"),
    do_csv: bool = typer.Option(False, "--csv", is_flag=True, help="Output in CSV"),
    do_table: bool = typer.Option(False, "--table", help="Output in table format",),
    outfile: Path = typer.Option(None, "--out", help="Output to file (and terminal)", writable=True),
    pager: bool = typer.Option(False, "--pager", help="Enable Paged Output"),
    update_cache: bool = typer.Option(False, "-U", hidden=True),  # Force Update of cli.cache for testing
    default: bool = typer.Option(False, "-d", is_flag=True, help="Use default central account", show_default=False,),
    debug: bool = typer.Option(False, "--debug", envvar="ARUBACLI_DEBUG", help="Enable Additional Debug Logging",),
    account: str = typer.Option("central_info",
                                envvar="ARUBACLI_ACCOUNT",
                                help="The Aruba Central Account to use (must be defined in the config)",),
):
    central = cli.central
    cli.cache(refresh=update_cache)

    if group:
        group = cli.cache.get_group_identifier(group).name

    if start:
        try:
            dt = pendulum.from_format(start, 'YYYY-MM-DDTHH:mm')
            start = (dt.int_timestamp)
        except Exception:
            typer.secho(f"start appears to be invalid {start}", fg="red")
            raise typer.Exit(1)
    if end:
        try:
            dt = pendulum.from_format(end, 'YYYY-MM-DDTHH:mm')
            end = (dt.int_timestamp)
        except Exception:
            typer.secho(f"end appears to be invalid {start}", fg="red")
            raise typer.Exit(1)
    if past:
        now = int(time.time())
        past = past.lower().replace(" ", "")
        if past.endswith("d"):
            start = now - (int(past.rstrip("d")) * 86400)
        if past.endswith("h"):
            start = now - (int(past.rstrip("h")) * 3600)
        if past.endswith("m"):
            start = now - (int(past.rstrip("m")) * 60)

    kwargs = {
        "from_timestamp": start,
        "to_timestamp": end,
        "group": group,
        "label": label,
        "site": site,
        # "swarm_id": swarm, TODO
    }

    resp = central.request(central.wids_get_rogue_aps, **kwargs)
    tablefmt = cli.get_format(do_json, do_yaml, do_csv, do_table, default="rich" if not verbose else "yaml")

    cli.display_results(
        resp,
        tablefmt=tablefmt,
        title="Rogues",
        pager=pager,
        outfile=outfile,
        sort_by=sort_by if not sort_by else sort_by.replace("_", " "),
        reverse=reverse,
        # cleaner=cleaner.get_audit_logs if not verbose else None,
        # cache_update_func=cli.cache.update_log_db if not verbose else None,
        # caption=f"Use {_cmd_txt} to see details for a log.  Logs lacking an id don\'t have details.",
    )
Beispiel #28
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)
Beispiel #29
0
def group(
    clone_group: str = typer.Argument(
        ...,
        metavar="[NAME OF GROUP TO CLONE]",
        autocompletion=cli.cache.group_completion),
    new_group: str = typer.Argument(..., metavar="[NAME OF GROUP TO CREATE]"),
    aos10: bool = typer.Option(None,
                               "--aos10",
                               help="Upgrade new cloned group to AOS10"),
    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",
                               callback=cli.debug_callback),
    default: bool = typer.Option(False,
                                 "-d",
                                 is_flag=True,
                                 help="Use default central account",
                                 show_default=False,
                                 callback=cli.default_callback),
    account: str = typer.Option(
        "central_info",
        envvar="ARUBACLI_ACCOUNT",
        help="The Aruba Central Account to use (must be defined in the config)",
        callback=cli.account_name_callback),
) -> None:
    yes = yes_ if yes_ else yes

    print(f"Clone group: {color(clone_group)} to new group {color(new_group)}")
    if aos10:
        print(f"    Upgrade cloned group to AOS10: {color(True)}")
        print(
            "\n    [dark_orange]WARNING[/dark_orange]: [italic]Upgrade doesn't always work despite "
            f"returning {color('success')},\n    Group is cloned if {color('success')} is returned "
            "but upgrade to AOS10 may not occur.\n    API method appears to have some caveats."
        )

    if yes or typer.confirm("\nProceed?"):
        resp = cli.central.request(cli.central.clone_group, clone_group,
                                   new_group)
        if resp:
            groups = cli.cache.groups
            if groups:
                groups = {
                    g["name"]: {
                        "template group": g["template group"]
                    }
                    for g in groups
                }
                # TODO put non async it async wrapper in cache.py
                asyncio.run(
                    cli.cache.update_group_db({
                        "name": new_group,
                        **groups[clone_group]
                    }))

        cli.display_results(resp, tablefmt="action")
Beispiel #30
0
def batch_add_groups(import_file: Path, yes: bool = False) -> List[Response]:
    console = Console(emoji=False)
    br = cli.central.BatchRequest
    data = config.get_file_data(import_file)
    # TODO handle csv
    if isinstance(data, dict) and "groups" in data:
        data = data["groups"]
    reqs, gw_reqs, ap_reqs = [], [], []
    pre_cfgs = []
    _pre_config_msg = ""
    cache_data = []
    for group in data:
        if "allowed-types" in data[group]:
            data[group]["allowed_types"] = data[group]["allowed-types"]
            del data[group]["allowed-types"]

        try:
            g = GroupImport(**{"group": group, **data[group]})
        except ValidationError as e:
            print(e)
            raise typer.Exit(1)
        reqs += [
            br(
                cli.central.create_group,
                g.group,
                allowed_types=g.allowed_types,
                wired_tg=g.wired_tg,
                wlan_tg=g.wlan_tg,
                aos10=g.aos10,
                microbranch=g.microbranch,
                gw_role=g.gw_role,
                monitor_only_sw=g.monitor_only_sw,
                monitor_only_cx=g.monitor_only_cx,
            )
        ]
        cache_data += [
            {"name": g.group, "template group": {"Wired": g.wired_tg, "Wireless": g.wlan_tg}}
        ]
        for dev_type, cfg_file, var_file in zip(["gw", "ap"], [g.gw_config, g.ap_config], [g.gw_vars, g.ap_vars]):
            if cfg_file is not None:
                pc = _build_pre_config(g.group, dev_type=dev_type, cfg_file=cfg_file, var_file=var_file)
                pre_cfgs += [pc]
                _pre_config_msg += (
                    f"  [bright_green]{len(pre_cfgs)}[/]. [cyan]{g.group}[/] {'gateways' if dev_type == 'gw' else 'AP'} "
                    f"group level will be configured based on [cyan]{cfg_file.name}[/]\n"
                )
                if dev_type == "gw":
                    gw_reqs += [pc.request]
                else:
                    ap_reqs += [pc.request]

    print(f"[bright_green]The following groups will be created:[/]")
    _ = [print(f"  [cyan]{g}[/]") for g in data]

    _pre_config_msg = (
        "\n[bright_green]Group level configurations will be sent:[/]\n"
        f"{_pre_config_msg}"
        f"\n[italic dark_olive_green2]{len(reqs) + len(gw_reqs) + len(ap_reqs)} API calls will be performed.[/]\n"
    )
    print(_pre_config_msg)
    for idx in range(len(pre_cfgs) + 1):
        if idx > 0:
            print(_pre_config_msg)
        print(f"Select [bright_green]#[/] to display config to be sent or [bright_green]go[/] to continue.")
        ch = utils.ask(
            ">",
            console=console,
            choices=[*[str(idx) for idx in range(1, len(pre_cfgs) + 1)], "abort", "go"],
        )
        if ch.lower() == "go":
            yes = True
            break
        else:
            pc: PreConfig = pre_cfgs[int(ch) - 1]
            console.rule(f"Config to be sent to {pc.name}")
            with console.pager():
                console.print(pc.config)
            console.rule(f" End {pc.name} config ")

    if reqs and yes or typer.confirm("Proceed?", abort=True):
        resp = cli.central.batch_request(reqs)
        if all(r.ok for r in resp):
            cache_resp = asyncio.run(cli.cache.update_group_db(cache_data))
            log.debug(f"batch add group cache resp: {cache_resp}")
        cli.display_results(resp)
        if gw_reqs:
            print("\n[bright_green]Results from Group level gateway config push (CLI commands)[/]")
            print("\n  [italic]This can take some time.[/]")
            resp = cli.central.batch_request(gw_reqs)
            cli.display_results(resp, cleaner=cleaner.parse_caas_response)
        if ap_reqs:
            print("\n[bright_green]Results from Group level AP config push (Replaces entire group level)[/]\n")
            resp = cli.central.batch_request(ap_reqs)
            cli.display_results(resp,  tablefmt="action")