diff --git a/apt-deps.txt b/apt-deps.txt index c752b41a..8a3d0686 100644 --- a/apt-deps.txt +++ b/apt-deps.txt @@ -27,6 +27,7 @@ python3-bson python3-coverage python3-debian python3-dev +python3-distro-info python3-distutils-extra python3-flake8 python3-gi diff --git a/snapcraft.yaml b/snapcraft.yaml index ba6dc260..8433475a 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -132,6 +132,7 @@ parts: - python3-attr - python3-bson - python3-debian + - python3-distro-info - python3-jsonschema - python3-minimal - python3-oauthlib diff --git a/subiquity/common/apidef.py b/subiquity/common/apidef.py index 93efdeef..7a7b10e1 100644 --- a/subiquity/common/apidef.py +++ b/subiquity/common/apidef.py @@ -67,6 +67,7 @@ from subiquity.common.types import ( StorageResponseV2, TimeZoneInfo, UbuntuProCheckTokenAnswer, + UbuntuProGeneralInfo, UbuntuProInfo, UbuntuProResponse, UPCSInitiateResponse, @@ -513,6 +514,10 @@ class API: def POST() -> None: ... + class info: + def GET() -> UbuntuProGeneralInfo: + ... + class identity: def GET() -> IdentityData: ... diff --git a/subiquity/common/types.py b/subiquity/common/types.py index c50f96cf..2c656753 100644 --- a/subiquity/common/types.py +++ b/subiquity/common/types.py @@ -718,6 +718,13 @@ class UbuntuProCheckTokenStatus(enum.Enum): UNKNOWN_ERROR = enum.auto() +@attr.s(auto_attribs=True) +class UbuntuProGeneralInfo: + eol_esm_year: Optional[int] + universe_packages: int + main_packages: int + + @attr.s(auto_attribs=True) class UPCSInitiateResponse: """Response to Ubuntu Pro contract selection initiate request.""" diff --git a/subiquity/server/controllers/tests/test_ubuntu_pro.py b/subiquity/server/controllers/tests/test_ubuntu_pro.py index c2787a53..90f19c6d 100644 --- a/subiquity/server/controllers/tests/test_ubuntu_pro.py +++ b/subiquity/server/controllers/tests/test_ubuntu_pro.py @@ -13,17 +13,20 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . -import unittest +from unittest import mock import jsonschema from jsonschema.validators import validator_for from subiquity.server.controllers.ubuntu_pro import UbuntuProController from subiquity.server.dryrun import DRConfig +from subiquitycore.lsb_release import lsb_release_from_path +from subiquitycore.tests import SubiTestCase from subiquitycore.tests.mocks import make_app +from subiquitycore.tests.parameterized import parameterized -class TestUbuntuProController(unittest.TestCase): +class TestUbuntuProController(SubiTestCase): def setUp(self): app = make_app() app.dr_cfg = DRConfig() @@ -45,3 +48,32 @@ class TestUbuntuProController(unittest.TestCase): ) JsonValidator.check_schema(UbuntuProController.autoinstall_schema) + + @parameterized.expand( + [ + ("focal", 23000, 2300, 2030), + ("impish", 23000, 2300, None), + ("jammy", 23000, 2300, 2032), + ("noble", 23000, 2300, 2034), + ] + ) + async def test_info_GET__series( + self, series: str, universe_pkgs: int, main_pkgs: int, esm_eol_year: int | None + ): + def fake_lsb_release(*args, **kwargs): + return lsb_release_from_path(f"examples/lsb-release-{series}") + + with mock.patch( + "subiquity.server.controllers.ubuntu_pro.lsb_release", + wraps=fake_lsb_release, + ) as m_lsb_release: + info = await self.controller.info_GET() + + m_lsb_release.assert_called_once() + + self.assertEqual(universe_pkgs, info.universe_packages) + self.assertEqual(main_pkgs, info.main_packages) + if esm_eol_year is not None: + self.assertEqual(esm_eol_year, info.eol_esm_year) + else: + self.assertIsNone(info.eol_esm_year) diff --git a/subiquity/server/controllers/ubuntu_pro.py b/subiquity/server/controllers/ubuntu_pro.py index 238b9cd6..f7d52096 100644 --- a/subiquity/server/controllers/ubuntu_pro.py +++ b/subiquity/server/controllers/ubuntu_pro.py @@ -19,10 +19,13 @@ import logging import os from typing import Optional +import distro_info + from subiquity.common.apidef import API from subiquity.common.types import ( UbuntuProCheckTokenAnswer, UbuntuProCheckTokenStatus, + UbuntuProGeneralInfo, UbuntuProInfo, UbuntuProResponse, UPCSInitiateResponse, @@ -40,6 +43,7 @@ from subiquity.server.ubuntu_advantage import ( UAInterface, UAInterfaceStrategy, ) +from subiquitycore.lsb_release import lsb_release log = logging.getLogger("subiquity.server.controllers.ubuntu_pro") @@ -199,3 +203,27 @@ class UbuntuProController(SubiquityController): self.cs.cancel() self.cs = None + + async def info_GET(self) -> UbuntuProGeneralInfo: + # Currently the number of packages is hard-coded. + main_packages = 2300 + universe_packages = 23000 + eol_esm_year: Optional[int] = None + + series = lsb_release(dry_run=self.app.opts.dry_run)["codename"] + + for release in distro_info.UbuntuDistroInfo()._releases: + if release.series == series: + try: + eol_esm_year = release.eol_esm.year + except AttributeError: + log.warning("series %s does not have an ESM EOL date", series) + break + else: + log.warning("could not find distro info for %s", series) + + return UbuntuProGeneralInfo( + eol_esm_year=eol_esm_year, + main_packages=main_packages, + universe_packages=universe_packages, + )