From f12d0fa6d24f9dde67b1fbb25ba69e7206095d20 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Wed, 15 Apr 2026 16:28:52 +0200 Subject: [PATCH 1/5] feat: enable building from private repositories Users who have pull access to a repository can now use them for building custom images. --- .../components/SessionForm/BuilderEnvironmentFields.tsx | 6 +++--- .../components/SessionForm/CodeRepositorySelector.tsx | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/client/src/features/sessionsV2/components/SessionForm/BuilderEnvironmentFields.tsx b/client/src/features/sessionsV2/components/SessionForm/BuilderEnvironmentFields.tsx index 1fff1297a5..7578af3d2e 100644 --- a/client/src/features/sessionsV2/components/SessionForm/BuilderEnvironmentFields.tsx +++ b/client/src/features/sessionsV2/components/SessionForm/BuilderEnvironmentFields.tsx @@ -60,7 +60,7 @@ export default function BuilderEnvironmentFields({ data?.findIndex( (repo) => repo.data?.status === "valid" && - repo.data.metadata?.visibility === "public" + repo.data.metadata?.pull_permission ), [data] ); @@ -91,8 +91,8 @@ export default function BuilderEnvironmentFields({ ) : firstEligibleRepository == null || firstEligibleRepository < 0 ? ( - No publicly accessible code repositories found in this project. RenkuLab - can only build session environments from public code repositories. + No accessible code repositories found in this project. + Please ensure that you have proper access to them. ) : (
diff --git a/client/src/features/sessionsV2/components/SessionForm/CodeRepositorySelector.tsx b/client/src/features/sessionsV2/components/SessionForm/CodeRepositorySelector.tsx index b39062da3c..f65e67c4ee 100644 --- a/client/src/features/sessionsV2/components/SessionForm/CodeRepositorySelector.tsx +++ b/client/src/features/sessionsV2/components/SessionForm/CodeRepositorySelector.tsx @@ -58,7 +58,7 @@ export default function CodeRepositorySelector({ : repositoriesDetails.find( (repo) => repo.data?.status === "valid" && - repo.data.metadata?.visibility === "public" + repo.data.metadata?.pull_permission )?.url, [controllerProps.defaultValue, repositoriesDetails] ); @@ -164,7 +164,7 @@ function CodeRepositorySelect({ unstyled isOptionDisabled={(option) => option.data?.status !== "valid" || - option.data.metadata?.visibility !== "public" + ! option.data.metadata?.pull_permission } onChange={onChange} onBlur={onBlur} From 50f810f5c2fe3a352f744d8a926eadab30675243 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Wed, 22 Apr 2026 11:49:24 +0200 Subject: [PATCH 2/5] chore: fix formatting --- .../components/SessionForm/BuilderEnvironmentFields.tsx | 7 +++---- .../components/SessionForm/CodeRepositorySelector.tsx | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/client/src/features/sessionsV2/components/SessionForm/BuilderEnvironmentFields.tsx b/client/src/features/sessionsV2/components/SessionForm/BuilderEnvironmentFields.tsx index 7578af3d2e..8422a75427 100644 --- a/client/src/features/sessionsV2/components/SessionForm/BuilderEnvironmentFields.tsx +++ b/client/src/features/sessionsV2/components/SessionForm/BuilderEnvironmentFields.tsx @@ -59,8 +59,7 @@ export default function BuilderEnvironmentFields({ () => data?.findIndex( (repo) => - repo.data?.status === "valid" && - repo.data.metadata?.pull_permission + repo.data?.status === "valid" && repo.data.metadata?.pull_permission ), [data] ); @@ -91,8 +90,8 @@ export default function BuilderEnvironmentFields({ ) : firstEligibleRepository == null || firstEligibleRepository < 0 ? ( - No accessible code repositories found in this project. - Please ensure that you have proper access to them. + No accessible code repositories found in this project. Please ensure that + you have proper access to them. ) : (
diff --git a/client/src/features/sessionsV2/components/SessionForm/CodeRepositorySelector.tsx b/client/src/features/sessionsV2/components/SessionForm/CodeRepositorySelector.tsx index f65e67c4ee..3c8b60e138 100644 --- a/client/src/features/sessionsV2/components/SessionForm/CodeRepositorySelector.tsx +++ b/client/src/features/sessionsV2/components/SessionForm/CodeRepositorySelector.tsx @@ -164,7 +164,7 @@ function CodeRepositorySelect({ unstyled isOptionDisabled={(option) => option.data?.status !== "valid" || - ! option.data.metadata?.pull_permission + !option.data.metadata?.pull_permission } onChange={onChange} onBlur={onBlur} From 14eaa1e4ebdfafb8bd14aae586029226ce9fb2b1 Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Fri, 24 Apr 2026 15:46:34 +0200 Subject: [PATCH 3/5] feat: add information box when a private repository is selected This will inform people that the resulting image will be sent to a different registry. --- .../SessionForm/BuilderEnvironmentFields.tsx | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/client/src/features/sessionsV2/components/SessionForm/BuilderEnvironmentFields.tsx b/client/src/features/sessionsV2/components/SessionForm/BuilderEnvironmentFields.tsx index 8422a75427..e036019bde 100644 --- a/client/src/features/sessionsV2/components/SessionForm/BuilderEnvironmentFields.tsx +++ b/client/src/features/sessionsV2/components/SessionForm/BuilderEnvironmentFields.tsx @@ -19,10 +19,10 @@ import { skipToken } from "@reduxjs/toolkit/query"; import cx from "classnames"; import { useContext, useMemo } from "react"; -import { type Control } from "react-hook-form"; +import { useWatch, type Control, type Path } from "react-hook-form"; import { useProject } from "~/routes/projects/root"; -import { ErrorAlert, WarnAlert } from "../../../../components/Alert"; +import { ErrorAlert, InfoAlert, WarnAlert } from "../../../../components/Alert"; import RtkOrDataServicesError from "../../../../components/errors/RtkOrDataServicesError"; import { Loader } from "../../../../components/Loader"; import AppContext from "../../../../utils/context/appContext"; @@ -55,6 +55,21 @@ export default function BuilderEnvironmentFields({ repositories.length > 0 ? repositories : skipToken ); + const selectedRepositoryUrl = useWatch({ + control, + name: "repository" as Path, + }) as string; + + const selectedRepositoryIsPrivate = useMemo( + () => + data?.find( + (repo) => + repo.url === selectedRepositoryUrl && + repo.data?.metadata?.visibility === "private" + ), + [data, selectedRepositoryUrl] + ); + const firstEligibleRepository = useMemo( () => data?.findIndex( @@ -101,6 +116,12 @@ export default function BuilderEnvironmentFields({ control={control} repositoriesDetails={data} /> + {selectedRepositoryIsPrivate && ( + + This is a private repository. The image built will be stored in a + separate dedicated registry. + + )}
From b88c91c98771005f18fcad73756fc5b497afc5cd Mon Sep 17 00:00:00 2001 From: Samuel Gaist Date: Fri, 24 Apr 2026 16:42:21 +0200 Subject: [PATCH 4/5] refactor: simplify the informational message --- .../components/SessionForm/BuilderEnvironmentFields.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/src/features/sessionsV2/components/SessionForm/BuilderEnvironmentFields.tsx b/client/src/features/sessionsV2/components/SessionForm/BuilderEnvironmentFields.tsx index e036019bde..9ec5e1714e 100644 --- a/client/src/features/sessionsV2/components/SessionForm/BuilderEnvironmentFields.tsx +++ b/client/src/features/sessionsV2/components/SessionForm/BuilderEnvironmentFields.tsx @@ -134,9 +134,7 @@ export default function BuilderEnvironmentFields({
{!isEdit && (

- Let RenkuLab create a customized environment from a code repository. A - container image will be created based on the requirements found in the - code repository. + Let RenkuLab create a customized environment from a code repository.

)} {content} From ad398bb7bd204eca9f334ac3356c9487126d250c Mon Sep 17 00:00:00 2001 From: Flora Thiebaut Date: Mon, 4 May 2026 14:08:40 +0200 Subject: [PATCH 5/5] fix: address rough edges for images built from a private repository (#4157) --- .../SessionList/SessionLauncherCard.tsx | 37 +++-- .../SessionView/EnvironmentCard.tsx | 154 ++++++++++-------- .../components/BuildStatusComponents.tsx | 12 +- .../SessionForm/BuilderEnvironmentFields.tsx | 4 +- 4 files changed, 120 insertions(+), 87 deletions(-) diff --git a/client/src/features/sessionsV2/SessionList/SessionLauncherCard.tsx b/client/src/features/sessionsV2/SessionList/SessionLauncherCard.tsx index 2d63ee4a57..122a52f067 100644 --- a/client/src/features/sessionsV2/SessionList/SessionLauncherCard.tsx +++ b/client/src/features/sessionsV2/SessionList/SessionLauncherCard.tsx @@ -19,10 +19,11 @@ import { skipToken } from "@reduxjs/toolkit/query"; import cx from "classnames"; import { useContext, useMemo } from "react"; -import { CircleFill, Link45deg, Pencil, Trash } from "react-bootstrap-icons"; +import { Link45deg, Pencil, Trash } from "react-bootstrap-icons"; import { Card, CardBody, Col, DropdownItem, Row } from "reactstrap"; import SessionEnvironmentGitLabWarningBadge from "~/features/legacy/SessionEnvironmentGitLabWarnBadge"; +import { useGetRepositoryQuery } from "~/features/repositories/api/repositories.api"; import { Loader } from "../../../components/Loader"; import AppContext from "../../../utils/context/appContext"; import { DEFAULT_APP_PARAMS } from "../../../utils/context/appParams.constants"; @@ -135,6 +136,15 @@ export default function SessionLauncherCard({ : skipToken ); + const { + data: imageRepositorySource, + isLoading: isLoadingImageRepositorySource, + } = useGetRepositoryQuery( + environment?.environment_image_source === "build" + ? { url: environment.build_parameters.repository } + : skipToken + ); + const { data: resourcePools, isLoading: isLoadingResourcePools } = computeResourcesApi.endpoints.getResourcePools.useQueryState({}); // Ref: https://github.com/facebook/react/issues/35577 @@ -221,6 +231,7 @@ export default function SessionLauncherCard({ {isCodeEnvironment && (isLoading || isLoadingContainerImage || + isLoadingImageRepositorySource || isLoadingResourcePools) ? ( - ) : !hasSession ? ( - - - - Not Running - - - ) : null} + ) : ( + + )} { @@ -248,82 +257,91 @@ function CustomImageEnvironmentValues({ isLoadingResourcePools={isLoadingResourcePools} /> )} - {!isLoading && data?.accessible === false && ( -
- {!data.connection && !data.provider ? ( - -

- The container image reference is invalid or points to an - unsupported registry. Please verify the image and check if the - registry is in the currently supported{" "} - - - integrations - - . If you're certain the image is correct and points to a - registry we don't currently support,{" "} - - - contact us - {" "} - about adding an integration. -

-
- ) : data.connection?.status === "connected" ? ( - -

- Either the container image reference does not exist, or you do - not have access to it. -

- {data?.provider?.id && ( - <> -

- If you think you should have access, check your - integration configuration. -

+ {!isLoading && + data?.accessible === false && + !isLoadingImageRepositorySource && ( +
+ {imageRepositorySource?.status === "invalid" ? ( + +

+ You do not have access to the repository used to build the + image for this session environment. +

+
+ ) : !data.connection && !data.provider ? ( + +

+ The container image reference is invalid or points to an + unsupported registry. Please verify the image and check if + the registry is in the currently supported{" "} - View integration + integrations - - )} - - ) : ( - -

- This container image reference is from a supported registry, - but you haven't activated the integration yet. Activate - the integration to check if you have access to this image. -

- - - Go to Integration - - - )} -
- )} + . If you're certain the image is correct and points to + a registry we don't currently support,{" "} + + + contact us + {" "} + about adding an integration. +

+
+ ) : data.connection?.status === "connected" ? ( + +

+ Either the container image reference does not exist, or you + do not have access to it. +

+ {data?.provider?.id && ( + <> +

+ If you think you should have access, check your + integration configuration. +

+ + + View integration + + + )} +
+ ) : ( + +

+ This container image reference is from a supported registry, + but you haven't activated the integration yet. Activate + the integration to check if you have access to this image. +

+ + + Go to Integration + +
+ )} +
+ )}
{ @@ -80,6 +83,11 @@ export function BuildStatusBadge({ return isImageCompatibleWith(imageCheck, resourcePool.platform); }, [imageCheck, resourcePool]); + const privateImageNotFound = useMemo( + () => imageSourceCheck?.status === "invalid", + [imageSourceCheck?.status] + ); + const badgeIcon = buildStatus === "in_progress" ? ( @@ -90,6 +98,8 @@ export function BuildStatusBadge({ const badgeText = isCompatible === false ? "Image incompatible" + : privateImageNotFound + ? "Image not accessible" : buildStatus === "in_progress" ? "Build in progress" : buildStatus === "cancelled" @@ -99,7 +109,7 @@ export function BuildStatusBadge({ : "Build failed"; const badgeColorClasses = - isCompatible === false + isCompatible === false || privateImageNotFound ? ["border-danger", "bg-danger-subtle", "text-danger-emphasis"] : buildStatus === "in_progress" ? ["border-warning", "bg-warning-subtle", "text-warning-emphasis"] diff --git a/client/src/features/sessionsV2/components/SessionForm/BuilderEnvironmentFields.tsx b/client/src/features/sessionsV2/components/SessionForm/BuilderEnvironmentFields.tsx index 9ec5e1714e..99817f3cf7 100644 --- a/client/src/features/sessionsV2/components/SessionForm/BuilderEnvironmentFields.tsx +++ b/client/src/features/sessionsV2/components/SessionForm/BuilderEnvironmentFields.tsx @@ -118,8 +118,8 @@ export default function BuilderEnvironmentFields({ /> {selectedRepositoryIsPrivate && ( - This is a private repository. The image built will be stored in a - separate dedicated registry. + This is a private repository, launching the session will only be + available to users who have pull access to the repository. )}