server side implementation of source selection
This commit is contained in:
parent
221f7f98f0
commit
a6270cbaa1
|
@ -118,11 +118,13 @@ subiquity/models/mirror.py
|
||||||
subiquity/models/network.py
|
subiquity/models/network.py
|
||||||
subiquity/models/proxy.py
|
subiquity/models/proxy.py
|
||||||
subiquity/models/snaplist.py
|
subiquity/models/snaplist.py
|
||||||
|
subiquity/models/source.py
|
||||||
subiquity/models/ssh.py
|
subiquity/models/ssh.py
|
||||||
subiquity/models/subiquity.py
|
subiquity/models/subiquity.py
|
||||||
subiquity/models/tests/__init__.py
|
subiquity/models/tests/__init__.py
|
||||||
subiquity/models/tests/test_filesystem.py
|
subiquity/models/tests/test_filesystem.py
|
||||||
subiquity/models/tests/test_mirror.py
|
subiquity/models/tests/test_mirror.py
|
||||||
|
subiquity/models/tests/test_source.py
|
||||||
subiquity/models/tests/test_subiquity.py
|
subiquity/models/tests/test_subiquity.py
|
||||||
subiquity/models/timezone.py
|
subiquity/models/timezone.py
|
||||||
subiquity/models/updates.py
|
subiquity/models/updates.py
|
||||||
|
@ -144,8 +146,11 @@ subiquity/server/controllers/refresh.py
|
||||||
subiquity/server/controllers/reporting.py
|
subiquity/server/controllers/reporting.py
|
||||||
subiquity/server/controllers/shutdown.py
|
subiquity/server/controllers/shutdown.py
|
||||||
subiquity/server/controllers/snaplist.py
|
subiquity/server/controllers/snaplist.py
|
||||||
|
subiquity/server/controllers/source.py
|
||||||
subiquity/server/controllers/ssh.py
|
subiquity/server/controllers/ssh.py
|
||||||
|
subiquity/server/controllers/tests/__init__.py
|
||||||
subiquity/server/controllers/tests/test_keyboard.py
|
subiquity/server/controllers/tests/test_keyboard.py
|
||||||
|
subiquity/server/controllers/tests/test_source.py
|
||||||
subiquity/server/controllers/timezone.py
|
subiquity/server/controllers/timezone.py
|
||||||
subiquity/server/controllers/updates.py
|
subiquity/server/controllers/updates.py
|
||||||
subiquity/server/controllers/userdata.py
|
subiquity/server/controllers/userdata.py
|
||||||
|
|
|
@ -61,6 +61,8 @@ def make_server_args_parser():
|
||||||
'--snap-section', action='store', default='server',
|
'--snap-section', action='store', default='server',
|
||||||
help=("Show snaps from this section of the store in the snap "
|
help=("Show snaps from this section of the store in the snap "
|
||||||
"list screen."))
|
"list screen."))
|
||||||
|
parser.add_argument(
|
||||||
|
'--source-catalog', dest='source_catalog', action='store')
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ from subiquity.common.types import (
|
||||||
SnapInfo,
|
SnapInfo,
|
||||||
SnapListResponse,
|
SnapListResponse,
|
||||||
SnapSelection,
|
SnapSelection,
|
||||||
|
SourceSelectionAndSetting,
|
||||||
SSHData,
|
SSHData,
|
||||||
LiveSessionSSHInfo,
|
LiveSessionSSHInfo,
|
||||||
StorageResponse,
|
StorageResponse,
|
||||||
|
@ -123,6 +124,10 @@ class API:
|
||||||
class steps:
|
class steps:
|
||||||
def GET(index: Optional[str]) -> AnyStep: ...
|
def GET(index: Optional[str]) -> AnyStep: ...
|
||||||
|
|
||||||
|
class source:
|
||||||
|
def GET() -> SourceSelectionAndSetting: ...
|
||||||
|
def POST(source_id: str) -> None: ...
|
||||||
|
|
||||||
class zdev:
|
class zdev:
|
||||||
def GET() -> List[ZdevInfo]: ...
|
def GET() -> List[ZdevInfo]: ...
|
||||||
|
|
||||||
|
|
|
@ -168,6 +168,22 @@ class KeyboardSetup:
|
||||||
layouts: List[KeyboardLayout]
|
layouts: List[KeyboardLayout]
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s(auto_attribs=True)
|
||||||
|
class SourceSelection:
|
||||||
|
name: str
|
||||||
|
description: str
|
||||||
|
id: str
|
||||||
|
size: int
|
||||||
|
variant: str
|
||||||
|
default: bool
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s(auto_attribs=True)
|
||||||
|
class SourceSelectionAndSetting:
|
||||||
|
sources: List[SourceSelection]
|
||||||
|
current_id: str
|
||||||
|
|
||||||
|
|
||||||
@attr.s(auto_attribs=True)
|
@attr.s(auto_attribs=True)
|
||||||
class ZdevInfo:
|
class ZdevInfo:
|
||||||
id: str
|
id: str
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
# 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 logging
|
||||||
|
import os
|
||||||
|
import typing
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
import attr
|
||||||
|
|
||||||
|
log = logging.getLogger('subiquity.models.source')
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s(auto_attribs=True)
|
||||||
|
class CatalogEntry:
|
||||||
|
variant: str
|
||||||
|
id: str
|
||||||
|
name: typing.Dict[str, str]
|
||||||
|
description: typing.Dict[str, str]
|
||||||
|
path: str
|
||||||
|
size: int
|
||||||
|
type: str
|
||||||
|
default: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
fake_entries = {
|
||||||
|
'server': CatalogEntry(
|
||||||
|
variant='server',
|
||||||
|
id='synthesized',
|
||||||
|
name={'en': 'Ubuntu Server'},
|
||||||
|
description={'en': 'the default'},
|
||||||
|
path='/media/filesystem',
|
||||||
|
type='cp',
|
||||||
|
default=True,
|
||||||
|
size=2 << 30),
|
||||||
|
'desktop': CatalogEntry(
|
||||||
|
variant='desktop',
|
||||||
|
id='synthesized',
|
||||||
|
name={'en': 'Ubuntu Desktop'},
|
||||||
|
description={'en': 'the default'},
|
||||||
|
path='/media/filesystem',
|
||||||
|
type='cp',
|
||||||
|
default=True,
|
||||||
|
size=5 << 30),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SourceModel:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._dir = '/cdrom/casper'
|
||||||
|
self.current = fake_entries['server']
|
||||||
|
self.sources = [self.current]
|
||||||
|
|
||||||
|
def load_from_file(self, fp):
|
||||||
|
self._dir = os.path.dirname(fp.name)
|
||||||
|
self.sources = []
|
||||||
|
self.current = None
|
||||||
|
entries = yaml.safe_load(fp)
|
||||||
|
for entry in entries:
|
||||||
|
kw = {}
|
||||||
|
for field in attr.fields(CatalogEntry):
|
||||||
|
if field.name in entry:
|
||||||
|
kw[field.name] = entry[field.name]
|
||||||
|
c = CatalogEntry(**kw)
|
||||||
|
self.sources.append(c)
|
||||||
|
if c.default:
|
||||||
|
self.current = c
|
||||||
|
log.debug("loaded %d sources from %r", len(self.sources), fp.name)
|
||||||
|
if self.current is None:
|
||||||
|
self.current = self.sources[0]
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
path = os.path.join(self._dir, self.current.path)
|
||||||
|
scheme = self.current.type
|
||||||
|
return {
|
||||||
|
'sources': {
|
||||||
|
'ubuntu00': f'{scheme}://{path}'
|
||||||
|
},
|
||||||
|
}
|
|
@ -39,6 +39,7 @@ from .mirror import MirrorModel
|
||||||
from .network import NetworkModel
|
from .network import NetworkModel
|
||||||
from .proxy import ProxyModel
|
from .proxy import ProxyModel
|
||||||
from .snaplist import SnapListModel
|
from .snaplist import SnapListModel
|
||||||
|
from .source import SourceModel
|
||||||
from .ssh import SSHModel
|
from .ssh import SSHModel
|
||||||
from .timezone import TimeZoneModel
|
from .timezone import TimeZoneModel
|
||||||
from .updates import UpdatesModel
|
from .updates import UpdatesModel
|
||||||
|
@ -116,6 +117,7 @@ class SubiquityModel:
|
||||||
self.proxy = ProxyModel()
|
self.proxy = ProxyModel()
|
||||||
self.snaplist = SnapListModel()
|
self.snaplist = SnapListModel()
|
||||||
self.ssh = SSHModel()
|
self.ssh = SSHModel()
|
||||||
|
self.source = SourceModel()
|
||||||
self.timezone = TimeZoneModel()
|
self.timezone = TimeZoneModel()
|
||||||
self.updates = UpdatesModel()
|
self.updates = UpdatesModel()
|
||||||
self.userdata = {}
|
self.userdata = {}
|
||||||
|
@ -307,16 +309,13 @@ class SubiquityModel:
|
||||||
config = {
|
config = {
|
||||||
'stages': stages,
|
'stages': stages,
|
||||||
|
|
||||||
'sources': {
|
|
||||||
'ubuntu00': 'cp:///media/filesystem'
|
|
||||||
},
|
|
||||||
|
|
||||||
'curthooks_commands': {
|
'curthooks_commands': {
|
||||||
'001-configure-apt': [
|
'001-configure-apt': [
|
||||||
resource_path('bin/subiquity-configure-apt'),
|
resource_path('bin/subiquity-configure-apt'),
|
||||||
sys.executable, str(self.network.has_network).lower(),
|
sys.executable, str(self.network.has_network).lower(),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
'grub': {
|
'grub': {
|
||||||
'terminal': 'unmodified',
|
'terminal': 'unmodified',
|
||||||
'probe_additional_os': True
|
'probe_additional_os': True
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
# 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 os
|
||||||
|
import random
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from subiquitycore.tests.util import random_string
|
||||||
|
|
||||||
|
from subiquity.models.source import (
|
||||||
|
SourceModel,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def make_entry(**fields):
|
||||||
|
raw = {
|
||||||
|
'name': {
|
||||||
|
'en': random_string(),
|
||||||
|
},
|
||||||
|
'description': {
|
||||||
|
'en': random_string(),
|
||||||
|
},
|
||||||
|
'id': random_string(),
|
||||||
|
'type': random_string(),
|
||||||
|
'variant': random_string(),
|
||||||
|
'path': random_string(),
|
||||||
|
'size': random.randint(1000000, 2000000),
|
||||||
|
}
|
||||||
|
for k, v in fields.items():
|
||||||
|
raw[k] = v
|
||||||
|
return raw
|
||||||
|
|
||||||
|
|
||||||
|
class TestMirrorModel(unittest.TestCase):
|
||||||
|
|
||||||
|
def tdir(self):
|
||||||
|
tdir = tempfile.mkdtemp()
|
||||||
|
self.addCleanup(shutil.rmtree, tdir)
|
||||||
|
return tdir
|
||||||
|
|
||||||
|
def write_and_load_entries(self, model, entries, dir=None):
|
||||||
|
if dir is None:
|
||||||
|
dir = self.tdir()
|
||||||
|
cat_path = os.path.join(dir, 'catalog.yaml')
|
||||||
|
with open(cat_path, 'w') as fp:
|
||||||
|
yaml.dump(entries, fp)
|
||||||
|
with open(cat_path) as fp:
|
||||||
|
model.load_from_file(fp)
|
||||||
|
|
||||||
|
def test_initially_server(self):
|
||||||
|
model = SourceModel()
|
||||||
|
self.assertEqual(model.current.variant, 'server')
|
||||||
|
|
||||||
|
def test_load_from_file_simple(self):
|
||||||
|
entry = make_entry(id='id1')
|
||||||
|
model = SourceModel()
|
||||||
|
self.write_and_load_entries(model, [entry])
|
||||||
|
self.assertEqual(model.current.id, 'id1')
|
||||||
|
|
||||||
|
def test_load_from_file_ignores_extra_keys(self):
|
||||||
|
entry = make_entry(id='id1', foobarbaz=random_string())
|
||||||
|
model = SourceModel()
|
||||||
|
self.write_and_load_entries(model, [entry])
|
||||||
|
self.assertEqual(model.current.id, 'id1')
|
||||||
|
|
||||||
|
def test_default(self):
|
||||||
|
entries = [
|
||||||
|
make_entry(id='id1'),
|
||||||
|
make_entry(id='id2', default=True),
|
||||||
|
make_entry(id='id3'),
|
||||||
|
]
|
||||||
|
model = SourceModel()
|
||||||
|
self.write_and_load_entries(model, entries)
|
||||||
|
self.assertEqual(model.current.id, 'id2')
|
||||||
|
|
||||||
|
def test_render_absolute(self):
|
||||||
|
entry = make_entry(
|
||||||
|
type='scheme',
|
||||||
|
path='/foo/bar/baz',
|
||||||
|
)
|
||||||
|
model = SourceModel()
|
||||||
|
self.write_and_load_entries(model, [entry])
|
||||||
|
self.assertEqual(
|
||||||
|
model.render(), {'sources': {'ubuntu00': 'scheme:///foo/bar/baz'}})
|
||||||
|
|
||||||
|
def test_render_relative(self):
|
||||||
|
dir = self.tdir()
|
||||||
|
entry = make_entry(
|
||||||
|
type='scheme',
|
||||||
|
path='foo/bar/baz',
|
||||||
|
)
|
||||||
|
model = SourceModel()
|
||||||
|
self.write_and_load_entries(model, [entry], dir)
|
||||||
|
self.assertEqual(
|
||||||
|
model.render(),
|
||||||
|
{'sources': {'ubuntu00': f'scheme://{dir}/foo/bar/baz'}})
|
||||||
|
|
||||||
|
def test_render_initial(self):
|
||||||
|
model = SourceModel()
|
||||||
|
self.assertEqual(
|
||||||
|
model.render(),
|
||||||
|
{'sources': {'ubuntu00': 'cp:///media/filesystem'}})
|
|
@ -29,6 +29,7 @@ from .refresh import RefreshController
|
||||||
from .reporting import ReportingController
|
from .reporting import ReportingController
|
||||||
from .shutdown import ShutdownController
|
from .shutdown import ShutdownController
|
||||||
from .snaplist import SnapListController
|
from .snaplist import SnapListController
|
||||||
|
from .source import SourceController
|
||||||
from .ssh import SSHController
|
from .ssh import SSHController
|
||||||
from .timezone import TimeZoneController
|
from .timezone import TimeZoneController
|
||||||
from .updates import UpdatesController
|
from .updates import UpdatesController
|
||||||
|
@ -54,6 +55,7 @@ __all__ = [
|
||||||
'ReportingController',
|
'ReportingController',
|
||||||
'ShutdownController',
|
'ShutdownController',
|
||||||
'SnapListController',
|
'SnapListController',
|
||||||
|
'SourceController',
|
||||||
'SSHController',
|
'SSHController',
|
||||||
'TimeZoneController',
|
'TimeZoneController',
|
||||||
'UpdatesController',
|
'UpdatesController',
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
# 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 os
|
||||||
|
|
||||||
|
from subiquity.common.apidef import API
|
||||||
|
from subiquity.common.types import (
|
||||||
|
SourceSelection,
|
||||||
|
SourceSelectionAndSetting,
|
||||||
|
)
|
||||||
|
from subiquity.server.controller import SubiquityController
|
||||||
|
|
||||||
|
|
||||||
|
def _translate(d, lang):
|
||||||
|
if lang:
|
||||||
|
for lang in lang, lang.split('_', 1)[0]:
|
||||||
|
if lang in d:
|
||||||
|
return d[lang]
|
||||||
|
return _(d['en'])
|
||||||
|
|
||||||
|
|
||||||
|
def convert_source(source, lang):
|
||||||
|
return SourceSelection(
|
||||||
|
name=_translate(source.name, lang),
|
||||||
|
description=_translate(source.description, lang),
|
||||||
|
id=source.id,
|
||||||
|
size=source.size,
|
||||||
|
variant=source.variant,
|
||||||
|
default=source.default)
|
||||||
|
|
||||||
|
|
||||||
|
class SourceController(SubiquityController):
|
||||||
|
|
||||||
|
model_name = "source"
|
||||||
|
|
||||||
|
endpoint = API.source
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
path = '/cdrom/casper/install-sources.yaml'
|
||||||
|
if self.app.opts.source_catalog is not None:
|
||||||
|
path = self.app.opts.source_catalog
|
||||||
|
if not os.path.exists(path):
|
||||||
|
return
|
||||||
|
with open(path) as fp:
|
||||||
|
self.model.load_from_file(fp)
|
||||||
|
|
||||||
|
def interactive(self):
|
||||||
|
if len(self.model.sources) <= 1:
|
||||||
|
return False
|
||||||
|
return super().interactive()
|
||||||
|
|
||||||
|
async def GET(self) -> SourceSelectionAndSetting:
|
||||||
|
cur_lang = self.app.base_model.locale.selected_language
|
||||||
|
cur_lang = cur_lang.rsplit('.', 1)[0]
|
||||||
|
|
||||||
|
return SourceSelectionAndSetting(
|
||||||
|
[
|
||||||
|
convert_source(source, cur_lang)
|
||||||
|
for source in self.model.sources
|
||||||
|
],
|
||||||
|
self.model.current.id)
|
||||||
|
|
||||||
|
def configured(self):
|
||||||
|
super().configured()
|
||||||
|
self.app.base_model.set_source_variant(self.model.current.variant)
|
||||||
|
|
||||||
|
async def POST(self, source_id: str) -> None:
|
||||||
|
for source in self.model.sources:
|
||||||
|
if source.id == source_id:
|
||||||
|
self.model.current = source
|
||||||
|
self.configured()
|
|
@ -0,0 +1 @@
|
||||||
|
#
|
|
@ -0,0 +1,65 @@
|
||||||
|
# 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 random
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import attr
|
||||||
|
|
||||||
|
from subiquitycore.tests.util import random_string
|
||||||
|
|
||||||
|
from subiquity.models.source import CatalogEntry
|
||||||
|
from subiquity.server.controllers.source import convert_source
|
||||||
|
|
||||||
|
|
||||||
|
def make_entry(**kw):
|
||||||
|
fields = {
|
||||||
|
'default': False,
|
||||||
|
'name': {'en': random_string()},
|
||||||
|
'description': {'en': random_string()},
|
||||||
|
'size': random.randint(1000000, 2000000),
|
||||||
|
}
|
||||||
|
for field in attr.fields(CatalogEntry):
|
||||||
|
if field.name not in fields:
|
||||||
|
fields[field.name] = random_string()
|
||||||
|
for k, v in kw.items():
|
||||||
|
fields[k] = v
|
||||||
|
return CatalogEntry(**fields)
|
||||||
|
|
||||||
|
|
||||||
|
class TestSubiquityModel(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_convert_source(self):
|
||||||
|
entry = make_entry()
|
||||||
|
source = convert_source(entry, 'C')
|
||||||
|
self.assertEqual(source.id, entry.id)
|
||||||
|
|
||||||
|
def test_convert_translations(self):
|
||||||
|
entry = make_entry(
|
||||||
|
name={
|
||||||
|
'en': 'English',
|
||||||
|
'fr': 'French',
|
||||||
|
'fr_CA': 'French Canadian',
|
||||||
|
})
|
||||||
|
self.assertEqual(
|
||||||
|
convert_source(entry, 'C').name, "English")
|
||||||
|
self.assertEqual(
|
||||||
|
convert_source(entry, 'en').name, "English")
|
||||||
|
self.assertEqual(
|
||||||
|
convert_source(entry, 'fr').name, "French")
|
||||||
|
self.assertEqual(
|
||||||
|
convert_source(entry, 'fr_CA').name, "French Canadian")
|
||||||
|
self.assertEqual(
|
||||||
|
convert_source(entry, 'fr_BE').name, "French")
|
|
@ -115,7 +115,13 @@ class MetaController:
|
||||||
async def client_variant_POST(self, variant: str) -> None:
|
async def client_variant_POST(self, variant: str) -> None:
|
||||||
if variant not in self.app.supported_variants:
|
if variant not in self.app.supported_variants:
|
||||||
raise ValueError(f'unrecognized client variant {variant}')
|
raise ValueError(f'unrecognized client variant {variant}')
|
||||||
self.app.base_model.set_source_variant(variant)
|
from subiquity.models.source import fake_entries
|
||||||
|
if variant in fake_entries:
|
||||||
|
if self.app.base_model.source.current.variant != variant:
|
||||||
|
self.app.base_model.source.current = fake_entries[variant]
|
||||||
|
self.app.controllers.Source.configured()
|
||||||
|
else:
|
||||||
|
self.app.base_model.set_source_variant(variant)
|
||||||
|
|
||||||
async def ssh_info_GET(self) -> Optional[LiveSessionSSHInfo]:
|
async def ssh_info_GET(self) -> Optional[LiveSessionSSHInfo]:
|
||||||
ips = []
|
ips = []
|
||||||
|
@ -169,6 +175,7 @@ INSTALL_MODEL_NAMES = ModelNames({
|
||||||
"mirror",
|
"mirror",
|
||||||
"network",
|
"network",
|
||||||
"proxy",
|
"proxy",
|
||||||
|
"source",
|
||||||
})
|
})
|
||||||
|
|
||||||
POSTINSTALL_MODEL_NAMES = ModelNames({
|
POSTINSTALL_MODEL_NAMES = ModelNames({
|
||||||
|
@ -213,6 +220,7 @@ class SubiquityServer(Application):
|
||||||
"Kernel",
|
"Kernel",
|
||||||
"Keyboard",
|
"Keyboard",
|
||||||
"Zdev",
|
"Zdev",
|
||||||
|
"Source",
|
||||||
"Network",
|
"Network",
|
||||||
"Proxy",
|
"Proxy",
|
||||||
"Mirror",
|
"Mirror",
|
||||||
|
|
|
@ -14,7 +14,13 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
|
||||||
|
|
||||||
def run_coro(coro):
|
def run_coro(coro):
|
||||||
return asyncio.get_event_loop().run_until_complete(coro)
|
return asyncio.get_event_loop().run_until_complete(coro)
|
||||||
|
|
||||||
|
|
||||||
|
def random_string():
|
||||||
|
return ''.join(random.choice(string.ascii_letters) for _ in range(8))
|
||||||
|
|
Loading…
Reference in New Issue