144 lines
3.9 KiB
Go
144 lines
3.9 KiB
Go
package services
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
"github.com/wild-cloud/wild-central/daemon/internal/contracts"
|
|
"github.com/wild-cloud/wild-central/daemon/internal/operations"
|
|
"github.com/wild-cloud/wild-central/daemon/internal/storage"
|
|
)
|
|
|
|
// UpdateConfig updates service configuration and optionally redeploys
|
|
func (m *Manager) UpdateConfig(instanceName, serviceName string, update contracts.ServiceConfigUpdate, broadcaster *operations.Broadcaster) (*contracts.ServiceConfigResponse, error) {
|
|
// 1. Validate service exists
|
|
manifest, err := m.GetManifest(serviceName)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("service not found: %w", err)
|
|
}
|
|
|
|
namespace := manifest.Namespace
|
|
if deployment, ok := serviceDeployments[serviceName]; ok {
|
|
namespace = deployment.namespace
|
|
}
|
|
|
|
// 2. Load instance config
|
|
instanceDir := filepath.Join(m.dataDir, "instances", instanceName)
|
|
configPath := filepath.Join(instanceDir, "config.yaml")
|
|
|
|
if !storage.FileExists(configPath) {
|
|
return nil, fmt.Errorf("config file not found for instance %s", instanceName)
|
|
}
|
|
|
|
configData, err := os.ReadFile(configPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read config: %w", err)
|
|
}
|
|
|
|
var config map[string]interface{}
|
|
if err := yaml.Unmarshal(configData, &config); err != nil {
|
|
return nil, fmt.Errorf("failed to parse config: %w", err)
|
|
}
|
|
|
|
// 3. Validate config keys against service manifest
|
|
validPaths := make(map[string]bool)
|
|
for _, path := range manifest.ConfigReferences {
|
|
validPaths[path] = true
|
|
}
|
|
for _, cfg := range manifest.ServiceConfig {
|
|
validPaths[cfg.Path] = true
|
|
}
|
|
|
|
for key := range update.Config {
|
|
if !validPaths[key] {
|
|
return nil, fmt.Errorf("invalid config key '%s' for service %s", key, serviceName)
|
|
}
|
|
}
|
|
|
|
// 4. Update config values
|
|
for key, value := range update.Config {
|
|
if err := setNestedValue(config, key, value); err != nil {
|
|
return nil, fmt.Errorf("failed to set config key '%s': %w", key, err)
|
|
}
|
|
}
|
|
|
|
// 5. Write updated config
|
|
updatedData, err := yaml.Marshal(config)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal config: %w", err)
|
|
}
|
|
|
|
if err := os.WriteFile(configPath, updatedData, 0644); err != nil {
|
|
return nil, fmt.Errorf("failed to write config: %w", err)
|
|
}
|
|
|
|
// 6. Redeploy if requested
|
|
redeployed := false
|
|
if update.Redeploy {
|
|
// Fetch fresh templates if requested
|
|
if update.Fetch {
|
|
if err := m.Fetch(instanceName, serviceName); err != nil {
|
|
return nil, fmt.Errorf("failed to fetch templates: %w", err)
|
|
}
|
|
}
|
|
|
|
// Recompile templates
|
|
if err := m.Compile(instanceName, serviceName); err != nil {
|
|
return nil, fmt.Errorf("failed to recompile templates: %w", err)
|
|
}
|
|
|
|
// Redeploy service
|
|
if err := m.Deploy(instanceName, serviceName, "", broadcaster); err != nil {
|
|
return nil, fmt.Errorf("failed to redeploy service: %w", err)
|
|
}
|
|
|
|
redeployed = true
|
|
}
|
|
|
|
// 7. Build response
|
|
message := "Service configuration updated successfully"
|
|
if redeployed {
|
|
message = "Service configuration updated and redeployed successfully"
|
|
}
|
|
|
|
return &contracts.ServiceConfigResponse{
|
|
Service: serviceName,
|
|
Namespace: namespace,
|
|
Config: update.Config,
|
|
Redeployed: redeployed,
|
|
Message: message,
|
|
}, nil
|
|
}
|
|
|
|
// setNestedValue sets a value in a nested map using dot notation
|
|
func setNestedValue(data map[string]interface{}, path string, value interface{}) error {
|
|
keys := strings.Split(path, ".")
|
|
current := data
|
|
|
|
for i, key := range keys {
|
|
if i == len(keys)-1 {
|
|
// Last key - set the value
|
|
current[key] = value
|
|
return nil
|
|
}
|
|
|
|
// Navigate to the next level
|
|
if next, ok := current[key].(map[string]interface{}); ok {
|
|
current = next
|
|
} else if current[key] == nil {
|
|
// Create intermediate map if it doesn't exist
|
|
next := make(map[string]interface{})
|
|
current[key] = next
|
|
current = next
|
|
} else {
|
|
return fmt.Errorf("path '%s' conflicts with existing non-map value at '%s'", path, key)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|