From 62c26f6fdadecc2867f0020faf8d7d86d34c361f Mon Sep 17 00:00:00 2001 From: Josh Date: Sat, 6 Jun 2026 08:50:32 +0330 Subject: [PATCH] Fix CSI node bind mount and publish to correct image registry. Use k8s mount-utils for bind mounts so the pod target directory is created before mounting, and push CI images to ghcr.io/kubelancloud/kks-csi-plugin to match the deployed chart. Co-authored-by: Cursor --- .github/workflows/build.yaml | 4 +- .../kloud-csi/templates/rbac-controller.yaml | 3 ++ charts/kloud-csi/values.yaml | 2 +- pkg/csi/driver/mount_linux.go | 12 +++-- pkg/csi/driver/mount_linux_test.go | 48 +++++++++++++++++++ pkg/csi/driver/node.go | 5 +- 6 files changed, 65 insertions(+), 9 deletions(-) create mode 100644 pkg/csi/driver/mount_linux_test.go diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index abfa1ed..ec09fba 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -21,7 +21,7 @@ jobs: - name: Container image name (GHCR is lowercase) run: | owner=$(echo "${GITHUB_REPOSITORY_OWNER}" | tr '[:upper:]' '[:lower:]') - echo "IMAGE_NAME=${REGISTRY_HOST}/${owner}/kloud-csi-plugin" >> "$GITHUB_ENV" + echo "IMAGE_NAME=${REGISTRY_HOST}/${owner}/kks-csi-plugin" >> "$GITHUB_ENV" echo "APP_VERSION=$(grep '^appVersion:' charts/kloud-csi/Chart.yaml | awk '{print $2}' | tr -d '\"')" >> "$GITHUB_ENV" - uses: docker/setup-buildx-action@v3 @@ -61,4 +61,4 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | owner=$(echo "${GITHUB_REPOSITORY_OWNER}" | tr '[:upper:]' '[:lower:]') - gh api --method PATCH "/orgs/${owner}/packages/container/kloud-csi-plugin/visibility" -f visibility=public || true + gh api --method PATCH "/orgs/${owner}/packages/container/kks-csi-plugin/visibility" -f visibility=public || true diff --git a/charts/kloud-csi/templates/rbac-controller.yaml b/charts/kloud-csi/templates/rbac-controller.yaml index 5aecc72..daf5897 100644 --- a/charts/kloud-csi/templates/rbac-controller.yaml +++ b/charts/kloud-csi/templates/rbac-controller.yaml @@ -39,6 +39,9 @@ rules: - apiGroups: ["storage.k8s.io"] resources: ["volumeattachments/status"] verbs: ["patch"] + - apiGroups: ["storage.k8s.io"] + resources: ["csinodes"] + verbs: ["get", "list", "watch"] - apiGroups: ["coordination.k8s.io"] resources: ["leases"] verbs: ["get", "watch", "list", "create", "update", "patch"] diff --git a/charts/kloud-csi/values.yaml b/charts/kloud-csi/values.yaml index e8d938b..23a0e3b 100644 --- a/charts/kloud-csi/values.yaml +++ b/charts/kloud-csi/values.yaml @@ -16,7 +16,7 @@ fullnameOverride: "" image: repository: ghcr.io/kubelancloud/kks-csi-plugin tag: "latest" - pullPolicy: IfNotPresent + pullPolicy: Always imagePullSecrets: [] diff --git a/pkg/csi/driver/mount_linux.go b/pkg/csi/driver/mount_linux.go index 538a388..1a1c505 100644 --- a/pkg/csi/driver/mount_linux.go +++ b/pkg/csi/driver/mount_linux.go @@ -107,9 +107,15 @@ func cleanupMountPoint(target string, mounter *mount.SafeFormatAndMount) error { } func bindMount(source, target string) error { - out, err := exec.Command("mount", "--bind", source, target).CombinedOutput() - if err != nil { - return fmt.Errorf("bind mount %s -> %s: %w: %s", source, target, err, strings.TrimSpace(string(out))) + if !isMounted(source) { + return fmt.Errorf("staging path %s is not mounted", source) + } + if isMounted(target) { + return nil + } + mounter := mount.New("") + if err := mounter.Mount(source, target, "", []string{"bind"}); err != nil { + return fmt.Errorf("bind mount %s -> %s: %w", source, target, err) } return nil } diff --git a/pkg/csi/driver/mount_linux_test.go b/pkg/csi/driver/mount_linux_test.go new file mode 100644 index 0000000..3486e41 --- /dev/null +++ b/pkg/csi/driver/mount_linux_test.go @@ -0,0 +1,48 @@ +//go:build linux + +package driver + +import ( + "os" + "path/filepath" + "testing" +) + +func TestBindMountCreatesTargetAndIsIdempotent(t *testing.T) { + t.Parallel() + + root := t.TempDir() + source := filepath.Join(root, "globalmount") + target := filepath.Join(root, "pods", "test", "mount") + if err := os.MkdirAll(source, 0o755); err != nil { + t.Fatalf("mkdir source: %v", err) + } + if err := os.WriteFile(filepath.Join(source, "probe"), []byte("ok"), 0o644); err != nil { + t.Fatalf("write probe file: %v", err) + } + + mounter := newMounter() + if err := mounter.Mount("tmpfs", source, "tmpfs", []string{}); err != nil { + t.Fatalf("mount tmpfs at source: %v", err) + } + t.Cleanup(func() { + _ = cleanupMountPoint(source, mounter) + }) + + if err := bindMount(source, target); err != nil { + t.Fatalf("first bindMount failed: %v", err) + } + t.Cleanup(func() { + _ = cleanupMountPoint(target, mounter) + }) + + if _, err := os.Stat(target); err != nil { + t.Fatalf("target mount path missing: %v", err) + } + if _, err := os.Stat(filepath.Join(target, "probe")); err != nil { + t.Fatalf("bind mount did not expose source contents: %v", err) + } + if err := bindMount(source, target); err != nil { + t.Fatalf("second bindMount failed: %v", err) + } +} diff --git a/pkg/csi/driver/node.go b/pkg/csi/driver/node.go index c0a7e52..8e5fee1 100644 --- a/pkg/csi/driver/node.go +++ b/pkg/csi/driver/node.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "os" - "path/filepath" "strconv" "strings" @@ -91,8 +90,8 @@ func (s *NodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublish return nil, invalidArgument("staging target path is required") } - if err := os.MkdirAll(filepath.Dir(req.GetTargetPath()), 0o755); err != nil { - return nil, internalError(err) + if !isMounted(req.GetStagingTargetPath()) { + return nil, internalError(fmt.Errorf("staging path %s is not mounted", req.GetStagingTargetPath())) } if err := bindMount(req.GetStagingTargetPath(), req.GetTargetPath()); err != nil {