The sweep asks remove now instead of deleting over the store's door, so what its door did for an index is the holder's: a removal for retention removes the platform manifests no tagged or held index of the repository names, and refuses a manifest such an index names. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
118 lines
3.9 KiB
Go
118 lines
3.9 KiB
Go
package holder
|
|
|
|
// **An index's platform manifests go with it, unless a kept index names them** (CAT-AS1: what the artifact sweep did
|
|
// over the store's door, ADR 0257, is the holder's now that the sweep asks `remove`). A removal for retention of an
|
|
// index removes first every platform manifest it names that no other kept index of the repository names, then the
|
|
// index; a kept index is one a tag names or one held (`hold`). A manifest a kept index names is never removed for
|
|
// retention, so holding an index keeps its platforms too.
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// spared is every manifest of name a kept index keeps on seat, but the one being removed (except): each tagged
|
|
// manifest and held digest, and the platform manifests each of those names.
|
|
func (v *Verbs) spared(seat, name, except string) (map[string]string, error) {
|
|
keepers := map[string]string{} // digest -> why it is kept
|
|
tags, err := v.Catalog.Versions(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, t := range tags {
|
|
d, err := v.Catalog.Digest(name, t)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if d != "" {
|
|
keepers[d] = "the tag " + t
|
|
}
|
|
}
|
|
holds, err := v.Holds.List()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, h := range holds {
|
|
if h.Seat == seat && h.Name == name {
|
|
keepers[h.Digest] = "the hold of " + h.Digest
|
|
}
|
|
}
|
|
out := map[string]string{}
|
|
for d, why := range keepers {
|
|
if d == except {
|
|
continue
|
|
}
|
|
out[d] = why
|
|
children, err := v.Catalog.Platforms(name, d)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading the kept %s@%s: %w", name, d, err)
|
|
}
|
|
for _, c := range children {
|
|
out[c] = "the index " + d + ", kept by " + why
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// removeForRetention removes name@digest and, for an index, the platform manifests no kept index names; a manifest a
|
|
// kept index names is refused. Any read that fails removes nothing.
|
|
func (v *Verbs) removeForRetention(seat, name, digest string) error {
|
|
children, err := v.Catalog.Platforms(name, digest)
|
|
if err != nil {
|
|
return fmt.Errorf("remove: the store cannot read %s@%s, so nothing is removed: %w", name, digest, err)
|
|
}
|
|
spared, err := v.spared(seat, name, digest)
|
|
if err != nil {
|
|
return fmt.Errorf("remove: which manifests a kept index of %s names cannot be read, so nothing is removed: %w", name, err)
|
|
}
|
|
if why, kept := spared[digest]; kept {
|
|
return fmt.Errorf("remove: refused: %s@%s is kept by %s", name, digest, why)
|
|
}
|
|
for _, c := range children {
|
|
if _, kept := spared[c]; kept {
|
|
continue
|
|
}
|
|
if err := v.Catalog.RemoveDigest(name, c); err != nil {
|
|
return fmt.Errorf("remove: the store did not remove %s@%s, a platform of %s: %w", name, c, digest, err)
|
|
}
|
|
}
|
|
if err := v.Catalog.RemoveDigest(name, digest); err != nil {
|
|
return fmt.Errorf("remove: the store did not remove %s@%s: %w", name, digest, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Platforms is the manifests an index names, by digest: none for a manifest that is not an index, or one the store
|
|
// does not hold.
|
|
func (o *OCI) Platforms(name, digest string) ([]string, error) {
|
|
resp, b, err := o.do(http.MethodGet, "/v2/"+name+"/manifests/"+digest, "", nil,
|
|
"application/vnd.oci.image.index.v1+json", "application/vnd.docker.distribution.manifest.list.v2+json",
|
|
mediaManifest, "application/vnd.docker.distribution.manifest.v2+json")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if resp.StatusCode == http.StatusNotFound {
|
|
return nil, nil
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, fmt.Errorf("the store answered %d for %s@%s", resp.StatusCode, name, digest)
|
|
}
|
|
var doc struct {
|
|
Manifests []struct {
|
|
Digest string `json:"digest"`
|
|
} `json:"manifests"`
|
|
}
|
|
if err := json.Unmarshal(b, &doc); err != nil {
|
|
return nil, fmt.Errorf("%s@%s is not a manifest: %w", name, digest, err)
|
|
}
|
|
var out []string
|
|
for _, m := range doc.Manifests {
|
|
if strings.HasPrefix(m.Digest, "sha256:") {
|
|
out = append(out, m.Digest)
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|