From 794667fecd1a885e0c31b468947fc7a6110acfbf Mon Sep 17 00:00:00 2001 From: Michael Hudson-Doyle Date: Mon, 6 Dec 2021 11:32:25 +1300 Subject: [PATCH 1/7] move setting up installation source to SourceController --- subiquity/server/controllers/install.py | 14 +++----------- subiquity/server/controllers/source.py | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/subiquity/server/controllers/install.py b/subiquity/server/controllers/install.py index 21b292e1..594eb8a1 100644 --- a/subiquity/server/controllers/install.py +++ b/subiquity/server/controllers/install.py @@ -19,12 +19,11 @@ import os import re import shutil -from curtin.commands.extract import get_handler_for_source from curtin.commands.install import ( ERROR_TARFILE, INSTALL_LOG, ) -from curtin.util import sanitize_source, write_file +from curtin.util import write_file import yaml @@ -157,15 +156,8 @@ class InstallController(SubiquityController): self.app.update_state(ApplicationState.RUNNING) - handler = get_handler_for_source( - sanitize_source(self.model.source.get_source())) - - if self.app.opts.dry_run: - path = '/' - else: - path = handler.setup() - - self.apt_configurer = get_apt_configurer(self.app, path) + self.apt_configurer = get_apt_configurer( + self.app, self.app.controllers.Source.source_path) for_install_path = await self.configure_apt(context=context) diff --git a/subiquity/server/controllers/source.py b/subiquity/server/controllers/source.py index fe9d211b..c76f61ca 100644 --- a/subiquity/server/controllers/source.py +++ b/subiquity/server/controllers/source.py @@ -15,6 +15,9 @@ import os +from curtin.commands.extract import get_handler_for_source +from curtin.util import sanitize_source + from subiquity.common.apidef import API from subiquity.common.types import ( SourceSelection, @@ -48,6 +51,11 @@ class SourceController(SubiquityController): endpoint = API.source + def __init__(self, app): + super().__init__(app) + self._handler = None + self.source_path = None + def start(self): path = '/cdrom/casper/install-sources.yaml' if self.app.opts.source_catalog is not None: @@ -80,6 +88,14 @@ class SourceController(SubiquityController): self.model.current.id) async def configured(self): + if self._handler is not None: + self._handler.cleanup() + self._handler = get_handler_for_source( + sanitize_source(self.model.get_source())) + if self.app.opts.dry_run: + self.source_path = '/' + else: + self.source_path = self._handler.setup() await super().configured() self.app.base_model.set_source_variant(self.model.current.variant) From 2de2ef7381a0048e2c2aafd6aba81e7f05b26e29 Mon Sep 17 00:00:00 2001 From: Michael Hudson-Doyle Date: Mon, 6 Dec 2021 12:09:50 +1300 Subject: [PATCH 2/7] move apt_configurer to MirrorController --- subiquity/server/apt.py | 12 ++++++++---- subiquity/server/controllers/install.py | 11 ++++------- subiquity/server/controllers/mirror.py | 20 ++++++++++++++++++++ 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/subiquity/server/apt.py b/subiquity/server/apt.py index 4d8eb221..5d01eb4b 100644 --- a/subiquity/server/apt.py +++ b/subiquity/server/apt.py @@ -150,6 +150,12 @@ class AptConfigurer: return for_install + async def cleanup(self): + for m in reversed(self._mounts): + await self.unmount(m) + for d in self._tdirs: + shutil.rmtree(d) + async def deconfigure(self, context, target): await self.unmount(f'{target}/cdrom') os.rmdir(f'{target}/cdrom') @@ -168,10 +174,8 @@ class AptConfigurer: self.app, context, "in-target", "-t", target, "--", "apt-get", "update") - for m in reversed(self._mounts): - await self.unmount(m) - for d in self._tdirs: - shutil.rmtree(d) + await self.cleanup() + if self.app.base_model.network.has_network: await run_curtin_command( self.app, context, "in-target", "-t", target, diff --git a/subiquity/server/controllers/install.py b/subiquity/server/controllers/install.py index 594eb8a1..cec84ca0 100644 --- a/subiquity/server/controllers/install.py +++ b/subiquity/server/controllers/install.py @@ -39,7 +39,6 @@ from subiquity.common.types import ( from subiquity.journald import ( journald_listen, ) -from subiquity.server.apt import get_apt_configurer from subiquity.server.controller import ( SubiquityController, ) @@ -79,7 +78,6 @@ class InstallController(SubiquityController): self.unattended_upgrades_cmd = None self.unattended_upgrades_ctx = None self.tb_extractor = TracebackExtractor() - self.apt_configurer = None def interactive(self): return True @@ -128,7 +126,8 @@ class InstallController(SubiquityController): @with_context( description="configuring apt", level="INFO", childlevel="DEBUG") async def configure_apt(self, *, context): - return await self.apt_configurer.configure(context) + configurer = self.app.controllers.Mirror.apt_configurer + return await configurer.configure(context) @with_context( description="installing system", level="INFO", childlevel="DEBUG") @@ -156,9 +155,6 @@ class InstallController(SubiquityController): self.app.update_state(ApplicationState.RUNNING) - self.apt_configurer = get_apt_configurer( - self.app, self.app.controllers.Source.source_path) - for_install_path = await self.configure_apt(context=context) if os.path.exists(self.model.target): @@ -223,7 +219,8 @@ class InstallController(SubiquityController): @with_context(description="restoring apt configuration") async def restore_apt_config(self, context): - await self.apt_configurer.deconfigure(context, self.tpath()) + configurer = self.app.controllers.Mirror.apt_configurer + await configurer.deconfigure(context, self.tpath()) @with_context(description="downloading and installing {policy} updates") async def run_unattended_upgrades(self, context, policy): diff --git a/subiquity/server/controllers/mirror.py b/subiquity/server/controllers/mirror.py index e623ba4c..ff6a4e37 100644 --- a/subiquity/server/controllers/mirror.py +++ b/subiquity/server/controllers/mirror.py @@ -23,6 +23,7 @@ from curtin.config import merge_config from subiquitycore.context import with_context from subiquity.common.apidef import API +from subiquity.server.apt import get_apt_configurer from subiquity.server.controller import SubiquityController from subiquity.server.types import InstallerChannels @@ -57,7 +58,11 @@ class MirrorController(SubiquityController): super().__init__(app) self.geoip_enabled = True self.app.hub.subscribe(InstallerChannels.GEOIP, self.on_geoip) + self.app.hub.subscribe( + (InstallerChannels.CONFIGURED, 'source'), self.on_source) self.cc_event = asyncio.Event() + self.configured_once = True + self.apt_configurer = None def load_autoinstall_data(self, data): if data is None: @@ -81,6 +86,10 @@ class MirrorController(SubiquityController): self.model.set_country(self.app.geoip.countrycode) self.cc_event.set() + def on_source(self): + if self.configured_once: + self.make_apt_configurer() + def serialize(self): return self.model.get_mirror() @@ -92,6 +101,17 @@ class MirrorController(SubiquityController): r['geoip'] = self.geoip_enabled return r + async def configured(self): + await super().configured() + self.configured_once = True + self.make_apt_configurer() + + def make_apt_configurer(self): + if self.apt_configurer is not None: + self.apt_configurer.cleanup() + self.apt_configurer = get_apt_configurer( + self.app, self.app.controllers.Source.source_path) + async def GET(self) -> str: return self.model.get_mirror() From 75d9f38dd138afe1acc0f6bdd85547ea0bfce4fd Mon Sep 17 00:00:00 2001 From: Michael Hudson-Doyle Date: Mon, 6 Dec 2021 12:53:21 +1300 Subject: [PATCH 3/7] refactor apt configuration a bit using some stuff I originally wrote for livefs-editor --- subiquity/server/apt.py | 128 ++++++++++++++++++++++++++++------------ 1 file changed, 89 insertions(+), 39 deletions(-) diff --git a/subiquity/server/apt.py b/subiquity/server/apt.py index 5d01eb4b..c122f60b 100644 --- a/subiquity/server/apt.py +++ b/subiquity/server/apt.py @@ -29,12 +29,38 @@ from subiquitycore.utils import arun_command from subiquity.server.curtin import run_curtin_command +class _MountBase: + + def p(self, *args): + for a in args: + if a.startswith('/'): + raise Exception('no absolute paths here please') + return os.path.join(self.mountpoint, *args) + + def write(self, path, content): + with open(self.p(path), 'w') as fp: + fp.write(content) + + +class Mountpoint(_MountBase): + def __init__(self, *, mountpoint): + self.mountpoint = mountpoint + + +class OverlayMountpoint(_MountBase): + def __init__(self, *, lowers, upperdir, mountpoint): + self.lowers = lowers + self.upperdir = upperdir + self.mountpoint = mountpoint + + class AptConfigurer: def __init__(self, app, source): self.app = app self.source = source - self.configured = None + self.configured_tree = None + self.install_mount = None self._mounts = [] self._tdirs = [] @@ -51,21 +77,42 @@ class AptConfigurer: opts.extend(['-t', type]) await self.app.command_runner.run( ['mount'] + opts + [device, mountpoint]) - self._mounts.append(mountpoint) + m = Mountpoint(mountpoint=mountpoint) + self._mounts.append(m) + return m async def unmount(self, mountpoint): await self.app.command_runner.run(['umount', mountpoint]) - async def setup_overlay(self, source, target): + async def setup_overlay(self, lowers): tdir = self.tdir() - w = f'{tdir}/work' - u = f'{tdir}/upper' - for d in w, u: + target = f'{tdir}/mount' + workdir = f'{tdir}/work' + upperdir = f'{tdir}/upper' + for d in target, workdir, upperdir: os.mkdir(d) - await self.mount( - 'overlay', target, type='overlay', - options=f'lowerdir={source},upperdir={u},workdir={w}') - return u + + def lowerdir_for(lower): + if isinstance(lower, str): + return lower + if isinstance(lower, Mountpoint): + return lower.p() + if isinstance(lower, OverlayMountpoint): + return lowerdir_for(lower.lowers) + if isinstance(lower, list): + return ':'.join(reversed([lowerdir_for(ll) for ll in lower])) + raise Exception(f'lowerdir_for({lower!r})') + + lowerdir = lowerdir_for(lowers) + options = f'lowerdir={lowerdir},upperdir={upperdir},workdir={workdir}' + + mount = await self.mount( + 'overlay', target, options=options, type='overlay') + + return OverlayMountpoint( + lowers=lowers, + mountpoint=mount.p(), + upperdir=upperdir) def apt_config(self): cfg = {} @@ -77,11 +124,11 @@ class AptConfigurer: # Configure apt so that installs from the pool on the cdrom are # preferred during installation but not in the installed system. # - # First we create an overlay ('configured') over the installation + # First we create an overlay ('configured_tree') over the installation # source and configure that overlay as we want the target system to # end up by running curtin's apt-config subcommand. # - # Then we create a fresh overlay ('for_install') over the first one + # Then we create a fresh overlay ('install_tree') over the first one # and configure it for the installation. This means: # # 1. Bind-mounting /cdrom into this new overlay. @@ -106,9 +153,7 @@ class AptConfigurer: # system, or if it is not, just copy /var/lib/apt/lists from the # 'configured' overlay. - self.configured = self.tdir() - - config_upper = await self.setup_overlay(self.source, self.configured) + self.configured_tree = await self.setup_overlay(self.source) config_location = os.path.join( self.app.root, 'var/log/installer/subiquity-curtin-apt.conf') @@ -120,75 +165,77 @@ class AptConfigurer: self.app.note_data_for_apport("CurtinAptConfig", config_location) await run_curtin_command( - self.app, context, 'apt-config', '-t', self.configured, + self.app, context, 'apt-config', '-t', self.configured_tree.p(), config=config_location) - for_install = self.tdir() - await self.setup_overlay(config_upper + ':' + self.source, for_install) + self.install_tree = await self.setup_overlay(self.configured_tree) - os.mkdir(f'{for_install}/cdrom') - await self.mount('/cdrom', f'{for_install}/cdrom', options='bind') + os.mkdir(self.install_tree.p('cdrom')) + await self.mount( + '/cdrom', self.install_tree.p('cdrom'), options='bind') if self.app.base_model.network.has_network: os.rename( - f'{for_install}/etc/apt/sources.list', - f'{for_install}/etc/apt/sources.list.d/original.list') + self.install_tree.p('etc/apt/sources.list'), + self.install_tree.p('etc/apt/sources.list.d/original.list')) else: - proxy_path = f'{for_install}/etc/apt/apt.conf.d/90curtin-aptproxy' + proxy_path = self.install_tree.p( + 'etc/apt/apt.conf.d/90curtin-aptproxy') if os.path.exists(proxy_path): os.unlink(proxy_path) codename = lsb_release()['codename'] write_file( - f'{for_install}/etc/apt/sources.list', + self.install_tree.p('etc/apt/sources.list'), f'deb [check-date=no] file:///cdrom {codename} main restricted\n') await run_curtin_command( - self.app, context, "in-target", "-t", for_install, + self.app, context, "in-target", "-t", self.install_tree.p(), "--", "apt-get", "update") - return for_install + return self.install_tree.p() async def cleanup(self): for m in reversed(self._mounts): - await self.unmount(m) + await self.unmount(m.mountpoint) for d in self._tdirs: shutil.rmtree(d) async def deconfigure(self, context, target): - await self.unmount(f'{target}/cdrom') - os.rmdir(f'{target}/cdrom') + target = Mountpoint(mountpoint=target) + + await self.unmount(target.p('cdrom')) + os.rmdir(target.p('cdrom')) restore_dirs = ['etc/apt'] if not self.app.base_model.network.has_network: restore_dirs.append('var/lib/apt/lists') for dir in restore_dirs: - shutil.rmtree(f'{target}/{dir}') + shutil.rmtree(target.p(dir)) await self.app.command_runner.run([ - 'cp', '-aT', f'{self.configured}/{dir}', f'{target}/{dir}', + 'cp', '-aT', self.configured_tree.p(dir), target.p(dir), ]) if self.app.base_model.network.has_network: await run_curtin_command( - self.app, context, "in-target", "-t", target, + self.app, context, "in-target", "-t", target.p(), "--", "apt-get", "update") await self.cleanup() if self.app.base_model.network.has_network: await run_curtin_command( - self.app, context, "in-target", "-t", target, + self.app, context, "in-target", "-t", target.p(), "--", "apt-get", "update") class DryRunAptConfigurer(AptConfigurer): - async def setup_overlay(self, source, target): - if source.startswith('u+'): - # Please excuse the obscure way the path is transmitted - # from the first invocation of this method to the second :/ - source = source.split(':')[0][2:] + async def setup_overlay(self, source): + if isinstance(source, OverlayMountpoint): + source = source.lowers[0] + target = self.tdir() os.mkdir(f'{target}/etc') await arun_command([ 'cp', '-aT', f'{source}/etc/apt', f'{target}/etc/apt', @@ -196,7 +243,10 @@ class DryRunAptConfigurer(AptConfigurer): if os.path.isdir(f'{target}/etc/apt/sources.list.d'): shutil.rmtree(f'{target}/etc/apt/sources.list.d') os.mkdir(f'{target}/etc/apt/sources.list.d') - return 'u+' + target + return OverlayMountpoint( + lowers=[source], + mountpoint=target, + upperdir=None) async def deconfigure(self, context, target): return From 93432ccbb1f804458b1ed4b6e8968f5b6c60d69b Mon Sep 17 00:00:00 2001 From: Michael Hudson-Doyle Date: Mon, 6 Dec 2021 13:26:11 +1300 Subject: [PATCH 4/7] split apt configuration into two methods --- subiquity/server/apt.py | 87 ++++++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 40 deletions(-) diff --git a/subiquity/server/apt.py b/subiquity/server/apt.py index c122f60b..81745fa2 100644 --- a/subiquity/server/apt.py +++ b/subiquity/server/apt.py @@ -120,6 +120,51 @@ class AptConfigurer: merge_config(cfg, self.app.base_model.proxy.get_apt_config()) return {'apt': cfg} + async def apply_apt_config(self, context): + self.configured_tree = await self.setup_overlay(self.source) + + config_location = os.path.join( + self.app.root, 'var/log/installer/subiquity-curtin-apt.conf') + + datestr = '# Autogenerated by Subiquity: {} UTC\n'.format( + str(datetime.datetime.utcnow())) + write_file(config_location, datestr + yaml.dump(self.apt_config())) + + self.app.note_data_for_apport("CurtinAptConfig", config_location) + + await run_curtin_command( + self.app, context, 'apt-config', '-t', self.configured_tree.p(), + config=config_location) + + async def configure_for_install(self, context): + assert self.configured_tree is not None + + self.install_tree = await self.setup_overlay(self.configured_tree) + + os.mkdir(self.install_tree.p('cdrom')) + await self.mount( + '/cdrom', self.install_tree.p('cdrom'), options='bind') + + if self.app.base_model.network.has_network: + os.rename( + self.install_tree.p('etc/apt/sources.list'), + self.install_tree.p('etc/apt/sources.list.d/original.list')) + else: + proxy_path = self.install_tree.p( + 'etc/apt/apt.conf.d/90curtin-aptproxy') + if os.path.exists(proxy_path): + os.unlink(proxy_path) + + codename = lsb_release()['codename'] + + write_file( + self.install_tree.p('etc/apt/sources.list'), + f'deb [check-date=no] file:///cdrom {codename} main restricted\n') + + await run_curtin_command( + self.app, context, "in-target", "-t", self.install_tree.p(), + "--", "apt-get", "update") + async def configure(self, context): # Configure apt so that installs from the pool on the cdrom are # preferred during installation but not in the installed system. @@ -153,46 +198,8 @@ class AptConfigurer: # system, or if it is not, just copy /var/lib/apt/lists from the # 'configured' overlay. - self.configured_tree = await self.setup_overlay(self.source) - - config_location = os.path.join( - self.app.root, 'var/log/installer/subiquity-curtin-apt.conf') - - datestr = '# Autogenerated by Subiquity: {} UTC\n'.format( - str(datetime.datetime.utcnow())) - write_file(config_location, datestr + yaml.dump(self.apt_config())) - - self.app.note_data_for_apport("CurtinAptConfig", config_location) - - await run_curtin_command( - self.app, context, 'apt-config', '-t', self.configured_tree.p(), - config=config_location) - - self.install_tree = await self.setup_overlay(self.configured_tree) - - os.mkdir(self.install_tree.p('cdrom')) - await self.mount( - '/cdrom', self.install_tree.p('cdrom'), options='bind') - - if self.app.base_model.network.has_network: - os.rename( - self.install_tree.p('etc/apt/sources.list'), - self.install_tree.p('etc/apt/sources.list.d/original.list')) - else: - proxy_path = self.install_tree.p( - 'etc/apt/apt.conf.d/90curtin-aptproxy') - if os.path.exists(proxy_path): - os.unlink(proxy_path) - - codename = lsb_release()['codename'] - - write_file( - self.install_tree.p('etc/apt/sources.list'), - f'deb [check-date=no] file:///cdrom {codename} main restricted\n') - - await run_curtin_command( - self.app, context, "in-target", "-t", self.install_tree.p(), - "--", "apt-get", "update") + await self.apply_apt_config(context) + await self.configure_for_install(context) return self.install_tree.p() From 06d42129a48adba6e445c397071ffd96a28b951d Mon Sep 17 00:00:00 2001 From: Michael Hudson-Doyle Date: Mon, 6 Dec 2021 14:02:08 +1300 Subject: [PATCH 5/7] do the apt config application asap --- subiquity/server/apt.py | 70 ++++++++++++------------- subiquity/server/controllers/install.py | 5 +- subiquity/server/controllers/mirror.py | 15 ++++-- 3 files changed, 49 insertions(+), 41 deletions(-) diff --git a/subiquity/server/apt.py b/subiquity/server/apt.py index 81745fa2..8bf70a7d 100644 --- a/subiquity/server/apt.py +++ b/subiquity/server/apt.py @@ -55,6 +55,40 @@ class OverlayMountpoint(_MountBase): class AptConfigurer: + # We configure apt during installation so that installs from the pool on + # the cdrom are preferred during installation but remove this again in the + # installed system. + # + # First we create an overlay ('configured_tree') over the installation + # source and configure that overlay as we want the target system to end up + # by running curtin's apt-config subcommand. This is done in the + # apply_apt_config method. + # + # Then in configure_for_install we create a fresh overlay ('install_tree') + # over the first one and configure it for the installation. This means: + # + # 1. Bind-mounting /cdrom into this new overlay. + # + # 2. When the network is expected to be working, copying the original + # /etc/apt/sources.list to /etc/apt/sources.list.d/original.list. + # + # 3. writing "deb file:///cdrom $(lsb_release -sc) main restricted" + # to /etc/apt/sources.list. + # + # 4. running "apt-get update" in the new overlay. + # + # When the install is done the deconfigure method makes the installed + # system's apt state look as if the pool had never been configured. So + # this means: + # + # 1. Removing /cdrom from the installed system. + # + # 2. Copying /etc/apt from the 'configured' overlay to the installed + # system. + # + # 3. If the network is working, run apt-get update in the installed + # system, or if it is not, just copy /var/lib/apt/lists from the + # 'configured_tree' overlay. def __init__(self, app, source): self.app = app @@ -165,42 +199,6 @@ class AptConfigurer: self.app, context, "in-target", "-t", self.install_tree.p(), "--", "apt-get", "update") - async def configure(self, context): - # Configure apt so that installs from the pool on the cdrom are - # preferred during installation but not in the installed system. - # - # First we create an overlay ('configured_tree') over the installation - # source and configure that overlay as we want the target system to - # end up by running curtin's apt-config subcommand. - # - # Then we create a fresh overlay ('install_tree') over the first one - # and configure it for the installation. This means: - # - # 1. Bind-mounting /cdrom into this new overlay. - # - # 2. When the network is expected to be working, copying the original - # /etc/apt/sources.list to /etc/apt/sources.list.d/original.list. - # - # 3. writing "deb file:///cdrom $(lsb_release -sc) main restricted" - # to /etc/apt/sources.list. - # - # 4. running "apt-get update" in the new overlay. - # - # When the install is done we try to make the installed system's apt - # state look as if the pool had never been configured. So this means: - # - # 1. Removing /cdrom from the installed system. - # - # 2. Copying /etc/apt from the 'configured' overlay to the installed - # system. - # - # 3. If the network is working, run apt-get update in the installed - # system, or if it is not, just copy /var/lib/apt/lists from the - # 'configured' overlay. - - await self.apply_apt_config(context) - await self.configure_for_install(context) - return self.install_tree.p() async def cleanup(self): diff --git a/subiquity/server/controllers/install.py b/subiquity/server/controllers/install.py index cec84ca0..6e17728c 100644 --- a/subiquity/server/controllers/install.py +++ b/subiquity/server/controllers/install.py @@ -126,8 +126,9 @@ class InstallController(SubiquityController): @with_context( description="configuring apt", level="INFO", childlevel="DEBUG") async def configure_apt(self, *, context): - configurer = self.app.controllers.Mirror.apt_configurer - return await configurer.configure(context) + mirror = self.app.controllers.Mirror + configurer = await mirror.wait_config() + return await configurer.configure_for_install(context) @with_context( description="installing system", level="INFO", childlevel="DEBUG") diff --git a/subiquity/server/controllers/mirror.py b/subiquity/server/controllers/mirror.py index ff6a4e37..9524a7a1 100644 --- a/subiquity/server/controllers/mirror.py +++ b/subiquity/server/controllers/mirror.py @@ -20,6 +20,7 @@ from typing import List from curtin.config import merge_config +from subiquitycore.async_helpers import SingleInstanceTask from subiquitycore.context import with_context from subiquity.common.apidef import API @@ -62,6 +63,9 @@ class MirrorController(SubiquityController): (InstallerChannels.CONFIGURED, 'source'), self.on_source) self.cc_event = asyncio.Event() self.configured_once = True + self._apt_config_key = None + self._apply_apt_config_task = SingleInstanceTask( + self._apply_apt_config) self.apt_configurer = None def load_autoinstall_data(self, data): @@ -88,7 +92,7 @@ class MirrorController(SubiquityController): def on_source(self): if self.configured_once: - self.make_apt_configurer() + self._apply_apt_config_task.start_sync() def serialize(self): return self.model.get_mirror() @@ -104,13 +108,18 @@ class MirrorController(SubiquityController): async def configured(self): await super().configured() self.configured_once = True - self.make_apt_configurer() + self._apply_apt_config_task.start_sync() - def make_apt_configurer(self): + async def _apply_apt_config(self): if self.apt_configurer is not None: self.apt_configurer.cleanup() self.apt_configurer = get_apt_configurer( self.app, self.app.controllers.Source.source_path) + await self.apt_configurer.apply_apt_config(self.context) + + async def wait_config(self): + await self._apply_apt_config_task.wait() + return self.apt_configurer async def GET(self) -> str: return self.model.get_mirror() From 45ef2338cd4de4fbd5a5a8f980d4b122e67f82b4 Mon Sep 17 00:00:00 2001 From: Michael Hudson-Doyle Date: Mon, 6 Dec 2021 14:27:35 +1300 Subject: [PATCH 6/7] another small refactor --- subiquity/server/apt.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/subiquity/server/apt.py b/subiquity/server/apt.py index 8bf70a7d..92939880 100644 --- a/subiquity/server/apt.py +++ b/subiquity/server/apt.py @@ -210,22 +210,23 @@ class AptConfigurer: async def deconfigure(self, context, target): target = Mountpoint(mountpoint=target) - await self.unmount(target.p('cdrom')) - os.rmdir(target.p('cdrom')) - - restore_dirs = ['etc/apt'] - if not self.app.base_model.network.has_network: - restore_dirs.append('var/lib/apt/lists') - for dir in restore_dirs: + async def _restore_dir(dir): shutil.rmtree(target.p(dir)) await self.app.command_runner.run([ 'cp', '-aT', self.configured_tree.p(dir), target.p(dir), ]) + await self.unmount(target.p('cdrom')) + os.rmdir(target.p('cdrom')) + + await _restore_dir('etc/apt') + if self.app.base_model.network.has_network: await run_curtin_command( self.app, context, "in-target", "-t", target.p(), "--", "apt-get", "update") + else: + await _restore_dir('var/lib/apt/lists') await self.cleanup() From 447b5730ec029359aaedc3b527a8d1a3366448e9 Mon Sep 17 00:00:00 2001 From: Michael Hudson-Doyle Date: Wed, 8 Dec 2021 13:56:04 +1300 Subject: [PATCH 7/7] refactor the lowerdir_for calculation and fix a bug --- subiquity/server/apt.py | 44 +++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/subiquity/server/apt.py b/subiquity/server/apt.py index 92939880..c6bfc563 100644 --- a/subiquity/server/apt.py +++ b/subiquity/server/apt.py @@ -14,6 +14,7 @@ # along with this program. If not, see . import datetime +import functools import os import shutil import tempfile @@ -54,6 +55,34 @@ class OverlayMountpoint(_MountBase): self.mountpoint = mountpoint +@functools.singledispatch +def lowerdir_for(x): + """Return value suitable for passing to the lowerdir= overlayfs option.""" + raise NotImplementedError(x) + + +@lowerdir_for.register(str) +def _lowerdir_for_str(path): + return path + + +@lowerdir_for.register(Mountpoint) +def _lowerdir_for_mnt(mnt): + return mnt.mountpoint + + +@lowerdir_for.register(OverlayMountpoint) +def _lowerdir_for_ovmnt(ovmnt): + # One cannot indefinitely stack overlayfses so construct an + # explicit list of the layers of the overlayfs. + return lowerdir_for(ovmnt.lowers + [ovmnt.upperdir]) + + +@lowerdir_for.register(list) +def _lowerdir_for_lst(lst): + return ':'.join(reversed([lowerdir_for(item) for item in lst])) + + class AptConfigurer: # We configure apt during installation so that installs from the pool on # the cdrom are preferred during installation but remove this again in the @@ -121,23 +150,12 @@ class AptConfigurer: async def setup_overlay(self, lowers): tdir = self.tdir() target = f'{tdir}/mount' - workdir = f'{tdir}/work' + lowerdir = lowerdir_for(lowers) upperdir = f'{tdir}/upper' + workdir = f'{tdir}/work' for d in target, workdir, upperdir: os.mkdir(d) - def lowerdir_for(lower): - if isinstance(lower, str): - return lower - if isinstance(lower, Mountpoint): - return lower.p() - if isinstance(lower, OverlayMountpoint): - return lowerdir_for(lower.lowers) - if isinstance(lower, list): - return ':'.join(reversed([lowerdir_for(ll) for ll in lower])) - raise Exception(f'lowerdir_for({lower!r})') - - lowerdir = lowerdir_for(lowers) options = f'lowerdir={lowerdir},upperdir={upperdir},workdir={workdir}' mount = await self.mount(