1// Package cli implements the CLI adapter for Gordon.
2package cli
3
4import (
5 "context"
6 "fmt"
7 "path/filepath"
8 "strings"
9
10 "github.com/bnema/zerowrap"
11 "github.com/spf13/viper"
12
13 "github.com/bnema/gordon/internal/adapters/out/domainsecrets"
14 "github.com/bnema/gordon/internal/app"
15 "github.com/bnema/gordon/internal/boundaries/in"
16 "github.com/bnema/gordon/internal/boundaries/out"
17 "github.com/bnema/gordon/internal/domain"
18 "github.com/bnema/gordon/internal/usecase/config"
19 secretsSvc "github.com/bnema/gordon/internal/usecase/secrets"
20)
21
22// LocalServices provides direct access to local services for CLI operations.
23type LocalServices struct {
24 configSvc in.ConfigService
25 secretSvc in.SecretService
26 dataDir string
27}
28
29// GetConfigService returns the config service.
30func (l *LocalServices) GetConfigService() in.ConfigService {
31 return l.configSvc
32}
33
34// GetSecretService returns the secret service.
35func (l *LocalServices) GetSecretService() in.SecretService {
36 return l.secretSvc
37}
38
39// GetDataDir returns the data directory.
40func (l *LocalServices) GetDataDir() string {
41 return l.dataDir
42}
43
44// GetLocalServices creates local services for CLI operations.
45// It loads the config and initializes services without starting the server.
46func GetLocalServices(configPath string) (*LocalServices, error) {
47 // Set up viper with defaults
48 v := viper.New()
49 v.SetDefault("server.port", 80)
50 v.SetDefault("server.registry_port", 5000)
51 v.SetDefault("server.data_dir", app.DefaultDataDir())
52
53 // Configure viper with config file paths
54 app.ConfigureViper(v, configPath)
55
56 // Read config file
57 if err := v.ReadInConfig(); err != nil {
58 if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
59 return nil, fmt.Errorf("failed to read config file: %w", err)
60 }
61 // Config file not found is OK for some operations
62 }
63
64 // Create a minimal logger for CLI operations
65 log := zerowrap.New(zerowrap.Config{
66 Level: "warn",
67 Format: "console",
68 })
69
70 // Create config service (without event bus for CLI operations)
71 configSvc := config.NewService(v, nil)
72 ctx := context.Background()
73 if err := configSvc.Load(ctx); err != nil {
74 return nil, fmt.Errorf("failed to load configuration: %w", err)
75 }
76
77 // Determine data directory and env directory
78 dataDir := v.GetString("server.data_dir")
79 if dataDir == "" {
80 dataDir = app.DefaultDataDir()
81 }
82
83 envDir := v.GetString("env.dir")
84 if envDir == "" {
85 envDir = filepath.Join(dataDir, "env")
86 }
87
88 // Create domain secret store using configured backend (pass or file-based)
89 domainSecretStore, err := createLocalDomainSecretStore(v, envDir, log)
90 if err != nil {
91 return nil, fmt.Errorf("failed to create domain secret store: %w", err)
92 }
93 secretSvc := secretsSvc.NewService(domainSecretStore, log, nil)
94
95 return &LocalServices{
96 configSvc: configSvc,
97 secretSvc: secretSvc,
98 dataDir: dataDir,
99 }, nil
100}
101
102func createLocalDomainSecretStore(v *viper.Viper, envDir string, log zerowrap.Logger) (out.DomainSecretStore, error) {
103 backend := resolveLocalSecretsBackend(v)
104 switch backend {
105 case domain.SecretsBackendPass:
106 return domainsecrets.NewPassStore(log)
107 case domain.SecretsBackendSops:
108 return nil, fmt.Errorf("sops backend not yet supported for domain secrets")
109 default:
110 return domainsecrets.NewFileStore(envDir, log)
111 }
112}
113
114func resolveLocalSecretsBackend(v *viper.Viper) domain.SecretsBackend {
115 backend := strings.TrimSpace(v.GetString("auth.secrets_backend"))
116 if backend == "" {
117 // Legacy key support for older configs.
118 backend = strings.TrimSpace(v.GetString("secrets.backend"))
119 }
120
121 switch backend {
122 case "pass":
123 return domain.SecretsBackendPass
124 case "sops":
125 return domain.SecretsBackendSops
126 case "unsafe", "":
127 return domain.SecretsBackendUnsafe
128 default:
129 log := zerowrap.Default()
130 log.Warn().Str("backend", backend).Msg("unrecognized secrets backend, falling back to unsafe")
131 return domain.SecretsBackendUnsafe
132 }
133}