@ -13,6 +13,8 @@ import (
"strings"
"strings"
"time"
"time"
"code.gitea.io/gitea/modules/log"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"github.com/minio/minio-go/v7/pkg/credentials"
)
)
@ -58,20 +60,42 @@ type MinioStorage struct {
basePath string
basePath string
}
}
func convertMinioErr ( err error ) error {
if err == nil {
return nil
}
errResp , ok := err . ( minio . ErrorResponse )
if ! ok {
return err
}
// Convert two responses to standard analogues
switch errResp . Code {
case "NoSuchKey" :
return os . ErrNotExist
case "AccessDenied" :
return os . ErrPermission
}
return err
}
// NewMinioStorage returns a minio storage
// NewMinioStorage returns a minio storage
func NewMinioStorage ( ctx context . Context , cfg interface { } ) ( ObjectStorage , error ) {
func NewMinioStorage ( ctx context . Context , cfg interface { } ) ( ObjectStorage , error ) {
configInterface , err := toConfig ( MinioStorageConfig { } , cfg )
configInterface , err := toConfig ( MinioStorageConfig { } , cfg )
if err != nil {
if err != nil {
return nil , err
return nil , convertMinioErr ( err )
}
}
config := configInterface . ( MinioStorageConfig )
config := configInterface . ( MinioStorageConfig )
log . Info ( "Creating Minio storage at %s:%s with base path %s" , config . Endpoint , config . Bucket , config . BasePath )
minioClient , err := minio . New ( config . Endpoint , & minio . Options {
minioClient , err := minio . New ( config . Endpoint , & minio . Options {
Creds : credentials . NewStaticV4 ( config . AccessKeyID , config . SecretAccessKey , "" ) ,
Creds : credentials . NewStaticV4 ( config . AccessKeyID , config . SecretAccessKey , "" ) ,
Secure : config . UseSSL ,
Secure : config . UseSSL ,
} )
} )
if err != nil {
if err != nil {
return nil , err
return nil , convertMinioErr ( err )
}
}
if err := minioClient . MakeBucket ( ctx , config . Bucket , minio . MakeBucketOptions {
if err := minioClient . MakeBucket ( ctx , config . Bucket , minio . MakeBucketOptions {
@ -80,7 +104,7 @@ func NewMinioStorage(ctx context.Context, cfg interface{}) (ObjectStorage, error
// Check to see if we already own this bucket (which happens if you run this twice)
// Check to see if we already own this bucket (which happens if you run this twice)
exists , errBucketExists := minioClient . BucketExists ( ctx , config . Bucket )
exists , errBucketExists := minioClient . BucketExists ( ctx , config . Bucket )
if ! exists || errBucketExists != nil {
if ! exists || errBucketExists != nil {
return nil , err
return nil , convertMinioErr ( err )
}
}
}
}
@ -101,7 +125,7 @@ func (m *MinioStorage) Open(path string) (Object, error) {
var opts = minio . GetObjectOptions { }
var opts = minio . GetObjectOptions { }
object , err := m . client . GetObject ( m . ctx , m . bucket , m . buildMinioPath ( path ) , opts )
object , err := m . client . GetObject ( m . ctx , m . bucket , m . buildMinioPath ( path ) , opts )
if err != nil {
if err != nil {
return nil , err
return nil , convertMinioErr ( err )
}
}
return & minioObject { object } , nil
return & minioObject { object } , nil
}
}
@ -117,7 +141,7 @@ func (m *MinioStorage) Save(path string, r io.Reader) (int64, error) {
minio . PutObjectOptions { ContentType : "application/octet-stream" } ,
minio . PutObjectOptions { ContentType : "application/octet-stream" } ,
)
)
if err != nil {
if err != nil {
return 0 , err
return 0 , convertMinioErr ( err )
}
}
return uploadInfo . Size , nil
return uploadInfo . Size , nil
}
}
@ -159,19 +183,16 @@ func (m *MinioStorage) Stat(path string) (os.FileInfo, error) {
minio . StatObjectOptions { } ,
minio . StatObjectOptions { } ,
)
)
if err != nil {
if err != nil {
if errResp , ok := err . ( minio . ErrorResponse ) ; ok {
return nil , convertMinioErr ( err )
if errResp . Code == "NoSuchKey" {
return nil , os . ErrNotExist
}
}
return nil , err
}
}
return & minioFileInfo { info } , nil
return & minioFileInfo { info } , nil
}
}
// Delete delete a file
// Delete delete a file
func ( m * MinioStorage ) Delete ( path string ) error {
func ( m * MinioStorage ) Delete ( path string ) error {
return m . client . RemoveObject ( m . ctx , m . bucket , m . buildMinioPath ( path ) , minio . RemoveObjectOptions { } )
err := m . client . RemoveObject ( m . ctx , m . bucket , m . buildMinioPath ( path ) , minio . RemoveObjectOptions { } )
return convertMinioErr ( err )
}
}
// URL gets the redirect URL to a file. The presigned link is valid for 5 minutes.
// URL gets the redirect URL to a file. The presigned link is valid for 5 minutes.
@ -179,7 +200,8 @@ func (m *MinioStorage) URL(path, name string) (*url.URL, error) {
reqParams := make ( url . Values )
reqParams := make ( url . Values )
// TODO it may be good to embed images with 'inline' like ServeData does, but we don't want to have to read the file, do we?
// TODO it may be good to embed images with 'inline' like ServeData does, but we don't want to have to read the file, do we?
reqParams . Set ( "response-content-disposition" , "attachment; filename=\"" + quoteEscaper . Replace ( name ) + "\"" )
reqParams . Set ( "response-content-disposition" , "attachment; filename=\"" + quoteEscaper . Replace ( name ) + "\"" )
return m . client . PresignedGetObject ( m . ctx , m . bucket , m . buildMinioPath ( path ) , 5 * time . Minute , reqParams )
u , err := m . client . PresignedGetObject ( m . ctx , m . bucket , m . buildMinioPath ( path ) , 5 * time . Minute , reqParams )
return u , convertMinioErr ( err )
}
}
// IterateObjects iterates across the objects in the miniostorage
// IterateObjects iterates across the objects in the miniostorage
@ -193,13 +215,13 @@ func (m *MinioStorage) IterateObjects(fn func(path string, obj Object) error) er
} ) {
} ) {
object , err := m . client . GetObject ( lobjectCtx , m . bucket , mObjInfo . Key , opts )
object , err := m . client . GetObject ( lobjectCtx , m . bucket , mObjInfo . Key , opts )
if err != nil {
if err != nil {
return err
return convertMinioErr ( err )
}
}
if err := func ( object * minio . Object , fn func ( path string , obj Object ) error ) error {
if err := func ( object * minio . Object , fn func ( path string , obj Object ) error ) error {
defer object . Close ( )
defer object . Close ( )
return fn ( strings . TrimPrefix ( m . basePath , mObjInfo . Key ) , & minioObject { object } )
return fn ( strings . TrimPrefix ( m . basePath , mObjInfo . Key ) , & minioObject { object } )
} ( object , fn ) ; err != nil {
} ( object , fn ) ; err != nil {
return err
return convertMinioErr ( err )
}
}
}
}
return nil
return nil