Skip to content
Draft
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
8 changes: 7 additions & 1 deletion api/core/v1beta1/site_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,16 @@ type SiteSpec struct {
// +kubebuilder:validation:Type=integer
NetworkTrust NetworkTrust `json:"networkTrust,omitempty"`

// PackageManagerUrl specifies the Package Manager URL for Workbench to use
// PackageManagerUrl specifies the Package Manager URL for Workbench to use for R packages (CRAN)
// If empty, Workbench will use the local Package Manager URL by default
// Example: https://packagemanager.example.com/cran/__linux__/jammy/latest
PackageManagerUrl string `json:"packageManagerUrl,omitempty"`

// PythonPackageManagerUrl specifies the Package Manager URL for Workbench to use for Python packages (PyPI)
// If empty, Workbench will derive it from the Package Manager domain: https://{domain}/pypi/latest/simple
// Example: https://packagemanager.example.com/pypi/latest/simple
PythonPackageManagerUrl string `json:"pythonPackageManagerUrl,omitempty"`

// EFSEnabled indicates whether EFS is enabled for this site
// When true, network policies will allow workbench sessions to access EFS mount targets
EFSEnabled bool `json:"efsEnabled,omitempty"`
Expand Down
9 changes: 9 additions & 0 deletions client-go/applyconfiguration/core/v1beta1/sitespec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion config/crd/bases/core.posit.team_sites.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -734,8 +734,15 @@ spec:
type: object
packageManagerUrl:
description: |-
PackageManagerUrl specifies the Package Manager URL for Workbench to use
PackageManagerUrl specifies the Package Manager URL for Workbench to use for R packages (CRAN)
If empty, Workbench will use the local Package Manager URL by default
Example: https://packagemanager.example.com/cran/__linux__/jammy/latest
type: string
pythonPackageManagerUrl:
description: |-
PythonPackageManagerUrl specifies the Package Manager URL for Workbench to use for Python packages (PyPI)
If empty, Workbench will derive it from the Package Manager domain: https://{domain}/pypi/latest/simple
Example: https://packagemanager.example.com/pypi/latest/simple
type: string
secret:
description: Secret configures the secret management for this Site
Expand Down
7 changes: 7 additions & 0 deletions internal/controller/core/site_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ func (r *SiteReconciler) reconcileResources(ctx context.Context, req ctrl.Reques
packageManagerRepoUrl = site.Spec.PackageManagerUrl
}

// Python PPM URL for pip index (PIP_INDEX_URL)
pythonPackageManagerUrl := fmt.Sprintf("https://%s/pypi/latest/simple", packageManagerUrl)
if site.Spec.PythonPackageManagerUrl != "" {
pythonPackageManagerUrl = site.Spec.PythonPackageManagerUrl
}

// VOLUMES

connectVolumeName := fmt.Sprintf("%s-connect", site.Name)
Expand Down Expand Up @@ -317,6 +323,7 @@ func (r *SiteReconciler) reconcileResources(ctx context.Context, req ctrl.Reques
devStorageClassName,
workbenchAdditionalVolumes,
packageManagerRepoUrl,
pythonPackageManagerUrl,
workbenchUrl,
); err != nil {
l.Error(err, "error reconciling workbench")
Expand Down
27 changes: 23 additions & 4 deletions internal/controller/core/site_controller_workbench.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func (r *SiteReconciler) reconcileWorkbench(
storageClassName string,
additionalVolumes []product.VolumeSpec,
packageManagerRepoUrl string,
pythonPackageManagerUrl string,
workbenchUrl string,
) error {

Expand Down Expand Up @@ -344,12 +345,17 @@ func (r *SiteReconciler) reconcileWorkbench(
}

if site.Spec.Workbench.ExperimentalFeatures.LauncherEnvPath != "" {
// Initialize with PATH from experimental features
launcherEnv := map[string]string{
"PATH": site.Spec.Workbench.ExperimentalFeatures.LauncherEnvPath,
}
// Add Python PPM URL
launcherEnv["PIP_INDEX_URL"] = pythonPackageManagerUrl

targetWorkbench.Spec.Config.WorkbenchDcfConfig = v1beta1.WorkbenchDcfConfig{
LauncherEnv: &v1beta1.WorkbenchLauncherEnvConfig{
JobType: "session",
Environment: map[string]string{
"PATH": site.Spec.Workbench.ExperimentalFeatures.LauncherEnvPath,
},
JobType: "session",
Environment: launcherEnv,
},
}
}
Expand All @@ -363,6 +369,19 @@ func (r *SiteReconciler) reconcileWorkbench(
}
}

// Configure Python to use Package Manager (PIP_INDEX_URL)
// Only set if WorkbenchDcfConfig.LauncherEnv wasn't already configured above
if targetWorkbench.Spec.Config.WorkbenchDcfConfig.LauncherEnv == nil {
targetWorkbench.Spec.Config.WorkbenchDcfConfig = v1beta1.WorkbenchDcfConfig{
LauncherEnv: &v1beta1.WorkbenchLauncherEnvConfig{
JobType: "session",
Environment: map[string]string{
"PIP_INDEX_URL": pythonPackageManagerUrl,
},
},
}
}

// set user provisioning
if site.Spec.Workbench.CreateUsersAutomatically {
targetWorkbench.Spec.Config.RServer.UserProvisioningRegisterOnFirstLogin = 1
Expand Down
64 changes: 64 additions & 0 deletions internal/controller/core/site_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -939,3 +939,67 @@ func TestSiteReconciler_WorkbenchSessionImagePullPolicyNever(t *testing.T) {
testWorkbench := getWorkbench(t, cli, siteNamespace, siteName)
assert.Equal(t, corev1.PullNever, testWorkbench.Spec.SessionConfig.Pod.ImagePullPolicy)
}

// TestSiteReconciler_PythonPPMUrl tests that Python sessions are configured to use PPM
func TestSiteReconciler_PythonPPMUrl(t *testing.T) {
siteName := "python-ppm-url"
siteNamespace := "posit-team"
site := defaultSite(siteName)
site.Spec.Domain = "example.posit.co"
site.Spec.PackageManager.DomainPrefix = "packagemanager"

cli, _, err := runFakeSiteReconciler(t, siteNamespace, siteName, site)
assert.Nil(t, err)

testWorkbench := getWorkbench(t, cli, siteNamespace, siteName)

// Verify PIP_INDEX_URL is set in launcher-env
assert.NotNil(t, testWorkbench.Spec.Config.WorkbenchDcfConfig.LauncherEnv)
assert.Equal(t, "session", testWorkbench.Spec.Config.WorkbenchDcfConfig.LauncherEnv.JobType)
assert.Contains(t, testWorkbench.Spec.Config.WorkbenchDcfConfig.LauncherEnv.Environment, "PIP_INDEX_URL")

// Verify the URL is derived from package manager domain
expectedUrl := "https://packagemanager.example.posit.co/pypi/latest/simple"
assert.Equal(t, expectedUrl, testWorkbench.Spec.Config.WorkbenchDcfConfig.LauncherEnv.Environment["PIP_INDEX_URL"])
}

// TestSiteReconciler_PythonPPMUrlCustom tests custom Python PPM URL override
func TestSiteReconciler_PythonPPMUrlCustom(t *testing.T) {
siteName := "python-ppm-url-custom"
siteNamespace := "posit-team"
site := defaultSite(siteName)
site.Spec.PythonPackageManagerUrl = "https://custom-ppm.example.com/pypi/latest/simple"

cli, _, err := runFakeSiteReconciler(t, siteNamespace, siteName, site)
assert.Nil(t, err)

testWorkbench := getWorkbench(t, cli, siteNamespace, siteName)

// Verify custom PIP_INDEX_URL is set
assert.NotNil(t, testWorkbench.Spec.Config.WorkbenchDcfConfig.LauncherEnv)
assert.Equal(t, "https://custom-ppm.example.com/pypi/latest/simple", testWorkbench.Spec.Config.WorkbenchDcfConfig.LauncherEnv.Environment["PIP_INDEX_URL"])
}

// TestSiteReconciler_PythonPPMUrlWithLauncherEnvPath tests that both PATH and PIP_INDEX_URL are set
func TestSiteReconciler_PythonPPMUrlWithLauncherEnvPath(t *testing.T) {
siteName := "python-ppm-with-path"
siteNamespace := "posit-team"
site := defaultSite(siteName)
site.Spec.Domain = "example.posit.co"
site.Spec.PackageManager.DomainPrefix = "packagemanager"
site.Spec.Workbench.ExperimentalFeatures = &v1beta1.InternalWorkbenchExperimentalFeatures{
LauncherEnvPath: "/opt/custom/bin:/usr/bin",
}

cli, _, err := runFakeSiteReconciler(t, siteNamespace, siteName, site)
assert.Nil(t, err)

testWorkbench := getWorkbench(t, cli, siteNamespace, siteName)

// Verify both PATH and PIP_INDEX_URL are set
assert.NotNil(t, testWorkbench.Spec.Config.WorkbenchDcfConfig.LauncherEnv)
assert.Contains(t, testWorkbench.Spec.Config.WorkbenchDcfConfig.LauncherEnv.Environment, "PATH")
assert.Contains(t, testWorkbench.Spec.Config.WorkbenchDcfConfig.LauncherEnv.Environment, "PIP_INDEX_URL")
assert.Equal(t, "/opt/custom/bin:/usr/bin", testWorkbench.Spec.Config.WorkbenchDcfConfig.LauncherEnv.Environment["PATH"])
assert.Equal(t, "https://packagemanager.example.posit.co/pypi/latest/simple", testWorkbench.Spec.Config.WorkbenchDcfConfig.LauncherEnv.Environment["PIP_INDEX_URL"])
}