Skip to content

Commit 4451600

Browse files
authored
feat: stage-out MAAP use case (#114)
* feat: add AUDIT log level for upload * chore: update outdated tests * fix: allow empty str as RESULT_PATH_PREFIX & replace w/ default val * fix: allowing optional original stac item * feat: add archive method * feat: adding stac fast api client * feat: fast api catalog code * fix: add missing file * fix: fix fast api catalog with test case * feat: add update item * fix: minor updates
1 parent be12ca7 commit 4451600

16 files changed

+700
-54
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ __pycache__
1313
.terraform.lock.hcl
1414
terraform.tf
1515
terraform.tfvars
16-
cumulus_lambda_functions_deployment.zip
16+
cumulus_lambda_functions_deployment.zip
17+
examples/**/*

mdps_ds_lib/ds_client/auth_token/token_factory.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ class TokenFactory(FactoryAbstract):
55
DUMMY = 'DUMMY'
66
COGNITO = 'COGNITO'
77

8+
def get_instance_from_env(self, **kwargs):
9+
raise NotImplementedError('not yet')
10+
811
def get_instance(self, class_type, **kwargs):
912
ct = class_type.upper()
1013
if ct == self.DUMMY:

mdps_ds_lib/ds_client/ds_client_user.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,25 @@ def add_archive_config(self, daac_config: dict):
290290
response = json.loads(response.text)
291291
return response
292292

293+
def get_archive_config(self):
294+
if self.tenant is None or self.tenant_venue is None or self.collection is None:
295+
raise ValueError(f'require to set tenant & tenant_venue & collection')
296+
collection_id = ':'.join([self.urn, self.org, self.project, self.tenant, self.tenant_venue, self.get_complete_collection()])
297+
298+
request_url = f'{self._uds_url}collections/{collection_id}/archive/'
299+
print(f'request_url: {request_url}')
300+
print(f'token: {self._token_retriever.get_token()}')
301+
s = requests.session()
302+
s.trust_env = self._trust_env
303+
response = s.get(url=request_url, headers={
304+
'Authorization': f'Bearer {self._token_retriever.get_token()}',
305+
'Content-Type': 'application/json',
306+
307+
}, verify=self._trust_env)
308+
response.raise_for_status()
309+
response = json.loads(response.text)
310+
return response
311+
293312
def archive_granule(self):
294313
if self.tenant is None or self.tenant_venue is None or self.collection is None or self.granule is None:
295314
raise ValueError(f'require to set tenant & tenant_venue & collection & granule')

mdps_ds_lib/lib/utils/factory_abstract.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@ class FactoryAbstract(ABC):
55
@abstractmethod
66
def get_instance(self, class_type, **kwargs):
77
return
8+
9+
@abstractmethod
10+
def get_instance_from_env(self, **kwargs):
11+
return

mdps_ds_lib/stac_fast_api_client/__init__.py

Whitespace-only changes.
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import os
2+
from abc import ABC, abstractmethod
3+
4+
import requests
5+
from dotenv import load_dotenv
6+
7+
8+
class SFAClientBase(ABC):
9+
def __init__(self, ds_url: str, ds_stage: str):
10+
load_dotenv()
11+
self._trust_env = os.getenv('TRUST_ENV', 'FALSE').upper().strip() == 'TRUE'
12+
self._base_url = f'{ds_url}/{ds_stage}'
13+
14+
@abstractmethod
15+
def create_session(self):
16+
s = requests.session()
17+
s.trust_env = self._trust_env
18+
# s.cookies.set('mod_auth_openidc_session', 'xxx-xxx-xxx')
19+
# s.auth = HTTPBasicAuth('user', 'pass')
20+
# s.headers.update({"Authorization": f"Bearer {token}"})
21+
return s
22+
23+
def _handle_response(self, response):
24+
"""
25+
Raises HTTPError with detailed response text if available.
26+
"""
27+
try:
28+
response.raise_for_status()
29+
except requests.HTTPError as e:
30+
# Attach response text to the exception for easier debugging
31+
error_text = response.text.strip()
32+
raise requests.HTTPError(
33+
f"{e}\nResponse content: {error_text}"
34+
) from None
35+
try:
36+
return response.json()
37+
except:
38+
raise requests.HTTPError(f'invalid JSON response: {response.text}')
39+
40+
def get_collections(self, **params):
41+
url = f"{self._base_url}/collections"
42+
response = self.create_session().get(url, params=params)
43+
return self._handle_response(response)
44+
45+
def get_collection(self, collection_id, **params) -> dict: # Return a COllection STAC JSON
46+
url = f"{self._base_url}/collections/{collection_id}"
47+
response = self.create_session().get(url, params=params)
48+
return self._handle_response(response)
49+
50+
def create_collection(self, collection):
51+
url = f"{self._base_url}/collections"
52+
response = self.create_session().post(url, json=collection)
53+
# NOTE: result if not found: {"code":"NotFoundError","description":"Collection Invalid-Collection not found"}
54+
return self._handle_response(response)
55+
56+
def get_items(self, collection_id, **params):
57+
url = f"{self._base_url}/collections/{collection_id}/items"
58+
response = self.create_session().get(url, params=params)
59+
return self._handle_response(response)
60+
61+
def create_item(self, collection_id, item):
62+
url = f"{self._base_url}/collections/{collection_id}/items"
63+
my_session = self.create_session()
64+
my_session.headers.update({'Content-Type': 'application/json'})
65+
response = self.create_session().post(url, json=item)
66+
return self._handle_response(response)
67+
68+
def update_item(self, collection_id, item_id, item, update_whole=True):
69+
url = f"{self._base_url}/collections/{collection_id}/items/{item_id}"
70+
my_session = self.create_session()
71+
my_session.headers.update({'Content-Type': 'application/json'})
72+
response = self.create_session().put(url, json=item) if update_whole else self.create_session().patch(url, json=item)
73+
return self._handle_response(response)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from requests.auth import HTTPBasicAuth
2+
3+
from mdps_ds_lib.stac_fast_api_client.sfa_client_base import SFAClientBase
4+
5+
6+
class SFAClientBasicAuth(SFAClientBase):
7+
8+
def __init__(self, username: str, password: str, ds_url: str, ds_stage: str):
9+
super().__init__(ds_url, ds_stage)
10+
self.__user = username
11+
self.__pass = password
12+
13+
def create_session(self):
14+
s1 = super().create_session()
15+
s1.auth = HTTPBasicAuth(self.__user, self.__pass)
16+
return s1
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from requests.auth import HTTPBasicAuth
2+
3+
from mdps_ds_lib.stac_fast_api_client.sfa_client_base import SFAClientBase
4+
5+
6+
class SFAClientBearer(SFAClientBase):
7+
8+
def __init__(self, bearer_token: str, ds_url: str, ds_stage: str):
9+
super().__init__(ds_url, ds_stage)
10+
self.__bearer_token = bearer_token
11+
12+
def create_session(self):
13+
s1 = super().create_session()
14+
s1.headers.update({"Authorization": f"Bearer {self.__bearer_token}"})
15+
return s1
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from requests.auth import HTTPBasicAuth
2+
3+
from mdps_ds_lib.stac_fast_api_client.sfa_client_base import SFAClientBase
4+
5+
6+
class SFAClientCookieToken(SFAClientBase):
7+
8+
def __init__(self, auth_key: str, auth_value: str, ds_url: str, ds_stage: str):
9+
super().__init__(ds_url, ds_stage)
10+
self.__auth_key = auth_key
11+
self.__auth_value = auth_value
12+
13+
def create_session(self):
14+
s1 = super().create_session()
15+
s1.cookies.set(self.__auth_key, self.__auth_value)
16+
return s1
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from mdps_ds_lib.lib.utils.factory_abstract import FactoryAbstract
2+
from mdps_ds_lib.stac_fast_api_client.sfa_client_base import SFAClientBase
3+
import os
4+
5+
6+
class SFAClientFactory(FactoryAbstract):
7+
NO_AUTH = 'NO_AUTH'
8+
BASIC_AUTH = 'BASIC_AUTH'
9+
COOKIE_AUTH = 'COOKIE_AUTH'
10+
BEARER_AUTH = 'BEARER_AUTH'
11+
12+
def get_instance_from_env(self, **kwargs) -> SFAClientBase:
13+
if 'DS_URL' not in os.environ:
14+
raise RuntimeError(f'missing mandatory env: DS_URL')
15+
class_env = {'ds_url': os.getenv('DS_URL'),
16+
'ds_stage': '' if 'ds_stage'.upper() not in os.environ else os.getenv('ds_stage'.upper())}
17+
18+
class_type_env_map = {
19+
SFAClientFactory.BASIC_AUTH: {
20+
'SFA_USERNAME': 'username',
21+
'SFA_PASSWORD': 'password'
22+
},
23+
SFAClientFactory.COOKIE_AUTH: {
24+
'SFA_AUTH_KEY': 'auth_key',
25+
'SFA_AUTH_VALUE': 'auth_value'
26+
},
27+
SFAClientFactory.BEARER_AUTH: {
28+
'SFA_BEARER_TOKEN': 'bearer_token',
29+
},
30+
SFAClientFactory.NO_AUTH: {
31+
},
32+
}
33+
chosen_class = None
34+
for k, v in class_type_env_map.items():
35+
if all([k1 in os.environ for k1 in list(v.keys())]):
36+
for k1, v1 in v.items():
37+
class_env[v1] = os.getenv(k1)
38+
chosen_class = k
39+
break
40+
if chosen_class is None:
41+
raise NotImplementedError(f'unknown class type: missing ENVs. require one of {class_type_env_map}')
42+
return self.get_instance(chosen_class, **class_env)
43+
44+
def get_instance(self, class_type, **kwargs) -> SFAClientBase:
45+
if class_type == self.NO_AUTH:
46+
from mdps_ds_lib.stac_fast_api_client.sfa_client_no_auth import SFAClientNoAuth
47+
return SFAClientNoAuth(kwargs['ds_url'], kwargs['ds_stage'])
48+
if class_type == self.BASIC_AUTH:
49+
from mdps_ds_lib.stac_fast_api_client.sfa_client_basic_auth import SFAClientBasicAuth
50+
return SFAClientBasicAuth(kwargs['username'], kwargs['password'], kwargs['ds_url'], kwargs['ds_stage'])
51+
if class_type == self.COOKIE_AUTH:
52+
from mdps_ds_lib.stac_fast_api_client.sfa_client_cookie_token import SFAClientCookieToken
53+
return SFAClientCookieToken(kwargs['auth_key'], kwargs['auth_value'], kwargs['ds_url'], kwargs['ds_stage'])
54+
if class_type == self.BEARER_AUTH:
55+
from mdps_ds_lib.stac_fast_api_client.sfa_client_bearer import SFAClientBearer
56+
return SFAClientBearer(kwargs['bearer_token'], kwargs['ds_url'], kwargs['ds_stage'])
57+
raise NotImplementedError(f'unknown class type: {class_type}')

0 commit comments

Comments
 (0)