Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ google-api-core==2.25.1
google-api-python-client==2.181.0
lief==0.17.2
lzfse>=0.4.2
objectstore-client>=0.0.14
objectstore-client>=0.0.15
pillow>=11.3.0
pillow_heif>=1.1.0
protobuf>=5.29.5,<6
Expand Down
5 changes: 2 additions & 3 deletions src/launchpad/artifact_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
from launchpad.tracing import request_context
from launchpad.utils.file_utils import IdPrefix, id_from_bytes
from launchpad.utils.logging import get_logger
from launchpad.utils.objectstore import create_objectstore_client
from launchpad.utils.statsd import StatsdInterface, get_statsd

logger = get_logger(__name__)
Expand Down Expand Up @@ -92,9 +93,7 @@ def process_message(
statsd = get_statsd()
if artifact_processor is None:
sentry_client = SentryClient(base_url=service_config.sentry_base_url)
objectstore_client = None
if service_config.objectstore_url is not None:
objectstore_client = ObjectstoreClient(service_config.objectstore_url)
objectstore_client = create_objectstore_client(service_config.objectstore_config)
artifact_processor = ArtifactProcessor(sentry_client, statsd, objectstore_client)

requested_features = []
Expand Down
30 changes: 27 additions & 3 deletions src/launchpad/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

from dataclasses import dataclass

from objectstore_client import Permission as ObjectstorePermission

from launchpad.sentry_client import SentryClient
from launchpad.utils.logging import get_logger
from launchpad.utils.statsd import NullStatsd, StatsdInterface, get_statsd
Expand Down Expand Up @@ -121,29 +123,51 @@ def _shutdown_server(self) -> None:
self._server_thread.join(timeout=10)


@dataclass
class ObjectstoreConfig:
"""Objectstore client configuration data."""

objectstore_url: str | None
key_id: str | None = None
key_file: str | None = None
token_expiry_seconds: int = 60
token_permissions: list[ObjectstorePermission] = ObjectstorePermission.max()


@dataclass
class ServiceConfig:
"""Service configuration data."""

sentry_base_url: str
projects_to_skip: list[str]
objectstore_url: str | None
objectstore_config: ObjectstoreConfig


def get_service_config() -> ServiceConfig:
"""Get service configuration from environment."""
sentry_base_url = os.getenv("SENTRY_BASE_URL")
projects_to_skip_str = os.getenv("PROJECT_IDS_TO_SKIP")
projects_to_skip = projects_to_skip_str.split(",") if projects_to_skip_str else []
objectstore_url = os.getenv("OBJECTSTORE_URL")

objectstore_config = ObjectstoreConfig(
objectstore_url=os.getenv("OBJECTSTORE_URL"),
key_id=os.getenv("OBJECTSTORE_SIGNING_KEY_ID"),
key_file=os.getenv("OBJECTSTORE_SIGNING_KEY_FILE"),
)
if expiry_seconds := os.getenv("OBJECTSTORE_TOKEN_EXPIRY_SECONDS"):
objectstore_config.token_expiry_seconds = int(expiry_seconds)
if permissions := os.getenv("OBJECTSTORE_TOKEN_PERMISSIONS"):
objectstore_config.token_permissions = [
ObjectstorePermission(permission) for permission in permissions.split(",")
]

if sentry_base_url is None:
sentry_base_url = "http://getsentry.default"

return ServiceConfig(
sentry_base_url=sentry_base_url,
projects_to_skip=projects_to_skip,
objectstore_url=objectstore_url,
objectstore_config=objectstore_config,
)


Expand Down
42 changes: 42 additions & 0 deletions src/launchpad/utils/objectstore.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from objectstore_client import (
Client as ObjectstoreClient,
)
from objectstore_client import (
TokenGenerator,
)

from launchpad.service import ObjectstoreConfig
from launchpad.utils.logging import get_logger

logger = get_logger(__name__)

_cached_keyfile: str | None = None


def _read_keyfile(path: str) -> str | None:
global _cached_keyfile
if not _cached_keyfile:
try:
with open(path) as f:
_cached_keyfile = f.read()
except Exception:
logger.exception(f"Failed to load objectstore keyfile at {path}")

return _cached_keyfile


def create_objectstore_client(config: ObjectstoreConfig) -> ObjectstoreClient | None:
if not config.objectstore_url:
return None

token_generator = None
if config.key_id and config.key_file:
if secret_key := _read_keyfile(config.key_file):
token_generator = TokenGenerator(
config.key_id,
secret_key,
config.token_expiry_seconds,
config.token_permissions,
)

return ObjectstoreClient(config.objectstore_url, token_generator=token_generator)
Loading