Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
bdf3c6b
api spec and models
wesjdj May 12, 2026
57358b5
add knative servive crd pydantic types
wesjdj May 13, 2026
b197e1f
reshape api spec for /apps endpoints
wesjdj May 18, 2026
081b4f6
fix quantity types and collapse models
wesjdj May 19, 2026
fb608f8
add k8s client
wesjdj May 19, 2026
85ad1ee
add knative service converter and make url optional
wesjdj May 19, 2026
f5b5b5c
make url nullable
wesjdj May 21, 2026
83e5521
add /apps POST and GET endpoints
wesjdj May 22, 2026
84c8a3e
fix: bundle renku_apps component in data_service wheel
wesjdj May 27, 2026
5256b3e
get apps kantive manifest from session launcher
wesjdj May 27, 2026
b49d236
derive app URL from project path
wesjdj May 27, 2026
7c3481e
get URL from knative status. name services per project
wesjdj May 28, 2026
7af9966
cache KnativeService GVK
wesjdj May 28, 2026
b8627b7
add dummy renku apps user id
wesjdj May 29, 2026
1310765
subscribe k8s watcher to Knative Services
wesjdj May 29, 2026
858b4ff
bundle renku_apps component in k8s_watcher
wesjdj May 29, 2026
814be8f
keep K8s types out of core.py and repository
wesjdj Jun 3, 2026
3b2dbcf
restore oci_schema target
wesjdj Jun 3, 2026
651da95
address PR review comments
wesjdj Jun 4, 2026
65ca6ec
add delete to api spec
wesjdj Jun 1, 2026
6c6be10
add delete app function to repository
wesjdj Jun 1, 2026
73e7473
add delete apps endpoint
wesjdj Jun 1, 2026
ea0d84d
use injected cluster_id in delete_app_deployment
wesjdj Jun 5, 2026
2861d65
add list and patch to api spec
wesjdj Jun 5, 2026
1b18a2d
implement patching apps
wesjdj Jun 8, 2026
649aa4c
add lowercase project id slug
wesjdj Jun 9, 2026
a6c9f4a
fix resource patch
wesjdj Jun 9, 2026
9b864d4
thread Resources override through KnativeService chain
wesjdj Jun 10, 2026
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
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
AMALTHEA_SESSIONS_VERSION ?= 0.25.0
KNATIVE_SERVING_VERSION ?= knative-v1.22.0
COMMON_CODEGEN_PARAMS := \
--output-model-type pydantic_v2.BaseModel \
--use-double-quotes \
Expand Down Expand Up @@ -55,6 +56,7 @@ API_SPECS := \
components/renku_data_services/notifications/apispec.py \
components/renku_data_services/capacity_reservation/apispec.py \
components/renku_data_services/resource_usage/apispec.py \
components/renku_data_services/renku_apps/apispec.py \
components/renku_data_services/authn/api/apispec.py

schemas: ${API_SPECS} ## Generate pydantic classes from apispec yaml files
Expand Down Expand Up @@ -119,6 +121,10 @@ amalthea_schema: ## Updates generates pydantic classes from CRDs
shipwright_schema: ## Updates the Shipwright pydantic classes
curl https://raw.githubusercontent.com/shipwright-io/build/refs/tags/v0.15.2/deploy/crds/shipwright.io_buildruns.yaml | yq '.spec.versions[] | select(.name == "v1beta1") | .schema.openAPIV3Schema' | poetry run datamodel-codegen --output components/renku_data_services/session/cr_shipwright_buildrun.py --base-class renku_data_services.session.cr_base.BaseCRD ${CR_CODEGEN_PARAMS}

.PHONY: knative_serving_schema
knative_serving_schema: ## Updates the Knative Serving pydantic classes
curl https://raw.githubusercontent.com/knative/serving/refs/tags/${KNATIVE_SERVING_VERSION}/config/core/300-resources/service.yaml | yq '.spec.versions[] | select(.name == "v1") | .schema.openAPIV3Schema' | poetry run datamodel-codegen --output components/renku_data_services/renku_apps/cr_knative_service.py --base-class renku_data_services.renku_apps.cr_base.BaseCRD ${CR_CODEGEN_PARAMS}

.PHONY: oci_schema
oci_schema: ## Updates the OCI classes
poetry run datamodel-codegen --url "https://raw.githubusercontent.com/opencontainers/image-spec/26647a49f642c7d22a1cd3aa0a48e4650a542269/schema/config-schema.json" --output components/renku_data_services/notebooks/oci/image_config.py --class-name ImageConfig --base-class renku_data_services.notebooks.oci.base_model.BaseOciModel ${CR_CODEGEN_PARAMS}
Expand Down
13 changes: 13 additions & 0 deletions bases/renku_data_services/data_api/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from renku_data_services.notifications.blueprints import NotificationsBP
from renku_data_services.platform.blueprints import PlatformConfigBP, PlatformUrlRedirectBP
from renku_data_services.project.blueprints import ProjectsBP, ProjectSessionSecretBP
from renku_data_services.renku_apps.blueprints import RenkuAppBP
from renku_data_services.repositories.blueprints import RepositoriesBP
from renku_data_services.resource_usage.blueprints import ResourceUsageBP
from renku_data_services.search.blueprints import SearchBP
Expand Down Expand Up @@ -177,6 +178,16 @@ def register_all_handlers(app: Sanic, dm: DependencyManager) -> Sanic:
authenticator=dm.authenticator,
metrics=dm.metrics,
)
renku_apps = (
RenkuAppBP(
name="renku_apps",
url_prefix=url_prefix,
apps_repo=dm.apps_repo,
authenticator=dm.authenticator,
)
if dm.config.apps.enabled and dm.apps_repo is not None
else None
)
builds = (
BuildsBP(
name="builds",
Expand Down Expand Up @@ -349,6 +360,8 @@ def register_all_handlers(app: Sanic, dm: DependencyManager) -> Sanic:
)
if builds is not None:
app.blueprint(builds.blueprint())
if renku_apps is not None:
app.blueprint(renku_apps.blueprint())

# We need to patch sanic_ext as since version 24.12 they only send a string representation of errors
import sanic_ext.extras.validation.setup
Expand Down
3 changes: 3 additions & 0 deletions bases/renku_data_services/data_api/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from renku_data_services.data_connectors.config import DepositConfig
from renku_data_services.db_config.config import DBConfig
from renku_data_services.notebooks.config import NotebooksConfig
from renku_data_services.renku_apps.config import AppsConfig
from renku_data_services.secrets.config import PublicSecretsConfig
from renku_data_services.session.config import BuildsConfig
from renku_data_services.solr.solr_client import SolrClientConfig
Expand All @@ -33,6 +34,7 @@ class Config:
k8s_config_root: str
db: DBConfig
builds: BuildsConfig
apps: AppsConfig
nb_config: NotebooksConfig
secrets: PublicSecretsConfig
sentry: SentryConfig
Expand Down Expand Up @@ -81,6 +83,7 @@ def from_env(cls, db: DBConfig | None = None) -> Self:
k8s_config_root=os.environ.get("K8S_CONFIGS_ROOT", "/secrets/kube_configs"),
db=db,
builds=BuildsConfig.from_env(),
apps=AppsConfig.from_env(),
nb_config=nb_config,
secrets=PublicSecretsConfig.from_env(),
sentry=SentryConfig.from_env(),
Expand Down
35 changes: 33 additions & 2 deletions bases/renku_data_services/data_api/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import renku_data_services.data_connectors
import renku_data_services.notifications
import renku_data_services.platform
import renku_data_services.renku_apps
import renku_data_services.repositories
import renku_data_services.search
import renku_data_services.storage
Expand Down Expand Up @@ -67,6 +68,8 @@
ProjectRepository,
ProjectSessionSecretRepository,
)
from renku_data_services.renku_apps.k8s_client import KNATIVE_SERVICE_GVK, RenkuAppsK8sClient
from renku_data_services.renku_apps.repository import RenkuAppsRepository
from renku_data_services.repositories.db import GitRepositoriesRepository
from renku_data_services.resource_usage.core import ResourceUsageService
from renku_data_services.resource_usage.db import ResourceRequestsRepo
Expand Down Expand Up @@ -145,6 +148,8 @@ class DependencyManager:
search_updates_repo: SearchUpdatesRepo
search_reprovisioning: SearchReprovision
session_repo: SessionRepository
apps_k8s_client: RenkuAppsK8sClient | None
apps_repo: RenkuAppsRepository | None
user_preferences_repo: UserPreferencesRepository
kc_user_repo: KcUserRepo
low_level_user_secrets_repo: LowLevelUserSecretsRepo
Expand Down Expand Up @@ -196,6 +201,7 @@ def load_apispec() -> dict[str, Any]:
renku_data_services.project.__file__,
renku_data_services.namespace.__file__,
renku_data_services.session.__file__,
renku_data_services.renku_apps.__file__,
renku_data_services.connected_services.__file__,
renku_data_services.repositories.__file__,
renku_data_services.notebooks.__file__,
Expand Down Expand Up @@ -257,7 +263,13 @@ def from_env(cls) -> DependencyManager:
default_kubeconfig=default_kubeconfig,
cluster_repo=cluster_repo,
cache=k8s_db_cache,
kinds_to_cache=[AMALTHEA_SESSION_GVK, JUPYTER_SESSION_GVK, BUILD_RUN_GVK, TASK_RUN_GVK],
kinds_to_cache=[
AMALTHEA_SESSION_GVK,
JUPYTER_SESSION_GVK,
BUILD_RUN_GVK,
TASK_RUN_GVK,
KNATIVE_SERVICE_GVK,
],
),
)
quota_repo = QuotaRepository(K8sResourceQuotaClient(client), K8sPriorityClassClient(client))
Expand Down Expand Up @@ -304,7 +316,13 @@ def from_env(cls) -> DependencyManager:
default_kubeconfig=default_kubeconfig,
cluster_repo=cluster_repo,
cache=k8s_db_cache,
kinds_to_cache=[AMALTHEA_SESSION_GVK, JUPYTER_SESSION_GVK, BUILD_RUN_GVK, TASK_RUN_GVK],
kinds_to_cache=[
AMALTHEA_SESSION_GVK,
JUPYTER_SESSION_GVK,
BUILD_RUN_GVK,
TASK_RUN_GVK,
KNATIVE_SERVICE_GVK,
],
),
),
namespace=config.k8s_namespace,
Expand Down Expand Up @@ -384,6 +402,17 @@ def from_env(cls) -> DependencyManager:
builds_config=config.builds,
git_repositories_repo=git_repositories_repo,
)
apps_k8s_client: RenkuAppsK8sClient | None = None
apps_repo: RenkuAppsRepository | None = None
if config.apps.enabled:
apps_k8s_client = RenkuAppsK8sClient(client=client, cluster_repo=cluster_repo)
apps_repo = RenkuAppsRepository(
authz=authz,
session_repo=session_repo,
rp_repo=rp_repo,
project_repo=project_repo,
k8s_client=apps_k8s_client,
)
project_migration_repo = ProjectMigrationRepository(
session_maker=config.db.async_session_maker,
authz=authz,
Expand Down Expand Up @@ -484,6 +513,8 @@ def from_env(cls) -> DependencyManager:
project_session_secret_repo=project_session_secret_repo,
group_repo=group_repo,
session_repo=session_repo,
apps_k8s_client=apps_k8s_client,
apps_repo=apps_repo,
user_preferences_repo=user_preferences_repo,
kc_user_repo=kc_user_repo,
user_secrets_repo=user_secrets_repo,
Expand Down
16 changes: 16 additions & 0 deletions bases/renku_data_services/k8s_cache/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,19 @@ def from_env(cls) -> _V1ServicesConfig:
return cls(enabled=enabled)


@dataclass
class _AppsConfig:
"""Configuration for Renku apps."""

enabled: bool

@classmethod
def from_env(cls) -> _AppsConfig:
"""Load values from environment variables."""
enabled = os.environ.get("APPS_ENABLED", "false").lower() == "true"
return cls(enabled=enabled)


@dataclass
class Config:
"""K8s cache config."""
Expand All @@ -74,6 +87,7 @@ class Config:
metrics: _MetricsConfig
image_builders: _ImageBuilderConfig
v1_services: _V1ServicesConfig
apps: _AppsConfig
sentry: SentryConfig

@classmethod
Expand All @@ -84,12 +98,14 @@ def from_env(cls) -> Config:
metrics = _MetricsConfig.from_env()
image_builders = _ImageBuilderConfig.from_env()
v1_services = _V1ServicesConfig.from_env()
apps = _AppsConfig.from_env()
sentry = SentryConfig.from_env()
return cls(
db=db,
k8s=k8s,
metrics=metrics,
image_builders=image_builders,
v1_services=v1_services,
apps=apps,
sentry=sentry,
)
3 changes: 3 additions & 0 deletions bases/renku_data_services/k8s_cache/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from renku_data_services.k8s.watcher import K8sWatcher, k8s_object_handler
from renku_data_services.k8s_cache.dependencies import DependencyManager
from renku_data_services.notebooks.constants import AMALTHEA_SESSION_GVK, JUPYTER_SESSION_GVK
from renku_data_services.renku_apps.k8s_client import KNATIVE_SERVICE_GVK
from renku_data_services.session.constants import BUILD_RUN_GVK, TASK_RUN_GVK

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -52,6 +53,8 @@ async def main() -> None:
kinds.append(JUPYTER_SESSION_GVK)
if dm.config.image_builders.enabled:
kinds.extend([BUILD_RUN_GVK, TASK_RUN_GVK])
if dm.config.apps.enabled:
kinds.append(KNATIVE_SERVICE_GVK)
logger.info(f"Resources: {kinds}")
watcher = K8sWatcher(
handler=k8s_object_handler(dm.k8s_cache(), dm.metrics(), rp_repo=dm.rp_repo()),
Expand Down
7 changes: 7 additions & 0 deletions components/renku_data_services/k8s/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,10 @@
Note: we can't curently propagate labels to TaskRuns through shipwright, so we just use a dummy user id for all of them.
This might change if shipwright SHIP-0034 gets implemented.
"""

DUMMY_RENKU_APP_USER_ID: Final[str] = "DummyRenkuAppUser"
"""The user id to use for Renku App Knative Services in the k8s cache.

Renku apps are public and shared across users, so they don't fit the per-user cache model. A fixed sentinel
ensures the cache row is shared across all readers instead of being written once per user.
"""
4 changes: 3 additions & 1 deletion components/renku_data_services/k8s/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from kubernetes.client import V1Secret

from renku_data_services.errors import ProgrammingError, errors
from renku_data_services.k8s.constants import DUMMY_TASK_RUN_USER_ID, ClusterId
from renku_data_services.k8s.constants import DUMMY_RENKU_APP_USER_ID, DUMMY_TASK_RUN_USER_ID, ClusterId

sanitizer = kubernetes.client.ApiClient().sanitize_for_serialization
K8sPatch = dict[str, Any]
Expand Down Expand Up @@ -426,6 +426,8 @@ def user_id(self) -> str | None:
return labels.get("renku.io/safe-username", None)
case "taskrun":
return DUMMY_TASK_RUN_USER_ID
case "service" if self.obj.version == "serving.knative.dev/v1":
return DUMMY_RENKU_APP_USER_ID
case _:
return None

Expand Down
1 change: 1 addition & 0 deletions components/renku_data_services/renku_apps/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Blueprints for Apps."""
Loading
Loading