Skip to content
Open
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
73 changes: 73 additions & 0 deletions cos-csi-mounter/server/s3mounter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package main

import (
"encoding/json"
"fmt"

"go.uber.org/zap"
)

type s3MounterArgs struct {
ReadOnly string `json:"read-only,omitempty"`
AllowOther string `json:"allow-other,omitempty"`
UID string `json:"uid,omitempty"`
GID string `json:"gid,omitempty"`
UMask string `json:"umask,omitempty"`
LogLevel string `json:"log-level,omitempty"`
}

func (args s3MounterArgs) PopulateArgsSlice(bucket, targetPath string) ([]string, error) {
// Marshal to JSON
raw, err := json.Marshal(args)
if err != nil {
return nil, err
}

// Unmarshal into map[string]string
var m map[string]string
if err := json.Unmarshal(raw, &m); err != nil {
return nil, err
}

// Convert to key=value slice
result := []string{"mount", bucket, targetPath}
for k, v := range m {
result = append(result, fmt.Sprintf("--%s=%v", k, v)) // --key=value
}

return result, nil // [mount, bucket, path, --key1=value1, --key2=value2, ...]
}

func (args s3MounterArgs) Validate(targetPath string) error {
if err := pathValidator(targetPath); err != nil {
return err
}

// Check if value of allow-other parameter is boolean "true" or "false"
if args.AllowOther != "" {
if isBool := isBoolString(args.AllowOther); !isBool {
logger.Error("cannot convert value of allow-other into boolean", zap.Any("allow-other", args.AllowOther))
return fmt.Errorf("cannot convert value of allow-other into boolean: %v", args.AllowOther)
}
}

// // Check if rclone config file exists or not
// if exists, err := FileExists(args.ConfigPath); err != nil {
// logger.Error("error checking rclone config file existence")
// return fmt.Errorf("error checking rclone config file existence")
// } else if !exists {
// logger.Error("rclone config file not found")
// return fmt.Errorf("rclone config file not found")
// }


// Check if value of read-only parameter is boolean "true" or "false"
if args.ReadOnly != "" {
if isBool := isBoolString(args.ReadOnly); !isBool {
logger.Error("cannot convert value of read-only into boolean", zap.Any("read-only", args.ReadOnly))
return fmt.Errorf("cannot convert value of read-only into boolean: %v", args.ReadOnly)
}
}

return nil
}
2 changes: 1 addition & 1 deletion cos-csi-mounter/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ func handleCosMount(mounter mounterUtils.MounterUtils, parser MounterArgsParser)

logger.Info("New mount request with values:", zap.String("Bucket", request.Bucket), zap.String("Path", request.Path), zap.String("Mounter", request.Mounter), zap.Any("Args", request.Args))

if request.Mounter != constants.S3FS && request.Mounter != constants.RClone {
if request.Mounter != constants.S3FS && request.Mounter != constants.RClone && request.Mounter != constants.S3MOUNTER {
logger.Error("invalid mounter", zap.Any("mounter", request.Mounter))
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid mounter"})
return
Expand Down
10 changes: 10 additions & 0 deletions cos-csi-mounter/server/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,16 @@ func (req *MountRequest) ParseMounterArgs() ([]string, error) {
return nil, fmt.Errorf("rclone args validation failed: %w", err)
}
return args.PopulateArgsSlice(req.Bucket, req.Path)

case constants.S3MOUNTER:
var args s3MounterArgs
if err := strictDecodeForUnknownFields(req.Args, &args); err != nil {
return nil, fmt.Errorf("invalid rclone args decode error: %w", err)
}
if err := args.Validate(req.Path); err != nil {
return nil, fmt.Errorf("s3Mounter args validation failed: %w", err)
}
return args.PopulateArgsSlice(req.Bucket, req.Path)

default:
return nil, fmt.Errorf("unknown mounter: %s", req.Mounter)
Expand Down
2 changes: 2 additions & 0 deletions pkg/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const (

S3FS = "s3fs"
RClone = "rclone"
S3MOUNTER = "s3mounter"

DefaultNamespace = "default"

IAMEP = "https://private.iam.cloud.ibm.com/identity/token"
Expand Down
2 changes: 2 additions & 0 deletions pkg/mounter/mounter.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ func (s *CSIMounterFactory) NewMounter(attrib map[string]string, secretMap map[s
return NewS3fsMounter(secretMap, mountFlags, mounterUtils, defaultMOMap)
case constants.RClone:
return NewRcloneMounter(secretMap, mountFlags, mounterUtils)
case constants.S3MOUNTER:
return NewMountpointS3Mounter(secretMap, mountFlags, mounterUtils)
default:
// default to s3fs
return NewS3fsMounter(secretMap, mountFlags, mounterUtils, defaultMOMap)
Expand Down
207 changes: 207 additions & 0 deletions pkg/mounter/mounter_s3mounter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
// pkg/mounter/mounter-mountpoint-s3.go
package mounter

import (
"encoding/json"
"fmt"
"strings"
"github.com/IBM/ibm-object-csi-driver/pkg/constants"
"github.com/IBM/ibm-object-csi-driver/pkg/mounter/utils"
"k8s.io/klog/v2"
)

// type MountpointS3Mounter struct {
// Bucket string
// Endpoint string
// Region string
// Prefix string
// ReadOnly bool
// UID string
// GID string
// MounterUtils utils.MounterUtils
// }

type MountpointS3Mounter struct {
BucketName string // from secret / SC
ObjPath string // optional prefix
Region string
Endpoint string
ReadOnly bool
UID string
GID string
MountOptions []string

MounterUtils utils.MounterUtils
}
// const (
// passFile = ".passwd-s3fs" // #nosec G101: not password
// maxRetries = 3
// )

// var (
// writePassWrap = writePass
// removeFile = removeS3FSCredFile
// )


// func NewMountpointS3Mounter(
// secretMap map[string]string,
// mountFlags []string,
// mounterUtils utils.MounterUtils,
// ) Mounter {

// return &MountpointS3Mounter{
// Bucket: secretMap["bucketName"],
// Endpoint: secretMap["endpoint"],
// Region: secretMap["region"],
// Prefix: secretMap["prefix"],
// UID: secretMap["uid"],
// GID: secretMap["gid"],
// MounterUtils: mounterUtils,
// }
// }
// func (m *MountpointS3Mounter) Mount(source, target string) error {
// klog.Infof("MountpointS3 Mount target=%s", target)

// args := map[string]string{
// "endpoint": m.Endpoint,
// "region": m.Region,
// "uid": m.UID,
// "gid": m.GID,
// }

// if m.Prefix != "" {
// args["prefix"] = m.Prefix
// }

// payloadMap := map[string]interface{}{
// "path": target,
// "bucket": m.Bucket,
// "mounter": constants.MountpointS3,
// "args": args,
// }

// payload, _ := json.Marshal(payloadMap)

// return mounterRequest(
// string(payload),
// "http://unix/api/cos/mount",
// )
// }
// func (m *MountpointS3Mounter) Unmount(target string) error {
// klog.Infof("MountpointS3 Unmount target=%s", target)

// payload := fmt.Sprintf(`{"path":"%s"}`, target)

// return mounterRequest(
// payload,
// "http://unix/api/cos/unmount",
// )
// }

func NewMountpointS3Mounter(
secretMap map[string]string,
mountOptions []string,
mounterUtils utils.MounterUtils,
) Mounter {

m := &MountpointS3Mounter{}

if v, ok := secretMap["bucketName"]; ok {
m.BucketName = v
}
if v, ok := secretMap["objPath"]; ok {
m.ObjPath = v
}
if v, ok := secretMap["region"]; ok {
m.Region = v
}
if v, ok := secretMap["endpoint"]; ok {
m.Endpoint = v
}
if v, ok := secretMap["uid"]; ok {
m.UID = v
}
if v, ok := secretMap["gid"]; ok {
m.GID = v
}
if v, ok := secretMap["readOnly"]; ok && v == "true" {
m.ReadOnly = true
}

m.MountOptions = mountOptions
m.MounterUtils = mounterUtils

klog.Infof(
"newMountpointS3Mounter bucket=%s prefix=%s region=%s endpoint=%s readonly=%v",
m.BucketName, m.ObjPath, m.Region, m.Endpoint, m.ReadOnly,
)

return m
}
func (m *MountpointS3Mounter) Mount(source, target string) error {
klog.Info("-MountpointS3 Mount-")
klog.Infof("Mount target: %s", target)

bucket := m.BucketName
if m.ObjPath != "" {
bucket = fmt.Sprintf("%s:%s", bucket, strings.TrimPrefix(m.ObjPath, "/"))
}

// Arguments sent to mounter daemon
args := map[string]string{
"region": m.Region,
}

if m.Endpoint != "" {
args["endpoint-url"] = m.Endpoint
}
if m.ReadOnly {
args["read-only"] = "true"
}
if m.UID != "" {
args["uid"] = m.UID
}
if m.GID != "" {
args["gid"] = m.GID
}

for _, opt := range m.MountOptions {
parts := strings.SplitN(opt, "=", 2)
if len(parts) == 2 {
args[parts[0]] = parts[1]
} else {
args[parts[0]] = "true"
}
}

payload := map[string]interface{}{
"path": target,
"bucket": bucket,
"mounter": constants.MountpointS3,
"args": args,
}

data, err := json.Marshal(payload)
if err != nil {
return err
}

klog.Infof("Worker Mounting Payload: %s", string(data))

return mounterRequest(
string(data),
"http://unix/api/cos/mount",
)
}
func (m *MountpointS3Mounter) Unmount(target string) error {
klog.Info("-MountpointS3 Unmount-")
klog.Infof("Unmount target: %s", target)

payload := fmt.Sprintf(`{"path":"%s"}`, target)

return mounterRequest(
payload,
"http://unix/api/cos/unmount",
)
}