Files
seaweedfs/weed/admin/plugin/config_manager.go

308 lines
7.5 KiB
Go

package plugin
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"sync"
"time"
"github.com/seaweedfs/seaweedfs/weed/pb/plugin_pb"
)
// ConfigManager handles configuration persistence and retrieval
type ConfigManager struct {
dataDir string
configs map[string]*JobTypeConfig
mu sync.RWMutex
}
// NewConfigManager creates a new configuration manager
func NewConfigManager(dataDir string) (*ConfigManager, error) {
cm := &ConfigManager{
dataDir: filepath.Join(dataDir, "plugins"),
configs: make(map[string]*JobTypeConfig),
}
// Create plugins directory if it doesn't exist
if err := os.MkdirAll(cm.dataDir, 0755); err != nil {
return nil, fmt.Errorf("failed to create plugin config directory: %w", err)
}
return cm, nil
}
// LoadConfig loads a configuration from disk
func (cm *ConfigManager) LoadConfig(jobType string) (*JobTypeConfig, error) {
cm.mu.Lock()
defer cm.mu.Unlock()
// Check cache first
if config, exists := cm.configs[jobType]; exists {
return config, nil
}
configPath := filepath.Join(cm.dataDir, jobType+".json")
// If file doesn't exist, create default config
if _, err := os.Stat(configPath); err != nil {
if os.IsNotExist(err) {
config := &JobTypeConfig{
JobType: jobType,
Enabled: false,
AdminConfig: make([]*plugin_pb.ConfigFieldValue, 0),
WorkerConfig: make([]*plugin_pb.ConfigFieldValue, 0),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
CreatedBy: "system",
}
cm.configs[jobType] = config
return config, nil
}
return nil, fmt.Errorf("failed to access config file: %w", err)
}
// Load from file
data, err := os.ReadFile(configPath)
if err != nil {
return nil, fmt.Errorf("failed to read config file: %w", err)
}
var configData map[string]interface{}
if err := json.Unmarshal(data, &configData); err != nil {
return nil, fmt.Errorf("failed to parse config file: %w", err)
}
config := &JobTypeConfig{
JobType: jobType,
AdminConfig: make([]*plugin_pb.ConfigFieldValue, 0),
WorkerConfig: make([]*plugin_pb.ConfigFieldValue, 0),
}
// Parse fields
if enabled, ok := configData["enabled"].(bool); ok {
config.Enabled = enabled
}
if createdBy, ok := configData["created_by"].(string); ok {
config.CreatedBy = createdBy
}
if createdAt, ok := configData["created_at"].(string); ok {
if t, err := time.Parse(time.RFC3339, createdAt); err == nil {
config.CreatedAt = t
}
}
if updatedAt, ok := configData["updated_at"].(string); ok {
if t, err := time.Parse(time.RFC3339, updatedAt); err == nil {
config.UpdatedAt = t
}
}
cm.configs[jobType] = config
return config, nil
}
// SaveConfig saves a configuration to disk
func (cm *ConfigManager) SaveConfig(config *JobTypeConfig) error {
cm.mu.Lock()
defer cm.mu.Unlock()
config.UpdatedAt = time.Now()
cm.configs[config.JobType] = config
configPath := filepath.Join(cm.dataDir, config.JobType+".json")
// Convert to JSON-serializable format
configData := map[string]interface{}{
"job_type": config.JobType,
"enabled": config.Enabled,
"created_by": config.CreatedBy,
"created_at": config.CreatedAt.Format(time.RFC3339),
"updated_at": config.UpdatedAt.Format(time.RFC3339),
"admin_config": cm.configValuesToJSON(config.AdminConfig),
"worker_config": cm.configValuesToJSON(config.WorkerConfig),
}
data, err := json.MarshalIndent(configData, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal config: %w", err)
}
if err := os.WriteFile(configPath, data, 0644); err != nil {
return fmt.Errorf("failed to write config file: %w", err)
}
return nil
}
// GetConfig retrieves a configuration
func (cm *ConfigManager) GetConfig(jobType string) (*JobTypeConfig, error) {
cm.mu.RLock()
if config, exists := cm.configs[jobType]; exists {
cm.mu.RUnlock()
return config, nil
}
cm.mu.RUnlock()
// Load from disk if not in cache
return cm.LoadConfig(jobType)
}
// UpdateConfig updates a configuration
func (cm *ConfigManager) UpdateConfig(jobType string, enabled bool, adminConfig, workerConfig []*plugin_pb.ConfigFieldValue, updatedBy string) (*JobTypeConfig, error) {
config, err := cm.GetConfig(jobType)
if err != nil {
return nil, err
}
config.SetConfig(enabled, adminConfig, workerConfig)
config.CreatedBy = updatedBy
if err := cm.SaveConfig(config); err != nil {
return nil, err
}
return config, nil
}
// ListConfigs lists all configurations
func (cm *ConfigManager) ListConfigs() []*JobTypeConfig {
cm.mu.RLock()
defer cm.mu.RUnlock()
configs := make([]*JobTypeConfig, 0, len(cm.configs))
for _, config := range cm.configs {
configs = append(configs, config)
}
return configs
}
// DeleteConfig deletes a configuration
func (cm *ConfigManager) DeleteConfig(jobType string) error {
cm.mu.Lock()
defer cm.mu.Unlock()
configPath := filepath.Join(cm.dataDir, jobType+".json")
if err := os.Remove(configPath); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to delete config file: %w", err)
}
delete(cm.configs, jobType)
return nil
}
// configValuesToJSON converts ConfigFieldValue slice to JSON
func (cm *ConfigManager) configValuesToJSON(values []*plugin_pb.ConfigFieldValue) []map[string]interface{} {
result := make([]map[string]interface{}, 0, len(values))
for _, val := range values {
item := map[string]interface{}{
"field_name": val.FieldName,
}
// Include non-empty values
if val.StringValue != "" {
item["string_value"] = val.StringValue
}
if val.IntValue != 0 {
item["int_value"] = val.IntValue
}
if val.FloatValue != 0 {
item["float_value"] = val.FloatValue
}
if val.BoolValue {
item["bool_value"] = val.BoolValue
}
if val.DurationValue != nil {
item["duration_value"] = val.DurationValue.String()
}
if len(val.MultiselectValues) > 0 {
item["multiselect_values"] = val.MultiselectValues
}
if val.JsonValue != "" {
item["json_value"] = val.JsonValue
}
result = append(result, item)
}
return result
}
// LoadAllConfigs loads all configurations from disk
func (cm *ConfigManager) LoadAllConfigs() error {
cm.mu.Lock()
defer cm.mu.Unlock()
entries, err := os.ReadDir(cm.dataDir)
if err != nil {
return fmt.Errorf("failed to read config directory: %w", err)
}
for _, entry := range entries {
if entry.IsDir() {
continue
}
// Only load .json files
if filepath.Ext(entry.Name()) != ".json" {
continue
}
jobType := entry.Name()[:len(entry.Name())-5] // Remove .json
configPath := filepath.Join(cm.dataDir, entry.Name())
data, err := os.ReadFile(configPath)
if err != nil {
continue
}
var configData map[string]interface{}
if err := json.Unmarshal(data, &configData); err != nil {
continue
}
config := &JobTypeConfig{
JobType: jobType,
AdminConfig: make([]*plugin_pb.ConfigFieldValue, 0),
WorkerConfig: make([]*plugin_pb.ConfigFieldValue, 0),
}
// Parse fields
if enabled, ok := configData["enabled"].(bool); ok {
config.Enabled = enabled
}
if createdBy, ok := configData["created_by"].(string); ok {
config.CreatedBy = createdBy
}
if createdAt, ok := configData["created_at"].(string); ok {
if t, err := time.Parse(time.RFC3339, createdAt); err == nil {
config.CreatedAt = t
}
}
if updatedAt, ok := configData["updated_at"].(string); ok {
if t, err := time.Parse(time.RFC3339, updatedAt); err == nil {
config.UpdatedAt = t
}
}
cm.configs[jobType] = config
}
return nil
}
// GetDataDir returns the data directory path
func (cm *ConfigManager) GetDataDir() string {
return cm.dataDir
}