Merge pull request #1122 from mwhudson/configure-apt-python
implement the bulk of subiquity-configure-apt in the server process
This commit is contained in:
commit
71b410ef4c
|
@ -1,67 +1,3 @@
|
|||
#!/bin/bash -eux
|
||||
|
||||
# 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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# 2. Bind-mount the cdrom into the installed system as /run/cdrom
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# 4. Configure apt in /target to look at the pool on the cdrom. This has two
|
||||
# subcases:
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# b. if the network is not expected to be working, we replace the
|
||||
# sources.list with a file just referencing the cdrom.
|
||||
#
|
||||
# 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).
|
||||
|
||||
if [ -z "$TARGET_MOUNT_POINT" ]; then
|
||||
# Nothing good can happen from running this script without
|
||||
# TARGET_MOUNT_POINT set.
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
PY=$1
|
||||
HAS_NETWORK=$2
|
||||
|
||||
$PY -m curtin apt-config
|
||||
|
||||
setup_overlay () {
|
||||
local dir=$1
|
||||
local t=$(mktemp -d)
|
||||
mkdir $t/work $t/upper
|
||||
mount -t overlay overlay -o lowerdir=$dir,workdir=$t/work,upperdir=$t/upper $dir
|
||||
}
|
||||
|
||||
setup_overlay $TARGET_MOUNT_POINT/etc/apt
|
||||
|
||||
mkdir -p "$TARGET_MOUNT_POINT/cdrom"
|
||||
mount --bind /cdrom "$TARGET_MOUNT_POINT/cdrom"
|
||||
|
||||
if [ $HAS_NETWORK = 'true' ]; then
|
||||
mv "$TARGET_MOUNT_POINT/etc/apt/sources.list" "$TARGET_MOUNT_POINT/etc/apt/sources.list.d/original.list"
|
||||
else
|
||||
# Do not use the configured proxy during the install if one was configured.
|
||||
rm -f $TARGET_MOUNT_POINT/etc/apt/apt.conf.d/90curtin-aptproxy
|
||||
setup_overlay $TARGET_MOUNT_POINT/var/lib/apt/lists
|
||||
fi
|
||||
|
||||
cat > "$TARGET_MOUNT_POINT/etc/apt/sources.list" <<EOF
|
||||
deb [check-date=no] file:///cdrom $(chroot $TARGET_MOUNT_POINT lsb_release -sc) main restricted
|
||||
EOF
|
||||
|
||||
$PY -m curtin in-target -- apt-get update
|
||||
curl --fail --unix-socket /run/subiquity/socket --data '' a/install/configure_apt
|
||||
|
|
|
@ -294,6 +294,11 @@ 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): ...
|
||||
|
||||
|
|
|
@ -72,6 +72,4 @@ class MirrorModel(object):
|
|||
config["uri"] = mirror
|
||||
|
||||
def render(self):
|
||||
return {
|
||||
'apt': copy.deepcopy(self.config)
|
||||
}
|
||||
return {}
|
||||
|
|
|
@ -18,7 +18,6 @@ from collections import OrderedDict
|
|||
import functools
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import uuid
|
||||
import yaml
|
||||
|
||||
|
@ -338,7 +337,6 @@ class SubiquityModel:
|
|||
'curthooks_commands': {
|
||||
'001-configure-apt': [
|
||||
resource_path('bin/subiquity-configure-apt'),
|
||||
sys.executable, str(self.network.has_network).lower(),
|
||||
],
|
||||
},
|
||||
|
||||
|
|
|
@ -182,14 +182,7 @@ class TestSubiquityModel(unittest.TestCase):
|
|||
mirror_val = 'http://my-mirror'
|
||||
model.mirror.set_mirror(mirror_val)
|
||||
config = model.render()
|
||||
from curtin.commands.apt_config import get_mirror
|
||||
try:
|
||||
from curtin.distro import get_architecture
|
||||
except ImportError:
|
||||
from curtin.util import get_architecture
|
||||
self.assertEqual(
|
||||
get_mirror(config["apt"], "primary", get_architecture()),
|
||||
mirror_val)
|
||||
self.assertNotIn('apt', config)
|
||||
|
||||
def test_cloud_init_user_list_merge(self):
|
||||
main_user = IdentityData(
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
# Copyright 2021 Canonical, Ltd.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
from curtin.util import write_file
|
||||
|
||||
import yaml
|
||||
|
||||
from subiquitycore.lsb_release import lsb_release
|
||||
|
||||
from subiquity.server.curtin import run_curtin_command
|
||||
|
||||
|
||||
class AptConfigurer:
|
||||
|
||||
def __init__(self, app, target):
|
||||
self.app = app
|
||||
self.target = target
|
||||
self._mounts = []
|
||||
self._tdirs = []
|
||||
|
||||
def tdir(self):
|
||||
d = tempfile.mkdtemp()
|
||||
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:
|
||||
opts.extend(['-o', options])
|
||||
if type is not None:
|
||||
opts.extend(['-t', type])
|
||||
await self.app.command_runner.run(
|
||||
['mount'] + opts + [device, mountpoint])
|
||||
self._mounts.append(mountpoint)
|
||||
|
||||
async def unmount(self, mountpoint):
|
||||
await self.app.command_runner.run(['umount', mountpoint])
|
||||
|
||||
async def setup_overlay(self, dir):
|
||||
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}')
|
||||
|
||||
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.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# 2. Bind-mount the cdrom into the installed system as /cdrom.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# 4. Configure apt in /target to look at the pool on the cdrom. This
|
||||
# has two subcases:
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# b. if the network is not expected to be working, we replace the
|
||||
# sources.list with a file just referencing the cdrom.
|
||||
#
|
||||
# 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).
|
||||
|
||||
config = {
|
||||
'apt': self.app.base_model.mirror.config,
|
||||
}
|
||||
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))
|
||||
|
||||
self.app.note_data_for_apport("CurtinAptConfig", config_location)
|
||||
|
||||
await run_curtin_command(
|
||||
self.app, context, 'apt-config', '-t', self.tpath(),
|
||||
config=config_location)
|
||||
|
||||
await self.setup_overlay(self.tpath('etc/apt'))
|
||||
|
||||
os.mkdir(self.tpath('cdrom'))
|
||||
await self.mount('/cdrom', self.tpath('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'))
|
||||
else:
|
||||
os.unlink(self.tpath('etc/apt/apt.conf.d/90curtin-aptproxy'))
|
||||
await self.setup_overlay(self.tpath('var/lib/apt/lists'))
|
||||
|
||||
codename = lsb_release()['codename']
|
||||
|
||||
write_file(
|
||||
self.tpath('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(),
|
||||
"--", "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(),
|
||||
"--", "apt-get", "update")
|
|
@ -32,6 +32,7 @@ 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,
|
||||
|
@ -39,14 +40,14 @@ from subiquity.common.types import (
|
|||
from subiquity.journald import (
|
||||
journald_listen,
|
||||
)
|
||||
from subiquity.server.apt import AptConfigurer
|
||||
from subiquity.server.controller import (
|
||||
SubiquityController,
|
||||
)
|
||||
from subiquity.server.curtin import (
|
||||
run_curtin_command,
|
||||
start_curtin_command,
|
||||
)
|
||||
from subiquity.server.controller import (
|
||||
SubiquityController,
|
||||
)
|
||||
|
||||
|
||||
log = logging.getLogger("subiquity.server.controllers.install")
|
||||
|
||||
|
@ -72,6 +73,8 @@ class TracebackExtractor:
|
|||
|
||||
class InstallController(SubiquityController):
|
||||
|
||||
endpoint = API.install
|
||||
|
||||
def __init__(self, app):
|
||||
super().__init__(app)
|
||||
self.model = app.base_model
|
||||
|
@ -79,6 +82,10 @@ 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())
|
||||
|
||||
def interactive(self):
|
||||
return True
|
||||
|
||||
def stop_uu(self):
|
||||
if self.app.state == ApplicationState.UU_RUNNING:
|
||||
|
@ -123,9 +130,9 @@ class InstallController(SubiquityController):
|
|||
|
||||
@with_context(
|
||||
description="installing system", level="INFO", childlevel="DEBUG")
|
||||
async def curtin_install(self, *, context, config_location):
|
||||
async def curtin_install(self, *, context):
|
||||
await run_curtin_command(
|
||||
self.app, context, 'install', config=config_location)
|
||||
self.app, context, 'install', config=self.write_config())
|
||||
|
||||
@with_context()
|
||||
async def install(self, *, context):
|
||||
|
@ -147,14 +154,11 @@ class InstallController(SubiquityController):
|
|||
|
||||
self.app.update_state(ApplicationState.RUNNING)
|
||||
|
||||
config_location = self.write_config()
|
||||
|
||||
if os.path.exists(self.model.target):
|
||||
await self.unmount_target(
|
||||
context=context, target=self.model.target)
|
||||
|
||||
await self.curtin_install(
|
||||
context=context, config_location=config_location)
|
||||
await self.curtin_install(context=context)
|
||||
|
||||
self.app.update_state(ApplicationState.POST_WAIT)
|
||||
|
||||
|
@ -210,14 +214,7 @@ class InstallController(SubiquityController):
|
|||
|
||||
@with_context(description="restoring apt configuration")
|
||||
async def restore_apt_config(self, context):
|
||||
await self.app.command_runner.run(["umount", self.tpath('etc/apt')])
|
||||
if self.model.network.has_network:
|
||||
await run_curtin_command(
|
||||
self.app, context, "in-target", "-t", self.tpath(),
|
||||
"--", "apt-get", "update")
|
||||
else:
|
||||
await self.app.command_runner.run(
|
||||
["umount", self.tpath('var/lib/apt/lists')])
|
||||
await self.apt_configurer.deconfigure(context)
|
||||
|
||||
@with_context(description="downloading and installing {policy} updates")
|
||||
async def run_unattended_upgrades(self, context, policy):
|
||||
|
@ -257,6 +254,9 @@ 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
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import asyncio
|
||||
import copy
|
||||
import logging
|
||||
|
||||
from curtin.config import merge_config
|
||||
|
@ -78,7 +79,7 @@ class MirrorController(SubiquityController):
|
|||
self.model.set_mirror(data)
|
||||
|
||||
def make_autoinstall(self):
|
||||
r = self.model.render()['apt']
|
||||
r = copy.deepcopy(self.model.config)
|
||||
r['geoip'] = self.geoip_enabled
|
||||
return r
|
||||
|
||||
|
|
Loading…
Reference in New Issue