Skip to content

Commit 1e7c7de

Browse files
authored
Merge pull request #540 from SiaFoundation/nate/config-upgrade
Attempt to upgrade existing configs instead of erroring
2 parents 39ee6df + 63daf67 commit 1e7c7de

File tree

4 files changed

+219
-27
lines changed

4 files changed

+219
-27
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
default: minor
3+
---
4+
5+
# Attempt to upgrade existing configs instead of exiting

cmd/hostd/main.go

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import (
1818
"go.sia.tech/hostd/persist/sqlite"
1919
"go.uber.org/zap"
2020
"go.uber.org/zap/zapcore"
21-
"gopkg.in/yaml.v3"
2221
"lukechampine.com/flagg"
2322
)
2423

@@ -65,7 +64,6 @@ var (
6564
},
6665
},
6766
Log: config.Log{
68-
Path: os.Getenv(logFileEnvVar), // deprecated. included for compatibility.
6967
Level: "info",
7068
File: config.LogFile{
7169
Enabled: true,
@@ -104,21 +102,8 @@ func tryLoadConfig() {
104102
configPath = str
105103
}
106104

107-
// If the config file doesn't exist, don't try to load it.
108-
if _, err := os.Stat(configPath); os.IsNotExist(err) {
109-
return
110-
}
111-
112-
f, err := os.Open(configPath)
113-
checkFatalError("failed to open config file", err)
114-
defer f.Close()
115-
116-
dec := yaml.NewDecoder(f)
117-
dec.KnownFields(true)
118-
119-
if err := dec.Decode(&cfg); err != nil {
120-
fmt.Println("failed to decode config file:", err)
121-
os.Exit(1)
105+
if err := config.LoadFile(configPath, &cfg); err != nil && !errors.Is(err, os.ErrNotExist) {
106+
checkFatalError("failed to load config file", err)
122107
}
123108
}
124109

@@ -427,13 +412,7 @@ func main() {
427412

428413
// normalize log path
429414
if cfg.Log.File.Path == "" {
430-
// If the log path is not set, try the deprecated log path. If that
431-
// is also not set, default to hostd.log in the data directory.
432-
if cfg.Log.Path != "" {
433-
cfg.Log.File.Path = filepath.Join(cfg.Log.Path, "hostd.log")
434-
} else {
435-
cfg.Log.File.Path = filepath.Join(cfg.Directory, "hostd.log")
436-
}
415+
cfg.Log.File.Path = filepath.Join(cfg.Directory, "hostd.log")
437416
}
438417

439418
// configure file logging

config/compat.go

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
package config
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"os"
7+
8+
"gopkg.in/yaml.v3"
9+
)
10+
11+
type configV112 struct {
12+
Name string `yaml:"name,omitempty"`
13+
Directory string `yaml:"directory,omitempty"`
14+
RecoveryPhrase string `yaml:"recoveryPhrase,omitempty"`
15+
AutoOpenWebUI bool `yaml:"autoOpenWebUI,omitempty"`
16+
17+
HTTP struct {
18+
Address string `yaml:"address,omitempty"`
19+
Password string `yaml:"password,omitempty"`
20+
} `yaml:"http,omitempty"`
21+
Consensus struct {
22+
GatewayAddress string `yaml:"gatewayAddress,omitempty"`
23+
Bootstrap bool `yaml:"bootstrap,omitempty"`
24+
Peers []string `yaml:"peers,omitempty"`
25+
} `yaml:"consensus,omitempty"`
26+
Explorer struct {
27+
Disable bool `yaml:"disable,omitempty"`
28+
URL string `yaml:"url,omitempty"`
29+
} `yaml:"explorer,omitempty"`
30+
RHP2 struct {
31+
Address string `yaml:"address,omitempty"`
32+
} `yaml:"rhp2,omitempty"`
33+
RHP3 struct {
34+
TCPAddress string `yaml:"tcp,omitempty"`
35+
WebSocketAddress string `yaml:"websocket,omitempty"`
36+
CertPath string `yaml:"certPath,omitempty"`
37+
KeyPath string `yaml:"keyPath,omitempty"`
38+
} `yaml:"rhp3,omitempty"`
39+
Log struct {
40+
// Path is the directory to store the hostd.log file.
41+
// Deprecated: use File.Path instead.
42+
Path string `yaml:"path,omitempty"`
43+
Level string `yaml:"level,omitempty"` // global log level
44+
StdOut struct {
45+
Level string `yaml:"level,omitempty"` // override the stdout log level
46+
Enabled bool `yaml:"enabled,omitempty"`
47+
Format string `yaml:"format,omitempty"`
48+
EnableANSI bool `yaml:"enableANSI,omitempty"` //nolint:tagliatelle
49+
} `yaml:"stdout,omitempty"`
50+
File struct {
51+
Enabled bool `yaml:"enabled,omitempty"`
52+
Level string `yaml:"level,omitempty"` // override the file log level
53+
Format string `yaml:"format,omitempty"`
54+
// Path is the path of the log file.
55+
Path string `yaml:"path,omitempty"`
56+
} `yaml:"file,omitempty"`
57+
} `yaml:"log,omitempty"`
58+
}
59+
60+
// updateConfigV112 updates the config file from v1.1.2 to the latest version.
61+
// It returns an error if the config file cannot be updated.
62+
func updateConfigV112(fp string, r io.Reader, cfg *Config) error {
63+
dec := yaml.NewDecoder(r)
64+
dec.KnownFields(true)
65+
66+
old := configV112{
67+
Consensus: struct {
68+
GatewayAddress string `yaml:"gatewayAddress,omitempty"`
69+
Bootstrap bool `yaml:"bootstrap,omitempty"`
70+
Peers []string `yaml:"peers,omitempty"`
71+
}{
72+
GatewayAddress: ":9981",
73+
Bootstrap: true,
74+
},
75+
RHP2: struct {
76+
Address string `yaml:"address,omitempty"`
77+
}{
78+
Address: ":9982",
79+
},
80+
RHP3: struct {
81+
TCPAddress string `yaml:"tcp,omitempty"`
82+
WebSocketAddress string `yaml:"websocket,omitempty"`
83+
CertPath string `yaml:"certPath,omitempty"`
84+
KeyPath string `yaml:"keyPath,omitempty"`
85+
}{
86+
TCPAddress: ":9983",
87+
},
88+
Log: struct {
89+
Path string `yaml:"path,omitempty"`
90+
Level string `yaml:"level,omitempty"`
91+
StdOut struct {
92+
Level string `yaml:"level,omitempty"` // override the stdout log level
93+
Enabled bool `yaml:"enabled,omitempty"`
94+
Format string `yaml:"format,omitempty"`
95+
EnableANSI bool `yaml:"enableANSI,omitempty"` //nolint:tagliatelle
96+
} `yaml:"stdout,omitempty"`
97+
File struct {
98+
Enabled bool `yaml:"enabled,omitempty"`
99+
Level string `yaml:"level,omitempty"` // override the file log level
100+
Format string `yaml:"format,omitempty"`
101+
// Path is the path of the log file.
102+
Path string `yaml:"path,omitempty"`
103+
} `yaml:"file,omitempty"`
104+
}{
105+
Level: "info",
106+
StdOut: struct {
107+
Level string `yaml:"level,omitempty"`
108+
Enabled bool `yaml:"enabled,omitempty"`
109+
Format string `yaml:"format,omitempty"`
110+
EnableANSI bool `yaml:"enableANSI,omitempty"` //nolint:tagliatelle
111+
}{
112+
Level: "info",
113+
Enabled: true,
114+
Format: "human",
115+
},
116+
File: struct {
117+
Enabled bool `yaml:"enabled,omitempty"`
118+
Level string `yaml:"level,omitempty"` // override the file log level
119+
Format string `yaml:"format,omitempty"`
120+
// Path is the path of the log file.
121+
Path string `yaml:"path,omitempty"`
122+
}{
123+
Enabled: true,
124+
Level: "info",
125+
Format: "json",
126+
},
127+
},
128+
}
129+
if err := dec.Decode(&old); err != nil {
130+
return fmt.Errorf("failed to decode config file: %w", err)
131+
}
132+
133+
cfg.Name = old.Name
134+
cfg.Directory = old.Directory
135+
cfg.RecoveryPhrase = old.RecoveryPhrase
136+
cfg.AutoOpenWebUI = old.AutoOpenWebUI
137+
cfg.HTTP.Address = old.HTTP.Address
138+
cfg.HTTP.Password = old.HTTP.Password
139+
cfg.Syncer.Address = old.Consensus.GatewayAddress
140+
cfg.Syncer.Bootstrap = old.Consensus.Bootstrap
141+
cfg.Syncer.Peers = old.Consensus.Peers
142+
cfg.Explorer.Disable = old.Explorer.Disable
143+
cfg.Explorer.URL = old.Explorer.URL
144+
cfg.RHP2.Address = old.RHP2.Address
145+
cfg.RHP3.TCPAddress = old.RHP3.TCPAddress
146+
cfg.Log.Level = old.Log.Level
147+
if old.Log.File.Path != "" {
148+
cfg.Log.File.Path = old.Log.File.Path
149+
} else {
150+
cfg.Log.File.Path = old.Log.Path
151+
}
152+
cfg.Log.StdOut.Level = old.Log.StdOut.Level
153+
cfg.Log.StdOut.Enabled = old.Log.StdOut.Enabled
154+
cfg.Log.StdOut.Format = old.Log.StdOut.Format
155+
cfg.Log.StdOut.EnableANSI = old.Log.StdOut.EnableANSI
156+
cfg.Log.File.Enabled = old.Log.File.Enabled
157+
cfg.Log.File.Level = old.Log.File.Level
158+
cfg.Log.File.Format = old.Log.File.Format
159+
160+
tmpFilePath := fp + ".tmp"
161+
f, err := os.Create(tmpFilePath)
162+
if err != nil {
163+
return fmt.Errorf("failed to open file: %w", err)
164+
}
165+
defer f.Close()
166+
167+
enc := yaml.NewEncoder(f)
168+
enc.SetIndent(2)
169+
if err := enc.Encode(cfg); err != nil {
170+
return fmt.Errorf("failed to encode config file: %w", err)
171+
} else if err := f.Sync(); err != nil {
172+
return fmt.Errorf("failed to sync file: %w", err)
173+
} else if err := f.Close(); err != nil {
174+
return fmt.Errorf("failed to close file: %w", err)
175+
} else if err := os.Rename(tmpFilePath, fp); err != nil {
176+
return fmt.Errorf("failed to rename file: %w", err)
177+
}
178+
return nil
179+
}

config/config.go

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
package config
22

3+
import (
4+
"bytes"
5+
"fmt"
6+
"os"
7+
8+
"gopkg.in/yaml.v3"
9+
)
10+
311
type (
412
// HTTP contains the configuration for the HTTP server.
513
HTTP struct {
@@ -67,9 +75,6 @@ type (
6775

6876
// Log contains the configuration for the logger.
6977
Log struct {
70-
// Path is the directory to store the hostd.log file.
71-
// Deprecated: use File.Path instead.
72-
Path string `yaml:"path,omitempty"`
7378
Level string `yaml:"level,omitempty"` // global log level
7479
StdOut StdOut `yaml:"stdout,omitempty"`
7580
File LogFile `yaml:"file,omitempty"`
@@ -92,3 +97,27 @@ type (
9297
Log Log `yaml:"log,omitempty"`
9398
}
9499
)
100+
101+
// LoadFile loads the configuration from the provided file path.
102+
// If the file does not exist, an error is returned.
103+
// If the file exists but cannot be decoded, the function will attempt
104+
// to upgrade the config file.
105+
func LoadFile(fp string, cfg *Config) error {
106+
buf, err := os.ReadFile(fp)
107+
if err != nil {
108+
return fmt.Errorf("failed to read config file: %w", err)
109+
}
110+
111+
r := bytes.NewReader(buf)
112+
dec := yaml.NewDecoder(r)
113+
dec.KnownFields(true)
114+
115+
if err := dec.Decode(cfg); err != nil {
116+
r.Reset(buf)
117+
if upgradeErr := updateConfigV112(fp, r, cfg); upgradeErr == nil {
118+
return nil
119+
}
120+
return fmt.Errorf("failed to decode config file: %w", err)
121+
}
122+
return nil
123+
}

0 commit comments

Comments
 (0)