-
Notifications
You must be signed in to change notification settings - Fork 201
Extend Volume type beyond HostPath-only #4548
Description
Summary
The operator defines a custom Volume type that only supports HostPath mounts. Users cannot mount ConfigMaps, Secrets, EmptyDir, or PVCs as volumes in MCP server containers. HostPath is generally discouraged in production Kubernetes due to node-affinity and security concerns.
Current custom type (cmd/thv-operator/api/v1alpha1/mcpserver_types.go, lines ~381-399)
type Volume struct {
Name string `json:"name"`
HostPath string `json:"hostPath"`
MountPath string `json:"mountPath"`
ReadOnly bool `json:"readOnly,omitempty"`
}Controller usage (cmd/thv-operator/controllers/mcpserver_controller.go, lines ~1150-1166)
The controller hardcodes corev1.HostPathVolumeSource:
for _, v := range m.Spec.Volumes {
volumes = append(volumes, corev1.Volume{
Name: v.Name,
VolumeSource: corev1.VolumeSource{
HostPath: &corev1.HostPathVolumeSource{
Path: v.HostPath,
},
},
})
}Why this matters
- HostPath ties pods to specific nodes and is a security risk (host filesystem access)
- Users cannot mount Secrets as files (common for TLS certs, credentials)
- Users cannot mount ConfigMaps as configuration files
- Users cannot use EmptyDir for scratch space or sidecar communication
- Users cannot mount PVCs for persistent data
PodTemplateSpeccan work around this but common volume patterns should be first-class
What to change
Option A (recommended): Extend the custom Volume type
Replace the single HostPath field with multiple optional source fields, enforced by a CEL mutual exclusivity rule:
// +kubebuilder:validation:XValidation:rule="(has(self.hostPath) ? 1 : 0) + (has(self.configMap) ? 1 : 0) + (has(self.secret) ? 1 : 0) + (has(self.emptyDir) ? 1 : 0) + (has(self.persistentVolumeClaim) ? 1 : 0) == 1",message="exactly one volume source must be specified"
type Volume struct {
// Name is the name of the volume
// +kubebuilder:validation:Required
Name string `json:"name"`
// MountPath is the path in the container to mount to
// +kubebuilder:validation:Required
MountPath string `json:"mountPath"`
// ReadOnly specifies whether the volume should be mounted read-only
// +kubebuilder:default=false
// +optional
ReadOnly bool `json:"readOnly,omitempty"`
// HostPath mounts a path from the host node's filesystem
// +optional
HostPath *string `json:"hostPath,omitempty"`
// ConfigMap mounts a ConfigMap as files
// +optional
ConfigMap *corev1.ConfigMapVolumeSource `json:"configMap,omitempty"`
// Secret mounts a Secret as files
// +optional
Secret *corev1.SecretVolumeSource `json:"secret,omitempty"`
// EmptyDir provides an empty directory for scratch space
// +optional
EmptyDir *corev1.EmptyDirVolumeSource `json:"emptyDir,omitempty"`
// PersistentVolumeClaim mounts a PVC
// +optional
PersistentVolumeClaim *corev1.PersistentVolumeClaimVolumeSource `json:"persistentVolumeClaim,omitempty"`
}Controller update (cmd/thv-operator/controllers/mcpserver_controller.go)
Replace the hardcoded HostPath conversion (~lines 1150-1166) with a switch/if-chain that creates the appropriate corev1.VolumeSource based on which field is set.
Tests
Update test files that construct Volume{Name: "x", HostPath: "/path", MountPath: "/mnt"} to use the new pointer field: Volume{Name: "x", HostPath: ptr("/path"), MountPath: "/mnt"}. Add test cases for ConfigMap and Secret volume sources.
Option B (simpler alternative)
Remove the custom Volume type entirely and direct users to PodTemplateSpec for all volume needs. Document this as the intended pattern. This is less user-friendly but avoids maintaining a custom volume abstraction.
Regenerate and verify
task gen
task crdref-gen
task lint-fix
task lint
task testNotes
- The
HostPathfield changes fromstring(required) to*string(optional) — this is a breaking change for existing manifests but acceptable forv1alpha1. - The CEL rule ensures exactly one source is set, preventing ambiguous configurations.
Generated with Claude Code