aboutsummaryrefslogtreecommitdiffstats
path: root/modules/storage
diff options
context:
space:
mode:
Diffstat (limited to 'modules/storage')
-rw-r--r--modules/storage/azureblob.go4
-rw-r--r--modules/storage/azureblob_test.go53
-rw-r--r--modules/storage/helper.go2
-rw-r--r--modules/storage/helper_test.go2
-rw-r--r--modules/storage/local.go2
-rw-r--r--modules/storage/local_test.go7
-rw-r--r--modules/storage/minio.go18
-rw-r--r--modules/storage/minio_test.go12
-rw-r--r--modules/storage/storage.go12
-rw-r--r--modules/storage/storage_test.go4
10 files changed, 84 insertions, 32 deletions
diff --git a/modules/storage/azureblob.go b/modules/storage/azureblob.go
index 96c2525b29..6860d81131 100644
--- a/modules/storage/azureblob.go
+++ b/modules/storage/azureblob.go
@@ -70,7 +70,7 @@ func (a *azureBlobObject) Seek(offset int64, whence int) (int64, error) {
case io.SeekCurrent:
offset += a.offset
case io.SeekEnd:
- offset = a.Size - offset
+ offset = a.Size + offset
default:
return 0, errors.New("Seek: invalid whence")
}
@@ -247,7 +247,7 @@ func (a *AzureBlobStorage) Delete(path string) error {
}
// URL gets the redirect URL to a file. The presigned link is valid for 5 minutes.
-func (a *AzureBlobStorage) URL(path, name string, reqParams url.Values) (*url.URL, error) {
+func (a *AzureBlobStorage) URL(path, name, _ string, reqParams url.Values) (*url.URL, error) {
blobClient := a.getBlobClient(path)
startTime := time.Now()
diff --git a/modules/storage/azureblob_test.go b/modules/storage/azureblob_test.go
index 604870cb98..b3791b4916 100644
--- a/modules/storage/azureblob_test.go
+++ b/modules/storage/azureblob_test.go
@@ -4,7 +4,9 @@
package storage
import (
+ "io"
"os"
+ "strings"
"testing"
"code.gitea.io/gitea/modules/setting"
@@ -31,14 +33,14 @@ func TestAzureBlobStorageIterator(t *testing.T) {
func TestAzureBlobStoragePath(t *testing.T) {
m := &AzureBlobStorage{cfg: &setting.AzureBlobStorageConfig{BasePath: ""}}
- assert.Equal(t, "", m.buildAzureBlobPath("/"))
- assert.Equal(t, "", m.buildAzureBlobPath("."))
+ assert.Empty(t, m.buildAzureBlobPath("/"))
+ assert.Empty(t, m.buildAzureBlobPath("."))
assert.Equal(t, "a", m.buildAzureBlobPath("/a"))
assert.Equal(t, "a/b", m.buildAzureBlobPath("/a/b/"))
m = &AzureBlobStorage{cfg: &setting.AzureBlobStorageConfig{BasePath: "/"}}
- assert.Equal(t, "", m.buildAzureBlobPath("/"))
- assert.Equal(t, "", m.buildAzureBlobPath("."))
+ assert.Empty(t, m.buildAzureBlobPath("/"))
+ assert.Empty(t, m.buildAzureBlobPath("."))
assert.Equal(t, "a", m.buildAzureBlobPath("/a"))
assert.Equal(t, "a/b", m.buildAzureBlobPath("/a/b/"))
@@ -54,3 +56,46 @@ func TestAzureBlobStoragePath(t *testing.T) {
assert.Equal(t, "base/a", m.buildAzureBlobPath("/a"))
assert.Equal(t, "base/a/b", m.buildAzureBlobPath("/a/b/"))
}
+
+func Test_azureBlobObject(t *testing.T) {
+ if os.Getenv("CI") == "" {
+ t.Skip("azureBlobStorage not present outside of CI")
+ return
+ }
+
+ s, err := NewStorage(setting.AzureBlobStorageType, &setting.Storage{
+ AzureBlobConfig: setting.AzureBlobStorageConfig{
+ // https://learn.microsoft.com/azure/storage/common/storage-use-azurite?tabs=visual-studio-code#ip-style-url
+ Endpoint: "http://devstoreaccount1.azurite.local:10000",
+ // https://learn.microsoft.com/azure/storage/common/storage-use-azurite?tabs=visual-studio-code#well-known-storage-account-and-key
+ AccountName: "devstoreaccount1",
+ AccountKey: "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==",
+ Container: "test",
+ },
+ })
+ assert.NoError(t, err)
+
+ data := "Q2xTckt6Y1hDOWh0"
+ _, err = s.Save("test.txt", strings.NewReader(data), int64(len(data)))
+ assert.NoError(t, err)
+ obj, err := s.Open("test.txt")
+ assert.NoError(t, err)
+ offset, err := obj.Seek(2, io.SeekStart)
+ assert.NoError(t, err)
+ assert.EqualValues(t, 2, offset)
+ buf1 := make([]byte, 3)
+ read, err := obj.Read(buf1)
+ assert.NoError(t, err)
+ assert.Equal(t, 3, read)
+ assert.Equal(t, data[2:5], string(buf1))
+ offset, err = obj.Seek(-5, io.SeekEnd)
+ assert.NoError(t, err)
+ assert.EqualValues(t, len(data)-5, offset)
+ buf2 := make([]byte, 4)
+ read, err = obj.Read(buf2)
+ assert.NoError(t, err)
+ assert.Equal(t, 4, read)
+ assert.Equal(t, data[11:15], string(buf2))
+ assert.NoError(t, obj.Close())
+ assert.NoError(t, s.Delete("test.txt"))
+}
diff --git a/modules/storage/helper.go b/modules/storage/helper.go
index 9e6cceb537..f6c3d5eebb 100644
--- a/modules/storage/helper.go
+++ b/modules/storage/helper.go
@@ -30,7 +30,7 @@ func (s discardStorage) Delete(_ string) error {
return fmt.Errorf("%s", s)
}
-func (s discardStorage) URL(_, _ string, _ url.Values) (*url.URL, error) {
+func (s discardStorage) URL(_, _, _ string, _ url.Values) (*url.URL, error) {
return nil, fmt.Errorf("%s", s)
}
diff --git a/modules/storage/helper_test.go b/modules/storage/helper_test.go
index 62ebd8753c..3cba1e13c0 100644
--- a/modules/storage/helper_test.go
+++ b/modules/storage/helper_test.go
@@ -37,7 +37,7 @@ func Test_discardStorage(t *testing.T) {
assert.Error(t, err, string(tt))
}
{
- got, err := tt.URL("path", "name", nil)
+ got, err := tt.URL("path", "name", "GET", nil)
assert.Nil(t, got)
assert.Errorf(t, err, string(tt))
}
diff --git a/modules/storage/local.go b/modules/storage/local.go
index 00c7f668aa..8a1776f606 100644
--- a/modules/storage/local.go
+++ b/modules/storage/local.go
@@ -114,7 +114,7 @@ func (l *LocalStorage) Delete(path string) error {
}
// URL gets the redirect URL to a file
-func (l *LocalStorage) URL(path, name string, reqParams url.Values) (*url.URL, error) {
+func (l *LocalStorage) URL(path, name, _ string, reqParams url.Values) (*url.URL, error) {
return nil, ErrURLNotSupported
}
diff --git a/modules/storage/local_test.go b/modules/storage/local_test.go
index e230323f67..0592fd716b 100644
--- a/modules/storage/local_test.go
+++ b/modules/storage/local_test.go
@@ -4,8 +4,6 @@
package storage
import (
- "os"
- "path/filepath"
"testing"
"code.gitea.io/gitea/modules/setting"
@@ -50,12 +48,11 @@ func TestBuildLocalPath(t *testing.T) {
t.Run(k.path, func(t *testing.T) {
l := LocalStorage{dir: k.localDir}
- assert.EqualValues(t, k.expected, l.buildLocalPath(k.path))
+ assert.Equal(t, k.expected, l.buildLocalPath(k.path))
})
}
}
func TestLocalStorageIterator(t *testing.T) {
- dir := filepath.Join(os.TempDir(), "TestLocalStorageIteratorTestDir")
- testStorageIterator(t, setting.LocalStorageType, &setting.Storage{Path: dir})
+ testStorageIterator(t, setting.LocalStorageType, &setting.Storage{Path: t.TempDir()})
}
diff --git a/modules/storage/minio.go b/modules/storage/minio.go
index 6b92be61fb..01f2c16267 100644
--- a/modules/storage/minio.go
+++ b/modules/storage/minio.go
@@ -86,13 +86,14 @@ func NewMinioStorage(ctx context.Context, cfg *setting.Storage) (ObjectStorage,
log.Info("Creating Minio storage at %s:%s with base path %s", config.Endpoint, config.Bucket, config.BasePath)
var lookup minio.BucketLookupType
- if config.BucketLookUpType == "auto" || config.BucketLookUpType == "" {
+ switch config.BucketLookUpType {
+ case "auto", "":
lookup = minio.BucketLookupAuto
- } else if config.BucketLookUpType == "dns" {
+ case "dns":
lookup = minio.BucketLookupDNS
- } else if config.BucketLookUpType == "path" {
+ case "path":
lookup = minio.BucketLookupPath
- } else {
+ default:
return nil, fmt.Errorf("invalid minio bucket lookup type: %s", config.BucketLookUpType)
}
@@ -278,7 +279,7 @@ func (m *MinioStorage) Delete(path string) error {
}
// URL gets the redirect URL to a file. The presigned link is valid for 5 minutes.
-func (m *MinioStorage) URL(path, name string, serveDirectReqParams url.Values) (*url.URL, error) {
+func (m *MinioStorage) URL(path, name, method string, serveDirectReqParams url.Values) (*url.URL, error) {
// copy serveDirectReqParams
reqParams, err := url.ParseQuery(serveDirectReqParams.Encode())
if err != nil {
@@ -286,7 +287,12 @@ func (m *MinioStorage) URL(path, name string, serveDirectReqParams 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?
reqParams.Set("response-content-disposition", "attachment; filename=\""+quoteEscaper.Replace(name)+"\"")
- u, err := m.client.PresignedGetObject(m.ctx, m.bucket, m.buildMinioPath(path), 5*time.Minute, reqParams)
+ expires := 5 * time.Minute
+ if method == http.MethodHead {
+ u, err := m.client.PresignedHeadObject(m.ctx, m.bucket, m.buildMinioPath(path), expires, reqParams)
+ return u, convertMinioErr(err)
+ }
+ u, err := m.client.PresignedGetObject(m.ctx, m.bucket, m.buildMinioPath(path), expires, reqParams)
return u, convertMinioErr(err)
}
diff --git a/modules/storage/minio_test.go b/modules/storage/minio_test.go
index 395da051e8..2726d765dd 100644
--- a/modules/storage/minio_test.go
+++ b/modules/storage/minio_test.go
@@ -34,19 +34,19 @@ func TestMinioStorageIterator(t *testing.T) {
func TestMinioStoragePath(t *testing.T) {
m := &MinioStorage{basePath: ""}
- assert.Equal(t, "", m.buildMinioPath("/"))
- assert.Equal(t, "", m.buildMinioPath("."))
+ assert.Empty(t, m.buildMinioPath("/"))
+ assert.Empty(t, m.buildMinioPath("."))
assert.Equal(t, "a", m.buildMinioPath("/a"))
assert.Equal(t, "a/b", m.buildMinioPath("/a/b/"))
- assert.Equal(t, "", m.buildMinioDirPrefix(""))
+ assert.Empty(t, m.buildMinioDirPrefix(""))
assert.Equal(t, "a/", m.buildMinioDirPrefix("/a/"))
m = &MinioStorage{basePath: "/"}
- assert.Equal(t, "", m.buildMinioPath("/"))
- assert.Equal(t, "", m.buildMinioPath("."))
+ assert.Empty(t, m.buildMinioPath("/"))
+ assert.Empty(t, m.buildMinioPath("."))
assert.Equal(t, "a", m.buildMinioPath("/a"))
assert.Equal(t, "a/b", m.buildMinioPath("/a/b/"))
- assert.Equal(t, "", m.buildMinioDirPrefix(""))
+ assert.Empty(t, m.buildMinioDirPrefix(""))
assert.Equal(t, "a/", m.buildMinioDirPrefix("/a/"))
m = &MinioStorage{basePath: "/base"}
diff --git a/modules/storage/storage.go b/modules/storage/storage.go
index 750ecdfe0d..1868817c05 100644
--- a/modules/storage/storage.go
+++ b/modules/storage/storage.go
@@ -59,11 +59,15 @@ type Object interface {
// ObjectStorage represents an object storage to handle a bucket and files
type ObjectStorage interface {
Open(path string) (Object, error)
- // Save store a object, if size is unknown set -1
+
+ // Save store an object, if size is unknown set -1
+ // NOTICE: Some storage SDK will close the Reader after saving if it is also a Closer,
+ // DO NOT use the reader anymore after Save, or wrap it to a non-Closer reader.
Save(path string, r io.Reader, size int64) (int64, error)
+
Stat(path string) (os.FileInfo, error)
Delete(path string) error
- URL(path, name string, reqParams url.Values) (*url.URL, error)
+ URL(path, name, method string, reqParams url.Values) (*url.URL, error)
IterateObjects(path string, iterator func(path string, obj Object) error) error
}
@@ -93,7 +97,7 @@ func Clean(storage ObjectStorage) error {
}
// SaveFrom saves data to the ObjectStorage with path p from the callback
-func SaveFrom(objStorage ObjectStorage, p string, callback func(w io.Writer) error) error {
+func SaveFrom(objStorage ObjectStorage, path string, callback func(w io.Writer) error) error {
pr, pw := io.Pipe()
defer pr.Close()
go func() {
@@ -103,7 +107,7 @@ func SaveFrom(objStorage ObjectStorage, p string, callback func(w io.Writer) err
}
}()
- _, err := objStorage.Save(p, pr, -1)
+ _, err := objStorage.Save(path, pr, -1)
return err
}
diff --git a/modules/storage/storage_test.go b/modules/storage/storage_test.go
index 7edde558f3..08f274e74b 100644
--- a/modules/storage/storage_test.go
+++ b/modules/storage/storage_test.go
@@ -4,7 +4,7 @@
package storage
import (
- "bytes"
+ "strings"
"testing"
"code.gitea.io/gitea/modules/setting"
@@ -26,7 +26,7 @@ func testStorageIterator(t *testing.T, typStr Type, cfg *setting.Storage) {
{"b/x 4.txt", "bx4"},
}
for _, f := range testFiles {
- _, err = l.Save(f[0], bytes.NewBufferString(f[1]), -1)
+ _, err = l.Save(f[0], strings.NewReader(f[1]), -1)
assert.NoError(t, err)
}