Skip to content

Commit 5ca5ed6

Browse files
committed
chore: Support multiple subpath mounts for a single PVC
Signed-off-by: Anatolii Bazko <abazko@redhat.com>
1 parent c338646 commit 5ca5ed6

9 files changed

Lines changed: 168 additions & 28 deletions

docs/additional-configuration.adoc

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,16 @@ By default, resources will be mounted based on the resource name:
141141
Mounting resources can be additionally configured via **annotations**:
142142

143143
* `controller.devfile.io/mount-path`: configure where the resource should be mounted
144-
* `controller.devfile.io/mount-sub-path`: for persistent volume claims only, configure a subpath within the PVC to mount instead of its root
144+
+
145+
For persistent volume claims, `controller.devfile.io/mount-path` also supports a JSON array to mount multiple subdirectories at different paths:
146+
+
147+
[source,yaml]
148+
----
149+
annotations:
150+
controller.devfile.io/mount-path: '[{"path":"/var/logs","subPath":"data/logs"},{"path":"/etc/config","subPath":"data/config"}]'
151+
----
152+
+
153+
Each entry requires a `path` field (the container mount path) and an optional `subPath` field (the subdirectory within the PVC). An empty or missing annotation falls back to `/tmp/<pvc-name>`.
145154
* `controller.devfile.io/mount-access-mode`: for secrets and configmaps only, configure file permissions on files mounted from this configmap/secret. Permissions can be specified in decimal (e.g. `"511"`) or octal notation by prefixing with a "0" (e.g. `"0777"`)
146155
* `controller.devfile.io/mount-as`: for secrets and configmaps only, configure how the resource should be mounted to the workspace
147156
+

pkg/constants/metadata.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,6 @@ const (
121121
// read-write. Automounted configmaps and secrets are always mounted read-only and this annotation is ignored.
122122
DevWorkspaceMountReadyOnlyAnnotation = "controller.devfile.io/read-only"
123123

124-
// DevWorkspaceMountSubPathAnnotation is an annotation to configure a subPath for a mounted PVC volume.
125-
// When set on a PVC with the automount label, the volume mount will use the specified subPath,
126-
// allowing a subdirectory within the PVC to be mounted instead of the root.
127-
// This annotation is only used for PersistentVolumeClaims.
128-
DevWorkspaceMountSubPathAnnotation = "controller.devfile.io/mount-sub-path"
129-
130124
// DevWorkspaceRestrictedAccessAnnotation marks the intention that devworkspace access is restricted to only the creator; setting this
131125
// annotation will cause devworkspace start to fail if webhooks are disabled.
132126
// Operator also propagates it to the devworkspace-related objects to perform authorization.

pkg/provision/automount/pvcs.go

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,51 @@
1616
package automount
1717

1818
import (
19+
"encoding/json"
20+
"fmt"
1921
"path"
22+
"strings"
2023

21-
"github.com/devfile/devworkspace-operator/pkg/provision/sync"
2224
corev1 "k8s.io/api/core/v1"
2325
k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
2426

2527
"github.com/devfile/devworkspace-operator/pkg/common"
2628
"github.com/devfile/devworkspace-operator/pkg/constants"
29+
"github.com/devfile/devworkspace-operator/pkg/provision/sync"
2730
)
2831

32+
type mountPathEntry struct {
33+
Path string `json:"path"`
34+
SubPath string `json:"subPath,omitempty"`
35+
}
36+
37+
func parseMountPathAnnotation(annotation string, pvcName string) ([]mountPathEntry, error) {
38+
if annotation == "" {
39+
return []mountPathEntry{{Path: path.Join("/tmp/", pvcName)}}, nil
40+
}
41+
42+
if !strings.HasPrefix(annotation, "[") {
43+
return []mountPathEntry{{Path: annotation}}, nil
44+
}
45+
46+
var entries []mountPathEntry
47+
if err := json.Unmarshal([]byte(annotation), &entries); err != nil {
48+
return nil, fmt.Errorf("failed to parse mount-path annotation on PVC %s: %w", pvcName, err)
49+
}
50+
51+
if len(entries) == 0 {
52+
return []mountPathEntry{{Path: path.Join("/tmp/", pvcName)}}, nil
53+
}
54+
55+
for i, entry := range entries {
56+
if entry.Path == "" {
57+
return nil, fmt.Errorf("mount-path annotation on PVC %s: entry %d is missing required field 'path'", pvcName, i)
58+
}
59+
}
60+
61+
return entries, nil
62+
}
63+
2964
func getAutoMountPVCs(namespace string, api sync.ClusterAPI) (*Resources, error) {
3065
pvcs := &corev1.PersistentVolumeClaimList{}
3166
if err := api.Client.List(api.Ctx, pvcs, k8sclient.InNamespace(namespace), k8sclient.MatchingLabels{
@@ -40,16 +75,7 @@ func getAutoMountPVCs(namespace string, api sync.ClusterAPI) (*Resources, error)
4075
var volumes []corev1.Volume
4176
var volumeMounts []corev1.VolumeMount
4277
for _, pvc := range pvcs.Items {
43-
mountPath := pvc.Annotations[constants.DevWorkspaceMountPathAnnotation]
44-
subPath := pvc.Annotations[constants.DevWorkspaceMountSubPathAnnotation]
45-
if mountPath == "" {
46-
mountPath = path.Join("/tmp/", pvc.Name)
47-
}
48-
49-
mountReadOnly := false
50-
if pvc.Annotations[constants.DevWorkspaceMountReadyOnlyAnnotation] == "true" {
51-
mountReadOnly = true
52-
}
78+
mountReadOnly := pvc.Annotations[constants.DevWorkspaceMountReadyOnlyAnnotation] == "true"
5379

5480
volumes = append(volumes, corev1.Volume{
5581
Name: common.AutoMountPVCVolumeName(pvc.Name),
@@ -60,11 +86,19 @@ func getAutoMountPVCs(namespace string, api sync.ClusterAPI) (*Resources, error)
6086
},
6187
},
6288
})
63-
volumeMounts = append(volumeMounts, corev1.VolumeMount{
64-
Name: common.AutoMountPVCVolumeName(pvc.Name),
65-
MountPath: mountPath,
66-
SubPath: subPath,
67-
})
89+
90+
mountPathEntries, err := parseMountPathAnnotation(pvc.Annotations[constants.DevWorkspaceMountPathAnnotation], pvc.Name)
91+
if err != nil {
92+
return nil, err
93+
}
94+
95+
for _, entry := range mountPathEntries {
96+
volumeMounts = append(volumeMounts, corev1.VolumeMount{
97+
Name: common.AutoMountPVCVolumeName(pvc.Name),
98+
MountPath: entry.Path,
99+
SubPath: entry.SubPath,
100+
})
101+
}
68102
}
69103
return &Resources{
70104
Volumes: volumes,
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Provisions automount PVC with empty array falls back to default mount path
2+
3+
input:
4+
pvcs:
5+
-
6+
apiVersion: v1
7+
kind: PersistentVolumeClaim
8+
metadata:
9+
name: test-pvc
10+
labels:
11+
controller.devfile.io/mount-to-devworkspace: "true"
12+
annotations:
13+
controller.devfile.io/mount-path: '[]'
14+
spec:
15+
accessModes:
16+
- ReadWriteOnce
17+
resources:
18+
requests:
19+
storage: 1Gi
20+
21+
output:
22+
volumes:
23+
- name: test-pvc
24+
persistentVolumeClaim:
25+
claimName: test-pvc
26+
volumeMounts:
27+
- name: test-pvc
28+
mountPath: /tmp/test-pvc
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Returns error for invalid JSON in mount-path annotation
2+
3+
input:
4+
pvcs:
5+
-
6+
apiVersion: v1
7+
kind: PersistentVolumeClaim
8+
metadata:
9+
name: test-pvc
10+
labels:
11+
controller.devfile.io/mount-to-devworkspace: "true"
12+
annotations:
13+
controller.devfile.io/mount-path: '[{"path":"/data"'
14+
spec:
15+
accessModes:
16+
- ReadWriteOnce
17+
resources:
18+
requests:
19+
storage: 1Gi
20+
21+
output:
22+
errRegexp: "failed to parse mount-path annotation on PVC test-pvc"
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Returns error when path field is missing in mount-path JSON entry
2+
3+
input:
4+
pvcs:
5+
-
6+
apiVersion: v1
7+
kind: PersistentVolumeClaim
8+
metadata:
9+
name: test-pvc
10+
labels:
11+
controller.devfile.io/mount-to-devworkspace: "true"
12+
annotations:
13+
controller.devfile.io/mount-path: '[{"subPath":"data/logs"}]'
14+
spec:
15+
accessModes:
16+
- ReadWriteOnce
17+
resources:
18+
requests:
19+
storage: 1Gi
20+
21+
output:
22+
errRegexp: "mount-path annotation on PVC test-pvc: entry 0 is missing required field 'path'"
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Provisions automount PVC with multiple subpath mounts via JSON array
2+
3+
input:
4+
pvcs:
5+
-
6+
apiVersion: v1
7+
kind: PersistentVolumeClaim
8+
metadata:
9+
name: test-pvc
10+
labels:
11+
controller.devfile.io/mount-to-devworkspace: "true"
12+
annotations:
13+
controller.devfile.io/mount-path: '[{"path":"/var/logs","subPath":"data/logs"},{"path":"/etc/config","subPath":"data/config"}]'
14+
spec:
15+
accessModes:
16+
- ReadWriteOnce
17+
resources:
18+
requests:
19+
storage: 1Gi
20+
21+
output:
22+
volumes:
23+
- name: test-pvc
24+
persistentVolumeClaim:
25+
claimName: test-pvc
26+
volumeMounts:
27+
- name: test-pvc
28+
mountPath: /var/logs
29+
subPath: data/logs
30+
- name: test-pvc
31+
mountPath: /etc/config
32+
subPath: data/config

pkg/provision/automount/testdata/testProvisionsPVCWithoutSubPath.yaml renamed to pkg/provision/automount/testdata/testProvisionsPVCWithSingleMount.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Provisions automount PVC without subPath
1+
name: Provisions automount PVC with plain string mount-path
22

33
input:
44
pvcs:

pkg/provision/automount/testdata/testProvisionsPVCWithSubPath.yaml renamed to pkg/provision/automount/testdata/testProvisionsPVCWithSingleSubPathEntry.yaml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Provisions automount PVC with subPath
1+
name: Provisions automount PVC with single entry JSON array
22

33
input:
44
pvcs:
@@ -10,8 +10,7 @@ input:
1010
labels:
1111
controller.devfile.io/mount-to-devworkspace: "true"
1212
annotations:
13-
controller.devfile.io/mount-path: /data
14-
controller.devfile.io/mount-sub-path: my/subdirectory
13+
controller.devfile.io/mount-path: '[{"path":"/data","subPath":"mydir"}]'
1514
spec:
1615
accessModes:
1716
- ReadWriteOnce
@@ -27,4 +26,4 @@ output:
2726
volumeMounts:
2827
- name: test-pvc
2928
mountPath: /data
30-
subPath: my/subdirectory
29+
subPath: mydir

0 commit comments

Comments
 (0)