1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package guestimage
import (
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
"github.com/johnandersen777/socialweb-computer-kcp/internal/versions"
)
const (
FetchPrefix = ".fetch-"
FetchKeep = time.Hour
BinaryTimeout = 30 * time.Second
)
var digestName = regexp.MustCompile(`^[0-9a-f]{64}$`)
func Names() ([]string, error) {
g, err := versions.GuestImages()
if err != nil {
return nil, err
}
var out []string
for _, t := range g.Targets {
m, ok := t.Outputs[ManifestName]
if !ok || !digestName.MatchString(m.SHA256) || t.Hypervisor == "" || t.Arch == "" {
continue
}
out = append(out, filepath.Join(t.Hypervisor, t.Arch, m.SHA256))
}
sort.Strings(out)
return out, nil
}
type binaryVersion struct {
GuestImages []string `json:"guestImages"`
}
func NamedByBinary(ctx context.Context, binary string) ([]string, error) {
if _, err := os.Stat(binary); err != nil {
return nil, err
}
ctx, cancel := context.WithTimeout(ctx, BinaryTimeout)
defer cancel()
out, err := exec.CommandContext(ctx, binary, "version", "--json").Output()
if err != nil {
return nil, fmt.Errorf("guestimage: %s version --json: %w", binary, err)
}
var v binaryVersion
if err := json.Unmarshal(out, &v); err != nil {
return nil, fmt.Errorf("guestimage: %s version --json: %w", binary, err)
}
return v.GuestImages, nil
}
type GCKept struct {
Dir string `json:"dir"`
Reason string `json:"reason"`
}
type GCReport struct {
Root string `json:"root"`
Removed []string `json:"removed"`
Kept []GCKept `json:"kept"`
}
func (s Store) Collect(keep map[string]string, now time.Time) (GCReport, error) {
rep := GCReport{Root: s.Root, Removed: []string{}, Kept: []GCKept{}}
if !filepath.IsAbs(s.Root) {
return rep, fmt.Errorf("guestimage: the image store root %q is not absolute", s.Root)
}
top, err := os.ReadDir(s.Root)
if os.IsNotExist(err) {
return rep, nil
}
if err != nil {
return rep, err
}
for _, h := range top {
dir := filepath.Join(s.Root, h.Name())
if h.IsDir() && strings.HasPrefix(h.Name(), FetchPrefix) {
if fi, err := h.Info(); err == nil && now.Sub(fi.ModTime()) > FetchKeep {
if err := os.RemoveAll(dir); err != nil {
return rep, err
}
rep.Removed = append(rep.Removed, dir)
}
continue
}
if !h.IsDir() {
continue
}
arches, err := os.ReadDir(dir)
if err != nil {
return rep, err
}
for _, a := range arches {
if !a.IsDir() {
continue
}
adir := filepath.Join(dir, a.Name())
images, err := os.ReadDir(adir)
if err != nil {
return rep, err
}
for _, img := range images {
if !img.IsDir() || !digestName.MatchString(img.Name()) {
continue
}
path := filepath.Join(adir, img.Name())
if why, ok := keep[img.Name()]; ok {
rep.Kept = append(rep.Kept, GCKept{Dir: path, Reason: why})
continue
}
if err := os.RemoveAll(path); err != nil {
return rep, err
}
rep.Removed = append(rep.Removed, path)
}
}
}
return rep, nil
}