Files
tiny-forge/internal/config/export.go
alexei.dolgolyov 32de5b26a8 feat(docker-watcher): phase 12 - hardening
Blue-green zero-downtime deploys, promote flow validation.
Dual auth: local (bcrypt + JWT) and OAuth2/OIDC (any provider).
Auth middleware, login page, auth settings UI.
Structured logging (slog JSON), config export to YAML.
Graceful shutdown with deploy draining.
Multi-stage Dockerfile and production docker-compose.yml.
Swap phase order: Volumes & Env before UI Polish.
2026-03-27 23:20:56 +03:00

119 lines
2.9 KiB
Go

package config
import (
"encoding/json"
"fmt"
"github.com/alexei/docker-watcher/internal/store"
"gopkg.in/yaml.v3"
)
// ExportConfig reads the current database state and produces a SeedConfig YAML
// representation. Credential fields (tokens, passwords) are exported as placeholder
// strings since they are encrypted in the database.
func ExportConfig(db *store.Store) ([]byte, error) {
cfg, err := buildSeedConfig(db)
if err != nil {
return nil, fmt.Errorf("build seed config: %w", err)
}
data, err := yaml.Marshal(cfg)
if err != nil {
return nil, fmt.Errorf("marshal yaml: %w", err)
}
return data, nil
}
// buildSeedConfig constructs a SeedConfig from the current database state.
func buildSeedConfig(db *store.Store) (SeedConfig, error) {
settings, err := db.GetSettings()
if err != nil {
return SeedConfig{}, fmt.Errorf("get settings: %w", err)
}
registries, err := db.GetAllRegistries()
if err != nil {
return SeedConfig{}, fmt.Errorf("get registries: %w", err)
}
projects, err := db.GetAllProjects()
if err != nil {
return SeedConfig{}, fmt.Errorf("get projects: %w", err)
}
cfg := SeedConfig{
Global: GlobalConfig{
Domain: settings.Domain,
ServerIP: settings.ServerIP,
Network: settings.Network,
SubdomainPattern: settings.SubdomainPattern,
NotificationURL: settings.NotificationURL,
Npm: NpmConfig{
URL: settings.NpmURL,
Email: settings.NpmEmail,
Password: "CHANGE_ME", // Encrypted value, export placeholder.
},
},
Registries: make(map[string]RegistryDef),
Projects: make(map[string]ProjectDef),
}
for _, reg := range registries {
cfg.Registries[reg.Name] = RegistryDef{
URL: reg.URL,
Type: reg.Type,
Token: "CHANGE_ME", // Encrypted value, export placeholder.
}
}
for _, proj := range projects {
stages, err := db.GetStagesByProjectID(proj.ID)
if err != nil {
return SeedConfig{}, fmt.Errorf("get stages for project %s: %w", proj.Name, err)
}
stageDefs := make(map[string]StageDef)
for _, st := range stages {
stageDefs[st.Name] = StageDef{
TagPattern: st.TagPattern,
AutoDeploy: st.AutoDeploy,
MaxInstances: st.MaxInstances,
Confirm: st.Confirm,
PromoteFrom: st.PromoteFrom,
Subdomain: st.Subdomain,
}
}
envMap := parseJSONMap(proj.Env)
volMap := parseJSONMap(proj.Volumes)
cfg.Projects[proj.Name] = ProjectDef{
Registry: proj.Registry,
Image: proj.Image,
Port: proj.Port,
Healthcheck: proj.Healthcheck,
Env: envMap,
Volumes: volMap,
Stages: stageDefs,
}
}
return cfg, nil
}
// parseJSONMap safely parses a JSON-encoded map string. Returns nil on failure.
func parseJSONMap(jsonStr string) map[string]string {
if jsonStr == "" || jsonStr == "{}" {
return nil
}
var m map[string]string
if err := json.Unmarshal([]byte(jsonStr), &m); err != nil {
return nil
}
if len(m) == 0 {
return nil
}
return m
}