129 lines
3.8 KiB
Go
129 lines
3.8 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
"gitea.maximumdirect.net/eric/narratio/internal/artifacts"
|
|
"gitea.maximumdirect.net/eric/narratio/internal/config"
|
|
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
|
|
)
|
|
|
|
// Resume continues execution from the first non-succeeded stage in the manifest.
|
|
func Resume(ctx context.Context, args []string, out io.Writer) error {
|
|
positionalSessionID, args := pullLeadingSessionID(args)
|
|
fs := flag.NewFlagSet("resume", flag.ContinueOnError)
|
|
fs.SetOutput(io.Discard)
|
|
|
|
var pipelinePath string
|
|
var campaignPath string
|
|
var campaignFilePath string
|
|
var sessionPath string
|
|
var sessionID string
|
|
var previousSessionID string
|
|
var force bool
|
|
var selectedArtifacts artifactSelectionFlag
|
|
fs.StringVar(&pipelinePath, "config", "", "path to pipeline.yml (optional; defaults searched)")
|
|
fs.StringVar(&campaignPath, "campaign", "", "campaign ID")
|
|
fs.StringVar(&campaignFilePath, "campaign-file", "", "path to campaign.yml")
|
|
fs.StringVar(&sessionPath, "session", "", "path to session.yml")
|
|
fs.StringVar(&previousSessionID, "previous-session-id", "", "expected previous session identifier")
|
|
fs.BoolVar(&force, "force", false, "force stage execution")
|
|
fs.Var(&selectedArtifacts, "artifacts", "configured artifact names to execute and publish (comma-separated or repeatable)")
|
|
|
|
if err := fs.Parse(args); err != nil {
|
|
return fmt.Errorf("resume: invalid flags: %w", err)
|
|
}
|
|
if positionalSessionID == "" {
|
|
if err := applyParsedSessionIDArg("resume", fs, &sessionID); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if fs.NArg() != 0 {
|
|
return fmt.Errorf("resume: unexpected positional arguments")
|
|
}
|
|
if err := applyPositionalSessionID("resume", positionalSessionID, &sessionID); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if strings.TrimSpace(sessionID) == "" {
|
|
return fmt.Errorf("resume: session_id is required")
|
|
}
|
|
cfg, err := loadCommandConfig(ctx, pipelinePath, campaignPath, campaignFilePath, sessionPath, config.SessionLoadOptions{
|
|
SessionID: sessionID,
|
|
PreviousSessionID: previousSessionID,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("resume: %w", err)
|
|
}
|
|
if err := config.Validate(cfg); err != nil {
|
|
return fmt.Errorf("resume: %w", err)
|
|
}
|
|
normalizedArtifacts, err := selectedArtifacts.Normalize()
|
|
if err != nil {
|
|
return fmt.Errorf("resume: invalid --artifacts: %w", err)
|
|
}
|
|
if err := validateSelectedArtifacts(cfg, normalizedArtifacts); err != nil {
|
|
return fmt.Errorf("resume: %w", err)
|
|
}
|
|
|
|
full := BuildFullPlan()
|
|
selected := full
|
|
if !force {
|
|
m, err := loadManifestIfPresent(ctx, cfg)
|
|
if err != nil {
|
|
return fmt.Errorf("resume: %w", err)
|
|
}
|
|
if m != nil {
|
|
start := firstNonSucceededIndex(full, m)
|
|
if start >= len(full) {
|
|
_, err := fmt.Fprintf(out, "narratio resume: session %s has no remaining stages\n", cfg.Session.SessionID)
|
|
return err
|
|
}
|
|
selected = full[start:]
|
|
}
|
|
}
|
|
|
|
summary, err := executeStagesFn(ctx, cfg, selected, RunOptions{
|
|
Force: force,
|
|
SelectedArtifacts: normalizedArtifacts,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("resume: %w", err)
|
|
}
|
|
|
|
_, err = fmt.Fprintf(
|
|
out,
|
|
"narratio resume: session %s; executed=%d skipped=%d; manifest=%s\n",
|
|
summary.SessionID,
|
|
len(summary.Executed),
|
|
len(summary.Skipped),
|
|
summary.ManifestPath,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func loadManifestIfPresent(ctx context.Context, cfg *config.Config) (*manifest.Manifest, error) {
|
|
path := artifacts.SessionManifestPathForCampaign(
|
|
cfg.Pipeline.Workspace.Root,
|
|
cfg.Session.Campaign,
|
|
cfg.Session.SessionID,
|
|
)
|
|
exists, err := fileExists(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("check manifest %q: %w", path, err)
|
|
}
|
|
if !exists {
|
|
return nil, nil
|
|
}
|
|
store := &manifest.LocalStore{}
|
|
m, err := store.Load(ctx, path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("load manifest %q: %w", path, err)
|
|
}
|
|
return m, nil
|
|
}
|