Merge in RR/pyinfra from RED-6273-multi-tenant-storage to master
Squashed commit of the following:
commit 0fead1f8b59c9187330879b4e48d48355885c27c
Author: Julius Unverfehrt <julius.unverfehrt@iqser.com>
Date: Tue Mar 28 15:02:22 2023 +0200
fix typos
commit 892a803726946876f8b8cd7905a0e73c419b2fb1
Author: Matthias Bisping <matthias.bisping@axbit.com>
Date: Tue Mar 28 14:41:49 2023 +0200
Refactoring
Replace custom storage caching logic with LRU decorator
commit eafcd90260731e3360ce960571f07dee8f521327
Author: Julius Unverfehrt <julius.unverfehrt@iqser.com>
Date: Fri Mar 24 12:50:13 2023 +0100
fix bug in storage connection from endpoint
commit d0c9fb5b7d1c55ae2f90e8faa1efec9f7587c26a
Author: Julius Unverfehrt <julius.unverfehrt@iqser.com>
Date: Fri Mar 24 11:49:34 2023 +0100
add logs to PayloadProcessor
- set log messages to determine if x-tenant
storage connection is working
commit 97309fe58037b90469cf7a3de342d4749a0edfde
Author: Julius Unverfehrt <julius.unverfehrt@iqser.com>
Date: Fri Mar 24 10:41:59 2023 +0100
update PayloadProcessor
- introduce storage cache to make every unique
storage connection only once
- add functionality to pass optional processing
kwargs in queue message like the operation key to
the processing function
commit d48e8108fdc0d463c89aaa0d672061ab7dca83a0
Author: Julius Unverfehrt <julius.unverfehrt@iqser.com>
Date: Wed Mar 22 13:34:43 2023 +0100
add multi-tenant storage connection 1st iteration
- forward x-tenant-id from queue message header to
payload processor
- add functions to receive storage infos from an
endpoint or the config. This enables hashing and
caching of connections created from these infos
- add function to initialize storage connections
from storage infos
- streamline and refactor tests to make them more
readable and robust and to make it easier to add
new tests
- update payload processor with first iteration
of multi tenancy storage connection support
with connection caching and backwards compability
commit 52c047c47b98e62d0b834a9b9b6c0e2bb0db41e5
Author: Julius Unverfehrt <julius.unverfehrt@iqser.com>
Date: Tue Mar 21 15:35:57 2023 +0100
add AES/GCM cipher functions
- decrypt x-tenant storage connection strings
126 lines
3.9 KiB
Python
126 lines
3.9 KiB
Python
from dataclasses import dataclass
|
|
|
|
import requests
|
|
from azure.storage.blob import BlobServiceClient
|
|
from minio import Minio
|
|
|
|
from pyinfra.config import Config
|
|
from pyinfra.exception import UnknownStorageBackend
|
|
from pyinfra.storage.storages.azure import AzureStorage
|
|
from pyinfra.storage.storages.interface import Storage
|
|
from pyinfra.storage.storages.s3 import S3Storage
|
|
from pyinfra.utils.cipher import decrypt
|
|
from pyinfra.utils.url_parsing import validate_and_parse_s3_endpoint
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class StorageInfo:
|
|
bucket_name: str
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class AzureStorageInfo(StorageInfo):
|
|
connection_string: str
|
|
|
|
def __hash__(self):
|
|
return hash(self.connection_string)
|
|
|
|
def __eq__(self, other):
|
|
if not isinstance(other, AzureStorageInfo):
|
|
return False
|
|
return self.connection_string == other.connection_string
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class S3StorageInfo(StorageInfo):
|
|
secure: bool
|
|
endpoint: str
|
|
access_key: str
|
|
secret_key: str
|
|
region: str
|
|
|
|
def __hash__(self):
|
|
return hash((self.secure, self.endpoint, self.access_key, self.secret_key, self.region))
|
|
|
|
def __eq__(self, other):
|
|
if not isinstance(other, S3StorageInfo):
|
|
return False
|
|
return (
|
|
self.secure == other.secure
|
|
and self.endpoint == other.endpoint
|
|
and self.access_key == other.access_key
|
|
and self.secret_key == other.secret_key
|
|
and self.region == other.region
|
|
)
|
|
|
|
|
|
def get_storage_from_storage_info(storage_info: StorageInfo) -> Storage:
|
|
if isinstance(storage_info, AzureStorageInfo):
|
|
return AzureStorage(BlobServiceClient.from_connection_string(conn_str=storage_info.connection_string))
|
|
elif isinstance(storage_info, S3StorageInfo):
|
|
return S3Storage(
|
|
Minio(
|
|
secure=storage_info.secure,
|
|
endpoint=storage_info.endpoint,
|
|
access_key=storage_info.access_key,
|
|
secret_key=storage_info.secret_key,
|
|
region=storage_info.region,
|
|
)
|
|
)
|
|
else:
|
|
raise UnknownStorageBackend()
|
|
|
|
|
|
def get_storage_info_from_endpoint(public_key: str, endpoint: str, x_tenant_id: str) -> StorageInfo:
|
|
resp = requests.get(f"{endpoint}/{x_tenant_id}").json()
|
|
|
|
maybe_azure = resp.get("azureStorageConnection")
|
|
maybe_s3 = resp.get("s3StorageConnection")
|
|
assert not (maybe_azure and maybe_s3)
|
|
|
|
if maybe_azure:
|
|
connection_string = decrypt(public_key, maybe_azure["connectionString"])
|
|
storage_info = AzureStorageInfo(
|
|
connection_string=connection_string,
|
|
bucket_name=maybe_azure["containerName"],
|
|
)
|
|
elif maybe_s3:
|
|
secure, endpoint = validate_and_parse_s3_endpoint(maybe_s3["endpoint"])
|
|
secret = decrypt(public_key, maybe_s3["secret"])
|
|
|
|
storage_info = S3StorageInfo(
|
|
secure=secure,
|
|
endpoint=endpoint,
|
|
access_key=maybe_s3["key"],
|
|
secret_key=secret,
|
|
region=maybe_s3["region"],
|
|
bucket_name=maybe_s3["bucketName"],
|
|
)
|
|
else:
|
|
raise UnknownStorageBackend()
|
|
|
|
return storage_info
|
|
|
|
|
|
def get_storage_info_from_config(config: Config) -> StorageInfo:
|
|
if config.storage_backend == "s3":
|
|
storage_info = S3StorageInfo(
|
|
secure=config.storage_secure_connection,
|
|
endpoint=config.storage_endpoint,
|
|
access_key=config.storage_key,
|
|
secret_key=config.storage_secret,
|
|
region=config.storage_region,
|
|
bucket_name=config.storage_bucket,
|
|
)
|
|
|
|
elif config.storage_backend == "azure":
|
|
storage_info = AzureStorageInfo(
|
|
connection_string=config.storage_azureconnectionstring,
|
|
bucket_name=config.storage_bucket,
|
|
)
|
|
|
|
else:
|
|
raise UnknownStorageBackend(f"Unknown storage backend '{config.storage_backend}'.")
|
|
|
|
return storage_info
|