diff --git a/README.md b/README.md index 5dfb9e0..441ed62 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,26 @@ stringData: s3-secret-access-key: "SECRET_ACCESS_KEY" ``` +Alternatively, you may specify rclone configuration file directly in the secret under `configData` field. + +``` +apiVersion: v1 +kind: Secret +metadata: + name: rclone-secret +type: Opaque +stringData: + remote: "my-s3" + remotePath: "projectname" + configData: | + [my-s3] + type = s3 + provider = Minio + access_key_id = ACCESS_KEY_ID + secret_access_key = SECRET_ACCESS_KEY + endpoint = http://minio-release.default:9000 +``` + Deploy example secret > `kubectl apply -f example/kubernetes/rclone-secret-example.yaml --namespace kube-system` diff --git a/VERSION b/VERSION index 8b3a022..23c38c2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v1.3.0 \ No newline at end of file +v1.3.1 \ No newline at end of file diff --git a/deploy/kubernetes/1.13/csi-controller-rclone.yaml b/deploy/kubernetes/1.13/csi-controller-rclone.yaml index e3543cf..fa17a7a 100644 --- a/deploy/kubernetes/1.13/csi-controller-rclone.yaml +++ b/deploy/kubernetes/1.13/csi-controller-rclone.yaml @@ -44,7 +44,7 @@ spec: - name: socket-dir mountPath: /csi - name: rclone - image: wunderio/csi-rclone:v1.3.0 + image: wunderio/csi-rclone:v1.3.1 args : - "/bin/csi-rclone-plugin" - "--nodeid=$(NODE_ID)" diff --git a/deploy/kubernetes/1.13/csi-nodeplugin-rclone.yaml b/deploy/kubernetes/1.13/csi-nodeplugin-rclone.yaml index ce3d08a..b4c83f0 100644 --- a/deploy/kubernetes/1.13/csi-nodeplugin-rclone.yaml +++ b/deploy/kubernetes/1.13/csi-nodeplugin-rclone.yaml @@ -44,7 +44,7 @@ spec: capabilities: add: ["SYS_ADMIN"] allowPrivilegeEscalation: true - image: wunderio/csi-rclone:v1.3.0 + image: wunderio/csi-rclone:v1.3.1 args: - "/bin/csi-rclone-plugin" - "--nodeid=$(NODE_ID)" diff --git a/deploy/kubernetes/1.19/csi-controller-rclone.yaml b/deploy/kubernetes/1.19/csi-controller-rclone.yaml index bcb889a..a4ade09 100644 --- a/deploy/kubernetes/1.19/csi-controller-rclone.yaml +++ b/deploy/kubernetes/1.19/csi-controller-rclone.yaml @@ -33,7 +33,7 @@ spec: - name: socket-dir mountPath: /csi - name: rclone - image: wunderio/csi-rclone:v1.3.0 + image: wunderio/csi-rclone:v1.3.1 args : - "/bin/csi-rclone-plugin" - "--nodeid=$(NODE_ID)" diff --git a/deploy/kubernetes/1.19/csi-nodeplugin-rclone.yaml b/deploy/kubernetes/1.19/csi-nodeplugin-rclone.yaml index ba96a10..f2711f2 100644 --- a/deploy/kubernetes/1.19/csi-nodeplugin-rclone.yaml +++ b/deploy/kubernetes/1.19/csi-nodeplugin-rclone.yaml @@ -44,7 +44,7 @@ spec: capabilities: add: ["SYS_ADMIN"] allowPrivilegeEscalation: true - image: wunderio/csi-rclone:v1.3.0 + image: wunderio/csi-rclone:v1.3.1 args: - "/bin/csi-rclone-plugin" - "--nodeid=$(NODE_ID)" diff --git a/example/kubernetes/rclone-secret-file-config.yaml b/example/kubernetes/rclone-secret-file-config.yaml new file mode 100644 index 0000000..6b588ba --- /dev/null +++ b/example/kubernetes/rclone-secret-file-config.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Secret +metadata: + name: rclone-secret + namespace: csi-rclone +type: Opaque +stringData: + remote: "my-s3" + remotePath: "projectname" + configData: | + [my-s3] + type = s3 + provider = Minio + access_key_id = ACCESS_KEY_ID + secret_access_key = SECRET_ACCESS_KEY + endpoint = http://minio-release.default:9000 \ No newline at end of file diff --git a/pkg/rclone/nodeserver.go b/pkg/rclone/nodeserver.go index 515489c..13d1628 100644 --- a/pkg/rclone/nodeserver.go +++ b/pkg/rclone/nodeserver.go @@ -77,13 +77,13 @@ func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis // Load default connection settings from secret secret, e := getSecret("rclone-secret") - remote, remotePath, flags, e := extractFlags(req.GetVolumeContext(), secret) + remote, remotePath, configData, flags, e := extractFlags(req.GetVolumeContext(), secret) if e != nil { klog.Warningf("storage parameter error: %s", e) return nil, e } - e = Mount(remote, remotePath, targetPath, flags) + e = Mount(remote, remotePath, targetPath, configData, flags) if e != nil { if os.IsPermission(e) { return nil, status.Error(codes.PermissionDenied, e.Error()) @@ -97,7 +97,7 @@ func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis return &csi.NodePublishVolumeResponse{}, nil } -func extractFlags(volumeContext map[string]string, secret *v1.Secret) (string, string, map[string]string, error) { +func extractFlags(volumeContext map[string]string, secret *v1.Secret) (string, string, string, map[string]string, error) { // Empty argument list flags := make(map[string]string) @@ -120,7 +120,7 @@ func extractFlags(volumeContext map[string]string, secret *v1.Secret) (string, s } if e := validateFlags(flags); e != nil { - return "", "", flags, e + return "", "", "", flags, e } remote := flags["remote"] @@ -131,10 +131,17 @@ func extractFlags(volumeContext map[string]string, secret *v1.Secret) (string, s delete(flags, "remotePathSuffix") } + configData := "" + ok := false + + if configData, ok = flags["configData"]; ok { + delete(flags, "configData") + } + delete(flags, "remote") delete(flags, "remotePath") - return remote, remotePath, flags, nil + return remote, remotePath, configData, flags, nil } func (ns *nodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) { @@ -221,7 +228,7 @@ func getSecret(secretName string) (*v1.Secret, error) { } // Mount routine. -func Mount(remote string, remotePath string, targetPath string, flags map[string]string) error { +func Mount(remote string, remotePath string, targetPath string, configData string, flags map[string]string) error { mountCmd := "rclone" mountArgs := []string{} @@ -233,16 +240,47 @@ func Mount(remote string, remotePath string, targetPath string, flags map[string defaultFlags["allow-non-empty"] = "true" defaultFlags["allow-other"] = "true" - // rclone mount remote:path /path/to/mountpoint [flags] + remoteWithPath := fmt.Sprintf(":%s:%s", remote, remotePath) + if strings.Contains(configData, "[" + remote + "]") { + remoteWithPath = fmt.Sprintf("%s:%s", remote, remotePath) + klog.Infof("remote %s found in configData, remoteWithPath set to %s", remote, remoteWithPath) + } + + // rclone mount remote:path /path/to/mountpoint [flags] mountArgs = append( mountArgs, "mount", - fmt.Sprintf(":%s:%s", remote, remotePath), + remoteWithPath, targetPath, "--daemon", ) + // If a custom flag configData is defined, + // create a temporary file, fill it with configData content, + // and run rclone with --config flag + if configData != "" { + + configFile, err := ioutil.TempFile("", "rclone.conf") + if err != nil { + return err + } + + // Normally, a defer os.Remove(configFile.Name()) should be placed here. + // However, due to a rclone mount --daemon flag, rclone forks and creates a race condition + // with this nodeplugin proceess. As a result, the config file gets deleted + // before it's reread by a forked process. + + if _, err := configFile.Write([]byte(configData)); err != nil { + return err + } + if err := configFile.Close(); err != nil { + return err + } + + mountArgs = append(mountArgs, "--config", configFile.Name()) + } + // Add default flags for k, v := range defaultFlags { // Exclude overriden flags @@ -262,12 +300,12 @@ func Mount(remote string, remotePath string, targetPath string, flags map[string return err } - klog.Infof("executing mount command cmd=%s, remote=:%s:%s, targetpath=%s", mountCmd, remote, remotePath, targetPath) + klog.Infof("executing mount command cmd=%s, remote=%s, targetpath=%s", mountCmd, remoteWithPath, targetPath) out, err := exec.Command(mountCmd, mountArgs...).CombinedOutput() if err != nil { - return fmt.Errorf("mounting failed: %v cmd: '%s' remote: ':%s:%s' targetpath: %s output: %q", - err, mountCmd, remote, remotePath, targetPath, string(out)) + return fmt.Errorf("mounting failed: %v cmd: '%s' remote: '%s' targetpath: %s output: %q", + err, mountCmd, remoteWithPath, targetPath, string(out)) } return nil