2026-05-02 10:37:38 -05:00
|
|
|
package app
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2026-05-02 10:43:45 -05:00
|
|
|
"flag"
|
|
|
|
|
"fmt"
|
2026-05-02 10:37:38 -05:00
|
|
|
"io"
|
2026-05-16 07:56:22 -05:00
|
|
|
"log/slog"
|
|
|
|
|
"os"
|
2026-05-02 10:43:45 -05:00
|
|
|
|
2026-05-02 10:56:42 -05:00
|
|
|
"gitea.maximumdirect.net/eric/narratio/internal/artifacts"
|
2026-05-02 10:43:45 -05:00
|
|
|
"gitea.maximumdirect.net/eric/narratio/internal/config"
|
2026-05-16 07:56:22 -05:00
|
|
|
"gitea.maximumdirect.net/eric/narratio/internal/logging"
|
2026-05-02 11:38:42 -05:00
|
|
|
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
|
2026-05-02 10:37:38 -05:00
|
|
|
)
|
|
|
|
|
|
2026-05-02 11:05:28 -05:00
|
|
|
// Plan validates configuration, prepares the local workdir, and prints stage order.
|
2026-05-02 11:38:42 -05:00
|
|
|
func Plan(ctx context.Context, args []string, out io.Writer) error {
|
2026-05-02 10:43:45 -05:00
|
|
|
fs := flag.NewFlagSet("plan", flag.ContinueOnError)
|
|
|
|
|
fs.SetOutput(io.Discard)
|
|
|
|
|
|
2026-05-23 13:47:39 +00:00
|
|
|
var flags commonConfigFlags
|
2026-05-02 11:05:28 -05:00
|
|
|
var force bool
|
2026-05-23 13:47:39 +00:00
|
|
|
addCommonConfigFlags(fs, &flags)
|
2026-08-10 00:14:46 +00:00
|
|
|
fs.BoolVar(&force, "force", false, "show all stages as scheduled to rerun")
|
2026-05-02 10:43:45 -05:00
|
|
|
|
2026-05-23 13:47:39 +00:00
|
|
|
if err := parseSessionAwareFlags("plan", fs, args, &flags.sessionID); err != nil {
|
|
|
|
|
return err
|
2026-05-02 10:43:45 -05:00
|
|
|
}
|
2026-05-23 13:47:39 +00:00
|
|
|
if flags.sessionID == "" {
|
2026-05-22 22:09:17 -05:00
|
|
|
return fmt.Errorf("plan: session_id is required")
|
2026-05-02 10:43:45 -05:00
|
|
|
}
|
2026-08-10 21:41:00 +00:00
|
|
|
loaded, err := loadCommandConfig(ctx, flags.pipelinePath, flags.campaignPath, flags.campaignFilePath, flags.sessionPath, flags.sessionOptions())
|
2026-05-02 10:43:45 -05:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("plan: %w", err)
|
|
|
|
|
}
|
2026-08-10 21:41:00 +00:00
|
|
|
defer func() { _ = loaded.Close() }()
|
|
|
|
|
cfg := loaded.Config
|
2026-05-02 10:43:45 -05:00
|
|
|
if err := config.Validate(cfg); err != nil {
|
|
|
|
|
return fmt.Errorf("plan: %w", err)
|
|
|
|
|
}
|
2026-05-16 07:56:22 -05:00
|
|
|
if _, err := loadSecretsFromConfig(cfg, logging.NewLogger(os.Stderr, slog.LevelInfo)); err != nil {
|
|
|
|
|
return fmt.Errorf("plan: %w", err)
|
|
|
|
|
}
|
2026-05-02 10:43:45 -05:00
|
|
|
|
2026-05-02 10:56:42 -05:00
|
|
|
store := artifacts.NewLocalStore(cfg.Pipeline.Workspace.Root)
|
2026-05-17 20:57:27 +00:00
|
|
|
paths, err := store.EnsureLayoutFor(cfg.Session.Campaign, cfg.Session.SessionID)
|
2026-05-02 10:56:42 -05:00
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("plan: prepare workdir: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-02 11:05:28 -05:00
|
|
|
stages := BuildFullPlan()
|
2026-05-02 11:38:42 -05:00
|
|
|
var m *manifest.Manifest
|
|
|
|
|
m, err = loadManifestIfPresent(ctx, cfg)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("plan: %w", err)
|
|
|
|
|
}
|
|
|
|
|
decisions := decideStageActions(stages, m, force)
|
|
|
|
|
|
|
|
|
|
runCount := 0
|
|
|
|
|
skipCount := 0
|
2026-05-22 22:09:17 -05:00
|
|
|
if _, err := fmt.Fprintf(out, "narratio session plan: workdir prepared at %s\n", paths.Root); err != nil {
|
2026-05-02 11:05:28 -05:00
|
|
|
return err
|
|
|
|
|
}
|
2026-05-02 11:38:42 -05:00
|
|
|
for _, d := range decisions {
|
|
|
|
|
if d.Action == stageActionRun {
|
|
|
|
|
runCount++
|
|
|
|
|
} else {
|
|
|
|
|
skipCount++
|
|
|
|
|
}
|
|
|
|
|
if _, err := fmt.Fprintf(out, "%s: %s\n", d.Stage.Name(), d.Action); err != nil {
|
2026-05-02 11:05:28 -05:00
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-02 11:38:42 -05:00
|
|
|
if _, err := fmt.Fprintf(out, "totals: run=%d skip=%d\n", runCount, skipCount); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
2026-05-02 11:05:28 -05:00
|
|
|
|
|
|
|
|
return nil
|
2026-05-02 10:37:38 -05:00
|
|
|
}
|