all: sync with master
This commit is contained in:
@@ -4,12 +4,11 @@ import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
|
||||
"github.com/AdguardTeam/golibs/container"
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/timeutil"
|
||||
)
|
||||
|
||||
// Configuration Structures
|
||||
|
||||
// config is the top-level on-disk configuration structure.
|
||||
type config struct {
|
||||
DNS *dnsConfig `yaml:"dns"`
|
||||
@@ -19,35 +18,33 @@ type config struct {
|
||||
SchemaVersion int `yaml:"schema_version"`
|
||||
}
|
||||
|
||||
const errNoConf errors.Error = "configuration not found"
|
||||
// type check
|
||||
var _ validator = (*config)(nil)
|
||||
|
||||
// validate returns an error if the configuration structure is invalid.
|
||||
// validate implements the [validator] interface for *config.
|
||||
func (c *config) validate() (err error) {
|
||||
if c == nil {
|
||||
return errNoConf
|
||||
return errors.ErrNoValue
|
||||
}
|
||||
|
||||
// TODO(a.garipov): Add more validations.
|
||||
|
||||
// Keep this in the same order as the fields in the config.
|
||||
validators := []struct {
|
||||
validate func() (err error)
|
||||
name string
|
||||
}{{
|
||||
validate: c.DNS.validate,
|
||||
name: "dns",
|
||||
validators := container.KeyValues[string, validator]{{
|
||||
Key: "dns",
|
||||
Value: c.DNS,
|
||||
}, {
|
||||
validate: c.HTTP.validate,
|
||||
name: "http",
|
||||
Key: "http",
|
||||
Value: c.HTTP,
|
||||
}, {
|
||||
validate: c.Log.validate,
|
||||
name: "log",
|
||||
Key: "log",
|
||||
Value: c.Log,
|
||||
}}
|
||||
|
||||
for _, v := range validators {
|
||||
err = v.validate()
|
||||
for _, kv := range validators {
|
||||
err = kv.Value.validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", v.name, err)
|
||||
return fmt.Errorf("%s: %w", kv.Key, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,16 +62,19 @@ type dnsConfig struct {
|
||||
UseDNS64 bool `yaml:"use_dns64"`
|
||||
}
|
||||
|
||||
// validate returns an error if the DNS configuration structure is invalid.
|
||||
// type check
|
||||
var _ validator = (*dnsConfig)(nil)
|
||||
|
||||
// validate implements the [validator] interface for *dnsConfig.
|
||||
//
|
||||
// TODO(a.garipov): Add more validations.
|
||||
func (c *dnsConfig) validate() (err error) {
|
||||
// TODO(a.garipov): Add more validations.
|
||||
switch {
|
||||
case c == nil:
|
||||
return errNoConf
|
||||
return errors.ErrNoValue
|
||||
case c.UpstreamTimeout.Duration <= 0:
|
||||
return newMustBePositiveError("upstream_timeout", c.UpstreamTimeout)
|
||||
return newErrNotPositive("upstream_timeout", c.UpstreamTimeout)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
@@ -91,15 +91,18 @@ type httpConfig struct {
|
||||
ForceHTTPS bool `yaml:"force_https"`
|
||||
}
|
||||
|
||||
// validate returns an error if the HTTP configuration structure is invalid.
|
||||
// type check
|
||||
var _ validator = (*httpConfig)(nil)
|
||||
|
||||
// validate implements the [validator] interface for *httpConfig.
|
||||
//
|
||||
// TODO(a.garipov): Add more validations.
|
||||
func (c *httpConfig) validate() (err error) {
|
||||
switch {
|
||||
case c == nil:
|
||||
return errNoConf
|
||||
return errors.ErrNoValue
|
||||
case c.Timeout.Duration <= 0:
|
||||
return newMustBePositiveError("timeout", c.Timeout)
|
||||
return newErrNotPositive("timeout", c.Timeout)
|
||||
default:
|
||||
return c.Pprof.validate()
|
||||
}
|
||||
@@ -111,10 +114,13 @@ type httpPprofConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
}
|
||||
|
||||
// validate returns an error if the pprof configuration structure is invalid.
|
||||
// type check
|
||||
var _ validator = (*httpPprofConfig)(nil)
|
||||
|
||||
// validate implements the [validator] interface for *httpPprofConfig.
|
||||
func (c *httpPprofConfig) validate() (err error) {
|
||||
if c == nil {
|
||||
return errNoConf
|
||||
return errors.ErrNoValue
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -126,12 +132,15 @@ type logConfig struct {
|
||||
Verbose bool `yaml:"verbose"`
|
||||
}
|
||||
|
||||
// validate returns an error if the HTTP configuration structure is invalid.
|
||||
// type check
|
||||
var _ validator = (*logConfig)(nil)
|
||||
|
||||
// validate implements the [validator] interface for *logConfig.
|
||||
//
|
||||
// TODO(a.garipov): Add more validations.
|
||||
func (c *logConfig) validate() (err error) {
|
||||
if c == nil {
|
||||
return errNoConf
|
||||
return errors.ErrNoValue
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
"net/netip"
|
||||
"os"
|
||||
"slices"
|
||||
@@ -19,18 +20,23 @@ 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/logutil/slogutil"
|
||||
"github.com/AdguardTeam/golibs/timeutil"
|
||||
"github.com/google/renameio/v2/maybe"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Configuration Manager
|
||||
|
||||
// Manager handles full and partial changes in the configuration, persisting
|
||||
// them to disk if necessary.
|
||||
//
|
||||
// TODO(a.garipov): Support missing configs and default values.
|
||||
type Manager struct {
|
||||
// baseLogger is used to create loggers for other entities.
|
||||
baseLogger *slog.Logger
|
||||
|
||||
// logger is used for logging the operation of the configuration manager.
|
||||
logger *slog.Logger
|
||||
|
||||
// updMu makes sure that at most one reconfiguration is performed at a time.
|
||||
// updMu protects all fields below.
|
||||
updMu *sync.RWMutex
|
||||
@@ -57,12 +63,24 @@ func Validate(fileName string) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// Don't wrap the error, because it's informative enough as is.
|
||||
return conf.validate()
|
||||
err = conf.validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("validating config: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Config contains the configuration parameters for the configuration manager.
|
||||
type Config struct {
|
||||
// BaseLogger is used to create loggers for other entities. It must not be
|
||||
// nil.
|
||||
BaseLogger *slog.Logger
|
||||
|
||||
// Logger is used for logging the operation of the configuration manager.
|
||||
// It must not be nil.
|
||||
Logger *slog.Logger
|
||||
|
||||
// Frontend is the filesystem with the frontend files.
|
||||
Frontend fs.FS
|
||||
|
||||
@@ -93,9 +111,11 @@ func New(ctx context.Context, c *Config) (m *Manager, err error) {
|
||||
}
|
||||
|
||||
m = &Manager{
|
||||
updMu: &sync.RWMutex{},
|
||||
current: conf,
|
||||
fileName: c.FileName,
|
||||
baseLogger: c.BaseLogger,
|
||||
logger: c.Logger,
|
||||
updMu: &sync.RWMutex{},
|
||||
current: conf,
|
||||
fileName: c.FileName,
|
||||
}
|
||||
|
||||
err = m.assemble(ctx, conf, c.Frontend, c.WebAddr, c.Start)
|
||||
@@ -137,6 +157,7 @@ func (m *Manager) assemble(
|
||||
start time.Time,
|
||||
) (err error) {
|
||||
dnsConf := &dnssvc.Config{
|
||||
Logger: m.baseLogger.With(slogutil.KeyPrefix, "dnssvc"),
|
||||
Addresses: conf.DNS.Addresses,
|
||||
BootstrapServers: conf.DNS.BootstrapDNS,
|
||||
UpstreamServers: conf.DNS.UpstreamDNS,
|
||||
@@ -151,6 +172,7 @@ func (m *Manager) assemble(
|
||||
}
|
||||
|
||||
webSvcConf := &websvc.Config{
|
||||
Logger: m.baseLogger.With(slogutil.KeyPrefix, "websvc"),
|
||||
Pprof: &websvc.PprofConfig{
|
||||
Port: conf.HTTP.Pprof.Port,
|
||||
Enabled: conf.HTTP.Pprof.Enabled,
|
||||
@@ -176,18 +198,18 @@ func (m *Manager) assemble(
|
||||
}
|
||||
|
||||
// write writes the current configuration to disk.
|
||||
func (m *Manager) write() (err error) {
|
||||
func (m *Manager) write(ctx context.Context) (err error) {
|
||||
b, err := yaml.Marshal(m.current)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encoding: %w", err)
|
||||
}
|
||||
|
||||
err = aghos.WriteFile(m.fileName, b, aghos.DefaultPermFile)
|
||||
err = maybe.WriteFile(m.fileName, b, aghos.DefaultPermFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing: %w", err)
|
||||
}
|
||||
|
||||
log.Info("configmgr: written to %q", m.fileName)
|
||||
m.logger.InfoContext(ctx, "config file written", "path", m.fileName)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -216,7 +238,7 @@ func (m *Manager) UpdateDNS(ctx context.Context, c *dnssvc.Config) (err error) {
|
||||
|
||||
m.updateCurrentDNS(c)
|
||||
|
||||
return m.write()
|
||||
return m.write(ctx)
|
||||
}
|
||||
|
||||
// updateDNS recreates the DNS service. m.updMu is expected to be locked.
|
||||
@@ -270,7 +292,7 @@ func (m *Manager) UpdateWeb(ctx context.Context, c *websvc.Config) (err error) {
|
||||
|
||||
m.updateCurrentWeb(c)
|
||||
|
||||
return m.write()
|
||||
return m.write(ctx)
|
||||
}
|
||||
|
||||
// updateWeb recreates the web service. m.upd is expected to be locked.
|
||||
|
||||
@@ -3,25 +3,29 @@ package configmgr
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/AdguardTeam/golibs/errors"
|
||||
"github.com/AdguardTeam/golibs/timeutil"
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
// validator is the interface for configuration entities that can validate
|
||||
// themselves.
|
||||
type validator interface {
|
||||
// validate returns an error if the entity isn't valid.
|
||||
validate() (err error)
|
||||
}
|
||||
|
||||
// numberOrDuration is the constraint for integer types along with
|
||||
// timeutil.Duration.
|
||||
type numberOrDuration interface {
|
||||
constraints.Integer | timeutil.Duration
|
||||
}
|
||||
|
||||
// newMustBePositiveError returns an error about the value that must be positive
|
||||
// but isn't. prop is the name of the property to mention in the error message.
|
||||
// newErrNotPositive returns an error about the value that must be positive but
|
||||
// isn't. prop is the name of the property to mention in the error message.
|
||||
//
|
||||
// TODO(a.garipov): Consider moving such helpers to golibs and use in AdGuardDNS
|
||||
// as well.
|
||||
func newMustBePositiveError[T numberOrDuration](prop string, v T) (err error) {
|
||||
if s, ok := any(v).(fmt.Stringer); ok {
|
||||
return fmt.Errorf("%s must be positive, got %s", prop, s)
|
||||
}
|
||||
|
||||
return fmt.Errorf("%s must be positive, got %d", prop, v)
|
||||
func newErrNotPositive[T numberOrDuration](prop string, v T) (err error) {
|
||||
return fmt.Errorf("%s: %w, got %v", prop, errors.ErrNotPositive, v)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user