Skip to content

Commit 51e15a3

Browse files
committed
LITE-28130 Add capability to Install/Update product in CBC using PLM API
1 parent bbe6dd2 commit 51e15a3

File tree

15 files changed

+814
-58
lines changed

15 files changed

+814
-58
lines changed

CBCClientHowTo.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,14 @@ endpoint = '******'
1212
client_id = '*****'
1313
# CBC OAuth Key
1414
client_secret = '*****'
15+
# CBC Extension App ID
16+
app_id = '*****'
1517

1618
client = CBCClient(
1719
endpoint=endpoint,
1820
oauth_key=client_id,
1921
oauth_secret=client_secret,
22+
app_id=app_id,
2023
)
2124

2225
```

connect_ext_ppr/client/client.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ def execute_request(
9494
raise ClientError(
9595
message=f'{type(e).__name__} : {str(e)}',
9696
status_code=response.status_code,
97+
response=response,
9798
cause=e,
9899
)
99100
else:

connect_ext_ppr/client/exception.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1+
from requests import Response
2+
3+
14
class ClientError(RuntimeError):
25
def __init__(
36
self,
47
message: str,
58
status_code: int = None,
9+
response: Response = None,
610
cause: Exception = None,
711
):
812
self.message = message
13+
self.response = response
914
self.status_code = status_code
1015
self.cause = cause

connect_ext_ppr/client/ns.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from functools import cached_property
2+
13
from connect_ext_ppr.client.mixin import (
24
ActionMixin,
35
GetMixin,
@@ -65,7 +67,15 @@ class Service(
6567
NSBase,
6668
ActionMixin,
6769
):
68-
def _get_service_path(self):
70+
def __init__(self, client, aps_type: str, path: str):
71+
super().__init__(
72+
client=client,
73+
path=path,
74+
)
75+
self.aps_type = aps_type
76+
77+
@cached_property
78+
def service_path(self):
6979
aps_type_object = self.client.execute_request(
7080
method='GET',
7181
path=f'{self.path}/aps/2/resources/?implementing({self.aps_type})',
@@ -79,13 +89,6 @@ def _get_service_path(self):
7989
service_id = aps_type_object[0]['aps']['id']
8090
return f'{self.path}/aps/2/resources/{service_id}'
8191

82-
def __init__(self, client, aps_type: str, path: str):
83-
super().__init__(
84-
client=client,
85-
path=path,
86-
)
87-
self.aps_type = aps_type
88-
8992
def __getattr__(self, name):
9093
if '_' in name:
9194
name = name.replace('_', '-')
@@ -101,7 +104,7 @@ def collection(self, name: str):
101104

102105
return Collection(
103106
self.client,
104-
f'{self._get_service_path()}/{name}',
107+
f'{self.service_path}/{name}',
105108
)
106109

107110
def get(self, **kwargs):

connect_ext_ppr/service.py

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,11 @@
44

55
from connect_ext_ppr.errors import ExtensionValidationError
66
from connect_ext_ppr.db import get_db_ctx_manager
7-
from connect_ext_ppr.models.cbc_extenstion import HubCredential
87
from connect_ext_ppr.models.deployment import Deployment
98
from connect_ext_ppr.models.replicas import Product
109
from connect_ext_ppr.utils import _parse_json_schema_error
1110
from connect_ext_ppr.constants import PPR_SCHEMA
1211

13-
from sqlalchemy import text
14-
1512

1613
def insert_product_from_listing(db, listing_data, logger):
1714
product_data = listing_data['product']
@@ -93,31 +90,3 @@ def update_product(data, config, logger):
9390
product.version = data['version']
9491
db.add(product)
9592
db.commit()
96-
97-
98-
def get_hub_credentials(hub_id, db):
99-
100-
query = text(
101-
'SELECT DISTINCT h.hub_id, g.app_instance_id, h.controller_uri, '
102-
'c.oauth_key, c.oauth_secret '
103-
'FROM hub_instances h '
104-
'INNER JOIN global_app_configuration g ON g.hub_uuid = h.extension_resource_uid '
105-
'INNER JOIN configuration c ON c.product_id = g.hub_uuid '
106-
'WHERE h.hub_id = :hub_id',
107-
)
108-
109-
query = query.columns(
110-
HubCredential.hub_id,
111-
HubCredential.app_id,
112-
HubCredential.controller_url,
113-
HubCredential.oauth_key,
114-
HubCredential.oauth_secret,
115-
)
116-
117-
return db.query(
118-
HubCredential,
119-
).from_statement(
120-
query,
121-
).params(
122-
hub_id=hub_id,
123-
).first()

connect_ext_ppr/services/__init__.py

Whitespace-only changes.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from connect_ext_ppr.models.cbc_extenstion import HubCredential
2+
3+
from sqlalchemy import text
4+
5+
6+
HUB_CREDENTIAL_QUERY = '''
7+
SELECT DISTINCT h.hub_id,
8+
g.app_instance_id,
9+
h.controller_uri,
10+
c.oauth_key,
11+
c.oauth_secret
12+
FROM hub_instances h
13+
INNER JOIN global_app_configuration g ON g.hub_uuid = h.extension_resource_uid
14+
INNER JOIN configuration c ON c.product_id = g.hub_uuid
15+
WHERE h.hub_id = :hub_id
16+
'''
17+
18+
19+
def get_hub_credentials(hub_id, db):
20+
21+
query = text(HUB_CREDENTIAL_QUERY)
22+
23+
query = query.columns(
24+
HubCredential.hub_id,
25+
HubCredential.app_id,
26+
HubCredential.controller_url,
27+
HubCredential.oauth_key,
28+
HubCredential.oauth_secret,
29+
)
30+
31+
return db.query(
32+
HubCredential,
33+
).from_statement(
34+
query,
35+
).params(
36+
hub_id=hub_id,
37+
).first()

connect_ext_ppr/services/cbc_hub.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from functools import cached_property
2+
3+
from connect_ext_ppr.client import CBCClient
4+
from connect_ext_ppr.client.exception import ClientError
5+
from connect_ext_ppr.models.cbc_extenstion import HubCredential
6+
7+
8+
class CBCService:
9+
10+
PLM_TYPE = 'http://com.odin.platform/inhouse-products/application'
11+
SUBSCRIPTION_TYPE = 'http://parallels.com/aps/types/pa/subscription'
12+
13+
def __init__(self, hub_credential: HubCredential, verify_certificate: bool = False):
14+
self.hub_credential = hub_credential
15+
self.__validate_hub_credentials_object()
16+
17+
self.client = CBCClient(
18+
endpoint=hub_credential.controller_url,
19+
oauth_key=hub_credential.oauth_key,
20+
oauth_secret=hub_credential.oauth_secret,
21+
app_id=hub_credential.app_id,
22+
verify_certificate=verify_certificate,
23+
)
24+
self.__validate_client()
25+
26+
def __validate_hub_credentials_object(self):
27+
if not self.hub_credential:
28+
raise ValueError('`hub_credential` must be not be empty.')
29+
if not isinstance(self.hub_credential, HubCredential):
30+
raise TypeError('`hub_credential` must be object of HubCredential.')
31+
if not self.hub_credential.controller_url:
32+
raise ValueError('`hub_credential.controller_url` must be not be empty.')
33+
if not self.hub_credential.oauth_key:
34+
raise ValueError('`hub_credential.oauth_key` must be not be empty.')
35+
if not self.hub_credential.oauth_secret:
36+
raise ValueError('`hub_credential.oauth_secret` must be not be empty.')
37+
38+
def __validate_client(self):
39+
try:
40+
self.client.execute_request(
41+
method='GET',
42+
path=f'{self.hub_credential.controller_url}/aps',
43+
)
44+
except ClientError:
45+
raise ValueError('hub_credential are not valid!')
46+
47+
@cached_property
48+
def primary_subscription_id(self):
49+
subscriptions = self.subscription_service.get(
50+
subscriptionId=1,
51+
)
52+
return subscriptions[0]['aps']['id']
53+
54+
@cached_property
55+
def plm_service(self):
56+
return self.client(self.PLM_TYPE)
57+
58+
@cached_property
59+
def subscription_service(self):
60+
return self.client(self.SUBSCRIPTION_TYPE)
61+
62+
def get_product_details(self, product_id: str):
63+
return self.plm_service.appDetails[product_id].get(
64+
fulfillmentSystem='connect',
65+
)
66+
67+
def install_product(self, product_id: str):
68+
self.plm_service.appDetails[product_id].action(
69+
name='import',
70+
payload={
71+
'subscriptionId': self.primary_subscription_id,
72+
'fulfillmentSystem': 'connect',
73+
},
74+
)
75+
76+
def update_product(self, product_id: str):
77+
return self.plm_service.appDetails[product_id].action(
78+
name='upgrade',
79+
payload={
80+
'subscriptionId': self.primary_subscription_id,
81+
'fulfillmentSystem': 'connect',
82+
},
83+
)

tests/client/test_service.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def test_service_discovery_collection_with_underscore(
9191

9292

9393
@responses.activate
94-
def test_service_discovery_collection_without_underscore(
94+
def test_service_discovery_collection(
9595
cbc_endpoint,
9696
cbc_client,
9797
flat_catalog_type,
@@ -104,10 +104,18 @@ def test_service_discovery_collection_without_underscore(
104104
json=flat_catalog_type_objects,
105105
)
106106

107-
collection = cbc_client(flat_catalog_type).flatcatalog
107+
service = cbc_client(flat_catalog_type)
108+
109+
# First time - calls service discovery API
110+
collection = service.flatcatalog
108111

109112
assert collection.path == f'{cbc_endpoint}/aps/2/resources/{service_id}/flatcatalog'
110113

114+
# 2nd time - no call to service discovery API
115+
collection = service.subscriptions
116+
117+
assert collection.path == f'{cbc_endpoint}/aps/2/resources/{service_id}/subscriptions'
118+
111119

112120
@responses.activate
113121
def test_service_discovery_collection_wrong_collection_value_type(

0 commit comments

Comments
 (0)