Merge pull request #1058 from dbungert/fr-1187-apt-components-v2

apt: disable_components
This commit is contained in:
Dan Bungert 2021-11-23 11:12:24 -07:00 committed by GitHub
commit cde7150446
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 135 additions and 6 deletions

View File

@ -281,6 +281,19 @@
},
"sources": {
"type": "object"
},
"disable_components": {
"type": "array",
"items": {
"type": "string",
"enum": [
"universe",
"multiverse",
"restricted",
"contrib",
"non-free"
]
}
}
}
},

View File

@ -60,7 +60,6 @@ class API:
"""The API offered by the subiquity installer process."""
identity = simple_endpoint(IdentityData)
locale = simple_endpoint(str)
mirror = simple_endpoint(str)
proxy = simple_endpoint(str)
ssh = simple_endpoint(SSHData)
updates = simple_endpoint(str)
@ -96,6 +95,14 @@ class API:
class ssh_info:
def GET() -> Optional[LiveSessionSSHInfo]: ...
class free_only:
def GET() -> bool: ...
def POST(enable: bool) -> None:
"""Enable or disable free-only mode. Currently only controlls
the list of components. free-only choice must be made prior to
confirmation of filesystem changes"""
class errors:
class wait:
def GET(error_ref: ErrorReportRef) -> ErrorReportRef:
@ -297,6 +304,14 @@ class API:
class shutdown:
def POST(mode: ShutdownMode, immediate: bool = False): ...
class mirror:
def GET() -> str: ...
def POST(data: Payload[str]): ...
class disable_components:
def GET() -> List[str]: ...
def POST(data: Payload[List[str]]): ...
class LinkAction(enum.Enum):
NEW = enum.auto()

View File

@ -51,12 +51,18 @@ class MirrorModel(object):
self.config = copy.deepcopy(DEFAULT)
self.architecture = get_architecture()
self.default_mirror = self.get_mirror()
self.disable_components = set()
def is_default(self):
def get_config(self):
config = copy.deepcopy(self.config)
config['disable_components'] = list(self.disable_components)
return config
def mirror_is_default(self):
return self.get_mirror() == self.default_mirror
def set_country(self, cc):
if not self.is_default():
if not self.mirror_is_default():
return
uri = self.get_mirror()
parsed = parse.urlparse(uri)

View File

@ -42,3 +42,13 @@ class TestMirrorModel(unittest.TestCase):
model.set_mirror("http://mymirror.invalid/")
model.set_country("CC")
self.assertEqual(model.get_mirror(), "http://mymirror.invalid/")
def test_default_disable_components(self):
config = MirrorModel().get_config()
self.assertEqual([], config['disable_components'])
def test_set_disable_components(self):
model = MirrorModel()
model.disable_components = set(['universe'])
config = model.get_config()
self.assertEqual(['universe'], config['disable_components'])

View File

@ -104,7 +104,7 @@ class AptConfigurer:
config_upper = await self.setup_overlay(self.source, self.configured)
config = {
'apt': self.app.base_model.mirror.config,
'apt': self.app.base_model.mirror.get_config(),
}
config_location = os.path.join(
self.app.root, 'var/log/installer/subiquity-curtin-apt.conf')

View File

@ -16,6 +16,7 @@
import asyncio
import copy
import logging
from typing import List
from curtin.config import merge_config
@ -40,8 +41,16 @@ class MirrorController(SubiquityController):
'primary': {'type': 'array'},
'geoip': {'type': 'boolean'},
'sources': {'type': 'object'},
},
'disable_components': {
'type': 'array',
'items': {
'type': 'string',
'enum': ['universe', 'multiverse', 'restricted',
'contrib', 'non-free']
}
}
}
}
model_name = "mirror"
def __init__(self, app):
@ -55,7 +64,7 @@ class MirrorController(SubiquityController):
return
geoip = data.pop('geoip', True)
merge_config(self.model.config, data)
self.geoip_enabled = geoip and self.model.is_default()
self.geoip_enabled = geoip and self.model.mirror_is_default()
@with_context()
async def apply_autoinstall_config(self, context):
@ -89,3 +98,9 @@ class MirrorController(SubiquityController):
async def POST(self, data: str):
self.model.set_mirror(data)
await self.configured()
async def disable_components_GET(self) -> List[str]:
return list(self.model.disable_components)
async def disable_components_POST(self, data: List[str]):
self.model.disable_components = set(data)

View File

@ -0,0 +1,38 @@
# 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 jsonschema
import unittest
from subiquity.server.controllers.mirror import MirrorController
class TestMirrorSchema(unittest.TestCase):
def validate(self, data):
jsonschema.validate(data, MirrorController.autoinstall_schema)
def test_empty(self):
self.validate({})
def test_disable_components(self):
self.validate({'disable_components': ['universe']})
def test_no_disable_main(self):
with self.assertRaises(jsonschema.ValidationError):
self.validate({'disable_components': ['main']})
def test_no_disable_random_junk(self):
with self.assertRaises(jsonschema.ValidationError):
self.validate({'disable_components': ['not-a-component']})

View File

@ -86,6 +86,7 @@ class MetaController:
def __init__(self, app):
self.app = app
self.context = app.context.child("Meta")
self.free_only = False
async def status_GET(self, cur: Optional[ApplicationState] = None) \
-> ApplicationStatus:
@ -158,6 +159,18 @@ class MetaController:
ips=ips,
host_key_fingerprints=host_fingerprints)
async def free_only_GET(self) -> bool:
return self.free_only
async def free_only_POST(self, enable: bool) -> None:
self.free_only = enable
to_disable = {'restricted', 'multiverse'}
if enable:
# enabling free only mode means disabling components
self.app.base_model.mirror.disable_components |= to_disable
else:
self.app.base_model.mirror.disable_components -= to_disable
def get_installer_password_from_cloudinit_log():
try:

View File

@ -676,6 +676,25 @@ class TestInfo(TestAPI):
self.assertEqual('/dev/sda', sda['path'])
class TestFree(TestAPI):
@timeout(5)
async def test_free_only(self):
async with start_server('examples/simple.json') as inst:
await inst.post('/meta/free_only', enable=True)
components = await inst.get('/mirror/disable_components')
components.sort()
self.assertEqual(['multiverse', 'restricted'], components)
@timeout(5)
async def test_not_free_only(self):
async with start_server('examples/simple.json') as inst:
comps = ['universe', 'multiverse']
await inst.post('/mirror/disable_components', comps)
await inst.post('/meta/free_only', enable=False)
components = await inst.get('/mirror/disable_components')
self.assertEqual(['universe'], components)
class TestRegression(TestAPI):
@timeout()
async def test_edit_not_trigger_boot_device(self):