A directory holding anything the mesh did not put there is never removed
Found by asking what the conversion needs, and it is the one failure in this system that cannot be undone. Unassigning a module made its directory an orphan, and an orphan directory was deleted with everything under it — os.RemoveAll — while the report said "removed". A database's files, a mail spool, somebody's uploads. Reproduced before fixing: assign a module, let a service write into its directory, unassign the module, and the file is gone. Now a directory that still holds something is kept and said so, naming how many items are in it. What makes that safe rather than merely cautious is the removal order, which was already right. Everything the mesh puts in a directory is itself a declared resource, and orphans are removed in reverse declaration order — so what the mesh wrote is already gone by the time the directory is reached. Anything still there was put there by something else, which is the definition of data. It is the host's own line applied to the one shape where getting it wrong does not recover: it removes what it made and leaves what it merely configured. An empty directory is what it made; a full one is not, and an empty one is still removed so nothing accumulates. Files are unchanged. A declared file is the mesh's own, and losing a config file is not the failure this is about.
This commit is contained in:
+31
-1
@@ -601,7 +601,37 @@ func applyService(ctx context.Context, sys system.System, r *declaration.Service
|
||||
// installed would be describing an effect it declined to have.
|
||||
func remove(ctx context.Context, sys system.System, a store.Applied, run Runner) (string, string, error) {
|
||||
switch declaration.Type(a.Type) {
|
||||
case declaration.TypeFile, declaration.TypeDirectory:
|
||||
case declaration.TypeDirectory:
|
||||
// **A directory with anything left in it is kept, and that is the rule that protects
|
||||
// data.** Everything the mesh put inside is itself a declared resource, and orphans are
|
||||
// removed in reverse declaration order — so by the time a directory comes to be removed,
|
||||
// what the mesh wrote there is already gone. Anything still present is something nobody
|
||||
// declared: a database's files, a mail spool, somebody's uploads.
|
||||
//
|
||||
// Before this, unassigning a module deleted its data directory and everything under it,
|
||||
// and the report said "removed". Nothing anywhere said what had been in there.
|
||||
//
|
||||
// This is the host's own line, applied to the one shape where getting it wrong is not
|
||||
// recoverable: it removes what it made and leaves what it merely configured. An empty
|
||||
// directory is what it made. A full one is not.
|
||||
entries, err := os.ReadDir(a.Target)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return "forgotten", "no longer there", nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
if len(entries) > 0 {
|
||||
return "kept", fmt.Sprintf(
|
||||
"no longer declared, and %d item(s) inside that the mesh did not put there — "+
|
||||
"remove it by hand once you know what it is", len(entries)), nil
|
||||
}
|
||||
if err := os.Remove(a.Target); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return "removed", "no longer declared, and empty", nil
|
||||
|
||||
case declaration.TypeFile:
|
||||
if err := os.RemoveAll(a.Target); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user