297 lines
8.3 KiB
Go
297 lines
8.3 KiB
Go
// Package config owns application configuration structures, defaults, loading,
|
|
// precedence, and validation.
|
|
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"gitea.maximumdirect.net/eric/weatherreporter/internal/module"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
type MissingSourcePolicy string
|
|
type NotifyFailurePolicy string
|
|
|
|
const (
|
|
MissingSourceError MissingSourcePolicy = "error"
|
|
MissingSourceWarn MissingSourcePolicy = "warn"
|
|
MissingSourceNone MissingSourcePolicy = "none"
|
|
|
|
NotifyFailureError NotifyFailurePolicy = "error"
|
|
)
|
|
|
|
type Config struct {
|
|
WeatherAPI WeatherAPIConfig `yaml:"weather_api"`
|
|
Location LocationConfig `yaml:"location"`
|
|
Secrets SecretsConfig `yaml:"secrets"`
|
|
Notify NotifyConfig `yaml:"notify"`
|
|
MissingSource MissingSourceConfig `yaml:"missing_source"`
|
|
Scriptorium ScriptoriumConfig `yaml:"scriptorium"`
|
|
Workspace WorkspaceConfig `yaml:"workspace"`
|
|
Dayparts []DaypartConfig `yaml:"dayparts"`
|
|
RecentChange RecentChangeConfig `yaml:"recent_change"`
|
|
Reports map[string]ReportConfig `yaml:"reports"`
|
|
}
|
|
|
|
type WeatherAPIConfig struct {
|
|
BaseURL string `yaml:"base_url"`
|
|
Timeout time.Duration `yaml:"timeout"`
|
|
Precision int `yaml:"precision"`
|
|
Units string `yaml:"units"`
|
|
Timezone string `yaml:"timezone"`
|
|
Format string `yaml:"format"`
|
|
}
|
|
|
|
type LocationConfig struct {
|
|
ID string `yaml:"id"`
|
|
Name string `yaml:"name"`
|
|
Region string `yaml:"region"`
|
|
}
|
|
|
|
type SecretsConfig struct {
|
|
Directory string `yaml:"directory"`
|
|
}
|
|
|
|
type NotifyConfig struct {
|
|
Distributor DistributorNotifyConfig `yaml:"distributor"`
|
|
}
|
|
|
|
type DistributorNotifyConfig struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
Endpoint string `yaml:"endpoint"`
|
|
TokenEnv string `yaml:"token_env"`
|
|
Timeout time.Duration `yaml:"timeout"`
|
|
FailurePolicy NotifyFailurePolicy `yaml:"failure_policy"`
|
|
PipelineIDTemplate string `yaml:"pipeline_id_template"`
|
|
BundleIDTemplate string `yaml:"bundle_id_template"`
|
|
IdempotencyKeyTemplate string `yaml:"idempotency_key_template"`
|
|
Batch DistributorBatchNotifyConfig `yaml:"batch"`
|
|
}
|
|
|
|
type DistributorBatchNotifyConfig struct {
|
|
Enabled bool `yaml:"enabled"`
|
|
PipelineIDTemplate string `yaml:"pipeline_id_template"`
|
|
BundleIDTemplate string `yaml:"bundle_id_template"`
|
|
IdempotencyKeyTemplate string `yaml:"idempotency_key_template"`
|
|
}
|
|
|
|
type MissingSourceConfig struct {
|
|
Default MissingSourcePolicy `yaml:"default"`
|
|
Sources map[string]MissingSourcePolicy `yaml:"sources"`
|
|
}
|
|
|
|
type ScriptoriumConfig struct {
|
|
Binary string `yaml:"binary"`
|
|
ConfigPath string `yaml:"config_path"`
|
|
Profile string `yaml:"profile"`
|
|
Timeout time.Duration `yaml:"timeout"`
|
|
ExtraArgs []string `yaml:"extra_args"`
|
|
}
|
|
|
|
type WorkspaceConfig struct {
|
|
Root string `yaml:"root"`
|
|
SnapshotsDir string `yaml:"snapshots_dir"`
|
|
ReportsDir string `yaml:"reports_dir"`
|
|
DataPackagesDir string `yaml:"data_packages_dir"`
|
|
PreflightDir string `yaml:"preflight_dir"`
|
|
NotificationsDir string `yaml:"notifications_dir"`
|
|
}
|
|
|
|
type DaypartConfig struct {
|
|
Name string `yaml:"name"`
|
|
Start string `yaml:"start"`
|
|
End string `yaml:"end"`
|
|
}
|
|
|
|
type RecentChangeConfig struct {
|
|
TemperatureDegrees float64 `yaml:"temperature_degrees"`
|
|
PrecipProbabilityPoints int `yaml:"precip_probability_points"`
|
|
WindGustMilesPerHour int `yaml:"wind_gust_miles_per_hour"`
|
|
PrecipTimingShiftMinutes int `yaml:"precip_timing_shift_minutes"`
|
|
}
|
|
|
|
type ReportConfig struct {
|
|
DeterministicModules []ModuleConfigItem `yaml:"deterministic_modules"`
|
|
Distributor ReportDistributorConfig `yaml:"distributor"`
|
|
deterministicModulesSet bool
|
|
}
|
|
|
|
type ReportDistributorConfig struct {
|
|
PathTemplates []string `yaml:"path_templates"`
|
|
pathTemplatesSet bool
|
|
}
|
|
|
|
type ModuleConfigItem struct {
|
|
ID module.ID `yaml:"id"`
|
|
Options any `yaml:"options,omitempty"`
|
|
}
|
|
|
|
func (c *ReportConfig) UnmarshalYAML(value *yaml.Node) error {
|
|
if value.Kind != yaml.MappingNode {
|
|
return fmt.Errorf("report entry must be a mapping")
|
|
}
|
|
for i := 0; i < len(value.Content); i += 2 {
|
|
key := value.Content[i].Value
|
|
node := value.Content[i+1]
|
|
switch key {
|
|
case "deterministic_modules":
|
|
if err := node.Decode(&c.DeterministicModules); err != nil {
|
|
return err
|
|
}
|
|
c.deterministicModulesSet = true
|
|
case "distributor":
|
|
if err := node.Decode(&c.Distributor); err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
return fmt.Errorf("unknown report entry field %q", key)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *DistributorNotifyConfig) UnmarshalYAML(value *yaml.Node) error {
|
|
if value.Kind != yaml.MappingNode {
|
|
return fmt.Errorf("notify distributor entry must be a mapping")
|
|
}
|
|
for i := 0; i < len(value.Content); i += 2 {
|
|
key := value.Content[i].Value
|
|
node := value.Content[i+1]
|
|
switch key {
|
|
case "enabled":
|
|
if err := node.Decode(&c.Enabled); err != nil {
|
|
return err
|
|
}
|
|
case "endpoint":
|
|
if err := node.Decode(&c.Endpoint); err != nil {
|
|
return err
|
|
}
|
|
case "token_env":
|
|
if err := node.Decode(&c.TokenEnv); err != nil {
|
|
return err
|
|
}
|
|
case "timeout":
|
|
if err := node.Decode(&c.Timeout); err != nil {
|
|
return err
|
|
}
|
|
case "failure_policy":
|
|
if err := node.Decode(&c.FailurePolicy); err != nil {
|
|
return err
|
|
}
|
|
case "pipeline_id_template":
|
|
if err := node.Decode(&c.PipelineIDTemplate); err != nil {
|
|
return err
|
|
}
|
|
case "bundle_id_template":
|
|
if err := node.Decode(&c.BundleIDTemplate); err != nil {
|
|
return err
|
|
}
|
|
case "idempotency_key_template":
|
|
if err := node.Decode(&c.IdempotencyKeyTemplate); err != nil {
|
|
return err
|
|
}
|
|
case "batch":
|
|
if err := node.Decode(&c.Batch); err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
return fmt.Errorf("unknown notify distributor field %q", key)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *DistributorBatchNotifyConfig) UnmarshalYAML(value *yaml.Node) error {
|
|
if value.Kind != yaml.MappingNode {
|
|
return fmt.Errorf("notify distributor batch entry must be a mapping")
|
|
}
|
|
for i := 0; i < len(value.Content); i += 2 {
|
|
key := value.Content[i].Value
|
|
node := value.Content[i+1]
|
|
switch key {
|
|
case "enabled":
|
|
if err := node.Decode(&c.Enabled); err != nil {
|
|
return err
|
|
}
|
|
case "pipeline_id_template":
|
|
if err := node.Decode(&c.PipelineIDTemplate); err != nil {
|
|
return err
|
|
}
|
|
case "bundle_id_template":
|
|
if err := node.Decode(&c.BundleIDTemplate); err != nil {
|
|
return err
|
|
}
|
|
case "idempotency_key_template":
|
|
if err := node.Decode(&c.IdempotencyKeyTemplate); err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
return fmt.Errorf("unknown notify distributor batch field %q", key)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *ReportDistributorConfig) UnmarshalYAML(value *yaml.Node) error {
|
|
if value.Kind != yaml.MappingNode {
|
|
return fmt.Errorf("report distributor entry must be a mapping")
|
|
}
|
|
for i := 0; i < len(value.Content); i += 2 {
|
|
key := value.Content[i].Value
|
|
node := value.Content[i+1]
|
|
switch key {
|
|
case "path_templates":
|
|
if err := node.Decode(&c.PathTemplates); err != nil {
|
|
return err
|
|
}
|
|
c.pathTemplatesSet = true
|
|
default:
|
|
return fmt.Errorf("unknown report distributor field %q", key)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c ReportDistributorConfig) PathTemplatesSet() bool {
|
|
return c.pathTemplatesSet
|
|
}
|
|
|
|
func (m *ModuleConfigItem) UnmarshalYAML(value *yaml.Node) error {
|
|
switch value.Kind {
|
|
case yaml.ScalarNode:
|
|
if value.Value == "" {
|
|
return fmt.Errorf("module id is required")
|
|
}
|
|
m.ID = module.ID(value.Value)
|
|
return nil
|
|
case yaml.MappingNode:
|
|
var sawID bool
|
|
for i := 0; i < len(value.Content); i += 2 {
|
|
key := value.Content[i].Value
|
|
node := value.Content[i+1]
|
|
switch key {
|
|
case "id":
|
|
if err := node.Decode(&m.ID); err != nil {
|
|
return err
|
|
}
|
|
sawID = true
|
|
case "options":
|
|
var options any
|
|
if err := node.Decode(&options); err != nil {
|
|
return err
|
|
}
|
|
m.Options = options
|
|
default:
|
|
return fmt.Errorf("unknown module entry field %q", key)
|
|
}
|
|
}
|
|
if !sawID || m.ID == "" {
|
|
return fmt.Errorf("module id is required")
|
|
}
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("module entry must be a string or mapping")
|
|
}
|
|
}
|