diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 43ad3762..36bb620a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,7 @@ name: Release on: push: branches: - - main + - ashima-server jobs: release: @@ -57,8 +57,8 @@ jobs: files: | /home/runner/work/ibm-object-csi-driver/ibm-object-csi-driver/cos-csi-mounter/cos-csi-mounter-latest.tar.gz /home/runner/work/ibm-object-csi-driver/ibm-object-csi-driver/cos-csi-mounter/cos-csi-mounter-latest.tar.gz.sha256 - tag_name: v0.1.0 - name: v0.1.0 + tag_name: v0.1.0-dev03 + name: v0.1.0-dev03 body: CSR generated with SHA1 is not supported to get certs using Metadata. prerelease: ${{ env.IS_LATEST_RELEASE != 'true' }} diff --git a/.secrets.baseline b/.secrets.baseline index 5f92eae2..90147344 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "go.sum|^.secrets.baseline$", "lines": null }, - "generated_at": "2025-05-07T06:35:25Z", + "generated_at": "2025-05-15T13:16:44Z", "plugins_used": [ { "name": "AWSKeyDetector" diff --git a/.travis.yml b/.travis.yml index 43b4efc2..c300f30d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,7 @@ dist: bionic language: go go: - - 1.24.1 + - 1.24.2 group: bluezone diff --git a/Dockerfile b/Dockerfile index fe03fa3d..808372a7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,7 +30,7 @@ FROM registry.access.redhat.com/ubi8/ubi as rclone-builder RUN yum install wget git gcc -y ENV ARCH=amd64 -ENV GO_VERSION=1.24.1 +ENV GO_VERSION=1.24.2 RUN echo $ARCH $GO_VERSION diff --git a/Dockerfile.builder b/Dockerfile.builder index 352d74e6..7f30c5c8 100644 --- a/Dockerfile.builder +++ b/Dockerfile.builder @@ -1,4 +1,4 @@ -FROM golang:1.24.1 +FROM golang:1.24.2 WORKDIR /go/src/github.com/IBM/ibm-object-csi-driver ADD . /go/src/github.com/IBM/ibm-object-csi-driver diff --git a/cos-csi-mounter/Makefile b/cos-csi-mounter/Makefile index d6b370d9..c80e149d 100644 --- a/cos-csi-mounter/Makefile +++ b/cos-csi-mounter/Makefile @@ -21,7 +21,7 @@ ut-coverage: test go mod tidy build-linux: - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -mod mod -o ${BIN_DIR}/cos-csi-mounter-server -ldflags "-s -w" -a ./server/server.go + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -mod mod -o ${BIN_DIR}/cos-csi-mounter-server -ldflags "-s -w" -a ./server deb-build: deb-build: build-linux diff --git a/cos-csi-mounter/go.mod b/cos-csi-mounter/go.mod index d6198819..e56749b3 100644 --- a/cos-csi-mounter/go.mod +++ b/cos-csi-mounter/go.mod @@ -1,3 +1,42 @@ -module cos-csi-mounter +module github.com/IBM/ibm-object-csi-driver/cos-csi-mounter -go 1.24.1 +go 1.24.2 + +require ( + github.com/IBM/ibm-object-csi-driver v0.1.0 + github.com/gin-gonic/gin v1.10.0 + go.uber.org/zap v1.27.0 +) + +require ( + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.24.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/go-ps v1.0.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/net v0.37.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect + google.golang.org/protobuf v1.36.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect +) diff --git a/cos-csi-mounter/go.sum b/cos-csi-mounter/go.sum new file mode 100644 index 00000000..477c92fd --- /dev/null +++ b/cos-csi-mounter/go.sum @@ -0,0 +1,104 @@ +github.com/IBM/ibm-object-csi-driver v0.1.0 h1:2b95Eh98JK1eewLCFDHZW0pJiD+xhRXcWx2DlAUXPnc= +github.com/IBM/ibm-object-csi-driver v0.1.0/go.mod h1:RLNxw4esPAlYYKHVAnlmi5TExNpuDl/6Luf+t0Du4Ik= +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg= +github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/cos-csi-mounter/install/postinst.sh b/cos-csi-mounter/install/postinst.sh index fc014a64..fb9ba963 100755 --- a/cos-csi-mounter/install/postinst.sh +++ b/cos-csi-mounter/install/postinst.sh @@ -1,6 +1,9 @@ #!/bin/bash set -e +# Reload systemd unit files +systemctl daemon-reload || true + # Once package is installed this script will called to enable the service systemctl enable cos-csi-mounter.service systemctl start cos-csi-mounter.service diff --git a/cos-csi-mounter/server/rclone.go b/cos-csi-mounter/server/rclone.go new file mode 100644 index 00000000..b8b0df89 --- /dev/null +++ b/cos-csi-mounter/server/rclone.go @@ -0,0 +1,154 @@ +package main + +import ( + "encoding/json" + "fmt" + + "go.uber.org/zap" +) + +type RCloneArgs struct { + AllowOther string `json:"allow-other,omitempty"` + AllowRoot string `json:"allow-root,omitempty"` + AsyncRead string `json:"async-read,omitempty"` + AttrTimeout string `json:"attr-timeout,omitempty"` + ConfigPath string `json:"config,omitempty"` + Daemon string `json:"daemon,omitempty"` + DaemonTimeout string `json:"daemon-timeout,omitempty"` + DaemonWait string `json:"daemon-wait,omitempty"` + DirCacheTime string `json:"dir-cache-time,omitempty"` + DirectIO string `json:"direct-io,omitempty"` + GID string `json:"gid,omitempty"` + LogFile string `json:"log-file,omitempty"` + NoModificationTime string `json:"no-modtime,omitempty"` + PollInterval string `json:"poll-interval,omitempty"` + ReadOnly string `json:"read-only,omitempty"` + UID string `json:"uid,omitempty"` + UMask string `json:"umask,omitempty"` + VfsCacheMaxAge string `json:"vfs-cache-max-age,omitempty"` + VfsCacheMaxSize string `json:"vfs-cache-max-size,omitempty"` + VfsCacheMinFreeSpace string `json:"vfs-cache-min-free-space,omitempty"` + VfsCacheMode string `json:"vfs-cache-mode,omitempty"` + VfsCachePollInterval string `json:"vfs-cache-poll-interval,omitempty"` + VfsDiskSpaceTotalSize string `json:"vfs-disk-space-total-size,omitempty"` + VfsReadAhead string `json:"vfs-read-ahead,omitempty"` + VfsReadChunkSize string `json:"vfs-read-chunk-size,omitempty"` + VfsReadChunkSizeLimit string `json:"vfs-read-chunk-size-limit,omitempty"` + VfsReadChunkStreams string `json:"vfs-read-chunk-streams,omitempty"` + VfsReadWait string `json:"vfs-read-wait,omitempty"` + VfsRefresh string `json:"vfs-refresh,omitempty"` + VfsWriteBack string `json:"vfs-write-back,omitempty"` + VfsWriteWait string `json:"vfs-write-wait,omitempty"` + WriteBackCache string `json:"write-back-cache,omitempty"` +} + +func (args RCloneArgs) 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 RCloneArgs) 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 value of allow-root parameter is boolean "true" or "false" + if args.AllowRoot != "" { + if isBool := isBoolString(args.AllowRoot); !isBool { + logger.Error("cannot convert value of allow-root into boolean", zap.Any("allow-root", args.AllowRoot)) + return fmt.Errorf("cannot convert value of allow-root into boolean: %v", args.AllowRoot) + } + } + + // Check if value of async-read parameter is boolean "true" or "false" + if args.AsyncRead != "" { + if isBool := isBoolString(args.AsyncRead); !isBool { + logger.Error("cannot convert value of async-read into boolean", zap.Any("async-read", args.AsyncRead)) + return fmt.Errorf("cannot convert value of async-read into boolean: %v", args.AsyncRead) + } + } + + // 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 daemon parameter is boolean "true" or "false" + if args.Daemon != "" { + if isBool := isBoolString(args.Daemon); !isBool { + logger.Error("cannot convert value of daemon into boolean", zap.Any("daemon", args.Daemon)) + return fmt.Errorf("cannot convert value of daemon into boolean: %v", args.Daemon) + } + } + + // Check if value of direct-io parameter is boolean "true" or "false" + if args.DirectIO != "" { + if isBool := isBoolString(args.DirectIO); !isBool { + logger.Error("cannot convert value of direct-io into boolean", zap.Any("direct-io", args.DirectIO)) + return fmt.Errorf("cannot convert value of direct-io into boolean: %v", args.DirectIO) + } + } + + // Check if value of no-modtime parameter is boolean "true" or "false" + if args.NoModificationTime != "" { + if isBool := isBoolString(args.NoModificationTime); !isBool { + logger.Error("cannot convert value of no-modtime into boolean", zap.Any("no-modtime", args.NoModificationTime)) + return fmt.Errorf("cannot convert value of no-modtime into boolean: %v", args.NoModificationTime) + } + } + + // 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) + } + } + + // Check if value of vfs-refresh parameter is boolean "true" or "false" + if args.VfsRefresh != "" { + if isBool := isBoolString(args.VfsRefresh); !isBool { + logger.Error("cannot convert value of vfs-refresh into boolean", zap.Any("vfs-refresh", args.VfsRefresh)) + return fmt.Errorf("cannot convert value of vfs-refresh into boolean: %v", args.VfsRefresh) + } + } + + // Check if value of write-back-cache parameter is boolean "true" or "false" + if args.WriteBackCache != "" { + if isBool := isBoolString(args.WriteBackCache); !isBool { + logger.Error("cannot convert value of write-back-cache into boolean", zap.Any("write-back-cache", args.WriteBackCache)) + return fmt.Errorf("cannot convert value of write-back-cache into boolean: %v", args.WriteBackCache) + } + } + + return nil +} diff --git a/cos-csi-mounter/server/s3fs.go b/cos-csi-mounter/server/s3fs.go new file mode 100644 index 00000000..19169d26 --- /dev/null +++ b/cos-csi-mounter/server/s3fs.go @@ -0,0 +1,299 @@ +package main + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + + "go.uber.org/zap" +) + +type S3FSArgs struct { + AllowOther string `json:"allow_other,omitempty"` + AutoCache string `json:"auto_cache,omitempty"` + CipherSuites string `json:"cipher_suites,omitempty"` + ConnectTimeoutSeconds string `json:"connect_timeout,omitempty"` + CurlDebug string `json:"curldbg,omitempty"` + DebugLevel string `json:"dbglevel,omitempty"` + DefaultACL string `json:"default_acl,omitempty"` + EndPoint string `json:"endpoint,omitempty"` + GID string `json:"gid,omitempty"` + IBMIamAuth string `json:"ibm_iam_auth,omitempty"` + IBMIamEndpoint string `json:"ibm_iam_endpoint,omitempty"` + InstanceName string `json:"instance_name,omitempty"` + KernelCache string `json:"kernel_cache,omitempty"` + MaxBackground string `json:"max_background,omitempty"` + MaxDirtyData string `json:"max_dirty_data,omitempty"` + MaxStatCacheSize string `json:"max_stat_cache_size,omitempty"` + MPUmask string `json:"mp_umask,omitempty"` + MultiPartSize string `json:"multipart_size,omitempty"` + MultiReqMax string `json:"multireq_max,omitempty"` + ParallelCount string `json:"parallel_count,omitempty"` + PasswdFilePath string `json:"passwd_file,omitempty"` + ReadOnly string `json:"ro,omitempty"` + ReadwriteTimeoutSeconds string `json:"readwrite_timeout,omitempty"` + RetryCount string `json:"retries,omitempty"` + SigV2 string `json:"sigv2,omitempty"` + SigV4 string `json:"sigv4,omitempty"` + StatCacheExpireSeconds string `json:"stat_cache_expire,omitempty"` + UID string `json:"uid,omitempty"` + URL string `json:"url,omitempty"` + UsePathRequestStyle string `json:"use_path_request_style,omitempty"` + UseXattr string `json:"use_xattr,string,omitempty"` +} + +func (args S3FSArgs) 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{bucket, targetPath} + for k, v := range m { + result = append(result, "-o") + if strings.ToLower(strings.TrimSpace(v)) == "true" { + result = append(result, fmt.Sprintf("%s", k)) // -o, key + } else { + result = append(result, fmt.Sprintf("%s=%v", k, v)) // -o, key=value + } + } + + return result, nil // [bucket, path, -o, key1=value1, -o, key2=value2, -o key3, ...] +} + +func (args S3FSArgs) 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 value of auto_cache parameter is boolean "true" or "false" + if args.AutoCache != "" { + if isBool := isBoolString(args.AutoCache); !isBool { + logger.Error("cannot convert value of auto_cache into boolean", zap.Any("auto_cache", args.AutoCache)) + return fmt.Errorf("cannot convert value of auto_cache into boolean: %v", args.AutoCache) + } + } + + // Check if value of connect_timeout parameter can be converted to integer + if args.ConnectTimeoutSeconds != "" { + _, err := strconv.Atoi(args.ConnectTimeoutSeconds) + if err != nil { + logger.Error("cannot convert value of connect_timeout into integer", zap.Error(err)) + return fmt.Errorf("cannot convert value of connect_timeout into integer: %v", err) + } + } + + // Check if value of curldbg parameter is either "body" or "normal" + if args.CurlDebug != "" && !(args.CurlDebug == "body" || args.CurlDebug == "normal") { + logger.Error("invalid value for 'curldbg' param. Should be either 'body' or 'normal'", zap.Any("curldbg", args.CurlDebug)) + return fmt.Errorf("invalid value for 'curldbg' param. Should be either 'body' or 'normal': %v", args.CurlDebug) + } + + // Check if value of gid parameter can be converted to integer + if args.GID != "" { + _, err := strconv.Atoi(args.GID) + if err != nil { + logger.Error("cannot convert value of gid into integer", zap.Error(err)) + return fmt.Errorf("cannot convert value of gid into integer: %v", err) + } + } + + // Check if value of ibm_iam_auth parameter is boolean "true" or "false" + if args.IBMIamAuth != "" { + if isBool := isBoolString(args.IBMIamAuth); !isBool { + logger.Error("cannot convert value of ibm_iam_auth into boolean", zap.Any("ibm_iam_auth", args.IBMIamAuth)) + return fmt.Errorf("cannot convert value of ibm_iam_auth into boolean: %v", args.IBMIamAuth) + } + } + + if args.IBMIamEndpoint != "" { + if !(strings.HasPrefix(args.IBMIamEndpoint, "https://") || strings.HasPrefix(args.IBMIamEndpoint, "http://")) { + logger.Error("bad value for ibm_iam_endpoint."+ + " Must be of the form https:// or http://", + zap.String("ibm_iam_endpoint", args.IBMIamEndpoint)) + return fmt.Errorf("bad value for ibm_iam_endpoint \"%v\":"+ + " Must be of the form https:// or http://", args.IBMIamEndpoint) + } + } + + // Check if value of kernel_cache parameter is boolean "true" or "false" + if args.KernelCache != "" { + if isBool := isBoolString(args.KernelCache); !isBool { + logger.Error("cannot convert value of kernel_cache into boolean", zap.Any("kernel_cache", args.KernelCache)) + return fmt.Errorf("cannot convert value of kernel_cache into boolean: %v", args.KernelCache) + } + } + + // Check if value of max_background parameter can be converted to integer + if args.MaxBackground != "" { + _, err := strconv.Atoi(args.MaxBackground) + if err != nil { + logger.Error("cannot convert value of max_background into integer", zap.Error(err)) + return fmt.Errorf("cannot convert value of max_background into integer: %v", err) + } + } + + // Check if value of max_dirty_data parameter can be converted to integer + if args.MaxDirtyData != "" { + _, err := strconv.Atoi(args.MaxDirtyData) + if err != nil { + logger.Error("cannot convert value of max_dirty_data into integer", zap.Error(err)) + return fmt.Errorf("cannot convert value of max_dirty_data into integer: %v", err) + } + } + + // Check if value of max_stat_cache_size parameter can be converted to integer + if args.MaxStatCacheSize != "" { + _, err := strconv.Atoi(args.MaxStatCacheSize) + if err != nil { + logger.Error("cannot convert value of max_stat_cache_size into integer", zap.Error(err)) + return fmt.Errorf("cannot convert value of max_stat_cache_size into integer: %v", err) + } + } + + // Check if value of multipart_size parameter can be converted to integer + if args.MultiPartSize != "" { + _, err := strconv.Atoi(args.MultiPartSize) + if err != nil { + logger.Error("cannot convert value of multipart_size into integer", zap.Error(err)) + return fmt.Errorf("cannot convert value of multipart_size into integer: %v", err) + } + } + + // Check if value of multireq_max parameter can be converted to integer + if args.MultiReqMax != "" { + _, err := strconv.Atoi(args.MultiReqMax) + if err != nil { + logger.Error("cannot convert value of multireq_max into integer", zap.Error(err)) + return fmt.Errorf("cannot convert value of multireq_max into integer: %v", err) + } + } + + // Check if value of parallel_count parameter can be converted to integer + if args.ParallelCount != "" { + _, err := strconv.Atoi(args.ParallelCount) + if err != nil { + logger.Error("cannot convert value of parallel_count into integer", zap.Error(err)) + return fmt.Errorf("cannot convert value of parallel_count into integer: %v", err) + } + } + + // Check if .passwd file exists or not + if exists, err := fileExists(args.PasswdFilePath); err != nil { + logger.Error("error checking credential file existence") + return fmt.Errorf("error checking credential file existence") + } else if !exists { + logger.Error("credential file not found") + return fmt.Errorf("credential file not found") + } + + // Check if value of ro parameter is boolean "true" or "false" + if args.ReadOnly != "" { + if isBool := isBoolString(args.ReadOnly); !isBool { + logger.Error("cannot convert value of ro into boolean", zap.Any("ro", args.ReadOnly)) + return fmt.Errorf("cannot convert value of roe into boolean: %v", args.ReadOnly) + } + } + + // Check if value of readwrite_timeout parameter can be converted to integer + if args.ReadwriteTimeoutSeconds != "" { + _, err := strconv.Atoi(args.ReadwriteTimeoutSeconds) + if err != nil { + logger.Error("cannot convert value of readwrite_timeout into integer", zap.Error(err)) + return fmt.Errorf("cannot convert value of readwrite_timeout into integer: %v", err) + } + } + + // Check if value of retries parameter can be converted to integer + if args.RetryCount != "" { + retryCount, err := strconv.Atoi(args.RetryCount) + if err != nil { + logger.Error("cannot convert value of retires into integer", zap.Error(err)) + return fmt.Errorf("cannot convert value of retires into integer: %v", err) + } + if retryCount < 1 { + logger.Error("value of retires should be >= 1") + return fmt.Errorf("value of retires should be >= 1") + } + } + + // Check if value of sigv2 parameter is boolean "true" or "false" + if args.SigV2 != "" { + if isBool := isBoolString(args.SigV2); !isBool { + logger.Error("cannot convert value of sigv2 into boolean", zap.Any("sigv2", args.SigV2)) + return fmt.Errorf("cannot convert value of sigv2 into boolean: %v", args.SigV2) + } + } + + // Check if value of sigv4 parameter is boolean "true" or "false" + if args.SigV4 != "" { + if isBool := isBoolString(args.SigV4); !isBool { + logger.Error("cannot convert value of sigv4 into boolean", zap.Any("sigv4", args.SigV4)) + return fmt.Errorf("cannot convert value of sigv4 into boolean: %v", args.SigV4) + } + } + + // Check if value of stat_cache_expire parameter can be converted to integer + if args.StatCacheExpireSeconds != "" { + cacheExpireSeconds, err := strconv.Atoi(args.StatCacheExpireSeconds) + if err != nil { + logger.Error("cannot convert value of stat_cache_expire into integer", zap.Error(err)) + return fmt.Errorf("cannot convert value of stat_cache_expire into integer: %v", err) + } else if cacheExpireSeconds < 0 { + logger.Error("value of stat_cache_expire should be >= 0") + return fmt.Errorf("value ofstat_cache_expire should be >= 0") + } + } + + // Check if value of uid parameter can be converted to integer + if args.UID != "" { + _, err := strconv.Atoi(args.UID) + if err != nil { + logger.Error("cannot convert value of uid into integer", zap.Error(err)) + return fmt.Errorf("cannot convert value of uid into integer: %v", err) + } + } + + // Check if value of use_path_request_style parameter is boolean "true" or "false" + if args.UsePathRequestStyle != "" { + if isBool := isBoolString(args.UsePathRequestStyle); !isBool { + logger.Error("cannot convert value of use_path_request_style into boolean", zap.Any("use_path_request_style", args.UsePathRequestStyle)) + return fmt.Errorf("cannot convert value of use_path_request_style into boolean: %v", args.UsePathRequestStyle) + } + } + + // Check if value of use_xattr parameter is boolean "true" or "false" + if args.UseXattr != "" { + if isBool := isBoolString(args.UseXattr); !isBool { + logger.Error("cannot convert value of use_xattr into boolean", zap.Any("use_xattr", args.UseXattr)) + return fmt.Errorf("cannot convert value of use_xattr into boolean: %v", args.UseXattr) + } + } + + if !(strings.HasPrefix(args.URL, "https://") || strings.HasPrefix(args.URL, "http://")) { + logger.Error("bad value for url: scheme is missing."+ + " Must be of the form http:// or https://", + zap.String("url", args.URL)) + return fmt.Errorf("bad value for url \"%v\": scheme is missing."+ + " Must be of the form http:// or https://", args.URL) + } + + return nil +} diff --git a/cos-csi-mounter/server/server.go b/cos-csi-mounter/server/server.go index 38dd16da..59018622 100644 --- a/cos-csi-mounter/server/server.go +++ b/cos-csi-mounter/server/server.go @@ -1,3 +1,164 @@ package main -func main() {} +import ( + "flag" + "fmt" + "net" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + mounterUtils "github.com/IBM/ibm-object-csi-driver/pkg/mounter/utils" + "github.com/gin-gonic/gin" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +var ( + logger *zap.Logger + socketPath = "/var/lib/coscsi.sock" + + s3fs = "s3fs" + rclone = "rclone" +) + +func init() { + _ = flag.Set("logtostderr", "true") // #nosec G104: Attempt to set flags for logging to stderr only on best-effort basis.Error cannot be usefully handled. + logger = setUpLogger() + defer logger.Sync() +} + +func setUpLogger() *zap.Logger { + // Prepare a new logger + atom := zap.NewAtomicLevel() + encoderCfg := zap.NewProductionEncoderConfig() + encoderCfg.TimeKey = "timestamp" + encoderCfg.EncodeTime = zapcore.ISO8601TimeEncoder + + logger := zap.New(zapcore.NewCore( + zapcore.NewJSONEncoder(encoderCfg), + zapcore.Lock(os.Stdout), + atom, + ), zap.AddCaller()).With(zap.String("ServiceName", "cos-csi-mounter-service")) + atom.SetLevel(zap.InfoLevel) + return logger +} + +func main() { + // Always create fresh socket file + err := os.Remove(socketPath) + if err != nil { + // Handle it properly: log it, retry, return, etc. + logger.Warn("Failed to remove Socket File") + } + + // Create a listener + logger.Info("Creating unix socket listener...") + listener, err := net.Listen("unix", socketPath) + if err != nil { + logger.Fatal("Failed to create unix socket listener:", zap.Error(err)) + } + // Close the listener at the end + defer listener.Close() + + // Handle SIGINT and SIGTERM signals to gracefully shut down the server + signals := make(chan os.Signal, 1) + signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) + go func() { + <-signals + err := os.Remove(socketPath) + if err != nil { + // Handle it properly: log it, retry, return, etc. + logger.Warn("Failed to remove Socket File") + } + os.Exit(0) + }() + + logger.Info("Starting cos-csi-mounter service...") + + // Create gin router + router := gin.Default() + + router.POST("/api/cos/mount", handleCosMount()) + router.POST("/api/cos/unmount", handleCosUnmount()) + + // Serve HTTP requests over Unix socket + // err = http.Serve(listener, router) + server := &http.Server{ + Handler: router, + ReadHeaderTimeout: 3 * time.Second, + } + err = server.Serve(listener) + if err != nil { + logger.Fatal("Error while serving HTTP requests:", zap.Error(err)) + } +} + +func handleCosMount() gin.HandlerFunc { + return func(c *gin.Context) { + var request MountRequest + + if err := c.BindJSON(&request); err != nil { + logger.Error("invalid request: ", zap.Error(err)) + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"}) + return + } + + 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 != s3fs && request.Mounter != rclone { + logger.Error("invalid mounter", zap.Any("mounter", request.Mounter)) + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid mounter"}) + return + } + + // validate mounter args + args, err := request.ParseMounterArgs() + if err != nil { + logger.Error("failed to parse mounter args", zap.Any("mounter", request.Mounter), zap.Error(err)) + + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("invalid args for mounter: %v", err)}) + return + } + + utils := mounterUtils.MounterOptsUtils{} + err = utils.FuseMount(request.Path, request.Mounter, args) + if err != nil { + logger.Error("mount failed: ", zap.Error(err)) + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("mount failed: %v", err)}) + return + } + + logger.Info("bucket mount is successful", zap.Any("bucket", request.Bucket), zap.Any("path", request.Path)) + c.JSON(http.StatusOK, "Success!!") + } +} + +func handleCosUnmount() gin.HandlerFunc { + return func(c *gin.Context) { + var request struct { + Path string `json:"path"` + } + + if err := c.BindJSON(&request); err != nil { + logger.Error("invalid request: ", zap.Error(err)) + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request"}) + return + } + + logger.Info("New unmount request with values: ", zap.String("Path:", request.Path)) + + utils := mounterUtils.MounterOptsUtils{} + err := utils.FuseUnmount(request.Path) + if err != nil { + logger.Error("unmount failed: ", zap.Error(err)) + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("unmount failed :%v", err)}) + return + } + + logger.Info("bucket unmount is successful", zap.Any("path", request.Path)) + c.JSON(http.StatusOK, "Success!!") + } +} diff --git a/cos-csi-mounter/server/utils.go b/cos-csi-mounter/server/utils.go new file mode 100644 index 00000000..3d6eb5cf --- /dev/null +++ b/cos-csi-mounter/server/utils.go @@ -0,0 +1,102 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" +) + +// MountRequest ... +type MountRequest struct { + Path string `json:"path"` + Bucket string `json:"bucket"` + Mounter string `json:"mounter"` + Args json.RawMessage `json:"args"` +} + +var ( + // Directories where bucket can be mounted + safeMountDirs = []string{"/var/data/kubelet/pods", "/var/lib/kubelet/pods"} + // Directories where s3fs/rclone configuration files need to be present + safeMounterConfigDir = "/var/lib/cos-csi" +) + +// MounterArgs ... +type MounterArgs interface { + Validate(path string) error + PopulateArgsSlice(bucket, path string) ([]string, error) +} + +func strictDecodeForUnknownFields(data json.RawMessage, v interface{}) error { + dec := json.NewDecoder(bytes.NewReader(data)) + dec.DisallowUnknownFields() + return dec.Decode(v) +} + +func pathValidator(targetPath string) error { + absPath, err := filepath.Abs(targetPath) + if err != nil { + return fmt.Errorf("failed to resolve absolute mount path: %v", err) + } + if !(strings.HasPrefix(absPath, safeMountDirs[0]) || strings.HasPrefix(absPath, safeMountDirs[1])) { + return fmt.Errorf("bad value for target path \"%v\"", targetPath) + } + return nil +} + +// --- Parser for Mounter Arguments --- + +func (req *MountRequest) ParseMounterArgs() ([]string, error) { + switch req.Mounter { + case s3fs: + var args S3FSArgs + if err := strictDecodeForUnknownFields(req.Args, &args); err != nil { + return nil, fmt.Errorf("invalid s3fs args decode error: %w", err) + } + if err := args.Validate(req.Path); err != nil { + return nil, fmt.Errorf("s3fs args validation failed: %w", err) + } + return args.PopulateArgsSlice(req.Bucket, req.Path) + + case rclone: + var args RCloneArgs + 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("rclone args validation failed: %w", err) + } + return args.PopulateArgsSlice(req.Bucket, req.Path) + + default: + return nil, fmt.Errorf("unknown mounter: %s", req.Mounter) + } +} + +// isBoolString checks if a string is "true" or "false" (case-insensitive) +func isBoolString(s string) bool { + s = strings.TrimSpace(strings.ToLower(s)) + return s == "true" || s == "false" +} + +// fileExists checks whether the given file path exists and is not a directory. +func fileExists(path string) (bool, error) { + absPath, err := filepath.Abs(path) + if err != nil { + return false, fmt.Errorf("failed to resolve absolute path: %v", err) + } + if !strings.HasPrefix(absPath, safeMounterConfigDir) { + return false, fmt.Errorf("path %v is outside the safe directory", absPath) + } + info, err := os.Stat(absPath) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err + } + return !info.IsDir(), nil +} diff --git a/go.mod b/go.mod index c30f5c8e..17ccb197 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/IBM/ibm-object-csi-driver -go 1.24.1 +go 1.24.2 require ( github.com/IBM/go-sdk-core/v5 v5.19.1