Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions deploy/csi-rclone/templates/csi-nodeplugin-rclone.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ spec:
{{- toYaml .Values.csiNodepluginRclone.podAnnotations | nindent 8 }}
spec:
serviceAccountName: {{ include "chart.fullname" . }}-nodeplugin
hostPID: true
hostIPC: true
dnsPolicy: ClusterFirstWithHostNet
containers:
- name: node-driver-registrar
Expand Down Expand Up @@ -143,6 +145,20 @@ spec:
name: pods-mount-dir
- mountPath: /var/lib/rclone
name: cache-dir
# Host Proc for nsenter
- name: host-proc
mountPath: /host/proc
readOnly: true
# Host Binaries for systemd-run and nsenter
- name: host-usr-bin
mountPath: /host/usr/bin
readOnly: true
# FUSE Device
- name: dev-fuse
mountPath: /dev/fuse
# The Binary Bridge for rclone
- name: bin-bridge
mountPath: /opt/rclone-csi/bin
{{- with .Values.csiNodepluginRclone.nodeSelector }}
nodeSelector:
{{ toYaml . | nindent 8 }}
Expand Down Expand Up @@ -170,3 +186,19 @@ spec:
name: registration-dir
- name: cache-dir
emptyDir: {}
- name: host-proc
hostPath:
path: /proc
type: Directory
- name: host-usr-bin
hostPath:
path: /usr/bin
type: Directory
- name: dev-fuse
hostPath:
path: /dev/fuse
type: CharDevice
- name: bin-bridge
hostPath:
path: /opt/rclone-csi/bin
type: DirectoryOrCreate
5 changes: 0 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ require (
)

require (
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect
github.com/beorn7/perks v1.0.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/go-logr/logr v1.4.2 // indirect
Expand All @@ -42,7 +40,6 @@ require (
github.com/imdario/mergo v0.3.7 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/json-iterator/go v1.1.10 // indirect
github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect
github.com/kubernetes-csi/csi-lib-utils v0.3.1 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/moby/sys/mountinfo v0.7.2 // indirect
Expand All @@ -52,7 +49,6 @@ require (
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect
github.com/prometheus/common v0.4.0 // indirect
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 // indirect
github.com/sirupsen/logrus v1.2.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/oauth2 v0.10.0 // indirect
Expand All @@ -64,7 +60,6 @@ require (
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230803162519-f966b187b2e5 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/alecthomas/kingpin.v2 v2.2.6 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down
5 changes: 0 additions & 5 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,7 @@ github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb0
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
Expand Down Expand Up @@ -212,7 +210,6 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
Expand Down Expand Up @@ -300,7 +297,6 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
Expand Down Expand Up @@ -570,7 +566,6 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
16 changes: 16 additions & 0 deletions pkg/rclone/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,22 @@ func (d *Driver) WithControllerServer(cs *controllerServer) *Driver {
}

func (d *Driver) Run() error {
init := func() {
// Path inside your container image
src := "/usr/bin/rclone"
// Path on the host (accessed via a volume mount)
dst := "/opt/rclone-csi/bin/rclone"

// Create directory if it doesn't exist
os.MkdirAll("/opt/rclone-csi/bin", 0755)

// Copy the binary from the image to the host path
// This ensures the host can "see" the rclone binary
// even if the pod is currently restarting.
data, _ := os.ReadFile(src)
os.WriteFile(dst, data, 0755)
}
init()
s := csicommon.NewNonBlockingGRPCServer()
s.Start(
d.endpoint,
Expand Down
97 changes: 79 additions & 18 deletions pkg/rclone/nodeserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"errors"
"fmt"
"os"
"os/exec"
"strings"
"time"

Expand Down Expand Up @@ -48,16 +49,53 @@ func (ns *nodeServer) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstag
return nil, status.Errorf(codes.Unimplemented, "method NodeUnstageVolume not implemented")
}

func configToEnvMap(configData string) (map[string]string, error) {
envVars := make(map[string]string)

// Parse the string data
cfg, err := ini.Load([]byte(configData))
if err != nil {
return nil, fmt.Errorf("failed to parse config: %w", err)
}

for _, section := range cfg.Sections() {
remoteName := section.Name()

// Skip the internal INI "DEFAULT" section
// I.e. the section without a heading that is not in any heading
// NOTE: This will clash with any section i.e. remote that is named "[DEFAULT]"
if remoteName == "DEFAULT" {
continue
}

// rclone convention: RCLONE_CONFIG_REMOTENAME_KEYNAME
// 1. Replace hyphens/spaces with underscores
// 2. Convert to uppercase
envRemote := strings.ToUpper(strings.ReplaceAll(remoteName, "-", "_"))
envRemote = strings.ReplaceAll(envRemote, " ", "_")

for _, key := range section.Keys() {
envKey := strings.ToUpper(strings.ReplaceAll(key.Name(), "-", "_"))
envKey = strings.ReplaceAll(envKey, " ", "_")

fullKey := fmt.Sprintf("RCLONE_CONFIG_%s_%s", envRemote, envKey)
envVars[fullKey] = key.String()
}
}

return envVars, nil
}

// Mounting Volume (Actual Mounting)
func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) {
if err := validatePublishVolumeRequest(req); err != nil {
return nil, err
}

targetPath := req.GetTargetPath()
volumeId := req.GetVolumeId()
//volumeId := req.GetVolumeId()
volumeContext := req.GetVolumeContext()
readOnly := req.GetReadonly()
//readOnly := req.GetReadonly()
secretName, foundSecret := volumeContext["secretName"]
secretNamespace, foundSecretNamespace := volumeContext["secretNamespace"]
// For backwards compatibility - prior to the change in #20 this field held the namespace
Expand Down Expand Up @@ -90,6 +128,7 @@ func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis
return nil, err
} else if apierrors.IsNotFound(err) {
klog.Warningf("Cannot find saved secrets %s: %s", savedSecretName, err)
savedPvcSecret = nil
}

remote, remotePath, configData, flags, e := extractFlags(req.GetVolumeContext(), req.GetSecrets(), pvcSecret, savedPvcSecret)
Expand Down Expand Up @@ -126,25 +165,47 @@ func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis
}
}

rcloneVol := &RcloneVolume{
ID: volumeId,
Remote: remote,
RemotePath: remotePath,
// rcloneVol := &RcloneVolume{
// ID: volumeId,
// Remote: remote,
// RemotePath: remotePath,
// }

envVars, err := configToEnvMap(configData)
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}

// 2. Build the systemd-run command with --setenv
args := []string{
"--mount=/host/proc/1/ns/mnt",
"--",
"systemd-run",
"--scope",
}

// Inject rclon config as env vars
for k, v := range envVars {
args = append(args, fmt.Sprintf("--setenv=%s=%s", k, v))
}
err = ns.RcloneOps.Mount(ctx, rcloneVol, targetPath, configData, readOnly, flags)

args = append(args,
"/opt/rclone-csi/bin/rclone", "mount",
remote+":"+remotePath, req.GetTargetPath(),
"--daemon",
"--allow-other",
)

cmd := exec.Command("/host/usr/bin/nsenter", args...)
cmd.Stderr = os.Stdout
cmd.Stdout = os.Stdout
klog.Infof("Args: %+v", args)
err = cmd.Run()
if err != nil {
if os.IsPermission(err) {
return nil, status.Error(codes.PermissionDenied, err.Error())
}
if strings.Contains(err.Error(), "invalid argument") {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
return nil, status.Error(codes.Internal, err.Error())
klog.Errorf("Failed to mount with error: %v", err)
return nil, status.Error(codes.InvalidArgument, err.Error())
}
// err = ns.WaitForMountAvailable(targetPath)
// if err != nil {
// return nil, status.Error(codes.Internal, err.Error())
// }

return &csi.NodePublishVolumeResponse{}, nil
}

Expand Down
44 changes: 44 additions & 0 deletions test-pod.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: csi-rclone-example
annotations:
csi-rclone.dev/secretName: csi-rclone-test
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
storageClassName: tasko-csi-rclone-secret-annotation
---
apiVersion: v1
kind: Secret
metadata:
name: csi-rclone-test
type: Opaque
stringData:
remote: giab
remotePath: giab/
configData: |
[giab]
type = s3
provider = AWS
---
apiVersion: v1
kind: Pod
metadata:
name: test-pod
spec:
containers:
- name: test
image: debian
volumeMounts:
- mountPath: "/giab"
name: task-pv-storage
command: ["sleep"]
args: ["9999999"]
volumes:
- name: task-pv-storage
persistentVolumeClaim:
claimName: csi-rclone-example
1 change: 1 addition & 0 deletions test-values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
storageClassName: tasko-csi-rclone
Loading