### Install pv-mounter Plugin Source: https://github.com/fenio/pv-mounter/blob/main/doc/USAGE.md Install the pv-mounter plugin using kubectl krew. Ensure krew is installed and configured. ```shell kubectl krew install pv-mounter ``` -------------------------------- ### Install pv-mounter via binary download Source: https://context7.com/fenio/pv-mounter/llms.txt Downloads and installs the pv-mounter binary for Linux amd64. After installation, it can be run as a standalone CLI tool. ```bash # Download the latest release for Linux amd64 curl -Lo pv-mounter.tar.gz \ https://github.com/fenio/pv-mounter/releases/latest/download/pv-mounter_linux_amd64.tar.gz ``` ```bash tar -xzf pv-mounter.tar.gz ``` ```bash chmod +x pv-mounter ``` ```bash sudo mv pv-mounter /usr/local/bin/ ``` ```bash # Run as standalone binary (not via kubectl) pv-mounter mount default my-pvc /mnt/data ``` -------------------------------- ### Install pv-mounter via krew Source: https://context7.com/fenio/pv-mounter/llms.txt Installs the pv-mounter kubectl plugin using the krew package manager. Ensure krew is installed first. ```bash kubectl krew install pv-mounter ``` ```bash # Verify kubectl pv-mounter --version ``` -------------------------------- ### Setup Port Forwarding with Kubectl Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Establishes port forwarding to a specified pod using kubectl. It handles starting the command, waiting for SSH readiness, and cleaning up if errors occur. Debug mode prints command output to stdout/stderr. ```go // setupPortForwarding establishes port forwarding to a pod. func setupPortForwarding(ctx context.Context, namespace, podName string, port int, debug bool, timeout time.Duration) (*exec.Cmd, error) { cmd := exec.CommandContext(ctx, "kubectl", "port-forward", fmt.Sprintf("pod/%s", podName), fmt.Sprintf("%d:%d", port, DefaultSSHPort), "-n", namespace) // #nosec G204 -- namespace and podName are validated Kubernetes resource names if debug { cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr } if err := cmd.Start(); err != nil { return nil, fmt.Errorf("failed to start port-forward: %w", err) } if err := waitForSSHReady(ctx, port, timeout); err != nil { cleanupPortForward(cmd) return nil, fmt.Errorf("failed to establish SSH connection: %w", err) } if !debug { fmt.Printf("Forwarding from 127.0.0.1:%d -> %d\n", port, DefaultSSHPort) } return cmd, nil } ``` -------------------------------- ### Setup Pod and Wait for Ready Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Creates a Kubernetes pod using provided configuration and waits until the pod reaches the 'Ready' state. Returns the pod name, assigned port, and any error during the process. ```go func setupPodAndWait(ctx context.Context, clientset *kubernetes.Clientset, namespace, pvcName, publicKey string, config mountConfig, needsRoot bool, image, imageSecret, cpuLimit string) (podName string, port int, err error) { podName, port, err = setupPod(ctx, clientset, namespace, pvcName, publicKey, config.role, config.sshPort, config.originalPodName, needsRoot, image, imageSecret, cpuLimit) if err != nil { return "", 0, err } if err := waitForPodReady(ctx, clientset, namespace, podName); err != nil { return "", 0, err } return podName, port, nil } ``` -------------------------------- ### Install NFS prerequisite Source: https://context7.com/fenio/pv-mounter/llms.txt Installs the NFS client packages required for the NFS backend on Debian/Ubuntu or RHEL/Fedora systems. ```bash # NFS backend — Linux sudo apt install nfs-common # Debian/Ubuntu ``` ```bash sudo dnf install nfs-utils # RHEL/Fedora ``` -------------------------------- ### Build Kubernetes Client with Go Source: https://context7.com/fenio/pv-mounter/llms.txt Constructs a Kubernetes client using the KUBECONFIG environment variable or the default ~/.kube/config file. This is called once at the start of Mount and Clean operations. ```go package main import ( "context" "fmt" "log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/fenio/pv-mounter/pkg/plugin" ) func main() { // Uses $KUBECONFIG or ~/.kube/config clientset, err := plugin.BuildKubeClient() if err != nil { log.Fatalf("failed to build kube client: %v", err) } // Use the clientset normally pods, err := clientset.CoreV1().Pods("default").List(context.Background(), metav1.ListOptions{}) if err != nil { log.Fatalf("failed to list pods: %v", err) } fmt.Printf("Found %d pods in default namespace\n", len(pods.Items)) // Override kubeconfig via environment variable // KUBECONFIG=/path/to/other/config pv-mounter mount ... } ``` -------------------------------- ### Setup Port Forwarding and Mount PVC Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Establishes SSH port forwarding to a specified pod port and then mounts a PVC over SSH to the local mount point. Includes cleanup logic for port forwarding if the mount fails. The timeout for port forwarding is configurable based on whether proxy mode is enabled. ```go func setupPortForwardAndMount(ctx context.Context, namespace, podName string, port int, localMountPoint, pvcName, privateKey string, needsRoot, debug bool, isProxyMode bool) error { timeout := 30 * time.Second if isProxyMode { timeout = 60 * time.Second } pfCmd, err := setupPortForwarding(ctx, namespace, podName, port, debug, timeout) if err != nil { return err } if err := mountPVCOverSSH(ctx, port, localMountPoint, pvcName, privateKey, needsRoot); err != nil { cleanupPortForward(pfCmd) return err } return nil } ``` -------------------------------- ### Install SSHFS prerequisite Source: https://context7.com/fenio/pv-mounter/llms.txt Installs the SSHFS package required for the SSH backend on Debian/Ubuntu or RHEL/Fedora systems. ```bash # SSH backend (default) — Linux sudo apt install sshfs # Debian/Ubuntu ``` ```bash sudo dnf install fuse-sshfs # RHEL/Fedora ``` -------------------------------- ### Setup Proxy Pod for RWO Access Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Creates a proxy pod specifically for RWO volume access. This function configures the mount settings for a proxy role. ```go func setupProxyPod(ctx context.Context, clientset *kubernetes.Clientset, namespace, pvcName, publicKey, originalPodName string, needsRoot bool, image, imageSecret, cpuLimit string) (string, int, error) { config := mountConfig{ role: "proxy", sshPort: ProxySSHPort, originalPodName: originalPodName, } return setupPodAndWait(ctx, clientset, namespace, pvcName, publicKey, config, needsRoot, image, imageSecret, cpuLimit) } ``` -------------------------------- ### SSH Backend Security Configuration Source: https://github.com/fenio/pv-mounter/blob/main/README.md Example security configurations for the SSH backend, focusing on minimal privileges and restricted sshd settings. ```yaml allowPrivilegeEscalation: false readOnlyRootFilesystem: true runAsUser = XYZ runAsGroup = XYZ runAsNonRoot: true ``` ```yaml PermitRootLogin no PasswordAuthentication no ``` -------------------------------- ### Setup Ephemeral Container with SSH Tunnel Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Creates an ephemeral container within a pod and establishes an SSH tunnel for secure communication. It retrieves the proxy pod's IP address and then creates the container. ```go func setupEphemeralContainerWithTunnel(ctx context.Context, clientset *kubernetes.Clientset, namespace, podUsingPVC, proxyPodName, privateKey, publicKey string, needsRoot, debug bool, image string) error { proxyPodIP, err := getPodIP(ctx, clientset, namespace, proxyPodName) if err != nil { return err } if err := createEphemeralContainer(ctx, clientset, namespace, podUsingPVC, privateKey, publicKey, proxyPodIP, needsRoot, image); err != nil { return err } return waitForEphemeralContainerReady(ctx, clientset, namespace, podUsingPVC, debug) } ``` -------------------------------- ### Setup Pod for PVC Exposure Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Creates a new pod specification designed for exposing a Persistent Volume Claim (PVC) via SSH. This function generates a unique pod name and port. ```go func setupPod(ctx context.Context, clientset *kubernetes.Clientset, namespace, pvcName, publicKey, role string, sshPort int, originalPodName string, needsRoot bool, image, imageSecret, cpuLimit string) (string, int, error) { podName, port := generatePodNameAndPort(role) pod := createPodSpec(podName, port, pvcName, publicKey, role, sshPort, originalPodName, needsRoot, image, imageSecret, cpuLimit) ``` -------------------------------- ### Get Container Security Context Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Generates the container security context, configuring privilege escalation, capabilities, seccomp profile, and user/group IDs. ```go // getSecurityContext creates the container security context. func getSecurityContext(needsRoot bool) *corev1.SecurityContext { allowPrivilegeEscalationTrue := true allowPrivilegeEscalationFalse := false readOnlyRootFilesystemTrue := true runAsNonRootTrue := true seccompProfileRuntimeDefault := corev1.SeccompProfile{ Type: corev1.SeccompProfileTypeRuntimeDefault, } if needsRoot { return &corev1.SecurityContext{ AllowPrivilegeEscalation: &allowPrivilegeEscalationTrue, ReadOnlyRootFilesystem: &readOnlyRootFilesystemTrue, Capabilities: &corev1.Capabilities{ Add: []corev1.Capability{"SYS_ADMIN", "SYS_CHROOT"}, }, SeccompProfile: &seccompProfileRuntimeDefault, } } return &corev1.SecurityContext{ AllowPrivilegeEscalation: &allowPrivilegeEscalationFalse, ReadOnlyRootFilesystem: &readOnlyRootFilesystemTrue, Capabilities: &corev1.Capabilities{ Drop: []corev1.Capability{"ALL"}, }, SeccompProfile: &seccompProfileRuntimeDefault, RunAsUser: &DefaultID, RunAsGroup: &DefaultID, RunAsNonRoot: &runAsNonRootTrue, } } ``` -------------------------------- ### Check SSHFS Availability Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Verifies if the 'sshfs' command is available in the system's PATH. Provides installation instructions for macOS and Linux if sshfs is not found, and exits the program. ```go func checkSSHFS() { _, err := exec.LookPath("sshfs") if err != nil { fmt.Println("sshfs is not available in your environment.") switch runtime.GOOS { case "darwin": fmt.Println("For macOS, please install sshfs by visiting: https://osxfuse.github.io/") case "linux": fmt.Println("For Linux, please install sshfs by visiting: https://github.com/libfuse/sshfs") default: fmt.Println("Please install sshfs and try again.") } os.Exit(1) } } ``` -------------------------------- ### Get Build Version in Go Source: https://context7.com/fenio/pv-mounter/llms.txt Retrieve the version string injected at build time using the `version.Version()` function. Returns 'dev' when running from source without build tags. ```go package main import ( "fmt" "github.com/fenio/pv-mounter/pkg/version" ) func main() { fmt.Println(version.Version()) // When built with goreleaser: "v0.14.2" // When built with: go build ./... → "dev" } ``` -------------------------------- ### NFS Backend Security - Ephemeral Container Mount Source: https://context7.com/fenio/pv-mounter/llms.txt Mounts a PVC using the NFS backend within an ephemeral container, inheriting the workload pod's UID. This setup uses FORCE_VFS=true for Ganesha to avoid needing a reserved port. ```bash # NFS ephemeral container (mounted RWO) — runs as workload pod's UID kubectl pv-mounter mount --backend nfs default my-rwo-pvc /mnt/rwo # Adding ephemeral container volume-exposer-xyz to pod my-app-pod (uid=1000) # FORCE_VFS=true is set so Ganesha uses VFS FSAL (no reserved port needed) # Mount command used on Linux: ``` -------------------------------- ### Mount PVC using environment variables Source: https://context7.com/fenio/pv-mounter/llms.txt Demonstrates mounting a PVC using all available options configured via environment variables, providing an alternative to command-line flags. ```bash # All environment variable equivalents NEEDS_ROOT=true DEBUG=true IMAGE=custom/image:tag IMAGE_SECRET=reg-secret \ CPU_LIMIT=500m BACKEND=nfs kubectl pv-mounter mount default my-pvc /mnt/data ``` -------------------------------- ### Build Kubernetes Client Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Constructs a Kubernetes clientset using the KUBECONFIG environment variable or the default path. Handles errors during configuration and client creation. ```go func BuildKubeClient() (*kubernetes.Clientset, error) { kubeconfig := os.Getenv("KUBECONFIG") if kubeconfig == "" { home := os.Getenv("HOME") kubeconfig = fmt.Sprintf("%s/.kube/config", home) } config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) if err != nil { return nil, fmt.Errorf("failed to build Kubernetes config: %w", err) } clientset, err := kubernetes.NewForConfig(config) if err != nil { return nil, fmt.Errorf("failed to create Kubernetes client: %w", err) } return clientset, nil } ``` -------------------------------- ### Build pv-mounter with Version Injection Source: https://context7.com/fenio/pv-mounter/llms.txt Build the pv-mounter binary with a specific version string injected using ldflags. The `--version` flag can then be used to display this version. ```bash go build -ldflags "-X github.com/fenio/pv-mounter/pkg/version.version=v1.0.0" \ -o pv-mounter ./cmd/plugin/main.go ./pv-mounter --version # pv-mounter version v1.0.0 ``` -------------------------------- ### Get PVC Volume Name Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Finds and returns the volume name associated with a Persistent Volume Claim (PVC) within a pod's specifications. ```go // getPVCVolumeName finds the volume name for a PVC in a pod. func getPVCVolumeName(pod *corev1.Pod) (string, error) { for _, volume := range pod.Spec.Volumes { if volume.PersistentVolumeClaim != nil && volume.PersistentVolumeClaim.ClaimName != "" { return volume.Name, nil } } return "", fmt.Errorf("failed to find volume name in the existing pod") } ``` -------------------------------- ### Initialize Signal Handling for Temporary File Cleanup Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Sets up a one-time signal handler to catch interrupt and termination signals (SIGINT, SIGTERM). Upon receiving a signal, it triggers a cleanup of temporary key files and then exits the application. This ensures resources are released gracefully. ```go func init() { cleanupOnce.Do(func() { sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) go func() { <-sigChan cleanupTempKeyFiles() os.Exit(1) }() }) } ``` -------------------------------- ### Build Pod Security Context Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Configures the pod security context, setting runAsUser, runAsGroup, and RunAsNonRoot based on whether root access is needed. ```go // buildPodSecurityContext creates the pod security context. func buildPodSecurityContext(needsRoot bool) *corev1.PodSecurityContext { runAsNonRoot := !needsRoot runAsUser := DefaultUserGroup runAsGroup := DefaultUserGroup if needsRoot { runAsUser = 0 runAsGroup = 0 } return &corev1.PodSecurityContext{ RunAsNonRoot: &runAsNonRoot, RunAsUser: &runAsUser, RunAsGroup: &runAsGroup, } } ``` -------------------------------- ### Mount PVC with Custom CPU Limit Source: https://context7.com/fenio/pv-mounter/llms.txt Use this command to set a CPU limit for the helper pod when environments have tight LimitRanges. ```bash kubectl pv-mounter mount --cpu-limit 500m default my-pvc /mnt/data ``` ```bash CPU_LIMIT=500m kubectl pv-mounter mount default my-pvc /mnt/data ``` -------------------------------- ### Build Container Specification Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Generates a corev1.Container object with environment variables, image, ports, security context, and resource requirements. Selects the appropriate image based on root requirements. ```go envVars := buildEnvVars(publicKey, role, sshPort, needsRoot) imageToUse := selectImage(image, needsRoot) resources := buildResourceRequirements(cpuLimit) return corev1.Container{ Name: "volume-exposer", Image: imageToUse, ImagePullPolicy: corev1.PullAlways, Ports: []corev1.ContainerPort{ {ContainerPort: int32(sshPort)}, }, Env: envVars, SecurityContext: getSecurityContext(needsRoot), Resources: resources, } } ``` -------------------------------- ### Build Pod Resources Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Constructs Kubernetes resource requirements for a pod, including CPU, memory, and ephemeral storage. ```go func buildPodResources(cpuLimit, memoryLimit, ephemeralStorageRequest, ephemeralStorageLimit string) corev1.ResourceRequirements { resources := corev1.ResourceRequirements{ Requests: corev1.ResourceList{ corev1.ResourceMemory: resource.MustParse(MemoryRequest), corev1.ResourceEphemeralStorage: resource.MustParse(EphemeralStorageRequest), }, Limits: corev1.ResourceList{ corev1.ResourceMemory: resource.MustParse(MemoryLimit), corev1.ResourceEphemeralStorage: resource.MustParse(EphemeralStorageLimit), }, } if cpuLimit != "" { resources.Limits[corev1.ResourceCPU] = resource.MustParse(cpuLimit) } return resources } ``` -------------------------------- ### plugin.Mount - Core Go API Source: https://context7.com/fenio/pv-mounter/llms.txt The `plugin.Mount` function is the Go API invoked by the `mount` CLI command. It handles PVC mounting logic, supporting various backends like SSHFS and NFS. ```APIDOC ## plugin.Mount ### Description Mounts a Persistent Volume Claim (PVC) to a specified local directory. This function validates inputs, builds a Kubernetes client, determines the PVC access mode and mount state, and dispatches to the appropriate handler. ### Parameters - `ctx` (context.Context) - The context for the operation. - `namespace` (string) - The Kubernetes namespace of the PVC. - `pvcName` (string) - The name of the PVC to mount. - `localMountPath` (string) - The local path where the PVC should be mounted. This directory must exist. - `needsRoot` (bool) - Whether root privileges are required for mounting. - `debug` (bool) - Enable debug logging. - `image` (string) - The container image to use for the volume exposer. If empty, a default image is used. - `imageSecret` (string) - The Kubernetes secret to use for pulling the image. - `cpuLimit` (string) - CPU limit for the helper pod (e.g., "200m"). Empty means no limit. - `backend` (string) - The backend type. Use "" or "ssh" for SSHFS, and "nfs" for NFS-Ganesha. ### Request Example ```go package main import ( "context" "log" "github.com/fenio/pv-mounter/pkg/plugin" ) func main() { ctx := context.Background() // Mount a PVC using the SSH backend (default) err := plugin.Mount( ctx, "default", // namespace "my-pvc", // PVC name "/mnt/data", // local mount point (must exist) false, // needsRoot false, // debug "", // image (empty = use default bfenski/volume-exposer) "", // imageSecret "", // cpuLimit (empty = no limit) "", // backend ("" or "ssh" = SSHFS, "nfs" = NFS-Ganesha) ) if err != nil { log.Fatalf("mount failed: %v", err) } // Output: // Pod volume-exposer-x7k2m created successfully // Forwarding from 127.0.0.1:54321 -> 2137 // PVC my-pvc mounted successfully to /mnt/data // Mount using NFS backend err = plugin.Mount(ctx, "staging", "data-pvc", "/mnt/staging", false, true, "", "", "200m", "nfs") if err != nil { log.Fatalf("nfs mount failed: %v", err) } } ``` ### Response - `error` - An error if the mount operation fails, otherwise nil. ``` -------------------------------- ### Build Resource Requirements Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Creates corev1.ResourceRequirements for a container, setting CPU and memory requests. Parses resource values from predefined constants. ```go resources := corev1.ResourceRequirements{ Requests: corev1.ResourceList{ corev1.ResourceCPU: resource.MustParse(CPURequest), corev1.ResourceMemory: resource.MustParse(MemoryRequest), } } ``` -------------------------------- ### Mount PVC using plugin.Mount Go API (SSH backend) Source: https://context7.com/fenio/pv-mounter/llms.txt Programmatically mount a PVC using the `plugin.Mount` function. Specify namespace, PVC name, local mount point, and optionally backend, image, and resource limits. The SSH backend is the default. ```go package main import ( "context" "log" "github.com/fenio/pv-mounter/pkg/plugin" ) func main() { ctx := context.Background() // Mount a PVC using the SSH backend (default) err := plugin.Mount( ctx, "default", // namespace "my-pvc", // PVC name "/mnt/data", // local mount point (must exist) false, // needsRoot false, // debug "", // image (empty = use default bfenski/volume-exposer) "", // imageSecret "", // cpuLimit (empty = no limit) "", // backend ("" or "ssh" = SSHFS, "nfs" = NFS-Ganesha) ) if err != nil { log.Fatalf("mount failed: %v", err) } // Output: // Pod volume-exposer-x7k2m created successfully // Forwarding from 127.0.0.1:54321 -> 2137 // PVC my-pvc mounted successfully to /mnt/data // Mount using NFS backend err = plugin.Mount(ctx, "staging", "data-pvc", "/mnt/staging", false, true, "", "", "200m", "nfs") if err != nil { log.Fatalf("nfs mount failed: %v", err) } } ``` -------------------------------- ### Mount PVC to Local Directory Source: https://github.com/fenio/pv-mounter/blob/main/doc/USAGE.md Mount a specified PVC from a namespace to a local directory. Replace 'some-ns', 'some-pvc', and 'some-mountpoint' with your actual values. ```shell kubectl pv-mounter mount some-ns some-pvc some-mountpoint ``` -------------------------------- ### Handle RWX Volume Mounts Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Handles mounting of ReadWriteMany (RWX) volumes or unmounted ReadWriteOnce (RWO) volumes. It sets up a standalone pod for direct access. ```go func handleRWX(ctx context.Context, clientset *kubernetes.Clientset, namespace, pvcName, localMountPoint string, needsRoot, debug bool, image, imageSecret, cpuLimit string) error { privateKey, publicKey, err := generateAndDebugKeys(debug) if err != nil { return err } config := mountConfig{ role: "standalone", sshPort: DefaultSSHPort, originalPodName: "", } podName, port, err := setupPodAndWait(ctx, clientset, namespace, pvcName, publicKey, config, needsRoot, image, imageSecret, cpuLimit) if err != nil { return err } return setupPortForwardAndMount(ctx, namespace, podName, port, localMountPoint, pvcName, privateKey, needsRoot, debug, false) } ``` -------------------------------- ### Create Temporary SSH Key File Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Creates a temporary file to store the provided SSH private key. It sets restrictive file permissions (0600) and registers the file for later cleanup. Returns the path to the temporary file and a cleanup function. ```go // createTempSSHKeyFile creates a temporary file containing the SSH private key. func createTempSSHKeyFile(privateKey string) (string, func(), error) { tmpFile, err := os.CreateTemp("", "ssh_key_*.pem") if err != nil { return "", nil, fmt.Errorf("failed to create temporary file for SSH private key: %w", err) } keyFilePath := tmpFile.Name() registerTempKeyFile(keyFilePath) cleanup := func() { _ = os.Remove(keyFilePath) unregisterTempKeyFile(keyFilePath) } if err := os.Chmod(keyFilePath, 0600); err != nil { cleanup() return "", nil, fmt.Errorf("failed to set permissions on temporary SSH key file: %w", err) } if _, err := tmpFile.Write([]byte(privateKey)); err != nil { cleanup() return "", nil, fmt.Errorf("failed to write SSH private key to temporary file: %w", err) } if err := tmpFile.Close(); err != nil { cleanup() ``` -------------------------------- ### Mount PVC with CPU Limit Source: https://github.com/fenio/pv-mounter/blob/main/doc/USAGE.md Mount a PVC while specifying a CPU limit for the helper pod. This can be done directly via the --cpu-limit flag. ```shell kubectl pv-mounter mount --cpu-limit 200m some-ns some-pvc some-mountpoint ``` -------------------------------- ### Create Kubernetes Pod Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Creates a Kubernetes pod within a specified namespace. Handles potential creation errors. ```go if _, err := clientset.CoreV1().Pods(namespace).Create(ctx, pod, metav1.CreateOptions{}); err != nil { return "", 0, fmt.Errorf("failed to create pod: %w", err) } fmt.Printf("Pod %s created successfully\n", podName) return podName, port, nil } ``` -------------------------------- ### Create Pod Specification Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Constructs a corev1.Pod object with specified configurations including name, labels, containers, security context, and image pull secrets. Handles SSH port validation. ```go if sshPort < 0 || sshPort > 65535 { sshPort = DefaultSSHPort } container := buildContainer(publicKey, role, sshPort, needsRoot, image, cpuLimit) labels := buildPodLabels(pvcName, port, originalPodName) imagePullSecrets := buildImagePullSecrets(imageSecret) podSecurityContext := buildPodSecurityContext(needsRoot) pod := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: podName, Labels: labels, }, Spec: corev1.PodSpec{ Containers: []corev1.Container{container}, SecurityContext: podSecurityContext, ImagePullSecrets: imagePullSecrets, }, } if role != "proxy" { attachPVCToPod(pod, pvcName) } return pod } ``` -------------------------------- ### pv-mounter mount Command Options Source: https://github.com/fenio/pv-mounter/blob/main/docs/pv-mounter_mount.md These options configure the behavior of the pv-mounter mount command. You can specify the mount backend, CPU limits, debug mode, custom container images, image pull secrets, and whether root privileges are needed. ```bash --backend string Mount backend: 'ssh' (default) or 'nfs' ``` ```bash --cpu-limit string Set CPU limit for the volume-exposer container (optional) ``` ```bash --debug Enable debug mode to print additional information (default: false) ``` ```bash -h, --help help for mount ``` ```bash --image string Custom container image for the volume-exposer (optional) ``` ```bash --image-secret string Kubernetes secret name for accessing private registry (optional) ``` ```bash --needs-root Mount the filesystem using the root account (default: false) ``` -------------------------------- ### pv-mounter mount Command Synopsis Source: https://github.com/fenio/pv-mounter/blob/main/docs/pv-mounter_mount.md Use this command to mount a PersistentVolumeClaim (PVC) to a local directory. Specify the namespace, PVC name, and the local mount point as arguments. The --backend flag can be used to select the mount method (ssh or nfs). ```bash pv-mounter mount [flags] ``` -------------------------------- ### Mount PVC with CPU Limit via Environment Variable Source: https://github.com/fenio/pv-mounter/blob/main/doc/USAGE.md Mount a PVC with a CPU limit set using the CPU_LIMIT environment variable. This offers an alternative to the command-line flag. ```shell CPU_LIMIT=200m kubectl pv-mounter mount some-ns some-pvc some-mountpoint ``` -------------------------------- ### Select Container Image Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Determines the container image to use. Returns a provided image name if specified, otherwise selects between a privileged or standard image based on the 'needsRoot' flag. ```go if image != "" { return image } if needsRoot { return PrivilegedImage } return Image } ``` -------------------------------- ### Build SSHFS Command Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Constructs an `exec.Cmd` for the `sshfs` command with specified options including identity file, host key checking, and port. The `keyFilePath` is a securely created temporary file. ```go func buildSSHFSCommand(ctx context.Context, keyFilePath, sshUser, localMountPoint string, port int) *exec.Cmd { return exec.CommandContext(ctx, // #nosec G204 -- keyFilePath is a securely created temp file, localMountPoint is user-provided "sshfs", "-o", fmt.Sprintf("IdentityFile=%s", keyFilePath), "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-o", "nomap=ignore", fmt.Sprintf("%s@localhost:/volume", sshUser), localMountPoint, "-p", fmt.Sprintf("%d", port), ) } ``` -------------------------------- ### Mount PVC with NFS backend Source: https://context7.com/fenio/pv-mounter/llms.txt Mounts a PVC to a local directory using the NFS backend. Specify the backend using the --backend flag. ```bash # Mount with NFS backend kubectl pv-mounter mount --backend nfs default my-pvc /mnt/data ``` -------------------------------- ### Mount PVC with CPU limit Source: https://context7.com/fenio/pv-mounter/llms.txt Sets a CPU limit for the helper pod used during the mount operation. This can be specified via the --cpu-limit flag or the CPU_LIMIT environment variable. ```bash # Set CPU limit for the helper pod kubectl pv-mounter mount --cpu-limit 200m default my-pvc /mnt/data # Equivalent: CPU_LIMIT=200m kubectl pv-mounter mount default my-pvc /mnt/data ``` -------------------------------- ### Check SSH Daemon Readiness Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Checks if the SSH daemon is ready by attempting a TCP connection and reading the initial SSH banner. It uses a short timeout for the connection and read operations. ```go // isSSHReady checks if SSH daemon is ready by attempting to connect and read the SSH banner. func isSSHReady(ctx context.Context, port int) bool { dialer := &net.Dialer{Timeout: time.Second} conn, err := dialer.DialContext(ctx, "tcp", fmt.Sprintf("127.0.0.1:%d", port)) if err != nil { return false } defer func() { _ = conn.Close() }() buf := make([]byte, 4) if err := conn.SetReadDeadline(time.Now().Add(2 * time.Second)); err != nil { return false } n, err := conn.Read(buf) return err == nil && n >= 3 && string(buf[:3]) == "SSH" } ``` -------------------------------- ### Register Temporary Key File Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Adds a temporary key file path to a global map for later cleanup. This function is thread-safe. ```go // registerTempKeyFile registers a temporary key file for cleanup. func registerTempKeyFile(path string) { tempKeyFilesMu.Lock() defer tempKeyFilesMu.Unlock() tempKeyFiles[path] = struct{}{} } ``` -------------------------------- ### Build Ephemeral Container Specification Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Constructs the core specification for a Kubernetes ephemeral container, including its name, image, environment variables, and volume mounts. ```go func buildEphemeralContainerSpec(name, volumeName, privateKey, publicKey, proxyPodIP string, needsRoot bool, image string) corev1.EphemeralContainer { imageToUse := selectImage(image, needsRoot) securityContext := getSecurityContext(needsRoot) return corev1.EphemeralContainer{ EphemeralContainerCommon: corev1.EphemeralContainerCommon{ Name: name, Image: imageToUse, ImagePullPolicy: corev1.PullAlways, Env: []corev1.EnvVar{ {Name: "ROLE", Value: "ephemeral"}, {Name: "SSH_PRIVATE_KEY", Value: privateKey}, {Name: "PROXY_POD_IP", Value: proxyPodIP}, {Name: "SSH_PUBLIC_KEY", Value: publicKey}, {Name: "NEEDS_ROOT", Value: fmt.Sprintf("%v", needsRoot)}, }, SecurityContext: securityContext, VolumeMounts: []corev1.VolumeMount{ { Name: volumeName, MountPath: "/volume", }, }, }, } } ``` -------------------------------- ### Handle RWO Volume Mounts Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Handles mounting of ReadWriteOnce (RWO) volumes that are already mounted. It sets up a proxy pod and an ephemeral container with an SSH tunnel for secure access. ```go func handleRWO(ctx context.Context, clientset *kubernetes.Clientset, namespace, pvcName, localMountPoint string, podUsingPVC string, needsRoot, debug bool, image, imageSecret, cpuLimit string) error { privateKey, publicKey, err := generateAndDebugKeys(debug) if err != nil { return err } proxyPodName, port, err := setupProxyPod(ctx, clientset, namespace, pvcName, publicKey, podUsingPVC, needsRoot, image, imageSecret, cpuLimit) if err != nil { return err } if err := setupEphemeralContainerWithTunnel(ctx, clientset, namespace, podUsingPVC, proxyPodName, privateKey, publicKey, needsRoot, debug, image); err != nil { return err } return setupPortForwardAndMount(ctx, namespace, proxyPodName, port, localMountPoint, pvcName, privateKey, needsRoot, debug, true) } ``` -------------------------------- ### plugin.Clean - Core Go API Source: https://context7.com/fenio/pv-mounter/llms.txt The `plugin.Clean` function is the Go API invoked by the `clean` CLI command. It performs cleanup operations for mounted PVCs, including unmounting, killing port-forward processes, and removing helper pods or terminating ephemeral containers. ```APIDOC ## plugin.Clean ### Description Cleans up a mounted Persistent Volume Claim (PVC). This function unmounts the local directory, kills the port-forward process, and removes the helper pod or terminates the ephemeral container process. ### Parameters - `ctx` (context.Context) - The context for the operation. - `namespace` (string) - The Kubernetes namespace of the PVC. - `pvcName` (string) - The name of the PVC to clean. - `localMountPath` (string) - The local path that was mounted. - `backend` (string) - The backend type used for mounting. Use "" for SSHFS or "nfs" for NFS-Ganesha. ### Request Example ```go package main import ( "context" "log" "github.com/fenio/pv-mounter/pkg/plugin" ) func main() { ctx := context.Background() // Clean SSH-mounted PVC if err := plugin.Clean(ctx, "default", "my-pvc", "/mnt/data", ""); err != nil { log.Fatalf("clean failed: %v", err) } // Output: // Unmounted /mnt/data successfully // Port-forward process for pod volume-exposer-x7k2m killed successfully // Pod volume-exposer-x7k2m deleted successfully // Clean NFS-mounted PVC if err := plugin.Clean(ctx, "staging", "data-pvc", "/mnt/staging", "nfs"); err != nil { log.Fatalf("nfs clean failed: %v", err) } } ``` ### Response - `error` - An error if the clean operation fails, otherwise nil. ``` -------------------------------- ### Build Pod Labels Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Generates labels for a Kubernetes pod, including PVC name, port number, and optionally the original pod name. ```go // buildPodLabels creates labels for the pod. func buildPodLabels(pvcName string, port int, originalPodName string) map[string]string { labels := map[string]string{ "app": "volume-exposer", "pvcName": pvcName, "portNumber": fmt.Sprintf("%d", port), } if originalPodName != "" { labels["originalPodName"] = originalPodName } return labels } ``` -------------------------------- ### Clean PVC using plugin.Clean Go API (SSH backend) Source: https://context7.com/fenio/pv-mounter/llms.txt Programmatically clean a mounted PVC using the `plugin.Clean` function. This unmounts the volume, kills port-forwarding, and removes the helper pod. Specify namespace, PVC name, mount path, and optionally the backend. ```go package main import ( "context" "log" "github.com/fenio/pv-mounter/pkg/plugin" ) func main() { ctx := context.Background() // Clean SSH-mounted PVC if err := plugin.Clean(ctx, "default", "my-pvc", "/mnt/data", ""); err != nil { log.Fatalf("clean failed: %v", err) } // Output: // Unmounted /mnt/data successfully // Port-forward process for pod volume-exposer-x7k2m killed successfully // Pod volume-exposer-x7k2m deleted successfully // Clean NFS-mounted PVC if err := plugin.Clean(ctx, "staging", "data-pvc", "/mnt/staging", "nfs"); err != nil { log.Fatalf("nfs clean failed: %v", err) } } ``` -------------------------------- ### Mount PVC with SSH backend Source: https://context7.com/fenio/pv-mounter/llms.txt Mounts a PVC to a local directory using the default SSHFS backend. This command handles pod creation, port forwarding, and local mounting. ```bash # Basic mount (SSH backend, non-root, no debug) kubectl pv-mounter mount default my-pvc /mnt/data # Pod volume-exposer-x7k2m created successfully # Forwarding from 127.0.0.1:54321 -> 2137 # PVC my-pvc mounted successfully to /mnt/data ``` -------------------------------- ### Mount PVC Over SSH using SSHFS Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Mounts a Persistent Volume Claim (PVC) to a local directory using SSHFS. It creates a temporary SSH key file, builds the SSHFS command, and executes it. Requires the SSH private key and mount point details. ```go // mountPVCOverSSH mounts a PVC using SSHFS. func mountPVCOverSSH( ctx context.Context, port int, localMountPoint, pvcName, privateKey string, needsRoot bool) error { keyFilePath, cleanup, err := createTempSSHKeyFile(privateKey) if err != nil { return err } defer cleanup() sshUser := selectSSHUser(needsRoot) sshfsCmd := buildSSHFSCommand(ctx, keyFilePath, sshUser, localMountPoint, port) sshfsCmd.Stdout = os.Stdout sshfsCmd.Stderr = os.Stderr if err := sshfsCmd.Run(); err != nil { return fmt.Errorf("failed to mount PVC using SSHFS: %w", err) } fmt.Printf("PVC %s mounted successfully to %s\n", pvcName, localMountPoint) return nil } ``` -------------------------------- ### Execute Command in Ephemeral Container Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Executes a command within an existing ephemeral container of a specified pod. Ensure the pod and ephemeral container exist before calling. ```go func executeCommandInEphemeralContainer(ctx context.Context, clientset *kubernetes.Clientset, namespace, podName, ephemeralContainerName string, killCmd []string) error { // Command to kill the process (adjust the process name or ID as necessary) // killCmd := []string{"pkill", "-f", "tail"} // Replace "tail" with the actual process name or use a specific PID cmd := exec.CommandContext(ctx, "kubectl", append([]string{"exec", podName, "-n", namespace, "-c", ephemeralContainerName, "--"}, killCmd...)...) // #nosec G204 -- podName, namespace, and ephemeralContainerName are from validated Kubernetes resources cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err := cmd.Run(); err != nil { return fmt.Errorf("failed to kill process in container %s of pod %s: %w", ephemeralContainerName, podName, err) } return nil } ``` -------------------------------- ### Wait for Pod Ready State Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Polls a Kubernetes pod until it reaches the 'Ready' condition or a timeout occurs. Requires a Kubernetes clientset and pod details. ```go return wait.PollUntilContextTimeout(ctx, time.Second, 5*time.Minute, true, func(ctx context.Context) (bool, error) { pod, err := clientset.CoreV1().Pods(namespace).Get(ctx, podName, metav1.GetOptions{}) if err != nil { return false, err } for _, cond := range pod.Status.Conditions { if cond.Type == corev1.PodReady && cond.Status == corev1.ConditionTrue { return true, nil } } return false, nil }) } ``` -------------------------------- ### Mount PVC with custom image and secret Source: https://context7.com/fenio/pv-mounter/llms.txt Mounts a PVC using a custom container image for the volume exposer and specifies a secret for private registry authentication. ```bash # Custom image and private registry secret kubectl pv-mounter mount \ --image myregistry.example.com/volume-exposer:custom \ --image-secret my-registry-secret \ default my-pvc /mnt/data ``` -------------------------------- ### Mount PVC with Custom Image and Secret Source: https://context7.com/fenio/pv-mounter/llms.txt Mount a PVC using a custom image from a private registry and specify an image pull secret. ```bash kubectl pv-mounter mount \ --image myregistry.internal/volume-exposer:2e1e3a138a \ --image-secret harbor-credentials \ default my-pvc /mnt/data ``` -------------------------------- ### Build Environment Variables for Container Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Creates a slice of corev1.EnvVar objects for a container, including SSH public key, port, and root requirement. Optionally adds a 'ROLE' environment variable. ```go envVars := []corev1.EnvVar{ {Name: "SSH_PUBLIC_KEY", Value: publicKey}, {Name: "SSH_PORT", Value: fmt.Sprintf("%d", sshPort)}, {Name: "NEEDS_ROOT", Value: fmt.Sprintf("%v", needsRoot)}, } if role == "standalone" || role == "proxy" { envVars = append(envVars, corev1.EnvVar{ Name: "ROLE", Value: role, }) } return envVars } ``` -------------------------------- ### Mount PVC with debug output Source: https://context7.com/fenio/pv-mounter/llms.txt Enables debug logging for the mount operation, which includes details like SSH keys, port-forward logs, and container status. ```bash # Enable debug output (prints SSH keys, port-forward logs, container wait status) kubectl pv-mounter mount --debug default my-pvc /mnt/data ``` -------------------------------- ### Mount Persistent Volume using SSHFS in Kubernetes Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Initiates the process to mount a Persistent Volume (PVC) to a local directory using SSHFS. It includes initial checks for SSHFS availability and validation of Kubernetes resource names (namespace, PVC). This function is the entry point for establishing a remote volume mount. ```go func Mount(ctx context.Context, namespace, pvcName, localMountPoint string, needsRoot, debug bool, image, imageSecret, cpuLimit string) error { checkSSHFS() if err := ValidateKubernetesName(namespace, "namespace"); err != nil { return err } if err := ValidateKubernetesName(pvcName, "pvc-name"); err != nil { return err } ``` -------------------------------- ### Build Image Pull Secrets Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Creates a list of local object references for image pull secrets if an image secret name is provided. ```go // buildImagePullSecrets creates image pull secrets if specified. func buildImagePullSecrets(imageSecret string) []corev1.LocalObjectReference { if imageSecret == "" { return []corev1.LocalObjectReference{} } return []corev1.LocalObjectReference{{Name: imageSecret}} } ``` -------------------------------- ### Cleanup All Temporary Key Files Source: https://github.com/fenio/pv-mounter/blob/main/coverage.html Removes all registered temporary key files from the filesystem and unlocks the mutex. This function should be called to clean up all temporary files created during the process. ```go // cleanupTempKeyFiles removes all registered temporary key files. func cleanupTempKeyFiles() { tempKeyFilesMu.Lock() defer tempKeyFilesMu.Unlock() for file := range tempKeyFiles { _ = os.Remove(file) } } ```