diff --git a/examples/uaclient-status-expired.json b/examples/uaclient-status-expired.json index 683337c8..524a7190 100644 --- a/examples/uaclient-status-expired.json +++ b/examples/uaclient-status-expired.json @@ -1,6 +1,12 @@ { "version": "27.4.2~21.10.1", "effective": null, + "contract": { + "name": "UA Apps - Essential (Virtual)" + }, + "account": { + "name": "user@domain.com" + }, "expires": "2010-12-31T00:00:00+00:00", "services": [ { diff --git a/examples/uaclient-status-valid.json b/examples/uaclient-status-valid.json index 97e4156d..8cc64410 100644 --- a/examples/uaclient-status-valid.json +++ b/examples/uaclient-status-valid.json @@ -2,6 +2,12 @@ "version": "27.4.2~21.10.1", "effective": null, "expires": "2035-12-31T00:00:00+00:00", + "contract": { + "name": "UA Apps - Essential (Virtual)" + }, + "account": { + "name": "user@domain.com" + }, "services": [ { "name": "cis", diff --git a/subiquity/client/controllers/ubuntu_pro.py b/subiquity/client/controllers/ubuntu_pro.py index f352e7c6..401b2c4c 100644 --- a/subiquity/client/controllers/ubuntu_pro.py +++ b/subiquity/client/controllers/ubuntu_pro.py @@ -70,7 +70,7 @@ class UbuntuProController(SubiquityTuiController): async def inner() -> None: answer = await self.endpoint.check_token.GET(token) if answer.status == TokenStatus.VALID_TOKEN: - on_success(answer.services) + on_success(answer.subscription.services) else: on_failure(answer.status) diff --git a/subiquity/common/types.py b/subiquity/common/types.py index e2eeef21..556c54f0 100644 --- a/subiquity/common/types.py +++ b/subiquity/common/types.py @@ -471,11 +471,18 @@ class UbuntuProService: auto_enabled: bool +@attr.s(auto_attribs=True) +class UbuntuProSubscription: + contract_name: str + account_name: str + services: List[UbuntuProService] + + @attr.s(auto_attribs=True) class UbuntuProCheckTokenAnswer: status: UbuntuProCheckTokenStatus - services: Optional[List[UbuntuProService]] + subscription: Optional[UbuntuProSubscription] class ShutdownMode(enum.Enum): diff --git a/subiquity/server/controllers/ubuntu_pro.py b/subiquity/server/controllers/ubuntu_pro.py index 1d1ab98b..d59387ae 100644 --- a/subiquity/server/controllers/ubuntu_pro.py +++ b/subiquity/server/controllers/ubuntu_pro.py @@ -122,13 +122,12 @@ class UbuntuProController(SubiquityController): async def check_token_GET(self, token: str) \ -> UbuntuProCheckTokenAnswer: """ Handle a GET request asking whether the contract token is valid or - not. If it is valid, we provide the list of activable services - associated with the subscription. + not. If it is valid, we provide the information about the subscription. """ - services = None + subscription = None try: - services = await \ - self.ua_interface.get_activable_services(token=token) + subscription = await \ + self.ua_interface.get_subscription(token=token) except InvalidTokenError: status = UbuntuProCheckTokenStatus.INVALID_TOKEN except ExpiredTokenError: @@ -138,4 +137,5 @@ class UbuntuProController(SubiquityController): else: status = UbuntuProCheckTokenStatus.VALID_TOKEN - return UbuntuProCheckTokenAnswer(status=status, services=services) + return UbuntuProCheckTokenAnswer(status=status, + subscription=subscription) diff --git a/subiquity/server/tests/test_ubuntu_advantage.py b/subiquity/server/tests/test_ubuntu_advantage.py index 4df6a76c..2b963e2b 100644 --- a/subiquity/server/tests/test_ubuntu_advantage.py +++ b/subiquity/server/tests/test_ubuntu_advantage.py @@ -125,30 +125,36 @@ class TestUAClientUAInterfaceStrategy(unittest.IsolatedAsyncioTestCase): class TestUAInterface(unittest.IsolatedAsyncioTestCase): - async def test_mocked_get_activable_services(self): + async def test_mocked_get_subscription(self): strategy = MockedUAInterfaceStrategy(scale_factor=1_000_000) interface = UAInterface(strategy) with self.assertRaises(InvalidTokenError): - await interface.get_activable_services(token="invalidToken") + await interface.get_subscription(token="invalidToken") # Tokens starting with "f" in dry-run mode simulate an "internal" # error. with self.assertRaises(CheckSubscriptionError): - await interface.get_activable_services(token="failure") + await interface.get_subscription(token="failure") # Tokens starting with "x" is dry-run mode simulate an expired token. with self.assertRaises(ExpiredTokenError): - await interface.get_activable_services(token="xpiredToken") + await interface.get_subscription(token="xpiredToken") # Other tokens are considered valid in dry-run mode. - await interface.get_activable_services(token="validToken") + await interface.get_subscription(token="validToken") - async def test_get_activable_services(self): + async def test_get_subscription(self): # We use the standard strategy but don't actually run it strategy = UAClientUAInterfaceStrategy() interface = UAInterface(strategy) - subscription = { + status = { + "account": { + "name": "user@domain.com", + }, + "contract": { + "name": "UA Apps - Essential (Virtual)", + }, "expires": "2035-12-31T00:00:00+00:00", "services": [ { @@ -183,30 +189,30 @@ class TestUAInterface(unittest.IsolatedAsyncioTestCase): }, ] } - interface.get_subscription = AsyncMock(return_value=subscription) - services = await interface.get_activable_services(token="XXX") + interface.get_subscription_status = AsyncMock(return_value=status) + subscription = await interface.get_subscription(token="XXX") self.assertIn(UbuntuProService( name="esm-infra", description="UA Infra: Extended Security Maintenance (ESM)", auto_enabled=True, - ), services) + ), subscription.services) self.assertIn(UbuntuProService( name="fips", description="NIST-certified core packages", auto_enabled=False, - ), services) + ), subscription.services) self.assertNotIn(UbuntuProService( name="esm-apps", description="UA Apps: Extended Security Maintenance (ESM)", auto_enabled=True, - ), services) + ), subscription.services) self.assertNotIn(UbuntuProService( name="cis", description="Center for Internet Security Audit Tools", auto_enabled=False, - ), services) + ), subscription.services) # Test with "Z" suffix for the expiration date. - subscription["expires"] = "2035-12-31T00:00:00Z" - services = await interface.get_activable_services(token="XXX") + status["expires"] = "2035-12-31T00:00:00Z" + subscription = await interface.get_subscription(token="XXX") diff --git a/subiquity/server/ubuntu_advantage.py b/subiquity/server/ubuntu_advantage.py index 65e11d8d..1315834f 100644 --- a/subiquity/server/ubuntu_advantage.py +++ b/subiquity/server/ubuntu_advantage.py @@ -23,7 +23,10 @@ from subprocess import CalledProcessError, CompletedProcess from typing import List, Sequence, Union import asyncio -from subiquity.common.types import UbuntuProService +from subiquity.common.types import ( + UbuntuProSubscription, + UbuntuProService, + ) from subiquitycore import utils @@ -144,16 +147,16 @@ class UAInterface: def __init__(self, strategy: UAInterfaceStrategy): self.strategy = strategy - async def get_subscription(self, token: str) -> dict: + async def get_subscription_status(self, token: str) -> dict: """ Return a dictionary containing the subscription information. """ return await self.strategy.query_info(token) - async def get_activable_services(self, token: str) \ - -> List[UbuntuProService]: - """ Return a list of activable services (i.e. services that are - entitled to the subscription and available on the current hardware). + async def get_subscription(self, token: str) -> UbuntuProSubscription: + """ Return the name of the contract, the name of the account and the + list of activable services (i.e. services that are entitled to the + subscription and available on the current hardware). """ - info = await self.get_subscription(token) + info = await self.get_subscription_status(token) # Sometimes, a time zone offset of 0 is replaced by the letter Z. This # is specified in RFC 3339 but not supported by fromisoformat. @@ -183,4 +186,8 @@ class UAInterface: if not is_activable_service(service): continue activable_services.append(service_from_dict(service)) - return activable_services + + return UbuntuProSubscription( + account_name=info["account"]["name"], + contract_name=info["contract"]["name"], + services=activable_services)