Merge pull request #1897 from Chris-Peterson444/autoinstall-exception-FR-6293

AutoinstallError exception
This commit is contained in:
Chris Peterson 2024-02-05 16:19:15 -08:00 committed by GitHub
commit 48e5f9c616
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 686 additions and 8 deletions

View File

@ -6,6 +6,12 @@
"minimum": 1, "minimum": 1,
"maximum": 1 "maximum": 1
}, },
"interactive-sections": {
"type": "array",
"items": {
"type": "string"
}
},
"early-commands": { "early-commands": {
"type": "array", "type": "array",
"items": { "items": {

View File

@ -6,6 +6,12 @@
"minimum": 1, "minimum": 1,
"maximum": 1 "maximum": 1
}, },
"interactive-sections": {
"type": "array",
"items": {
"type": "string"
}
},
"early-commands": { "early-commands": {
"type": "array", "type": "array",
"items": { "items": {

View File

@ -0,0 +1,31 @@
# Copyright 2024 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
log = logging.getLogger("subiquity.server.autoinstall")
class AutoinstallError(Exception):
pass
class AutoinstallValidationError(AutoinstallError):
def __init__(
self,
owner: str,
):
self.message = f"Malformed autoinstall in {owner!r} section"
self.owner = owner
super().__init__(self.message)

View File

@ -19,8 +19,10 @@ import os
from typing import Any, Optional from typing import Any, Optional
import jsonschema import jsonschema
from jsonschema.exceptions import ValidationError
from subiquity.common.api.server import bind from subiquity.common.api.server import bind
from subiquity.server.autoinstall import AutoinstallValidationError
from subiquity.server.types import InstallerChannels from subiquity.server.types import InstallerChannels
from subiquitycore.context import with_context from subiquitycore.context import with_context
from subiquitycore.controller import BaseController from subiquitycore.controller import BaseController
@ -54,6 +56,19 @@ class SubiquityController(BaseController):
await self.configured() await self.configured()
self._active = False self._active = False
def validate_autoinstall(self, ai_data: dict) -> None:
try:
jsonschema.validate(ai_data, self.autoinstall_schema)
except ValidationError as original_exception:
section = self.autoinstall_key
new_exception: AutoinstallValidationError = AutoinstallValidationError(
section,
)
raise new_exception from original_exception
def setup_autoinstall(self): def setup_autoinstall(self):
if not self.app.autoinstall_config: if not self.app.autoinstall_config:
return return
@ -72,7 +87,8 @@ class SubiquityController(BaseController):
ai_data = self.autoinstall_default ai_data = self.autoinstall_default
if ai_data is not None and self.autoinstall_schema is not None: if ai_data is not None and self.autoinstall_schema is not None:
jsonschema.validate(ai_data, self.autoinstall_schema) self.validate_autoinstall(ai_data)
self.load_autoinstall_data(ai_data) self.load_autoinstall_data(ai_data)
def load_autoinstall_data(self, data): def load_autoinstall_data(self, data):

View File

@ -0,0 +1,31 @@
# Copyright 2024 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
from jsonschema.validators import validator_for
from subiquity.server.controllers.cmdlist import CmdListController
from subiquitycore.tests import SubiTestCase
class TestCmdListController(SubiTestCase):
def test_valid_schema(self):
"""Test that the expected autoinstall JSON schema is valid"""
JsonValidator: jsonschema.protocols.Validator = validator_for(
CmdListController.autoinstall_schema
)
JsonValidator.check_schema(CmdListController.autoinstall_schema)

View File

@ -0,0 +1,31 @@
# Copyright 2024 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
from jsonschema.validators import validator_for
from subiquity.server.controllers.codecs import CodecsController
from subiquitycore.tests import SubiTestCase
class TestCodecsController(SubiTestCase):
def test_valid_schema(self):
"""Test that the expected autoinstall JSON schema is valid"""
JsonValidator: jsonschema.protocols.Validator = validator_for(
CodecsController.autoinstall_schema
)
JsonValidator.check_schema(CodecsController.autoinstall_schema)

View File

@ -0,0 +1,31 @@
# Copyright 2024 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
from jsonschema.validators import validator_for
from subiquity.server.controllers.debconf import DebconfController
from subiquitycore.tests import SubiTestCase
class TestDebconfController(SubiTestCase):
def test_valid_schema(self):
"""Test that the expected autoinstall JSON schema is valid"""
JsonValidator: jsonschema.protocols.Validator = validator_for(
DebconfController.autoinstall_schema
)
JsonValidator.check_schema(DebconfController.autoinstall_schema)

View File

@ -0,0 +1,31 @@
# Copyright 2024 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
from jsonschema.validators import validator_for
from subiquity.server.controllers.drivers import DriversController
from subiquitycore.tests import SubiTestCase
class TestDriversController(SubiTestCase):
def test_valid_schema(self):
"""Test that the expected autoinstall JSON schema is valid"""
JsonValidator: jsonschema.protocols.Validator = validator_for(
DriversController.autoinstall_schema
)
JsonValidator.check_schema(DriversController.autoinstall_schema)

View File

@ -18,7 +18,9 @@ import subprocess
import uuid import uuid
from unittest import IsolatedAsyncioTestCase, mock from unittest import IsolatedAsyncioTestCase, mock
import jsonschema
from curtin.commands.extract import TrivialSourceHandler from curtin.commands.extract import TrivialSourceHandler
from jsonschema.validators import validator_for
from subiquity.common.filesystem import gaps, labels from subiquity.common.filesystem import gaps, labels
from subiquity.common.filesystem.actions import DeviceAction from subiquity.common.filesystem.actions import DeviceAction
@ -399,6 +401,15 @@ class TestSubiquityControllerFilesystem(IsolatedAsyncioTestCase):
self.assertEqual(len(self.fsc._variation_info), 1) self.assertEqual(len(self.fsc._variation_info), 1)
self.assertEqual(self.fsc._variation_info["default"].name, "default") self.assertEqual(self.fsc._variation_info["default"].name, "default")
def test_valid_schema(self):
"""Test that the expected autoinstall JSON schema is valid"""
JsonValidator: jsonschema.protocols.Validator = validator_for(
FilesystemController.autoinstall_schema
)
JsonValidator.check_schema(FilesystemController.autoinstall_schema)
class TestGuided(IsolatedAsyncioTestCase): class TestGuided(IsolatedAsyncioTestCase):
boot_expectations = [ boot_expectations = [

View File

@ -0,0 +1,31 @@
# Copyright 2024 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
from jsonschema.validators import validator_for
from subiquity.server.controllers.identity import IdentityController
from subiquitycore.tests import SubiTestCase
class TestIdentityController(SubiTestCase):
def test_valid_schema(self):
"""Test that the expected autoinstall JSON schema is valid"""
JsonValidator: jsonschema.protocols.Validator = validator_for(
IdentityController.autoinstall_schema
)
JsonValidator.check_schema(IdentityController.autoinstall_schema)

View File

@ -17,6 +17,9 @@ import os
import unittest import unittest
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
import jsonschema
from jsonschema.validators import validator_for
from subiquity.common.types import KeyboardSetting from subiquity.common.types import KeyboardSetting
from subiquity.models.keyboard import KeyboardModel from subiquity.models.keyboard import KeyboardModel
from subiquity.server.controllers.keyboard import KeyboardController from subiquity.server.controllers.keyboard import KeyboardController
@ -29,6 +32,16 @@ class opts:
dry_run = True dry_run = True
class TestKeyboardController(SubiTestCase):
def test_valid_schema(self):
"""Test that the expected autoinstall JSON schema is valid"""
JsonValidator: jsonschema.protocols.Validator = validator_for(
KeyboardController.autoinstall_schema
)
JsonValidator.check_schema(KeyboardController.autoinstall_schema)
class TestSubiquityModel(SubiTestCase): class TestSubiquityModel(SubiTestCase):
async def test_write_config(self): async def test_write_config(self):
os.environ["SUBIQUITY_REPLAY_TIMESCALE"] = "100" os.environ["SUBIQUITY_REPLAY_TIMESCALE"] = "100"

View File

@ -0,0 +1,31 @@
# Copyright 2024 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
from jsonschema.validators import validator_for
from subiquity.server.controllers.locale import LocaleController
from subiquitycore.tests import SubiTestCase
class TestLocaleController(SubiTestCase):
def test_valid_schema(self):
"""Test that the expected autoinstall JSON schema is valid"""
JsonValidator: jsonschema.protocols.Validator = validator_for(
LocaleController.autoinstall_schema
)
JsonValidator.check_schema(LocaleController.autoinstall_schema)

View File

@ -19,6 +19,7 @@ import unittest
from unittest import mock from unittest import mock
import jsonschema import jsonschema
from jsonschema.validators import validator_for
from subiquity.common.types import MirrorSelectionFallback from subiquity.common.types import MirrorSelectionFallback
from subiquity.models.mirror import MirrorModel from subiquity.models.mirror import MirrorModel
@ -264,3 +265,12 @@ class TestMirrorController(unittest.IsolatedAsyncioTestCase):
mock_fallback.assert_not_called() mock_fallback.assert_not_called()
await controller.run_mirror_selection_or_fallback(context=None) await controller.run_mirror_selection_or_fallback(context=None)
mock_fallback.assert_called_once() mock_fallback.assert_called_once()
def test_valid_schema(self):
"""Test that the expected autoinstall JSON schema is valid"""
JsonValidator: jsonschema.protocols.Validator = validator_for(
MirrorController.autoinstall_schema
)
JsonValidator.check_schema(MirrorController.autoinstall_schema)

View File

@ -0,0 +1,31 @@
# Copyright 2024 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
from jsonschema.validators import validator_for
from subiquity.server.controllers.network import NetworkController
from subiquitycore.tests import SubiTestCase
class TestNetworkController(SubiTestCase):
def test_valid_schema(self):
"""Test that the expected autoinstall JSON schema is valid"""
JsonValidator: jsonschema.protocols.Validator = validator_for(
NetworkController.autoinstall_schema
)
JsonValidator.check_schema(NetworkController.autoinstall_schema)

View File

@ -16,6 +16,9 @@
import subprocess import subprocess
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
import jsonschema
from jsonschema.validators import validator_for
from subiquity.server.controllers.oem import OEMController from subiquity.server.controllers.oem import OEMController
from subiquitycore.tests import SubiTestCase from subiquitycore.tests import SubiTestCase
from subiquitycore.tests.mocks import make_app from subiquitycore.tests.mocks import make_app
@ -110,3 +113,12 @@ Ubuntu-Oem-Kernel-Flavour: oem
"oem-sutton-balint-meta", context=None, overlay=Mock() "oem-sutton-balint-meta", context=None, overlay=Mock()
) )
) )
def test_valid_schema(self):
"""Test that the expected autoinstall JSON schema is valid"""
JsonValidator: jsonschema.protocols.Validator = validator_for(
OEMController.autoinstall_schema
)
JsonValidator.check_schema(OEMController.autoinstall_schema)

View File

@ -0,0 +1,31 @@
# Copyright 2024 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
from jsonschema.validators import validator_for
from subiquity.server.controllers.package import PackageController
from subiquitycore.tests import SubiTestCase
class TestPackageController(SubiTestCase):
def test_valid_schema(self):
"""Test that the expected autoinstall JSON schema is valid"""
JsonValidator: jsonschema.protocols.Validator = validator_for(
PackageController.autoinstall_schema
)
JsonValidator.check_schema(PackageController.autoinstall_schema)

View File

@ -0,0 +1,31 @@
# Copyright 2024 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
from jsonschema.validators import validator_for
from subiquity.server.controllers.proxy import ProxyController
from subiquitycore.tests import SubiTestCase
class TestProxyController(SubiTestCase):
def test_valid_schema(self):
"""Test that the expected autoinstall JSON schema is valid"""
JsonValidator: jsonschema.protocols.Validator = validator_for(
ProxyController.autoinstall_schema
)
JsonValidator.check_schema(ProxyController.autoinstall_schema)

View File

@ -15,6 +15,9 @@
from unittest import mock from unittest import mock
import jsonschema
from jsonschema.validators import validator_for
from subiquity.server import snapdapi from subiquity.server import snapdapi
from subiquity.server.controllers import refresh as refresh_mod from subiquity.server.controllers import refresh as refresh_mod
from subiquity.server.controllers.refresh import RefreshController, SnapChannelSource from subiquity.server.controllers.refresh import RefreshController, SnapChannelSource
@ -99,3 +102,12 @@ class TestRefreshController(SubiTestCase):
await self.rc.configure_snapd(context=self.rc.context) await self.rc.configure_snapd(context=self.rc.context)
paw.assert_not_called() paw.assert_not_called()
def test_valid_schema(self):
"""Test that the expected autoinstall JSON schema is valid"""
JsonValidator: jsonschema.protocols.Validator = validator_for(
RefreshController.autoinstall_schema
)
JsonValidator.check_schema(RefreshController.autoinstall_schema)

View File

@ -0,0 +1,31 @@
# Copyright 2024 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
from jsonschema.validators import validator_for
from subiquity.server.controllers.reporting import ReportingController
from subiquitycore.tests import SubiTestCase
class TestReportingController(SubiTestCase):
def test_valid_schema(self):
"""Test that the expected autoinstall JSON schema is valid"""
JsonValidator: jsonschema.protocols.Validator = validator_for(
ReportingController.autoinstall_schema
)
JsonValidator.check_schema(ReportingController.autoinstall_schema)

View File

@ -0,0 +1,31 @@
# Copyright 2024 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
from jsonschema.validators import validator_for
from subiquity.server.controllers.shutdown import ShutdownController
from subiquitycore.tests import SubiTestCase
class TestShutdownController(SubiTestCase):
def test_valid_schema(self):
"""Test that the expected autoinstall JSON schema is valid"""
JsonValidator: jsonschema.protocols.Validator = validator_for(
ShutdownController.autoinstall_schema
)
JsonValidator.check_schema(ShutdownController.autoinstall_schema)

View File

@ -16,13 +16,17 @@
import unittest import unittest
from unittest.mock import AsyncMock from unittest.mock import AsyncMock
import jsonschema
import requests import requests
from jsonschema.validators import validator_for
from subiquity.models.snaplist import SnapListModel from subiquity.models.snaplist import SnapListModel
from subiquity.server.controllers.snaplist import ( from subiquity.server.controllers.snaplist import (
SnapdSnapInfoLoader, SnapdSnapInfoLoader,
SnapListController,
SnapListFetchError, SnapListFetchError,
) )
from subiquitycore.tests import SubiTestCase
from subiquitycore.tests.mocks import make_app from subiquitycore.tests.mocks import make_app
@ -56,3 +60,14 @@ class TestSnapdSnapInfoLoader(unittest.IsolatedAsyncioTestCase):
await self.loader.get_snap_list_task() await self.loader.get_snap_list_task()
self.assertTrue(self.loader.fetch_list_completed()) self.assertTrue(self.loader.fetch_list_completed())
self.assertFalse(self.loader.fetch_list_failed()) self.assertFalse(self.loader.fetch_list_failed())
class TestSnapListController(SubiTestCase):
def test_valid_schema(self):
"""Test that the expected autoinstall JSON schema is valid"""
JsonValidator: jsonschema.protocols.Validator = validator_for(
SnapListController.autoinstall_schema
)
JsonValidator.check_schema(SnapListController.autoinstall_schema)

View File

@ -16,6 +16,9 @@
import unittest import unittest
from unittest import mock from unittest import mock
import jsonschema
from jsonschema.validators import validator_for
from subiquity.common.types import SSHFetchIdStatus, SSHIdentity from subiquity.common.types import SSHFetchIdStatus, SSHIdentity
from subiquity.server.controllers.ssh import ( from subiquity.server.controllers.ssh import (
SSHController, SSHController,
@ -116,3 +119,12 @@ class TestSSHController(unittest.IsolatedAsyncioTestCase):
self.assertEqual(response.status, SSHFetchIdStatus.FINGERPRINT_ERROR) self.assertEqual(response.status, SSHFetchIdStatus.FINGERPRINT_ERROR)
self.assertEqual(response.error, stderr) self.assertEqual(response.error, stderr)
self.assertIsNone(response.identities) self.assertIsNone(response.identities)
def test_valid_schema(self):
"""Test that the expected autoinstall JSON schema is valid"""
JsonValidator: jsonschema.protocols.Validator = validator_for(
SSHController.autoinstall_schema
)
JsonValidator.check_schema(SSHController.autoinstall_schema)

View File

@ -0,0 +1,31 @@
# Copyright 2024 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
from jsonschema.validators import validator_for
from subiquity.server.controllers.timezone import TimeZoneController
from subiquitycore.tests import SubiTestCase
class TestTimeZoneController(SubiTestCase):
def test_valid_schema(self):
"""Test that the expected autoinstall JSON schema is valid"""
JsonValidator: jsonschema.protocols.Validator = validator_for(
TimeZoneController.autoinstall_schema
)
JsonValidator.check_schema(TimeZoneController.autoinstall_schema)

View File

@ -15,6 +15,9 @@
import unittest import unittest
import jsonschema
from jsonschema.validators import validator_for
from subiquity.server.controllers.ubuntu_pro import UbuntuProController from subiquity.server.controllers.ubuntu_pro import UbuntuProController
from subiquity.server.dryrun import DRConfig from subiquity.server.dryrun import DRConfig
from subiquitycore.tests.mocks import make_app from subiquitycore.tests.mocks import make_app
@ -33,3 +36,12 @@ class TestUbuntuProController(unittest.TestCase):
def test_deserialize(self): def test_deserialize(self):
self.controller.deserialize("1A2B3C4D") self.controller.deserialize("1A2B3C4D")
self.assertEqual(self.controller.model.token, "1A2B3C4D") self.assertEqual(self.controller.model.token, "1A2B3C4D")
def test_valid_schema(self):
"""Test that the expected autoinstall JSON schema is valid"""
JsonValidator: jsonschema.protocols.Validator = validator_for(
UbuntuProController.autoinstall_schema
)
JsonValidator.check_schema(UbuntuProController.autoinstall_schema)

View File

@ -0,0 +1,31 @@
# Copyright 2024 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
from jsonschema.validators import validator_for
from subiquity.server.controllers.updates import UpdatesController
from subiquitycore.tests import SubiTestCase
class TestUpdatesController(SubiTestCase):
def test_valid_schema(self):
"""Test that the expected autoinstall JSON schema is valid"""
JsonValidator: jsonschema.protocols.Validator = validator_for(
UpdatesController.autoinstall_schema
)
JsonValidator.check_schema(UpdatesController.autoinstall_schema)

View File

@ -15,7 +15,9 @@
import unittest import unittest
import jsonschema
from cloudinit.config.schema import SchemaValidationError from cloudinit.config.schema import SchemaValidationError
from jsonschema.validators import validator_for
from subiquity.server.controllers.userdata import UserdataController from subiquity.server.controllers.userdata import UserdataController
from subiquitycore.tests.mocks import make_app from subiquitycore.tests.mocks import make_app
@ -58,3 +60,12 @@ class TestUserdataController(unittest.TestCase):
validate.assert_called_with( validate.assert_called_with(
data=invalid_schema, data_source="autoinstall.user-data" data=invalid_schema, data_source="autoinstall.user-data"
) )
def test_valid_schema(self):
"""Test that the expected autoinstall JSON schema is valid"""
JsonValidator: jsonschema.protocols.Validator = validator_for(
UserdataController.autoinstall_schema
)
JsonValidator.check_schema(UserdataController.autoinstall_schema)

View File

@ -24,12 +24,13 @@ import jsonschema
import yaml import yaml
from aiohttp import web from aiohttp import web
from cloudinit.config.cc_set_passwords import rand_user_password from cloudinit.config.cc_set_passwords import rand_user_password
from jsonschema.exceptions import ValidationError
from systemd import journal from systemd import journal
from subiquity.cloudinit import get_host_combined_cloud_config from subiquity.cloudinit import get_host_combined_cloud_config
from subiquity.common.api.server import bind, controller_for_request from subiquity.common.api.server import bind, controller_for_request
from subiquity.common.apidef import API from subiquity.common.apidef import API
from subiquity.common.errorreport import ErrorReporter, ErrorReportKind from subiquity.common.errorreport import ErrorReport, ErrorReporter, ErrorReportKind
from subiquity.common.serialize import to_json from subiquity.common.serialize import to_json
from subiquity.common.types import ( from subiquity.common.types import (
ApplicationState, ApplicationState,
@ -40,6 +41,7 @@ from subiquity.common.types import (
PasswordKind, PasswordKind,
) )
from subiquity.models.subiquity import ModelNames, SubiquityModel from subiquity.models.subiquity import ModelNames, SubiquityModel
from subiquity.server.autoinstall import AutoinstallError, AutoinstallValidationError
from subiquity.server.controller import SubiquityController from subiquity.server.controller import SubiquityController
from subiquity.server.dryrun import DRConfig from subiquity.server.dryrun import DRConfig
from subiquity.server.errors import ErrorController from subiquity.server.errors import ErrorController
@ -222,6 +224,12 @@ class SubiquityServer(Application):
"minimum": 1, "minimum": 1,
"maximum": 1, "maximum": 1,
}, },
"interactive-sections": {
"type": "array",
"items": {
"type": "string",
},
},
}, },
"required": ["version"], "required": ["version"],
"additionalProperties": True, "additionalProperties": True,
@ -395,8 +403,9 @@ class SubiquityServer(Application):
def make_apport_report(self, kind, thing, *, wait=False, **kw): def make_apport_report(self, kind, thing, *, wait=False, **kw):
return self.error_reporter.make_apport_report(kind, thing, wait=wait, **kw) return self.error_reporter.make_apport_report(kind, thing, wait=wait, **kw)
async def _run_error_cmds(self, report): async def _run_error_cmds(self, report: Optional[ErrorReport] = None) -> None:
await report._info_task if report is not None and report._info_task is not None:
await report._info_task
Error = getattr(self.controllers, "Error", None) Error = getattr(self.controllers, "Error", None)
if Error is not None and Error.cmds: if Error is not None and Error.cmds:
try: try:
@ -413,7 +422,7 @@ class SubiquityServer(Application):
return return
report = self.error_reporter.report_for_exc(exc) report = self.error_reporter.report_for_exc(exc)
log.error("top level error", exc_info=exc) log.error("top level error", exc_info=exc)
if not report: if not isinstance(exc, AutoinstallError) and not report:
report = self.make_apport_report( report = self.make_apport_report(
ErrorReportKind.UNKNOWN, "unknown error", exc=exc ErrorReportKind.UNKNOWN, "unknown error", exc=exc
) )
@ -466,6 +475,20 @@ class SubiquityServer(Application):
await controller.apply_autoinstall_config() await controller.apply_autoinstall_config()
await controller.configured() await controller.configured()
def validate_autoinstall(self):
with self.context.child("core_validation", level="INFO"):
try:
jsonschema.validate(self.autoinstall_config, self.base_schema)
except ValidationError as original_exception:
# SubiquityServer currently only checks for these sections
# of autoinstall. Hardcode until we have better validation.
section = "version or interative-sessions"
new_exception: AutoinstallValidationError = AutoinstallValidationError(
section,
)
raise new_exception from original_exception
def load_autoinstall_config(self, *, only_early): def load_autoinstall_config(self, *, only_early):
log.debug( log.debug(
"load_autoinstall_config only_early %s file %s", "load_autoinstall_config only_early %s file %s",
@ -480,8 +503,7 @@ class SubiquityServer(Application):
self.controllers.Reporting.setup_autoinstall() self.controllers.Reporting.setup_autoinstall()
self.controllers.Reporting.start() self.controllers.Reporting.start()
self.controllers.Error.setup_autoinstall() self.controllers.Error.setup_autoinstall()
with self.context.child("core_validation", level="INFO"): self.validate_autoinstall()
jsonschema.validate(self.autoinstall_config, self.base_schema)
self.controllers.Early.setup_autoinstall() self.controllers.Early.setup_autoinstall()
else: else:
for controller in self.controllers.instances: for controller in self.controllers.instances:

View File

@ -16,6 +16,7 @@
import contextlib import contextlib
from unittest.mock import patch from unittest.mock import patch
from subiquity.server.autoinstall import AutoinstallValidationError
from subiquity.server.controller import SubiquityController from subiquity.server.controller import SubiquityController
from subiquitycore.tests import SubiTestCase from subiquitycore.tests import SubiTestCase
from subiquitycore.tests.mocks import make_app from subiquitycore.tests.mocks import make_app
@ -41,7 +42,6 @@ class TestController(SubiTestCase):
} }
self.controller.autoinstall_key = "sample" self.controller.autoinstall_key = "sample"
self.controller.autoinstall_key_alias = "sample-alias" self.controller.autoinstall_key_alias = "sample-alias"
self.controller.autoinstall_default = "default-data"
self.controller.setup_autoinstall() self.controller.setup_autoinstall()
mock_load.assert_called_once_with("some-sample-data") mock_load.assert_called_once_with("some-sample-data")
@ -56,5 +56,36 @@ class TestController(SubiTestCase):
mock_load.reset_mock() mock_load.reset_mock()
self.controller.autoinstall_key = "inexistent" self.controller.autoinstall_key = "inexistent"
self.controller.autoinstall_key_alias = "inexistent" self.controller.autoinstall_key_alias = "inexistent"
self.controller.autoinstall_default = "default-data"
self.controller.setup_autoinstall() self.controller.setup_autoinstall()
mock_load.assert_called_once_with("default-data") mock_load.assert_called_once_with("default-data")
def test_autoinstall_validation(self):
"""Test validation error type and no apport reporting"""
self.controller.autoinstall_schema = {
"type": "object",
"properties": {
"some-key": {
"type": "boolean",
},
},
}
self.bad_ai_data = {"some-key": "not a bool"}
self.controller.autoinstall_key = "some-key"
# Assert error type is correct
with self.assertRaises(AutoinstallValidationError) as ctx:
self.controller.validate_autoinstall(self.bad_ai_data)
exception = ctx.exception
# Assert error section is based on autoinstall_key
self.assertEquals(exception.owner, "some-key")
# Assert apport report is not created
# This only checks that controllers do not manually create an apport
# report on validation. Should also be tested in Server
self.controller.app.make_apport_report.assert_not_called()

View File

@ -17,7 +17,11 @@ import os
import shlex import shlex
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
import jsonschema
from jsonschema.validators import validator_for
from subiquity.common.types import PasswordKind from subiquity.common.types import PasswordKind
from subiquity.server.autoinstall import AutoinstallValidationError
from subiquity.server.server import ( from subiquity.server.server import (
MetaController, MetaController,
SubiquityServer, SubiquityServer,
@ -141,6 +145,56 @@ early-commands: ["{cmd}"]
self.assertEqual(after_early, self.server.autoinstall_config) self.assertEqual(after_early, self.server.autoinstall_config)
class TestAutoinstallValidation(SubiTestCase):
async def asyncSetUp(self):
opts = Mock()
opts.dry_run = True
opts.output_base = self.tmp_dir()
opts.machine_config = "examples/machines/simple.json"
self.server = SubiquityServer(opts, None)
self.server.base_schema = {
"type": "object",
"properties": {
"some-key": {
"type": "boolean",
},
},
}
self.server.make_apport_report = Mock()
def test_valid_schema(self):
"""Test that the expected autoinstall JSON schema is valid"""
JsonValidator: jsonschema.protocols.Validator = validator_for(
SubiquityServer.base_schema
)
JsonValidator.check_schema(SubiquityServer.base_schema)
def test_autoinstall_validation__error_type(self):
"""Test that bad autoinstall data throws AutoinstallValidationError"""
bad_ai_data = {"some-key": "not a bool"}
self.server.autoinstall_config = bad_ai_data
with self.assertRaises(AutoinstallValidationError):
self.server.validate_autoinstall()
async def test_autoinstall_validation__no_error_report(self):
"""Test no apport reporting"""
exception = AutoinstallValidationError("Mock")
loop = Mock()
context = {"exception": exception}
with patch("subiquity.server.server.log"):
with patch.object(self.server, "_run_error_cmds"):
self.server._exception_handler(loop, context)
self.server.make_apport_report.assert_not_called()
class TestMetaController(SubiTestCase): class TestMetaController(SubiTestCase):
async def test_interactive_sections_not_present(self): async def test_interactive_sections_not_present(self):
mc = MetaController(make_app()) mc = MetaController(make_app())

View File

@ -50,5 +50,6 @@ def make_app(model=None):
app.log_syslog_id = None app.log_syslog_id = None
app.report_start_event = mock.Mock() app.report_start_event = mock.Mock()
app.report_finish_event = mock.Mock() app.report_finish_event = mock.Mock()
app.make_apport_report = mock.Mock()
return app return app