Confine cleanup and use held session locks

This commit is contained in:
2026-08-10 18:14:09 +00:00
parent 18ddf00d3d
commit 363313d99c
25 changed files with 919 additions and 72 deletions

View File

@@ -11,6 +11,7 @@ import (
"gitea.maximumdirect.net/eric/narratio/internal/artifacts"
"gitea.maximumdirect.net/eric/narratio/internal/config"
"gitea.maximumdirect.net/eric/narratio/internal/fileops"
)
// Clean removes local workspace/spool state while preserving durable cache
@@ -135,7 +136,7 @@ func reportCleanScopedDir(out io.Writer, root, target, policy string, dryRun boo
fmt.Fprintf(out, "Missing: %s\n", dir.TargetAbs)
return nil
}
if err := os.RemoveAll(dir.TargetAbs); err != nil {
if err := fileops.RemoveAllUnderRoot(dir.RootAbs, dir.TargetAbs); err != nil {
return fmt.Errorf("cleanup policy %s: remove %q: %w", policy, dir.TargetAbs, err)
}
fmt.Fprintf(out, "Deleted: %s\n", dir.TargetAbs)
@@ -160,7 +161,7 @@ func reportCleanRootChildren(out io.Writer, root, policy string, dryRun bool) er
fmt.Fprintf(out, "Would delete: %s\n", entry)
continue
}
if err := os.RemoveAll(entry); err != nil {
if err := fileops.RemoveAllUnderRoot(rootAbs, entry); err != nil {
return fmt.Errorf("cleanup policy %s: remove %q: %w", policy, entry, err)
}
fmt.Fprintf(out, "Deleted: %s\n", entry)
@@ -265,7 +266,7 @@ func reportCleanScopedFile(out io.Writer, root, target, policy string, dryRun bo
fmt.Fprintf(out, "Missing cache file: %s\n", file.TargetAbs)
return false, nil
}
if err := os.Remove(file.TargetAbs); err != nil {
if err := fileops.RemoveAllUnderRoot(file.RootAbs, file.TargetAbs); err != nil {
return false, fmt.Errorf("cleanup policy %s: remove %q: %w", policy, file.TargetAbs, err)
}
fmt.Fprintf(out, "Deleted cache file: %s\n", file.TargetAbs)

View File

@@ -3,12 +3,12 @@ package app
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"gitea.maximumdirect.net/eric/narratio/internal/artifacts"
"gitea.maximumdirect.net/eric/narratio/internal/config"
"gitea.maximumdirect.net/eric/narratio/internal/fileops"
"gitea.maximumdirect.net/eric/narratio/internal/manifest"
)
@@ -167,7 +167,7 @@ func removeRunScopedDir(root, target, policy string) error {
if !dir.Exists {
return nil
}
if err := os.RemoveAll(dir.TargetAbs); err != nil {
if err := fileops.RemoveAllUnderRoot(dir.RootAbs, dir.TargetAbs); err != nil {
return fmt.Errorf("cleanup policy %s: remove %q: %w", policy, dir.TargetAbs, err)
}
return nil

View File

@@ -22,7 +22,7 @@ var buildRestorePlanFn = buildRestorePlan
var executeRestorePlanFn = executeRestorePlan
// Restore validates restore CLI/config inputs and storage preflight for future restore phases.
func Restore(ctx context.Context, args []string, out io.Writer) error {
func Restore(ctx context.Context, args []string, out io.Writer) (resultErr error) {
positionalSessionID, args := pullLeadingSessionID(args)
fs := flag.NewFlagSet("restore", flag.ContinueOnError)
fs.SetOutput(out)
@@ -96,12 +96,18 @@ func Restore(ctx context.Context, args []string, out io.Writer) error {
if _, err := artifactStore.EnsureLayoutFor(cfg.Session.Campaign, cfg.Session.SessionID); err != nil {
return fmt.Errorf("restore: prepare workdir: %w", err)
}
lock, err := artifactStore.AcquireSessionLockFor(cfg.Session.Campaign, cfg.Session.SessionID)
lock, err := artifactStore.AcquireSessionLockForContext(ctx, cfg.Session.Campaign, cfg.Session.SessionID)
if err != nil {
return fmt.Errorf("restore: acquire session lock: %w", err)
}
defer func() {
_ = artifactStore.ReleaseSessionLock(lock)
if releaseErr := artifactStore.ReleaseSessionLock(lock); releaseErr != nil {
if resultErr == nil {
resultErr = fmt.Errorf("restore: release session lock: %w", releaseErr)
} else {
resultErr = errors.Join(resultErr, fmt.Errorf("restore: release session lock: %w", releaseErr))
}
}
}()
if plan.ConflictCount > 0 && !force {

View File

@@ -2,6 +2,7 @@ package app
import (
"context"
"errors"
"fmt"
"log/slog"
"os"
@@ -39,7 +40,7 @@ type RunSummary struct {
var executeStagesFn = executeStages
func executeStages(ctx context.Context, cfg *config.Config, stages []stage.Stage, opts RunOptions) (*RunSummary, error) {
func executeStages(ctx context.Context, cfg *config.Config, stages []stage.Stage, opts RunOptions) (summary *RunSummary, resultErr error) {
env := opts.Env
if env == nil {
env = &Env{}
@@ -111,12 +112,18 @@ func executeStages(ctx context.Context, cfg *config.Config, stages []stage.Stage
return nil, fmt.Errorf("prepare workdir: %w", err)
}
lock, err := artifactStore.AcquireSessionLockFor(cfg.Session.Campaign, cfg.Session.SessionID)
lock, err := artifactStore.AcquireSessionLockForContext(ctx, cfg.Session.Campaign, cfg.Session.SessionID)
if err != nil {
return nil, fmt.Errorf("acquire session lock: %w", err)
}
defer func() {
_ = artifactStore.ReleaseSessionLock(lock)
if releaseErr := artifactStore.ReleaseSessionLock(lock); releaseErr != nil {
if resultErr == nil {
resultErr = fmt.Errorf("release session lock: %w", releaseErr)
} else {
resultErr = errors.Join(resultErr, fmt.Errorf("release session lock: %w", releaseErr))
}
}
}()
manifestPath := paths.ManifestPath