Smail/server/config/snapshot.go
smail 70a2f39042
Some checks are pending
Security Checks / security (push) Waiting to run
完善配置快照预检与数据库迁移失败处理
2026-08-30 21:34:23 +08:00

96 lines
2.4 KiB
Go

package config
import (
"errors"
"sync/atomic"
)
var ErrRuntimeSnapshotUnavailable = errors.New("runtime configuration snapshot unavailable")
type SnapshotStore struct {
current atomic.Pointer[RuntimeConfig]
options RuntimeValidationOptions
}
func NewSnapshotStore(initial RuntimeConfig, options RuntimeValidationOptions) (*SnapshotStore, error) {
store := &SnapshotStore{options: options}
if err := store.Publish(initial); err != nil {
return nil, err
}
return store, nil
}
func (s *SnapshotStore) Validate(candidate RuntimeConfig) error {
if s == nil {
return ErrRuntimeSnapshotUnavailable
}
return ValidateRuntimeConfig(candidate, s.options)
}
func (s *SnapshotStore) Publish(candidate RuntimeConfig) error {
if s == nil {
return ErrRuntimeSnapshotUnavailable
}
candidate = cloneRuntimeConfig(candidate)
if err := s.Validate(candidate); err != nil {
return err
}
for {
current := s.current.Load()
if current != nil && candidate.Generation < current.Generation {
return ErrGenerationConflict
}
if s.current.CompareAndSwap(current, &candidate) {
return nil
}
}
}
func (s *SnapshotStore) Snapshot() (RuntimeConfig, error) {
if s == nil {
return RuntimeConfig{}, ErrRuntimeSnapshotUnavailable
}
current := s.current.Load()
if current == nil {
return RuntimeConfig{}, ErrRuntimeSnapshotUnavailable
}
return cloneRuntimeConfig(*current), nil
}
func (s *SnapshotStore) Generation() uint64 {
if s == nil {
return 0
}
current := s.current.Load()
if current == nil {
return 0
}
return current.Generation
}
func cloneRuntimeConfig(source RuntimeConfig) RuntimeConfig {
clone := source
clone.Root = cloneRootConfig(source.Root)
clone.SMTP = source.SMTP
clone.Email = source.Email
clone.SSL = source.SSL
clone.Plugins = clonePluginsSection(source.Plugins)
return clone
}
func clonePluginsSection(source PluginsSectionConfig) PluginsSectionConfig {
clone := source
if source.Plugins == nil {
return clone
}
clone.Plugins = make(map[string]PluginInstance, len(source.Plugins))
for logicalID, plugin := range source.Plugins {
clonedPlugin := plugin
clonedPlugin.Permissions.Capabilities = append([]string(nil), plugin.Permissions.Capabilities...)
clonedPlugin.Permissions.Network = append([]PluginNetworkTarget(nil), plugin.Permissions.Network...)
clonedPlugin.Settings = append([]byte(nil), plugin.Settings...)
clone.Plugins[logicalID] = clonedPlugin
}
return clone
}