Ejemplo n.º 1
0
def capture_logs_from_instance(instance: Executor) -> None:
    """Retrieve logs from instance.

    :param instance: Instance to retrieve logs from.

    :returns: String of logs.
    """
    # Get a temporary file path.
    with tempfile.NamedTemporaryFile(delete=False,
                                     prefix="snapcraft-") as tmp_file:
        local_log_path = pathlib.Path(tmp_file.name)

    instance_log_path = get_managed_environment_log_path()

    try:
        instance.pull_file(source=instance_log_path,
                           destination=local_log_path)
    except FileNotFoundError:
        emit.debug("No logs found in instance.")
        return

    emit.debug("Logs captured from managed instance:")
    with local_log_path.open("rt", encoding="utf8") as logfile:
        for line in logfile:
            emit.debug(":: " + line.rstrip())
    local_log_path.unlink()
Ejemplo n.º 2
0
def capture_logs_from_instance(instance: Executor) -> None:
    """Retrieve logs from instance.

    :param instance: Instance to retrieve logs from.

    :returns: String of logs.
    """
    # Get a temporary file path.
    tmp_file = tempfile.NamedTemporaryFile(delete=False, prefix="charmcraft-")
    tmp_file.close()

    local_log_path = pathlib.Path(tmp_file.name)
    instance_log_path = get_managed_environment_log_path()

    try:
        instance.pull_file(source=instance_log_path,
                           destination=local_log_path)
    except FileNotFoundError:
        emit.trace("No logs found in instance.")
        return

    emit.trace("Logs captured from managed instance:")
    with open(local_log_path, "rt", encoding="utf8") as fh:
        for line in fh:
            emit.trace(f":: {line.rstrip()}")
    local_log_path.unlink()
Ejemplo n.º 3
0
 def _write_craft_image_config(self, *, executor: Executor) -> None:
     conf = {"compatibility_tag": self.compatibility_tag}
     executor.create_file(
         destination=pathlib.Path("/etc/craft-image.conf"),
         content=yaml.dump(conf).encode(),
         file_mode="0644",
     )
Ejemplo n.º 4
0
    def _setup_apt(self, *, executor: Executor) -> None:
        """Configure apt & update cache.

        :param executor: Executor for target container.
        """
        executor.execute_run(command=["apt-get", "update"], check=True)
        executor.execute_run(command=["apt-get", "install", "-y", "apt-utils"],
                             check=True)
Ejemplo n.º 5
0
    def _setup_hostname(self, *, executor: Executor) -> None:
        """Configure hostname, installing /etc/hostname.

        :param executor: Executor for target container.
        """
        executor.create_file(
            destination=pathlib.Path("/etc/hostname"),
            content=self.hostname.encode(),
            file_mode="0644",
        )
Ejemplo n.º 6
0
    def _setup_snapcraft(*, executor: Executor) -> None:
        """Install Snapcraft in target environment.

        On Linux, the default behavior is to inject the host snap into the target
        environment.

        On other platforms, the Snapcraft snap is installed from the Snap Store.

        When installing the snap from the Store, we check if the user specifies a
        channel, using SNAPCRAFT_INSTALL_SNAP_CHANNEL=<channel>.  If unspecified,
        we use the "stable" channel on the default track.

        On Linux, the user may specify this environment variable to force Snapcraft
        to install the snap from the Store rather than inject the host snap.

        :raises BaseConfigurationError: on error.
        """
        # Requirement for apt gpg and version:git
        executor.execute_run(
            ["apt-get", "install", "-y", "gnupg", "dirmngr", "git"],
            capture_output=True,
            check=True,
        )

        snap_channel = utils.get_managed_environment_snap_channel()
        if snap_channel is None and sys.platform != "linux":
            snap_channel = "stable"

        # Snaps that are already installed won't be reinstalled.
        # See https://github.com/canonical/craft-providers/issues/91

        if snap_channel:
            try:
                snap_installer.install_from_store(
                    executor=executor,
                    snap_name="snapcraft",
                    channel=snap_channel,
                    classic=True,
                )
            except snap_installer.SnapInstallationError as error:
                raise bases.BaseConfigurationError(
                    "Failed to install snapcraft snap from store channel "
                    f"{snap_channel!r} into target environment."
                ) from error
        else:
            try:
                snap_installer.inject_from_host(
                    executor=executor, snap_name="snapcraft", classic=True
                )
            except snap_installer.SnapInstallationError as error:
                raise bases.BaseConfigurationError(
                    "Failed to inject host snapcraft snap into target environment."
                ) from error
Ejemplo n.º 7
0
    def setup(
        self,
        *,
        executor: Executor,
        retry_wait: float = 0.25,
        timeout: Optional[float] = None,
    ) -> None:
        """Prepare base instance for use by the application.

        In addition to the guarantees provided by buildd:

            - charmcraft installed

            - python3 pip and setuptools installed

        :param executor: Executor for target container.
        :param retry_wait: Duration to sleep() between status checks (if
            required).
        :param timeout: Timeout in seconds.

        :raises BaseCompatibilityError: if instance is incompatible.
        :raises BaseConfigurationError: on other unexpected error.
        """
        super().setup(executor=executor, retry_wait=retry_wait, timeout=timeout)

        try:
            # XXX Claudio 2021-07-22: craft-parts uses sudo, install it until
            # we adjust it to detect if changing to superuser is needed.
            executor.execute_run(
                [
                    "apt-get",
                    "install",
                    "-y",
                    "sudo",
                ],
                check=True,
                capture_output=True,
            )
        except subprocess.CalledProcessError as error:
            raise bases.BaseConfigurationError(
                brief="Failed to install the required dependencies.",
            ) from error

        try:
            snap_installer.inject_from_host(
                executor=executor, snap_name="charmcraft", classic=True
            )
        except snap_installer.SnapInstallationError as error:
            raise bases.BaseConfigurationError(
                brief="Failed to inject host Charmcraft snap into target environment.",
            ) from error
def save(
    *, executor: Executor, config: Dict[str, Any], config_path: pathlib.Path
) -> None:
    """Save craft image config.

    :param executor: Executor for instance.
    :param config: Configuration data to write.
    :param config_path: Path to configuration file.
    """
    executor.create_file(
        destination=config_path,
        content=yaml.dump(config).encode(),
        file_mode="0644",
    )
Ejemplo n.º 9
0
    def _setup_wait_for_system_ready(self,
                                     *,
                                     executor: Executor,
                                     retry_count=120,
                                     retry_interval: float = 0.5) -> None:
        """Wait until system is ready.

        :param executor: Executor for target container.
        :param timeout_secs: Timeout in seconds.
        """
        logger.info("Waiting for container to be ready...")
        for _ in range(retry_count):
            proc = executor.execute_run(
                command=["systemctl", "is-system-running"],
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                check=False,
            )

            running_state = proc.stdout.decode().strip()
            if running_state in ["running", "degraded"]:
                break

            logger.debug("systemctl is-system-running status: %s",
                         running_state)
            sleep(retry_interval)
        else:
            logger.warning("System exceeded timeout to get ready.")
Ejemplo n.º 10
0
def wait_for_system_ready(*,
                          executor: Executor,
                          retry_count=120,
                          retry_interval: float = 0.5) -> None:
    """Wait until system is ready as defined by sysemctl is-system-running.

    :param executor: Executor for target container.
    :param retry_count: Number of times to check systemctl.
    :param retry_interval: Time between checks to systemctl.
    """
    logger.info("Waiting for container to be ready...")
    for _ in range(retry_count):
        proc = executor.execute_run(
            command=["systemctl", "is-system-running"],
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            check=False,
        )

        running_state = proc.stdout.decode().strip()
        if proc.returncode == 0:
            if running_state in ["running", "degraded"]:
                break

            logger.warning(
                "Unexpected state for systemctl is-system-running: %s",
                running_state,
            )

        sleep(retry_interval)
    else:
        logger.warning("System exceeded timeout to get ready.")
Ejemplo n.º 11
0
def is_target_file(*, executor: Executor, target: pathlib.Path) -> bool:
    """Check if path is file in executed environment.

    :param target: Path to check.

    :returns: True if file, False otherwise.
    """
    proc = executor.execute_run(command=["test", "-f", target.as_posix()])
    return proc.returncode == 0
Ejemplo n.º 12
0
    def _read_craft_image_config(
            self, *, executor: Executor) -> Optional[Dict[str, Any]]:
        try:
            proc = executor.execute_run(
                command=["cat", "/etc/craft-image.conf"],
                check=True,
                stdout=subprocess.PIPE,
            )
        except subprocess.CalledProcessError:
            return None

        return yaml.load(proc.stdout, Loader=yaml.SafeLoader)
Ejemplo n.º 13
0
    def _read_os_release(self, *,
                         executor: Executor) -> Optional[Dict[str, Any]]:
        try:
            proc = executor.execute_run(
                command=["cat", "/etc/os-release"],
                check=False,
                stdout=subprocess.PIPE,
            )
        except subprocess.CalledProcessError:
            return None

        return parse_os_release(proc.stdout.decode())
Ejemplo n.º 14
0
def capture_logs_from_instance(instance: Executor) -> None:
    """Retrieve logs from instance.

    :param instance: Instance to retrieve logs from.

    :returns: String of logs.
    """
    _, tmp_path = tempfile.mkstemp(prefix="charmcraft-")
    local_log_path = pathlib.Path(tmp_path)
    instance_log_path = get_managed_environment_log_path()

    try:
        instance.pull_file(source=instance_log_path, destination=local_log_path)
    except FileNotFoundError:
        logger.debug("No logs found in instance.")
        return

    logs = local_log_path.read_text()
    local_log_path.unlink()

    logger.debug("Logs captured from managed instance:\n%s", logs)
Ejemplo n.º 15
0
    def _setup_networkd(self, *, executor: Executor) -> None:
        """Configure networkd and start it.

        Installs eth0 network configuration using ipv4.

        :param executor: Executor for target container.
        """
        executor.create_file(
            destination=pathlib.Path("/etc/systemd/network/10-eth0.network"),
            content=dedent("""
                [Match]
                Name=eth0

                [Network]
                DHCP=ipv4
                LinkLocalAddressing=ipv6

                [DHCP]
                RouteMetric=100
                UseMTU=true
                """).encode(),
            file_mode="0644",
        )

        executor.execute_run(
            command=["systemctl", "enable", "systemd-networkd"], check=True)

        executor.execute_run(
            command=["systemctl", "restart", "systemd-networkd"], check=True)
Ejemplo n.º 16
0
def directory_sync_to_remote(*,
                             executor: Executor,
                             source: pathlib.Path,
                             destination: pathlib.Path,
                             delete=True,
                             host_tar_cmd: str = "tar",
                             target_tar_cmd: str = "tar") -> None:
    """Naive sync to remote using tarball.

    :param source: Host directory to copy.
    :param destination: Target destination directory to copy to.
    :param delete: Flag to delete existing destination, if exists.
    """
    destination_path = destination.as_posix()

    if delete is True:
        executor.execute_run(["rm", "-rf", destination_path], check=True)

    executor.execute_run(["mkdir", "-p", destination_path], check=True)

    archive_proc = subprocess.Popen(
        [host_tar_cmd, "cpf", "-", "-C",
         str(source), "."],
        stdout=subprocess.PIPE,
    )

    target_proc = executor.execute_popen(
        [target_tar_cmd, "xpvf", "-", "-C", destination_path],
        stdin=archive_proc.stdout,
    )

    # Allow archive_proc to receive a SIGPIPE if destination_proc exits.
    if archive_proc.stdout:
        archive_proc.stdout.close()

    # Waot until done.
    target_proc.communicate()
def load(*, executor: Executor, config_path: pathlib.Path) -> Optional[Dict[str, Any]]:
    """Load craft configuration.

    :param executor: Executor for instance.
    :param config_path: Path to configuration file.
    """
    try:
        proc = executor.execute_run(
            command=["cat", str(config_path)],
            check=True,
            stdout=subprocess.PIPE,
        )
    except subprocess.CalledProcessError:
        return None

    return yaml.load(proc.stdout, Loader=yaml.SafeLoader)
Ejemplo n.º 18
0
    def _setup_wait_for_network(self,
                                *,
                                executor: Executor,
                                timeout_secs: int = 60) -> None:
        """Wait until networking is ready.

        :param executor: Executor for target container.
        :param timeout_secs: Timeout in seconds.
        """
        logger.info("Waiting for networking to be ready...")
        for _ in range(timeout_secs * 2):
            proc = executor.execute_run(
                command=["getent", "hosts", "snapcraft.io"],
                stdout=subprocess.DEVNULL)
            if proc.returncode == 0:
                break

            sleep(0.5)
        else:
            logger.warning("Failed to setup networking.")
Ejemplo n.º 19
0
def directory_sync_from_remote(*,
                               executor: Executor,
                               source: pathlib.Path,
                               destination: pathlib.Path,
                               delete: bool = False,
                               host_tar_cmd: str = "tar",
                               target_tar_cmd: str = "tar") -> None:
    """Naive sync from remote using tarball.

    Relies on only the required Executor.interfaces.

    :param source: Target directory to copy from.
    :param destination: Host destination directory to copy to.
    """
    destination_path = destination.as_posix()

    if delete and destination.exists():
        shutil.rmtree(destination)

    destination.mkdir(parents=True)

    archive_proc = executor.execute_popen(
        [host_tar_cmd, "cpf", "-", "-C",
         source.as_posix(), "."],
        stdout=subprocess.PIPE,
    )

    target_proc = subprocess.Popen(
        [target_tar_cmd, "xpvf", "-,", "-C", destination_path],
        stdin=archive_proc.stdout,
    )

    # Allow archive_proc to receive a SIGPIPE if destination_proc exits.
    if archive_proc.stdout:
        archive_proc.stdout.close()

    # Waot until done.
    target_proc.communicate()
Ejemplo n.º 20
0
    def _setup_wait_for_system_ready(self,
                                     *,
                                     executor: Executor,
                                     timeout_secs: int = 60) -> None:
        """Wait until system is ready.

        :param executor: Executor for target container.
        :param timeout_secs: Timeout in seconds.
        """
        logger.info("Waiting for container to be ready...")
        for _ in range(timeout_secs * 2):
            proc = executor.execute_run(
                command=["systemctl", "is-system-running"],
                stdout=subprocess.PIPE)

            running_state = proc.stdout.decode().strip()
            if running_state in ["running", "degraded"]:
                break

            logger.debug("systemctl is-system-running: %s", running_state)
            sleep(0.5)
        else:
            logger.warning("Systemd failed to reach target before timeout.")
Ejemplo n.º 21
0
    def _setup_resolved(self, *, executor: Executor) -> None:
        """Configure system-resolved to manage resolve.conf.

        :param executor: Executor for target container.
        :param timeout_secs: Timeout in seconds.
        """
        executor.execute_run(
            command=[
                "ln",
                "-sf",
                "/run/systemd/resolve/resolv.conf",
                "/etc/resolv.conf",
            ],
            check=True,
        )

        executor.execute_run(
            command=["systemctl", "enable", "systemd-resolved"], check=True)

        executor.execute_run(
            command=["systemctl", "restart", "systemd-resolved"], check=True)
Ejemplo n.º 22
0
    def _setup_snapd(self, *, executor: Executor) -> None:
        """Install snapd and dependencies and wait until ready.

        :param executor: Executor for target container.
        :param timeout_secs: Timeout in seconds.
        """
        executor.execute_run(
            command=[
                "apt-get",
                "install",
                "fuse",
                "udev",
                "--yes",
            ],
            check=True,
        )

        executor.execute_run(command=["systemctl", "enable", "systemd-udevd"],
                             check=True)
        executor.execute_run(command=["systemctl", "start", "systemd-udevd"],
                             check=True)
        executor.execute_run(command=["apt-get", "install", "snapd", "--yes"],
                             check=True)
        executor.execute_run(command=["systemctl", "start", "snapd.socket"],
                             check=True)
        executor.execute_run(command=["systemctl", "start", "snapd.service"],
                             check=True)
        executor.execute_run(command=["snap", "wait", "system", "seed.loaded"],
                             check=True)