gitea源码

config_provider.go 9.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package setting
  4. import (
  5. "errors"
  6. "fmt"
  7. "os"
  8. "path/filepath"
  9. "strconv"
  10. "strings"
  11. "time"
  12. "code.gitea.io/gitea/modules/log"
  13. "code.gitea.io/gitea/modules/util"
  14. "gopkg.in/ini.v1" //nolint:depguard // wrapper for this package
  15. )
  16. type ConfigKey interface {
  17. Name() string
  18. Value() string
  19. SetValue(v string)
  20. In(defaultVal string, candidates []string) string
  21. String() string
  22. Strings(delim string) []string
  23. Bool() (bool, error)
  24. MustString(defaultVal string) string
  25. MustBool(defaultVal ...bool) bool
  26. MustInt(defaultVal ...int) int
  27. MustInt64(defaultVal ...int64) int64
  28. MustDuration(defaultVal ...time.Duration) time.Duration
  29. }
  30. type ConfigSection interface {
  31. Name() string
  32. MapTo(any) error
  33. HasKey(key string) bool
  34. NewKey(name, value string) (ConfigKey, error)
  35. Key(key string) ConfigKey
  36. Keys() []ConfigKey
  37. ChildSections() []ConfigSection
  38. }
  39. // ConfigProvider represents a config provider
  40. type ConfigProvider interface {
  41. Section(section string) ConfigSection
  42. Sections() []ConfigSection
  43. NewSection(name string) (ConfigSection, error)
  44. GetSection(name string) (ConfigSection, error)
  45. Save() error
  46. SaveTo(filename string) error
  47. DisableSaving()
  48. PrepareSaving() (ConfigProvider, error)
  49. IsLoadedFromEmpty() bool
  50. }
  51. type iniConfigProvider struct {
  52. file string
  53. ini *ini.File
  54. disableSaving bool // disable the "Save" method because the config options could be polluted
  55. loadedFromEmpty bool // whether the file has not existed previously
  56. }
  57. type iniConfigSection struct {
  58. sec *ini.Section
  59. }
  60. var (
  61. _ ConfigProvider = (*iniConfigProvider)(nil)
  62. _ ConfigSection = (*iniConfigSection)(nil)
  63. _ ConfigKey = (*ini.Key)(nil)
  64. )
  65. // ConfigSectionKey only searches the keys in the given section, but it is O(n).
  66. // ini package has a special behavior: with "[sec] a=1" and an empty "[sec.sub]",
  67. // then in "[sec.sub]", Key()/HasKey() can always see "a=1" because it always tries parent sections.
  68. // It returns nil if the key doesn't exist.
  69. func ConfigSectionKey(sec ConfigSection, key string) ConfigKey {
  70. if sec == nil {
  71. return nil
  72. }
  73. for _, k := range sec.Keys() {
  74. if k.Name() == key {
  75. return k
  76. }
  77. }
  78. return nil
  79. }
  80. func ConfigSectionKeyString(sec ConfigSection, key string, def ...string) string {
  81. k := ConfigSectionKey(sec, key)
  82. if k != nil && k.String() != "" {
  83. return k.String()
  84. }
  85. if len(def) > 0 {
  86. return def[0]
  87. }
  88. return ""
  89. }
  90. func ConfigSectionKeyBool(sec ConfigSection, key string, def ...bool) bool {
  91. k := ConfigSectionKey(sec, key)
  92. if k != nil && k.String() != "" {
  93. b, _ := strconv.ParseBool(k.String())
  94. return b
  95. }
  96. if len(def) > 0 {
  97. return def[0]
  98. }
  99. return false
  100. }
  101. // ConfigInheritedKey works like ini.Section.Key(), but it always returns a new key instance, it is O(n) because NewKey is O(n)
  102. // and the returned key is safe to be used with "MustXxx", it doesn't change the parent's values.
  103. // Otherwise, ini.Section.Key().MustXxx would pollute the parent section's keys.
  104. // It never returns nil.
  105. func ConfigInheritedKey(sec ConfigSection, key string) ConfigKey {
  106. k := sec.Key(key)
  107. if k != nil && k.String() != "" {
  108. newKey, _ := sec.NewKey(k.Name(), k.String())
  109. return newKey
  110. }
  111. newKey, _ := sec.NewKey(key, "")
  112. return newKey
  113. }
  114. func ConfigInheritedKeyString(sec ConfigSection, key string, def ...string) string {
  115. k := sec.Key(key)
  116. if k != nil && k.String() != "" {
  117. return k.String()
  118. }
  119. if len(def) > 0 {
  120. return def[0]
  121. }
  122. return ""
  123. }
  124. func (s *iniConfigSection) Name() string {
  125. return s.sec.Name()
  126. }
  127. func (s *iniConfigSection) MapTo(v any) error {
  128. return s.sec.MapTo(v)
  129. }
  130. func (s *iniConfigSection) HasKey(key string) bool {
  131. return s.sec.HasKey(key)
  132. }
  133. func (s *iniConfigSection) NewKey(name, value string) (ConfigKey, error) {
  134. return s.sec.NewKey(name, value)
  135. }
  136. func (s *iniConfigSection) Key(key string) ConfigKey {
  137. return s.sec.Key(key)
  138. }
  139. func (s *iniConfigSection) Keys() (keys []ConfigKey) {
  140. for _, k := range s.sec.Keys() {
  141. keys = append(keys, k)
  142. }
  143. return keys
  144. }
  145. func (s *iniConfigSection) ChildSections() (sections []ConfigSection) {
  146. for _, s := range s.sec.ChildSections() {
  147. sections = append(sections, &iniConfigSection{s})
  148. }
  149. return sections
  150. }
  151. func configProviderLoadOptions() ini.LoadOptions {
  152. return ini.LoadOptions{
  153. KeyValueDelimiterOnWrite: " = ",
  154. IgnoreContinuation: true,
  155. }
  156. }
  157. // NewConfigProviderFromData this function is mainly for testing purpose
  158. func NewConfigProviderFromData(configContent string) (ConfigProvider, error) {
  159. cfg, err := ini.LoadSources(configProviderLoadOptions(), strings.NewReader(configContent))
  160. if err != nil {
  161. return nil, err
  162. }
  163. cfg.NameMapper = ini.SnackCase
  164. return &iniConfigProvider{
  165. ini: cfg,
  166. loadedFromEmpty: true,
  167. }, nil
  168. }
  169. // NewConfigProviderFromFile load configuration from file.
  170. // NOTE: do not print any log except error.
  171. func NewConfigProviderFromFile(file string) (ConfigProvider, error) {
  172. cfg := ini.Empty(configProviderLoadOptions())
  173. loadedFromEmpty := true
  174. if file != "" {
  175. isFile, err := util.IsFile(file)
  176. if err != nil {
  177. return nil, fmt.Errorf("unable to check if %q is a file. Error: %v", file, err)
  178. }
  179. if isFile {
  180. if err = cfg.Append(file); err != nil {
  181. return nil, fmt.Errorf("failed to load config file %q: %v", file, err)
  182. }
  183. loadedFromEmpty = false
  184. }
  185. }
  186. cfg.NameMapper = ini.SnackCase
  187. return &iniConfigProvider{
  188. file: file,
  189. ini: cfg,
  190. loadedFromEmpty: loadedFromEmpty,
  191. }, nil
  192. }
  193. func (p *iniConfigProvider) Section(section string) ConfigSection {
  194. return &iniConfigSection{sec: p.ini.Section(section)}
  195. }
  196. func (p *iniConfigProvider) Sections() (sections []ConfigSection) {
  197. for _, s := range p.ini.Sections() {
  198. sections = append(sections, &iniConfigSection{s})
  199. }
  200. return sections
  201. }
  202. func (p *iniConfigProvider) NewSection(name string) (ConfigSection, error) {
  203. sec, err := p.ini.NewSection(name)
  204. if err != nil {
  205. return nil, err
  206. }
  207. return &iniConfigSection{sec: sec}, nil
  208. }
  209. func (p *iniConfigProvider) GetSection(name string) (ConfigSection, error) {
  210. sec, err := p.ini.GetSection(name)
  211. if err != nil {
  212. return nil, err
  213. }
  214. return &iniConfigSection{sec: sec}, nil
  215. }
  216. var errDisableSaving = errors.New("this config can't be saved, developers should prepare a new config to save")
  217. // Save saves the content into file
  218. func (p *iniConfigProvider) Save() error {
  219. if p.disableSaving {
  220. return errDisableSaving
  221. }
  222. filename := p.file
  223. if filename == "" {
  224. return errors.New("config file path must not be empty")
  225. }
  226. if p.loadedFromEmpty {
  227. if err := os.MkdirAll(filepath.Dir(filename), os.ModePerm); err != nil {
  228. return fmt.Errorf("failed to create %q: %v", filename, err)
  229. }
  230. }
  231. if err := p.ini.SaveTo(filename); err != nil {
  232. return fmt.Errorf("failed to save %q: %v", filename, err)
  233. }
  234. // Change permissions to be more restrictive
  235. fi, err := os.Stat(filename)
  236. if err != nil {
  237. return fmt.Errorf("failed to determine current conf file permissions: %v", err)
  238. }
  239. if fi.Mode().Perm() > 0o600 {
  240. if err = os.Chmod(filename, 0o600); err != nil {
  241. log.Warn("Failed changing conf file permissions to -rw-------. Consider changing them manually.")
  242. }
  243. }
  244. return nil
  245. }
  246. func (p *iniConfigProvider) SaveTo(filename string) error {
  247. if p.disableSaving {
  248. return errDisableSaving
  249. }
  250. return p.ini.SaveTo(filename)
  251. }
  252. // DisableSaving disables the saving function, use PrepareSaving to get clear config options.
  253. func (p *iniConfigProvider) DisableSaving() {
  254. p.disableSaving = true
  255. }
  256. // PrepareSaving loads the ini from file again to get clear config options.
  257. // Otherwise, the "MustXxx" calls would have polluted the current config provider,
  258. // it makes the "Save" outputs a lot of garbage options
  259. // After the INI package gets refactored, no "MustXxx" pollution, this workaround can be dropped.
  260. func (p *iniConfigProvider) PrepareSaving() (ConfigProvider, error) {
  261. if p.file == "" {
  262. return nil, errors.New("no config file to save")
  263. }
  264. return NewConfigProviderFromFile(p.file)
  265. }
  266. func (p *iniConfigProvider) IsLoadedFromEmpty() bool {
  267. return p.loadedFromEmpty
  268. }
  269. func mustMapSetting(rootCfg ConfigProvider, sectionName string, setting any) {
  270. if err := rootCfg.Section(sectionName).MapTo(setting); err != nil {
  271. log.Fatal("Failed to map %s settings: %v", sectionName, err)
  272. }
  273. }
  274. // StartupProblems contains the messages for various startup problems, including: setting option, file/folder, etc
  275. var StartupProblems []string
  276. func LogStartupProblem(skip int, level log.Level, format string, args ...any) {
  277. msg := fmt.Sprintf(format, args...)
  278. log.Log(skip+1, level, "%s", msg)
  279. StartupProblems = append(StartupProblems, msg)
  280. }
  281. func deprecatedSetting(rootCfg ConfigProvider, oldSection, oldKey, newSection, newKey, version string) {
  282. if rootCfg.Section(oldSection).HasKey(oldKey) {
  283. LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` presents, please use `[%s].%s` instead because this fallback will be/has been removed in %s", oldSection, oldKey, newSection, newKey, version)
  284. }
  285. }
  286. // deprecatedSettingDB add a hint that the configuration has been moved to database but still kept in app.ini
  287. func deprecatedSettingDB(rootCfg ConfigProvider, oldSection, oldKey string) {
  288. if rootCfg.Section(oldSection).HasKey(oldKey) {
  289. LogStartupProblem(1, log.ERROR, "Deprecation: config option `[%s].%s` presents but it won't take effect because it has been moved to admin panel -> config setting", oldSection, oldKey)
  290. }
  291. }
  292. // NewConfigProviderForLocale loads locale configuration from source and others. "string" if for a local file path, "[]byte" is for INI content
  293. func NewConfigProviderForLocale(source any, others ...any) (ConfigProvider, error) {
  294. iniFile, err := ini.LoadSources(ini.LoadOptions{
  295. IgnoreInlineComment: true,
  296. UnescapeValueCommentSymbols: true,
  297. IgnoreContinuation: true,
  298. }, source, others...)
  299. if err != nil {
  300. return nil, fmt.Errorf("unable to load locale ini: %w", err)
  301. }
  302. iniFile.BlockMode = false
  303. return &iniConfigProvider{
  304. ini: iniFile,
  305. loadedFromEmpty: true,
  306. }, nil
  307. }
  308. func init() {
  309. ini.PrettyFormat = false
  310. }