Smail/server/config/loader.go

113 lines
3.4 KiB
Go

package config
import (
"errors"
"fmt"
"os"
"path/filepath"
"time"
)
var (
ErrConfigRootMissing = errors.New("configuration root is missing")
ErrConfigSectionMissing = errors.New("configuration section is missing")
ErrConfigDigestMismatch = errors.New("configuration section digest mismatch")
ErrConfigGenerationMismatch = errors.New("configuration generation mismatch")
ErrConfigRecoveryRequired = errors.New("configuration recovery is required")
)
type ConfigLoaderOptions struct {
Validation RuntimeValidationOptions
}
type ConfigLoader struct {
root string
options ConfigLoaderOptions
}
func NewConfigLoader(root string, options ConfigLoaderOptions) *ConfigLoader {
return &ConfigLoader{root: root, options: options}
}
func (l *ConfigLoader) Load() (RuntimeConfig, error) {
if l == nil || l.root == "" {
return RuntimeConfig{}, ErrConfigRootMissing
}
directory, err := secureManagerConfigDirectory(l.root)
if err != nil {
return RuntimeConfig{}, err
}
rootData, err := readManagerConfigFile(directory, "config.json")
if err != nil {
if os.IsNotExist(err) {
return RuntimeConfig{}, ErrConfigRootMissing
}
return RuntimeConfig{}, err
}
var root RootConfig
if err := ParseStrict(rootData, &root); err != nil {
return RuntimeConfig{}, fmt.Errorf("load root configuration: %w", err)
}
root = normalizeRootConfig(root)
if err := ValidateSectionReferences(root.Sections, root.Generation); err != nil {
return RuntimeConfig{}, err
}
runtime := RuntimeConfig{
Generation: root.Generation,
SetupState: root.SetupState,
Root: cloneRootConfig(root),
LoadedAt: time.Now().UTC(),
}
sectionData := make(map[Section][]byte, len(requiredPartitionSections))
for _, section := range requiredPartitionSections {
file, _ := FixedSectionFile(section)
data, readErr := readManagerConfigFile(directory, file)
if readErr != nil {
if os.IsNotExist(readErr) {
return RuntimeConfig{}, fmt.Errorf("%w: %s", ErrConfigSectionMissing, file)
}
return RuntimeConfig{}, readErr
}
reference := root.Sections[section]
if SectionDigest(data) != reference.SHA256 {
return RuntimeConfig{}, fmt.Errorf("%w: %s", ErrConfigDigestMismatch, file)
}
sectionData[section] = data
}
if err := ParseStrict(sectionData[SectionSMTP], &runtime.SMTP); err != nil {
return RuntimeConfig{}, fmt.Errorf("load SMTP section: %w", err)
}
if err := ParseStrict(sectionData[SectionEmail], &runtime.Email); err != nil {
return RuntimeConfig{}, fmt.Errorf("load email section: %w", err)
}
if err := ParseStrict(sectionData[SectionSSL], &runtime.SSL); err != nil {
return RuntimeConfig{}, fmt.Errorf("load SSL section: %w", err)
}
if err := ParseStrict(sectionData[SectionPlugins], &runtime.Plugins); err != nil {
return RuntimeConfig{}, fmt.Errorf("load plugin section: %w", err)
}
if err := ValidateRuntimeConfig(runtime, l.options.Validation); err != nil {
return RuntimeConfig{}, err
}
return cloneRuntimeConfig(runtime), nil
}
func (l *ConfigLoader) ConfigDirectory() (string, error) {
if l == nil || l.root == "" {
return "", ErrConfigRootMissing
}
return secureManagerConfigDirectory(l.root)
}
func readConfigSection(root string, section Section) ([]byte, error) {
file, ok := FixedSectionFile(section)
if !ok || section == SectionRoot {
return nil, ErrInvalidSection
}
directory, err := secureManagerConfigDirectory(root)
if err != nil {
return nil, err
}
return readManagerConfigFile(directory, filepath.Base(file))
}