CAT-AS1: distribution removes an index's platforms with it, but those a kept index names
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
This commit is contained in:
@@ -89,6 +89,7 @@ type fakeCatalog struct {
|
||||
tags map[string]string // name:version -> digest
|
||||
removed []string
|
||||
mirrorErr error
|
||||
indexes map[string][]string // name@digest -> the platform manifests it names
|
||||
}
|
||||
|
||||
func (c *fakeCatalog) Versions(name string) ([]string, error) {
|
||||
@@ -112,6 +113,9 @@ func (c *fakeCatalog) RemoveDigest(name, digest string) error {
|
||||
c.removed = append(c.removed, name+"@"+digest)
|
||||
return nil
|
||||
}
|
||||
func (c *fakeCatalog) Platforms(name, digest string) ([]string, error) {
|
||||
return c.indexes[name+"@"+digest], nil
|
||||
}
|
||||
func (c *fakeCatalog) Mirror(source, digest string) (string, error) {
|
||||
if c.mirrorErr != nil {
|
||||
return "", c.mirrorErr
|
||||
@@ -359,6 +363,69 @@ func TestTheHolderOfTheTwoImageSeats(t *testing.T) {
|
||||
t.Errorf("the builder's removal of a tagged version: %v", err)
|
||||
}
|
||||
})
|
||||
// CAT-AS1: what the sweep did over the store's door is the holder's: a removal for retention of an index removes
|
||||
// its platform manifests first, but those a tagged or held index of the repository names; a manifest such an index
|
||||
// names is refused; OCI.Platforms reads an index's from the store.
|
||||
t.Run("an index's platforms go with it, but those a kept index names", func(t *testing.T) {
|
||||
d := func(s string) string { return "sha256:" + strings.Repeat(s, 64) }
|
||||
sweep := "novox.artifact-store-tools"
|
||||
name := "upstream/docker.io/library/alpine"
|
||||
for _, c := range []struct {
|
||||
case_, remove string
|
||||
tagged, held string // the kept index, by a tag or a hold; none when empty
|
||||
removed []string
|
||||
refused string
|
||||
}{
|
||||
{case_: "no kept index", remove: d("1"), removed: []string{d("a"), d("b"), d("1")}},
|
||||
{case_: "a tagged index shares a platform", remove: d("1"), tagged: d("2"), removed: []string{d("a"), d("1")}},
|
||||
{case_: "a held index shares a platform", remove: d("1"), held: d("2"), removed: []string{d("a"), d("1")}},
|
||||
{case_: "a platform of a held index", remove: d("b"), held: d("2"), refused: "is kept by the index " + d("2")},
|
||||
{case_: "an image that is no index", remove: d("c"), removed: []string{d("c")}},
|
||||
} {
|
||||
b := newBed(true)
|
||||
b.cat.indexes = map[string][]string{name + "@" + d("1"): {d("a"), d("b")}, name + "@" + d("2"): {d("b")}}
|
||||
if c.tagged != "" {
|
||||
b.cat.tags[name+":3.20"] = c.tagged
|
||||
}
|
||||
if c.held != "" {
|
||||
if err := b.v.HoldOne(ImageSeat, sweep, HoldRequest{Name: name, Digest: c.held}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
err := b.v.Remove(ImageSeat, sweep, RemoveRequest{Name: name, Digest: c.remove, Reason: ReasonRetention})
|
||||
var removed []string
|
||||
for _, r := range b.cat.removed {
|
||||
removed = append(removed, strings.TrimPrefix(r, name+"@"))
|
||||
}
|
||||
if c.refused != "" {
|
||||
if err == nil || !strings.Contains(err.Error(), c.refused) || len(removed) != 0 {
|
||||
t.Errorf("%s: %v, removed %v; want refused saying %q and nothing removed", c.case_, err, removed, c.refused)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if err != nil || !reflect.DeepEqual(removed, c.removed) {
|
||||
t.Errorf("%s: %v, removed %v, want %v", c.case_, err, removed, c.removed)
|
||||
}
|
||||
}
|
||||
store := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/v2/" + name + "/manifests/" + d("1"):
|
||||
_, _ = w.Write([]byte(`{"mediaType":"application/vnd.oci.image.index.v1+json","manifests":[{"digest":"` + d("a") + `"},{"digest":"` + d("b") + `"}]}`))
|
||||
case "/v2/" + name + "/manifests/" + d("c"):
|
||||
_, _ = w.Write([]byte(`{"mediaType":"application/vnd.oci.image.manifest.v1+json","layers":[]}`))
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
defer store.Close()
|
||||
u, _ := url.Parse(store.URL)
|
||||
o := &OCI{Base: u}
|
||||
for digest, want := range map[string][]string{d("1"): {d("a"), d("b")}, d("c"): nil, d("9"): nil} {
|
||||
if got, err := o.Platforms(name, digest); err != nil || !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("Platforms(%s) = %v, %v, want %v", digest[:10], got, err, want)
|
||||
}
|
||||
}
|
||||
})
|
||||
t.Run("the store's port: reads open, writes from distribution alone, and the direct-write window", func(t *testing.T) {
|
||||
store := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusCreated) }))
|
||||
defer store.Close()
|
||||
|
||||
Reference in New Issue
Block a user