← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/lpcraft:remove-managed-mode into lpcraft:main

 

Colin Watson has proposed merging ~cjwatson/lpcraft:remove-managed-mode into lpcraft:main.

Commit message:
Stop running lpcraft inside the container

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/lpcraft/+git/lpcraft/+merge/412433

I borrowed the previous approach from charmcraft, and initially thought it would be useful enough to pay its way: if we needed to wrap the job in any complex logic that would be difficult to do at arm's length from outside the container, then the ability to run Python code inside the container would be helpful.

However, this approach also has a number of disadvantages: injecting lpcraft as a snap is complex and relatively slow; it incurs all kinds of complexity spread over lpcraft; and the split between `_run_job` and `_run_pipeline` wasn't very clearly motivated and was confusing.

Since there doesn't turn out to be a pressing practical need for all this complexity, remove it, and instead just execute job commands directly inside the container.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/lpcraft:remove-managed-mode into lpcraft:main.
diff --git a/README.rst b/README.rst
index b28f79c..ad7a4da 100644
--- a/README.rst
+++ b/README.rst
@@ -15,13 +15,10 @@ the CLI design is based on both those tools.
 Running
 =======
 
-``lpcraft`` is mainly intended to be consumed as a snap, and it currently
-needs to be a snap in order to be able to inject itself into the containers
-it starts (though this may be made more flexible in future).  Use
-``snapcraft`` to build the snap, which you can then install using ``snap
-install --classic --dangerous lpcraft_<version>_<architecture>.snap``.
-(Once ``lpcraft`` is more complete and stable, it will be made available
-from the snap store.)
+``lpcraft`` is mainly intended to be consumed as a snap.  Use ``snapcraft``
+to build the snap, which you can then install using ``snap install --classic
+--dangerous lpcraft_<version>_<architecture>.snap``.  (Once ``lpcraft`` is
+more complete and stable, it will be made available from the snap store.)
 
 You can run ``lpcraft`` from a directory containing ``.launchpad.yaml``,
 although it won't do very much useful yet.
diff --git a/lpcraft/commands/run.py b/lpcraft/commands/run.py
index e5a5fcc..b1fe013 100644
--- a/lpcraft/commands/run.py
+++ b/lpcraft/commands/run.py
@@ -1,62 +1,19 @@
 # Copyright 2021 Canonical Ltd.  This software is licensed under the
 # GNU General Public License version 3 (see the file LICENSE).
 
-import subprocess
 from argparse import Namespace
 from pathlib import Path
-from typing import List, Optional
 
-from craft_cli import EmitterMode, emit
+from craft_cli import emit
 
 from lpcraft import env
-from lpcraft.config import Config, Job
+from lpcraft.config import Config
 from lpcraft.errors import CommandError
-from lpcraft.providers import get_provider, replay_logs
+from lpcraft.providers import get_provider
 from lpcraft.utils import get_host_architecture
 
 
-def _get_jobs(
-    config: Config, job_name: str, series: Optional[str] = None
-) -> List[Job]:
-    jobs = config.jobs.get(job_name, [])
-    if not jobs:
-        raise CommandError(f"No job definition for {job_name!r}")
-    if series is not None:
-        jobs = [job for job in jobs if job.series == series]
-        if not jobs:
-            raise CommandError(
-                f"No job definition for {job_name!r} for {series}"
-            )
-    return jobs
-
-
-def _run_job(args: Namespace) -> None:
-    """Run a single job in a managed environment."""
-    if args.series is None:
-        raise CommandError("Series is required in managed mode")
-    if args.job_name is None:
-        raise CommandError("Job name is required in managed mode")
-
-    config = Config.load(Path(".launchpad.yaml"))
-    jobs = _get_jobs(config, args.job_name, series=args.series)
-    if len(jobs) > 1:
-        raise CommandError(
-            f"Ambiguous job definitions for {args.job_name!r} for "
-            f"{args.series}"
-        )
-    [job] = jobs
-    if job.run is None:
-        raise CommandError(f"'run' not set for job {args.job_name!r}")
-    proc = subprocess.run(["bash", "--noprofile", "--norc", "-ec", job.run])
-    if proc.returncode != 0:
-        raise CommandError(
-            f"Job {args.job_name!r} failed with exit status "
-            f"{proc.returncode}.",
-            retcode=proc.returncode,
-        )
-
-
-def _run_pipeline(args: Namespace) -> None:
+def run(args: Namespace) -> int:
     """Run a pipeline, launching managed environments as needed."""
     config = Config.load(Path(".launchpad.yaml"))
     host_architecture = get_host_architecture()
@@ -66,21 +23,19 @@ def _run_pipeline(args: Namespace) -> None:
     provider.ensure_provider_is_available()
 
     for job_name in config.pipeline:
-        jobs = _get_jobs(config, job_name)
+        jobs = config.jobs.get(job_name, [])
+        if not jobs:
+            raise CommandError(f"No job definition for {job_name!r}")
         for job in jobs:
             if host_architecture not in job.architectures:
                 continue
+            if job.run is None:
+                raise CommandError(
+                    f"Job {job_name!r} for {job.series}/{host_architecture} "
+                    f"does not set 'run'"
+                )
 
-            cmd = ["lpcraft"]
-            # XXX jugmac00 2021-11-25: coverage ignored for now
-            # but should be tested in future
-            if emit.get_mode() == EmitterMode.QUIET:
-                cmd.append("--quiet")  # pragma: no cover
-            elif emit.get_mode() == EmitterMode.VERBOSE:
-                cmd.append("--verbose")  # pragma: no cover
-            elif emit.get_mode() == EmitterMode.TRACE:
-                cmd.append("--trace")  # pragma: no cover
-            cmd.extend(["run", "--series", job.series, job_name])
+            cmd = ["bash", "--noprofile", "--norc", "-ec", job.run]
 
             emit.progress(
                 f"Launching environment for {job.series}/{host_architecture}"
@@ -100,7 +55,6 @@ def _run_pipeline(args: Namespace) -> None:
                         stderr=stream,
                     )
                 if proc.returncode != 0:
-                    replay_logs(instance)
                     raise CommandError(
                         f"Job {job_name!r} for "
                         f"{job.series}/{host_architecture} failed with "
@@ -108,13 +62,4 @@ def _run_pipeline(args: Namespace) -> None:
                         retcode=proc.returncode,
                     )
 
-
-def run(args: Namespace) -> int:
-    """Run a job."""
-    if env.is_managed_mode():
-        # XXX cjwatson 2021-11-09: Perhaps it would be simpler to split this
-        # into a separate internal command instead?
-        _run_job(args)
-    else:
-        _run_pipeline(args)
     return 0
diff --git a/lpcraft/commands/tests/test_run.py b/lpcraft/commands/tests/test_run.py
index b31b75c..7d19987 100644
--- a/lpcraft/commands/tests/test_run.py
+++ b/lpcraft/commands/tests/test_run.py
@@ -9,8 +9,7 @@ from typing import Optional
 from unittest.mock import ANY, Mock, call, patch
 
 from craft_providers.lxd import LXC, launch
-from fixtures import EnvironmentVariable, TempDir
-from systemfixtures import FakeProcesses
+from fixtures import TempDir
 from testtools.matchers import MatchesStructure
 
 from lpcraft.commands.tests import CommandBaseTestCase
@@ -19,224 +18,9 @@ from lpcraft.providers._lxd import LXDProvider, _LXDLauncher
 from lpcraft.providers.tests import FakeLXDInstaller
 
 
-class RunJobTestCase(CommandBaseTestCase):
+class TestRun(CommandBaseTestCase):
     def setUp(self):
         super().setUp()
-        self.useFixture(EnvironmentVariable("LPCRAFT_MANAGED_MODE", "1"))
-        cwd = os.getcwd()
-        os.chdir(self.useFixture(TempDir()).path)
-        self.addCleanup(os.chdir, cwd)
-
-    def test_no_series(self):
-        result = self.run_command("run")
-
-        self.assertThat(
-            result,
-            MatchesStructure.byEquality(
-                exit_code=1,
-                errors=[CommandError("Series is required in managed mode")],
-            ),
-        )
-
-    def test_no_job_name(self):
-        result = self.run_command("run", "--series", "focal")
-
-        self.assertThat(
-            result,
-            MatchesStructure.byEquality(
-                exit_code=1,
-                errors=[CommandError("Job name is required in managed mode")],
-            ),
-        )
-
-    def test_missing_config_file(self):
-        result = self.run_command("run", "--series", "focal", "test")
-
-        self.assertThat(
-            result,
-            MatchesStructure.byEquality(
-                exit_code=1,
-                errors=[
-                    YAMLError("Couldn't find config file '.launchpad.yaml'")
-                ],
-            ),
-        )
-
-    def test_no_matching_job(self):
-        config = dedent(
-            """
-            pipeline:
-                - test
-
-            jobs:
-                test:
-                    series: focal
-                    architectures: amd64
-                    run: tox
-            """
-        )
-        Path(".launchpad.yaml").write_text(config)
-
-        result = self.run_command("run", "--series", "focal", "build")
-
-        self.assertThat(
-            result,
-            MatchesStructure.byEquality(
-                exit_code=1,
-                errors=[CommandError("No job definition for 'build'")],
-            ),
-        )
-
-    def test_no_matching_job_for_series(self):
-        config = dedent(
-            """
-            pipeline:
-                - test
-
-            jobs:
-                test:
-                    series: focal
-                    architectures: amd64
-                    run: tox
-            """
-        )
-        Path(".launchpad.yaml").write_text(config)
-
-        result = self.run_command("run", "--series", "bionic", "test")
-
-        self.assertThat(
-            result,
-            MatchesStructure.byEquality(
-                exit_code=1,
-                errors=[
-                    CommandError("No job definition for 'test' for bionic")
-                ],
-            ),
-        )
-
-    def test_ambiguous_job_definitions(self):
-        config = dedent(
-            """
-            pipeline:
-                - test
-
-            jobs:
-                test:
-                    matrix:
-                        - run: make
-                        - run: tox
-                    series: focal
-                    architectures: amd64
-            """
-        )
-        Path(".launchpad.yaml").write_text(config)
-
-        result = self.run_command("run", "--series", "focal", "test")
-
-        self.assertThat(
-            result,
-            MatchesStructure.byEquality(
-                exit_code=1,
-                errors=[
-                    CommandError(
-                        "Ambiguous job definitions for 'test' for focal"
-                    )
-                ],
-            ),
-        )
-
-    def test_no_run_definition(self):
-        config = dedent(
-            """
-            pipeline:
-                - test
-
-            jobs:
-                test:
-                    series: focal
-                    architectures: amd64
-            """
-        )
-        Path(".launchpad.yaml").write_text(config)
-
-        result = self.run_command("run", "--series", "focal", "test")
-
-        self.assertThat(
-            result,
-            MatchesStructure.byEquality(
-                exit_code=1,
-                errors=[CommandError("'run' not set for job 'test'")],
-            ),
-        )
-
-    def test_run_fails(self):
-        processes_fixture = self.useFixture(FakeProcesses())
-        processes_fixture.add(lambda _: {"returncode": 2}, name="bash")
-        config = dedent(
-            """
-            pipeline:
-                - test
-
-            jobs:
-                test:
-                    series: focal
-                    architectures: amd64
-                    run: |
-                        exit 2
-            """
-        )
-        Path(".launchpad.yaml").write_text(config)
-
-        result = self.run_command("run", "--series", "focal", "test")
-
-        self.assertThat(
-            result,
-            MatchesStructure.byEquality(
-                exit_code=2,
-                errors=[
-                    CommandError(
-                        "Job 'test' failed with exit status 2.", retcode=2
-                    )
-                ],
-            ),
-        )
-        self.assertEqual(
-            [["bash", "--noprofile", "--norc", "-ec", "exit 2\n"]],
-            [proc._args["args"] for proc in processes_fixture.procs],
-        )
-
-    def test_run_succeeds(self):
-        processes_fixture = self.useFixture(FakeProcesses())
-        processes_fixture.add(lambda _: {"returncode": 0}, name="bash")
-        config = dedent(
-            """
-            pipeline:
-                - test
-
-            jobs:
-                test:
-                    series: focal
-                    architectures: amd64
-                    run: |
-                        echo hello
-                        tox
-            """
-        )
-        Path(".launchpad.yaml").write_text(config)
-
-        result = self.run_command("run", "--series", "focal", "test")
-
-        self.assertEqual(0, result.exit_code)
-        self.assertEqual(
-            [["bash", "--noprofile", "--norc", "-ec", "echo hello\ntox\n"]],
-            [proc._args["args"] for proc in processes_fixture.procs],
-        )
-
-
-class RunPipelineTestCase(CommandBaseTestCase):
-    def setUp(self):
-        super().setUp()
-        self.useFixture(EnvironmentVariable("LPCRAFT_MANAGED_MODE", None))
         tmp_project_dir = self.useFixture(TempDir()).join("test-project")
         os.mkdir(tmp_project_dir)
         cwd = os.getcwd()
@@ -358,9 +142,13 @@ class RunPipelineTestCase(CommandBaseTestCase):
 
         self.assertEqual(0, result.exit_code)
         self.assertEqual(
+            ["focal"],
+            [c.kwargs["image_name"] for c in launcher.call_args_list],
+        )
+        self.assertEqual(
             [
                 call(
-                    ["lpcraft", "run", "--series", "focal", "test"],
+                    ["bash", "--noprofile", "--norc", "-ec", "tox"],
                     cwd=Path("/root/project"),
                     stdout=ANY,
                     stderr=ANY,
@@ -371,6 +159,39 @@ class RunPipelineTestCase(CommandBaseTestCase):
 
     @patch("lpcraft.commands.run.get_provider")
     @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64")
+    def test_no_run_definition(
+        self, mock_get_host_architecture, mock_get_provider
+    ):
+        mock_get_provider.return_value = self.makeLXDProvider()
+        config = dedent(
+            """
+            pipeline:
+                - test
+
+            jobs:
+                test:
+                    series: focal
+                    architectures: amd64
+            """
+        )
+        Path(".launchpad.yaml").write_text(config)
+
+        result = self.run_command("run")
+
+        self.assertThat(
+            result,
+            MatchesStructure.byEquality(
+                exit_code=1,
+                errors=[
+                    CommandError(
+                        "Job 'test' for focal/amd64 does not set 'run'"
+                    )
+                ],
+            ),
+        )
+
+    @patch("lpcraft.commands.run.get_provider")
+    @patch("lpcraft.commands.run.get_host_architecture", return_value="amd64")
     def test_one_job_fails(
         self, mock_get_host_architecture, mock_get_provider
     ):
@@ -414,7 +235,7 @@ class RunPipelineTestCase(CommandBaseTestCase):
             ),
         )
         execute_run.assert_called_once_with(
-            ["lpcraft", "run", "--series", "focal", "test"],
+            ["bash", "--noprofile", "--norc", "-ec", "tox"],
             cwd=Path("/root/project"),
             stdout=ANY,
             stderr=ANY,
@@ -453,15 +274,25 @@ class RunPipelineTestCase(CommandBaseTestCase):
 
         self.assertEqual(0, result.exit_code)
         self.assertEqual(
+            ["focal", "bionic"],
+            [c.kwargs["image_name"] for c in launcher.call_args_list],
+        )
+        self.assertEqual(
             [
                 call(
-                    ["lpcraft", "run", "--series", "focal", "test"],
+                    ["bash", "--noprofile", "--norc", "-ec", "tox"],
                     cwd=Path("/root/project"),
                     stdout=ANY,
                     stderr=ANY,
                 ),
                 call(
-                    ["lpcraft", "run", "--series", "bionic", "build-wheel"],
+                    [
+                        "bash",
+                        "--noprofile",
+                        "--norc",
+                        "-ec",
+                        "pyproject-build",
+                    ],
                     cwd=Path("/root/project"),
                     stdout=ANY,
                     stderr=ANY,
@@ -506,21 +337,31 @@ class RunPipelineTestCase(CommandBaseTestCase):
 
         self.assertEqual(0, result.exit_code)
         self.assertEqual(
+            ["bionic", "focal", "bionic"],
+            [c.kwargs["image_name"] for c in launcher.call_args_list],
+        )
+        self.assertEqual(
             [
                 call(
-                    ["lpcraft", "run", "--series", "bionic", "test"],
+                    ["bash", "--noprofile", "--norc", "-ec", "tox"],
                     cwd=Path("/root/project"),
                     stdout=ANY,
                     stderr=ANY,
                 ),
                 call(
-                    ["lpcraft", "run", "--series", "focal", "test"],
+                    ["bash", "--noprofile", "--norc", "-ec", "tox"],
                     cwd=Path("/root/project"),
                     stdout=ANY,
                     stderr=ANY,
                 ),
                 call(
-                    ["lpcraft", "run", "--series", "bionic", "build-wheel"],
+                    [
+                        "bash",
+                        "--noprofile",
+                        "--norc",
+                        "-ec",
+                        "pyproject-build",
+                    ],
                     cwd=Path("/root/project"),
                     stdout=ANY,
                     stderr=ANY,
diff --git a/lpcraft/env.py b/lpcraft/env.py
index 0e4503d..2e9a0be 100644
--- a/lpcraft/env.py
+++ b/lpcraft/env.py
@@ -3,7 +3,6 @@
 
 """lpcraft environment utilities."""
 
-import os
 from pathlib import Path
 
 
@@ -12,15 +11,6 @@ def get_managed_environment_home_path() -> Path:
     return Path("/root")
 
 
-def get_managed_environment_log_path() -> Path:
-    """Path for log file when running in managed environment."""
-    return Path("/tmp/lpcraft.log")
-
-
 def get_managed_environment_project_path() -> Path:
     """Path for project when running in managed environment."""
     return get_managed_environment_home_path() / "project"
-
-
-def is_managed_mode() -> bool:
-    return os.environ.get("LPCRAFT_MANAGED_MODE", "0") == "1"
diff --git a/lpcraft/main.py b/lpcraft/main.py
index 7b88a89..cf6dcf8 100644
--- a/lpcraft/main.py
+++ b/lpcraft/main.py
@@ -8,7 +8,6 @@ from argparse import ArgumentParser
 
 from craft_cli import CraftError, EmitterMode, emit
 
-from lpcraft import env
 from lpcraft._version import version_description as lpcraft_version
 from lpcraft.commands.run import run
 from lpcraft.commands.version import version
@@ -62,13 +61,6 @@ def main() -> int:
     # alongside the individual subcommands rather than here.
 
     parser_run = subparsers.add_parser("run", help=run.__doc__)
-    if env.is_managed_mode():
-        parser_run.add_argument(
-            "--series", help="Only run jobs for this series."
-        )
-        parser_run.add_argument(
-            "job_name", nargs="?", help="Only run this job name."
-        )
     parser_run.set_defaults(func=run)
 
     parser_version = subparsers.add_parser("version", help=version.__doc__)
diff --git a/lpcraft/providers/__init__.py b/lpcraft/providers/__init__.py
index 1c2fb2c..cdc1577 100644
--- a/lpcraft/providers/__init__.py
+++ b/lpcraft/providers/__init__.py
@@ -3,16 +3,8 @@
 
 __all__ = [
     "get_provider",
-    "replay_logs",
 ]
 
-import tempfile
-from pathlib import Path
-
-from craft_cli import emit
-from craft_providers import Executor
-
-from lpcraft.env import get_managed_environment_log_path
 from lpcraft.providers._base import Provider
 from lpcraft.providers._lxd import LXDProvider
 
@@ -20,27 +12,3 @@ from lpcraft.providers._lxd import LXDProvider
 def get_provider() -> Provider:
     """Get the configured or appropriate provider for the host OS."""
     return LXDProvider()
-
-
-def replay_logs(instance: Executor) -> None:
-    """Capture and re-emit log files from a provider instance."""
-    tmp = tempfile.NamedTemporaryFile(delete=False, prefix="lpcraft-")
-    tmp.close()
-    local_log_path = Path(tmp.name)
-    try:
-        remote_log_path = get_managed_environment_log_path()
-
-        try:
-            instance.pull_file(
-                source=remote_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) as local_log:
-            for line in local_log:
-                emit.trace(f":: {line.rstrip()}")
-    finally:
-        local_log_path.unlink()
diff --git a/lpcraft/providers/_base.py b/lpcraft/providers/_base.py
index 398246c..559143b 100644
--- a/lpcraft/providers/_base.py
+++ b/lpcraft/providers/_base.py
@@ -68,7 +68,6 @@ class Provider(ABC):
     def get_command_environment(self) -> Dict[str, Optional[str]]:
         """Construct the required environment."""
         env = bases.buildd.default_command_environment()
-        env["LPCRAFT_MANAGED_MODE"] = "1"
 
         # Pass through host environment that target may need.
         for env_key in ("http_proxy", "https_proxy", "no_proxy"):
diff --git a/lpcraft/providers/_buildd.py b/lpcraft/providers/_buildd.py
index 9acbc3a..f5b93b6 100644
--- a/lpcraft/providers/_buildd.py
+++ b/lpcraft/providers/_buildd.py
@@ -8,10 +8,9 @@ __all__ = [
     "SERIES_TO_BUILDD_IMAGE_ALIAS",
 ]
 
-from typing import Any, Optional
+from typing import Any
 
-from craft_providers import Executor, bases
-from craft_providers.actions import snap_installer
+from craft_providers import bases
 
 # Why can't we just pass a series name and be done with it?
 SERIES_TO_BUILDD_IMAGE_ALIAS = {
@@ -34,51 +33,6 @@ class LPCraftBuilddBaseConfiguration(bases.BuilddBase):
 
     compatibility_tag: str = f"lpcraft-{bases.BuilddBase.compatibility_tag}.0"
 
-    def _setup_lpcraft(self, *, executor: Executor) -> None:
-        """Install lpcraft in target environment.
-
-        The default behaviour is to inject the host snap into the target
-        environment.
-
-        :raises BaseConfigurationError: on error.
-        """
-        try:
-            snap_installer.inject_from_host(
-                executor=executor, snap_name="lpcraft", classic=True
-            )
-        except snap_installer.SnapInstallationError as error:
-            raise bases.BaseConfigurationError(
-                brief=(
-                    "Failed to inject host lpcraft snap into target "
-                    "environment."
-                )
-            ) from error
-
-    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, the lpcraft snap
-        is 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 the instance is incompatible.
-        :raises BaseConfigurationError: on any other unexpected error.
-        """
-        super().setup(
-            executor=executor, retry_wait=retry_wait, timeout=timeout
-        )
-        self._setup_lpcraft(executor=executor)
-
     def __eq__(self, other: Any) -> bool:
         if not isinstance(other, LPCraftBuilddBaseConfiguration):
             raise TypeError
diff --git a/lpcraft/providers/tests/__init__.py b/lpcraft/providers/tests/__init__.py
index f9e3072..c568294 100644
--- a/lpcraft/providers/tests/__init__.py
+++ b/lpcraft/providers/tests/__init__.py
@@ -4,20 +4,6 @@
 from dataclasses import dataclass
 
 from craft_providers.lxd import LXDError, LXDInstallationError
-from fixtures import MockPatch
-from testtools import TestCase
-
-
-class ProviderBaseTestCase(TestCase):
-    def setUp(self):
-        super().setUp()
-        # Patch out inherited setup steps.
-        self.useFixture(
-            MockPatch(
-                "craft_providers.bases.BuilddBase.setup",
-                lambda *args, **kwargs: None,
-            )
-        )
 
 
 @dataclass
diff --git a/lpcraft/providers/tests/test_buildd.py b/lpcraft/providers/tests/test_buildd.py
index 03898c8..e1226e8 100644
--- a/lpcraft/providers/tests/test_buildd.py
+++ b/lpcraft/providers/tests/test_buildd.py
@@ -1,53 +1,14 @@
 # Copyright 2021 Canonical Ltd.  This software is licensed under the
 # GNU General Public License version 3 (see the file LICENSE).
 
-from unittest.mock import Mock, patch
-
 import pytest
-from craft_providers import Executor, bases
-from craft_providers.actions import snap_installer
 from craft_providers.bases.buildd import BuilddBaseAlias
+from testtools import TestCase
 
-from lpcraft.providers._buildd import (
-    SERIES_TO_BUILDD_IMAGE_ALIAS,
-    LPCraftBuilddBaseConfiguration,
-)
-from lpcraft.providers.tests import ProviderBaseTestCase
-
-
-class TestLPCraftBuilddBaseConfiguration(ProviderBaseTestCase):
-    @patch("craft_providers.actions.snap_installer.inject_from_host")
-    def test_setup_inject_from_host(self, mock_inject):
-        mock_instance = Mock(spec=Executor)
-        config = LPCraftBuilddBaseConfiguration(
-            alias=SERIES_TO_BUILDD_IMAGE_ALIAS["focal"]
-        )
-
-        config.setup(executor=mock_instance)
-
-        self.assertEqual("lpcraft-buildd-base-v0.0", config.compatibility_tag)
-        mock_inject.assert_called_once_with(
-            executor=mock_instance, snap_name="lpcraft", classic=True
-        )
-
-    @patch("craft_providers.actions.snap_installer.inject_from_host")
-    def test_setup_inject_from_host_error(self, mock_inject):
-        mock_instance = Mock(spec=Executor)
-        mock_inject.side_effect = snap_installer.SnapInstallationError(
-            brief="Boom"
-        )
-        config = LPCraftBuilddBaseConfiguration(
-            alias=SERIES_TO_BUILDD_IMAGE_ALIAS["focal"]
-        )
-
-        with self.assertRaisesRegex(
-            bases.BaseConfigurationError,
-            r"^Failed to inject host lpcraft snap into target environment\.$",
-        ) as raised:
-            config.setup(executor=mock_instance)
+from lpcraft.providers._buildd import LPCraftBuilddBaseConfiguration
 
-        self.assertIsNotNone(raised.exception.__cause__)
 
+class TestLPCraftBuilddBaseConfiguration(TestCase):
     def test_compare_configuration_with_other_type(self):
         """The configuration should only be comparable to its own type"""
         with pytest.raises(TypeError):
diff --git a/lpcraft/providers/tests/test_lxd.py b/lpcraft/providers/tests/test_lxd.py
index 0e111b7..408f15a 100644
--- a/lpcraft/providers/tests/test_lxd.py
+++ b/lpcraft/providers/tests/test_lxd.py
@@ -9,11 +9,12 @@ from unittest.mock import Mock, call, patch
 
 from craft_providers.bases import BaseConfigurationError, BuilddBaseAlias
 from craft_providers.lxd import LXC, LXDError, launch
+from testtools import TestCase
 
 from lpcraft.errors import CommandError
 from lpcraft.providers._buildd import LPCraftBuilddBaseConfiguration
 from lpcraft.providers._lxd import LXDProvider, _LXDLauncher
-from lpcraft.providers.tests import FakeLXDInstaller, ProviderBaseTestCase
+from lpcraft.providers.tests import FakeLXDInstaller
 from lpcraft.tests.fixtures import RecordingEmitterFixture
 
 _base_path = (
@@ -21,7 +22,7 @@ _base_path = (
 )
 
 
-class TestLXDProvider(ProviderBaseTestCase):
+class TestLXDProvider(TestCase):
     def setUp(self):
         super().setUp()
         self.mock_path = Mock(spec=Path)
@@ -242,13 +243,7 @@ class TestLXDProvider(ProviderBaseTestCase):
 
         env = provider.get_command_environment()
 
-        self.assertEqual(
-            {
-                "LPCRAFT_MANAGED_MODE": "1",
-                "PATH": _base_path,
-            },
-            env,
-        )
+        self.assertEqual({"PATH": _base_path}, env)
 
     @patch.dict(
         os.environ,
@@ -267,7 +262,6 @@ class TestLXDProvider(ProviderBaseTestCase):
 
         self.assertEqual(
             {
-                "LPCRAFT_MANAGED_MODE": "1",
                 "PATH": _base_path,
                 "http_proxy": "test-http-proxy",
                 "https_proxy": "test-https-proxy",
@@ -299,10 +293,7 @@ class TestLXDProvider(ProviderBaseTestCase):
                         name=expected_instance_name,
                         base_configuration=LPCraftBuilddBaseConfiguration(
                             alias=BuilddBaseAlias.FOCAL,
-                            environment={
-                                "LPCRAFT_MANAGED_MODE": "1",
-                                "PATH": _base_path,
-                            },
+                            environment={"PATH": _base_path},
                             hostname=expected_instance_name,
                         ),
                         image_name="focal",
diff --git a/lpcraft/providers/tests/test_replay_logs.py b/lpcraft/providers/tests/test_replay_logs.py
deleted file mode 100644
index 0a7e9c6..0000000
--- a/lpcraft/providers/tests/test_replay_logs.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# Copyright 2021 Canonical Ltd.  This software is licensed under the
-# GNU General Public License version 3 (see the file LICENSE).
-
-from pathlib import Path
-from shutil import copyfile
-from unittest.mock import Mock
-
-from craft_providers import Executor
-from fixtures import TempDir
-
-from lpcraft.commands.tests import CommandBaseTestCase
-from lpcraft.providers import replay_logs
-from lpcraft.tests.fixtures import RecordingEmitterFixture
-
-
-class TestReplayLogs(CommandBaseTestCase):
-    def test_cannot_pull_file(self):
-        mock_instance = Mock(spec=Executor)
-        mock_instance.pull_file.side_effect = FileNotFoundError()
-
-        with RecordingEmitterFixture() as emitter:
-            replay_logs(mock_instance)
-
-        self.assertEqual(
-            ("trace", "No logs found in instance."),
-            emitter.recorder.interactions[0].args,
-        )
-
-    def test_replay_logs(self):
-        self.tempdir = Path(self.useFixture(TempDir()).path)
-        path = self.tempdir / "stub_remote_log_file"
-        path.write_text("line1\nline2\nline3")
-
-        def fake_pull_file(source, destination):
-            # use injected `path` rather than source, which would be a
-            # lpcraft.env.get_managed_environment_log_path, which is not
-            # available in a test
-            self.assertEqual(Path("/tmp/lpcraft.log"), Path(source))
-            copyfile(path, destination)
-
-        mock_instance = Mock(spec=Executor)
-        mock_instance.pull_file = fake_pull_file
-
-        with RecordingEmitterFixture() as emitter:
-            replay_logs(mock_instance)
-
-        self.assertEqual(
-            ("trace", "Logs captured from managed instance:"),
-            emitter.recorder.interactions[0].args,
-        )
-        self.assertEqual(
-            ("trace", ":: line1"), emitter.recorder.interactions[1].args
-        )
-        self.assertEqual(
-            ("trace", ":: line2"), emitter.recorder.interactions[2].args
-        )
-        self.assertEqual(
-            ("trace", ":: line3"), emitter.recorder.interactions[3].args
-        )
diff --git a/lpcraft/tests/test_env.py b/lpcraft/tests/test_env.py
index 64847fb..54a8018 100644
--- a/lpcraft/tests/test_env.py
+++ b/lpcraft/tests/test_env.py
@@ -1,9 +1,7 @@
 # Copyright 2021 Canonical Ltd.  This software is licensed under the
 # GNU General Public License version 3 (see the file LICENSE).
 
-import os
 from pathlib import Path
-from unittest.mock import patch
 
 from testtools import TestCase
 
@@ -16,24 +14,7 @@ class TestEnvironment(TestCase):
             Path("/root"), env.get_managed_environment_home_path()
         )
 
-    def test_get_managed_environment_log_path(self):
-        self.assertEqual(
-            Path("/tmp/lpcraft.log"), env.get_managed_environment_log_path()
-        )
-
     def test_get_managed_environment_project_path(self):
         self.assertEqual(
             Path("/root/project"), env.get_managed_environment_project_path()
         )
-
-    @patch.dict(os.environ, {})
-    def test_is_managed_mode_unset(self):
-        self.assertIs(False, env.is_managed_mode())
-
-    @patch.dict(os.environ, {"LPCRAFT_MANAGED_MODE": "0"})
-    def test_is_managed_mode_0(self):
-        self.assertIs(False, env.is_managed_mode())
-
-    @patch.dict(os.environ, {"LPCRAFT_MANAGED_MODE": "1"})
-    def test_is_managed_mode_1(self):
-        self.assertIs(True, env.is_managed_mode())
diff --git a/lpcraft/tests/test_utils.py b/lpcraft/tests/test_utils.py
index 9ebc83b..e33a40c 100644
--- a/lpcraft/tests/test_utils.py
+++ b/lpcraft/tests/test_utils.py
@@ -2,7 +2,6 @@
 # GNU General Public License version 3 (see the file LICENSE).
 
 import io
-import os
 import re
 from pathlib import Path
 from unittest.mock import patch
@@ -146,7 +145,3 @@ class TestAskUser(TestCase):
 
                 mock_input.assert_called_once_with("prompt [y/N]: ")
                 mock_input.reset_mock()
-
-    @patch.dict(os.environ, {"LPCRAFT_MANAGED_MODE": "1"})
-    def test_errors_in_managed_mode(self):
-        self.assertRaises(RuntimeError, ask_user, "prompt")
diff --git a/lpcraft/utils.py b/lpcraft/utils.py
index 22c866c..afddfdb 100644
--- a/lpcraft/utils.py
+++ b/lpcraft/utils.py
@@ -15,7 +15,6 @@ from typing import Any, Dict
 
 import yaml
 
-from lpcraft.env import is_managed_mode
 from lpcraft.errors import YAMLError
 
 
@@ -57,9 +56,6 @@ def ask_user(prompt: str, default: bool = False) -> bool:
     :return: True if answer starts with [yY], False if answer starts with
         [nN], otherwise the default.
     """
-    if is_managed_mode():
-        raise RuntimeError("confirmation not yet supported in managed mode")
-
     if not sys.stdin.isatty():
         return default