Esempio n. 1
0
    def ensure_provider_is_available(cls) -> None:
        """Ensure provider is available, prompting the user to install it if required.

        :raises ProviderError: if provider is not available.
        """
        if not multipass.is_installed():
            with emit.pause():
                confirmation = utils.confirm_with_user(
                    "Multipass is required, but not installed. Do you wish to install Multipass "
                    "and configure it with the defaults?",
                    default=False,
                )
            if confirmation:
                try:
                    multipass.install()
                except multipass.MultipassInstallationError as error:
                    raise ProviderError(
                        "Failed to install Multipass. Visit https://multipass.run/ for "
                        "instructions on installing Multipass for your operating system.",
                    ) from error
            else:
                raise ProviderError(
                    "Multipass is required, but not installed. Visit https://multipass.run/ for "
                    "instructions on installing Multipass for your operating system.",
                )

        try:
            multipass.ensure_multipass_is_ready()
        except multipass.MultipassError as error:
            raise ProviderError(str(error)) from error
Esempio n. 2
0
def launch_shell(*, cwd: Optional[pathlib.Path] = None) -> None:
    """Launch a user shell for debugging environment.

    :param cwd: Working directory to start user in.
    """
    with emit.pause():
        subprocess.run(["bash"], check=False, cwd=cwd)
Esempio n. 3
0
def confirm_with_user(prompt, default=False) -> bool:
    """Query user for yes/no answer.

    If stdin is not a tty, the default value is returned.

    If user returns an empty answer, the default value is returned.

    :returns: True if answer starts with [yY], False if answer starts with [nN],
        otherwise the default.
    """
    if is_charmcraft_running_in_managed_mode():
        raise RuntimeError("confirmation not yet supported in managed-mode")

    if not sys.stdin.isatty():
        return default

    choices = " [Y/n]: " if default else " [y/N]: "

    with emit.pause():
        reply = input(prompt + choices).lower().strip()

    if reply and reply[0] == "y":
        return True
    elif reply and reply[0] == "n":
        return False
    else:
        return default
Esempio n. 4
0
def _run_in_provider(project: Project, command_name: str,
                     parsed_args: "argparse.Namespace") -> None:
    """Pack image in provider instance."""
    emit.debug("Checking build provider availability")
    provider_name = "lxd" if parsed_args.use_lxd else None
    provider = providers.get_provider(provider_name)
    provider.ensure_provider_is_available()

    cmd = ["snapcraft", command_name]

    if hasattr(parsed_args, "parts"):
        cmd.extend(parsed_args.parts)

    if getattr(parsed_args, "output", None):
        cmd.extend(["--output", parsed_args.output])

    if emit.get_mode() == EmitterMode.VERBOSE:
        cmd.append("--verbose")
    elif emit.get_mode() == EmitterMode.QUIET:
        cmd.append("--quiet")
    elif emit.get_mode() == EmitterMode.DEBUG:
        cmd.append("--verbosity=debug")
    elif emit.get_mode() == EmitterMode.TRACE:
        cmd.append("--verbosity=trace")

    if parsed_args.debug:
        cmd.append("--debug")
    if getattr(parsed_args, "shell", False):
        cmd.append("--shell")
    if getattr(parsed_args, "shell_after", False):
        cmd.append("--shell-after")

    if getattr(parsed_args, "enable_manifest", False):
        cmd.append("--enable-manifest")
    build_information = getattr(parsed_args, "manifest_build_information",
                                None)
    if build_information:
        cmd.append("--manifest-build-information")
        cmd.append(build_information)

    output_dir = utils.get_managed_environment_project_path()

    emit.progress("Launching instance...")
    with provider.launched_environment(
            project_name=project.name,
            project_path=Path().absolute(),
            base=project.get_effective_base(),
            bind_ssh=parsed_args.bind_ssh,
            build_on=get_host_architecture(),
            build_for=get_host_architecture(),
    ) as instance:
        try:
            with emit.pause():
                instance.execute_run(cmd, check=True, cwd=output_dir)
            capture_logs_from_instance(instance)
        except subprocess.CalledProcessError as err:
            capture_logs_from_instance(instance)
            raise providers.ProviderError(
                f"Failed to execute {command_name} in instance.") from err
Esempio n. 5
0
def _launch_shell(*, cwd: Optional[pathlib.Path] = None) -> None:
    """Launch a user shell for debugging environment.

    :param cwd: Working directory to start user in.
    """
    emit.progress("Launching shell on build environment...", permanent=True)
    with emit.pause():
        subprocess.run(["bash"], check=False, cwd=cwd)
Esempio n. 6
0
def prompt(prompt_text: str, *, hide: bool = False) -> str:
    """Prompt and return the entered string.

    :param prompt_text: string used for the prompt.
    :param hide: hide user input if True.
    """
    if is_managed_mode():
        raise RuntimeError("prompting not yet supported in managed-mode")

    if not sys.stdin.isatty():
        raise errors.SnapcraftError("prompting not possible with no tty")

    if hide:
        method = getpass
    else:
        method = input  # type: ignore

    with emit.pause():
        return str(method(prompt_text))
Esempio n. 7
0
    def pack_charm_in_instance(self, *, bases_index: int, build_on: Base,
                               build_on_index: int) -> str:
        """Pack instance in Charm."""
        charm_name = format_charm_file_name(self.metadata.name,
                                            self.config.bases[bases_index])

        # If building in project directory, use the project path as the working
        # directory. The output charms will be placed in the correct directory
        # without needing retrieval. If outputting to a directory other than the
        # charm project directory, we need to output the charm outside the
        # project directory and can retrieve it when complete.
        cwd = pathlib.Path.cwd()
        if cwd == self.charmdir:
            instance_output_dir = env.get_managed_environment_project_path()
            pull_charm = False
        else:
            instance_output_dir = env.get_managed_environment_home_path()
            pull_charm = True

        cmd = ["charmcraft", "pack", "--bases-index", str(bases_index)]

        if emit.get_mode() == EmitterMode.VERBOSE:
            cmd.append("--verbose")
        elif emit.get_mode() == EmitterMode.QUIET:
            cmd.append("--quiet")
        elif emit.get_mode() == EmitterMode.TRACE:
            cmd.append("--trace")

        if self.debug:
            cmd.append("--debug")

        if self.shell:
            cmd.append("--shell")

        if self.shell_after:
            cmd.append("--shell-after")

        emit.progress(f"Launching environment to pack for base {build_on} "
                      "(may take a while the first time but it's reusable)")
        with self.provider.launched_environment(
                charm_name=self.metadata.name,
                project_path=self.charmdir,
                base=build_on,
                bases_index=bases_index,
                build_on_index=build_on_index,
        ) as instance:
            emit.progress("Packing the charm")
            emit.trace(f"Running {cmd}")
            try:
                with emit.pause():
                    instance.execute_run(cmd,
                                         check=True,
                                         cwd=instance_output_dir)
            except subprocess.CalledProcessError as error:
                raise CraftError(
                    f"Failed to build charm for bases index '{bases_index}'."
                ) from error
            finally:
                capture_logs_from_instance(instance)

            if pull_charm:
                try:
                    instance.pull_file(
                        source=instance_output_dir / charm_name,
                        destination=cwd / charm_name,
                    )
                except FileNotFoundError as error:
                    raise CraftError(
                        "Unexpected error retrieving charm from instance."
                    ) from error

        emit.progress("Charm packed ok")
        return charm_name