Merge pull request #1123 from mwhudson/configure-apt-earlier
configure apt before starting install
This commit is contained in:
commit
e9892e5032
|
@ -1,3 +0,0 @@
|
|||
#!/bin/bash -eux
|
||||
|
||||
curl --fail --unix-socket /run/subiquity/socket --data '' a/install/configure_apt
|
1
setup.py
1
setup.py
|
@ -108,7 +108,6 @@ setup(name='subiquity',
|
|||
'bin/console-conf-wait',
|
||||
'bin/console-conf-wrapper',
|
||||
'bin/subiquity-debug',
|
||||
'bin/subiquity-configure-apt',
|
||||
'bin/subiquity-loadkeys',
|
||||
'bin/subiquity-service',
|
||||
'bin/subiquity-server',
|
||||
|
|
|
@ -42,7 +42,7 @@ parts:
|
|||
plugin: python
|
||||
source-type: git
|
||||
source: https://git.launchpad.net/curtin
|
||||
source-commit: 809817fdac257de1769e385a42aeaf3d5bcc2c60
|
||||
source-commit: f13edc1ac71dde678149b4918ee923b25a1b7039
|
||||
build-packages:
|
||||
- shared-mime-info
|
||||
- zlib1g-dev
|
||||
|
|
|
@ -294,11 +294,6 @@ class API:
|
|||
def GET() -> TimeZoneInfo: ...
|
||||
def POST(tz: str): ...
|
||||
|
||||
class install:
|
||||
class configure_apt:
|
||||
def POST():
|
||||
"Not for client use."
|
||||
|
||||
class shutdown:
|
||||
def POST(mode: ShutdownMode, immediate: bool = False): ...
|
||||
|
||||
|
|
|
@ -87,7 +87,7 @@ class SourceModel:
|
|||
if self.current is None:
|
||||
self.current = self.sources[0]
|
||||
|
||||
def render(self):
|
||||
def get_source(self):
|
||||
path = os.path.join(self._dir, self.current.path)
|
||||
if self.current.preinstalled_langs:
|
||||
base, ext = os.path.splitext(path)
|
||||
|
@ -97,8 +97,7 @@ class SourceModel:
|
|||
suffix = 'no-languages'
|
||||
path = base + '.' + suffix + ext
|
||||
scheme = self.current.type
|
||||
return {
|
||||
'sources': {
|
||||
'ubuntu00': f'{scheme}://{path}'
|
||||
},
|
||||
}
|
||||
return f'{scheme}://{path}'
|
||||
|
||||
def render(self):
|
||||
return {}
|
||||
|
|
|
@ -26,7 +26,7 @@ from curtin.config import merge_config
|
|||
|
||||
from subiquitycore.file_util import write_file
|
||||
|
||||
from subiquity.common.resources import resource_path, get_users_and_groups
|
||||
from subiquity.common.resources import get_users_and_groups
|
||||
from subiquity.server.types import InstallerChannels
|
||||
|
||||
from .filesystem import FilesystemModel
|
||||
|
@ -335,8 +335,8 @@ class SubiquityModel:
|
|||
'stages': stages,
|
||||
|
||||
'curthooks_commands': {
|
||||
'001-configure-apt': [
|
||||
resource_path('bin/subiquity-configure-apt'),
|
||||
'001-mount-cdrom': [
|
||||
'mount', '--bind', '/cdrom', '/target/cdrom',
|
||||
],
|
||||
},
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ def make_entry(**fields):
|
|||
return raw
|
||||
|
||||
|
||||
class TestMirrorModel(unittest.TestCase):
|
||||
class TestSourceModel(unittest.TestCase):
|
||||
|
||||
def tdir(self):
|
||||
tdir = tempfile.mkdtemp()
|
||||
|
@ -88,7 +88,7 @@ class TestMirrorModel(unittest.TestCase):
|
|||
self.write_and_load_entries(model, entries)
|
||||
self.assertEqual(model.current.id, 'id2')
|
||||
|
||||
def test_render_absolute(self):
|
||||
def test_get_source_absolute(self):
|
||||
entry = make_entry(
|
||||
type='scheme',
|
||||
path='/foo/bar/baz',
|
||||
|
@ -96,9 +96,9 @@ class TestMirrorModel(unittest.TestCase):
|
|||
model = SourceModel()
|
||||
self.write_and_load_entries(model, [entry])
|
||||
self.assertEqual(
|
||||
model.render(), {'sources': {'ubuntu00': 'scheme:///foo/bar/baz'}})
|
||||
model.get_source(), 'scheme:///foo/bar/baz')
|
||||
|
||||
def test_render_relative(self):
|
||||
def test_get_source_relative(self):
|
||||
dir = self.tdir()
|
||||
entry = make_entry(
|
||||
type='scheme',
|
||||
|
@ -107,11 +107,15 @@ class TestMirrorModel(unittest.TestCase):
|
|||
model = SourceModel()
|
||||
self.write_and_load_entries(model, [entry], dir)
|
||||
self.assertEqual(
|
||||
model.render(),
|
||||
{'sources': {'ubuntu00': f'scheme://{dir}/foo/bar/baz'}})
|
||||
model.get_source(),
|
||||
f'scheme://{dir}/foo/bar/baz')
|
||||
|
||||
def test_render_initial(self):
|
||||
def test_get_source_initial(self):
|
||||
model = SourceModel()
|
||||
self.assertEqual(
|
||||
model.render(),
|
||||
{'sources': {'ubuntu00': 'cp:///media/filesystem'}})
|
||||
model.get_source(),
|
||||
'cp:///media/filesystem')
|
||||
|
||||
def test_render(self):
|
||||
model = SourceModel()
|
||||
self.assertEqual(model.render(), {})
|
||||
|
|
|
@ -172,10 +172,10 @@ class TestSubiquityModel(unittest.TestCase):
|
|||
netplan = yaml.safe_load(netplan_content)
|
||||
self.assertConfigHasVal(netplan, 'network.version', 2)
|
||||
|
||||
def test_has_sources(self):
|
||||
def test_sources(self):
|
||||
model = self.make_model()
|
||||
config = model.render()
|
||||
self.assertIn('sources', config)
|
||||
self.assertNotIn('sources', config)
|
||||
|
||||
def test_mirror(self):
|
||||
model = self.make_model()
|
||||
|
|
|
@ -23,15 +23,17 @@ from curtin.util import write_file
|
|||
import yaml
|
||||
|
||||
from subiquitycore.lsb_release import lsb_release
|
||||
from subiquitycore.utils import arun_command
|
||||
|
||||
from subiquity.server.curtin import run_curtin_command
|
||||
|
||||
|
||||
class AptConfigurer:
|
||||
|
||||
def __init__(self, app, target):
|
||||
def __init__(self, app, source):
|
||||
self.app = app
|
||||
self.target = target
|
||||
self.source = source
|
||||
self.configured = None
|
||||
self._mounts = []
|
||||
self._tdirs = []
|
||||
|
||||
|
@ -40,9 +42,6 @@ class AptConfigurer:
|
|||
self._tdirs.append(d)
|
||||
return d
|
||||
|
||||
def tpath(self, *args):
|
||||
return os.path.join(self.target, *args)
|
||||
|
||||
async def mount(self, device, mountpoint, options=None, type=None):
|
||||
opts = []
|
||||
if options is not None:
|
||||
|
@ -56,47 +55,53 @@ class AptConfigurer:
|
|||
async def unmount(self, mountpoint):
|
||||
await self.app.command_runner.run(['umount', mountpoint])
|
||||
|
||||
async def setup_overlay(self, dir):
|
||||
async def setup_overlay(self, source, target):
|
||||
tdir = self.tdir()
|
||||
w = f'{tdir}/work'
|
||||
u = f'{tdir}/upper'
|
||||
for d in w, u:
|
||||
os.mkdir(d)
|
||||
await self.mount(
|
||||
'overlay', dir, type='overlay',
|
||||
options=f'lowerdir={dir},upperdir={u},workdir={w}')
|
||||
'overlay', target, type='overlay',
|
||||
options=f'lowerdir={source},upperdir={u},workdir={w}')
|
||||
return u
|
||||
|
||||
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.
|
||||
#
|
||||
# This has a few steps.
|
||||
# First we create an overlay ('configured') over the installation
|
||||
# source and configure that overlay as we want the target system to
|
||||
# end up by running curtin's apt-config subcommand.
|
||||
#
|
||||
# 1. As the remaining steps mean that any changes to apt configuration
|
||||
# are do not persist into the installed system, we get curtin to
|
||||
# configure apt a bit earlier than it would by default.
|
||||
# Then we create a fresh overlay ('for_install') over the first one
|
||||
# and configure it for the installation. This means:
|
||||
#
|
||||
# 2. Bind-mount the cdrom into the installed system as /cdrom.
|
||||
# 1. Bind-mounting /cdrom into this new overlay.
|
||||
#
|
||||
# 3. Set up an overlay over /target/etc/apt. This means that any
|
||||
# changes we make will not persist into the installed system and we
|
||||
# do not have to worry about cleaning up after ourselves.
|
||||
# 2. When the network is expected to be working, copying the original
|
||||
# /etc/apt/sources.list to /etc/apt/sources.list.d/original.list.
|
||||
#
|
||||
# 4. Configure apt in /target to look at the pool on the cdrom. This
|
||||
# has two subcases:
|
||||
# 3. writing "deb file:///cdrom $(lsb_release -sc) main restricted"
|
||||
# to /etc/apt/sources.list.
|
||||
#
|
||||
# a. if we expect the network to be working, this basically means
|
||||
# prepending
|
||||
# "deb file:///run/cdrom $(lsb_release -sc) main restricted"
|
||||
# to the sources.list file.
|
||||
# 4. running "apt-get update" in the new overlay.
|
||||
#
|
||||
# b. if the network is not expected to be working, we replace the
|
||||
# sources.list with a file just referencing the cdrom.
|
||||
# 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:
|
||||
#
|
||||
# 5. If the network is not expected to be working, we also set up an
|
||||
# overlay over /target/var/lib/apt/lists (if the network is working,
|
||||
# we'll run "apt update" after the /target/etc/apt overlay has been
|
||||
# cleared).
|
||||
# 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.
|
||||
|
||||
self.configured = self.tdir()
|
||||
|
||||
config_upper = await self.setup_overlay(self.source, self.configured)
|
||||
|
||||
config = {
|
||||
'apt': self.app.base_model.mirror.config,
|
||||
|
@ -104,50 +109,93 @@ class AptConfigurer:
|
|||
config_location = os.path.join(
|
||||
self.app.root, 'var/log/installer/subiquity-curtin-apt.conf')
|
||||
|
||||
with open(config_location, 'w') as conf:
|
||||
datestr = '# Autogenerated by Subiquity: {} UTC\n'.format(
|
||||
str(datetime.datetime.utcnow()))
|
||||
conf.write(datestr)
|
||||
conf.write(yaml.dump(config))
|
||||
write_file(config_location, datestr + yaml.dump(config))
|
||||
|
||||
self.app.note_data_for_apport("CurtinAptConfig", config_location)
|
||||
|
||||
await run_curtin_command(
|
||||
self.app, context, 'apt-config', '-t', self.tpath(),
|
||||
self.app, context, 'apt-config', '-t', self.configured,
|
||||
config=config_location)
|
||||
|
||||
await self.setup_overlay(self.tpath('etc/apt'))
|
||||
for_install = self.tdir()
|
||||
await self.setup_overlay(config_upper + ':' + self.source, for_install)
|
||||
|
||||
os.mkdir(self.tpath('cdrom'))
|
||||
await self.mount('/cdrom', self.tpath('cdrom'), options='bind')
|
||||
os.mkdir(f'{for_install}/cdrom')
|
||||
await self.mount('/cdrom', f'{for_install}/cdrom', options='bind')
|
||||
|
||||
if self.app.base_model.network.has_network:
|
||||
os.rename(
|
||||
self.tpath('etc/apt/sources.list'),
|
||||
self.tpath('etc/apt/sources.list.d/original.list'))
|
||||
f'{for_install}/etc/apt/sources.list',
|
||||
f'{for_install}/etc/apt/sources.list.d/original.list')
|
||||
else:
|
||||
os.unlink(self.tpath('etc/apt/apt.conf.d/90curtin-aptproxy'))
|
||||
await self.setup_overlay(self.tpath('var/lib/apt/lists'))
|
||||
proxy_path = f'{for_install}/etc/apt/apt.conf.d/90curtin-aptproxy'
|
||||
if os.path.exists(proxy_path):
|
||||
os.unlink(proxy_path)
|
||||
|
||||
codename = lsb_release()['codename']
|
||||
|
||||
write_file(
|
||||
self.tpath('etc/apt/sources.list'),
|
||||
f'deb [check-date=no] file:///cdrom {codename} main restricted\n',
|
||||
)
|
||||
f'{for_install}/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.tpath(),
|
||||
self.app, context, "in-target", "-t", for_install,
|
||||
"--", "apt-get", "update")
|
||||
|
||||
return for_install
|
||||
|
||||
async def deconfigure(self, context, target):
|
||||
await self.unmount(f'{target}/cdrom')
|
||||
os.rmdir(f'{target}/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}')
|
||||
await self.app.command_runner.run([
|
||||
'cp', '-aT', f'{self.configured}/{dir}', f'{target}/{dir}',
|
||||
])
|
||||
|
||||
if self.app.base_model.network.has_network:
|
||||
await run_curtin_command(
|
||||
self.app, context, "in-target", "-t", target,
|
||||
"--", "apt-get", "update")
|
||||
|
||||
async def deconfigure(self, context):
|
||||
for m in reversed(self._mounts):
|
||||
await self.unmount(m)
|
||||
for d in self._tdirs:
|
||||
shutil.rmtree(d)
|
||||
if not self.app.opts.dry_run:
|
||||
os.rmdir(self.tpath('cdrom'))
|
||||
if self.app.base_model.network.has_network:
|
||||
await run_curtin_command(
|
||||
self.app, context, "in-target", "-t", self.tpath(),
|
||||
self.app, context, "in-target", "-t", target,
|
||||
"--", "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:]
|
||||
os.mkdir(f'{target}/etc')
|
||||
await arun_command([
|
||||
'cp', '-aT', f'{source}/etc/apt', f'{target}/etc/apt',
|
||||
], check=True)
|
||||
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
|
||||
|
||||
async def deconfigure(self, context, target):
|
||||
return
|
||||
|
||||
|
||||
def get_apt_configurer(app, source):
|
||||
if app.opts.dry_run:
|
||||
return DryRunAptConfigurer(app, source)
|
||||
else:
|
||||
return AptConfigurer(app, source)
|
||||
|
|
|
@ -19,11 +19,12 @@ 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 write_file
|
||||
from curtin.util import sanitize_source, write_file
|
||||
|
||||
import yaml
|
||||
|
||||
|
@ -32,7 +33,6 @@ from subiquitycore.async_helpers import (
|
|||
)
|
||||
from subiquitycore.context import with_context
|
||||
|
||||
from subiquity.common.apidef import API
|
||||
from subiquity.common.errorreport import ErrorReportKind
|
||||
from subiquity.common.types import (
|
||||
ApplicationState,
|
||||
|
@ -40,7 +40,7 @@ from subiquity.common.types import (
|
|||
from subiquity.journald import (
|
||||
journald_listen,
|
||||
)
|
||||
from subiquity.server.apt import AptConfigurer
|
||||
from subiquity.server.apt import get_apt_configurer
|
||||
from subiquity.server.controller import (
|
||||
SubiquityController,
|
||||
)
|
||||
|
@ -73,8 +73,6 @@ class TracebackExtractor:
|
|||
|
||||
class InstallController(SubiquityController):
|
||||
|
||||
endpoint = API.install
|
||||
|
||||
def __init__(self, app):
|
||||
super().__init__(app)
|
||||
self.model = app.base_model
|
||||
|
@ -82,7 +80,7 @@ class InstallController(SubiquityController):
|
|||
self.unattended_upgrades_cmd = None
|
||||
self.unattended_upgrades_ctx = None
|
||||
self.tb_extractor = TracebackExtractor()
|
||||
self.apt_configurer = AptConfigurer(self.app, self.tpath())
|
||||
self.apt_configurer = None
|
||||
|
||||
def interactive(self):
|
||||
return True
|
||||
|
@ -128,11 +126,16 @@ class InstallController(SubiquityController):
|
|||
if not self.app.opts.dry_run:
|
||||
shutil.rmtree(target)
|
||||
|
||||
@with_context(
|
||||
description="configuring apt", level="INFO", childlevel="DEBUG")
|
||||
async def configure_apt(self, *, context):
|
||||
return await self.apt_configurer.configure(context)
|
||||
|
||||
@with_context(
|
||||
description="installing system", level="INFO", childlevel="DEBUG")
|
||||
async def curtin_install(self, *, context):
|
||||
async def curtin_install(self, *, context, source):
|
||||
await run_curtin_command(
|
||||
self.app, context, 'install', config=self.write_config())
|
||||
self.app, context, 'install', source, config=self.write_config())
|
||||
|
||||
@with_context()
|
||||
async def install(self, *, context):
|
||||
|
@ -154,11 +157,24 @@ 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)
|
||||
|
||||
for_install_path = await self.configure_apt()
|
||||
|
||||
if os.path.exists(self.model.target):
|
||||
await self.unmount_target(
|
||||
context=context, target=self.model.target)
|
||||
|
||||
await self.curtin_install(context=context)
|
||||
await self.curtin_install(
|
||||
context=context, source='cp://' + for_install_path)
|
||||
|
||||
self.app.update_state(ApplicationState.POST_WAIT)
|
||||
|
||||
|
@ -214,7 +230,7 @@ class InstallController(SubiquityController):
|
|||
|
||||
@with_context(description="restoring apt configuration")
|
||||
async def restore_apt_config(self, context):
|
||||
await self.apt_configurer.deconfigure(context)
|
||||
await self.apt_configurer.deconfigure(context, self.tpath())
|
||||
|
||||
@with_context(description="downloading and installing {policy} updates")
|
||||
async def run_unattended_upgrades(self, context, policy):
|
||||
|
@ -254,9 +270,6 @@ class InstallController(SubiquityController):
|
|||
self.unattended_upgrades_cmd is not None:
|
||||
self.unattended_upgrades_cmd.proc.terminate()
|
||||
|
||||
async def configure_apt_POST(self, context):
|
||||
await self.apt_configurer.configure(context)
|
||||
|
||||
|
||||
uu_apt_conf = b"""\
|
||||
# Config for the unattended-upgrades run to avoid failing on battery power or
|
||||
|
|
Loading…
Reference in New Issue