ubuntu-pro: add subscription info in response to /ubuntu_pro/check_token
The response to /ubuntu_pro/check_token now includes information about the subscription: the name of the contract and the "name" of the account. Instead of returning the list of services as an optional field, we now include the list of services in the subscription object. The subscription object is itself marked optional. This is a backward incompatible change. { "status": "VALID_TOKEN", "services": [ { "name": "esm-infra", "description": "UA Infra: Extended Security Maintenance (ESM)", "auto_enabled": true } ] } => { "status": "VALID_TOKEN", "subscription": { "account_name": "user@domain.com", "contract_name": "UA Apps - Essential (Virtual)", "services": [ { "name": "esm-infra", "description": "UA Infra: Extended Security Maintenance (ESM)", "auto_enabled": true } ] } } If the token is not valid, the subscription object will be null: { "status": "EXPIRED_TOKEN", "subscription": null } Signed-off-by: Olivier Gayot <olivier.gayot@canonical.com>
This commit is contained in:
parent
971ba36704
commit
7ebef6d609
|
@ -1,6 +1,12 @@
|
||||||
{
|
{
|
||||||
"version": "27.4.2~21.10.1",
|
"version": "27.4.2~21.10.1",
|
||||||
"effective": null,
|
"effective": null,
|
||||||
|
"contract": {
|
||||||
|
"name": "UA Apps - Essential (Virtual)"
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"name": "user@domain.com"
|
||||||
|
},
|
||||||
"expires": "2010-12-31T00:00:00+00:00",
|
"expires": "2010-12-31T00:00:00+00:00",
|
||||||
"services": [
|
"services": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,6 +2,12 @@
|
||||||
"version": "27.4.2~21.10.1",
|
"version": "27.4.2~21.10.1",
|
||||||
"effective": null,
|
"effective": null,
|
||||||
"expires": "2035-12-31T00:00:00+00:00",
|
"expires": "2035-12-31T00:00:00+00:00",
|
||||||
|
"contract": {
|
||||||
|
"name": "UA Apps - Essential (Virtual)"
|
||||||
|
},
|
||||||
|
"account": {
|
||||||
|
"name": "user@domain.com"
|
||||||
|
},
|
||||||
"services": [
|
"services": [
|
||||||
{
|
{
|
||||||
"name": "cis",
|
"name": "cis",
|
||||||
|
|
|
@ -70,7 +70,7 @@ class UbuntuProController(SubiquityTuiController):
|
||||||
async def inner() -> None:
|
async def inner() -> None:
|
||||||
answer = await self.endpoint.check_token.GET(token)
|
answer = await self.endpoint.check_token.GET(token)
|
||||||
if answer.status == TokenStatus.VALID_TOKEN:
|
if answer.status == TokenStatus.VALID_TOKEN:
|
||||||
on_success(answer.services)
|
on_success(answer.subscription.services)
|
||||||
else:
|
else:
|
||||||
on_failure(answer.status)
|
on_failure(answer.status)
|
||||||
|
|
||||||
|
|
|
@ -463,11 +463,18 @@ class UbuntuProService:
|
||||||
auto_enabled: bool
|
auto_enabled: bool
|
||||||
|
|
||||||
|
|
||||||
|
@attr.s(auto_attribs=True)
|
||||||
|
class UbuntuProSubscription:
|
||||||
|
contract_name: str
|
||||||
|
account_name: str
|
||||||
|
services: List[UbuntuProService]
|
||||||
|
|
||||||
|
|
||||||
@attr.s(auto_attribs=True)
|
@attr.s(auto_attribs=True)
|
||||||
class UbuntuProCheckTokenAnswer:
|
class UbuntuProCheckTokenAnswer:
|
||||||
status: UbuntuProCheckTokenStatus
|
status: UbuntuProCheckTokenStatus
|
||||||
|
|
||||||
services: Optional[List[UbuntuProService]]
|
subscription: Optional[UbuntuProSubscription]
|
||||||
|
|
||||||
|
|
||||||
class ShutdownMode(enum.Enum):
|
class ShutdownMode(enum.Enum):
|
||||||
|
|
|
@ -122,13 +122,12 @@ class UbuntuProController(SubiquityController):
|
||||||
async def check_token_GET(self, token: str) \
|
async def check_token_GET(self, token: str) \
|
||||||
-> UbuntuProCheckTokenAnswer:
|
-> UbuntuProCheckTokenAnswer:
|
||||||
""" Handle a GET request asking whether the contract token is valid or
|
""" Handle a GET request asking whether the contract token is valid or
|
||||||
not. If it is valid, we provide the list of activable services
|
not. If it is valid, we provide the information about the subscription.
|
||||||
associated with the subscription.
|
|
||||||
"""
|
"""
|
||||||
services = None
|
subscription = None
|
||||||
try:
|
try:
|
||||||
services = await \
|
subscription = await \
|
||||||
self.ua_interface.get_activable_services(token=token)
|
self.ua_interface.get_subscription(token=token)
|
||||||
except InvalidTokenError:
|
except InvalidTokenError:
|
||||||
status = UbuntuProCheckTokenStatus.INVALID_TOKEN
|
status = UbuntuProCheckTokenStatus.INVALID_TOKEN
|
||||||
except ExpiredTokenError:
|
except ExpiredTokenError:
|
||||||
|
@ -138,4 +137,5 @@ class UbuntuProController(SubiquityController):
|
||||||
else:
|
else:
|
||||||
status = UbuntuProCheckTokenStatus.VALID_TOKEN
|
status = UbuntuProCheckTokenStatus.VALID_TOKEN
|
||||||
|
|
||||||
return UbuntuProCheckTokenAnswer(status=status, services=services)
|
return UbuntuProCheckTokenAnswer(status=status,
|
||||||
|
subscription=subscription)
|
||||||
|
|
|
@ -125,30 +125,36 @@ class TestUAClientUAInterfaceStrategy(unittest.IsolatedAsyncioTestCase):
|
||||||
|
|
||||||
class TestUAInterface(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)
|
strategy = MockedUAInterfaceStrategy(scale_factor=1_000_000)
|
||||||
interface = UAInterface(strategy)
|
interface = UAInterface(strategy)
|
||||||
|
|
||||||
with self.assertRaises(InvalidTokenError):
|
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"
|
# Tokens starting with "f" in dry-run mode simulate an "internal"
|
||||||
# error.
|
# error.
|
||||||
with self.assertRaises(CheckSubscriptionError):
|
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.
|
# Tokens starting with "x" is dry-run mode simulate an expired token.
|
||||||
with self.assertRaises(ExpiredTokenError):
|
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.
|
# 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
|
# We use the standard strategy but don't actually run it
|
||||||
strategy = UAClientUAInterfaceStrategy()
|
strategy = UAClientUAInterfaceStrategy()
|
||||||
interface = UAInterface(strategy)
|
interface = UAInterface(strategy)
|
||||||
|
|
||||||
subscription = {
|
status = {
|
||||||
|
"account": {
|
||||||
|
"name": "user@domain.com",
|
||||||
|
},
|
||||||
|
"contract": {
|
||||||
|
"name": "UA Apps - Essential (Virtual)",
|
||||||
|
},
|
||||||
"expires": "2035-12-31T00:00:00+00:00",
|
"expires": "2035-12-31T00:00:00+00:00",
|
||||||
"services": [
|
"services": [
|
||||||
{
|
{
|
||||||
|
@ -183,30 +189,30 @@ class TestUAInterface(unittest.IsolatedAsyncioTestCase):
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
interface.get_subscription = AsyncMock(return_value=subscription)
|
interface.get_subscription_status = AsyncMock(return_value=status)
|
||||||
services = await interface.get_activable_services(token="XXX")
|
subscription = await interface.get_subscription(token="XXX")
|
||||||
|
|
||||||
self.assertIn(UbuntuProService(
|
self.assertIn(UbuntuProService(
|
||||||
name="esm-infra",
|
name="esm-infra",
|
||||||
description="UA Infra: Extended Security Maintenance (ESM)",
|
description="UA Infra: Extended Security Maintenance (ESM)",
|
||||||
auto_enabled=True,
|
auto_enabled=True,
|
||||||
), services)
|
), subscription.services)
|
||||||
self.assertIn(UbuntuProService(
|
self.assertIn(UbuntuProService(
|
||||||
name="fips",
|
name="fips",
|
||||||
description="NIST-certified core packages",
|
description="NIST-certified core packages",
|
||||||
auto_enabled=False,
|
auto_enabled=False,
|
||||||
), services)
|
), subscription.services)
|
||||||
self.assertNotIn(UbuntuProService(
|
self.assertNotIn(UbuntuProService(
|
||||||
name="esm-apps",
|
name="esm-apps",
|
||||||
description="UA Apps: Extended Security Maintenance (ESM)",
|
description="UA Apps: Extended Security Maintenance (ESM)",
|
||||||
auto_enabled=True,
|
auto_enabled=True,
|
||||||
), services)
|
), subscription.services)
|
||||||
self.assertNotIn(UbuntuProService(
|
self.assertNotIn(UbuntuProService(
|
||||||
name="cis",
|
name="cis",
|
||||||
description="Center for Internet Security Audit Tools",
|
description="Center for Internet Security Audit Tools",
|
||||||
auto_enabled=False,
|
auto_enabled=False,
|
||||||
), services)
|
), subscription.services)
|
||||||
|
|
||||||
# Test with "Z" suffix for the expiration date.
|
# Test with "Z" suffix for the expiration date.
|
||||||
subscription["expires"] = "2035-12-31T00:00:00Z"
|
status["expires"] = "2035-12-31T00:00:00Z"
|
||||||
services = await interface.get_activable_services(token="XXX")
|
subscription = await interface.get_subscription(token="XXX")
|
||||||
|
|
|
@ -23,7 +23,10 @@ from subprocess import CalledProcessError, CompletedProcess
|
||||||
from typing import List, Sequence, Union
|
from typing import List, Sequence, Union
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from subiquity.common.types import UbuntuProService
|
from subiquity.common.types import (
|
||||||
|
UbuntuProSubscription,
|
||||||
|
UbuntuProService,
|
||||||
|
)
|
||||||
from subiquitycore import utils
|
from subiquitycore import utils
|
||||||
|
|
||||||
|
|
||||||
|
@ -144,16 +147,16 @@ class UAInterface:
|
||||||
def __init__(self, strategy: UAInterfaceStrategy):
|
def __init__(self, strategy: UAInterfaceStrategy):
|
||||||
self.strategy = strategy
|
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 a dictionary containing the subscription information. """
|
||||||
return await self.strategy.query_info(token)
|
return await self.strategy.query_info(token)
|
||||||
|
|
||||||
async def get_activable_services(self, token: str) \
|
async def get_subscription(self, token: str) -> UbuntuProSubscription:
|
||||||
-> List[UbuntuProService]:
|
""" Return the name of the contract, the name of the account and the
|
||||||
""" Return a list of activable services (i.e. services that are
|
list of activable services (i.e. services that are entitled to the
|
||||||
entitled to the subscription and available on the current hardware).
|
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
|
# Sometimes, a time zone offset of 0 is replaced by the letter Z. This
|
||||||
# is specified in RFC 3339 but not supported by fromisoformat.
|
# is specified in RFC 3339 but not supported by fromisoformat.
|
||||||
|
@ -183,4 +186,8 @@ class UAInterface:
|
||||||
if not is_activable_service(service):
|
if not is_activable_service(service):
|
||||||
continue
|
continue
|
||||||
activable_services.append(service_from_dict(service))
|
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)
|
||||||
|
|
Loading…
Reference in New Issue