Skip to content

Commit

Permalink
feat(commands): make the lifecycle commands hybrids
Browse files Browse the repository at this point in the history
This turns the lifecycle commands into hybrid commands, enabling them
to run both on the host and in the managed instance.
  • Loading branch information
lengau committed Mar 7, 2025
1 parent 005d4c2 commit a9dc719
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 211 deletions.
97 changes: 41 additions & 56 deletions craft_application/commands/lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
from __future__ import annotations

import argparse
import os
import pathlib
import subprocess
import textwrap
Expand All @@ -26,6 +25,7 @@
from craft_parts.features import Features
from typing_extensions import override

from craft_application import util
from craft_application.commands import base


Expand Down Expand Up @@ -77,32 +77,27 @@ def _fill_parser(self, parser: argparse.ArgumentParser) -> None:
help="Build in a LXD container.",
)

@override
def get_managed_cmd(self, parsed_args: argparse.Namespace) -> list[str]:
cmd = super().get_managed_cmd(parsed_args)

cmd.extend(parsed_args.parts)

return cmd

@override
def provider_name(self, parsed_args: argparse.Namespace) -> str | None:
return "lxd" if parsed_args.use_lxd else None

@override
def run_managed(self, parsed_args: argparse.Namespace) -> bool:
"""Return whether the command should run in managed mode or not.
def _run_manager_for_build_plan(self, fetch_service_policy: str | None) -> None:
"""Run this command in managed mode, iterating over the generated build plan."""
provider = self._services.get("provider")
for build in self._services.get("build_plan").plan():
provider.run_managed(build, bool(fetch_service_policy))

The command will run in managed mode unless the `--destructive-mode` flag
is passed OR `CRAFT_BUILD_ENVIRONMENT` is set to `host`.
"""
def _use_provider(self, parsed_args: argparse.Namespace) -> bool:
"""Determine whether to build in a managed provider."""
if util.is_managed_mode():
return False
if parsed_args.destructive_mode:
emit.debug(
"Not running managed mode because `--destructive-mode` was passed"
)
return False

build_env = os.getenv("CRAFT_BUILD_ENVIRONMENT")
build_env = self._services.get("config").get("build_environment")
if build_env and build_env.lower().strip() == "host":
emit.debug(
f"Not running managed mode because CRAFT_BUILD_ENVIRONMENT={build_env}"
Expand Down Expand Up @@ -156,23 +151,6 @@ def _fill_parser(self, parser: argparse.ArgumentParser) -> None:
help="Set architecture to build for",
)

@override
def get_managed_cmd(self, parsed_args: argparse.Namespace) -> list[str]:
"""Get the command to run in managed mode.
:param parsed_args: The parsed arguments used.
:returns: A list of strings ready to be passed into a craft-providers executor.
:raises: RuntimeError if this command is not supposed to run managed.
"""
cmd = super().get_managed_cmd(parsed_args)

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

return cmd

@override
def _run(
self,
Expand All @@ -183,6 +161,15 @@ def _run(
"""Run a lifecycle step command."""
super()._run(parsed_args)

if self._use_provider(parsed_args):
fetch_service_policy = getattr(parsed_args, "fetch_service_policy", None)
if fetch_service_policy:
self._services.get("fetch").set_policy(
fetch_service_policy # type: ignore[reportArgumentType]
)
self._run_manager_for_build_plan(fetch_service_policy)
return

shell = getattr(parsed_args, "shell", False)
shell_after = getattr(parsed_args, "shell_after", False)
debug = getattr(parsed_args, "debug", False)
Expand Down Expand Up @@ -363,14 +350,12 @@ def _fill_parser(self, parser: argparse.ArgumentParser) -> None:
dest="fetch_service_policy",
)

@override
def _run(
def _run_real(
self,
parsed_args: argparse.Namespace,
step_name: str | None = None,
**kwargs: Any,
) -> None:
"""Run the pack command."""
"""Run the actual pack command."""
if step_name not in ("pack", None):
raise RuntimeError(f"Step name {step_name} passed to pack command.")

Expand Down Expand Up @@ -414,6 +399,17 @@ def _run(
if shell_after:
_launch_shell()

@override
def _run(
self,
parsed_args: argparse.Namespace,
step_name: str | None = None,
**kwargs: Any,
) -> None:
if self._use_provider(parsed_args=parsed_args):
return super()._run(parsed_args=parsed_args, step_name=step_name)
return self._run_real(parsed_args=parsed_args, step_name=step_name)


class CleanCommand(_BaseLifecycleCommand):
"""Command to remove part assets."""
Expand Down Expand Up @@ -462,26 +458,15 @@ def _run(
"""
super()._run(parsed_args)

if not super().run_managed(parsed_args) or not self._should_clean_instances(
parsed_args
):
self._services.lifecycle.clean(parsed_args.parts)
else:
self._services.provider.clean_instances()

@override
def run_managed(self, parsed_args: argparse.Namespace) -> bool:
"""Return whether the command should run in managed mode or not.
build_managed = self._use_provider(parsed_args)
clean_instances = self._should_clean_instances(parsed_args)

Clean will run in managed mode unless:
- the `--destructive-mode` flag is provided OR
- `CRAFT_BUILD_ENVIRONMENT` is set to `host` OR
- a list of specific parts to clean is provided
"""
if not super().run_managed(parsed_args):
return False

return not self._should_clean_instances(parsed_args)
if build_managed and clean_instances:
self._services.provider.clean_instances()
elif build_managed:
self._run_manager_for_build_plan(fetch_service_policy=None)
else:
self._services.get("lifecycle").clean(parsed_args.parts)

@staticmethod
def _should_clean_instances(parsed_args: argparse.Namespace) -> bool:
Expand Down
4 changes: 0 additions & 4 deletions spread.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,9 @@ backends:
halt-timeout: 3h
systems:
- ubuntu-24.04-64:
<<<<<<< HEAD
workers: 2
=======
workers: 4
memory: 2G
storage: 10G
>>>>>>> a390947 (fix: oops)
# lxd: # Disabled due to https://github.com/canonical/spread/issues/215
# systems:
# - ubuntu-24.04
Expand Down
6 changes: 2 additions & 4 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,10 +432,8 @@ def fake_services(
factory.update_kwargs(
"lifecycle", work_dir=tmp_path, cache_dir=tmp_path / "cache", build_plan=[]
)
factory.update_kwargs(
"project",
project_dir=project_path,
)
factory.update_kwargs("project", project_dir=project_path)
factory.update_kwargs("provider", work_dir=project_path)
platform = (
request.getfixturevalue("fake_platform")
if "fake_platform" in request.fixturenames
Expand Down
Loading

0 comments on commit a9dc719

Please sign in to comment.