Merge pull request #1340 from ogayot/ubuntu-pro-subscription-information

ubuntu-pro: add subscription info in response to /ubuntu_pro/check_token
This commit is contained in:
Olivier Gayot 2022-07-01 16:42:14 +02:00 committed by GitHub
commit a9b206dedd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 63 additions and 31 deletions

View File

@ -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": [
{

View File

@ -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",

View File

@ -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)

View File

@ -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):

View File

@ -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)

View File

@ -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")

View File

@ -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)