config.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. package config
  2. import (
  3. "fmt"
  4. "os"
  5. "strings" // Added for PreviewTLD validation
  6. "gopkg.in/yaml.v3"
  7. )
  8. // Config holds application configuration
  9. type Config struct {
  10. Server *Server `yaml:"server"`
  11. Database *Database `yaml:"database"`
  12. Auth *Auth `yaml:"auth"`
  13. Providers map[string]map[string]string `yaml:"providers"`
  14. Debug bool `yaml:"debug"`
  15. LocalPreview bool `yaml:"local_preview"` // For development/testing only - use false for production
  16. PreviewTLD string `yaml:"preview_tld"`
  17. BuildkitHost string `yaml:"buildkit_host"`
  18. ReistryUrl string `yaml:"registry_url"` // URL of the Docker registry
  19. }
  20. func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
  21. type plain Config
  22. err := unmarshal((*plain)(c))
  23. if err != nil {
  24. return err
  25. }
  26. // Set default preview TLD if not specified
  27. if c.PreviewTLD == "" {
  28. c.PreviewTLD = "home.local"
  29. }
  30. // Set default Buildkit host if not specified
  31. if c.BuildkitHost == "" {
  32. c.BuildkitHost = "unix:///run/buildkit/buildkitd.sock"
  33. }
  34. return nil
  35. }
  36. // Validate performs a comprehensive validation of the configuration.
  37. func (c *Config) Validate() error {
  38. if c.Server == nil {
  39. return fmt.Errorf("server configuration block is required")
  40. }
  41. if err := c.Server.Validate(); err != nil {
  42. return fmt.Errorf("server config validation failed: %w", err)
  43. }
  44. if c.Database == nil {
  45. return fmt.Errorf("database configuration block is required")
  46. }
  47. if err := c.Database.Validate(); err != nil {
  48. return fmt.Errorf("database config validation failed: %w", err)
  49. }
  50. if c.Auth == nil {
  51. return fmt.Errorf("auth configuration block is required")
  52. }
  53. if err := c.Auth.Validate(); err != nil {
  54. return fmt.Errorf("auth config validation failed: %w", err)
  55. }
  56. if len(c.Providers) == 0 { // Corrected: Removed redundant nil check
  57. return fmt.Errorf("at least one provider configuration is required in 'providers' block")
  58. }
  59. for providerName, providerConfig := range c.Providers {
  60. if len(providerConfig) == 0 {
  61. return fmt.Errorf("provider '%s' configuration is empty", providerName)
  62. }
  63. }
  64. return nil
  65. }
  66. // Server holds server configuration
  67. type Server struct {
  68. Host string `yaml:"host"`
  69. Port int `yaml:"port"`
  70. Tls *TlsConfig `yaml:"tls"`
  71. }
  72. func (s *Server) UnmarshalYAML(unmarshal func(interface{}) error) error {
  73. type plain Server
  74. err := unmarshal((*plain)(s))
  75. if err != nil {
  76. return err
  77. }
  78. if s.Host == "" {
  79. s.Host = "0.0.0.0" // Default to all interfaces
  80. }
  81. if s.Port == 0 {
  82. s.Port = 443 // Default port
  83. }
  84. return nil
  85. }
  86. // Validate performs validation for Server configuration.
  87. func (s *Server) Validate() error {
  88. if strings.TrimSpace(s.Host) == "" {
  89. return fmt.Errorf("server host is required and cannot be empty")
  90. }
  91. if s.Port <= 0 || s.Port > 65535 {
  92. return fmt.Errorf("server port must be between 1 and 65535, got %d", s.Port)
  93. }
  94. if s.Tls == nil {
  95. return fmt.Errorf("TLS configuration block is required")
  96. }
  97. if err := s.Tls.Validate(); err != nil {
  98. return fmt.Errorf("TLS config validation failed: %w", err)
  99. }
  100. return nil
  101. }
  102. // TlsConfig holds TLS configuration
  103. type TlsConfig struct {
  104. Enabled bool `yaml:"enabled"`
  105. CertFile string `yaml:"cert_file"`
  106. KeyFile string `yaml:"key_file"`
  107. }
  108. func (t *TlsConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
  109. type plain TlsConfig
  110. err := unmarshal((*plain)(t))
  111. if err != nil {
  112. return err
  113. }
  114. return nil
  115. }
  116. // Validate performs validation for TlsConfig.
  117. func (t *TlsConfig) Validate() error {
  118. if t.Enabled {
  119. if strings.TrimSpace(t.CertFile) == "" {
  120. return fmt.Errorf("TLS cert_file is required and cannot be empty when TLS is enabled")
  121. }
  122. if strings.TrimSpace(t.KeyFile) == "" {
  123. return fmt.Errorf("TLS key_file is required and cannot be empty when TLS is enabled")
  124. }
  125. }
  126. return nil
  127. }
  128. // Database holds database configuration
  129. type Database struct {
  130. DSN string `yaml:"dsn"`
  131. }
  132. func (d *Database) UnmarshalYAML(unmarshal func(interface{}) error) error {
  133. type plain Database
  134. err := unmarshal((*plain)(d))
  135. if err != nil {
  136. return err
  137. }
  138. return nil
  139. }
  140. // Validate performs validation for Database configuration.
  141. func (d *Database) Validate() error {
  142. if strings.TrimSpace(d.DSN) == "" {
  143. return fmt.Errorf("database DSN is required and cannot be empty")
  144. }
  145. if !strings.HasPrefix(d.DSN, "file:") && d.DSN != ":memory:" && !strings.Contains(d.DSN, ".db") {
  146. // This is a basic check, might need refinement based on actual DSN formats used
  147. fmt.Printf("Warning: DSN '%s' might not be a typical SQLite DSN\n", d.DSN)
  148. }
  149. return nil
  150. }
  151. // Sql holds SQL database configuration
  152. type Sqlite struct {
  153. File string `yaml:"file"`
  154. }
  155. func (c *Sqlite) UnmarshalYAML(unmarshal func(interface{}) error) error {
  156. type plain Sqlite
  157. err := unmarshal((*plain)(c))
  158. if err != nil {
  159. return err
  160. }
  161. if c.File == "" {
  162. return fmt.Errorf("SQLite file is required")
  163. }
  164. return nil
  165. }
  166. // Auth holds authentication configuration
  167. type Auth struct {
  168. PrivateKey string `yaml:"private_key"`
  169. TokenDuration int `yaml:"token_duration"`
  170. CleanupInterval int `yaml:"cleanup_interval"`
  171. }
  172. func (a *Auth) UnmarshalYAML(unmarshal func(interface{}) error) error {
  173. type plain Auth
  174. err := unmarshal((*plain)(a))
  175. if err != nil {
  176. return err
  177. }
  178. if a.TokenDuration == 0 {
  179. a.TokenDuration = 3600 // Default to 1 hour
  180. }
  181. if a.CleanupInterval == 0 {
  182. a.CleanupInterval = 3600 // Default to 1 hour
  183. }
  184. return nil
  185. }
  186. // Validate performs validation for Auth configuration.
  187. func (a *Auth) Validate() error {
  188. if strings.TrimSpace(a.PrivateKey) == "" {
  189. return fmt.Errorf("auth private_key is required and cannot be empty")
  190. }
  191. if a.TokenDuration <= 0 {
  192. return fmt.Errorf("auth token_duration must be a positive integer, got %d", a.TokenDuration)
  193. }
  194. if a.CleanupInterval <= 0 {
  195. return fmt.Errorf("auth cleanup_interval must be a positive integer, got %d", a.CleanupInterval)
  196. }
  197. return nil
  198. }
  199. func Load(configPath string) (*Config, error) {
  200. cnf := &Config{}
  201. b, err := os.ReadFile(configPath)
  202. if err != nil {
  203. return nil, err
  204. }
  205. err = yaml.Unmarshal(b, cnf)
  206. if err != nil {
  207. return nil, err
  208. }
  209. // Validate the loaded configuration
  210. if err := cnf.Validate(); err != nil {
  211. return nil, fmt.Errorf("configuration validation failed: %w", err)
  212. }
  213. return cnf, nil
  214. }