import argparse
import logging
import traceback
from typing import List
from sys import exit, platform
from os import getcwd

import click

from cli.echo import Echo
from common import definitions
from common.auxiliary import Windows, MacOS, Linux
from common.errors import BaseError
from common.file_utils import get_kubeconfig_path, clear_kubeconfig
from vm_providers.factory import get_provider_for
from vm_providers.errors import ProviderNotFound, ProviderInstanceNotFoundError

logger = logging.getLogger(__name__)


@click.command(
    name="microk8s",
    context_settings=dict(
        ignore_unknown_options=True,
        allow_extra_args=True,
    ),
)
@click.option("-h", "--help", is_flag=True)
@click.pass_context
def cli(ctx, help):
    try:
        if help and len(ctx.args) == 0:
            show_help()
            exit(0)
        elif help:
            ctx.args.append("--help")

        if len(ctx.args) == 0:
            show_error()
            exit(1)
        if ctx.args[0] == "install":
            install(ctx.args[1:])
            exit(0)
        elif ctx.args[0] == "uninstall":
            uninstall()
            exit(0)
        elif ctx.args[0] == "start":
            start()
            run(ctx.args)
            exit(0)
        elif ctx.args[0] == "stop":
            run(ctx.args)
            stop()
            exit(0)
        elif ctx.args[0] == "kubectl":
            exit(kubectl(ctx.args[1:]))
        elif ctx.args[0] == "dashboard-proxy":
            dashboard_proxy()
            exit(0)
        elif ctx.args[0] == "inspect":
            inspect()
            exit(0)
        else:
            run(ctx.args)
            exit(0)

    except BaseError as e:
        Echo.error(str(e))
        exit(e.get_exit_code())
    except Exception as e:
        Echo.error("An unexpected error occurred.")
        Echo.info(str(e))
        Echo.info(traceback.print_exc())
        exit(254)


def show_error():
    msg = """Usage: microk8s [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Shows the available COMMANDS."""
    click.echo(msg)


def show_help():
    msg = """Usage: microk8s [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  install         Installs MicroK8s. Use --cpu, --mem, --disk and --channel to configure your setup.
  uninstall       Removes MicroK8s"""
    click.echo(msg)
    commands = _get_microk8s_commands()
    for command in commands:
        if command in definitions.command_descriptions:
            click.echo("  {:<15} {}".format(command, definitions.command_descriptions[command]))
        else:
            click.echo("  {:<15}".format(command))
    if len(commands) == 2:
        click.echo("")
        click.echo("Install and start MicroK8s to see the full list of commands.")


def _show_install_help():
    msg = f"""Usage: microk8s install OPTIONS

    Options:
      --help     Show this message and exit.
      --cpu      Cores used by MicroK8s (default={definitions.DEFAULT_CORES}, min={definitions.MIN_CORES})
      --mem      RAM in GB used by MicroK8s (default={definitions.DEFAULT_MEMORY_GB}, min={definitions.MIN_MEMORY_GB})
      --disk     Max volume in GB of the dynamically expandable hard disk to be used (default={definitions.DEFAULT_DISK_GB}, min={definitions.MIN_DISK_GB})
      --channel  Kubernetes version to install (default={definitions.DEFAULT_CHANNEL})
      -y, --assume-yes  Automatic yes to prompts"""  # noqa
    Echo.info(msg)


def memory(mem_gb: str) -> int:
    """
    Validates the value in --mem parameter of the install command.
    """
    mem_gb = int(mem_gb)
    if mem_gb < definitions.MIN_MEMORY_GB:
        raise ValueError("Out of valid memory range")
    return mem_gb


def cpu(cpus: str) -> int:
    """
    Validates the value in --cpu parameter of the install command.
    """
    cpus = int(cpus)
    if cpus < definitions.MIN_CORES:
        raise ValueError("Invalid number of cpus")
    return cpus


def disk(disk_gb: str) -> int:
    """
    Validates the value in --disk parameter of the install command.
    """
    disk_gb = int(disk_gb)
    if disk_gb < definitions.MIN_DISK_GB:
        raise ValueError("Out of valid disk range")
    return disk_gb


def install(args) -> None:
    if "--help" in args or "-h" in args:
        _show_install_help()
        return

    parser = argparse.ArgumentParser("microk8s install")
    parser.add_argument("--cpu", default=definitions.DEFAULT_CORES, type=cpu)
    parser.add_argument("--mem", default=definitions.DEFAULT_MEMORY_GB, type=memory)
    parser.add_argument("--disk", default=definitions.DEFAULT_DISK_GB, type=disk)
    parser.add_argument("--channel", default=definitions.DEFAULT_CHANNEL, type=str)
    parser.add_argument(
        "-y", "--assume-yes", action="store_true", default=definitions.DEFAULT_ASSUME
    )
    args = parser.parse_args(args)

    echo = Echo()

    if platform == "win32":
        host = Windows(args)
    elif platform == "darwin":
        host = MacOS(args)
    else:
        host = Linux(args)

    if not host.has_enough_cpus():
        echo.error("VM cpus requested exceed number of available cores on host.")
        exit(1)
    if not host.has_enough_memory():
        echo.warning("VM memory requested exceeds the total memory on host.")
        exit(1)
    if not host.has_enough_disk_space():
        echo.warning("VM disk size requested exceeds free space on host.")

    vm_provider_name: str = "multipass"
    vm_provider_class = get_provider_for(vm_provider_name)
    try:
        vm_provider_class.ensure_provider()
    except ProviderNotFound as provider_error:
        if provider_error.prompt_installable:
            if args.assume_yes or (
                echo.is_tty_connected()
                and echo.confirm(
                    "Support for {!r} needs to be set up. "
                    "Would you like to do that now?".format(provider_error.provider)
                )
            ):
                vm_provider_class.setup_provider(echoer=echo)
            else:
                raise provider_error
        else:
            raise provider_error

    instance = vm_provider_class(echoer=echo)
    spec = vars(args)
    spec.update({"kubeconfig": get_kubeconfig_path()})
    instance.launch_instance(spec)
    echo.info("MicroK8s is up and running. See the available commands with `microk8s --help`.")


def uninstall() -> None:
    vm_provider_name = "multipass"
    vm_provider_class = get_provider_for(vm_provider_name)
    echo = Echo()
    try:
        vm_provider_class.ensure_provider()
    except ProviderNotFound as provider_error:
        if provider_error.prompt_installable:
            if echo.is_tty_connected():
                echo.warning(
                    (
                        "MicroK8s is not running. VM provider {!r} has been removed.".format(
                            provider_error.provider
                        )
                    )
                )
            return 1
        else:
            raise provider_error

    instance = vm_provider_class(echoer=echo)
    instance.destroy()
    clear_kubeconfig()
    echo.info("Thank you for using MicroK8s!")


def kubectl(args) -> int:
    if platform == "win32":
        return Windows(args).kubectl()
    if platform == "darwin":
        return MacOS(args).kubectl()
    else:
        return Linux(args).kubectl()


def inspect() -> None:
    vm_provider_name = "multipass"
    vm_provider_class = get_provider_for(vm_provider_name)
    echo = Echo()
    try:
        vm_provider_class.ensure_provider()
        instance = vm_provider_class(echoer=echo)
        instance.get_instance_info()

        command = ["microk8s.inspect"]
        output = instance.run(command, hide_output=True)
        tarball_location = None
        host_destination = getcwd()
        if b"Report tarball is at" not in output:
            echo.error("Report tarball not generated")
        else:
            for line_out in output.split(b"\n"):
                line_out = line_out.decode()
                line = line_out.strip()
                if line.startswith("Report tarball is at "):
                    tarball_location = line.split("Report tarball is at ")[1]
                    break
                echo.wrapped(line_out)
            if not tarball_location:
                echo.error("Cannot find tarball file location")
            else:
                instance.pull_file(name=tarball_location, destination=host_destination)
                echo.wrapped(
                    "The report tarball {} is stored on the current directory".format(
                        tarball_location.split("/")[-1]
                    )
                )

    except ProviderInstanceNotFoundError:
        _not_installed(echo)
        return 1


def dashboard_proxy() -> None:
    vm_provider_name = "multipass"
    vm_provider_class = get_provider_for(vm_provider_name)
    echo = Echo()
    try:
        vm_provider_class.ensure_provider()
        instance = vm_provider_class(echoer=echo)
        instance.get_instance_info()

        echo.info("Checking if Dashboard is running.")
        command = ["microk8s.enable", "dashboard"]
        output = instance.run(command, hide_output=True)
        if b"Addon dashboard is already enabled." not in output:
            echo.info("Waiting for Dashboard to come up.")
            command = [
                "microk8s.kubectl",
                "-n",
                "kube-system",
                "wait",
                "--timeout=240s",
                "deployment",
                "kubernetes-dashboard",
                "--for",
                "condition=available",
            ]
            instance.run(command, hide_output=True)

        command = ["microk8s.kubectl", "-n", "kube-system", "get", "secret"]
        output = instance.run(command, hide_output=True)
        secret_name = None
        for line in output.split(b"\n"):
            if line.startswith(b"default-token"):
                secret_name = line.split()[0].decode()
                break

        if not secret_name:
            echo.error("Cannot find the dashboard secret.")

        command = ["microk8s.kubectl", "-n", "kube-system", "describe", "secret", secret_name]
        output = instance.run(command, hide_output=True)
        token = None
        for line in output.split(b"\n"):
            if line.startswith(b"token:"):
                token = line.split()[1].decode()

        if not token:
            echo.error("Cannot find token from secret.")

        ip = instance.get_instance_info().ipv4[0]

        echo.info("Dashboard will be available at https://{}:10443".format(ip))
        echo.info("Use the following token to login:")
        echo.info(token)

        command = [
            "microk8s.kubectl",
            "port-forward",
            "-n",
            "kube-system",
            "service/kubernetes-dashboard",
            "10443:443",
            "--address",
            "0.0.0.0",
        ]

        try:
            instance.run(command)
        except KeyboardInterrupt:
            return
    except ProviderInstanceNotFoundError:
        _not_installed(echo)
        return 1


def start() -> None:
    vm_provider_name = "multipass"
    vm_provider_class = get_provider_for(vm_provider_name)
    vm_provider_class.ensure_provider()
    instance = vm_provider_class(echoer=Echo())
    instance_info = instance.get_instance_info()
    if not instance_info.is_running():
        instance.start()
        instance.run(["microk8s.start"])


def stop() -> None:
    vm_provider_name = "multipass"
    vm_provider_class = get_provider_for(vm_provider_name)
    vm_provider_class.ensure_provider()
    instance = vm_provider_class(echoer=Echo())
    instance_info = instance.get_instance_info()
    if instance_info.is_running():
        instance.stop()


def run(cmd) -> None:
    vm_provider_name = "multipass"
    vm_provider_class = get_provider_for(vm_provider_name)
    echo = Echo()
    try:
        vm_provider_class.ensure_provider()
        instance = vm_provider_class(echoer=echo)
        instance_info = instance.get_instance_info()
        if not instance_info.is_running():
            echo.warning("MicroK8s is not running. Please run `microk8s start`.")
            return 1
        command = cmd[0]
        cmd[0] = "microk8s.{}".format(command)
        instance.run(cmd)
    except ProviderInstanceNotFoundError:
        _not_installed(echo)
        return 1


def _not_installed(echo) -> None:
    if echo.is_tty_connected():
        echo.warning("MicroK8s is not installed. Please run `microk8s install`.")


def _get_microk8s_commands() -> List:
    vm_provider_name = "multipass"
    vm_provider_class = get_provider_for(vm_provider_name)
    echo = Echo()
    try:
        vm_provider_class.ensure_provider()
        instance = vm_provider_class(echoer=echo)
        instance_info = instance.get_instance_info()
        if instance_info.is_running():
            commands = instance.run("ls -1 /snap/bin/".split(), hide_output=True)
            mk8s = [
                c.decode().replace("microk8s.", "")
                for c in commands.split()
                if c.decode().startswith("microk8s.")
            ]
            complete = mk8s
            if "dashboard-proxy" not in mk8s:
                complete += ["dashboard-proxy"]
            complete.sort()
            return complete
        else:
            return ["start", "stop"]
    except ProviderNotFound:
        return ["start", "stop"]


if __name__ == "__main__":
    cli()

installer/cli/microk8s.py
import argparse
import logging
import traceback
from typing import List
from sys import exit, platform
from os import getcwd

import click

from cli.echo import Echo
from common import definitions
from common.auxiliary import Windows, MacOS, Linux
from common.errors import BaseError
from common.file_utils import get_kubeconfig_path, clear_kubeconfig
from vm_providers.factory import get_provider_for
from vm_providers.errors import ProviderNotFound, ProviderInstanceNotFoundError

logger = logging.getLogger(__name__)


@click.command(
    name="microk8s",
    context_settings=dict(
        ignore_unknown_options=True,
        allow_extra_args=True,
    ),
)
@click.option("-h", "--help", is_flag=True)
@click.pass_context
def cli(ctx, help):
    try:
        if help and len(ctx.args) == 0:
            show_help()
            exit(0)
        elif help:
            ctx.args.append("--help")

        if len(ctx.args) == 0:
            show_error()
            exit(1)
        if ctx.args[0] == "install":
            install(ctx.args[1:])
            exit(0)
        elif ctx.args[0] == "uninstall":
            uninstall()
            exit(0)
        elif ctx.args[0] == "start":
            start()
            run(ctx.args)
            exit(0)
        elif ctx.args[0] == "stop":
            run(ctx.args)
            stop()
            exit(0)
        elif ctx.args[0] == "kubectl":
            exit(kubectl(ctx.args[1:]))
        elif ctx.args[0] == "dashboard-proxy":
            dashboard_proxy()
            exit(0)
        elif ctx.args[0] == "inspect":
            inspect()
            exit(0)
        else:
            run(ctx.args)
            exit(0)

    except BaseError as e:
        Echo.error(str(e))
        exit(e.get_exit_code())
    except Exception as e:
        Echo.error("An unexpected error occurred.")
        Echo.info(str(e))
        Echo.info(traceback.print_exc())
        exit(254)


def show_error():
    msg = """Usage: microk8s [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Shows the available COMMANDS."""
    click.echo(msg)


def show_help():
    msg = """Usage: microk8s [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  install         Installs MicroK8s. Use --cpu, --mem, --disk and --channel to configure your setup.
  uninstall       Removes MicroK8s"""
    click.echo(msg)
    commands = _get_microk8s_commands()
    for command in commands:
        if command in definitions.command_descriptions:
            click.echo("  {:<15} {}".format(command, definitions.command_descriptions[command]))
        else:
            click.echo("  {:<15}".format(command))
    if len(commands) == 2:
        click.echo("")
        click.echo("Install and start MicroK8s to see the full list of commands.")


def _show_install_help():
    msg = f"""Usage: microk8s install OPTIONS

    Options:
      --help     Show this message and exit.
      --cpu      Cores used by MicroK8s (default={definitions.DEFAULT_CORES}, min={definitions.MIN_CORES})
      --mem      RAM in GB used by MicroK8s (default={definitions.DEFAULT_MEMORY_GB}, min={definitions.MIN_MEMORY_GB})
      --disk     Max volume in GB of the dynamically expandable hard disk to be used (default={definitions.DEFAULT_DISK_GB}, min={definitions.MIN_DISK_GB})
      --channel  Kubernetes version to install (default={definitions.DEFAULT_CHANNEL})
      -y, --assume-yes  Automatic yes to prompts"""  # noqa
    Echo.info(msg)


def memory(mem_gb: str) -> int:
    """
    Validates the value in --mem parameter of the install command.
    """
    mem_gb = int(mem_gb)
    if mem_gb < definitions.MIN_MEMORY_GB:
        raise ValueError("Out of valid memory range")
    return mem_gb


def cpu(cpus: str) -> int:
    """
    Validates the value in --cpu parameter of the install command.
    """
    cpus = int(cpus)
    if cpus < definitions.MIN_CORES:
        raise ValueError("Invalid number of cpus")
    return cpus


def disk(disk_gb: str) -> int:
    """
    Validates the value in --disk parameter of the install command.
    """
    disk_gb = int(disk_gb)
    if disk_gb < definitions.MIN_DISK_GB:
        raise ValueError("Out of valid disk range")
    return disk_gb


def install(args) -> None:
    if "--help" in args or "-h" in args:
        _show_install_help()
        return

    parser = argparse.ArgumentParser("microk8s install")
    parser.add_argument("--cpu", default=definitions.DEFAULT_CORES, type=cpu)
    parser.add_argument("--mem", default=definitions.DEFAULT_MEMORY_GB, type=memory)
    parser.add_argument("--disk", default=definitions.DEFAULT_DISK_GB, type=disk)
    parser.add_argument("--channel", default=definitions.DEFAULT_CHANNEL, type=str)
    parser.add_argument(
        "-y", "--assume-yes", action="store_true", default=definitions.DEFAULT_ASSUME
    )
    args = parser.parse_args(args)

    echo = Echo()

    if platform == "win32":
        host = Windows(args)
    elif platform == "darwin":
        host = MacOS(args)
    else:
        host = Linux(args)

    if not host.has_enough_cpus():
        echo.error("VM cpus requested exceed number of available cores on host.")
        exit(1)
    if not host.has_enough_memory():
        echo.warning("VM memory requested exceeds the total memory on host.")
        exit(1)
    if not host.has_enough_disk_space():
        echo.warning("VM disk size requested exceeds free space on host.")

    vm_provider_name: str = "multipass"
    vm_provider_class = get_provider_for(vm_provider_name)
    try:
        vm_provider_class.ensure_provider()
    except ProviderNotFound as provider_error:
        if provider_error.prompt_installable:
            if args.assume_yes or (
                echo.is_tty_connected()
                and echo.confirm(
                    "Support for {!r} needs to be set up. "
                    "Would you like to do that now?".format(provider_error.provider)
                )
            ):
                vm_provider_class.setup_provider(echoer=echo)
            else:
                raise provider_error
        else:
            raise provider_error

    instance = vm_provider_class(echoer=echo)
    spec = vars(args)
    spec.update({"kubeconfig": get_kubeconfig_path()})
    instance.launch_instance(spec)
    echo.info("MicroK8s is up and running. See the available commands with `microk8s --help`.")


def uninstall() -> None:
    vm_provider_name = "multipass"
    vm_provider_class = get_provider_for(vm_provider_name)
    echo = Echo()
    try:
        vm_provider_class.ensure_provider()
    except ProviderNotFound as provider_error:
        if provider_error.prompt_installable:
            if echo.is_tty_connected():
                echo.warning(
                    (
                        "MicroK8s is not running. VM provider {!r} has been removed.".format(
                            provider_error.provider
                        )
                    )
                )
            return 1
        else:
            raise provider_error

    instance = vm_provider_class(echoer=echo)
    instance.destroy()
    clear_kubeconfig()
    echo.info("Thank you for using MicroK8s!")


def kubectl(args) -> int:
    if platform == "win32":
        return Windows(args).kubectl()
    if platform == "darwin":
        return MacOS(args).kubectl()
    else:
        return Linux(args).kubectl()


def inspect() -> None:
    vm_provider_name = "multipass"
    vm_provider_class = get_provider_for(vm_provider_name)
    echo = Echo()
    try:
        vm_provider_class.ensure_provider()
        instance = vm_provider_class(echoer=echo)
        instance.get_instance_info()

        command = ["microk8s.inspect"]
        output = instance.run(command, hide_output=True)
        tarball_location = None
        host_destination = getcwd()
        if b"Report tarball is at" not in output:
            echo.error("Report tarball not generated")
        else:
            for line_out in output.split(b"\n"):
                line_out = line_out.decode()
                line = line_out.strip()
                if line.startswith("Report tarball is at "):
                    tarball_location = line.split("Report tarball is at ")[1]
                    break
                echo.wrapped(line_out)
            if not tarball_location:
                echo.error("Cannot find tarball file location")
            else:
                instance.pull_file(name=tarball_location, destination=host_destination)
                echo.wrapped(
                    "The report tarball {} is stored on the current directory".format(
                        tarball_location.split("/")[-1]
                    )
                )

    except ProviderInstanceNotFoundError:
        _not_installed(echo)
        return 1


def dashboard_proxy() -> None:
    vm_provider_name = "multipass"
    vm_provider_class = get_provider_for(vm_provider_name)
    echo = Echo()
    try:
        vm_provider_class.ensure_provider()
        instance = vm_provider_class(echoer=echo)
        instance.get_instance_info()

        echo.info("Checking if Dashboard is running.")
        command = ["microk8s.enable", "dashboard"]
        output = instance.run(command, hide_output=True)
        if b"Addon dashboard is already enabled." not in output:
            echo.info("Waiting for Dashboard to come up.")
            command = [
                "microk8s.kubectl",
                "-n",
                "kube-system",
                "wait",
                "--timeout=240s",
                "deployment",
                "kubernetes-dashboard",
                "--for",
                "condition=available",
            ]
            instance.run(command, hide_output=True)

        command = ["microk8s.kubectl", "-n", "kube-system", "get", "secret"]
        output = instance.run(command, hide_output=True)
        secret_name = None
        for line in output.split(b"\n"):
            if line.startswith(b"microk8s-dashboard-token"):
                secret_name = line.split()[0].decode()
                break

        if not secret_name:
            echo.error("Cannot find the dashboard secret.")

        command = ["microk8s.kubectl", "-n", "kube-system", "describe", "secret", secret_name]
        output = instance.run(command, hide_output=True)
        token = None
        for line in output.split(b"\n"):
            if line.startswith(b"token:"):
                token = line.split()[1].decode()

        if not token:
            echo.error("Cannot find token from secret.")

        ip = instance.get_instance_info().ipv4[0]

        echo.info("Dashboard will be available at https://{}:10443".format(ip))
        echo.info("Use the following token to login:")
        echo.info(token)

        command = [
            "microk8s.kubectl",
            "port-forward",
            "-n",
            "kube-system",
            "service/kubernetes-dashboard",
            "10443:443",
            "--address",
            "0.0.0.0",
        ]

        try:
            instance.run(command)
        except KeyboardInterrupt:
            return
    except ProviderInstanceNotFoundError:
        _not_installed(echo)
        return 1


def start() -> None:
    vm_provider_name = "multipass"
    vm_provider_class = get_provider_for(vm_provider_name)
    vm_provider_class.ensure_provider()
    instance = vm_provider_class(echoer=Echo())
    instance_info = instance.get_instance_info()
    if not instance_info.is_running():
        instance.start()
        instance.run(["microk8s.start"])


def stop() -> None:
    vm_provider_name = "multipass"
    vm_provider_class = get_provider_for(vm_provider_name)
    vm_provider_class.ensure_provider()
    instance = vm_provider_class(echoer=Echo())
    instance_info = instance.get_instance_info()
    if instance_info.is_running():
        instance.stop()


def run(cmd) -> None:
    vm_provider_name = "multipass"
    vm_provider_class = get_provider_for(vm_provider_name)
    echo = Echo()
    try:
        vm_provider_class.ensure_provider()
        instance = vm_provider_class(echoer=echo)
        instance_info = instance.get_instance_info()
        if not instance_info.is_running():
            echo.warning("MicroK8s is not running. Please run `microk8s start`.")
            return 1
        command = cmd[0]
        cmd[0] = "microk8s.{}".format(command)
        instance.run(cmd)
    except ProviderInstanceNotFoundError:
        _not_installed(echo)
        return 1


def _not_installed(echo) -> None:
    if echo.is_tty_connected():
        echo.warning("MicroK8s is not installed. Please run `microk8s install`.")


def _get_microk8s_commands() -> List:
    vm_provider_name = "multipass"
    vm_provider_class = get_provider_for(vm_provider_name)
    echo = Echo()
    try:
        vm_provider_class.ensure_provider()
        instance = vm_provider_class(echoer=echo)
        instance_info = instance.get_instance_info()
        if instance_info.is_running():
            commands = instance.run("ls -1 /snap/bin/".split(), hide_output=True)
            mk8s = [
                c.decode().replace("microk8s.", "")
                for c in commands.split()
                if c.decode().startswith("microk8s.")
            ]
            complete = mk8s
            if "dashboard-proxy" not in mk8s:
                complete += ["dashboard-proxy"]
            complete.sort()
            return complete
        else:
            return ["start", "stop"]
    except ProviderNotFound:
        return ["start", "stop"]


if __name__ == "__main__":
    cli()

installer/cli/microk8s.py
diff --git a/installer/cli/microk8s.py b/installer/cli/microk8s.py
index f2db9b27e2..1f0b412f27 100755
--- a/installer/cli/microk8s.py
+++ b/installer/cli/microk8s.py
@@ -309,7 +309,7 @@ def dashboard_proxy() -> None:
         output = instance.run(command, hide_output=True)
         secret_name = None
         for line in output.split(b"\n"):
-            if line.startswith(b"default-token"):
+            if line.startswith(b"microk8s-dashboard-token"):
                 secret_name = line.split()[0].decode()
                 break
 

TypeError: sequence item 10: expected str instance, NoneType found (microk8s dashboard-proxy)
#### Summary

I installed MicroK8s on Windows 10 [Version 10.0.19043.2130]. I can run `microk8s dashboard-proxy` inside `microk8s-vm` VM, but I can't run `microk8s dashboard-proxy` on the Host.

#### What Should Happen Instead?

It should run without error on host.

#### Reproduction Steps

On Windows PowerShell

1. `multipass launch --name 'microk8s-vm' --bridged --disk 50G --cpus 2 --mem 4G`
2. `multipass shell microk8s-vm`

On `microk8s-vm`

1. `sudo snap install microk8s --classic --channel 1.25/stable`

    ```txt
    microk8s (1.25/stable) v1.25.2 from Canonical✓ installed
    ```

3. `sudo microk8s status --wait-ready`

    ```txt
    microk8s is running
    high-availability: no
      datastore master nodes: 127.0.0.1:19001
      datastore standby nodes: none
    addons:
      enabled:
        ha-cluster           # (core) Configure high availability on the current node
        helm                 # (core) Helm - the package manager for Kubernetes
        helm3                # (core) Helm 3 - the package manager for Kubernetes
      disabled:
        cert-manager         # (core) Cloud native certificate management
        community            # (core) The community addons repository
        dashboard            # (core) The Kubernetes dashboard
        dns                  # (core) CoreDNS
        gpu                  # (core) Automatic enablement of Nvidia CUDA
        host-access          # (core) Allow Pods connecting to Host services smoothly
        hostpath-storage     # (core) Storage class; allocates storage from host directory
        ingress              # (core) Ingress controller for external access
        kube-ovn             # (core) An advanced network fabric for Kubernetes
        mayastor             # (core) OpenEBS MayaStor
        metallb              # (core) Loadbalancer for your Kubernetes cluster
        metrics-server       # (core) K8s Metrics Server for API access to service metrics
        observability        # (core) A lightweight observability stack for logs, traces and metrics
        prometheus           # (core) Prometheus operator for monitoring and logging
        rbac                 # (core) Role-Based Access Control for authorisation
        registry             # (core) Private image registry exposed on localhost:32000
        storage              # (core) Alias to hostpath-storage add-on, deprecated
    ```

4. `sudo microk8s enable dashboard`

    ```txt
    Infer repository core for addon dashboard
    Enabling Kubernetes Dashboard
    Infer repository core for addon metrics-server
    Enabling Metrics-Server
    serviceaccount/metrics-server created
    clusterrole.rbac.authorization.k8s.io/system:aggregated-metrics-reader created
    clusterrole.rbac.authorization.k8s.io/system:metrics-server created
    rolebinding.rbac.authorization.k8s.io/metrics-server-auth-reader created
    clusterrolebinding.rbac.authorization.k8s.io/metrics-server:system:auth-delegator created
    clusterrolebinding.rbac.authorization.k8s.io/system:metrics-server created
    service/metrics-server created
    deployment.apps/metrics-server created
    apiservice.apiregistration.k8s.io/v1beta1.metrics.k8s.io created
    clusterrolebinding.rbac.authorization.k8s.io/microk8s-admin created
    Metrics-Server is enabled
    Applying manifest
    serviceaccount/kubernetes-dashboard created
    service/kubernetes-dashboard created
    secret/kubernetes-dashboard-certs created
    secret/kubernetes-dashboard-csrf created
    secret/kubernetes-dashboard-key-holder created
    configmap/kubernetes-dashboard-settings created
    role.rbac.authorization.k8s.io/kubernetes-dashboard created
    clusterrole.rbac.authorization.k8s.io/kubernetes-dashboard created
    rolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created
    clusterrolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created
    deployment.apps/kubernetes-dashboard created
    service/dashboard-metrics-scraper created
    deployment.apps/dashboard-metrics-scraper created
    secret/microk8s-dashboard-token created

    If RBAC is not enabled access the dashboard using the token retrieved with:

    microk8s kubectl describe secret -n kube-system microk8s-dashboard-token

    Use this token in the https login UI of the kubernetes-dashboard service.

    In an RBAC enabled setup (microk8s enable RBAC) you need to create a user with restricted
    permissions as shown in:
    https://github.com/kubernetes/dashboard/blob/master/docs/user/access-control/creating-sample-user.md
    ```

5. `sudo microk8s dashboard-proxy`

    ```txt
    Checking if Dashboard is running.
    Infer repository core for addon dashboard
    Infer repository core for addon metrics-server
    Waiting for Dashboard to come up.
    Trying to get token from microk8s-dashboard-token
    Waiting for secret token (attempt 0)
    Dashboard will be available at https://127.0.0.1:10443
    Use the following token to login:
    eyJhbGciOiJSUzI1NiI...(redacted)
    ```

6. `exit`

    Exit to the Windows PowerShell
    
On Windows PowerShell

1. `microk8s config > $env:LOCALAPPDATA\MicroK8s\config`

2. `microk8s dashboard-proxy`

    ```txt
    Checking if Dashboard is running.
    Infer repository core for addon dashboard
    Waiting for Dashboard to come up.
    Cannot find the dashboard secret.
    An unexpected error occurred.
    sequence item 10: expected str instance, NoneType found
    Traceback (most recent call last):
    File "cli\microk8s.py", line 57, in cli
    File "cli\microk8s.py", line 253, in dashboard_proxy
    File "vm_providers\_multipass\_multipass.py", line 54, in run
    File "vm_providers\_multipass\_multipass_command.py", line 232, in execute
    File "vm_providers\_multipass\_multipass_command.py", line 38, in _run_output
    TypeError: sequence item 10: expected str instance, NoneType found
    None
    ```

Run under Host. It will produce error.

![image](https://user-images.githubusercontent.com/88981/196414835-487b6eed-148e-45b1-bb2e-9a3f1597c874.png)

Run under Ubuntu VM. It's OK.

![image](https://user-images.githubusercontent.com/88981/196415890-2f7d5165-b663-4395-a5f2-c72641271fdb.png)


#### Introspection Report

[inspection-report-20221019_111239.tar.gz](https://github.com/canonical/microk8s/files/9816525/inspection-report-20221019_111239.tar.gz)

#### Can you suggest a fix?

No.

#### Are you interested in contributing with a fix?

yes if I can.

### More details

I checked the source code here:
https://github.com/canonical/microk8s/blob/e1d115f46a38ada6a2c5b236c9e26687b3529c45/installer/cli/microk8s.py#L308-L314

Here is my output:

```txt
$ microk8s.kubectl -n kube-system get secret
NAME                              TYPE                                  DATA   AGE
kubernetes-dashboard-certs        Opaque                                0      159m
microk8s-dashboard-token          kubernetes.io/service-account-token   3      159m
kubernetes-dashboard-csrf         Opaque                                1      159m
kubernetes-dashboard-key-holder   Opaque                                2      159m
```

It seems I don't have a `default-token` in the output. I don't know why I still can run `microk8s dashboard-proxy` inside Ubuntu Linux?

