Skip to content

Commit 8302b95

Browse files
GiteaBotwxiaoguang
andauthored
Avoid polluting config file when "save" (#25395) (#25406)
Backport #25395 by @wxiaoguang That's a longstanding INI package problem: the "MustXxx" calls change the option values, and the following "Save" will save a lot of garbage options into the user's config file. Ideally we should refactor the INI package to a clear solution, but it's a huge work. A clear workaround is what this PR does: when "Save", load a clear INI instance and save it. Partially fix #25377, the "install" page needs more fine tunes. Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
1 parent 6f1c95e commit 8302b95

File tree

7 files changed

+94
-16
lines changed

7 files changed

+94
-16
lines changed

cmd/web.go

+9-3
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,15 @@ func setPort(port string) error {
217217
defaultLocalURL += ":" + setting.HTTPPort + "/"
218218

219219
// Save LOCAL_ROOT_URL if port changed
220-
setting.CfgProvider.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL)
221-
if err := setting.CfgProvider.Save(); err != nil {
222-
return fmt.Errorf("Failed to save config file: %v", err)
220+
rootCfg := setting.CfgProvider
221+
saveCfg, err := rootCfg.PrepareSaving()
222+
if err != nil {
223+
return fmt.Errorf("failed to save config file: %v", err)
224+
}
225+
rootCfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL)
226+
saveCfg.Section("server").Key("LOCAL_ROOT_URL").SetValue(defaultLocalURL)
227+
if err = saveCfg.Save(); err != nil {
228+
return fmt.Errorf("failed to save config file: %v", err)
223229
}
224230
}
225231
return nil

modules/setting/config_provider.go

+35-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package setting
55

66
import (
7+
"errors"
78
"fmt"
89
"os"
910
"path/filepath"
@@ -51,11 +52,17 @@ type ConfigProvider interface {
5152
GetSection(name string) (ConfigSection, error)
5253
Save() error
5354
SaveTo(filename string) error
55+
56+
DisableSaving()
57+
PrepareSaving() (ConfigProvider, error)
5458
}
5559

5660
type iniConfigProvider struct {
57-
opts *Options
58-
ini *ini.File
61+
opts *Options
62+
ini *ini.File
63+
64+
disableSaving bool
65+
5966
newFile bool // whether the file has not existed previously
6067
}
6168

@@ -191,7 +198,7 @@ type Options struct {
191198
// NewConfigProviderFromFile load configuration from file.
192199
// NOTE: do not print any log except error.
193200
func NewConfigProviderFromFile(opts *Options) (ConfigProvider, error) {
194-
cfg := ini.Empty()
201+
cfg := ini.Empty(ini.LoadOptions{KeyValueDelimiterOnWrite: " = "})
195202
newFile := true
196203

197204
if opts.CustomConf != "" {
@@ -252,8 +259,13 @@ func (p *iniConfigProvider) GetSection(name string) (ConfigSection, error) {
252259
return &iniConfigSection{sec: sec}, nil
253260
}
254261

262+
var errDisableSaving = errors.New("this config can't be saved, developers should prepare a new config to save")
263+
255264
// Save saves the content into file
256265
func (p *iniConfigProvider) Save() error {
266+
if p.disableSaving {
267+
return errDisableSaving
268+
}
257269
filename := p.opts.CustomConf
258270
if filename == "" {
259271
if !p.opts.AllowEmpty {
@@ -285,9 +297,29 @@ func (p *iniConfigProvider) Save() error {
285297
}
286298

287299
func (p *iniConfigProvider) SaveTo(filename string) error {
300+
if p.disableSaving {
301+
return errDisableSaving
302+
}
288303
return p.ini.SaveTo(filename)
289304
}
290305

306+
// DisableSaving disables the saving function, use PrepareSaving to get clear config options.
307+
func (p *iniConfigProvider) DisableSaving() {
308+
p.disableSaving = true
309+
}
310+
311+
// PrepareSaving loads the ini from file again to get clear config options.
312+
// Otherwise, the "MustXxx" calls would have polluted the current config provider,
313+
// it makes the "Save" outputs a lot of garbage options
314+
// After the INI package gets refactored, no "MustXxx" pollution, this workaround can be dropped.
315+
func (p *iniConfigProvider) PrepareSaving() (ConfigProvider, error) {
316+
cfgFile := p.opts.CustomConf
317+
if cfgFile == "" {
318+
return nil, errors.New("no config file to save")
319+
}
320+
return NewConfigProviderFromFile(p.opts)
321+
}
322+
291323
func mustMapSetting(rootCfg ConfigProvider, sectionName string, setting any) {
292324
if err := rootCfg.Section(sectionName).MapTo(setting); err != nil {
293325
log.Fatal("Failed to map %s settings: %v", sectionName, err)

modules/setting/config_provider_test.go

+27-3
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,11 @@ func TestNewConfigProviderFromFile(t *testing.T) {
8484

8585
bs, err := os.ReadFile(testFile)
8686
assert.NoError(t, err)
87-
assert.Equal(t, "[foo]\nk1=a\n", string(bs))
87+
assert.Equal(t, "[foo]\nk1 = a\n", string(bs))
8888

8989
bs, err = os.ReadFile(testFile1)
9090
assert.NoError(t, err)
91-
assert.Equal(t, "[foo]\nk1=a\nk2=b\n", string(bs))
91+
assert.Equal(t, "[foo]\nk1 = a\nk2 = b\n", string(bs))
9292

9393
// load existing file and save
9494
cfg, err = NewConfigProviderFromFile(&Options{CustomConf: testFile, AllowEmpty: true})
@@ -99,7 +99,7 @@ func TestNewConfigProviderFromFile(t *testing.T) {
9999
assert.NoError(t, cfg.Save())
100100
bs, err = os.ReadFile(testFile)
101101
assert.NoError(t, err)
102-
assert.Equal(t, "[foo]\nk1=a\n\n[bar]\nk1=b\n", string(bs))
102+
assert.Equal(t, "[foo]\nk1 = a\n\n[bar]\nk1 = b\n", string(bs))
103103
}
104104

105105
func TestNewConfigProviderForLocale(t *testing.T) {
@@ -119,3 +119,27 @@ func TestNewConfigProviderForLocale(t *testing.T) {
119119
assert.Equal(t, "foo", cfg.Section("").Key("k1").String())
120120
assert.Equal(t, "xxx", cfg.Section("").Key("k2").String())
121121
}
122+
123+
func TestDisableSaving(t *testing.T) {
124+
testFile := t.TempDir() + "/test.ini"
125+
_ = os.WriteFile(testFile, []byte("k1=a\nk2=b"), 0o644)
126+
cfg, err := NewConfigProviderFromFile(&Options{CustomConf: testFile, AllowEmpty: true})
127+
assert.NoError(t, err)
128+
129+
cfg.DisableSaving()
130+
err = cfg.Save()
131+
assert.ErrorIs(t, err, errDisableSaving)
132+
133+
saveCfg, err := cfg.PrepareSaving()
134+
assert.NoError(t, err)
135+
136+
saveCfg.Section("").Key("k1").MustString("x")
137+
saveCfg.Section("").Key("k2").SetValue("y")
138+
saveCfg.Section("").Key("k3").SetValue("z")
139+
err = saveCfg.Save()
140+
assert.NoError(t, err)
141+
142+
bs, err := os.ReadFile(testFile)
143+
assert.NoError(t, err)
144+
assert.Equal(t, "k1 = a\nk2 = y\nk3 = z\n", string(bs))
145+
}

modules/setting/lfs.go

+9-4
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,18 @@ func loadLFSFrom(rootCfg ConfigProvider) error {
5959
if err != nil || n != 32 {
6060
LFS.JWTSecretBase64, err = generate.NewJwtSecretBase64()
6161
if err != nil {
62-
return fmt.Errorf("Error generating JWT Secret for custom config: %v", err)
62+
return fmt.Errorf("error generating JWT Secret for custom config: %v", err)
6363
}
6464

6565
// Save secret
66-
sec.Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64)
67-
if err := rootCfg.Save(); err != nil {
68-
return fmt.Errorf("Error saving JWT Secret for custom config: %v", err)
66+
saveCfg, err := rootCfg.PrepareSaving()
67+
if err != nil {
68+
return fmt.Errorf("error saving JWT Secret for custom config: %v", err)
69+
}
70+
rootCfg.Section("server").Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64)
71+
saveCfg.Section("server").Key("LFS_JWT_SECRET").SetValue(LFS.JWTSecretBase64)
72+
if err := saveCfg.Save(); err != nil {
73+
return fmt.Errorf("error saving JWT Secret for custom config: %v", err)
6974
}
7075
}
7176

modules/setting/oauth2.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,13 @@ func loadOAuth2From(rootCfg ConfigProvider) {
130130
}
131131

132132
secretBase64 := base64.RawURLEncoding.EncodeToString(key)
133+
saveCfg, err := rootCfg.PrepareSaving()
134+
if err != nil {
135+
log.Fatal("save oauth2.JWT_SECRET failed: %v", err)
136+
}
133137
rootCfg.Section("oauth2").Key("JWT_SECRET").SetValue(secretBase64)
134-
if err := rootCfg.Save(); err != nil {
138+
saveCfg.Section("oauth2").Key("JWT_SECRET").SetValue(secretBase64)
139+
if err := saveCfg.Save(); err != nil {
135140
log.Fatal("save oauth2.JWT_SECRET failed: %v", err)
136141
}
137142
}

modules/setting/security.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,13 @@ func generateSaveInternalToken(rootCfg ConfigProvider) {
8989
}
9090

9191
InternalToken = token
92+
saveCfg, err := rootCfg.PrepareSaving()
93+
if err != nil {
94+
log.Fatal("Error saving internal token: %v", err)
95+
}
9296
rootCfg.Section("security").Key("INTERNAL_TOKEN").SetValue(token)
93-
if err := rootCfg.Save(); err != nil {
97+
saveCfg.Section("security").Key("INTERNAL_TOKEN").SetValue(token)
98+
if err = saveCfg.Save(); err != nil {
9499
log.Fatal("Error saving internal token: %v", err)
95100
}
96101
}

modules/setting/setting.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ func Init(opts *Options) {
202202
}
203203
var err error
204204
CfgProvider, err = NewConfigProviderFromFile(opts)
205+
CfgProvider.DisableSaving() // do not allow saving the CfgProvider into file, it will be polluted by the "MustXxx" calls
205206
if err != nil {
206207
log.Fatal("newConfigProviderFromFile[%v]: %v", opts, err)
207208
}
@@ -214,7 +215,7 @@ func Init(opts *Options) {
214215

215216
// loadCommonSettingsFrom loads common configurations from a configuration provider.
216217
func loadCommonSettingsFrom(cfg ConfigProvider) error {
217-
// WARNNING: don't change the sequence except you know what you are doing.
218+
// WARNING: don't change the sequence except you know what you are doing.
218219
loadRunModeFrom(cfg)
219220
loadLogGlobalFrom(cfg)
220221
loadServerFrom(cfg)

0 commit comments

Comments
 (0)