package config import ( "encoding/json" "fmt" "log/slog" "os" "github.com/robfig/cron/v3" ) type ( Configuration struct { GlobalContainerConfiguration ContainerConfiguration `json:"containers"` DaemonConfiguration DaemonConfiguration `json:"daemon"` } DaemonConfiguration struct { PullInterval uint `json:"pull_interval"` } ContainerConfiguration struct { Enabled bool `json:"enabled"` Schedule string `json:"schedule"` } ) func Load(configPath string) (Configuration, error) { file, err := os.OpenFile(configPath, os.O_RDONLY, 0) if err != nil { slog.Warn("no configuration found, loading the default one", "thread", "main", "path", configPath) return Default(), nil } defer file.Close() var c map[string]any d := json.NewDecoder(file) if err := d.Decode(&c); err != nil { return Configuration{}, fmt.Errorf("unable to parse the configuration file: %s", err) } return parseConfiguration(c) } func Default() Configuration { return Configuration{ GlobalContainerConfiguration: ContainerConfiguration{ Enabled: false, Schedule: "* * * * *", }, DaemonConfiguration: DaemonConfiguration{ PullInterval: 2, }, } } func parseConfiguration(currentConfig map[string]any) (Configuration, error) { c := Default() if defaultContainerConfiguration, ok := currentConfig["containers"]; ok { if defaultContainerData, ok := defaultContainerConfiguration.(map[string]any); ok { if schedule, ok := defaultContainerData["schedule"]; ok { if data, ok := schedule.(string); ok { c.GlobalContainerConfiguration.Schedule = data } else { return Configuration{}, fmt.Errorf("configuration parsing: invalid containers.schedule value, expected a string") } } if enabled, ok := defaultContainerData["enabled"]; ok { if data, ok := enabled.(bool); ok { c.GlobalContainerConfiguration.Enabled = data } else { return Configuration{}, fmt.Errorf("configuration parsing: invalid containers.enabled value, expected a bool") } } } else { return Configuration{}, fmt.Errorf("configuration parsing: invalid containers section, expected an object") } } if daemonConfiguration, ok := currentConfig["daemon"]; ok { if daemonData, ok := daemonConfiguration.(map[string]any); ok { if pullInterval, ok := daemonData["pull_interval"]; ok { if data, ok := pullInterval.(uint); ok { c.DaemonConfiguration.PullInterval = data } } else { return Configuration{}, fmt.Errorf("configuration parsing: invalid daemon.pull_interval value, expected an unsigned integer") } } else { return Configuration{}, fmt.Errorf("configuration parsing: invalid daemon section, expected an object") } } return c, nil } func (c Configuration) Validate() error { _, err := cron.ParseStandard(c.GlobalContainerConfiguration.Schedule) if err != nil { return fmt.Errorf("failed to validate: invalid schedule: %s", err) } return nil }