Some checks are pending
Security Checks / security (push) Waiting to run
统一 frontend、backend、test、test-log 和 out 目录,更新构建、部署及测试入口;纳入配置管理兼容与首次安全引导修复;排除本机配置、密钥和测试产物。
113 lines
3.4 KiB
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))
|
|
}
|