Skip to content

Commit 6b1266b

Browse files
zeripathlunny
andauthored
Provide self-registering storage system (#12978)
* Provide self-registering storage system Signed-off-by: Andrew Thornton <[email protected]> * More simplification Signed-off-by: Andrew Thornton <[email protected]> * Remove old strings from setting Signed-off-by: Andrew Thornton <[email protected]> * oops attachments not attachment Signed-off-by: Andrew Thornton <[email protected]> Co-authored-by: Lunny Xiao <[email protected]>
1 parent ade9c8d commit 6b1266b

File tree

10 files changed

+257
-167
lines changed

10 files changed

+257
-167
lines changed

cmd/migrate_storage.go

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ var CmdMigrateStorage = cli.Command{
3232
},
3333
cli.StringFlag{
3434
Name: "storage, s",
35-
Value: setting.LocalStorageType,
36-
Usage: "New storage type, local or minio",
35+
Value: "",
36+
Usage: "New storage type: local (default) or minio",
3737
},
3838
cli.StringFlag{
3939
Name: "path, p",
@@ -107,31 +107,40 @@ 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
}
113115

114116
var dstStorage storage.ObjectStorage
115117
var err error
116118
switch strings.ToLower(ctx.String("storage")) {
117-
case setting.LocalStorageType:
119+
case "":
120+
fallthrough
121+
case string(storage.LocalStorageType):
118122
p := ctx.String("path")
119123
if p == "" {
120124
log.Fatal("Path must be given when storage is loal")
121125
return nil
122126
}
123-
dstStorage, err = storage.NewLocalStorage(p)
124-
case setting.MinioStorageType:
127+
dstStorage, err = storage.NewLocalStorage(
128+
goCtx,
129+
storage.LocalStorageConfig{
130+
Path: p,
131+
})
132+
case string(storage.MinioStorageType):
125133
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-
)
134+
goCtx,
135+
storage.MinioStorageConfig{
136+
Endpoint: ctx.String("minio-endpoint"),
137+
AccessKeyID: ctx.String("minio-access-key-id"),
138+
SecretAccessKey: ctx.String("minio-secret-access-key"),
139+
Bucket: ctx.String("minio-bucket"),
140+
Location: ctx.String("minio-location"),
141+
BasePath: ctx.String("minio-base-path"),
142+
UseSSL: ctx.Bool("minio-use-ssl"),
143+
})
135144
default:
136145
return fmt.Errorf("Unsupported attachments storage type: %s", ctx.String("storage"))
137146
}

models/unit_tests.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,8 @@ func MainTest(m *testing.M, pathToGiteaRoot string) {
6767
if err != nil {
6868
fatalTestError("url.Parse: %v\n", err)
6969
}
70-
setting.Attachment.Storage.Type = setting.LocalStorageType
7170
setting.Attachment.Storage.Path = filepath.Join(setting.AppDataPath, "attachments")
7271

73-
setting.LFS.Storage.Type = setting.LocalStorageType
7472
setting.LFS.Storage.Path = filepath.Join(setting.AppDataPath, "lfs")
7573
if err = storage.Init(); err != nil {
7674
fatalTestError("storage.Init: %v\n", err)

modules/setting/attachment.go

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,6 @@
44

55
package setting
66

7-
import (
8-
"path/filepath"
9-
10-
"code.gitea.io/gitea/modules/log"
11-
)
12-
137
var (
148
// Attachment settings
159
Attachment = struct {
@@ -20,7 +14,6 @@ var (
2014
Enabled bool
2115
}{
2216
Storage: Storage{
23-
Type: LocalStorageType,
2417
ServeDirect: false,
2518
},
2619
AllowedTypes: "image/jpeg,image/png,application/zip,application/gzip",
@@ -32,37 +25,9 @@ var (
3225

3326
func newAttachmentService() {
3427
sec := Cfg.Section("attachment")
35-
Attachment.Storage.Type = sec.Key("STORAGE_TYPE").MustString("")
36-
if Attachment.Storage.Type == "" {
37-
Attachment.Storage.Type = "default"
38-
}
28+
storageType := sec.Key("STORAGE_TYPE").MustString("")
3929

40-
if Attachment.Storage.Type != LocalStorageType && Attachment.Storage.Type != MinioStorageType {
41-
storage, ok := storages[Attachment.Storage.Type]
42-
if !ok {
43-
log.Fatal("Failed to get attachment storage type: %s", Attachment.Storage.Type)
44-
}
45-
Attachment.Storage = storage
46-
}
47-
48-
// Override
49-
Attachment.ServeDirect = sec.Key("SERVE_DIRECT").MustBool(Attachment.ServeDirect)
50-
51-
switch Attachment.Storage.Type {
52-
case LocalStorageType:
53-
Attachment.Path = sec.Key("PATH").MustString(filepath.Join(AppDataPath, "attachments"))
54-
if !filepath.IsAbs(Attachment.Path) {
55-
Attachment.Path = filepath.Join(AppWorkPath, Attachment.Path)
56-
}
57-
case MinioStorageType:
58-
Attachment.Minio.Endpoint = sec.Key("MINIO_ENDPOINT").MustString(Attachment.Minio.Endpoint)
59-
Attachment.Minio.AccessKeyID = sec.Key("MINIO_ACCESS_KEY_ID").MustString(Attachment.Minio.AccessKeyID)
60-
Attachment.Minio.SecretAccessKey = sec.Key("MINIO_SECRET_ACCESS_KEY").MustString(Attachment.Minio.SecretAccessKey)
61-
Attachment.Minio.Bucket = sec.Key("MINIO_BUCKET").MustString(Attachment.Minio.Bucket)
62-
Attachment.Minio.Location = sec.Key("MINIO_LOCATION").MustString(Attachment.Minio.Location)
63-
Attachment.Minio.UseSSL = sec.Key("MINIO_USE_SSL").MustBool(Attachment.Minio.UseSSL)
64-
Attachment.Minio.BasePath = sec.Key("MINIO_BASE_PATH").MustString("attachments/")
65-
}
30+
Attachment.Storage = getStorage("attachments", storageType, sec)
6631

6732
Attachment.AllowedTypes = sec.Key("ALLOWED_TYPES").MustString(".docx,.gif,.gz,.jpeg,.jpg,.log,.pdf,.png,.pptx,.txt,.xlsx,.zip")
6833
Attachment.MaxSize = sec.Key("MAX_SIZE").MustInt64(4)

modules/setting/lfs.go

Lines changed: 6 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -37,40 +37,15 @@ func newLFSService() {
3737
}
3838

3939
lfsSec := Cfg.Section("lfs")
40-
LFS.Storage.Type = lfsSec.Key("STORAGE_TYPE").MustString("")
41-
if LFS.Storage.Type == "" {
42-
LFS.Storage.Type = "default"
43-
}
44-
45-
if LFS.Storage.Type != LocalStorageType && LFS.Storage.Type != MinioStorageType {
46-
storage, ok := storages[LFS.Storage.Type]
47-
if !ok {
48-
log.Fatal("Failed to get lfs storage type: %s", LFS.Storage.Type)
49-
}
50-
LFS.Storage = storage
51-
}
40+
storageType := lfsSec.Key("STORAGE_TYPE").MustString("")
5241

53-
// Override
54-
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-
}
42+
// Specifically default PATH to LFS_CONTENT_PATH
43+
lfsSec.Key("PATH").MustString(
44+
sec.Key("LFS_CONTENT_PATH").String())
6345

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/")
72-
}
46+
LFS.Storage = getStorage("lfs", storageType, lfsSec)
7347

48+
// Rest of LFS service settings
7449
if LFS.LocksPagingNum == 0 {
7550
LFS.LocksPagingNum = 50
7651
}

modules/setting/setting.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -804,7 +804,6 @@ func NewContext() {
804804
}
805805
}
806806

807-
newStorageService()
808807
newAttachmentService()
809808
newLFSService()
810809

modules/setting/storage.go

Lines changed: 54 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,65 +5,77 @@
55
package setting
66

77
import (
8-
"strings"
8+
"path/filepath"
9+
"reflect"
910

10-
"code.gitea.io/gitea/modules/log"
1111
ini "gopkg.in/ini.v1"
1212
)
1313

14-
// enumerate all storage types
15-
const (
16-
LocalStorageType = "local"
17-
MinioStorageType = "minio"
18-
)
19-
2014
// Storage represents configuration of storages
2115
type Storage struct {
2216
Type string
2317
Path string
18+
Section *ini.Section
2419
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
20+
}
21+
22+
// MapTo implements the Mappable interface
23+
func (s *Storage) MapTo(v interface{}) error {
24+
pathValue := reflect.ValueOf(v).FieldByName("Path")
25+
if pathValue.IsValid() && pathValue.Kind() == reflect.String {
26+
pathValue.SetString(s.Path)
27+
}
28+
if s.Section != nil {
29+
return s.Section.MapTo(v)
3330
}
31+
return nil
3432
}
3533

36-
var (
37-
storages = make(map[string]Storage)
38-
)
34+
func getStorage(name, typ string, overrides ...*ini.Section) Storage {
35+
sectionName := "storage"
36+
if len(name) > 0 {
37+
sectionName = sectionName + "." + typ
38+
}
39+
sec := Cfg.Section(sectionName)
40+
41+
if len(overrides) == 0 {
42+
overrides = []*ini.Section{
43+
Cfg.Section(sectionName + "." + name),
44+
}
45+
}
3946

40-
func getStorage(sec *ini.Section) Storage {
4147
var storage Storage
42-
storage.Type = sec.Key("STORAGE_TYPE").MustString(LocalStorageType)
48+
49+
storage.Type = sec.Key("STORAGE_TYPE").MustString("")
4350
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-
}
54-
return storage
55-
}
5651

57-
func newStorageService() {
58-
sec := Cfg.Section("storage")
59-
storages["default"] = getStorage(sec)
52+
// Global Defaults
53+
sec.Key("MINIO_ENDPOINT").MustString("localhost:9000")
54+
sec.Key("MINIO_ACCESS_KEY_ID").MustString("")
55+
sec.Key("MINIO_SECRET_ACCESS_KEY").MustString("")
56+
sec.Key("MINIO_BUCKET").MustString("gitea")
57+
sec.Key("MINIO_LOCATION").MustString("us-east-1")
58+
sec.Key("MINIO_USE_SSL").MustBool(false)
59+
60+
storage.Section = sec
6061

61-
for _, sec := range Cfg.Section("storage").ChildSections() {
62-
name := strings.TrimPrefix(sec.Name(), "storage.")
63-
if name == "default" || name == LocalStorageType || name == MinioStorageType {
64-
log.Error("storage name %s is system reserved!", name)
65-
continue
62+
for _, override := range overrides {
63+
for _, key := range storage.Section.Keys() {
64+
if !override.HasKey(key.Name()) {
65+
_, _ = override.NewKey(key.Name(), key.Value())
66+
}
6667
}
67-
storages[name] = getStorage(sec)
68+
storage.ServeDirect = override.Key("SERVE_DIRECT").MustBool(false)
69+
storage.Section = override
6870
}
71+
72+
// Specific defaults
73+
storage.Path = storage.Section.Key("PATH").MustString(filepath.Join(AppDataPath, name))
74+
if !filepath.IsAbs(storage.Path) {
75+
storage.Path = filepath.Join(AppWorkPath, storage.Path)
76+
storage.Section.Key("PATH").SetValue(storage.Path)
77+
}
78+
storage.Section.Key("MINIO_BASE_PATH").MustString(name + "/")
79+
80+
return storage
6981
}

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+
}

0 commit comments

Comments
 (0)