Supply credentials using volume secrets instead of cli config
This commit is contained in:
parent
ea4022e9c6
commit
4e8bbf0126
18 changed files with 136 additions and 146 deletions
|
@ -31,27 +31,12 @@ func init() {
|
||||||
var (
|
var (
|
||||||
endpoint = flag.String("endpoint", "unix://tmp/csi.sock", "CSI endpoint")
|
endpoint = flag.String("endpoint", "unix://tmp/csi.sock", "CSI endpoint")
|
||||||
nodeID = flag.String("nodeid", "", "node id")
|
nodeID = flag.String("nodeid", "", "node id")
|
||||||
accessKeyID = flag.String("access-key-id", "", "S3 Access Key ID to use")
|
|
||||||
secretAccessKey = flag.String("secret-access-key", "", "S3 Secret Access Key to use")
|
|
||||||
s3endpoint = flag.String("s3-endpoint", "", "S3 Endpoint URL to use")
|
|
||||||
region = flag.String("region", "", "S3 Region to use")
|
|
||||||
mounter = flag.String("mounter", "s3fs", "Specify which Mounter to use")
|
|
||||||
encryptionKey = flag.String("encryption-key", "", "Encryption key for file system (only used with s3ql)")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
cfg := &s3.Config{
|
driver, err := s3.NewS3(*nodeID, *endpoint)
|
||||||
AccessKeyID: *accessKeyID,
|
|
||||||
SecretAccessKey: *secretAccessKey,
|
|
||||||
Endpoint: *s3endpoint,
|
|
||||||
Region: *region,
|
|
||||||
Mounter: *mounter,
|
|
||||||
EncryptionKey: *encryptionKey,
|
|
||||||
}
|
|
||||||
|
|
||||||
driver, err := s3.NewS3(*nodeID, *endpoint, cfg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -42,3 +42,5 @@ require (
|
||||||
k8s.io/kubernetes v1.13.4
|
k8s.io/kubernetes v1.13.4
|
||||||
k8s.io/utils v0.0.0-20180703210027-ab9069044f32 // indirect
|
k8s.io/utils v0.0.0-20180703210027-ab9069044f32 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace github.com/kubernetes-csi/csi-test => github.com/ctrox/csi-test v1.1.1-0.20190310103436-e50382dcb47f
|
||||||
|
|
10
go.sum
10
go.sum
|
@ -4,6 +4,14 @@ github.com/aws/aws-sdk-go v1.14.27 h1:fRVME5X3sxZnctdCcabNTWZq7ZGrpVgUAYk4OA5EG0
|
||||||
github.com/aws/aws-sdk-go v1.14.27/go.mod h1:ZRmQr0FajVIyZ4ZzBYKG5P3ZqPz9IHG41ZoMu1ADI3k=
|
github.com/aws/aws-sdk-go v1.14.27/go.mod h1:ZRmQr0FajVIyZ4ZzBYKG5P3ZqPz9IHG41ZoMu1ADI3k=
|
||||||
github.com/container-storage-interface/spec v1.0.0 h1:3DyXuJgf9MU6kyULESegQUmozsSxhpyrrv9u5bfwA3E=
|
github.com/container-storage-interface/spec v1.0.0 h1:3DyXuJgf9MU6kyULESegQUmozsSxhpyrrv9u5bfwA3E=
|
||||||
github.com/container-storage-interface/spec v1.0.0/go.mod h1:6URME8mwIBbpVyZV93Ce5St17xBiQJQY67NDsuohiy4=
|
github.com/container-storage-interface/spec v1.0.0/go.mod h1:6URME8mwIBbpVyZV93Ce5St17xBiQJQY67NDsuohiy4=
|
||||||
|
github.com/ctrox/csi-test v1.1.0 h1:YwOvPrlZw6/qgG+G8EQMkMniPt2HJmTOYVBiawgfiQ8=
|
||||||
|
github.com/ctrox/csi-test v1.1.0/go.mod h1:Sdb3sQ5DaEikqpKZNzj+abr8x/OCMXB0KTaxIAXP1RI=
|
||||||
|
github.com/ctrox/csi-test v1.1.1-0.20190310103436-e50382dcb47f h1:FLD1xv7Vwv7+JZizABfim+tR8Ctj68B2mnS529kHBPg=
|
||||||
|
github.com/ctrox/csi-test v1.1.1-0.20190310103436-e50382dcb47f/go.mod h1:Sdb3sQ5DaEikqpKZNzj+abr8x/OCMXB0KTaxIAXP1RI=
|
||||||
|
github.com/ctrox/csi-test v1.1.2-0.20190310094942-e965dacfef26 h1:KbZ3qIvoWP0CD7ZnUULipd5QGg0gmNLCfxikgAYnKwQ=
|
||||||
|
github.com/ctrox/csi-test v1.1.2-0.20190310094942-e965dacfef26/go.mod h1:Sdb3sQ5DaEikqpKZNzj+abr8x/OCMXB0KTaxIAXP1RI=
|
||||||
|
github.com/ctrox/csi-test v1.1.2-0.20190310103005-3f3cc7817699 h1:bQ82DNERrJuin7/+sRCoeBz7FV8/HNS6LpIe48XUWCo=
|
||||||
|
github.com/ctrox/csi-test v1.1.2-0.20190310103005-3f3cc7817699/go.mod h1:Sdb3sQ5DaEikqpKZNzj+abr8x/OCMXB0KTaxIAXP1RI=
|
||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/go-ini/ini v1.38.1 h1:hbtfM8emWUVo9GnXSloXYyFbXxZ+tG6sbepSStoe1FY=
|
github.com/go-ini/ini v1.38.1 h1:hbtfM8emWUVo9GnXSloXYyFbXxZ+tG6sbepSStoe1FY=
|
||||||
|
@ -30,6 +38,8 @@ github.com/kahing/goofys v0.19.0 h1:jcuffrnpvZq+LjXtRODo0pvNOglw32ClzBZ1XLShFnk=
|
||||||
github.com/kahing/goofys v0.19.0/go.mod h1:erC9E45nY5m8v6FE+tYIGRVjIC2N8viMlJrgrsXB2Q4=
|
github.com/kahing/goofys v0.19.0/go.mod h1:erC9E45nY5m8v6FE+tYIGRVjIC2N8viMlJrgrsXB2Q4=
|
||||||
github.com/kubernetes-csi/csi-test v1.1.0 h1:a7CfGqhGDs0h7AZt1f6LTIUzBazcRf6eBdTUBXB4xE4=
|
github.com/kubernetes-csi/csi-test v1.1.0 h1:a7CfGqhGDs0h7AZt1f6LTIUzBazcRf6eBdTUBXB4xE4=
|
||||||
github.com/kubernetes-csi/csi-test v1.1.0/go.mod h1:YxJ4UiuPWIhMBkxUKY5c267DyA0uDZ/MtAimhx/2TA0=
|
github.com/kubernetes-csi/csi-test v1.1.0/go.mod h1:YxJ4UiuPWIhMBkxUKY5c267DyA0uDZ/MtAimhx/2TA0=
|
||||||
|
github.com/kubernetes-csi/csi-test v1.1.1 h1:L4RPre34ICeoQW7ez4X5t0PnFKaKs8K5q0c1XOrvXEM=
|
||||||
|
github.com/kubernetes-csi/csi-test v1.1.1/go.mod h1:YxJ4UiuPWIhMBkxUKY5c267DyA0uDZ/MtAimhx/2TA0=
|
||||||
github.com/kubernetes-csi/drivers v0.0.0-20181207022357-c1e71bdcce6e h1:BkkRJIF329ps8digiMWthYzDPl9KB8PwkDwvVWDwM4A=
|
github.com/kubernetes-csi/drivers v0.0.0-20181207022357-c1e71bdcce6e h1:BkkRJIF329ps8digiMWthYzDPl9KB8PwkDwvVWDwM4A=
|
||||||
github.com/kubernetes-csi/drivers v0.0.0-20181207022357-c1e71bdcce6e/go.mod h1:V6rHbbSLCZGaQoIZ8MkyDtoXtcKXZM0F7N3bkloDCOY=
|
github.com/kubernetes-csi/drivers v0.0.0-20181207022357-c1e71bdcce6e/go.mod h1:V6rHbbSLCZGaQoIZ8MkyDtoXtcKXZM0F7N3bkloDCOY=
|
||||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
|
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
|
||||||
|
|
|
@ -29,12 +29,11 @@ import (
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
"github.com/container-storage-interface/spec/lib/go/csi"
|
"github.com/container-storage-interface/spec/lib/go/csi"
|
||||||
"github.com/kubernetes-csi/drivers/pkg/csi-common"
|
csicommon "github.com/kubernetes-csi/drivers/pkg/csi-common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type controllerServer struct {
|
type controllerServer struct {
|
||||||
*csicommon.DefaultControllerServer
|
*csicommon.DefaultControllerServer
|
||||||
*s3
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {
|
func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {
|
||||||
|
@ -59,13 +58,17 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
|
||||||
|
|
||||||
glog.V(4).Infof("Got a request to create volume %s", volumeID)
|
glog.V(4).Infof("Got a request to create volume %s", volumeID)
|
||||||
|
|
||||||
exists, err := cs.s3.client.bucketExists(volumeID)
|
s3, err := newS3ClientFromSecrets(req.GetSecrets())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to initialize S3 client: %s", err)
|
||||||
|
}
|
||||||
|
exists, err := s3.bucketExists(volumeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to check if bucket %s exists: %v", volumeID, err)
|
return nil, fmt.Errorf("failed to check if bucket %s exists: %v", volumeID, err)
|
||||||
}
|
}
|
||||||
if exists {
|
if exists {
|
||||||
var b *bucket
|
var b *bucket
|
||||||
b, err = cs.s3.client.getBucket(volumeID)
|
b, err = s3.getBucket(volumeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get bucket metadata of bucket %s: %v", volumeID, err)
|
return nil, fmt.Errorf("failed to get bucket metadata of bucket %s: %v", volumeID, err)
|
||||||
}
|
}
|
||||||
|
@ -74,10 +77,10 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
|
||||||
return nil, status.Error(codes.AlreadyExists, fmt.Sprintf("Volume with the same name: %s but with smaller size already exist", volumeID))
|
return nil, status.Error(codes.AlreadyExists, fmt.Sprintf("Volume with the same name: %s but with smaller size already exist", volumeID))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err = cs.s3.client.createBucket(volumeID); err != nil {
|
if err = s3.createBucket(volumeID); err != nil {
|
||||||
return nil, fmt.Errorf("failed to create volume %s: %v", volumeID, err)
|
return nil, fmt.Errorf("failed to create volume %s: %v", volumeID, err)
|
||||||
}
|
}
|
||||||
if err = cs.s3.client.createPrefix(volumeID, fsPrefix); err != nil {
|
if err = s3.createPrefix(volumeID, fsPrefix); err != nil {
|
||||||
return nil, fmt.Errorf("failed to create prefix %s: %v", fsPrefix, err)
|
return nil, fmt.Errorf("failed to create prefix %s: %v", fsPrefix, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,7 +90,7 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
|
||||||
CapacityBytes: capacityBytes,
|
CapacityBytes: capacityBytes,
|
||||||
FSPath: fsPrefix,
|
FSPath: fsPrefix,
|
||||||
}
|
}
|
||||||
if err := cs.s3.client.setBucket(b); err != nil {
|
if err := s3.setBucket(b); err != nil {
|
||||||
return nil, fmt.Errorf("Error setting bucket metadata: %v", err)
|
return nil, fmt.Errorf("Error setting bucket metadata: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,12 +121,16 @@ func (cs *controllerServer) DeleteVolume(ctx context.Context, req *csi.DeleteVol
|
||||||
}
|
}
|
||||||
glog.V(4).Infof("Deleting volume %s", volumeID)
|
glog.V(4).Infof("Deleting volume %s", volumeID)
|
||||||
|
|
||||||
exists, err := cs.s3.client.bucketExists(volumeID)
|
s3, err := newS3ClientFromSecrets(req.GetSecrets())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to initialize S3 client: %s", err)
|
||||||
|
}
|
||||||
|
exists, err := s3.bucketExists(volumeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if exists {
|
if exists {
|
||||||
if err := cs.s3.client.removeBucket(volumeID); err != nil {
|
if err := s3.removeBucket(volumeID); err != nil {
|
||||||
glog.V(3).Infof("Failed to remove volume: %v", err)
|
glog.V(3).Infof("Failed to remove volume: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -144,7 +151,11 @@ func (cs *controllerServer) ValidateVolumeCapabilities(ctx context.Context, req
|
||||||
return nil, status.Error(codes.InvalidArgument, "Volume capabilities missing in request")
|
return nil, status.Error(codes.InvalidArgument, "Volume capabilities missing in request")
|
||||||
}
|
}
|
||||||
|
|
||||||
exists, err := cs.s3.client.bucketExists(req.GetVolumeId())
|
s3, err := newS3ClientFromSecrets(req.GetSecrets())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to initialize S3 client: %s", err)
|
||||||
|
}
|
||||||
|
exists, err := s3.bucketExists(req.GetVolumeId())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,10 +17,9 @@ limitations under the License.
|
||||||
package s3
|
package s3
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kubernetes-csi/drivers/pkg/csi-common"
|
csicommon "github.com/kubernetes-csi/drivers/pkg/csi-common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type identityServer struct {
|
type identityServer struct {
|
||||||
*csicommon.DefaultIdentityServer
|
*csicommon.DefaultIdentityServer
|
||||||
*s3
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@ type Mounter interface {
|
||||||
Stage(stagePath string) error
|
Stage(stagePath string) error
|
||||||
Unstage(stagePath string) error
|
Unstage(stagePath string) error
|
||||||
Mount(source string, target string) error
|
Mount(source string, target string) error
|
||||||
Unmount(target string) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -67,12 +66,12 @@ func fuseMount(path string, command string, args []string) error {
|
||||||
return waitForMount(path, 10*time.Second)
|
return waitForMount(path, 10*time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fuseUnmount(path string, command string) error {
|
func fuseUnmount(path string) error {
|
||||||
if err := mount.New("").Unmount(path); err != nil {
|
if err := mount.New("").Unmount(path); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// as fuse quits immediately, we will try to wait until the process is done
|
// as fuse quits immediately, we will try to wait until the process is done
|
||||||
process, err := findFuseMountProcess(path, command)
|
process, err := findFuseMountProcess(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("Error getting PID of fuse mount: %s", err)
|
glog.Errorf("Error getting PID of fuse mount: %s", err)
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -69,7 +69,3 @@ func (goofys *goofysMounter) Mount(source string, target string) error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (goofys *goofysMounter) Unmount(targetPath string) error {
|
|
||||||
return fuseUnmount(targetPath, goofysCmd)
|
|
||||||
}
|
|
||||||
|
|
|
@ -54,7 +54,3 @@ func (rclone *rcloneMounter) Mount(source string, target string) error {
|
||||||
os.Setenv("AWS_SECRET_ACCESS_KEY", rclone.secretAccessKey)
|
os.Setenv("AWS_SECRET_ACCESS_KEY", rclone.secretAccessKey)
|
||||||
return fuseMount(target, rcloneCmd, args)
|
return fuseMount(target, rcloneCmd, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rclone *rcloneMounter) Unmount(target string) error {
|
|
||||||
return fuseUnmount(target, rcloneCmd)
|
|
||||||
}
|
|
||||||
|
|
|
@ -71,14 +71,14 @@ func (s3backer *s3backerMounter) Stage(stageTarget string) error {
|
||||||
// ensure 'file' device is formatted
|
// ensure 'file' device is formatted
|
||||||
err := formatFs(s3backerFsType, path.Join(stageTarget, s3backerDevice))
|
err := formatFs(s3backerFsType, path.Join(stageTarget, s3backerDevice))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fuseUnmount(stageTarget, s3backerCmd)
|
fuseUnmount(stageTarget)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s3backer *s3backerMounter) Unstage(stageTarget string) error {
|
func (s3backer *s3backerMounter) Unstage(stageTarget string) error {
|
||||||
// Unmount the s3backer fuse mount
|
// Unmount the s3backer fuse mount
|
||||||
return fuseUnmount(stageTarget, s3backerCmd)
|
return fuseUnmount(stageTarget)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s3backer *s3backerMounter) Mount(source string, target string) error {
|
func (s3backer *s3backerMounter) Mount(source string, target string) error {
|
||||||
|
@ -87,17 +87,12 @@ func (s3backer *s3backerMounter) Mount(source string, target string) error {
|
||||||
err := mount.New("").Mount(device, target, s3backerFsType, []string{})
|
err := mount.New("").Mount(device, target, s3backerFsType, []string{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// cleanup fuse mount
|
// cleanup fuse mount
|
||||||
fuseUnmount(target, s3backerCmd)
|
fuseUnmount(target)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s3backer *s3backerMounter) Unmount(targetPath string) error {
|
|
||||||
// Unmount the filesystem first
|
|
||||||
return mount.New("").Unmount(targetPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s3backer *s3backerMounter) mountInit(path string) error {
|
func (s3backer *s3backerMounter) mountInit(path string) error {
|
||||||
args := []string{
|
args := []string{
|
||||||
fmt.Sprintf("--blockSize=%s", s3backerBlockSize),
|
fmt.Sprintf("--blockSize=%s", s3backerBlockSize),
|
||||||
|
|
|
@ -51,10 +51,6 @@ func (s3fs *s3fsMounter) Mount(source string, target string) error {
|
||||||
return fuseMount(target, s3fsCmd, args)
|
return fuseMount(target, s3fsCmd, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s3fs *s3fsMounter) Unmount(target string) error {
|
|
||||||
return fuseUnmount(target, s3fsCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func writes3fsPass(pwFileContent string) error {
|
func writes3fsPass(pwFileContent string) error {
|
||||||
pwFileName := fmt.Sprintf("%s/.passwd-s3fs", os.Getenv("HOME"))
|
pwFileName := fmt.Sprintf("%s/.passwd-s3fs", os.Getenv("HOME"))
|
||||||
pwFile, err := os.OpenFile(pwFileName, os.O_RDWR|os.O_CREATE, 0600)
|
pwFile, err := os.OpenFile(pwFileName, os.O_RDWR|os.O_CREATE, 0600)
|
||||||
|
|
|
@ -93,10 +93,6 @@ func (s3ql *s3qlMounter) Mount(source string, target string) error {
|
||||||
return fuseMount(target, s3qlCmdMount, append(args, s3ql.options...))
|
return fuseMount(target, s3qlCmdMount, append(args, s3ql.options...))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s3ql *s3qlMounter) Unmount(target string) error {
|
|
||||||
return fuseUnmount(target, s3qlCmdMount)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s3ql *s3qlMounter) writeConfig() error {
|
func (s3ql *s3qlMounter) writeConfig() error {
|
||||||
s3qlIni := ini.Empty()
|
s3qlIni := ini.Empty()
|
||||||
section, err := s3qlIni.NewSection("s3ql")
|
section, err := s3qlIni.NewSection("s3ql")
|
||||||
|
|
|
@ -17,6 +17,7 @@ limitations under the License.
|
||||||
package s3
|
package s3
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
@ -27,12 +28,11 @@ import (
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
"k8s.io/kubernetes/pkg/util/mount"
|
"k8s.io/kubernetes/pkg/util/mount"
|
||||||
|
|
||||||
"github.com/kubernetes-csi/drivers/pkg/csi-common"
|
csicommon "github.com/kubernetes-csi/drivers/pkg/csi-common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type nodeServer struct {
|
type nodeServer struct {
|
||||||
*csicommon.DefaultNodeServer
|
*csicommon.DefaultNodeServer
|
||||||
*s3
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) {
|
func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) {
|
||||||
|
@ -76,12 +76,16 @@ func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis
|
||||||
glog.V(4).Infof("target %v\ndevice %v\nreadonly %v\nvolumeId %v\nattributes %v\nmountflags %v\n",
|
glog.V(4).Infof("target %v\ndevice %v\nreadonly %v\nvolumeId %v\nattributes %v\nmountflags %v\n",
|
||||||
targetPath, deviceID, readOnly, volumeID, attrib, mountFlags)
|
targetPath, deviceID, readOnly, volumeID, attrib, mountFlags)
|
||||||
|
|
||||||
b, err := ns.s3.client.getBucket(volumeID)
|
s3, err := newS3ClientFromSecrets(req.GetSecrets())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to initialize S3 client: %s", err)
|
||||||
|
}
|
||||||
|
b, err := s3.getBucket(volumeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
mounter, err := newMounter(b, ns.s3.cfg)
|
mounter, err := newMounter(b, s3.cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -106,15 +110,7 @@ func (ns *nodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpu
|
||||||
return nil, status.Error(codes.InvalidArgument, "Target path missing in request")
|
return nil, status.Error(codes.InvalidArgument, "Target path missing in request")
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := ns.s3.client.getBucket(volumeID)
|
if err := fuseUnmount(targetPath); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mounter, err := newMounter(b, ns.s3.cfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := mounter.Unmount(targetPath); err != nil {
|
|
||||||
return nil, status.Error(codes.Internal, err.Error())
|
return nil, status.Error(codes.Internal, err.Error())
|
||||||
}
|
}
|
||||||
glog.V(4).Infof("s3: bucket %s has been unmounted.", volumeID)
|
glog.V(4).Infof("s3: bucket %s has been unmounted.", volumeID)
|
||||||
|
@ -146,12 +142,15 @@ func (ns *nodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStageVol
|
||||||
if !notMnt {
|
if !notMnt {
|
||||||
return &csi.NodeStageVolumeResponse{}, nil
|
return &csi.NodeStageVolumeResponse{}, nil
|
||||||
}
|
}
|
||||||
b, err := ns.s3.client.getBucket(volumeID)
|
s3, err := newS3ClientFromSecrets(req.GetSecrets())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to initialize S3 client: %s", err)
|
||||||
|
}
|
||||||
|
b, err := s3.getBucket(volumeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
mounter, err := newMounter(b, s3.cfg)
|
||||||
mounter, err := newMounter(b, ns.s3.cfg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -174,18 +173,6 @@ func (ns *nodeServer) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstag
|
||||||
return nil, status.Error(codes.InvalidArgument, "Target path missing in request")
|
return nil, status.Error(codes.InvalidArgument, "Target path missing in request")
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := ns.s3.client.getBucket(volumeID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mounter, err := newMounter(b, ns.s3.cfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := mounter.Unstage(stagingTargetPath); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &csi.NodeUnstageVolumeResponse{}, nil
|
return &csi.NodeUnstageVolumeResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,18 @@ func newS3Client(cfg *Config) (*s3Client, error) {
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func newS3ClientFromSecrets(secrets map[string]string) (*s3Client, error) {
|
||||||
|
return newS3Client(&Config{
|
||||||
|
AccessKeyID: secrets["accessKeyID"],
|
||||||
|
SecretAccessKey: secrets["secretAccessKey"],
|
||||||
|
Region: secrets["region"],
|
||||||
|
Endpoint: secrets["endpoint"],
|
||||||
|
EncryptionKey: secrets["encryptionKey"],
|
||||||
|
// Mounter is set in the volume preferences, not secrets
|
||||||
|
Mounter: "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (client *s3Client) bucketExists(bucketName string) (bool, error) {
|
func (client *s3Client) bucketExists(bucketName string) (bool, error) {
|
||||||
return client.minio.BucketExists(bucketName)
|
return client.minio.BucketExists(bucketName)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,14 +20,12 @@ import (
|
||||||
"github.com/container-storage-interface/spec/lib/go/csi"
|
"github.com/container-storage-interface/spec/lib/go/csi"
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
|
|
||||||
"github.com/kubernetes-csi/drivers/pkg/csi-common"
|
csicommon "github.com/kubernetes-csi/drivers/pkg/csi-common"
|
||||||
)
|
)
|
||||||
|
|
||||||
type s3 struct {
|
type s3 struct {
|
||||||
driver *csicommon.CSIDriver
|
driver *csicommon.CSIDriver
|
||||||
client *s3Client
|
|
||||||
endpoint string
|
endpoint string
|
||||||
cfg *Config
|
|
||||||
|
|
||||||
ids *identityServer
|
ids *identityServer
|
||||||
ns *nodeServer
|
ns *nodeServer
|
||||||
|
@ -47,22 +45,15 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewS3 initializes the driver
|
// NewS3 initializes the driver
|
||||||
func NewS3(nodeID string, endpoint string, cfg *Config) (*s3, error) {
|
func NewS3(nodeID string, endpoint string) (*s3, error) {
|
||||||
driver := csicommon.NewCSIDriver(driverName, vendorVersion, nodeID)
|
driver := csicommon.NewCSIDriver(driverName, vendorVersion, nodeID)
|
||||||
if driver == nil {
|
if driver == nil {
|
||||||
glog.Fatalln("Failed to initialize CSI Driver.")
|
glog.Fatalln("Failed to initialize CSI Driver.")
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := newS3Client(cfg)
|
|
||||||
if err != nil {
|
|
||||||
glog.V(3).Infof("Failed to create s3 client: %v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
s3Driver := &s3{
|
s3Driver := &s3{
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
driver: driver,
|
driver: driver,
|
||||||
client: client,
|
|
||||||
cfg: cfg,
|
|
||||||
}
|
}
|
||||||
return s3Driver, nil
|
return s3Driver, nil
|
||||||
}
|
}
|
||||||
|
@ -70,21 +61,18 @@ func NewS3(nodeID string, endpoint string, cfg *Config) (*s3, error) {
|
||||||
func (s3 *s3) newIdentityServer(d *csicommon.CSIDriver) *identityServer {
|
func (s3 *s3) newIdentityServer(d *csicommon.CSIDriver) *identityServer {
|
||||||
return &identityServer{
|
return &identityServer{
|
||||||
DefaultIdentityServer: csicommon.NewDefaultIdentityServer(d),
|
DefaultIdentityServer: csicommon.NewDefaultIdentityServer(d),
|
||||||
s3: s3,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s3 *s3) newControllerServer(d *csicommon.CSIDriver) *controllerServer {
|
func (s3 *s3) newControllerServer(d *csicommon.CSIDriver) *controllerServer {
|
||||||
return &controllerServer{
|
return &controllerServer{
|
||||||
DefaultControllerServer: csicommon.NewDefaultControllerServer(d),
|
DefaultControllerServer: csicommon.NewDefaultControllerServer(d),
|
||||||
s3: s3,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s3 *s3) newNodeServer(d *csicommon.CSIDriver) *nodeServer {
|
func (s3 *s3) newNodeServer(d *csicommon.CSIDriver) *nodeServer {
|
||||||
return &nodeServer{
|
return &nodeServer{
|
||||||
DefaultNodeServer: csicommon.NewDefaultNodeServer(d),
|
DefaultNodeServer: csicommon.NewDefaultNodeServer(d),
|
||||||
s3: s3,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,16 +24,10 @@ var _ = Describe("S3Driver", func() {
|
||||||
Context("goofys", func() {
|
Context("goofys", func() {
|
||||||
socket := "/tmp/csi-goofys.sock"
|
socket := "/tmp/csi-goofys.sock"
|
||||||
csiEndpoint := "unix://" + socket
|
csiEndpoint := "unix://" + socket
|
||||||
cfg := &s3.Config{
|
|
||||||
AccessKeyID: "FJDSJ",
|
|
||||||
SecretAccessKey: "DSG643HGDS",
|
|
||||||
Endpoint: "http://127.0.0.1:9000",
|
|
||||||
Mounter: "goofys",
|
|
||||||
}
|
|
||||||
if err := os.Remove(socket); err != nil && !os.IsNotExist(err) {
|
if err := os.Remove(socket); err != nil && !os.IsNotExist(err) {
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
}
|
}
|
||||||
driver, err := s3.NewS3("test-node", csiEndpoint, cfg)
|
driver, err := s3.NewS3("test-node", csiEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -44,6 +38,10 @@ var _ = Describe("S3Driver", func() {
|
||||||
TargetPath: mntDir,
|
TargetPath: mntDir,
|
||||||
StagingPath: stagingDir,
|
StagingPath: stagingDir,
|
||||||
Address: csiEndpoint,
|
Address: csiEndpoint,
|
||||||
|
SecretsFile: "../../test/secret.yaml",
|
||||||
|
TestVolumeParameters: map[string]string{
|
||||||
|
"mounter": "goofys",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
sanity.GinkgoTest(sanityCfg)
|
sanity.GinkgoTest(sanityCfg)
|
||||||
})
|
})
|
||||||
|
@ -52,16 +50,10 @@ var _ = Describe("S3Driver", func() {
|
||||||
Context("s3fs", func() {
|
Context("s3fs", func() {
|
||||||
socket := "/tmp/csi-s3fs.sock"
|
socket := "/tmp/csi-s3fs.sock"
|
||||||
csiEndpoint := "unix://" + socket
|
csiEndpoint := "unix://" + socket
|
||||||
cfg := &s3.Config{
|
|
||||||
AccessKeyID: "FJDSJ",
|
|
||||||
SecretAccessKey: "DSG643HGDS",
|
|
||||||
Endpoint: "http://127.0.0.1:9000",
|
|
||||||
Mounter: "s3fs",
|
|
||||||
}
|
|
||||||
if err := os.Remove(socket); err != nil && !os.IsNotExist(err) {
|
if err := os.Remove(socket); err != nil && !os.IsNotExist(err) {
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
}
|
}
|
||||||
driver, err := s3.NewS3("test-node", csiEndpoint, cfg)
|
driver, err := s3.NewS3("test-node", csiEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -72,6 +64,10 @@ var _ = Describe("S3Driver", func() {
|
||||||
TargetPath: mntDir,
|
TargetPath: mntDir,
|
||||||
StagingPath: stagingDir,
|
StagingPath: stagingDir,
|
||||||
Address: csiEndpoint,
|
Address: csiEndpoint,
|
||||||
|
SecretsFile: "../../test/secret.yaml",
|
||||||
|
TestVolumeParameters: map[string]string{
|
||||||
|
"mounter": "s3fs",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
sanity.GinkgoTest(sanityCfg)
|
sanity.GinkgoTest(sanityCfg)
|
||||||
})
|
})
|
||||||
|
@ -81,16 +77,10 @@ var _ = Describe("S3Driver", func() {
|
||||||
socket := "/tmp/csi-s3ql.sock"
|
socket := "/tmp/csi-s3ql.sock"
|
||||||
csiEndpoint := "unix://" + socket
|
csiEndpoint := "unix://" + socket
|
||||||
|
|
||||||
cfg := &s3.Config{
|
|
||||||
AccessKeyID: "FJDSJ",
|
|
||||||
SecretAccessKey: "DSG643HGDS",
|
|
||||||
Endpoint: "http://127.0.0.1:9000",
|
|
||||||
Mounter: "s3ql",
|
|
||||||
}
|
|
||||||
if err := os.Remove(socket); err != nil && !os.IsNotExist(err) {
|
if err := os.Remove(socket); err != nil && !os.IsNotExist(err) {
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
}
|
}
|
||||||
driver, err := s3.NewS3("test-node", csiEndpoint, cfg)
|
driver, err := s3.NewS3("test-node", csiEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -103,6 +93,10 @@ var _ = Describe("S3Driver", func() {
|
||||||
TargetPath: mntDir,
|
TargetPath: mntDir,
|
||||||
StagingPath: stagingDir,
|
StagingPath: stagingDir,
|
||||||
Address: csiEndpoint,
|
Address: csiEndpoint,
|
||||||
|
SecretsFile: "../../test/secret.yaml",
|
||||||
|
TestVolumeParameters: map[string]string{
|
||||||
|
"mounter": "s3ql",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
sanity.GinkgoTest(sanityCfg)
|
sanity.GinkgoTest(sanityCfg)
|
||||||
})
|
})
|
||||||
|
@ -112,18 +106,12 @@ var _ = Describe("S3Driver", func() {
|
||||||
socket := "/tmp/csi-s3backer.sock"
|
socket := "/tmp/csi-s3backer.sock"
|
||||||
csiEndpoint := "unix://" + socket
|
csiEndpoint := "unix://" + socket
|
||||||
|
|
||||||
cfg := &s3.Config{
|
|
||||||
AccessKeyID: "FJDSJ",
|
|
||||||
SecretAccessKey: "DSG643HGDS",
|
|
||||||
Endpoint: "http://127.0.0.1:9000",
|
|
||||||
Mounter: "s3backer",
|
|
||||||
}
|
|
||||||
if err := os.Remove(socket); err != nil && !os.IsNotExist(err) {
|
if err := os.Remove(socket); err != nil && !os.IsNotExist(err) {
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
}
|
}
|
||||||
// Clear loop device so we cover the creation of it
|
// Clear loop device so we cover the creation of it
|
||||||
os.Remove(s3.S3backerLoopDevice)
|
os.Remove(s3.S3backerLoopDevice)
|
||||||
driver, err := s3.NewS3("test-node", csiEndpoint, cfg)
|
driver, err := s3.NewS3("test-node", csiEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -134,6 +122,10 @@ var _ = Describe("S3Driver", func() {
|
||||||
TargetPath: mntDir,
|
TargetPath: mntDir,
|
||||||
StagingPath: stagingDir,
|
StagingPath: stagingDir,
|
||||||
Address: csiEndpoint,
|
Address: csiEndpoint,
|
||||||
|
SecretsFile: "../../test/secret.yaml",
|
||||||
|
TestVolumeParameters: map[string]string{
|
||||||
|
"mounter": "s3backer",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
sanity.GinkgoTest(sanityCfg)
|
sanity.GinkgoTest(sanityCfg)
|
||||||
})
|
})
|
||||||
|
@ -143,16 +135,10 @@ var _ = Describe("S3Driver", func() {
|
||||||
socket := "/tmp/csi-rclone.sock"
|
socket := "/tmp/csi-rclone.sock"
|
||||||
csiEndpoint := "unix://" + socket
|
csiEndpoint := "unix://" + socket
|
||||||
|
|
||||||
cfg := &s3.Config{
|
|
||||||
AccessKeyID: "FJDSJ",
|
|
||||||
SecretAccessKey: "DSG643HGDS",
|
|
||||||
Endpoint: "http://127.0.0.1:9000",
|
|
||||||
Mounter: "rclone",
|
|
||||||
}
|
|
||||||
if err := os.Remove(socket); err != nil && !os.IsNotExist(err) {
|
if err := os.Remove(socket); err != nil && !os.IsNotExist(err) {
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
}
|
}
|
||||||
driver, err := s3.NewS3("test-node", csiEndpoint, cfg)
|
driver, err := s3.NewS3("test-node", csiEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -163,6 +149,10 @@ var _ = Describe("S3Driver", func() {
|
||||||
TargetPath: mntDir,
|
TargetPath: mntDir,
|
||||||
StagingPath: stagingDir,
|
StagingPath: stagingDir,
|
||||||
Address: csiEndpoint,
|
Address: csiEndpoint,
|
||||||
|
SecretsFile: "../../test/secret.yaml",
|
||||||
|
TestVolumeParameters: map[string]string{
|
||||||
|
"mounter": "rclone",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
sanity.GinkgoTest(sanityCfg)
|
sanity.GinkgoTest(sanityCfg)
|
||||||
})
|
})
|
||||||
|
|
|
@ -60,13 +60,12 @@ func waitForMount(path string, timeout time.Duration) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func findFuseMountProcess(path string, name string) (*os.Process, error) {
|
func findFuseMountProcess(path string) (*os.Process, error) {
|
||||||
processes, err := ps.Processes()
|
processes, err := ps.Processes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, p := range processes {
|
for _, p := range processes {
|
||||||
if strings.Contains(p.Executable(), name) {
|
|
||||||
cmdLine, err := getCmdLine(p.Pid())
|
cmdLine, err := getCmdLine(p.Pid())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("Unable to get cmdline of PID %v: %s", p.Pid(), err)
|
glog.Errorf("Unable to get cmdline of PID %v: %s", p.Pid(), err)
|
||||||
|
@ -77,7 +76,6 @@ func findFuseMountProcess(path string, name string) (*os.Process, error) {
|
||||||
return os.FindProcess(p.Pid())
|
return os.FindProcess(p.Pid())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
30
test/secret.yaml
Normal file
30
test/secret.yaml
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
CreateVolumeSecret:
|
||||||
|
accessKeyID: FJDSJ
|
||||||
|
secretAccessKey: DSG643HGDS
|
||||||
|
endpoint: http://127.0.0.1:9000
|
||||||
|
region: ""
|
||||||
|
encryptionKey: ""
|
||||||
|
DeleteVolumeSecret:
|
||||||
|
accessKeyID: FJDSJ
|
||||||
|
secretAccessKey: DSG643HGDS
|
||||||
|
endpoint: http://127.0.0.1:9000
|
||||||
|
region: ""
|
||||||
|
encryptionKey: ""
|
||||||
|
NodeStageVolumeSecret:
|
||||||
|
accessKeyID: FJDSJ
|
||||||
|
secretAccessKey: DSG643HGDS
|
||||||
|
endpoint: http://127.0.0.1:9000
|
||||||
|
region: ""
|
||||||
|
encryptionKey: ""
|
||||||
|
NodePublishVolumeSecret:
|
||||||
|
accessKeyID: FJDSJ
|
||||||
|
secretAccessKey: DSG643HGDS
|
||||||
|
endpoint: http://127.0.0.1:9000
|
||||||
|
region: ""
|
||||||
|
encryptionKey: ""
|
||||||
|
ControllerValidateVolumeCapabilitiesSecret:
|
||||||
|
accessKeyID: FJDSJ
|
||||||
|
secretAccessKey: DSG643HGDS
|
||||||
|
endpoint: http://127.0.0.1:9000
|
||||||
|
region: ""
|
||||||
|
encryptionKey: ""
|
|
@ -5,4 +5,4 @@ export MINIO_SECRET_KEY=DSG643HGDS
|
||||||
mkdir -p /tmp/minio
|
mkdir -p /tmp/minio
|
||||||
minio server --quiet /tmp/minio &>/dev/null &
|
minio server --quiet /tmp/minio &>/dev/null &
|
||||||
sleep 5
|
sleep 5
|
||||||
go test github.com/ctrox/csi-s3/pkg/s3 -cover
|
go test github.com/ctrox/csi-s3/pkg/s3 -cover -coverprofile=coverage.out
|
Loading…
Reference in a new issue