Every held thing carries what a take compares: for a found container its image and the image's date, the networks it is on and the other containers on each, its mounts and published ports, beside the declared image (and its date once pulled), ports and volumes, with the downgrade decided when both dates are known; for a found file whether the declared content differs and how, as lines lost and lines new. A resource whose target moved keeps the former target on record as an orphan, so the next apply removes the container or file the host wrote under the old name (issue 097). Every apply reports the strays: containers the mesh neither wrote nor holds.
55 lines
1.8 KiB
Go
55 lines
1.8 KiB
Go
package apply
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/novox/mesh-host/internal/declaration"
|
|
"github.com/novox/mesh-host/internal/store"
|
|
)
|
|
|
|
// Strays is what runs on the machine that the mesh neither wrote nor holds (novox/hq ADR 0163):
|
|
// every container the runtime has that no record names and no hold names. The question nothing
|
|
// answered on 2026-09-23, when a renamed resource left its old container running for a day; asked
|
|
// on every apply now, and reported, so a thing left behind is seen the day it is left.
|
|
//
|
|
// Containers only, today. A listener nobody declared is harder to attribute to a thing, and the
|
|
// machine's own services are not strays; that account is issue 160's.
|
|
func Strays(ctx context.Context, run Runner, known store.State) ([]store.Stray, error) {
|
|
cri, err := containerRuntime(ctx, run)
|
|
if err != nil {
|
|
return nil, nil // a machine with no runtime has no containers to stray
|
|
}
|
|
out, err := run(ctx, cri, "ps", "-a", "--format", "{{.Names}}\t{{.Image}}\t{{.State}}")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ours := map[string]bool{}
|
|
for _, r := range known.Resources {
|
|
if declaration.Type(r.Type) == declaration.TypeContainer {
|
|
ours[r.Target] = true
|
|
}
|
|
}
|
|
for _, h := range known.Held {
|
|
if h.Kind == string(declaration.TypeContainer) {
|
|
ours[h.Target] = true
|
|
}
|
|
}
|
|
var strays []store.Stray
|
|
for _, line := range strings.Split(strings.TrimSpace(out), "\n") {
|
|
parts := strings.Split(line, "\t")
|
|
name := strings.TrimSpace(parts[0])
|
|
if name == "" || ours[name] {
|
|
continue
|
|
}
|
|
detail := ""
|
|
if len(parts) > 2 {
|
|
detail = strings.TrimSpace(parts[1]) + ", " + strings.TrimSpace(parts[2])
|
|
}
|
|
strays = append(strays, store.Stray{Kind: string(declaration.TypeContainer), Name: name, Detail: detail})
|
|
}
|
|
sort.Slice(strays, func(i, j int) bool { return strays[i].Name < strays[j].Name })
|
|
return strays, nil
|
|
}
|