Skip to content

Commit

Permalink
identity: look-ahead on source variant
Browse files Browse the repository at this point in the history
The current check for Desktop doesn't work in load_autoinstall_data
because the variant isn't set until the controllers have started
(and load_autoinstall_data happens before this). The simple way
to get around this is to re-implement this logic in the identity
controller so it can tell ahead of time what the result will be in
the source controller/model. We should remove this logic once we can move
this check to a time after all the controllers have started.
  • Loading branch information
Chris-Peterson444 committed Apr 10, 2024
1 parent 995afb2 commit 14a2f7a
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 1 deletion.
55 changes: 54 additions & 1 deletion subiquity/server/controllers/identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,18 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import logging
import os
import re
from typing import Set
from typing import List, Set

import attr
import yaml

from subiquity.common.apidef import API
from subiquity.common.resources import resource_path
from subiquity.common.serialize import Serializer
from subiquity.common.types import IdentityData, UsernameValidation
from subiquity.models.source import CatalogEntry
from subiquity.server.autoinstall import AutoinstallError
from subiquity.server.controller import SubiquityController
from subiquitycore.context import with_context
Expand Down Expand Up @@ -89,6 +93,9 @@ def load_autoinstall_data(self, data):
# 2. we are installing not-Server (Desktop)
if self.app.base_model.source.current.variant != "server":
return
# 2.1 (see definition of _install_source_is_desktop)
if self._install_source_is_desktop():
return
# 3. we are only refreshing the reset partition
# (The identity controller doesn't figure this out until the apply
# step, so we are going to cheat and inspect the situation here)
Expand Down Expand Up @@ -151,3 +158,49 @@ async def validate_username_GET(self, username: str) -> UsernameValidation:
return UsernameValidation.TOO_LONG

return UsernameValidation.OK

# The identity section (and user-data) section are entirely optional
# on Desktop, but setting the variant doesn't happen until controllers
# are started. For the identity controller to properly validate
# this behavior, we need to do look-ahead on the source controller
# logic for loading the available sources.
# See: https://github.com/canonical/subiquity/pull/1965
def _install_source_is_desktop(self) -> bool:
# Check the autoinstall config first
source_config = self.app.autoinstall_config.get("source")
if (
source_config is not None
and (id := source_config.get("id")) is not None
and "server" not in id
):
return True

# In dryrun we are on server, unless you spoof with:
# source:
# id: desktop
if self.app.opts.dry_run:
return False

# Check the sources available the same way the source controller does
# but return a bool for if the variant is desktop
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):
# Be restrictive here, default variant is Server
return False
current = None
with open(path) as fp:
sources = Serializer(ignore_unknown_fields=True).deserialize(
List[CatalogEntry], yaml.safe_load(fp)
)
for entry in sources:
if entry.default:
current = entry
if current is None:
current = sources[0]

if "server" in current.variant:
return False

return True
39 changes: 39 additions & 0 deletions subiquity/server/controllers/tests/test_identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
# 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/>.

from unittest.mock import mock_open, patch

import jsonschema
from jsonschema.validators import validator_for

Expand Down Expand Up @@ -83,3 +85,40 @@ async def test_desktop_does_not_require_identity_case_4a2(self, config, valid):
self.app.autoinstall_config = config
# should never raise
self.ic.load_autoinstall_data(None)

def test_install_source_is_desktop__autoinstall(self):
"""Test _install_source_is_desktop autoinstall."""

self.app.autoinstall_config = {"source": {"id": "desktop"}}
self.assertTrue(self.ic._install_source_is_desktop())

self.app.autoinstall_config = {"source": {"id": "server"}}
self.assertFalse(self.ic._install_source_is_desktop())

def test_install_source_is_desktop__no_sources(self):
"""Test _install_source_is_desktop no sources."""

# Default server if /cdrom/casper/install-sources.yaml DNE
with patch("os.path.exists", return_value=False):
self.assertFalse(self.ic._install_source_is_desktop())

@parameterized.expand(
# (Sources list, is_desktop)
(
("examples/sources/desktop.yaml", True),
("examples/sources/install.yaml", False),
)
)
def test_install_source_is_desktop__sources(self, sources, is_desktop):
"""Test _install_source_is_desktop sources."""

self.app.opts.dry_run = False # Fun!

with open(sources) as f:
source_data = f.read()

with (
patch("os.path.exists", return_value=True),
patch("builtins.open", mock_open(read_data=source_data)),
):
self.assertEqual(self.ic._install_source_is_desktop(), is_desktop)

0 comments on commit 14a2f7a

Please sign in to comment.