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 }