mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 19:06:18 +01:00 
			
		
		
		
	Refactor INI package (first step) (#25024)
The INI package has many bugs and quirks, and in fact it is unmaintained. This PR is the first step for the INI package refactoring: * Use Gitea's "config_provider" to provide INI access * Deprecate the INI package by golangci.yml rule
This commit is contained in:
		| @@ -86,6 +86,7 @@ linters-settings: | |||||||
|       - io/ioutil: "use os or io instead" |       - io/ioutil: "use os or io instead" | ||||||
|       - golang.org/x/exp: "it's experimental and unreliable." |       - golang.org/x/exp: "it's experimental and unreliable." | ||||||
|       - code.gitea.io/gitea/modules/git/internal: "do not use the internal package, use AddXxx function instead" |       - code.gitea.io/gitea/modules/git/internal: "do not use the internal package, use AddXxx function instead" | ||||||
|  |       - gopkg.in/ini.v1: "do not use the ini package, use gitea's config system instead" | ||||||
|  |  | ||||||
| issues: | issues: | ||||||
|   max-issues-per-linter: 0 |   max-issues-per-linter: 0 | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ import ( | |||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	"gopkg.in/ini.v1" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func main() { | func main() { | ||||||
| @@ -22,14 +22,13 @@ func main() { | |||||||
| 		os.Exit(1) | 		os.Exit(1) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ini.PrettyFormat = false |  | ||||||
| 	mustNoErr := func(err error) { | 	mustNoErr := func(err error) { | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			panic(err) | 			panic(err) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	collectInis := func(ref string) map[string]*ini.File { | 	collectInis := func(ref string) map[string]setting.ConfigProvider { | ||||||
| 		inis := map[string]*ini.File{} | 		inis := map[string]setting.ConfigProvider{} | ||||||
| 		err := filepath.WalkDir("options/locale", func(path string, d os.DirEntry, err error) error { | 		err := filepath.WalkDir("options/locale", func(path string, d os.DirEntry, err error) error { | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return err | 				return err | ||||||
| @@ -37,10 +36,7 @@ func main() { | |||||||
| 			if d.IsDir() || !strings.HasSuffix(d.Name(), ".ini") { | 			if d.IsDir() || !strings.HasSuffix(d.Name(), ".ini") { | ||||||
| 				return nil | 				return nil | ||||||
| 			} | 			} | ||||||
| 			cfg, err := ini.LoadSources(ini.LoadOptions{ | 			cfg, err := setting.NewConfigProviderForLocale(path) | ||||||
| 				IgnoreInlineComment:         true, |  | ||||||
| 				UnescapeValueCommentSymbols: true, |  | ||||||
| 			}, path) |  | ||||||
| 			mustNoErr(err) | 			mustNoErr(err) | ||||||
| 			inis[path] = cfg | 			inis[path] = cfg | ||||||
| 			fmt.Printf("collecting: %s @ %s\n", path, ref) | 			fmt.Printf("collecting: %s @ %s\n", path, ref) | ||||||
|   | |||||||
| @@ -9,10 +9,8 @@ import ( | |||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/util" |  | ||||||
|  |  | ||||||
| 	"github.com/urfave/cli" | 	"github.com/urfave/cli" | ||||||
| 	"gopkg.in/ini.v1" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // EnvironmentPrefix environment variables prefixed with this represent ini values to write | // EnvironmentPrefix environment variables prefixed with this represent ini values to write | ||||||
| @@ -97,19 +95,10 @@ func runEnvironmentToIni(c *cli.Context) error { | |||||||
| 	providedWorkPath := c.String("work-path") | 	providedWorkPath := c.String("work-path") | ||||||
| 	setting.SetCustomPathAndConf(providedCustom, providedConf, providedWorkPath) | 	setting.SetCustomPathAndConf(providedCustom, providedConf, providedWorkPath) | ||||||
|  |  | ||||||
| 	cfg := ini.Empty() | 	cfg, err := setting.NewConfigProviderFromFile(&setting.Options{CustomConf: setting.CustomConf, AllowEmpty: true}) | ||||||
| 	confFileExists, err := util.IsFile(setting.CustomConf) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Fatal("Unable to check if %s is a file. Error: %v", setting.CustomConf, err) | 		log.Fatal("Failed to load custom conf '%s': %v", setting.CustomConf, err) | ||||||
| 	} | 	} | ||||||
| 	if confFileExists { |  | ||||||
| 		if err := cfg.Append(setting.CustomConf); err != nil { |  | ||||||
| 			log.Fatal("Failed to load custom conf '%s': %v", setting.CustomConf, err) |  | ||||||
| 		} |  | ||||||
| 	} else { |  | ||||||
| 		log.Warn("Custom config '%s' not found, ignore this if you're running first time", setting.CustomConf) |  | ||||||
| 	} |  | ||||||
| 	cfg.NameMapper = ini.SnackCase |  | ||||||
|  |  | ||||||
| 	prefixGitea := c.String("prefix") + "__" | 	prefixGitea := c.String("prefix") + "__" | ||||||
| 	suffixFile := "__FILE" | 	suffixFile := "__FILE" | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/timeutil" | 	"code.gitea.io/gitea/modules/timeutil" | ||||||
| 	"code.gitea.io/gitea/modules/util" | 	"code.gitea.io/gitea/modules/util" | ||||||
|  |  | ||||||
| 	"gopkg.in/ini.v1" | 	"gopkg.in/ini.v1" //nolint:depguard | ||||||
| ) | ) | ||||||
|  |  | ||||||
| /* | /* | ||||||
| @@ -241,7 +241,7 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, | |||||||
| // cleanUpMigrateGitConfig removes mirror info which prevents "push --all". | // cleanUpMigrateGitConfig removes mirror info which prevents "push --all". | ||||||
| // This also removes possible user credentials. | // This also removes possible user credentials. | ||||||
| func cleanUpMigrateGitConfig(configPath string) error { | func cleanUpMigrateGitConfig(configPath string) error { | ||||||
| 	cfg, err := ini.Load(configPath) | 	cfg, err := ini.Load(configPath) // FIXME: the ini package doesn't really work with git config files | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("open config file: %w", err) | 		return fmt.Errorf("open config file: %w", err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -10,8 +10,6 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
|  |  | ||||||
| 	"gopkg.in/ini.v1" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const escapeRegexpString = "_0[xX](([0-9a-fA-F][0-9a-fA-F])+)_" | const escapeRegexpString = "_0[xX](([0-9a-fA-F][0-9a-fA-F])+)_" | ||||||
| @@ -89,7 +87,7 @@ func decodeEnvironmentKey(prefixGitea, suffixFile, envKey string) (ok bool, sect | |||||||
| 	return ok, section, key, useFileValue | 	return ok, section, key, useFileValue | ||||||
| } | } | ||||||
|  |  | ||||||
| func EnvironmentToConfig(cfg *ini.File, prefixGitea, suffixFile string, envs []string) (changed bool) { | func EnvironmentToConfig(cfg ConfigProvider, prefixGitea, suffixFile string, envs []string) (changed bool) { | ||||||
| 	for _, kv := range envs { | 	for _, kv := range envs { | ||||||
| 		idx := strings.IndexByte(kv, '=') | 		idx := strings.IndexByte(kv, '=') | ||||||
| 		if idx < 0 { | 		if idx < 0 { | ||||||
|   | |||||||
| @@ -8,7 +8,6 @@ import ( | |||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| 	"gopkg.in/ini.v1" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestDecodeEnvSectionKey(t *testing.T) { | func TestDecodeEnvSectionKey(t *testing.T) { | ||||||
| @@ -71,15 +70,15 @@ func TestDecodeEnvironmentKey(t *testing.T) { | |||||||
| } | } | ||||||
|  |  | ||||||
| func TestEnvironmentToConfig(t *testing.T) { | func TestEnvironmentToConfig(t *testing.T) { | ||||||
| 	cfg := ini.Empty() | 	cfg, _ := NewConfigProviderFromData("") | ||||||
|  |  | ||||||
| 	changed := EnvironmentToConfig(cfg, "GITEA__", "__FILE", nil) | 	changed := EnvironmentToConfig(cfg, "GITEA__", "__FILE", nil) | ||||||
| 	assert.False(t, changed) | 	assert.False(t, changed) | ||||||
|  |  | ||||||
| 	cfg, err := ini.Load([]byte(` | 	cfg, err := NewConfigProviderFromData(` | ||||||
| [sec] | [sec] | ||||||
| key = old | key = old | ||||||
| `)) | `) | ||||||
| 	assert.NoError(t, err) | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
| 	changed = EnvironmentToConfig(cfg, "GITEA__", "__FILE", []string{"GITEA__sec__key=new"}) | 	changed = EnvironmentToConfig(cfg, "GITEA__", "__FILE", []string{"GITEA__sec__key=new"}) | ||||||
|   | |||||||
| @@ -8,36 +8,71 @@ import ( | |||||||
| 	"os" | 	"os" | ||||||
| 	"path/filepath" | 	"path/filepath" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/util" | 	"code.gitea.io/gitea/modules/util" | ||||||
|  |  | ||||||
| 	ini "gopkg.in/ini.v1" | 	"gopkg.in/ini.v1" //nolint:depguard | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | type ConfigKey interface { | ||||||
|  | 	Name() string | ||||||
|  | 	Value() string | ||||||
|  | 	SetValue(v string) | ||||||
|  |  | ||||||
|  | 	In(defaultVal string, candidates []string) string | ||||||
|  | 	String() string | ||||||
|  | 	Strings(delim string) []string | ||||||
|  |  | ||||||
|  | 	MustString(defaultVal string) string | ||||||
|  | 	MustBool(defaultVal ...bool) bool | ||||||
|  | 	MustInt(defaultVal ...int) int | ||||||
|  | 	MustInt64(defaultVal ...int64) int64 | ||||||
|  | 	MustDuration(defaultVal ...time.Duration) time.Duration | ||||||
|  | } | ||||||
|  |  | ||||||
| type ConfigSection interface { | type ConfigSection interface { | ||||||
| 	Name() string | 	Name() string | ||||||
| 	MapTo(interface{}) error | 	MapTo(any) error | ||||||
| 	HasKey(key string) bool | 	HasKey(key string) bool | ||||||
| 	NewKey(name, value string) (*ini.Key, error) | 	NewKey(name, value string) (ConfigKey, error) | ||||||
| 	Key(key string) *ini.Key | 	Key(key string) ConfigKey | ||||||
| 	Keys() []*ini.Key | 	Keys() []ConfigKey | ||||||
| 	ChildSections() []*ini.Section | 	ChildSections() []ConfigSection | ||||||
| } | } | ||||||
|  |  | ||||||
| // ConfigProvider represents a config provider | // ConfigProvider represents a config provider | ||||||
| type ConfigProvider interface { | type ConfigProvider interface { | ||||||
| 	Section(section string) ConfigSection | 	Section(section string) ConfigSection | ||||||
|  | 	Sections() []ConfigSection | ||||||
| 	NewSection(name string) (ConfigSection, error) | 	NewSection(name string) (ConfigSection, error) | ||||||
| 	GetSection(name string) (ConfigSection, error) | 	GetSection(name string) (ConfigSection, error) | ||||||
| 	Save() error | 	Save() error | ||||||
|  | 	SaveTo(filename string) error | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type iniConfigProvider struct { | ||||||
|  | 	opts    *Options | ||||||
|  | 	ini     *ini.File | ||||||
|  | 	newFile bool // whether the file has not existed previously | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type iniConfigSection struct { | ||||||
|  | 	sec *ini.Section | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	_ ConfigProvider = (*iniConfigProvider)(nil) | ||||||
|  | 	_ ConfigSection  = (*iniConfigSection)(nil) | ||||||
|  | 	_ ConfigKey      = (*ini.Key)(nil) | ||||||
|  | ) | ||||||
|  |  | ||||||
| // ConfigSectionKey only searches the keys in the given section, but it is O(n). | // ConfigSectionKey only searches the keys in the given section, but it is O(n). | ||||||
| // ini package has a special behavior:  with "[sec] a=1" and an empty "[sec.sub]", | // ini package has a special behavior:  with "[sec] a=1" and an empty "[sec.sub]", | ||||||
| // then in "[sec.sub]", Key()/HasKey() can always see "a=1" because it always tries parent sections. | // then in "[sec.sub]", Key()/HasKey() can always see "a=1" because it always tries parent sections. | ||||||
| // It returns nil if the key doesn't exist. | // It returns nil if the key doesn't exist. | ||||||
| func ConfigSectionKey(sec ConfigSection, key string) *ini.Key { | func ConfigSectionKey(sec ConfigSection, key string) ConfigKey { | ||||||
| 	if sec == nil { | 	if sec == nil { | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| @@ -64,7 +99,7 @@ func ConfigSectionKeyString(sec ConfigSection, key string, def ...string) string | |||||||
| // and the returned key is safe to be used with "MustXxx", it doesn't change the parent's values. | // and the returned key is safe to be used with "MustXxx", it doesn't change the parent's values. | ||||||
| // Otherwise, ini.Section.Key().MustXxx would pollute the parent section's keys. | // Otherwise, ini.Section.Key().MustXxx would pollute the parent section's keys. | ||||||
| // It never returns nil. | // It never returns nil. | ||||||
| func ConfigInheritedKey(sec ConfigSection, key string) *ini.Key { | func ConfigInheritedKey(sec ConfigSection, key string) ConfigKey { | ||||||
| 	k := sec.Key(key) | 	k := sec.Key(key) | ||||||
| 	if k != nil && k.String() != "" { | 	if k != nil && k.String() != "" { | ||||||
| 		newKey, _ := sec.NewKey(k.Name(), k.String()) | 		newKey, _ := sec.NewKey(k.Name(), k.String()) | ||||||
| @@ -85,41 +120,64 @@ func ConfigInheritedKeyString(sec ConfigSection, key string, def ...string) stri | |||||||
| 	return "" | 	return "" | ||||||
| } | } | ||||||
|  |  | ||||||
| type iniFileConfigProvider struct { | func (s *iniConfigSection) Name() string { | ||||||
| 	opts *Options | 	return s.sec.Name() | ||||||
| 	*ini.File |  | ||||||
| 	newFile bool // whether the file has not existed previously |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // NewConfigProviderFromData this function is only for testing | func (s *iniConfigSection) MapTo(v any) error { | ||||||
|  | 	return s.sec.MapTo(v) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *iniConfigSection) HasKey(key string) bool { | ||||||
|  | 	return s.sec.HasKey(key) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *iniConfigSection) NewKey(name, value string) (ConfigKey, error) { | ||||||
|  | 	return s.sec.NewKey(name, value) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *iniConfigSection) Key(key string) ConfigKey { | ||||||
|  | 	return s.sec.Key(key) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *iniConfigSection) Keys() (keys []ConfigKey) { | ||||||
|  | 	for _, k := range s.sec.Keys() { | ||||||
|  | 		keys = append(keys, k) | ||||||
|  | 	} | ||||||
|  | 	return keys | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (s *iniConfigSection) ChildSections() (sections []ConfigSection) { | ||||||
|  | 	for _, s := range s.sec.ChildSections() { | ||||||
|  | 		sections = append(sections, &iniConfigSection{s}) | ||||||
|  | 	} | ||||||
|  | 	return sections | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NewConfigProviderFromData this function is mainly for testing purpose | ||||||
| func NewConfigProviderFromData(configContent string) (ConfigProvider, error) { | func NewConfigProviderFromData(configContent string) (ConfigProvider, error) { | ||||||
| 	var cfg *ini.File | 	cfg, err := ini.Load(strings.NewReader(configContent)) | ||||||
| 	var err error | 	if err != nil { | ||||||
| 	if configContent == "" { | 		return nil, err | ||||||
| 		cfg = ini.Empty() |  | ||||||
| 	} else { |  | ||||||
| 		cfg, err = ini.Load(strings.NewReader(configContent)) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| 	cfg.NameMapper = ini.SnackCase | 	cfg.NameMapper = ini.SnackCase | ||||||
| 	return &iniFileConfigProvider{ | 	return &iniConfigProvider{ | ||||||
| 		File:    cfg, | 		ini:     cfg, | ||||||
| 		newFile: true, | 		newFile: true, | ||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| type Options struct { | type Options struct { | ||||||
| 	CustomConf                string // the ini file path | 	CustomConf  string // the ini file path | ||||||
| 	AllowEmpty                bool   // whether not finding configuration files is allowed (only true for the tests) | 	AllowEmpty  bool   // whether not finding configuration files is allowed | ||||||
| 	ExtraConfig               string | 	ExtraConfig string | ||||||
| 	DisableLoadCommonSettings bool |  | ||||||
|  | 	DisableLoadCommonSettings bool // only used by "Init()", not used by "NewConfigProvider()" | ||||||
| } | } | ||||||
|  |  | ||||||
| // newConfigProviderFromFile load configuration from file. | // NewConfigProviderFromFile load configuration from file. | ||||||
| // NOTE: do not print any log except error. | // NOTE: do not print any log except error. | ||||||
| func newConfigProviderFromFile(opts *Options) (*iniFileConfigProvider, error) { | func NewConfigProviderFromFile(opts *Options) (ConfigProvider, error) { | ||||||
| 	cfg := ini.Empty() | 	cfg := ini.Empty() | ||||||
| 	newFile := true | 	newFile := true | ||||||
|  |  | ||||||
| @@ -147,61 +205,77 @@ func newConfigProviderFromFile(opts *Options) (*iniFileConfigProvider, error) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	cfg.NameMapper = ini.SnackCase | 	cfg.NameMapper = ini.SnackCase | ||||||
| 	return &iniFileConfigProvider{ | 	return &iniConfigProvider{ | ||||||
| 		opts:    opts, | 		opts:    opts, | ||||||
| 		File:    cfg, | 		ini:     cfg, | ||||||
| 		newFile: newFile, | 		newFile: newFile, | ||||||
| 	}, nil | 	}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p *iniFileConfigProvider) Section(section string) ConfigSection { | func (p *iniConfigProvider) Section(section string) ConfigSection { | ||||||
| 	return p.File.Section(section) | 	return &iniConfigSection{sec: p.ini.Section(section)} | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p *iniFileConfigProvider) NewSection(name string) (ConfigSection, error) { | func (p *iniConfigProvider) Sections() (sections []ConfigSection) { | ||||||
| 	return p.File.NewSection(name) | 	for _, s := range p.ini.Sections() { | ||||||
|  | 		sections = append(sections, &iniConfigSection{s}) | ||||||
|  | 	} | ||||||
|  | 	return sections | ||||||
| } | } | ||||||
|  |  | ||||||
| func (p *iniFileConfigProvider) GetSection(name string) (ConfigSection, error) { | func (p *iniConfigProvider) NewSection(name string) (ConfigSection, error) { | ||||||
| 	return p.File.GetSection(name) | 	sec, err := p.ini.NewSection(name) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &iniConfigSection{sec: sec}, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // Save save the content into file | func (p *iniConfigProvider) GetSection(name string) (ConfigSection, error) { | ||||||
| func (p *iniFileConfigProvider) Save() error { | 	sec, err := p.ini.GetSection(name) | ||||||
| 	if p.opts.CustomConf == "" { | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &iniConfigSection{sec: sec}, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Save saves the content into file | ||||||
|  | func (p *iniConfigProvider) Save() error { | ||||||
|  | 	filename := p.opts.CustomConf | ||||||
|  | 	if filename == "" { | ||||||
| 		if !p.opts.AllowEmpty { | 		if !p.opts.AllowEmpty { | ||||||
| 			return fmt.Errorf("custom config path must not be empty") | 			return fmt.Errorf("custom config path must not be empty") | ||||||
| 		} | 		} | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if p.newFile { | 	if p.newFile { | ||||||
| 		if err := os.MkdirAll(filepath.Dir(CustomConf), os.ModePerm); err != nil { | 		if err := os.MkdirAll(filepath.Dir(filename), os.ModePerm); err != nil { | ||||||
| 			return fmt.Errorf("failed to create '%s': %v", CustomConf, err) | 			return fmt.Errorf("failed to create '%s': %v", filename, err) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if err := p.SaveTo(p.opts.CustomConf); err != nil { | 	if err := p.ini.SaveTo(filename); err != nil { | ||||||
| 		return fmt.Errorf("failed to save '%s': %v", p.opts.CustomConf, err) | 		return fmt.Errorf("failed to save '%s': %v", filename, err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Change permissions to be more restrictive | 	// Change permissions to be more restrictive | ||||||
| 	fi, err := os.Stat(CustomConf) | 	fi, err := os.Stat(filename) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("failed to determine current conf file permissions: %v", err) | 		return fmt.Errorf("failed to determine current conf file permissions: %v", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if fi.Mode().Perm() > 0o600 { | 	if fi.Mode().Perm() > 0o600 { | ||||||
| 		if err = os.Chmod(CustomConf, 0o600); err != nil { | 		if err = os.Chmod(filename, 0o600); err != nil { | ||||||
| 			log.Warn("Failed changing conf file permissions to -rw-------. Consider changing them manually.") | 			log.Warn("Failed changing conf file permissions to -rw-------. Consider changing them manually.") | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| // a file is an implementation ConfigProvider and other implementations are possible, i.e. from docker, k8s, … | func (p *iniConfigProvider) SaveTo(filename string) error { | ||||||
| var _ ConfigProvider = &iniFileConfigProvider{} | 	return p.ini.SaveTo(filename) | ||||||
|  | } | ||||||
|  |  | ||||||
| func mustMapSetting(rootCfg ConfigProvider, sectionName string, setting interface{}) { | func mustMapSetting(rootCfg ConfigProvider, sectionName string, setting any) { | ||||||
| 	if err := rootCfg.Section(sectionName).MapTo(setting); err != nil { | 	if err := rootCfg.Section(sectionName).MapTo(setting); err != nil { | ||||||
| 		log.Fatal("Failed to map %s settings: %v", sectionName, err) | 		log.Fatal("Failed to map %s settings: %v", sectionName, err) | ||||||
| 	} | 	} | ||||||
| @@ -219,3 +293,23 @@ func deprecatedSettingDB(rootCfg ConfigProvider, oldSection, oldKey string) { | |||||||
| 		log.Error("Deprecated `[%s]` `%s` present which has been copied to database table sys_setting", oldSection, oldKey) | 		log.Error("Deprecated `[%s]` `%s` present which has been copied to database table sys_setting", oldSection, oldKey) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // NewConfigProviderForLocale loads locale configuration from source and others. "string" if for a local file path, "[]byte" is for INI content | ||||||
|  | func NewConfigProviderForLocale(source any, others ...any) (ConfigProvider, error) { | ||||||
|  | 	iniFile, err := ini.LoadSources(ini.LoadOptions{ | ||||||
|  | 		IgnoreInlineComment:         true, | ||||||
|  | 		UnescapeValueCommentSymbols: true, | ||||||
|  | 	}, source, others...) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("unable to load locale ini: %w", err) | ||||||
|  | 	} | ||||||
|  | 	iniFile.BlockMode = false | ||||||
|  | 	return &iniConfigProvider{ | ||||||
|  | 		ini:     iniFile, | ||||||
|  | 		newFile: true, | ||||||
|  | 	}, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	ini.PrettyFormat = false | ||||||
|  | } | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ | |||||||
| package setting | package setting | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"os" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| @@ -64,3 +65,57 @@ key = 123 | |||||||
| 	assert.Equal(t, "", ConfigSectionKeyString(sec, "empty")) | 	assert.Equal(t, "", ConfigSectionKeyString(sec, "empty")) | ||||||
| 	assert.Equal(t, "def", ConfigSectionKeyString(secSub, "empty")) | 	assert.Equal(t, "def", ConfigSectionKeyString(secSub, "empty")) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestNewConfigProviderFromFile(t *testing.T) { | ||||||
|  | 	_, err := NewConfigProviderFromFile(&Options{CustomConf: "no-such.ini", AllowEmpty: false}) | ||||||
|  | 	assert.ErrorContains(t, err, "unable to find configuration file") | ||||||
|  |  | ||||||
|  | 	// load non-existing file and save | ||||||
|  | 	testFile := t.TempDir() + "/test.ini" | ||||||
|  | 	testFile1 := t.TempDir() + "/test1.ini" | ||||||
|  | 	cfg, err := NewConfigProviderFromFile(&Options{CustomConf: testFile, AllowEmpty: true}) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  |  | ||||||
|  | 	sec, _ := cfg.NewSection("foo") | ||||||
|  | 	_, _ = sec.NewKey("k1", "a") | ||||||
|  | 	assert.NoError(t, cfg.Save()) | ||||||
|  | 	_, _ = sec.NewKey("k2", "b") | ||||||
|  | 	assert.NoError(t, cfg.SaveTo(testFile1)) | ||||||
|  |  | ||||||
|  | 	bs, err := os.ReadFile(testFile) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.Equal(t, "[foo]\nk1=a\n", string(bs)) | ||||||
|  |  | ||||||
|  | 	bs, err = os.ReadFile(testFile1) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.Equal(t, "[foo]\nk1=a\nk2=b\n", string(bs)) | ||||||
|  |  | ||||||
|  | 	// load existing file and save | ||||||
|  | 	cfg, err = NewConfigProviderFromFile(&Options{CustomConf: testFile, AllowEmpty: true}) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.Equal(t, "a", cfg.Section("foo").Key("k1").String()) | ||||||
|  | 	sec, _ = cfg.NewSection("bar") | ||||||
|  | 	_, _ = sec.NewKey("k1", "b") | ||||||
|  | 	assert.NoError(t, cfg.Save()) | ||||||
|  | 	bs, err = os.ReadFile(testFile) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.Equal(t, "[foo]\nk1=a\n\n[bar]\nk1=b\n", string(bs)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestNewConfigProviderForLocale(t *testing.T) { | ||||||
|  | 	// load locale from file | ||||||
|  | 	localeFile := t.TempDir() + "/locale.ini" | ||||||
|  | 	_ = os.WriteFile(localeFile, []byte(`k1=a`), 0o644) | ||||||
|  | 	cfg, err := NewConfigProviderForLocale(localeFile) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.Equal(t, "a", cfg.Section("").Key("k1").String()) | ||||||
|  |  | ||||||
|  | 	// load locale from bytes | ||||||
|  | 	cfg, err = NewConfigProviderForLocale([]byte("k1=foo\nk2=bar")) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.Equal(t, "foo", cfg.Section("").Key("k1").String()) | ||||||
|  | 	cfg, err = NewConfigProviderForLocale([]byte("k1=foo\nk2=bar"), []byte("k2=xxx")) | ||||||
|  | 	assert.NoError(t, err) | ||||||
|  | 	assert.Equal(t, "foo", cfg.Section("").Key("k1").String()) | ||||||
|  | 	assert.Equal(t, "xxx", cfg.Section("").Key("k2").String()) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -201,7 +201,7 @@ func Init(opts *Options) { | |||||||
| 		opts.CustomConf = CustomConf | 		opts.CustomConf = CustomConf | ||||||
| 	} | 	} | ||||||
| 	var err error | 	var err error | ||||||
| 	CfgProvider, err = newConfigProviderFromFile(opts) | 	CfgProvider, err = NewConfigProviderFromFile(opts) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Fatal("Init[%v]: %v", opts, err) | 		log.Fatal("Init[%v]: %v", opts, err) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -7,8 +7,7 @@ import ( | |||||||
| 	"fmt" | 	"fmt" | ||||||
|  |  | ||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
|  | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"gopkg.in/ini.v1" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // This file implements the static LocaleStore that will not watch for changes | // This file implements the static LocaleStore that will not watch for changes | ||||||
| @@ -47,14 +46,10 @@ func (store *localeStore) AddLocaleByIni(langName, langDesc string, source, more | |||||||
| 	l := &locale{store: store, langName: langName, idxToMsgMap: make(map[int]string)} | 	l := &locale{store: store, langName: langName, idxToMsgMap: make(map[int]string)} | ||||||
| 	store.localeMap[l.langName] = l | 	store.localeMap[l.langName] = l | ||||||
|  |  | ||||||
| 	iniFile, err := ini.LoadSources(ini.LoadOptions{ | 	iniFile, err := setting.NewConfigProviderForLocale(source, moreSource) | ||||||
| 		IgnoreInlineComment:         true, |  | ||||||
| 		UnescapeValueCommentSymbols: true, |  | ||||||
| 	}, source, moreSource) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return fmt.Errorf("unable to load ini: %w", err) | 		return fmt.Errorf("unable to load ini: %w", err) | ||||||
| 	} | 	} | ||||||
| 	iniFile.BlockMode = false |  | ||||||
|  |  | ||||||
| 	for _, section := range iniFile.Sections() { | 	for _, section := range iniFile.Sections() { | ||||||
| 		for _, key := range section.Keys() { | 		for _, key := range section.Keys() { | ||||||
|   | |||||||
| @@ -35,7 +35,6 @@ import ( | |||||||
| 	"code.gitea.io/gitea/services/forms" | 	"code.gitea.io/gitea/services/forms" | ||||||
|  |  | ||||||
| 	"gitea.com/go-chi/session" | 	"gitea.com/go-chi/session" | ||||||
| 	"gopkg.in/ini.v1" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
| @@ -371,17 +370,11 @@ func SubmitInstall(ctx *context.Context) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Save settings. | 	// Save settings. | ||||||
| 	cfg := ini.Empty() | 	cfg, err := setting.NewConfigProviderFromFile(&setting.Options{CustomConf: setting.CustomConf, AllowEmpty: true}) | ||||||
| 	isFile, err := util.IsFile(setting.CustomConf) |  | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Error("Unable to check if %s is a file. Error: %v", setting.CustomConf, err) | 		log.Error("Failed to load custom conf '%s': %v", setting.CustomConf, err) | ||||||
| 	} |  | ||||||
| 	if isFile { |  | ||||||
| 		// Keeps custom settings if there is already something. |  | ||||||
| 		if err = cfg.Append(setting.CustomConf); err != nil { |  | ||||||
| 			log.Error("Failed to load custom conf '%s': %v", setting.CustomConf, err) |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	cfg.Section("database").Key("DB_TYPE").SetValue(setting.Database.Type.String()) | 	cfg.Section("database").Key("DB_TYPE").SetValue(setting.Database.Type.String()) | ||||||
| 	cfg.Section("database").Key("HOST").SetValue(setting.Database.Host) | 	cfg.Section("database").Key("HOST").SetValue(setting.Database.Host) | ||||||
| 	cfg.Section("database").Key("NAME").SetValue(setting.Database.Name) | 	cfg.Section("database").Key("NAME").SetValue(setting.Database.Name) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user