feat(config): add config loading functions

This commit is contained in:
hexxa 2022-01-28 22:16:32 +08:00 committed by Hexxa
parent 32a003d789
commit 48a239bef2
5 changed files with 576 additions and 0 deletions

107
src/server/config_load.go Normal file
View file

@ -0,0 +1,107 @@
package server
import (
"encoding/json"
"fmt"
"os"
"strings"
"github.com/ihexxa/gocfg"
"github.com/ihexxa/quickshare/src/db/sitestore"
"github.com/ihexxa/quickshare/src/kvstore/boltdbpvd"
)
type Opts struct {
Host string `short:"h" long:"host" description:"server hostname"`
Port int `short:"p" long:"port" description:"server port"`
DbPath string `short:"d" long:"db" description:"path of the quickshare.db"`
Configs []string `short:"c" long:"configs" description:"config path"`
}
func LoadCfg(opts *Opts) (*gocfg.Cfg, error) {
defaultCfg, err := DefaultConfig()
if err != nil {
return nil, err
}
cfg, err := gocfg.New(NewConfig()).Load(gocfg.JSONStr(defaultCfg))
if err != nil {
return nil, err
}
if opts.DbPath != "" {
cfg, err = mergeDbConfig(cfg, opts.DbPath)
if err != nil {
return nil, err
}
} else {
_, err := os.Stat(boltdbpvd.DBName)
if err == nil {
cfg, err = mergeDbConfig(cfg, boltdbpvd.DBName)
} else if err != nil {
if !os.IsNotExist(err) {
return nil, err
}
}
}
cfg, err = mergeConfigFiles(cfg, opts.Configs)
if err != nil {
return nil, err
}
return mergeArgs(cfg, opts)
}
func mergeDbConfig(cfg *gocfg.Cfg, dbPath string) (*gocfg.Cfg, error) {
kv := boltdbpvd.New(dbPath, 1024)
defer kv.Close()
siteStore, err := sitestore.NewSiteStore(kv)
if err != nil {
return nil, fmt.Errorf("fail to new site config store: %s", err)
}
clientCfg, err := siteStore.GetCfg()
if err != nil {
return nil, err
}
clientCfgBytes, err := json.Marshal(clientCfg)
if err != nil {
return nil, err
}
cfgStr := fmt.Sprintf(`{"site": %s}`, string(clientCfgBytes))
return cfg.Load(gocfg.JSONStr(cfgStr))
}
func mergeConfigFiles(cfg *gocfg.Cfg, configPaths []string) (*gocfg.Cfg, error) {
var err error
for _, configPath := range configPaths {
if strings.HasSuffix(configPath, ".yml") || strings.HasSuffix(configPath, ".yaml") {
cfg, err = cfg.Load(gocfg.YAML(configPath))
} else if strings.HasSuffix(configPath, ".json") {
cfg, err = cfg.Load(gocfg.JSON(configPath))
} else {
return nil, fmt.Errorf("unknown config file type (.yml .yaml .json are supported): %s", configPath)
}
if err != nil {
return nil, err
}
}
return cfg, nil
}
func mergeArgs(cfg *gocfg.Cfg, opts *Opts) (*gocfg.Cfg, error) {
if opts.Host != "" {
cfg.SetString("Server.Host", opts.Host)
}
if opts.Port != 0 {
cfg.SetInt("Server.Port", opts.Port)
}
return cfg, nil
}

View file

@ -0,0 +1,352 @@
package server
import (
"encoding/json"
"fmt"
"testing"
"github.com/ihexxa/gocfg"
"github.com/ihexxa/quickshare/src/db/sitestore"
"github.com/ihexxa/quickshare/src/db/userstore"
)
func TestLoadCfg(t *testing.T) {
argsList := []*Opts{
// default config
&Opts{
Host: "",
Port: 0,
DbPath: "",
Configs: []string{},
},
// default config + db
&Opts{
Host: "",
Port: 0,
DbPath: "testdata",
Configs: []string{},
},
// default config + db + config_1
&Opts{
Host: "",
Port: 0,
DbPath: "testdata",
Configs: []string{"testdata/config_1.yml"},
},
// default config + db + config_1 + config_2
&Opts{
Host: "",
Port: 0,
DbPath: "testdata",
Configs: []string{"testdata/config_1.yml", "testdata/config_4.yml"},
},
// config partial overwrite
&Opts{
Host: "",
Port: 0,
DbPath: "testdata",
Configs: []string{
"testdata/config_1.yml",
"testdata/config_4.yml",
"testdata/config_partial_users.yml",
},
},
// arg overwrite
}
cfg1 := DefaultConfigStruct()
cfg2 := DefaultConfigStruct()
cfg2.Site.ClientCfg.SiteName = "Quickshare"
cfg2.Site.ClientCfg.SiteDesc = "Quickshare"
cfg2.Site.ClientCfg.Bg.Url = "test.png"
cfg2.Site.ClientCfg.Bg.Repeat = "no-repeat"
cfg2.Site.ClientCfg.Bg.Position = "top"
cfg2.Site.ClientCfg.Bg.Align = "scroll"
cfg3 := &Config{
Fs: &FSConfig{
Root: "1",
OpensLimit: 1,
OpenTTL: 1,
},
Users: &UsersCfg{
EnableAuth: true,
DefaultAdmin: "1",
DefaultAdminPwd: "1",
CookieTTL: 1,
CookieSecure: true,
CookieHttpOnly: true,
MinUserNameLen: 1,
MinPwdLen: 1,
CaptchaWidth: 1,
CaptchaHeight: 1,
CaptchaEnabled: true,
UploadSpeedLimit: 1,
DownloadSpeedLimit: 1,
SpaceLimit: 1,
LimiterCapacity: 1,
LimiterCyc: 1,
PredefinedUsers: []*userstore.UserCfg{
&userstore.UserCfg{
Name: "1",
Pwd: "1",
Role: "1",
},
},
},
Secrets: &Secrets{
TokenSecret: "1",
},
Server: &ServerCfg{
Debug: true,
Host: "1",
Port: 1,
ReadTimeout: 1,
WriteTimeout: 1,
MaxHeaderBytes: 1,
PublicPath: "1",
},
Workers: &WorkerPoolCfg{
QueueSize: 1,
SleepCyc: 1,
WorkerCount: 1,
},
Site: &sitestore.SiteConfig{
ClientCfg: &sitestore.ClientConfig{
SiteName: "1",
SiteDesc: "1",
Bg: &sitestore.BgConfig{
Url: "1",
Repeat: "1",
Position: "1",
Align: "1",
},
},
},
}
cfg4 := &Config{
Fs: &FSConfig{
Root: "4",
OpensLimit: 4,
OpenTTL: 4,
},
Users: &UsersCfg{
EnableAuth: false,
DefaultAdmin: "4",
DefaultAdminPwd: "4",
CookieTTL: 4,
CookieSecure: false,
CookieHttpOnly: false,
MinUserNameLen: 4,
MinPwdLen: 4,
CaptchaWidth: 4,
CaptchaHeight: 4,
CaptchaEnabled: false,
UploadSpeedLimit: 4,
DownloadSpeedLimit: 4,
SpaceLimit: 4,
LimiterCapacity: 4,
LimiterCyc: 4,
PredefinedUsers: []*userstore.UserCfg{
&userstore.UserCfg{
Name: "4",
Pwd: "4",
Role: "4",
},
},
},
Secrets: &Secrets{
TokenSecret: "4",
},
Server: &ServerCfg{
Debug: false,
Host: "4",
Port: 4,
ReadTimeout: 4,
WriteTimeout: 4,
MaxHeaderBytes: 4,
PublicPath: "4",
},
Workers: &WorkerPoolCfg{
QueueSize: 4,
SleepCyc: 4,
WorkerCount: 4,
},
Site: &sitestore.SiteConfig{
ClientCfg: &sitestore.ClientConfig{
SiteName: "4",
SiteDesc: "4",
Bg: &sitestore.BgConfig{
Url: "4",
Repeat: "4",
Position: "4",
Align: "4",
},
},
},
}
cfg5 := &Config{
Fs: &FSConfig{
Root: "4",
OpensLimit: 4,
OpenTTL: 4,
},
Users: &UsersCfg{
EnableAuth: true,
DefaultAdmin: "5",
DefaultAdminPwd: "5",
CookieTTL: 5,
CookieSecure: true,
CookieHttpOnly: true,
MinUserNameLen: 5,
MinPwdLen: 5,
CaptchaWidth: 5,
CaptchaHeight: 5,
CaptchaEnabled: true,
UploadSpeedLimit: 5,
DownloadSpeedLimit: 5,
SpaceLimit: 5,
LimiterCapacity: 5,
LimiterCyc: 5,
PredefinedUsers: []*userstore.UserCfg{
&userstore.UserCfg{
Name: "5",
Pwd: "5",
Role: "5",
},
},
},
Secrets: &Secrets{
TokenSecret: "4",
},
Server: &ServerCfg{
Debug: false,
Host: "4",
Port: 4,
ReadTimeout: 4,
WriteTimeout: 4,
MaxHeaderBytes: 4,
PublicPath: "4",
},
Workers: &WorkerPoolCfg{
QueueSize: 4,
SleepCyc: 4,
WorkerCount: 4,
},
Site: &sitestore.SiteConfig{
ClientCfg: &sitestore.ClientConfig{
SiteName: "4",
SiteDesc: "4",
Bg: &sitestore.BgConfig{
Url: "4",
Repeat: "4",
Position: "4",
Align: "4",
},
},
},
}
expects := []*Config{
cfg1,
cfg2,
cfg3,
cfg4,
cfg5,
}
testLoadCfg := func(t *testing.T) {
for i, opts := range argsList {
gotCfg, err := LoadCfg(opts)
if err != nil {
t.Fatal(err)
}
expectCfgStruct := expects[i]
expectCfgBytes, err := json.Marshal(expectCfgStruct)
if err != nil {
t.Fatal(err)
}
expectCfg, err := gocfg.New(NewConfig()).Load(gocfg.JSONStr(string(expectCfgBytes)))
if err != nil {
t.Fatal(err)
}
if !Equal(gotCfg, expectCfg) {
t.Fatal("cfgs are not identical")
}
}
}
t.Run("test LoadCfg", testLoadCfg)
}
// TODO: use better comparing method
func Equal(cfg1, cfg2 *gocfg.Cfg) bool {
// check cfg1
for k1, v1 := range cfg1.Bools() {
v2, ok := cfg2.Bool(k1)
if !ok || v2 != v1 {
fmt.Println(k1, v1, v2)
return false
}
}
for k1, v1 := range cfg1.Ints() {
v2, ok := cfg2.Int(k1)
if !ok || v2 != v1 {
fmt.Println(k1, v1, v2)
return false
}
}
for k1, v1 := range cfg1.Floats() {
v2, ok := cfg2.Float(k1)
if !ok || v2 != v1 {
fmt.Println(k1, v1, v2)
return false
}
}
for k1, v1 := range cfg1.Strings() {
v2, ok := cfg2.String(k1)
if !ok || v2 != v1 {
fmt.Println(k1, v1, v2)
return false
}
}
// check cfg2
for k2, v2 := range cfg2.Bools() {
v1, ok := cfg1.Bool(k2)
if !ok || v1 != v2 {
fmt.Println(k2, v1, v2)
return false
}
}
for k2, v2 := range cfg2.Ints() {
v1, ok := cfg1.Int(k2)
if !ok || v1 != v2 {
fmt.Println(k2, v1, v2)
return false
}
}
for k2, v2 := range cfg2.Floats() {
v1, ok := cfg1.Float(k2)
if !ok || v1 != v2 {
fmt.Println(k2, v1, v2)
return false
}
}
for k2, v2 := range cfg2.Strings() {
v1, ok := cfg1.String(k2)
if !ok || v1 != v2 {
fmt.Println(k2, v1, v2)
return false
}
}
return true
}

48
src/server/testdata/config_1.yml vendored Normal file
View file

@ -0,0 +1,48 @@
fs:
root: "1"
opensLimit: 1
openTTL: 1
secrets:
tokenSecret: "1"
server:
debug: true
host: "1"
port: 1
readTimeout: 1
writeTimeout: 1
maxHeaderBytes: 1
publicPath: "1"
users:
enableAuth: true
defaultAdmin: "1"
defaultAdminPwd: "1"
cookieTTL: 1
cookieSecure: true
cookieHttpOnly: true
minUserNameLen: 1
minPwdLen: 1
captchaWidth: 1
captchaHeight: 1
captchaEnabled: true
uploadSpeedLimit: 1
downloadSpeedLimit: 1
spaceLimit: 1
limiterCapacity: 1
limiterCyc: 1
predefinedUsers:
- name: "1"
pwd: "1"
role: "1"
workers:
queueSize: 1
sleepCyc: 1
workerCount: 1
site:
clientCfg:
siteName: "1"
siteDesc: "1"
bg:
url: "1"
repeat: "1"
position: "1"
align: "1"

48
src/server/testdata/config_4.yml vendored Normal file
View file

@ -0,0 +1,48 @@
fs:
root: "4"
opensLimit: 4
openTTL: 4
secrets:
tokenSecret: "4"
server:
debug: false
host: "4"
port: 4
readTimeout: 4
writeTimeout: 4
maxHeaderBytes: 4
publicPath: "4"
users:
enableAuth: false
defaultAdmin: "4"
defaultAdminPwd: "4"
cookieTTL: 4
cookieSecure: false
cookieHttpOnly: false
minUserNameLen: 4
minPwdLen: 4
captchaWidth: 4
captchaHeight: 4
captchaEnabled: false
uploadSpeedLimit: 4
downloadSpeedLimit: 4
spaceLimit: 4
limiterCapacity: 4
limiterCyc: 4
predefinedUsers:
- name: "4"
pwd: "4"
role: "4"
workers:
queueSize: 4
sleepCyc: 4
workerCount: 4
site:
clientCfg:
siteName: "4"
siteDesc: "4"
bg:
url: "4"
repeat: "4"
position: "4"
align: "4"

View file

@ -0,0 +1,21 @@
users:
enableAuth: true
defaultAdmin: "5"
defaultAdminPwd: "5"
cookieTTL: 5
cookieSecure: true
cookieHttpOnly: true
minUserNameLen: 5
minPwdLen: 5
captchaWidth: 5
captchaHeight: 5
captchaEnabled: true
uploadSpeedLimit: 5
downloadSpeedLimit: 5
spaceLimit: 5
limiterCapacity: 5
limiterCyc: 5
predefinedUsers:
- name: "5"
pwd: "5"
role: "5"