GoBroke/conf/funcs.go
2025-02-04 12:14:08 -05:00

197 lines
5.0 KiB
Go

package conf
import (
`encoding/json`
`encoding/xml`
`hash`
`os`
`github.com/BurntSushi/toml`
`github.com/creasty/defaults`
`github.com/davecgh/go-spew/spew`
`github.com/goccy/go-yaml`
`github.com/zeebo/blake3`
`r00t2.io/gobroke/tplCmd`
`r00t2.io/goutils/logging`
`r00t2.io/sysutils/paths`
)
/*
Checksum guarantees a standard checksum of a configuration.
If you are comparing new vs. old configs, be sure to use this function
and compare against a Config.Checksum() to ensure consistent hashing algorithms etc.
*/
func Checksum(confBytes []byte) (cksum []byte, err error) {
// If built with CGO enabled, this'll take advantage of SIMD.
// Should be fast enough without it, though.
var h hash.Hash = blake3.New()
if _, err = h.Write(confBytes); err != nil {
return
}
cksum = h.Sum(nil)
return
}
/*
ChecksumPath is a convenience wrapper around Checksum, operating on a filepath instead of bytes.
*/
func ChecksumPath(confPath string) (cksum []byte, err error) {
var b []byte
if err = paths.RealPath(&confPath); err != nil {
return
}
if b, err = os.ReadFile(confPath); err != nil {
return
}
if cksum, err = Checksum(b); err != nil {
return
}
return
}
// NewConfig returns a conf.Config from filepath path.
func NewConfig(path string, debug bool, log logging.Logger) (cfg *Config, err error) {
var b []byte
if log == nil {
log = &logging.NullLogger{}
}
log.Debug("conf.NewConfig: New config from path '%s'", path)
if err = paths.RealPath(&path); err != nil {
log.Err("conf.NewConfig: Received error canonizing config path '%s': %s", path, err)
return
}
log.Debug("conf.NewConfig: Canonized configuration path to '%s'", path)
if cfg, err = NewConfigFromBytes(b, debug, log); err != nil {
log.Err("conf.NewConfig: Received error parsing config '%s': %s", path, err)
return
}
cfg.confPath = new(string)
*cfg.confPath = path
log.Debug("conf.NewConfig: Successfully parsed and loaded configuration '%s'", path)
return
}
// NewConfigFromBytes returns a conf.Config from bytes b. b may be a JSON, TOML, XML, or YAML representation.
func NewConfigFromBytes(b []byte, debug bool, log logging.Logger) (cfg *Config, err error) {
var tplBytes []byte
if b == nil || len(b) == 0 {
return
}
if log == nil {
log = &logging.NullLogger{}
}
log.Debug("conf.NewConfigFromBytes: New config from %d bytes.", len(b))
log.Debug("conf.NewConfigFromBytes: Config before parsing:\n%s", string(b))
if err = json.Unmarshal(b, &cfg); err != nil {
if err = xml.Unmarshal(b, &cfg); err != nil {
if err = yaml.Unmarshal(b, &cfg); err != nil {
if err = toml.Unmarshal(b, &cfg); err != nil {
log.Err("conf.NewConfigFromBytes: Unable to parse config as JSON, XML, YAML, or TOML; config invalid.")
err = ErrUnkownSyntax
return
} else {
log.Debug("conf.NewConfigFromBytes: Config parsed as TOML.")
}
} else {
log.Debug("conf.NewConfigFromBytes: Config parsed as YAML.")
}
} else {
log.Debug("conf.NewConfigFromBytes: Config parsed as XML.")
}
} else {
log.Debug("conf.NewConfigFromBytes: Config parsed as JSON.")
}
if err = defaults.Set(cfg); err != nil {
return
}
log.Debug("conf.NewConfigFromBytes: Configuration after parsing and defaults:\n%s", spew.Sdump(cfg))
if err = validate.Struct(cfg); err != nil {
log.Err("conf.NewConfigFromBytes: Config validation failed: %v", err)
return
}
cfg.log = log
cfg.debug = debug
for _, t := range cfg.Tunnels {
if t == nil {
continue
}
t.cfg = cfg
if t.Username == nil {
if cfg.Username == nil {
log.Err(
"conf.NewConfigFromBytes: Username is not provided for tunnel %d and no default username was provided.",
t.TunnelID,
)
err = ErrMissingUser
return
} else {
t.Username = cfg.Username
}
}
if t.TemplateConfigs != nil && len(t.TemplateConfigs) > 0 {
for _, tpl := range t.TemplateConfigs {
if tpl == nil {
continue
}
if err = paths.RealPath(&tpl.Template); err != nil {
log.Err("conf.NewConfigFromBytes: Unable to canonize path to template '%s': %s", tpl.Template, err)
return
}
if err = paths.RealPath(&tpl.Dest); err != nil {
log.Err("conf.NewConfigFromBytes: Unable to canonize path to destination '%s': %s", tpl.Dest, err)
return
}
if tplBytes, err = os.ReadFile(tpl.Template); err != nil {
log.Err("conf.NewConfigFromBytes: Unable to read template file '%s': %s", tpl.Template, err)
return
}
tpl.Tpl = tplCmd.GetTpl()
if _, err = tpl.Tpl.Parse(string(tplBytes)); err != nil {
log.Err("conf.NewConfigFromBytes: Unable to parse template file '%s': %s", tpl.Template, err)
return
}
if tpl.Perms != nil {
if err = tpl.Perms.SetMissing(); err != nil {
log.Err("conf.NewConfigFromBytes: Unable to enrich permissions for template '%s': %s", tpl.Template, err)
return
}
}
}
}
}
if cfg.cksum, err = Checksum(b); err != nil {
log.Err("conf.NewConfigFromBytes: Error calculating checksum: %s", err)
return
}
log.Debug("conf.NewConfigFromBytes: Successfully parsed and loaded configuration.")
return
}