Skip to content

Commit 1b352a4

Browse files
committed
Provide self-registering storage system
Signed-off-by: Andrew Thornton <[email protected]>
1 parent 3878e98 commit 1b352a4

File tree

8 files changed

+232
-100
lines changed

8 files changed

+232
-100
lines changed

cmd/migrate_storage.go

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ func runMigrateStorage(ctx *cli.Context) error {
107107
return err
108108
}
109109

110+
goCtx := context.Background()
111+
110112
if err := storage.Init(); err != nil {
111113
return err
112114
}
@@ -120,18 +122,23 @@ func runMigrateStorage(ctx *cli.Context) error {
120122
log.Fatal("Path must be given when storage is loal")
121123
return nil
122124
}
123-
dstStorage, err = storage.NewLocalStorage(p)
125+
dstStorage, err = storage.NewLocalStorage(
126+
goCtx,
127+
storage.LocalStorageConfig{
128+
Path: p,
129+
})
124130
case setting.MinioStorageType:
125131
dstStorage, err = storage.NewMinioStorage(
126-
context.Background(),
127-
ctx.String("minio-endpoint"),
128-
ctx.String("minio-access-key-id"),
129-
ctx.String("minio-secret-access-key"),
130-
ctx.String("minio-bucket"),
131-
ctx.String("minio-location"),
132-
ctx.String("minio-base-path"),
133-
ctx.Bool("minio-use-ssl"),
134-
)
132+
goCtx,
133+
storage.MinioStorageConfig{
134+
Endpoint: ctx.String("minio-endpoint"),
135+
AccessKeyID: ctx.String("minio-access-key-id"),
136+
SecretAccessKey: ctx.String("minio-secret-access-key"),
137+
Bucket: ctx.String("minio-bucket"),
138+
Location: ctx.String("minio-location"),
139+
BasePath: ctx.String("minio-base-path"),
140+
UseSSL: ctx.Bool("minio-use-ssl"),
141+
})
135142
default:
136143
return fmt.Errorf("Unsupported attachments storage type: %s", ctx.String("storage"))
137144
}

modules/setting/attachment.go

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,27 +44,26 @@ func newAttachmentService() {
4444
log.Fatal("Failed to get attachment storage type: %s", Attachment.Storage.Type)
4545
}
4646
Attachment.Storage = storage
47+
48+
for _, key := range storage.Section.Keys() {
49+
if !sec.HasKey(key.Name()) {
50+
_, _ = sec.NewKey(key.Name(), key.Value())
51+
}
52+
}
53+
Attachment.Storage.Section = sec
4754
}
4855

4956
// Override
5057
Attachment.ServeDirect = sec.Key("SERVE_DIRECT").MustBool(Attachment.ServeDirect)
5158

52-
switch Attachment.Storage.Type {
53-
case LocalStorageType:
54-
Attachment.Path = sec.Key("PATH").MustString(filepath.Join(AppDataPath, "attachments"))
55-
if !filepath.IsAbs(Attachment.Path) {
56-
Attachment.Path = filepath.Join(AppWorkPath, Attachment.Path)
57-
}
58-
case MinioStorageType:
59-
Attachment.Minio.Endpoint = sec.Key("MINIO_ENDPOINT").MustString(Attachment.Minio.Endpoint)
60-
Attachment.Minio.AccessKeyID = sec.Key("MINIO_ACCESS_KEY_ID").MustString(Attachment.Minio.AccessKeyID)
61-
Attachment.Minio.SecretAccessKey = sec.Key("MINIO_SECRET_ACCESS_KEY").MustString(Attachment.Minio.SecretAccessKey)
62-
Attachment.Minio.Bucket = sec.Key("MINIO_BUCKET").MustString(Attachment.Minio.Bucket)
63-
Attachment.Minio.Location = sec.Key("MINIO_LOCATION").MustString(Attachment.Minio.Location)
64-
Attachment.Minio.UseSSL = sec.Key("MINIO_USE_SSL").MustBool(Attachment.Minio.UseSSL)
65-
Attachment.Minio.BasePath = sec.Key("MINIO_BASE_PATH").MustString("attachments/")
59+
Attachment.Storage.Path = sec.Key("PATH").MustString(filepath.Join(AppDataPath, "attachments"))
60+
if !filepath.IsAbs(Attachment.Storage.Path) {
61+
Attachment.Storage.Path = filepath.Join(AppWorkPath, Attachment.Storage.Path)
62+
sec.Key("PATH").SetValue(Attachment.Storage.Path)
6663
}
6764

65+
sec.Key("MINIO_BASE_PATH").MustString("attachments/")
66+
6867
Attachment.AllowedTypes = strings.Replace(sec.Key("ALLOWED_TYPES").MustString("image/jpeg,image/png,application/zip,application/gzip"), "|", ",", -1)
6968
Attachment.MaxSize = sec.Key("MAX_SIZE").MustInt64(4)
7069
Attachment.MaxFiles = sec.Key("MAX_FILES").MustInt(5)

modules/setting/lfs.go

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -48,28 +48,23 @@ func newLFSService() {
4848
log.Fatal("Failed to get lfs storage type: %s", LFS.Storage.Type)
4949
}
5050
LFS.Storage = storage
51+
52+
for _, key := range storage.Section.Keys() {
53+
if !lfsSec.HasKey(key.Name()) {
54+
_, _ = lfsSec.NewKey(key.Name(), key.Value())
55+
}
56+
}
57+
LFS.Storage.Section = lfsSec
5158
}
5259

5360
// Override
5461
LFS.ServeDirect = lfsSec.Key("SERVE_DIRECT").MustBool(LFS.ServeDirect)
55-
switch LFS.Storage.Type {
56-
case LocalStorageType:
57-
// keep compatible
58-
LFS.Path = sec.Key("LFS_CONTENT_PATH").MustString(filepath.Join(AppDataPath, "lfs"))
59-
LFS.Path = lfsSec.Key("PATH").MustString(LFS.Path)
60-
if !filepath.IsAbs(LFS.Path) {
61-
LFS.Path = filepath.Join(AppWorkPath, LFS.Path)
62-
}
63-
64-
case MinioStorageType:
65-
LFS.Minio.Endpoint = lfsSec.Key("MINIO_ENDPOINT").MustString(LFS.Minio.Endpoint)
66-
LFS.Minio.AccessKeyID = lfsSec.Key("MINIO_ACCESS_KEY_ID").MustString(LFS.Minio.AccessKeyID)
67-
LFS.Minio.SecretAccessKey = lfsSec.Key("MINIO_SECRET_ACCESS_KEY").MustString(LFS.Minio.SecretAccessKey)
68-
LFS.Minio.Bucket = lfsSec.Key("MINIO_BUCKET").MustString(LFS.Minio.Bucket)
69-
LFS.Minio.Location = lfsSec.Key("MINIO_LOCATION").MustString(LFS.Minio.Location)
70-
LFS.Minio.UseSSL = lfsSec.Key("MINIO_USE_SSL").MustBool(LFS.Minio.UseSSL)
71-
LFS.Minio.BasePath = lfsSec.Key("MINIO_BASE_PATH").MustString("lfs/")
62+
LFS.Storage.Path = sec.Key("LFS_CONTENT_PATH").MustString(filepath.Join(AppDataPath, "lfs"))
63+
LFS.Storage.Path = lfsSec.Key("PATH").MustString(LFS.Storage.Path)
64+
if !filepath.IsAbs(LFS.Storage.Path) {
65+
lfsSec.Key("PATH").SetValue(filepath.Join(AppWorkPath, LFS.Storage.Path))
7266
}
67+
lfsSec.Key("MINIO_BASE_PATH").MustString("lfs/")
7368

7469
if LFS.LocksPagingNum == 0 {
7570
LFS.LocksPagingNum = 50

modules/setting/storage.go

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package setting
66

77
import (
8+
"reflect"
89
"strings"
910

1011
"code.gitea.io/gitea/modules/log"
@@ -21,16 +22,20 @@ const (
2122
type Storage struct {
2223
Type string
2324
Path string
25+
Section *ini.Section
2426
ServeDirect bool
25-
Minio struct {
26-
Endpoint string
27-
AccessKeyID string
28-
SecretAccessKey string
29-
UseSSL bool
30-
Bucket string
31-
Location string
32-
BasePath string
27+
}
28+
29+
// MapTo implements the Mappable interface
30+
func (s *Storage) MapTo(v interface{}) error {
31+
pathValue := reflect.ValueOf(v).FieldByName("Path")
32+
if pathValue.IsValid() && pathValue.Kind() == reflect.String {
33+
pathValue.SetString(s.Path)
3334
}
35+
if s.Section != nil {
36+
return s.Section.MapTo(v)
37+
}
38+
return nil
3439
}
3540

3641
var (
@@ -40,17 +45,14 @@ var (
4045
func getStorage(sec *ini.Section) Storage {
4146
var storage Storage
4247
storage.Type = sec.Key("STORAGE_TYPE").MustString(LocalStorageType)
48+
storage.Section = sec
4349
storage.ServeDirect = sec.Key("SERVE_DIRECT").MustBool(false)
44-
switch storage.Type {
45-
case LocalStorageType:
46-
case MinioStorageType:
47-
storage.Minio.Endpoint = sec.Key("MINIO_ENDPOINT").MustString("localhost:9000")
48-
storage.Minio.AccessKeyID = sec.Key("MINIO_ACCESS_KEY_ID").MustString("")
49-
storage.Minio.SecretAccessKey = sec.Key("MINIO_SECRET_ACCESS_KEY").MustString("")
50-
storage.Minio.Bucket = sec.Key("MINIO_BUCKET").MustString("gitea")
51-
storage.Minio.Location = sec.Key("MINIO_LOCATION").MustString("us-east-1")
52-
storage.Minio.UseSSL = sec.Key("MINIO_USE_SSL").MustBool(false)
53-
}
50+
sec.Key("MINIO_ENDPOINT").MustString("localhost:9000")
51+
sec.Key("MINIO_ACCESS_KEY_ID").MustString("")
52+
sec.Key("MINIO_SECRET_ACCESS_KEY").MustString("")
53+
sec.Key("MINIO_BUCKET").MustString("gitea")
54+
sec.Key("MINIO_LOCATION").MustString("us-east-1")
55+
sec.Key("MINIO_USE_SSL").MustBool(false)
5456
return storage
5557
}
5658

modules/storage/helper.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package storage
6+
7+
import (
8+
"encoding/json"
9+
"reflect"
10+
)
11+
12+
// Mappable represents an interface that can MapTo another interface
13+
type Mappable interface {
14+
MapTo(v interface{}) error
15+
}
16+
17+
// toConfig will attempt to convert a given configuration cfg into the provided exemplar type.
18+
//
19+
// It will tolerate the cfg being passed as a []byte or string of a json representation of the
20+
// exemplar or the correct type of the exemplar itself
21+
func toConfig(exemplar, cfg interface{}) (interface{}, error) {
22+
23+
// First of all check if we've got the same type as the exemplar - if so it's all fine.
24+
if reflect.TypeOf(cfg).AssignableTo(reflect.TypeOf(exemplar)) {
25+
return cfg, nil
26+
}
27+
28+
// Now if not - does it provide a MapTo function we can try?
29+
if mappable, ok := cfg.(Mappable); ok {
30+
newVal := reflect.New(reflect.TypeOf(exemplar))
31+
if err := mappable.MapTo(newVal.Interface()); err == nil {
32+
return newVal.Elem().Interface(), nil
33+
}
34+
// MapTo has failed us ... let's try the json route ...
35+
}
36+
37+
// OK we've been passed a byte array right?
38+
configBytes, ok := cfg.([]byte)
39+
if !ok {
40+
// oh ... it's a string then?
41+
var configStr string
42+
43+
configStr, ok = cfg.(string)
44+
configBytes = []byte(configStr)
45+
}
46+
if !ok {
47+
// hmm ... can we marshal it to json?
48+
var err error
49+
50+
configBytes, err = json.Marshal(cfg)
51+
ok = (err == nil)
52+
}
53+
if !ok {
54+
// no ... we've tried hard enough at this point - throw an error!
55+
return nil, ErrInvalidConfiguration{cfg: cfg}
56+
}
57+
58+
// OK unmarshal the byte array into a new copy of the exemplar
59+
newVal := reflect.New(reflect.TypeOf(exemplar))
60+
if err := json.Unmarshal(configBytes, newVal.Interface()); err != nil {
61+
// If we can't unmarshal it then return an error!
62+
return nil, ErrInvalidConfiguration{cfg: cfg, err: err}
63+
}
64+
return newVal.Elem().Interface(), nil
65+
}

modules/storage/local.go

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package storage
66

77
import (
8+
"context"
89
"io"
910
"net/url"
1011
"os"
@@ -17,19 +18,35 @@ var (
1718
_ ObjectStorage = &LocalStorage{}
1819
)
1920

21+
// LocalStorageType is the type descriptor for local storage
22+
const LocalStorageType Type = "local"
23+
24+
// LocalStorageConfig represents the configuration for a local storage
25+
type LocalStorageConfig struct {
26+
Path string `ini:"PATH"`
27+
}
28+
2029
// LocalStorage represents a local files storage
2130
type LocalStorage struct {
31+
ctx context.Context
2232
dir string
2333
}
2434

2535
// NewLocalStorage returns a local files
26-
func NewLocalStorage(bucket string) (*LocalStorage, error) {
27-
if err := os.MkdirAll(bucket, os.ModePerm); err != nil {
36+
func NewLocalStorage(ctx context.Context, cfg interface{}) (ObjectStorage, error) {
37+
configInterface, err := toConfig(LocalStorageConfig{}, cfg)
38+
if err != nil {
39+
return nil, err
40+
}
41+
config := configInterface.(LocalStorageConfig)
42+
43+
if err := os.MkdirAll(config.Path, os.ModePerm); err != nil {
2844
return nil, err
2945
}
3046

3147
return &LocalStorage{
32-
dir: bucket,
48+
ctx: ctx,
49+
dir: config.Path,
3350
}, nil
3451
}
3552

@@ -80,6 +97,11 @@ func (l *LocalStorage) IterateObjects(fn func(path string, obj Object) error) er
8097
if err != nil {
8198
return err
8299
}
100+
select {
101+
case <-l.ctx.Done():
102+
return l.ctx.Err()
103+
default:
104+
}
83105
if path == l.dir {
84106
return nil
85107
}
@@ -98,3 +120,7 @@ func (l *LocalStorage) IterateObjects(fn func(path string, obj Object) error) er
98120
return fn(relPath, obj)
99121
})
100122
}
123+
124+
func init() {
125+
RegisterStorageType(LocalStorageType, NewLocalStorage)
126+
}

0 commit comments

Comments
 (0)