32de5b26a8
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.
119 lines
2.9 KiB
Go
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
|
|
}
|