41 lines
1.8 KiB
Go
41 lines
1.8 KiB
Go
package appbackup
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
)
|
|
|
|
// ExcludedDockerVolumes (R-49, operator ruling D21 = B, 2026-10-22) returns the project-prefixed Docker names of the
|
|
// named volumes the template's backup block marks `excluded`, and a sentence per entry it ignored. An entry is ignored
|
|
// when its class is not `excluded`, when its name is not a top-level named volume of the compose file, or — for every
|
|
// entry at once — when the exclusions would cover EVERY named volume: a unit with no volume dump at all is the shape
|
|
// the hollow-unit guard (R-403, r403_hollow.go) alarms on, and an app whose every volume is a cache needs no volume
|
|
// leg anyway, so that is a template mistake, never a silent hollow unit. Pinned by TestR49_*.
|
|
func ExcludedDockerVolumes(spec *BackupSpec, composePath string) (excluded []string, ignored []string) {
|
|
if spec == nil || len(spec.Volumes) == 0 {
|
|
return nil, nil
|
|
}
|
|
declared := map[string]bool{}
|
|
vols := ParseComposeNamedVolumes(composePath)
|
|
for _, v := range vols {
|
|
declared[v.Name] = true
|
|
}
|
|
project := filepath.Base(filepath.Dir(composePath))
|
|
seen := map[string]bool{}
|
|
for _, v := range spec.Volumes {
|
|
switch {
|
|
case v.Class != ClassExcluded:
|
|
ignored = append(ignored, fmt.Sprintf("volume %q: class %q — only \"excluded\" is meaningful for a named volume", v.Name, v.Class))
|
|
case !declared[v.Name]:
|
|
ignored = append(ignored, fmt.Sprintf("volume %q is not a named volume of the compose file", v.Name))
|
|
case !seen[v.Name]:
|
|
seen[v.Name] = true
|
|
excluded = append(excluded, project+"_"+v.Name)
|
|
}
|
|
}
|
|
if len(excluded) > 0 && len(excluded) == len(vols) {
|
|
return nil, append(ignored, fmt.Sprintf("the backup block excludes every named volume (%d) — ignored: a unit with no volume dump reads as hollow (R-403)", len(vols)))
|
|
}
|
|
return excluded, ignored
|
|
}
|