aboutsummaryrefslogtreecommitdiff
path: root/internal/admin/yaml_util.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/admin/yaml_util.go')
-rw-r--r--internal/admin/yaml_util.go36
1 files changed, 23 insertions, 13 deletions
diff --git a/internal/admin/yaml_util.go b/internal/admin/yaml_util.go
index 44712a3..8cd3252 100644
--- a/internal/admin/yaml_util.go
+++ b/internal/admin/yaml_util.go
@@ -194,11 +194,14 @@ func moveEntityFile(dataDir, subdir, id, targetDir string) (string, string, []by
if _, err := os.Stat(newPath); err == nil {
return "", "", nil, fmt.Errorf("entity already exists in target directory")
}
- if err := os.WriteFile(newPath, data, 0644); err != nil {
- return "", "", nil, fmt.Errorf("write: %w", err)
+ // Move in one journaled transaction: a failed write or source removal
+ // must not leave the entity in two directories.
+ txn := persistence.NewTransaction(filepath.Join(dataDir, ".journal"))
+ if err := txn.Move(oldPath, newPath); err != nil {
+ return "", "", nil, fmt.Errorf("move: %w", err)
}
- if err := os.Remove(oldPath); err != nil {
- return "", "", nil, fmt.Errorf("remove old: %w", err)
+ if err := txn.Commit(); err != nil {
+ return "", "", nil, fmt.Errorf("move: %w", err)
}
return oldPath, newPath, data, nil
}
@@ -253,11 +256,14 @@ func renameEntityFile(dataDir, subdir, oldID, newID string) (string, string, []b
if err != nil {
return "", "", nil, fmt.Errorf("read: %w", err)
}
- if err := os.WriteFile(newPath, data, 0644); err != nil {
- return "", "", nil, fmt.Errorf("write: %w", err)
+ // Rename in one journaled transaction: a failed write or source removal
+ // must not leave the entity in two directories.
+ txn := persistence.NewTransaction(filepath.Join(dataDir, ".journal"))
+ if err := txn.Move(oldPath, newPath); err != nil {
+ return "", "", nil, fmt.Errorf("rename: %w", err)
}
- if err := os.Remove(oldPath); err != nil {
- return "", "", nil, fmt.Errorf("remove old: %w", err)
+ if err := txn.Commit(); err != nil {
+ return "", "", nil, fmt.Errorf("rename: %w", err)
}
return oldPath, newPath, data, nil
}
@@ -308,6 +314,7 @@ func deleteEntityDir(dataDir, subdir, dir string) ([]ChangeDesc, error) {
}
var changes []ChangeDesc
+ txn := persistence.NewTransaction(filepath.Join(dataDir, ".journal"))
for _, e := range entries {
if e.IsDir() || filepath.Ext(e.Name()) != ".yaml" {
continue
@@ -318,11 +325,8 @@ func deleteEntityDir(dataDir, subdir, dir string) ([]ChangeDesc, error) {
if err != nil {
return nil, fmt.Errorf("read %s: %w", e.Name(), err)
}
- if err := os.WriteFile(newPath, data, 0644); err != nil {
- return nil, fmt.Errorf("write %s: %w", e.Name(), err)
- }
- if err := os.Remove(oldPath); err != nil {
- return nil, fmt.Errorf("remove %s: %w", e.Name(), err)
+ if err := txn.Move(oldPath, newPath); err != nil {
+ return nil, fmt.Errorf("plan move %s: %w", e.Name(), err)
}
changes = append(changes, ChangeDesc{
Description: fmt.Sprintf("move %s %s to root", subdir, e.Name()),
@@ -333,6 +337,12 @@ func deleteEntityDir(dataDir, subdir, dir string) ([]ChangeDesc, error) {
})
}
+ // Move every file in one journaled transaction so a failure cannot leave
+ // the directory half-emptied (some files moved, some not).
+ if err := txn.Commit(); err != nil {
+ return nil, fmt.Errorf("move files: %w", err)
+ }
+
if err := os.Remove(srcDir); err != nil {
return nil, fmt.Errorf("remove directory: %w", err)
}