feat: deployed app removal + missing field injection (v0.19.0)
Add "Eltávolítás" to remove deployed (non-orphaned) stacks — reverts them to "Nincs telepítve" while preserving templates for redeploy. Modal offers HDD data and backup data cleanup choices. Auto-inject missing deploy fields (secrets, domains) into existing app.yaml when templates are updated via sync or on controller startup. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ package stacks
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
@@ -424,6 +425,16 @@ func generateValue(spec string) (string, error) {
|
||||
return "", fmt.Errorf("reading random bytes: %w", err)
|
||||
}
|
||||
return hex.EncodeToString(b), nil
|
||||
case "base64key":
|
||||
byteLen := 0
|
||||
if _, err := fmt.Sscanf(parts[1], "%d", &byteLen); err != nil || byteLen <= 0 {
|
||||
return "", fmt.Errorf("invalid base64key length: %q", parts[1])
|
||||
}
|
||||
b := make([]byte, byteLen)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return "", fmt.Errorf("reading random bytes: %w", err)
|
||||
}
|
||||
return "base64:" + base64.StdEncoding.EncodeToString(b), nil
|
||||
case "static":
|
||||
return parts[1], nil
|
||||
default:
|
||||
@@ -431,6 +442,77 @@ func generateValue(spec string) (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// InjectMissingFields checks deployed stacks for new deploy_fields that are not
|
||||
// yet in app.yaml and auto-generates values for secret/domain fields.
|
||||
// Called after sync (for updated stacks) and on startup (for all deployed stacks).
|
||||
func (m *Manager) InjectMissingFields(stackNames []string) {
|
||||
for _, name := range stackNames {
|
||||
stack, ok := m.GetStack(name)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
stackDir := filepath.Dir(stack.ComposePath)
|
||||
meta := LoadMetadata(stackDir)
|
||||
appCfg := LoadAppConfig(stackDir)
|
||||
if appCfg == nil || !appCfg.Deployed {
|
||||
continue
|
||||
}
|
||||
|
||||
var injected []string
|
||||
for _, field := range meta.DeployFields {
|
||||
if _, exists := appCfg.Env[field.EnvVar]; exists {
|
||||
continue // already present
|
||||
}
|
||||
|
||||
switch field.Type {
|
||||
case "secret":
|
||||
if field.Generate == "" {
|
||||
m.logger.Printf("[WARN] Stack %s: new secret field %s has no generator — skipping", name, field.EnvVar)
|
||||
continue
|
||||
}
|
||||
value, err := generateValue(field.Generate)
|
||||
if err != nil {
|
||||
m.logger.Printf("[ERROR] Stack %s: failed to generate %s: %v", name, field.EnvVar, err)
|
||||
continue
|
||||
}
|
||||
appCfg.Env[field.EnvVar] = value
|
||||
if field.LockedAfterDeploy {
|
||||
appCfg.LockedFields = append(appCfg.LockedFields, field.EnvVar)
|
||||
}
|
||||
injected = append(injected, field.EnvVar)
|
||||
|
||||
case "domain":
|
||||
appCfg.Env[field.EnvVar] = m.cfg.Customer.Domain
|
||||
if field.LockedAfterDeploy && !containsStr(appCfg.LockedFields, field.EnvVar) {
|
||||
appCfg.LockedFields = append(appCfg.LockedFields, field.EnvVar)
|
||||
}
|
||||
injected = append(injected, field.EnvVar)
|
||||
|
||||
default:
|
||||
m.logger.Printf("[WARN] Stack %s: new field %s (type=%s) requires manual configuration", name, field.EnvVar, field.Type)
|
||||
}
|
||||
}
|
||||
|
||||
if len(injected) > 0 {
|
||||
if err := SaveAppConfig(stackDir, appCfg); err != nil {
|
||||
m.logger.Printf("[ERROR] Stack %s: failed to save app.yaml after injection: %v", name, err)
|
||||
continue
|
||||
}
|
||||
m.logger.Printf("[SYNC] Stack %s: injected missing fields: %s", name, strings.Join(injected, ", "))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func containsStr(slice []string, s string) bool {
|
||||
for _, v := range slice {
|
||||
if v == s {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func randomAlphanumeric(length int) (string, error) {
|
||||
result := make([]byte, length)
|
||||
for i := range result {
|
||||
|
||||
Reference in New Issue
Block a user