Harden scheduled report runs

This commit is contained in:
2026-05-29 18:20:39 +00:00
parent 5d543b6b4d
commit 108a1618f6
6 changed files with 416 additions and 19 deletions

View File

@@ -2,6 +2,7 @@ package cli
import (
"context"
"encoding/json"
"flag"
"fmt"
"io"
@@ -20,15 +21,16 @@ Usage:
weatherreporter generate three-day [--config PATH] [--units VALUE] [--tz NAME] [--out PATH]
weatherreporter generate weekend [--config PATH] [--units VALUE] [--tz NAME] [--out PATH]
weatherreporter generate storm [--config PATH] [--units VALUE] [--tz NAME] [--out PATH] --start TIME --end TIME
weatherreporter run morning [--config PATH] [--units VALUE] [--tz NAME]
weatherreporter run evening [--config PATH] [--units VALUE] [--tz NAME]
weatherreporter run morning [--config PATH] [--units VALUE] [--tz NAME] [--out-dir PATH]
weatherreporter run evening [--config PATH] [--units VALUE] [--tz NAME] [--out-dir PATH]
Options:
-h, --help Show this help message.
--config PATH Load configuration from PATH instead of /usr/local/etc/weatherreporter/config.yml.
--units VALUE Override weather API units.
--tz NAME Override weather API timezone.
--out PATH Write an extra Markdown report copy for generate daily or tomorrow.
--out PATH Write an extra Markdown report copy for generate commands.
--out-dir PATH Write extra Markdown report copies for run commands.
`
type Runner struct {
@@ -61,7 +63,17 @@ func (r Runner) Run(ctx context.Context, args []string, stdout io.Writer, stderr
if err != nil {
return err
}
return app.RunBatch(ctx, req)
result, err := app.RunBatchDetailed(ctx, req)
if result != nil {
writeRunLogs(stderr, result)
if encodeErr := writeRunSummary(stdout, result); encodeErr != nil {
return encodeErr
}
if result.Failed > 0 {
return app.BatchError{Result: result}
}
}
return err
default:
return fmt.Errorf("unknown command %q", args[0])
}
@@ -72,6 +84,7 @@ type commonOptions struct {
Units string
Timezone string
Output string
OutputDir string
}
type generateOptions struct {
@@ -174,7 +187,7 @@ func (r Runner) resolveRun(args []string) (app.BatchRequest, error) {
if err != nil {
return app.BatchRequest{}, err
}
return app.BatchRequest{Config: cfg, Batch: batch, Now: r.Clock.Now()}, nil
return app.BatchRequest{Config: cfg, Batch: batch, Now: r.Clock.Now(), OutputDir: opts.OutputDir}, nil
}
func resolveRun(args []string) (app.BatchRequest, error) {
@@ -207,6 +220,7 @@ func parseRunFlags(args []string) (commonOptions, error) {
fs.SetOutput(io.Discard)
opts := commonOptions{}
addCommonFlags(fs, &opts, false)
fs.StringVar(&opts.OutputDir, "out-dir", "", "extra Markdown report copy directory")
if err := fs.Parse(args); err != nil {
return commonOptions{}, err
}
@@ -216,6 +230,26 @@ func parseRunFlags(args []string) (commonOptions, error) {
return opts, nil
}
func writeRunSummary(stdout io.Writer, result *app.BatchResult) error {
encoder := json.NewEncoder(stdout)
encoder.SetIndent("", " ")
return encoder.Encode(result)
}
func writeRunLogs(stderr io.Writer, result *app.BatchResult) {
if stderr == nil || result == nil {
return
}
for _, item := range result.Reports {
if item.Status == "failed" {
_, _ = fmt.Fprintf(stderr, "report=%s status=failed error=%q\n", item.ReportID, item.Error)
continue
}
_, _ = fmt.Fprintf(stderr, "report=%s status=succeeded output=%q\n", item.ReportID, item.OutputPath)
}
_, _ = fmt.Fprintf(stderr, "batch=%s total=%d succeeded=%d failed=%d\n", result.Batch, result.Total, result.Succeeded, result.Failed)
}
func addCommonFlags(fs *flag.FlagSet, opts *commonOptions, includeOutput bool) {
fs.StringVar(&opts.ConfigPath, "config", "", "configuration file path")
fs.StringVar(&opts.Units, "units", "", "weather API units")