fix: minio prefix
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
package miniostorage
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Endpoint string
|
||||
AccessKeyID string
|
||||
SecretKey string
|
||||
Token string
|
||||
BucketName string
|
||||
Prefix string
|
||||
Region string
|
||||
Secure bool
|
||||
}
|
||||
|
||||
// NewConfig парсирует строку подключения
|
||||
func NewConfig(connString string) (*Config, error) {
|
||||
u, err := url.Parse(connString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
queries := u.Query()
|
||||
var accessKeyID, secretKey string
|
||||
if u.User != nil {
|
||||
accessKeyID = u.User.Username()
|
||||
if s, ok := u.User.Password(); ok {
|
||||
secretKey = s
|
||||
}
|
||||
}
|
||||
token := queries.Get("token")
|
||||
|
||||
cfg := &Config{}
|
||||
cfg.Endpoint = u.Host
|
||||
cfg.AccessKeyID = accessKeyID
|
||||
cfg.SecretKey = secretKey
|
||||
cfg.Token = token
|
||||
if queries.Has("secure") {
|
||||
secure, err := strconv.ParseBool(queries.Get("secure"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.Secure = secure
|
||||
}
|
||||
if queries.Has("region") {
|
||||
cfg.Region = queries.Get("region")
|
||||
} else {
|
||||
cfg.Region = "us-east-1"
|
||||
}
|
||||
|
||||
parts := strings.SplitN(strings.Trim(u.Path, "/"), "/", 2)
|
||||
cfg.BucketName = parts[0]
|
||||
if len(parts) > 1 {
|
||||
cfg.Prefix = parts[1]
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func ConnString(cfg Config) string {
|
||||
params := url.Values{}
|
||||
if cfg.Region != "" {
|
||||
params.Add("region", cfg.Region)
|
||||
}
|
||||
if cfg.Secure {
|
||||
params.Add("secure", "1")
|
||||
}
|
||||
u := url.URL{
|
||||
Scheme: "minio",
|
||||
Host: cfg.Endpoint,
|
||||
User: url.UserPassword(cfg.AccessKeyID, cfg.SecretKey),
|
||||
Path: path.Join("/", cfg.BucketName, cfg.Prefix),
|
||||
RawQuery: params.Encode(),
|
||||
}
|
||||
return u.String()
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package miniostorage
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewConfig(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
connString string
|
||||
expected *Config
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
name: "Test 1",
|
||||
connString: "minio://e5JXrHpr093RVk1s9IcE:QJSEqip9gX2b3041deUR1K5BCJjSDubYwy48K3SQ@play.min.io/bucket",
|
||||
expected: &Config{
|
||||
Endpoint: "play.min.io",
|
||||
AccessKeyID: "e5JXrHpr093RVk1s9IcE",
|
||||
SecretKey: "QJSEqip9gX2b3041deUR1K5BCJjSDubYwy48K3SQ",
|
||||
BucketName: "bucket",
|
||||
Prefix: "",
|
||||
Region: "us-east-1",
|
||||
Secure: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test 2",
|
||||
connString: "minio://e5JXrHpr093RVk1s9IcE:QJSEqip9gX2b3041deUR1K5BCJjSDubYwy48K3SQ@play.min.io:9443/bucket?secure=1®ion=us-west-1",
|
||||
expected: &Config{
|
||||
Endpoint: "play.min.io:9443",
|
||||
AccessKeyID: "e5JXrHpr093RVk1s9IcE",
|
||||
SecretKey: "QJSEqip9gX2b3041deUR1K5BCJjSDubYwy48K3SQ",
|
||||
BucketName: "bucket",
|
||||
Prefix: "",
|
||||
Region: "us-west-1",
|
||||
Secure: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test 3",
|
||||
connString: "minio://e5JXrHpr093RVk1s9IcE:QJSEqip9gX2b3041deUR1K5BCJjSDubYwy48K3SQ@play.min.io:9443/bucket/prefix?secure=1",
|
||||
expected: &Config{
|
||||
Endpoint: "play.min.io:9443",
|
||||
AccessKeyID: "e5JXrHpr093RVk1s9IcE",
|
||||
SecretKey: "QJSEqip9gX2b3041deUR1K5BCJjSDubYwy48K3SQ",
|
||||
BucketName: "bucket",
|
||||
Prefix: "prefix",
|
||||
Region: "us-east-1",
|
||||
Secure: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cfg, err := NewConfig(tc.connString)
|
||||
if err != nil {
|
||||
if err != tc.expectedErr {
|
||||
t.Fatalf("Expected %q, got %q", tc.expectedErr, err)
|
||||
}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(cfg, tc.expected) {
|
||||
t.Errorf("expected %v, got %v", cfg, tc.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnString(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
cfg Config
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "Test 1",
|
||||
cfg: Config{
|
||||
Endpoint: "play.min.io",
|
||||
AccessKeyID: "e5JXrHpr093RVk1s9IcE",
|
||||
SecretKey: "QJSEqip9gX2b3041deUR1K5BCJjSDubYwy48K3SQ",
|
||||
BucketName: "bucket",
|
||||
Prefix: "",
|
||||
Region: "us-east-1",
|
||||
Secure: false,
|
||||
},
|
||||
expected: "minio://e5JXrHpr093RVk1s9IcE:QJSEqip9gX2b3041deUR1K5BCJjSDubYwy48K3SQ@play.min.io/bucket?region=us-east-1",
|
||||
},
|
||||
{
|
||||
name: "Test 2",
|
||||
cfg: Config{
|
||||
Endpoint: "play.min.io:9443",
|
||||
AccessKeyID: "e5JXrHpr093RVk1s9IcE",
|
||||
SecretKey: "QJSEqip9gX2b3041deUR1K5BCJjSDubYwy48K3SQ",
|
||||
BucketName: "bucket",
|
||||
Prefix: "prefix",
|
||||
Region: "us-east-1",
|
||||
Secure: true,
|
||||
},
|
||||
expected: "minio://e5JXrHpr093RVk1s9IcE:QJSEqip9gX2b3041deUR1K5BCJjSDubYwy48K3SQ@play.min.io:9443/bucket/prefix?region=us-east-1&secure=1",
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
str := ConnString(tc.cfg)
|
||||
if str != tc.expected {
|
||||
t.Errorf("expected %q, got %q", str, tc.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package miniostorage
|
||||
|
||||
import "net/url"
|
||||
|
||||
// getUserPassword возвращает имя пользователя и пароль.
|
||||
func getUserPassword(u *url.URL) (string, string) {
|
||||
var user, password string
|
||||
if u.User != nil {
|
||||
user = u.User.Username()
|
||||
if p, ok := u.User.Password(); ok {
|
||||
password = p
|
||||
}
|
||||
}
|
||||
return user, password
|
||||
}
|
||||
@@ -4,14 +4,12 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"git.company.lan/gopkg/filestore/remote"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
|
||||
"git.company.lan/gopkg/filestore/remote"
|
||||
)
|
||||
|
||||
// Убеждаемся в том, что мы всегда реализуем интерфейс remote.Storage.
|
||||
@@ -24,86 +22,85 @@ func init() {
|
||||
type MinioStorage struct {
|
||||
ctx context.Context
|
||||
client *minio.Client
|
||||
bucket string
|
||||
cfg *Config
|
||||
}
|
||||
|
||||
func (s *MinioStorage) NewStorage(ctx context.Context, connString string) (remote.Storage, error) {
|
||||
u, err := url.Parse(connString)
|
||||
cfg, err := NewConfig(connString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
queries := u.Query()
|
||||
username, password := getUserPassword(u)
|
||||
token := queries.Get("token")
|
||||
|
||||
opts := &minio.Options{
|
||||
Creds: credentials.NewStaticV4(username, password, token),
|
||||
Region: "us-east-1",
|
||||
}
|
||||
if queries.Has("secure") {
|
||||
secure, err := strconv.ParseBool(queries.Get("secure"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts.Secure = secure
|
||||
}
|
||||
if queries.Has("region") {
|
||||
opts.Region = queries.Get("region")
|
||||
Creds: credentials.NewStaticV4(cfg.AccessKeyID, cfg.SecretKey, cfg.Token),
|
||||
Region: cfg.Region,
|
||||
Secure: cfg.Secure,
|
||||
}
|
||||
|
||||
client, err := minio.New(u.Host, opts)
|
||||
client, err := minio.New(cfg.Endpoint, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.ctx = ctx
|
||||
s.client = client
|
||||
s.bucket = u.Path[1:]
|
||||
s.cfg = cfg
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *MinioStorage) Create(path string, opts ...remote.Option) (io.WriteCloser, error) {
|
||||
return newMinioWriter(s.ctx, s.client, s.bucket, path, opts...), nil
|
||||
func (s *MinioStorage) Create(name string, opts ...remote.Option) (io.WriteCloser, error) {
|
||||
name = path.Join(s.cfg.Prefix, name)
|
||||
|
||||
return newMinioWriter(s.ctx, s.client, s.cfg.BucketName, name, opts...), nil
|
||||
}
|
||||
|
||||
func (s *MinioStorage) Open(name string) (http.File, error) {
|
||||
obj, err := s.client.GetObject(s.ctx, s.bucket, name, minio.GetObjectOptions{})
|
||||
name = path.Join(s.cfg.Prefix, name)
|
||||
|
||||
obj, err := s.client.GetObject(s.ctx, s.cfg.BucketName, name, minio.GetObjectOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &minioFileWrapper{Object: obj, name: name}, nil
|
||||
}
|
||||
|
||||
func (s *MinioStorage) Remove(path string) error {
|
||||
return s.client.RemoveObject(s.ctx, s.bucket, path, minio.RemoveObjectOptions{})
|
||||
func (s *MinioStorage) Remove(name string) error {
|
||||
name = path.Join(s.cfg.Prefix, name)
|
||||
|
||||
return s.client.RemoveObject(s.ctx, s.cfg.BucketName, name, minio.RemoveObjectOptions{})
|
||||
}
|
||||
|
||||
func (s *MinioStorage) Stat(path string) (remote.FileInfo, error) {
|
||||
info, err := s.client.StatObject(s.ctx, s.bucket, path, minio.StatObjectOptions{})
|
||||
func (s *MinioStorage) Stat(name string) (remote.FileInfo, error) {
|
||||
name = path.Join(s.cfg.Prefix, name)
|
||||
|
||||
info, err := s.client.StatObject(s.ctx, s.cfg.BucketName, name, minio.StatObjectOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newMinioFileInfo(info), nil
|
||||
}
|
||||
|
||||
func (s *MinioStorage) Exists(path string) (bool, error) {
|
||||
func (s *MinioStorage) Exists(name string) (bool, error) {
|
||||
name = path.Join(s.cfg.Prefix, name)
|
||||
|
||||
// Сначала проверяем, является ли путь файлом
|
||||
if ok, err := s.IsFile(path); err == nil && ok {
|
||||
if ok, err := s.IsFile(name); err == nil && ok {
|
||||
return true, nil
|
||||
}
|
||||
// Если не файл, то проверяем, является ли путь каталогом
|
||||
return s.IsDir(path)
|
||||
return s.IsDir(name)
|
||||
}
|
||||
|
||||
func (s *MinioStorage) IsDir(path string) (bool, error) {
|
||||
func (s *MinioStorage) IsDir(name string) (bool, error) {
|
||||
name = path.Join(s.cfg.Prefix, name)
|
||||
|
||||
options := minio.ListObjectsOptions{
|
||||
Prefix: strings.TrimRight(path, "/") + "/",
|
||||
Prefix: strings.TrimRight(name, "/") + "/",
|
||||
Recursive: false,
|
||||
MaxKeys: 1,
|
||||
}
|
||||
objectChan := s.client.ListObjects(s.ctx, s.bucket, options)
|
||||
objectChan := s.client.ListObjects(s.ctx, s.cfg.BucketName, options)
|
||||
object, ok := <-objectChan
|
||||
if !ok {
|
||||
return false, nil
|
||||
@@ -114,8 +111,10 @@ func (s *MinioStorage) IsDir(path string) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (s *MinioStorage) IsFile(path string) (bool, error) {
|
||||
_, err := s.client.StatObject(s.ctx, s.bucket, path, minio.StatObjectOptions{})
|
||||
func (s *MinioStorage) IsFile(name string) (bool, error) {
|
||||
name = path.Join(s.cfg.Prefix, name)
|
||||
|
||||
_, err := s.client.StatObject(s.ctx, s.cfg.BucketName, name, minio.StatObjectOptions{})
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
+6
-6
@@ -24,22 +24,22 @@ type Storage interface {
|
||||
Open(name string) (http.File, error)
|
||||
|
||||
// Create создаёт файл и возвращает io.WriteCloser.
|
||||
Create(path string, opts ...Option) (io.WriteCloser, error)
|
||||
Create(name string, opts ...Option) (io.WriteCloser, error)
|
||||
|
||||
// Remove удаляет файл.
|
||||
Remove(path string) error
|
||||
Remove(name string) error
|
||||
|
||||
// Stat получает информацию о файле/каталоге.
|
||||
Stat(path string) (FileInfo, error)
|
||||
Stat(name string) (FileInfo, error)
|
||||
|
||||
// Exists определяет, существует ли файл или каталог.
|
||||
Exists(path string) (bool, error)
|
||||
Exists(name string) (bool, error)
|
||||
|
||||
// IsDir определяет, является ли путь каталогом.
|
||||
IsDir(path string) (bool, error)
|
||||
IsDir(name string) (bool, error)
|
||||
|
||||
// IsFile определяет, является ли путь файлом.
|
||||
IsFile(path string) (bool, error)
|
||||
IsFile(name string) (bool, error)
|
||||
|
||||
Uploader() Uploader
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user