From 4cb32f00d00fede1de039c8039929a0728efee81 Mon Sep 17 00:00:00 2001 From: Jawad Qureshi Date: Tue, 19 Aug 2025 14:37:05 -0500 Subject: [PATCH 01/15] Add s3 mounting capabilites --- hatchery/config.go | 9 ++ hatchery/pods.go | 220 +++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 213 insertions(+), 16 deletions(-) diff --git a/hatchery/config.go b/hatchery/config.go index e9871c87..f82f3e90 100644 --- a/hatchery/config.go +++ b/hatchery/config.go @@ -31,6 +31,14 @@ type NextflowConfig struct { InstanceMaxVCpus int32 `json:"instance-max-vcpus"` } +// Configuration for S3 bucket mounting into workspace pods +type S3Config struct { + BucketName string `json:"bucketName"` // e.g. "workspace-software-s3-qa-gen3" + Region string `json:"region"` // e.g. "us-east-1" + // Optional; if empty we’ll default to "/". + PrefixBase string `json:"prefixBase"` // e.g. "" (we’ll compute from userName) +} + // LicenseInfo contains configuration for Gen3 supplied licenses. type LicenseInfo struct { Enabled bool `json:"enabled"` @@ -133,6 +141,7 @@ type HatcheryConfig struct { Sidecar SidecarContainer `json:"sidecar"` MoreConfigs []AppConfigInfo `json:"more-configs"` PrismaConfig PrismaConfig `json:"prisma"` + S3 S3Config `json:s3-config` NextflowGlobalConfig NextflowGlobalConfig `json:"nextflow-global"` } diff --git a/hatchery/pods.go b/hatchery/pods.go index 27693641..15241462 100644 --- a/hatchery/pods.go +++ b/hatchery/pods.go @@ -6,6 +6,7 @@ import ( "fmt" "log" "os" + "path/filepath" "strconv" "strings" @@ -16,6 +17,8 @@ import ( "k8s.io/client-go/kubernetes" corev1 "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/util/homedir" // AWS modules "github.com/aws/aws-sdk-go/aws" @@ -76,27 +79,61 @@ func getPodClient(ctx context.Context, userName string, payModelPtr *PayModel) ( return podClient, true, nil } } else { - return getLocalPodClient(), false, nil + podClient, err := getLocalPodClient() + if err != nil { + Config.Logger.Printf("Error fetching local kubeconfig: %v", err) + return nil, true, err + } + return podClient, false, nil } } -func getLocalPodClient() corev1.CoreV1Interface { - // creates the in-cluster config - config, err := rest.InClusterConfig() +// Use in-cluster if available; otherwise fall back to kubeconfig. +// Honors $KUBECONFIG (or ~/.kube/config) and optional $KUBE_CONTEXT. +func getLocalPodClient() (corev1.CoreV1Interface, error) { + // 1) In-cluster (works when running inside a pod) + if cfg, err := rest.InClusterConfig(); err == nil { + cs, err := kubernetes.NewForConfig(cfg) + if err != nil { + return nil, fmt.Errorf("in-cluster clientset: %w", err) + } + return cs.CoreV1(), nil + } + + // 2) Kubeconfig (local dev) + var precedence []string + if kc := os.Getenv("KUBECONFIG"); kc != "" { + // Split on OS-specific list separator (":" on *nix, ";" on Windows). + precedence = filepath.SplitList(kc) + } else { + // Default to ~/.kube/config + precedence = []string{filepath.Join(homedir.HomeDir(), ".kube", "config")} + } + + kubeContext := os.Getenv("KUBE_CONTEXT") // optional + + loading := &clientcmd.ClientConfigLoadingRules{ + Precedence: precedence, // <-- multiple files supported & merged + // Note: leave ExplicitPath empty to allow Precedence merging. + } + overrides := &clientcmd.ConfigOverrides{} + if kubeContext != "" { + overrides.CurrentContext = kubeContext + } + + cfg, err := clientcmd. + NewNonInteractiveDeferredLoadingClientConfig(loading, overrides). + ClientConfig() if err != nil { - Config.Logger.Printf("Error creating in-cluster config: %v", err) - return nil + return nil, fmt.Errorf("kubeconfig load (files=%v): %w", precedence, err) } - // creates the clientset - clientset, err := kubernetes.NewForConfig(config) + + cs, err := kubernetes.NewForConfig(cfg) if err != nil { - Config.Logger.Printf("Error creating in-cluster clientset: %v", err) - return nil + Config.Logger.Printf("Error fetching kubeconfig clientset: %v", err) + return nil, fmt.Errorf("kubeconfig clientset: %w", err) } - // Access jobs. We can't do it all in one line, since we need to receive the - // errors and manage them appropriately - podClient := clientset.CoreV1() - return podClient + return cs.CoreV1(), nil } // Generate EKS kubeconfig using AWS role @@ -347,6 +384,136 @@ func userToResourceName(userName string, resourceType string) string { return fmt.Sprintf("%s-%s", resourceType, safeUserName) } +func s3NamesForUser(userName string) (pvName, pvcName, volumeHandle string) { + base := userToResourceName(userName, "s3") // e.g. john-doe-s3 + return base + "-pv", base + "-pvc", "s3-" + base // unique handle +} + +func s3PrefixForUser(userName string) string { + // If you prefer purely the username: return fmt.Sprintf("%s/", userName) + // Using resource-safe version tends to be nicer: + return fmt.Sprintf("%s/", userToResourceName(userName, "")) +} + +func ensureS3PVandPVC( + ctx context.Context, + podClient corev1.CoreV1Interface, + namespace string, + userName string, + bucket string, + region string, +) (pvcName string, err error) { + + pvName, pvcName, volumeHandle := s3NamesForUser(userName) + prefix := s3PrefixForUser(userName) + + // ----- Ensure PV exists (cluster-scoped) ----- + // Try GET; if not found, create. + if _, errGet := podClient.PersistentVolumes().Get(ctx, pvName, metav1.GetOptions{}); errGet != nil { + pv := &k8sv1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: pvName, + }, + Spec: k8sv1.PersistentVolumeSpec{ + Capacity: k8sv1.ResourceList{ + k8sv1.ResourceStorage: resource.MustParse("1200Gi"), // ignored by S3 CSI, but required + }, + AccessModes: []k8sv1.PersistentVolumeAccessMode{k8sv1.ReadWriteMany}, + StorageClassName: "", // static provisioning + ClaimRef: &k8sv1.ObjectReference{ + Namespace: namespace, + Name: pvcName, + }, + MountOptions: []string{ + "allow-delete", + fmt.Sprintf("region %s", region), + fmt.Sprintf("prefix %s", prefix), + }, + PersistentVolumeSource: k8sv1.PersistentVolumeSource{ + CSI: &k8sv1.CSIPersistentVolumeSource{ + Driver: "s3.csi.aws.com", + VolumeHandle: volumeHandle, // must be unique + VolumeAttributes: map[string]string{ + "bucketName": bucket, + }, + }, + }, + }, + } + if _, errCreatePV := podClient.PersistentVolumes().Create(ctx, pv, metav1.CreateOptions{}); errCreatePV != nil { + return "", fmt.Errorf("failed to create S3 PV %s: %w", pvName, errCreatePV) + } + } + + // ----- Ensure PVC exists (namespaced) ----- + if _, errGet := podClient.PersistentVolumeClaims(namespace).Get(ctx, pvcName, metav1.GetOptions{}); errGet != nil { + empty := "" // PVC.StorageClassName is *string + pvc := &k8sv1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: pvcName, + Namespace: namespace, + }, + Spec: k8sv1.PersistentVolumeClaimSpec{ + AccessModes: []k8sv1.PersistentVolumeAccessMode{k8sv1.ReadWriteMany}, + StorageClassName: &empty, + Resources: k8sv1.VolumeResourceRequirements{ + Requests: k8sv1.ResourceList{ + k8sv1.ResourceStorage: resource.MustParse("1200Gi"), + }, + }, + VolumeName: pvName, + }, + } + if _, errCreatePVC := podClient.PersistentVolumeClaims(namespace).Create(ctx, pvc, metav1.CreateOptions{}); errCreatePVC != nil { + return "", fmt.Errorf("failed to create S3 PVC %s: %w", pvcName, errCreatePVC) + } + } + + return pvcName, nil +} + +// Adds the S3 volume + mount to the pod in-place. +// Call this after buildPod(), before creating it. +func addS3VolumeToPod(pod *k8sv1.Pod, pvcName string) { + // 1) Add Volume + hasVolume := false + for _, v := range pod.Spec.Volumes { + if v.Name == "s3-volume" { + hasVolume = true + break + } + } + if !hasVolume { + pod.Spec.Volumes = append(pod.Spec.Volumes, k8sv1.Volume{ + Name: "s3-volume", + VolumeSource: k8sv1.VolumeSource{ + PersistentVolumeClaim: &k8sv1.PersistentVolumeClaimVolumeSource{ + ClaimName: pvcName, + // ReadOnly: false, // optional + }, + }, + }) + } + + // 2) Add VolumeMount (/mnt/s3) to every container (or just the main one) + for ci := range pod.Spec.Containers { + c := &pod.Spec.Containers[ci] + alreadyMounted := false + for _, m := range c.VolumeMounts { + if m.Name == "s3-volume" { + alreadyMounted = true + break + } + } + if !alreadyMounted { + c.VolumeMounts = append(c.VolumeMounts, k8sv1.VolumeMount{ + Name: "s3-volume", + MountPath: "/mnt/s3", + }) + } + } +} + // buildPod returns a pod ready to pass to the k8s API given // a hatchery Container instance, and the name of the user // launching the app @@ -659,6 +826,24 @@ var createLocalK8sPod = func(ctx context.Context, hash string, userName string, Config.Logger.Panicf("Error in createLocalK8sPod: %v", err) return err } + // ensure S3 PV/PVC (dynamic per user) and wire into pod + if Config.Config.S3.BucketName != "" && Config.Config.S3.Region != "" { + Config.Logger.Print("Mounting S3 bucket as well..") + s3PVCName, err := ensureS3PVandPVC( + ctx, + podClient, + Config.Config.UserNamespace, + userName, + Config.Config.S3.BucketName, + Config.Config.S3.Region, + ) + if err != nil { + Config.Logger.Printf("Failed ensuring S3 PV/PVC for user %s: %v", userName, err) + return err + } + addS3VolumeToPod(pod, s3PVCName) + } + // a null image indicates a dockstore app - always mount user volume mountUserVolume := hatchApp.UserVolumeLocation != "" if mountUserVolume { @@ -955,8 +1140,11 @@ tls: %s annotationsService := make(map[string]string) annotationsService["getambassador.io/config"] = fmt.Sprintf(localAmbassadorYaml, userToResourceName(userName, "mapping"), userName, serviceURL, NodePort, hatchApp.PathRewrite, hatchApp.UseTLS) - localPodClient := getLocalPodClient() - _, err := localPodClient.Services(Config.Config.UserNamespace).Get(ctx, serviceName, metav1.GetOptions{}) + localPodClient, err := getLocalPodClient() + if err == nil { + Config.Logger.Printf("Error fetching local kubeconfig: %v", err) + } + _, err = localPodClient.Services(Config.Config.UserNamespace).Get(ctx, serviceName, metav1.GetOptions{}) if err == nil { // This probably happened as the result of some error... there was no pod but was a service // Lets just clean it up and proceed From 960294b642d841fd94c0b7f2e96455aa0b9d47d5 Mon Sep 17 00:00:00 2001 From: Jawad Qureshi Date: Tue, 19 Aug 2025 14:44:14 -0500 Subject: [PATCH 02/15] Add s3 mounting capabilites --- hatchery/config.go | 2 +- hatchery/pods.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hatchery/config.go b/hatchery/config.go index f82f3e90..567d3f18 100644 --- a/hatchery/config.go +++ b/hatchery/config.go @@ -141,7 +141,7 @@ type HatcheryConfig struct { Sidecar SidecarContainer `json:"sidecar"` MoreConfigs []AppConfigInfo `json:"more-configs"` PrismaConfig PrismaConfig `json:"prisma"` - S3 S3Config `json:s3-config` + S3Config S3Config `json:s3-config` NextflowGlobalConfig NextflowGlobalConfig `json:"nextflow-global"` } diff --git a/hatchery/pods.go b/hatchery/pods.go index 15241462..97196950 100644 --- a/hatchery/pods.go +++ b/hatchery/pods.go @@ -827,15 +827,15 @@ var createLocalK8sPod = func(ctx context.Context, hash string, userName string, return err } // ensure S3 PV/PVC (dynamic per user) and wire into pod - if Config.Config.S3.BucketName != "" && Config.Config.S3.Region != "" { + if Config.Config.S3Config.BucketName != "" && Config.Config.S3Config.Region != "" { Config.Logger.Print("Mounting S3 bucket as well..") s3PVCName, err := ensureS3PVandPVC( ctx, podClient, Config.Config.UserNamespace, userName, - Config.Config.S3.BucketName, - Config.Config.S3.Region, + Config.Config.S3Config.BucketName, + Config.Config.S3Config.Region, ) if err != nil { Config.Logger.Printf("Failed ensuring S3 PV/PVC for user %s: %v", userName, err) From 2767786d8f7a050999f47d82e100232c5bb68383 Mon Sep 17 00:00:00 2001 From: Jawad Qureshi Date: Tue, 19 Aug 2025 14:56:53 -0500 Subject: [PATCH 03/15] Add s3 mounting capabilites --- hatchery/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hatchery/config.go b/hatchery/config.go index 567d3f18..b13d0d9a 100644 --- a/hatchery/config.go +++ b/hatchery/config.go @@ -141,7 +141,7 @@ type HatcheryConfig struct { Sidecar SidecarContainer `json:"sidecar"` MoreConfigs []AppConfigInfo `json:"more-configs"` PrismaConfig PrismaConfig `json:"prisma"` - S3Config S3Config `json:s3-config` + S3Config S3Config `json:"s3-config"` NextflowGlobalConfig NextflowGlobalConfig `json:"nextflow-global"` } From df873e2e01c01c910e69254e7fb07e8066e86926 Mon Sep 17 00:00:00 2001 From: "J. Q." <55899496+jawadqur@users.noreply.github.com> Date: Mon, 10 Nov 2025 12:50:39 -0600 Subject: [PATCH 04/15] Mount s3 at /apps instead of /mnt/s3 --- hatchery/pods.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hatchery/pods.go b/hatchery/pods.go index 02e4ea5d..a0593df1 100644 --- a/hatchery/pods.go +++ b/hatchery/pods.go @@ -497,7 +497,7 @@ func addS3VolumeToPod(pod *k8sv1.Pod, pvcName string) { }) } - // 2) Add VolumeMount (/mnt/s3) to every container (or just the main one) + // 2) Add VolumeMount (/apps) to every container (or just the main one) for ci := range pod.Spec.Containers { c := &pod.Spec.Containers[ci] alreadyMounted := false @@ -510,7 +510,8 @@ func addS3VolumeToPod(pod *k8sv1.Pod, pvcName string) { if !alreadyMounted { c.VolumeMounts = append(c.VolumeMounts, k8sv1.VolumeMount{ Name: "s3-volume", - MountPath: "/mnt/s3", + // TODO: Read this path from config + MountPath: "/apps", }) } } From cc140f97461e08abe93ec1a548e82e84d76ff070 Mon Sep 17 00:00:00 2001 From: Jawad Date: Tue, 2 Dec 2025 19:11:00 +0100 Subject: [PATCH 05/15] set bucket prefix to / --- hatchery/pods.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hatchery/pods.go b/hatchery/pods.go index a0593df1..c868c153 100644 --- a/hatchery/pods.go +++ b/hatchery/pods.go @@ -407,7 +407,10 @@ func ensureS3PVandPVC( ) (pvcName string, err error) { pvName, pvcName, volumeHandle := s3NamesForUser(userName) - prefix := s3PrefixForUser(userName) + // Commented out for now as we don't need a prefix, this bucket will be mounted at / for all users, but read only. + // This will be used for the "software-repository" feature + // prefix := s3PrefixForUser(userName) + prefix := "/" // ----- Ensure PV exists (cluster-scoped) ----- // Try GET; if not found, create. From 0bd9cf4b60866fa494aa6dda2e6e06883035f7a1 Mon Sep 17 00:00:00 2001 From: Jawad Date: Tue, 2 Dec 2025 23:12:24 +0100 Subject: [PATCH 06/15] remove bucket prefix --- hatchery/pods.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hatchery/pods.go b/hatchery/pods.go index c868c153..fd593286 100644 --- a/hatchery/pods.go +++ b/hatchery/pods.go @@ -432,7 +432,7 @@ func ensureS3PVandPVC( MountOptions: []string{ "allow-delete", fmt.Sprintf("region %s", region), - fmt.Sprintf("prefix %s", prefix), + // fmt.Sprintf("prefix %s", prefix), }, PersistentVolumeSource: k8sv1.PersistentVolumeSource{ CSI: &k8sv1.CSIPersistentVolumeSource{ From d5f906e73357dc352cee287e4cea0a1a163feeb9 Mon Sep 17 00:00:00 2001 From: Jawad Date: Tue, 2 Dec 2025 23:12:45 +0100 Subject: [PATCH 07/15] remove bucket prefix --- hatchery/pods.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hatchery/pods.go b/hatchery/pods.go index fd593286..d09edf29 100644 --- a/hatchery/pods.go +++ b/hatchery/pods.go @@ -410,7 +410,7 @@ func ensureS3PVandPVC( // Commented out for now as we don't need a prefix, this bucket will be mounted at / for all users, but read only. // This will be used for the "software-repository" feature // prefix := s3PrefixForUser(userName) - prefix := "/" + // prefix := "/" // ----- Ensure PV exists (cluster-scoped) ----- // Try GET; if not found, create. From 38eb6238f8c7c8feaaa6ff345c8ab9ca5cd27af3 Mon Sep 17 00:00:00 2001 From: Mingfei Shao Date: Wed, 3 Dec 2025 11:22:33 -0600 Subject: [PATCH 08/15] update file mode --- hatchery/pods.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hatchery/pods.go b/hatchery/pods.go index d09edf29..926d63c3 100644 --- a/hatchery/pods.go +++ b/hatchery/pods.go @@ -432,6 +432,7 @@ func ensureS3PVandPVC( MountOptions: []string{ "allow-delete", fmt.Sprintf("region %s", region), + "file-mode 0555", // fmt.Sprintf("prefix %s", prefix), }, PersistentVolumeSource: k8sv1.PersistentVolumeSource{ @@ -512,7 +513,7 @@ func addS3VolumeToPod(pod *k8sv1.Pod, pvcName string) { } if !alreadyMounted { c.VolumeMounts = append(c.VolumeMounts, k8sv1.VolumeMount{ - Name: "s3-volume", + Name: "s3-volume", // TODO: Read this path from config MountPath: "/apps", }) From f554e0b9309eeadecb500464be5b02b4cc8c36c1 Mon Sep 17 00:00:00 2001 From: Mingfei Shao Date: Wed, 3 Dec 2025 11:23:07 -0600 Subject: [PATCH 09/15] update --- hatchery/pods.go | 1 + 1 file changed, 1 insertion(+) diff --git a/hatchery/pods.go b/hatchery/pods.go index 926d63c3..ca78f27a 100644 --- a/hatchery/pods.go +++ b/hatchery/pods.go @@ -431,6 +431,7 @@ func ensureS3PVandPVC( }, MountOptions: []string{ "allow-delete", + "allow-other", fmt.Sprintf("region %s", region), "file-mode 0555", // fmt.Sprintf("prefix %s", prefix), From 163d8d4886007dd99314c1b185a5f9cd5b658bd2 Mon Sep 17 00:00:00 2001 From: Mingfei Shao Date: Wed, 3 Dec 2025 13:22:31 -0600 Subject: [PATCH 10/15] update format --- hatchery/pods.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hatchery/pods.go b/hatchery/pods.go index ca78f27a..a276cfd9 100644 --- a/hatchery/pods.go +++ b/hatchery/pods.go @@ -432,8 +432,9 @@ func ensureS3PVandPVC( MountOptions: []string{ "allow-delete", "allow-other", + "file-mode=0555", + "dir-mode=0555", fmt.Sprintf("region %s", region), - "file-mode 0555", // fmt.Sprintf("prefix %s", prefix), }, PersistentVolumeSource: k8sv1.PersistentVolumeSource{ From 634df5e76df10389429fbaa5c9fb44f7f8d61be8 Mon Sep 17 00:00:00 2001 From: Mingfei Shao Date: Wed, 3 Dec 2025 16:10:55 -0600 Subject: [PATCH 11/15] add uid and gid --- hatchery/pods.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hatchery/pods.go b/hatchery/pods.go index a276cfd9..117d6fae 100644 --- a/hatchery/pods.go +++ b/hatchery/pods.go @@ -432,6 +432,8 @@ func ensureS3PVandPVC( MountOptions: []string{ "allow-delete", "allow-other", + "uid=1010", + "gid=100", "file-mode=0555", "dir-mode=0555", fmt.Sprintf("region %s", region), From d52c0eedfe3c673db3cc7938f446fd5d29f3af4a Mon Sep 17 00:00:00 2001 From: Mingfei Shao Date: Thu, 4 Dec 2025 15:56:03 -0600 Subject: [PATCH 12/15] remove uid setting --- hatchery/pods.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/hatchery/pods.go b/hatchery/pods.go index 117d6fae..c457c4b8 100644 --- a/hatchery/pods.go +++ b/hatchery/pods.go @@ -432,10 +432,9 @@ func ensureS3PVandPVC( MountOptions: []string{ "allow-delete", "allow-other", - "uid=1010", "gid=100", - "file-mode=0555", - "dir-mode=0555", + "file-mode=555", + "dir-mode=555", fmt.Sprintf("region %s", region), // fmt.Sprintf("prefix %s", prefix), }, From a2e2f57e64714a24b4361c621c3c20e82c1745db Mon Sep 17 00:00:00 2001 From: Jawad Date: Tue, 10 Mar 2026 12:39:17 -0500 Subject: [PATCH 13/15] Enhance S3 volume configuration with caching options for improved performance --- hatchery/pods.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/hatchery/pods.go b/hatchery/pods.go index aab1d8ae..b91fa215 100644 --- a/hatchery/pods.go +++ b/hatchery/pods.go @@ -437,6 +437,8 @@ func ensureS3PVandPVC( "dir-mode=555", fmt.Sprintf("region %s", region), // fmt.Sprintf("prefix %s", prefix), + // Caches directory listings and file stats for 1 hour (fixes the 77x walk/stat slowdown) + "metadata-ttl=3600", }, PersistentVolumeSource: k8sv1.PersistentVolumeSource{ CSI: &k8sv1.CSIPersistentVolumeSource{ @@ -444,6 +446,9 @@ func ensureS3PVandPVC( VolumeHandle: volumeHandle, // must be unique VolumeAttributes: map[string]string{ "bucketName": bucket, + // Enables safe file caching using an ephemeral emptyDir on the K8s node + // and hard-caps the cache at 10 Gigabytes to prevent disk pressure + "cacheEmptyDirSizeLimit": "10Gi", }, }, }, From c5775f0ef80211b19dc26f000d13655c47ea9e0f Mon Sep 17 00:00:00 2001 From: Aidan Hilt <11202897+AidanHilt@users.noreply.github.com> Date: Wed, 11 Mar 2026 17:01:44 -0400 Subject: [PATCH 14/15] Add namespace to name of s3 pv --- hatchery/pods.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/hatchery/pods.go b/hatchery/pods.go index b91fa215..6bc89a30 100644 --- a/hatchery/pods.go +++ b/hatchery/pods.go @@ -386,9 +386,9 @@ func userToResourceName(userName string, resourceType string) string { return fmt.Sprintf("%s-%s", resourceType, safeUserName) } -func s3NamesForUser(userName string) (pvName, pvcName, volumeHandle string) { +func s3NamesForUser(userName string, namespace string) (pvName, pvcName, volumeHandle string) { base := userToResourceName(userName, "s3") // e.g. john-doe-s3 - return base + "-pv", base + "-pvc", "s3-" + base // unique handle + return base + "-" + namespace + "-pv", base + "-pvc", "s3-" + base // unique handle } func s3PrefixForUser(userName string) string { @@ -406,7 +406,7 @@ func ensureS3PVandPVC( region string, ) (pvcName string, err error) { - pvName, pvcName, volumeHandle := s3NamesForUser(userName) + pvName, pvcName, volumeHandle := s3NamesForUser(userName, namespace) // Commented out for now as we don't need a prefix, this bucket will be mounted at / for all users, but read only. // This will be used for the "software-repository" feature // prefix := s3PrefixForUser(userName) From 7bea510049a4e269bbabf41c0c4f7b0f15277d37 Mon Sep 17 00:00:00 2001 From: George Thomas Date: Thu, 19 Mar 2026 20:20:31 -0700 Subject: [PATCH 15/15] Make volumeHandle unique --- hatchery/pods.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hatchery/pods.go b/hatchery/pods.go index 6bc89a30..3aeca5ec 100644 --- a/hatchery/pods.go +++ b/hatchery/pods.go @@ -387,8 +387,8 @@ func userToResourceName(userName string, resourceType string) string { } func s3NamesForUser(userName string, namespace string) (pvName, pvcName, volumeHandle string) { - base := userToResourceName(userName, "s3") // e.g. john-doe-s3 - return base + "-" + namespace + "-pv", base + "-pvc", "s3-" + base // unique handle + base := userToResourceName(userName, "s3") // e.g. john-doe-s3 + return base + "-" + namespace + "-pv", base + "-pvc", base + "-" + namespace // unique handle } func s3PrefixForUser(userName string) string {