167 lines
5.5 KiB
Go
167 lines
5.5 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"sort"
|
|
"strings"
|
|
|
|
"gitea.maximumdirect.net/eric/narratio/internal/adapters/storage"
|
|
"gitea.maximumdirect.net/eric/narratio/internal/artifacts"
|
|
"gitea.maximumdirect.net/eric/narratio/internal/config"
|
|
)
|
|
|
|
// Status reports effective local/remote session state.
|
|
func Status(ctx context.Context, args []string, out io.Writer) error {
|
|
fs := flag.NewFlagSet("status", flag.ContinueOnError)
|
|
fs.SetOutput(io.Discard)
|
|
var flags commonConfigFlags
|
|
addCommonConfigFlags(fs, &flags)
|
|
if err := parseSessionAwareFlags("status", fs, args, &flags.sessionID); err != nil {
|
|
return err
|
|
}
|
|
if strings.TrimSpace(flags.sessionID) == "" {
|
|
return fmt.Errorf("status: session_id is required")
|
|
}
|
|
cfg, err := loadCommandConfig(ctx, flags.pipelinePath, flags.campaignPath, flags.campaignFilePath, flags.sessionPath, flags.sessionOptions())
|
|
if err != nil {
|
|
return fmt.Errorf("status: %w", err)
|
|
}
|
|
if err := config.Validate(cfg); err != nil {
|
|
return fmt.Errorf("status: %w", err)
|
|
}
|
|
paths := artifacts.NewLocalStore(cfg.Pipeline.Workspace.Root).SessionPathsFor(cfg.Session.Campaign, cfg.Session.SessionID)
|
|
fmt.Fprintf(out, "Session: %s\n", cfg.Session.SessionID)
|
|
fmt.Fprintf(out, "Campaign: %s\n", cfg.Session.Campaign)
|
|
fmt.Fprintf(out, "Workspace: %s\n", paths.Root)
|
|
fmt.Fprintf(out, "Session config: %s\n", sessionSourceSummary(cfg))
|
|
writeStatusStableInputs(out, inspectStableInputs(cfg))
|
|
writeStatusLocalAudio(out, inspectLocalAudioPresence(cfg))
|
|
|
|
if m, err := loadLocalManifest(ctx, paths.ManifestPath); err != nil {
|
|
fmt.Fprintf(out, "Local manifest: error: %v\n", err)
|
|
} else if m == nil {
|
|
fmt.Fprintln(out, "Local manifest: missing")
|
|
} else {
|
|
fmt.Fprintf(out, "Local manifest: %s\n", paths.ManifestPath)
|
|
writeStageStatuses(out, m)
|
|
}
|
|
|
|
store, storeErr := objectStoreIfConfigured(ctx, cfg)
|
|
if storeErr != nil {
|
|
fmt.Fprintf(out, "Remote publish: unavailable: %v\n", storeErr)
|
|
} else if store != nil {
|
|
current := inspectRemoteCurrentState(ctx, cfg, store)
|
|
if current.Err != nil {
|
|
fmt.Fprintf(out, "Remote publish: missing or unavailable: %v\n", current.Err)
|
|
} else {
|
|
fmt.Fprintf(out, "Remote publish: current run %s\n", current.State.RunID)
|
|
fmt.Fprintf(out, "Remote manifest: %s\n", current.State.CurrentManifestKey)
|
|
}
|
|
}
|
|
writeStatusRemoteAudio(ctx, out, cfg, store, storeErr)
|
|
writeStatusPreviousArtifacts(out, inspectPreviousArtifactReadiness(
|
|
ctx,
|
|
cfg,
|
|
store,
|
|
artifacts.CollectPreviousArtifactRequirements(configuredScriptoriumArtifacts(cfg)),
|
|
))
|
|
|
|
lockChecks := inspectEffectiveLocks(ctx, cfg, store)
|
|
locks := lockChecks.Locks
|
|
lockErr := lockChecks.Err
|
|
if catalog, catalogErr := buildHelperArtifactCatalog(cfg); catalogErr != nil {
|
|
fmt.Fprintf(out, "Remote outputs: error: %v\n", catalogErr)
|
|
} else if storeErr == nil {
|
|
catalogLocks := locks
|
|
if lockErr != nil {
|
|
catalogLocks = &effectiveLocks{
|
|
Static: staticPublishLocks(cfg),
|
|
All: staticPublishLocks(cfg),
|
|
}
|
|
}
|
|
publishedRemoteState := map[string]string{}
|
|
if store != nil {
|
|
publishedRemoteState = remotePublishedOutputAvailability(ctx, cfg, store, catalog)
|
|
}
|
|
fmt.Fprintln(out, "Remote outputs:")
|
|
writeArtifactList(out, cfg, catalog, catalogLocks, publishedRemoteState)
|
|
}
|
|
if lockErr != nil {
|
|
fmt.Fprintf(out, "Publish locks: error: %v\n", lockErr)
|
|
} else {
|
|
writeLocks(out, cfg, locks)
|
|
}
|
|
fmt.Fprintln(out, "Next actions:")
|
|
fmt.Fprintf(out, "- narratio session validate %s\n", cfg.Session.SessionID)
|
|
fmt.Fprintf(out, "- narratio session restore %s --dry-run\n", cfg.Session.SessionID)
|
|
return nil
|
|
}
|
|
|
|
func writeStatusStableInputs(out io.Writer, checks []stableInputCheck) {
|
|
if len(checks) == 0 {
|
|
return
|
|
}
|
|
for _, check := range checks {
|
|
if check.Err != nil {
|
|
if strings.TrimSpace(check.Path) != "" {
|
|
fmt.Fprintf(out, "Stable input %s: unavailable: %v\n", check.Name, check.Err)
|
|
} else {
|
|
fmt.Fprintf(out, "Stable input %s: unavailable: %s\n", check.Name, check.Err.Error())
|
|
}
|
|
continue
|
|
}
|
|
fmt.Fprintf(out, "Stable input %s: %s\n", check.Name, check.Path)
|
|
}
|
|
}
|
|
|
|
func writeStatusLocalAudio(out io.Writer, check localAudioCheck) {
|
|
if !check.Checked {
|
|
return
|
|
}
|
|
if check.Err != nil {
|
|
fmt.Fprintf(out, "Local audio: unavailable: %v\n", check.Err)
|
|
return
|
|
}
|
|
fmt.Fprintf(out, "Local audio: %d file(s)\n", len(check.Paths))
|
|
}
|
|
|
|
func writeStatusRemoteAudio(ctx context.Context, out io.Writer, cfg *config.Config, store storage.ObjectStore, storeErr error) {
|
|
if cfg.Session.Inputs.AudioS3 == nil {
|
|
return
|
|
}
|
|
if storeErr != nil {
|
|
fmt.Fprintf(out, "Remote audio: unavailable: %v\n", storeErr)
|
|
return
|
|
}
|
|
check := inspectRemoteAudioPresence(ctx, cfg, store)
|
|
if check.Err != nil {
|
|
fmt.Fprintf(out, "Remote audio: unavailable: %v\n", check.Err)
|
|
return
|
|
}
|
|
fmt.Fprintf(out, "Remote audio: %d .flac object(s)\n", len(check.Keys))
|
|
}
|
|
|
|
func writeStatusPreviousArtifacts(out io.Writer, readiness previousArtifactReadiness) {
|
|
if len(readiness.Requirements) == 0 {
|
|
fmt.Fprintln(out, "Previous-session artifacts: not required")
|
|
return
|
|
}
|
|
if readiness.MissingID {
|
|
fmt.Fprintln(out, "Previous-session artifacts: unavailable: previous_session_id is required by configured previous-session artifacts")
|
|
return
|
|
}
|
|
if readiness.Err != nil {
|
|
fmt.Fprintf(out, "Previous-session artifacts: unavailable: %v\n", readiness.Err)
|
|
return
|
|
}
|
|
names := make([]string, 0, len(readiness.Requirements))
|
|
for _, req := range readiness.Requirements {
|
|
names = append(names, fmt.Sprintf("%s(required=%t)", req.Name, req.Required))
|
|
}
|
|
sort.Strings(names)
|
|
fmt.Fprintf(out, "Previous-session artifacts: ready: %s\n", strings.Join(names, ", "))
|
|
}
|