package main import ( "sort" ) // The states a manifest the store holds can be in, as the records see it (novox/hq to-be 51). const ( StateKept = "kept" StateHolderKept = "holder-of-kept-archive" StateEligible = "eligible" StateHolderEligible = "holder-of-eligible-archive" StateCollectedPresent = "let-go-yet-present" StateNamedDocument = "named-document" StateUnrecorded = "unrecorded" collectionRemovesThese = "eligible and holder-of-eligible-archive, through the controller's collect; nothing else" ) // Entry is one manifest the store holds, and what the records say of it. type Entry struct { Repository string `json:"repository"` Digest string `json:"digest"` State string `json:"state"` Why []string `json:"why,omitempty"` // Archive is the archive a holder keeps. Archive string `json:"archive,omitempty"` Tags []string `json:"tags,omitempty"` // Bytes are the blobs it marks, its own content included. Bytes int64 `json:"bytes"` // Unread says its content could not be read, so what it marks is not known beyond itself. Unread string `json:"unread,omitempty"` } // recordIndex finds a record by repository and digest. type recordIndex map[string]*Record func indexRecords(r *Records) recordIndex { idx := recordIndex{} for i := range r.References { rec := &r.References[i] if rec.Repository == "" || rec.Digest == "" { continue } idx[rec.Kind+" "+Key(rec.Repository, rec.Digest)] = rec } return idx } // Classify gives every manifest the store holds one state. func Classify(v *View, recs *Records) []Entry { idx := indexRecords(recs) var out []Entry for _, name := range v.L.RepoNames() { r := v.L.Repos[name] tagsAt := map[string][]string{} for t, d := range r.Tags { tagsAt[d] = append(tagsAt[d], t) } digests := make([]string, 0, len(r.Revisions)) for d := range r.Revisions { digests = append(digests, d) } sort.Strings(digests) for _, d := range digests { e := Entry{Repository: name, Digest: d, State: StateUnrecorded, Tags: tagsAt[d]} sort.Strings(e.Tags) blobs := map[string]bool{} for _, b := range v.Marks(name, d) { blobs[b] = true } e.Bytes = v.Bytes(blobs) e.Unread = v.Unread[Key(name, d)] m := v.M[Key(name, d)] if rec := idx["image "+Key(name, d)]; rec != nil { e.State, e.Why = stateOf(rec, false), rec.Why } else if m.Holder() { archive := m.Layers[0] if rec := idx["archive "+Key(name, archive)]; rec != nil { e.State, e.Why, e.Archive = stateOf(rec, true), rec.Why, archive } else if len(e.Tags) > 0 { e.State = StateNamedDocument } } out = append(out, e) } } return out } func stateOf(rec *Record, holder bool) string { switch rec.State { case "kept": if holder { return StateHolderKept } return StateKept case "eligible": if holder { return StateHolderEligible } return StateEligible case "collected": return StateCollectedPresent } // A state this bundle does not know is kept: never offered as removable. return StateKept } // Missing are the references the records keep that the store does not hold: an image whose manifest // is not in its repository, an archive whose blob is not in the store. func Missing(v *View, recs *Records) []string { var out []string for _, rec := range recs.References { if rec.State != "kept" || rec.Repository == "" || rec.Digest == "" { continue } switch rec.Kind { case "image": if r := v.L.Repos[rec.Repository]; r == nil || !r.Revisions[rec.Digest] { out = append(out, rec.Reference) } case "archive": if _, ok := v.L.Blobs[rec.Digest]; !ok { out = append(out, rec.Reference) } } } sort.Strings(out) return out } // StateSum is the manifests of one state: how many, the bytes they mark together, and the bytes only // they mark — what the store would give back if they alone went. type StateSum struct { Manifests int `json:"manifests"` Bytes int64 `json:"bytes"` OnlyTheirBytes int64 `json:"only_their_bytes"` } // Summarise sums entries by state, over the whole store or one repository's entries, the "only theirs" // always judged against every manifest in the store. func Summarise(v *View, entries []Entry, all []Entry) map[string]*StateSum { statesOf := map[string]map[string]bool{} for _, e := range all { for _, b := range v.Marks(e.Repository, e.Digest) { if statesOf[b] == nil { statesOf[b] = map[string]bool{} } statesOf[b][e.State] = true } } sums := map[string]*StateSum{} blobs := map[string]map[string]bool{} for _, e := range entries { s := sums[e.State] if s == nil { s = &StateSum{} sums[e.State] = s blobs[e.State] = map[string]bool{} } s.Manifests++ for _, b := range v.Marks(e.Repository, e.Digest) { blobs[e.State][b] = true } } for state, set := range blobs { sums[state].Bytes = v.Bytes(set) only := map[string]bool{} for b := range set { if len(statesOf[b]) == 1 { only[b] = true } } sums[state].OnlyTheirBytes = v.Bytes(only) } return sums } // LetGoKeys are the manifests the store would no longer hold once the controller lets go of these // references: an image's manifest, and an archive's holder. func LetGoKeys(v *View, refs []string) map[string]bool { out := map[string]bool{} for _, ref := range refs { repo, digest, kind := splitReference(ref) r := v.L.Repos[repo] if r == nil { continue } switch kind { case "image": if r.Revisions[digest] { out[Key(repo, digest)] = true } case "archive": for d := range r.Revisions { if m := v.M[Key(repo, d)]; m.Holder() && m.Layers[0] == digest { out[Key(repo, d)] = true } } } } return out }