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
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)
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
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
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)
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))
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