Merge pull request #1417 from mwhudson/install-step-refactor

refactor CurtinInstallStep a bit
This commit is contained in:
Michael Hudson-Doyle 2022-09-15 08:44:22 +12:00 committed by GitHub
commit 60d6746a41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 100 additions and 99 deletions

View File

@ -438,12 +438,6 @@ class SubiquityModel:
def render(self): def render(self):
config = { config = {
'curthooks_commands': {
'001-mount-cdrom': [
'mount', '--bind', '/cdrom', '/target/cdrom',
],
},
'grub': { 'grub': {
'terminal': 'unmodified', 'terminal': 'unmodified',
'probe_additional_os': True 'probe_additional_os': True

View File

@ -278,6 +278,11 @@ class AptConfigurer:
await self.cleanup() await self.cleanup()
async def setup_target(self):
# Call this after the rootfs has been extracted to the real target
# system but before any configuration is applied to it.
await self.mount('/cdrom', '/target/cdrom', options='bind')
class DryRunAptConfigurer(AptConfigurer): class DryRunAptConfigurer(AptConfigurer):

View File

@ -19,7 +19,6 @@ import os
from pathlib import Path from pathlib import Path
import re import re
import shutil import shutil
import subprocess
import tempfile import tempfile
from typing import Any, Callable, Dict, List from typing import Any, Callable, Dict, List
@ -75,14 +74,41 @@ class TracebackExtractor:
@attr.s(auto_attribs=True) @attr.s(auto_attribs=True)
class CurtinInstallStep: class CurtinInstallStep:
""" Represents the parameters of a single step (i.e., invocation of curtin """Represents the parameters of a single invocation of curtin install."""
install). """ controller: "InstallController"
name: str name: str
stages: List[str] stages: List[str]
config_file: Path config_file: Path
log_file: Path log_file: Path
error_file: Path error_file: Path
acquire_config: Callable[["CurtinInstallStep", Path], Dict[str, Any]] resume_data_file: Path
source: str
acquire_config: Callable[["CurtinInstallStep"], Dict[str, Any]]
@with_context(
description="executing curtin install {self.name} step")
async def run(self, context):
""" Run a curtin install step. """
self.controller.app.note_file_for_apport(
f"Curtin{self.name}Config", str(self.config_file))
self.controller.write_config(
config_file=self.config_file,
config=self.acquire_config(self)
)
# Make sure the log directory exists.
self.log_file.parent.mkdir(parents=True, exist_ok=True)
# Add a marker to identify the step in the log file.
with open(str(self.log_file), mode="a") as fh:
fh.write(f"\n---- [[ subiquity step {self.name} ]] ----\n")
await run_curtin_command(
self.controller.app, context, "install", self.source,
"--set", f'json:stages={json.dumps(self.stages)}',
config=str(self.config_file), private_mounts=False)
class InstallController(SubiquityController): class InstallController(SubiquityController):
@ -120,19 +146,19 @@ class InstallController(SubiquityController):
config_file.parent.mkdir(parents=True, exist_ok=True) config_file.parent.mkdir(parents=True, exist_ok=True)
generate_config_yaml(str(config_file), config) generate_config_yaml(str(config_file), config)
def acquire_generic_config(self, step: CurtinInstallStep, def acquire_generic_config(self,
resume_data_file: Path) -> Dict[str, Any]: step: CurtinInstallStep) -> Dict[str, Any]:
""" Return a dictionary object to be used as the configuration of a """ Return a dictionary object to be used as the configuration of a
generic curtin install step. """ generic curtin install step. """
config = self.model.render() config = self.model.render()
config["install"]["log_file"] = str(step.log_file) config["install"]["log_file"] = str(step.log_file)
config["install"]["log_file_append"] = True config["install"]["log_file_append"] = True
config["install"]["error_tarfile"] = str(step.error_file) config["install"]["error_tarfile"] = str(step.error_file)
config["install"]["resume_data"] = str(resume_data_file) config["install"]["resume_data"] = str(step.resume_data_file)
return config return config
def acquire_initial_config(self, step: CurtinInstallStep, def acquire_initial_config(self,
resume_data_file: Path) -> Dict[str, Any]: step: CurtinInstallStep) -> Dict[str, Any]:
""" Return a dictionary object to be used as the configuration of the """ Return a dictionary object to be used as the configuration of the
initial curtin install step. """ initial curtin install step. """
return { return {
@ -144,7 +170,7 @@ class InstallController(SubiquityController):
"log_file": str(step.log_file), "log_file": str(step.log_file),
"log_file_append": True, "log_file_append": True,
"error_tarfile": str(step.error_file), "error_tarfile": str(step.error_file),
"resume_data": str(resume_data_file), "resume_data": str(step.resume_data_file),
} }
} }
@ -162,34 +188,9 @@ class InstallController(SubiquityController):
configurer = await mirror.wait_config() configurer = await mirror.wait_config()
return await configurer.configure_for_install(context) return await configurer.configure_for_install(context)
@with_context( async def setup_target(self, context):
description="executing curtin install {step.name} step") mirror = self.app.controllers.Mirror
async def run_curtin_install_step( await mirror.apt_configurer.setup_target()
self, *, context, step: CurtinInstallStep, resume_data_file: Path,
source) -> subprocess.CompletedProcess:
""" Run a curtin install step. """
self.app.note_file_for_apport(
f"Curtin{step.name}Config", str(step.config_file))
self.write_config(
config_file=step.config_file,
config=step.acquire_config(step, resume_data_file)
)
# Make sure the log directory exists.
step.log_file.parent.mkdir(parents=True, exist_ok=True)
# Add a marker to identify the step in the log file.
with open(str(step.log_file), mode="a") as fh:
fh.write(f"\n---- [[ subiquity step {step.name} ]] ----\n")
return await run_curtin_command(
self.app, context,
"install", source,
"--set", f'json:stages={json.dumps(step.stages)}',
config=str(step.config_file),
private_mounts=False)
@with_context( @with_context(
description="installing system", level="INFO", childlevel="DEBUG") description="installing system", level="INFO", childlevel="DEBUG")
@ -210,45 +211,42 @@ class InstallController(SubiquityController):
resume_data_file = Path(tempfile.mkdtemp()) / "resume-data.json" resume_data_file = Path(tempfile.mkdtemp()) / "resume-data.json"
await self.run_curtin_install_step(step=CurtinInstallStep( def make_curtin_step(name, stages, acquire_config):
name="initial", return CurtinInstallStep(
stages=[], controller=self,
config_file=config_dir / "subiquity-initial.conf", name=name,
stages=stages,
config_file=config_dir / f"subiquity-{name}.conf",
log_file=install_log_file, log_file=install_log_file,
error_file=error_file, error_file=error_file,
acquire_config=self.acquire_initial_config, resume_data_file=resume_data_file,
), resume_data_file=resume_data_file, source=source,
context=context, source=source) acquire_config=acquire_config,
)
generic_steps = [ generic_steps = [
CurtinInstallStep( make_curtin_step(
name="partitioning", "initial",
stages=["partitioning"], stages=[],
config_file=config_dir / "subiquity-partitioning.conf", acquire_config=self.acquire_initial_config
log_file=install_log_file, ).run,
error_file=error_file, make_curtin_step(
name="partitioning", stages=["partitioning"],
acquire_config=self.acquire_generic_config, acquire_config=self.acquire_generic_config,
), CurtinInstallStep( ).run,
name="extract", make_curtin_step(
stages=["extract"], name="extract", stages=["extract"],
config_file=config_dir / "subiquity-extract.conf",
log_file=install_log_file,
error_file=error_file,
acquire_config=self.acquire_generic_config, acquire_config=self.acquire_generic_config,
), CurtinInstallStep( ).run,
name="curthooks", self.setup_target,
stages=["curthooks"], make_curtin_step(
config_file=config_dir / "subiquity-curthooks.conf", name="curthooks", stages=["curthooks"],
log_file=install_log_file,
error_file=error_file,
acquire_config=self.acquire_generic_config, acquire_config=self.acquire_generic_config,
), ).run,
] ]
for step in generic_steps: for step in generic_steps:
await self.run_curtin_install_step( await step(context=context)
step=step, resume_data_file=resume_data_file,
context=context, source=source)
@with_context() @with_context()
async def install(self, *, context): async def install(self, *, context):

View File

@ -38,19 +38,19 @@ class TestWriteConfig(unittest.IsolatedAsyncioTestCase):
@patch("subiquity.server.controllers.install.run_curtin_command") @patch("subiquity.server.controllers.install.run_curtin_command")
async def test_run_curtin_install_step(self, run_cmd): async def test_run_curtin_install_step(self, run_cmd):
step = CurtinInstallStep( step = CurtinInstallStep(
name="MyStep", controller=self.controller,
stages=["partitioning", "extract"], name="MyStep",
config_file=Path("/config.yaml"), stages=["partitioning", "extract"],
log_file=Path("/logfile.log"), config_file=Path("/config.yaml"),
error_file=Path("/error.tar"), log_file=Path("/logfile.log"),
acquire_config=self.controller.acquire_initial_config) error_file=Path("/error.tar"),
resume_data_file=Path("/resume-data.json"),
source="/source",
acquire_config=self.controller.acquire_initial_config)
with patch("subiquity.server.controllers.install.open", with patch("subiquity.server.controllers.install.open",
mock_open()) as m_open: mock_open()) as m_open:
await self.controller.run_curtin_install_step( await step.run(context=step.controller.context)
step=step,
resume_data_file=Path("/resume-data.json"),
source="/source")
m_open.assert_called_once_with("/logfile.log", mode="a") m_open.assert_called_once_with("/logfile.log", mode="a")
@ -64,15 +64,17 @@ class TestWriteConfig(unittest.IsolatedAsyncioTestCase):
def test_acquire_initial_config(self): def test_acquire_initial_config(self):
step = CurtinInstallStep( step = CurtinInstallStep(
name="initial", controller=self.controller,
stages=["initial"], name="initial",
config_file=Path("/config-initial.yaml"), stages=["initial"],
log_file=Path("/logfile-initial.log"), config_file=Path("/config-initial.yaml"),
error_file=Path("/error-initial.tar"), log_file=Path("/logfile-initial.log"),
acquire_config=self.controller.acquire_initial_config) error_file=Path("/error-initial.tar"),
resume_data_file=Path("/resume-data.json"),
source="/source",
acquire_config=self.controller.acquire_initial_config)
config = self.controller.acquire_initial_config( config = self.controller.acquire_initial_config(step=step)
step=step, resume_data_file=Path("/resume-data.json"))
self.assertDictEqual(config, { self.assertDictEqual(config, {
"install": { "install": {
@ -89,17 +91,19 @@ class TestWriteConfig(unittest.IsolatedAsyncioTestCase):
def test_acquire_generic_config(self): def test_acquire_generic_config(self):
step = CurtinInstallStep( step = CurtinInstallStep(
name="partitioning", controller=self.controller,
stages=["partitioning"], name="partitioning",
config_file=Path("/config-partitioning.yaml"), stages=["partitioning"],
log_file=Path("/logfile-partitioning.log"), config_file=Path("/config-partitioning.yaml"),
error_file=Path("/error-partitioning.tar"), log_file=Path("/logfile-partitioning.log"),
acquire_config=self.controller.acquire_initial_config) error_file=Path("/error-partitioning.tar"),
resume_data_file=Path("/resume-data.json"),
source="/source",
acquire_config=self.controller.acquire_initial_config)
with patch.object(self.controller.model, "render", with patch.object(self.controller.model, "render",
return_value={"install": {}}): return_value={"install": {}}):
config = self.controller.acquire_generic_config( config = self.controller.acquire_generic_config(step=step)
step=step, resume_data_file=Path("/resume-data.json"))
self.assertEqual(config["install"]["log_file"], self.assertEqual(config["install"]["log_file"],
"/logfile-partitioning.log") "/logfile-partitioning.log")