initial
This commit is contained in:
373
pkg/config/manager.go
Normal file
373
pkg/config/manager.go
Normal file
@@ -0,0 +1,373 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
// Manager manages application configuration with value resolution.
|
||||
type Manager struct {
|
||||
mu sync.RWMutex
|
||||
config map[string]interface{}
|
||||
environment string
|
||||
resolver *Resolver
|
||||
loader *Loader
|
||||
configFile string
|
||||
resolvedCache map[string]interface{}
|
||||
fs afero.Fs
|
||||
}
|
||||
|
||||
// NewManager creates a new configuration manager.
|
||||
func NewManager() *Manager {
|
||||
fs := afero.NewOsFs()
|
||||
return &Manager{
|
||||
config: make(map[string]interface{}),
|
||||
loader: NewLoader(fs),
|
||||
resolvedCache: make(map[string]interface{}),
|
||||
fs: fs,
|
||||
}
|
||||
}
|
||||
|
||||
// SetFs sets the filesystem to use for all file operations.
|
||||
// This is primarily useful for testing with an in-memory filesystem.
|
||||
func (m *Manager) SetFs(fs afero.Fs) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
m.fs = fs
|
||||
m.loader = NewLoader(fs)
|
||||
|
||||
// If we have a resolver, recreate it with the new fs
|
||||
if m.resolver != nil {
|
||||
gcpProject := ""
|
||||
awsRegion := "us-east-1"
|
||||
|
||||
// Try to get the current settings
|
||||
if gcpProj := m.getConfigValue("GCPProject", ""); gcpProj != nil {
|
||||
if str, ok := gcpProj.(string); ok {
|
||||
gcpProject = str
|
||||
}
|
||||
}
|
||||
if awsReg := m.getConfigValue("AWSRegion", "us-east-1"); awsReg != nil {
|
||||
if str, ok := awsReg.(string); ok {
|
||||
awsRegion = str
|
||||
}
|
||||
}
|
||||
|
||||
m.resolver = NewResolver(gcpProject, awsRegion, fs)
|
||||
}
|
||||
|
||||
// Clear caches as filesystem changed
|
||||
m.resolvedCache = make(map[string]interface{})
|
||||
}
|
||||
|
||||
// LoadFile loads configuration from a specific file.
|
||||
func (m *Manager) LoadFile(configFile string) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
config, err := m.loader.LoadYAML(configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.config = config
|
||||
m.configFile = configFile
|
||||
m.resolvedCache = make(map[string]interface{}) // Clear cache
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadConfig loads the configuration from file.
|
||||
func (m *Manager) loadConfig() error {
|
||||
if m.configFile == "" {
|
||||
// Try to find config.yaml
|
||||
configPath, err := m.loader.FindConfigFile("config.yaml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.configFile = configPath
|
||||
}
|
||||
|
||||
config, err := m.loader.LoadYAML(m.configFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.config = config
|
||||
m.resolvedCache = make(map[string]interface{}) // Clear cache
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetEnvironment sets the active environment.
|
||||
func (m *Manager) SetEnvironment(environment string) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
m.environment = strings.ToLower(environment)
|
||||
|
||||
// Create resolver with GCP project and AWS region if available
|
||||
gcpProject := m.getConfigValue("GCPProject", "")
|
||||
awsRegion := m.getConfigValue("AWSRegion", "us-east-1")
|
||||
|
||||
if gcpProjectStr, ok := gcpProject.(string); ok {
|
||||
if awsRegionStr, ok := awsRegion.(string); ok {
|
||||
m.resolver = NewResolver(gcpProjectStr, awsRegionStr, m.fs)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear resolved cache when environment changes
|
||||
m.resolvedCache = make(map[string]interface{})
|
||||
}
|
||||
|
||||
// Get retrieves a configuration value.
|
||||
func (m *Manager) Get(key string, defaultValue interface{}) interface{} {
|
||||
m.mu.RLock()
|
||||
|
||||
// Ensure config is loaded
|
||||
if m.config == nil || len(m.config) == 0 {
|
||||
// Need to upgrade to write lock to load config
|
||||
m.mu.RUnlock()
|
||||
m.mu.Lock()
|
||||
// Double-check after acquiring write lock
|
||||
if m.config == nil || len(m.config) == 0 {
|
||||
if err := m.loadConfig(); err != nil {
|
||||
log.Printf("Failed to load config: %v", err)
|
||||
m.mu.Unlock()
|
||||
return defaultValue
|
||||
}
|
||||
}
|
||||
// Downgrade back to read lock
|
||||
m.mu.Unlock()
|
||||
m.mu.RLock()
|
||||
}
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
// Check cache first
|
||||
cacheKey := fmt.Sprintf("config.%s", key)
|
||||
if cached, ok := m.resolvedCache[cacheKey]; ok {
|
||||
return cached
|
||||
}
|
||||
|
||||
// Try environment-specific config first
|
||||
var rawValue interface{}
|
||||
if m.environment != "" {
|
||||
envMap, ok := m.config["environments"].(map[string]interface{})
|
||||
if ok {
|
||||
if env, ok := envMap[m.environment].(map[string]interface{}); ok {
|
||||
if config, ok := env["config"].(map[string]interface{}); ok {
|
||||
if val, exists := config[key]; exists {
|
||||
rawValue = val
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to configDefaults
|
||||
if rawValue == nil {
|
||||
if defaults, ok := m.config["configDefaults"].(map[string]interface{}); ok {
|
||||
if val, exists := defaults[key]; exists {
|
||||
rawValue = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if rawValue == nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// Resolve the value if we have a resolver
|
||||
var resolvedValue interface{}
|
||||
if m.resolver != nil {
|
||||
resolvedValue = m.resolver.Resolve(rawValue)
|
||||
} else {
|
||||
resolvedValue = rawValue
|
||||
}
|
||||
|
||||
// Cache the resolved value
|
||||
m.resolvedCache[cacheKey] = resolvedValue
|
||||
|
||||
return resolvedValue
|
||||
}
|
||||
|
||||
// GetSecret retrieves a secret value for the current environment.
|
||||
func (m *Manager) GetSecret(key string, defaultValue interface{}) interface{} {
|
||||
m.mu.RLock()
|
||||
|
||||
// Ensure config is loaded
|
||||
if m.config == nil || len(m.config) == 0 {
|
||||
// Need to upgrade to write lock to load config
|
||||
m.mu.RUnlock()
|
||||
m.mu.Lock()
|
||||
// Double-check after acquiring write lock
|
||||
if m.config == nil || len(m.config) == 0 {
|
||||
if err := m.loadConfig(); err != nil {
|
||||
log.Printf("Failed to load config: %v", err)
|
||||
m.mu.Unlock()
|
||||
return defaultValue
|
||||
}
|
||||
}
|
||||
// Downgrade back to read lock
|
||||
m.mu.Unlock()
|
||||
m.mu.RLock()
|
||||
}
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
if m.environment == "" {
|
||||
log.Printf("No environment set when getting secret '%s'", key)
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// Get the current environment's config
|
||||
envMap, ok := m.config["environments"].(map[string]interface{})
|
||||
if !ok {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
env, ok := envMap[m.environment].(map[string]interface{})
|
||||
if !ok {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
secrets, ok := env["secrets"].(map[string]interface{})
|
||||
if !ok {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
secretValue, exists := secrets[key]
|
||||
if !exists {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// Resolve the value
|
||||
if m.resolver != nil {
|
||||
resolved := m.resolver.Resolve(secretValue)
|
||||
if resolved == nil {
|
||||
return defaultValue
|
||||
}
|
||||
return resolved
|
||||
}
|
||||
|
||||
return secretValue
|
||||
}
|
||||
|
||||
// getConfigValue is an internal helper to get config values without locking.
|
||||
func (m *Manager) getConfigValue(key string, defaultValue interface{}) interface{} {
|
||||
// Try environment-specific config first
|
||||
var rawValue interface{}
|
||||
if m.environment != "" {
|
||||
envMap, ok := m.config["environments"].(map[string]interface{})
|
||||
if ok {
|
||||
if env, ok := envMap[m.environment].(map[string]interface{}); ok {
|
||||
if config, ok := env["config"].(map[string]interface{}); ok {
|
||||
if val, exists := config[key]; exists {
|
||||
rawValue = val
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to configDefaults
|
||||
if rawValue == nil {
|
||||
if defaults, ok := m.config["configDefaults"].(map[string]interface{}); ok {
|
||||
if val, exists := defaults[key]; exists {
|
||||
rawValue = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if rawValue == nil {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
return rawValue
|
||||
}
|
||||
|
||||
// Reload reloads the configuration from file.
|
||||
func (m *Manager) Reload() error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
return m.loadConfig()
|
||||
}
|
||||
|
||||
// GetAllConfig returns all configuration values for the current environment.
|
||||
func (m *Manager) GetAllConfig() map[string]interface{} {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
result := make(map[string]interface{})
|
||||
|
||||
// Start with configDefaults
|
||||
if defaults, ok := m.config["configDefaults"].(map[string]interface{}); ok {
|
||||
for k, v := range defaults {
|
||||
if m.resolver != nil {
|
||||
result[k] = m.resolver.Resolve(v)
|
||||
} else {
|
||||
result[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Override with environment-specific config
|
||||
if m.environment != "" {
|
||||
envMap, ok := m.config["environments"].(map[string]interface{})
|
||||
if ok {
|
||||
if env, ok := envMap[m.environment].(map[string]interface{}); ok {
|
||||
if config, ok := env["config"].(map[string]interface{}); ok {
|
||||
for k, v := range config {
|
||||
if m.resolver != nil {
|
||||
result[k] = m.resolver.Resolve(v)
|
||||
} else {
|
||||
result[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GetAllSecrets returns all secrets for the current environment.
|
||||
func (m *Manager) GetAllSecrets() map[string]interface{} {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
if m.environment == "" {
|
||||
return make(map[string]interface{})
|
||||
}
|
||||
|
||||
envMap, ok := m.config["environments"].(map[string]interface{})
|
||||
if !ok {
|
||||
return make(map[string]interface{})
|
||||
}
|
||||
|
||||
env, ok := envMap[m.environment].(map[string]interface{})
|
||||
if !ok {
|
||||
return make(map[string]interface{})
|
||||
}
|
||||
|
||||
secrets, ok := env["secrets"].(map[string]interface{})
|
||||
if !ok {
|
||||
return make(map[string]interface{})
|
||||
}
|
||||
|
||||
// Resolve all secrets
|
||||
result := make(map[string]interface{})
|
||||
for k, v := range secrets {
|
||||
if m.resolver != nil {
|
||||
result[k] = m.resolver.Resolve(v)
|
||||
} else {
|
||||
result[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user