Files

105 lines
2.5 KiB
Go

package main
import (
"errors"
"fmt"
"os"
"regexp"
"strings"
"text/template"
"time"
"github.com/robfig/cron/v3"
)
const defaultTextTemplate = "Scheduled trigger at {{.LocalTime}}"
// Config is the validated runtime configuration assembled from env vars.
type Config struct {
FireURL string
Token string
Schedules []string
Location *time.Location
Template *template.Template
LogLevel string
}
// Load reads env vars and returns a fully validated Config or an error.
// All required vars must be present and all parsed values must pass validation.
func Load() (*Config, error) {
cfg := &Config{}
cfg.FireURL = strings.TrimSpace(os.Getenv("ROUTINE_FIRE_URL"))
if cfg.FireURL == "" {
return nil, errors.New("missing required env var ROUTINE_FIRE_URL")
}
cfg.Token = strings.TrimSpace(os.Getenv("ROUTINE_FIRE_TOKEN"))
if cfg.Token == "" {
return nil, errors.New("missing required env var ROUTINE_FIRE_TOKEN")
}
rawCron := os.Getenv("CRON_SCHEDULE")
schedules, err := parseSchedules(rawCron)
if err != nil {
return nil, err
}
cfg.Schedules = schedules
tz := strings.TrimSpace(os.Getenv("TZ"))
if tz == "" {
cfg.Location = time.UTC
} else {
loc, err := time.LoadLocation(tz)
if err != nil {
return nil, fmt.Errorf("invalid TZ %q: %w", tz, err)
}
cfg.Location = loc
}
rawTpl := os.Getenv("TEXT_TEMPLATE")
if rawTpl == "" {
rawTpl = defaultTextTemplate
}
tpl, err := template.New("text").Parse(rawTpl)
if err != nil {
return nil, fmt.Errorf("invalid TEXT_TEMPLATE: %w", err)
}
cfg.Template = tpl
cfg.LogLevel = strings.TrimSpace(os.Getenv("LOG_LEVEL"))
if cfg.LogLevel == "" {
cfg.LogLevel = "info"
}
return cfg, nil
}
var scheduleSeparators = regexp.MustCompile(`[;\n]+`)
// parseSchedules splits CRON_SCHEDULE on `;` or newlines, trims, drops empties,
// and validates each expression against the standard 5-field parser.
func parseSchedules(raw string) ([]string, error) {
if strings.TrimSpace(raw) == "" {
return nil, errors.New("missing required env var CRON_SCHEDULE")
}
parts := scheduleSeparators.Split(raw, -1)
parser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
out := make([]string, 0, len(parts))
for _, p := range parts {
s := strings.TrimSpace(p)
if s == "" {
continue
}
if _, err := parser.Parse(s); err != nil {
return nil, fmt.Errorf("invalid cron %q: %w", s, err)
}
out = append(out, s)
}
if len(out) == 0 {
return nil, errors.New("CRON_SCHEDULE has no non-empty expressions")
}
return out, nil
}