Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor config provider #24245

Merged
merged 16 commits into from
Apr 25, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions cmd/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (

"github.com/felixge/fgprof"
"github.com/urfave/cli"
ini "gopkg.in/ini.v1"
)

// PIDFile could be set from build tag
Expand Down Expand Up @@ -223,9 +222,10 @@ func setPort(port string) error {
defaultLocalURL += ":" + setting.HTTPPort + "/"

// Save LOCAL_ROOT_URL if port changed
setting.CreateOrAppendToCustomConf("server.LOCAL_ROOT_URL", func(cfg *ini.File) {
cfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL)
})
setting.CfgProvider.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL)
if err := setting.CfgProvider.Save(); err != nil {
lunny marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("Failed to save config file: %v", err)
}
}
return nil
}
Expand Down
3 changes: 1 addition & 2 deletions modules/indexer/issues/indexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import (
_ "code.gitea.io/gitea/models"

"github.com/stretchr/testify/assert"
"gopkg.in/ini.v1"
)

func TestMain(m *testing.M) {
Expand All @@ -27,7 +26,7 @@ func TestMain(m *testing.M) {

func TestBleveSearchIssues(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
setting.CfgProvider = ini.Empty()
setting.CfgProvider = setting.NewEmptyConfigProvider()

tmpIndexerDir := t.TempDir()

Expand Down
3 changes: 1 addition & 2 deletions modules/indexer/stats/indexer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
_ "code.gitea.io/gitea/models"

"github.com/stretchr/testify/assert"
"gopkg.in/ini.v1"
)

func TestMain(m *testing.M) {
Expand All @@ -29,7 +28,7 @@ func TestMain(m *testing.M) {

func TestRepoStatsIndex(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
setting.CfgProvider = ini.Empty()
setting.CfgProvider = setting.NewEmptyConfigProvider()

setting.LoadQueueSettings()

Expand Down
141 changes: 137 additions & 4 deletions modules/setting/config_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,153 @@
package setting

import (
"fmt"
"os"
"path/filepath"

"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"

ini "gopkg.in/ini.v1"
)

type ConfigSection interface {
Name() string
MapTo(interface{}) error
HasKey(key string) bool
NewKey(name, value string) (*ini.Key, error)
Key(key string) *ini.Key
Keys() []*ini.Key
ChildSections() []*ini.Section
lunny marked this conversation as resolved.
Show resolved Hide resolved
}

// ConfigProvider represents a config provider
type ConfigProvider interface {
Section(section string) *ini.Section
NewSection(name string) (*ini.Section, error)
GetSection(name string) (*ini.Section, error)
Section(section string) ConfigSection
NewSection(name string) (ConfigSection, error)
GetSection(name string) (ConfigSection, error)
DeleteSection(name string) error
Save() error
}

type iniFileConfigProvider struct {
*ini.File
filepath string // the ini file path
newFile bool // whether the file has not existed previously
allowEmpty bool // whether not finding configuration files is allowed (only true for the tests)
}

// NewEmptyConfigProvider create a new empty config provider
func NewEmptyConfigProvider() ConfigProvider {
cfg := ini.Empty()
cfg.NameMapper = ini.SnackCase
return &iniFileConfigProvider{
File: cfg,
newFile: true,
}
}

func newConfigProviderFromData(bs []byte) (ConfigProvider, error) {
cfg, err := ini.Load(bs)
if err != nil {
return nil, err
}
cfg.NameMapper = ini.SnackCase
return &iniFileConfigProvider{
File: cfg,
newFile: true,
}, nil
}

// newConfigProviderFromFile load configuration from file.
// NOTE: do not print any log except error.
func newConfigProviderFromFile(customConf string, allowEmpty bool, extraConfig string) (*iniFileConfigProvider, error) {
cfg := ini.Empty()
newFile := true

if customConf != "" {
isFile, err := util.IsFile(customConf)
if err != nil {
return nil, fmt.Errorf("unable to check if %s is a file. Error: %v", customConf, err)
}
if isFile {
if err := cfg.Append(customConf); err != nil {
return nil, fmt.Errorf("failed to load custom conf '%s': %v", customConf, err)
}
newFile = false
}
}

if newFile && !allowEmpty {
return nil, fmt.Errorf("unable to find configuration file: %q, please ensure you are running in the correct environment or set the correct configuration file with -c", CustomConf)
}

if extraConfig != "" {
if err := cfg.Append([]byte(extraConfig)); err != nil {
return nil, fmt.Errorf("unable to append more config: %v", err)
}
}

cfg.NameMapper = ini.SnackCase
return &iniFileConfigProvider{
File: cfg,
filepath: customConf,
newFile: newFile,
allowEmpty: allowEmpty,
}, nil
}

func (p *iniFileConfigProvider) Section(section string) ConfigSection {
return p.File.Section(section)
}

func (p *iniFileConfigProvider) NewSection(name string) (ConfigSection, error) {
return p.File.NewSection(name)
}

func (p *iniFileConfigProvider) GetSection(name string) (ConfigSection, error) {
return p.File.GetSection(name)
}

func (p *iniFileConfigProvider) DeleteSection(name string) error {
p.File.DeleteSection(name)
return nil
}

// Save save the content into file
func (p *iniFileConfigProvider) Save() error {
if p.filepath == "" {
if !p.allowEmpty {
return fmt.Errorf("custom config path must not be empty")
}
return nil
}

if p.newFile {
if err := os.MkdirAll(filepath.Dir(CustomConf), os.ModePerm); err != nil {
return fmt.Errorf("failed to create '%s': %v", CustomConf, err)
}
}
if err := p.SaveTo(p.filepath); err != nil {
return fmt.Errorf("failed to save '%s': %v", p.filepath, err)
}

// Change permissions to be more restrictive
fi, err := os.Stat(CustomConf)
if err != nil {
return fmt.Errorf("failed to determine current conf file permissions: %v", err)
}

if fi.Mode().Perm() > 0o600 {
if err = os.Chmod(CustomConf, 0o600); err != nil {
log.Warn("Failed changing conf file permissions to -rw-------. Consider changing them manually.")
}
}
return nil
}

// a file is an implementation ConfigProvider and other implementations are possible, i.e. from docker, k8s, …
var _ ConfigProvider = &ini.File{}
var _ ConfigProvider = &iniFileConfigProvider{}

func mustMapSetting(rootCfg ConfigProvider, sectionName string, setting interface{}) {
if err := rootCfg.Section(sectionName).MapTo(setting); err != nil {
Expand Down
3 changes: 1 addition & 2 deletions modules/setting/cron_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"testing"

"github.com/stretchr/testify/assert"
ini "gopkg.in/ini.v1"
)

func Test_getCronSettings(t *testing.T) {
Expand All @@ -27,7 +26,7 @@ Base = true
Second = white rabbit
Extend = true
`
cfg, err := ini.Load([]byte(iniStr))
cfg, err := newConfigProviderFromData([]byte(iniStr))
assert.NoError(t, err)

extended := &Extended{
Expand Down
13 changes: 6 additions & 7 deletions modules/setting/lfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import (

"code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/log"

ini "gopkg.in/ini.v1"
)

// LFS represents the configuration for Git LFS
Expand Down Expand Up @@ -38,8 +36,7 @@ func loadLFSFrom(rootCfg ConfigProvider) {
// DEPRECATED should not be removed because users maybe upgrade from lower version to the latest version
// if these are removed, the warning will not be shown
deprecatedSetting(rootCfg, "server", "LFS_CONTENT_PATH", "lfs", "PATH", "v1.19.0")
lfsSec.Key("PATH").MustString(
sec.Key("LFS_CONTENT_PATH").String())
lfsSec.Key("PATH").MustString(sec.Key("LFS_CONTENT_PATH").String())

LFS.Storage = getStorage(rootCfg, "lfs", storageType, lfsSec)

Expand All @@ -62,9 +59,11 @@ func loadLFSFrom(rootCfg ConfigProvider) {
}

// Save secret
CreateOrAppendToCustomConf("server.LFS_JWT_SECRET", func(cfg *ini.File) {
cfg.Section("server").Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64)
})
sec.Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64)
if err := rootCfg.Save(); err != nil {
log.Fatal("Error saving JWT Secret for custom config: %v", err)
return
}
}
}
}
8 changes: 3 additions & 5 deletions modules/setting/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ import (
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"

ini "gopkg.in/ini.v1"
)

var (
Expand Down Expand Up @@ -131,12 +129,12 @@ type LogDescription struct {
SubLogDescriptions []SubLogDescription
}

func getLogLevel(section *ini.Section, key string, defaultValue log.Level) log.Level {
func getLogLevel(section ConfigSection, key string, defaultValue log.Level) log.Level {
value := section.Key(key).MustString(defaultValue.String())
return log.FromString(value)
}

func getStacktraceLogLevel(section *ini.Section, key, defaultValue string) string {
func getStacktraceLogLevel(section ConfigSection, key, defaultValue string) string {
value := section.Key(key).MustString(defaultValue)
return log.FromString(value).String()
}
Expand Down Expand Up @@ -165,7 +163,7 @@ func loadLogFrom(rootCfg ConfigProvider) {
Log.EnableXORMLog = rootCfg.Section("log").Key("ENABLE_XORM_LOG").MustBool(true)
}

func generateLogConfig(sec *ini.Section, name string, defaults defaultLogOptions) (mode, jsonConfig, levelName string) {
func generateLogConfig(sec ConfigSection, name string, defaults defaultLogOptions) (mode, jsonConfig, levelName string) {
level := getLogLevel(sec, "LEVEL", Log.Level)
levelName = level.String()
stacktraceLevelName := getStacktraceLogLevel(sec, "STACKTRACE_LEVEL", Log.StacktraceLogLevel)
Expand Down
3 changes: 1 addition & 2 deletions modules/setting/mailer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ import (
"testing"

"github.com/stretchr/testify/assert"
ini "gopkg.in/ini.v1"
)

func Test_loadMailerFrom(t *testing.T) {
iniFile := ini.Empty()
iniFile := NewEmptyConfigProvider()
kases := map[string]*Mailer{
"smtp.mydomain.com": {
SMTPAddr: "smtp.mydomain.com",
Expand Down
8 changes: 3 additions & 5 deletions modules/setting/markup.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import (
"strings"

"code.gitea.io/gitea/modules/log"

"gopkg.in/ini.v1"
)

// ExternalMarkupRenderers represents the external markup renderers
Expand Down Expand Up @@ -82,7 +80,7 @@ func loadMarkupFrom(rootCfg ConfigProvider) {
}
}

func newMarkupSanitizer(name string, sec *ini.Section) {
func newMarkupSanitizer(name string, sec ConfigSection) {
rule, ok := createMarkupSanitizerRule(name, sec)
if ok {
if strings.HasPrefix(name, "sanitizer.") {
Expand All @@ -99,7 +97,7 @@ func newMarkupSanitizer(name string, sec *ini.Section) {
}
}

func createMarkupSanitizerRule(name string, sec *ini.Section) (MarkupSanitizerRule, bool) {
func createMarkupSanitizerRule(name string, sec ConfigSection) (MarkupSanitizerRule, bool) {
var rule MarkupSanitizerRule

ok := false
Expand Down Expand Up @@ -141,7 +139,7 @@ func createMarkupSanitizerRule(name string, sec *ini.Section) (MarkupSanitizerRu
return rule, true
}

func newMarkupRenderer(name string, sec *ini.Section) {
func newMarkupRenderer(name string, sec ConfigSection) {
extensionReg := regexp.MustCompile(`\.\w`)

extensions := sec.Key("FILE_EXTENSIONS").Strings(",")
Expand Down
21 changes: 18 additions & 3 deletions modules/setting/oauth2.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
package setting

import (
"encoding/base64"
"math"
"path/filepath"

"code.gitea.io/gitea/modules/generate"
"code.gitea.io/gitea/modules/log"

"gopkg.in/ini.v1"
)

// OAuth2UsernameType is enum describing the way gitea 'name' should be generated from oauth2 data
Expand Down Expand Up @@ -80,7 +80,7 @@ func loadOAuth2ClientFrom(rootCfg ConfigProvider) {
}
}

func parseScopes(sec *ini.Section, name string) []string {
func parseScopes(sec ConfigSection, name string) []string {
parts := sec.Key(name).Strings(" ")
scopes := make([]string, 0, len(parts))
for _, scope := range parts {
Expand Down Expand Up @@ -119,4 +119,19 @@ func loadOAuth2From(rootCfg ConfigProvider) {
if !filepath.IsAbs(OAuth2.JWTSigningPrivateKeyFile) {
OAuth2.JWTSigningPrivateKeyFile = filepath.Join(AppDataPath, OAuth2.JWTSigningPrivateKeyFile)
}

key := make([]byte, 32)
n, err := base64.RawURLEncoding.Decode(key, []byte(OAuth2.JWTSecretBase64))
if err != nil || n != 32 {
key, err = generate.NewJwtSecret()
if err != nil {
log.Fatal("error generating JWT secret: %v", err)
}

secretBase64 := base64.RawURLEncoding.EncodeToString(key)
rootCfg.Section("oauth2").Key("JWT_SECRET").SetValue(secretBase64)
if err := rootCfg.Save(); err != nil {
log.Fatal("save oauth2.JWT_SECRET failed: %v", err)
}
}
}
Loading