aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/config/config.go86
1 files changed, 84 insertions, 2 deletions
diff --git a/internal/config/config.go b/internal/config/config.go
index 7542d3e..f97add5 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -4,6 +4,7 @@ import (
"fmt"
"net/url"
"os"
+ "path/filepath"
"strings"
"time"
@@ -12,6 +13,7 @@ import (
// Config represents the root configuration structure
type Config struct {
+ Imports []string `yaml:"imports,omitempty"`
Site SiteConfig `yaml:"site"`
Server ServerConfig `yaml:"server"`
Storage StorageConfig `yaml:"storage"`
@@ -238,6 +240,21 @@ func (d Duration) MarshalYAML() (interface{}, error) {
// Load reads and parses a configuration file
func Load(path string) (*Config, error) {
+ visited := make(map[string]bool)
+ return loadWithImports(path, visited)
+}
+
+func loadWithImports(path string, visited map[string]bool) (*Config, error) {
+ absPath, err := filepath.Abs(path)
+ if err != nil {
+ return nil, fmt.Errorf("failed to resolve path %q: %w", path, err)
+ }
+
+ if visited[absPath] {
+ return nil, fmt.Errorf("circular import detected: %q", path)
+ }
+ visited[absPath] = true
+
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to read config file: %w", err)
@@ -248,10 +265,15 @@ func Load(path string) (*Config, error) {
return nil, fmt.Errorf("failed to parse config file: %w", err)
}
- // Apply defaults
+ if len(cfg.Imports) > 0 {
+ baseDir := filepath.Dir(absPath)
+ if err := cfg.processImports(baseDir, visited); err != nil {
+ return nil, err
+ }
+ }
+
cfg.applyDefaults()
- // Validate configuration
if err := cfg.validate(); err != nil {
return nil, fmt.Errorf("invalid configuration: %w", err)
}
@@ -259,6 +281,66 @@ func Load(path string) (*Config, error) {
return &cfg, nil
}
+func (c *Config) processImports(baseDir string, visited map[string]bool) error {
+ for _, importPath := range c.Imports {
+ fullPath := importPath
+ if !filepath.IsAbs(importPath) {
+ fullPath = filepath.Join(baseDir, importPath)
+ }
+
+ imported, err := loadWithImports(fullPath, visited)
+ if err != nil {
+ return fmt.Errorf("failed to import %q: %w", importPath, err)
+ }
+
+ c.mergeImported(imported)
+ }
+ return nil
+}
+
+func (c *Config) mergeImported(imported *Config) {
+ groupIndex := make(map[string]int)
+ for i, g := range c.Groups {
+ groupIndex[g.Name] = i
+ }
+
+ for _, importedGroup := range imported.Groups {
+ if idx, exists := groupIndex[importedGroup.Name]; exists {
+ c.Groups[idx] = mergeGroups(c.Groups[idx], importedGroup)
+ } else {
+ c.Groups = append(c.Groups, importedGroup)
+ groupIndex[importedGroup.Name] = len(c.Groups) - 1
+ }
+ }
+
+ incidentSet := make(map[string]bool)
+ for _, inc := range c.Incidents {
+ incidentSet[inc.Title] = true
+ }
+ for _, inc := range imported.Incidents {
+ if !incidentSet[inc.Title] {
+ c.Incidents = append(c.Incidents, inc)
+ incidentSet[inc.Title] = true
+ }
+ }
+}
+
+func mergeGroups(local, imported GroupConfig) GroupConfig {
+ monitorIndex := make(map[string]int)
+ for i, m := range local.Monitors {
+ monitorIndex[m.Name] = i
+ }
+
+ for _, importedMon := range imported.Monitors {
+ if _, exists := monitorIndex[importedMon.Name]; !exists {
+ local.Monitors = append(local.Monitors, importedMon)
+ monitorIndex[importedMon.Name] = len(local.Monitors) - 1
+ }
+ }
+
+ return local
+}
+
// applyDefaults sets default values for missing configuration
func (c *Config) applyDefaults() {
if c.Site.Name == "" {