Pull request 1897: nextapi-write-conf
Squashed commit of the following: commit 72f25ffe73d6b8216b01e590fba66fb5f6944113 Author: Ainar Garipov <A.Garipov@AdGuard.COM> Date: Wed Jun 28 21:29:04 2023 +0300 next: add conf writing, validation
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
// Package configmgr defines the AdGuard Home on-disk configuration entities and
|
||||
// configuration manager.
|
||||
//
|
||||
// TODO(a.garipov): Add tests.
|
||||
package configmgr
|
||||
|
||||
import (
|
||||
@@ -14,6 +16,10 @@ import (
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc"
|
||||
"github.com/AdguardTeam/AdGuardHome/internal/next/websvc"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/timeutil"
|
||||
"github.com/google/renameio/maybe"
|
||||
"golang.org/x/exp/slices"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
@@ -39,17 +45,58 @@ type Manager struct {
|
||||
fileName string
|
||||
}
|
||||
|
||||
// Validate returns an error if the configuration file with the given name does
|
||||
// not exist or is invalid.
|
||||
func Validate(fileName string) (err error) {
|
||||
conf, err := read(fileName)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return err
|
||||
}
|
||||
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return conf.validate()
|
||||
}
|
||||
|
||||
// New creates a new *Manager that persists changes to the file pointed to by
|
||||
// fileName. It reads the configuration file and populates the service fields.
|
||||
// start is the startup time of AdGuard Home.
|
||||
func New(
|
||||
ctx context.Context,
|
||||
fileName string,
|
||||
frontend fs.FS,
|
||||
start time.Time,
|
||||
) (m *Manager, err error) {
|
||||
conf, err := read(fileName)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = conf.validate()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("validating config: %w", err)
|
||||
}
|
||||
|
||||
m = &Manager{
|
||||
updMu: &sync.RWMutex{},
|
||||
current: conf,
|
||||
fileName: fileName,
|
||||
}
|
||||
|
||||
err = m.assemble(ctx, conf, frontend, start)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating config manager: %w", err)
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// read reads and decodes configuration from the provided filename.
|
||||
func read(fileName string) (conf *config, err error) {
|
||||
defer func() { err = errors.Annotate(err, "reading config: %w") }()
|
||||
|
||||
conf := &config{}
|
||||
conf = &config{}
|
||||
f, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
@@ -63,27 +110,7 @@ func New(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Validate the configuration structure. Return an error
|
||||
// if it's incorrect.
|
||||
|
||||
m = &Manager{
|
||||
updMu: &sync.RWMutex{},
|
||||
current: conf,
|
||||
fileName: fileName,
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Get the context with the timeout from the arguments?
|
||||
const assemblyTimeout = 5 * time.Second
|
||||
ctx, cancel := context.WithTimeout(context.Background(), assemblyTimeout)
|
||||
defer cancel()
|
||||
|
||||
err = m.assemble(ctx, conf, frontend, start)
|
||||
if err != nil {
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m, nil
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
// assemble creates all services and puts them into the corresponding fields.
|
||||
@@ -128,6 +155,23 @@ func (m *Manager) assemble(
|
||||
return nil
|
||||
}
|
||||
|
||||
// write writes the current configuration to disk.
|
||||
func (m *Manager) write() (err error) {
|
||||
b, err := yaml.Marshal(m.current)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encoding: %w", err)
|
||||
}
|
||||
|
||||
err = maybe.WriteFile(m.fileName, b, 0o755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing: %w", err)
|
||||
}
|
||||
|
||||
log.Info("configmgr: written to %q", m.fileName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DNS returns the current DNS service. It is safe for concurrent use.
|
||||
func (m *Manager) DNS() (dns agh.ServiceWithConfig[*dnssvc.Config]) {
|
||||
m.updMu.RLock()
|
||||
@@ -150,7 +194,9 @@ func (m *Manager) UpdateDNS(ctx context.Context, c *dnssvc.Config) (err error) {
|
||||
return fmt.Errorf("reassembling dnssvc: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
m.updateCurrentDNS(c)
|
||||
|
||||
return m.write()
|
||||
}
|
||||
|
||||
// updateDNS recreates the DNS service. m.updMu is expected to be locked.
|
||||
@@ -172,6 +218,17 @@ func (m *Manager) updateDNS(ctx context.Context, c *dnssvc.Config) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateCurrentDNS updates the DNS configuration in the current config.
|
||||
func (m *Manager) updateCurrentDNS(c *dnssvc.Config) {
|
||||
m.current.DNS.Addresses = slices.Clone(c.Addresses)
|
||||
m.current.DNS.BootstrapDNS = slices.Clone(c.BootstrapServers)
|
||||
m.current.DNS.UpstreamDNS = slices.Clone(c.UpstreamServers)
|
||||
m.current.DNS.DNS64Prefixes = slices.Clone(c.DNS64Prefixes)
|
||||
m.current.DNS.UpstreamTimeout = timeutil.Duration{Duration: c.UpstreamTimeout}
|
||||
m.current.DNS.BootstrapPreferIPv6 = c.BootstrapPreferIPv6
|
||||
m.current.DNS.UseDNS64 = c.UseDNS64
|
||||
}
|
||||
|
||||
// Web returns the current web service. It is safe for concurrent use.
|
||||
func (m *Manager) Web() (web agh.ServiceWithConfig[*websvc.Config]) {
|
||||
m.updMu.RLock()
|
||||
@@ -194,7 +251,9 @@ func (m *Manager) UpdateWeb(ctx context.Context, c *websvc.Config) (err error) {
|
||||
return fmt.Errorf("reassembling websvc: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
m.updateCurrentWeb(c)
|
||||
|
||||
return m.write()
|
||||
}
|
||||
|
||||
// updateWeb recreates the web service. m.upd is expected to be locked.
|
||||
@@ -213,3 +272,11 @@ func (m *Manager) updateWeb(ctx context.Context, c *websvc.Config) (err error) {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateCurrentWeb updates the web configuration in the current config.
|
||||
func (m *Manager) updateCurrentWeb(c *websvc.Config) {
|
||||
m.current.HTTP.Addresses = slices.Clone(c.Addresses)
|
||||
m.current.HTTP.SecureAddresses = slices.Clone(c.SecureAddresses)
|
||||
m.current.HTTP.Timeout = timeutil.Duration{Duration: c.Timeout}
|
||||
m.current.HTTP.ForceHTTPS = c.ForceHTTPS
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user