F-parts: on registry 2.8.3 a shared-digest tagged version's remove keeps the tag and says removed
The store's pinned image is registry 2.8.3, whose DeleteManifest cannot delete a tag alone: by a tag it deletes the empty digest and answers 400 DIGEST_INVALID. The fakes accepted a DELETE by tag, a fixture agreeing with the mistake, so every merge of a reproducible build would have been a refused remove and a retry finding live. RemoveTag now answers ErrNoTagAlone for that answer; remove then keeps the tag, whose bytes stay under the other tag anyway, and says removed with that reason, so the builder lets it go. Any other store failure is still refused; a digest shared with nothing is still deleted by digest. The fakes answer as 2.8.3 does. Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
@@ -117,13 +118,14 @@ func (c *fakeCatalog) Remove(name, version string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveTag answers as OCI.RemoveTag over registry 2.8.3 does (fakeRegistry's DELETE by tag): the store cannot
|
||||
// remove a tag alone, and nothing goes. tagErr is any other failure of the store.
|
||||
func (c *fakeCatalog) RemoveTag(name, version string) error {
|
||||
if c.tagErr != nil {
|
||||
return c.tagErr
|
||||
}
|
||||
c.removed = append(c.removed, name+":"+version+" (tag)")
|
||||
delete(c.tags, name+":"+version)
|
||||
return nil
|
||||
return fmt.Errorf("%w: DELETE of %s:%s by its tag answered 400 DIGEST_INVALID", ErrNoTagAlone, name, version)
|
||||
}
|
||||
func (c *fakeCatalog) RemoveDigest(name, digest string) error {
|
||||
c.removed = append(c.removed, name+"@"+digest)
|
||||
@@ -427,22 +429,42 @@ func TestTheHolderOfTheTwoImageSeats(t *testing.T) {
|
||||
if err := b.v.Remove(ArtifactSeat, "novox.mesh-delivery-builder", RemoveRequest{Name: "m/p", Version: "0.1.5-feat-x.1", Reason: "branch merged"}); err != nil || b.cat.tags["m/p:0.1.5-feat-x.1"] != "" {
|
||||
t.Errorf("the builder's removal of a tagged version: %v", err)
|
||||
}
|
||||
// Security review F1: a reproducible build gives a branch's tagged version and the trunk's numbered one one
|
||||
// digest; the tagged one's removal takes its tag alone, and the numbered one stays pullable.
|
||||
// Security review F1 on registry 2.8.3: a reproducible build gives a branch's tagged version and the trunk's
|
||||
// numbered one one digest; the store cannot remove the tag alone, so nothing goes, the numbered one stays
|
||||
// pullable, and the removal is said removed with that reason: no refusal, so the builder raises no retry.
|
||||
b.cat.tags["m/p:0.1.6-feat-y.1"], b.cat.tags["m/p:0.1.6"] = part.Digest, part.Digest
|
||||
if err := b.v.Remove(ArtifactSeat, "novox.mesh-delivery-builder", RemoveRequest{Name: "m/p", Version: "0.1.6-feat-y.1", Reason: "branch merged"}); err != nil {
|
||||
t.Fatalf("the removal of a tagged version sharing its digest: %v", err)
|
||||
t.Fatalf("the removal of a tagged version sharing its digest: %v; want no error", err)
|
||||
}
|
||||
if b.cat.tags["m/p:0.1.6-feat-y.1"] != "" || b.cat.tags["m/p:0.1.6"] != part.Digest {
|
||||
t.Errorf("tags after the tagged version's removal: %v; want the tag gone and 0.1.6 still at %s", b.cat.tags, part.Digest)
|
||||
if b.cat.tags["m/p:0.1.6-feat-y.1"] != part.Digest || b.cat.tags["m/p:0.1.6"] != part.Digest {
|
||||
t.Errorf("tags after the tagged version's removal: %v; want both kept at %s", b.cat.tags, part.Digest)
|
||||
}
|
||||
// A store that cannot delete a tag alone removes nothing, and the removal is refused naming the shared digest.
|
||||
if last := b.said[len(b.said)-1]; last.event != EventRemoved || last.body.Reason != "branch merged; the store cannot remove a tag alone, so the tag 0.1.6-feat-y.1 is kept and its bytes stay under 0.1.6" {
|
||||
t.Errorf("said %s %q; want removed, saying the tag is kept under 0.1.6", last.event, last.body.Reason)
|
||||
}
|
||||
// Any other failure of the store removes nothing and is refused naming the shared digest.
|
||||
b.cat.tags["m/p:0.1.7-feat-z.1"], b.cat.tags["m/p:0.1.7"] = part.Digest, part.Digest
|
||||
b.cat.tagErr = errors.New("UNSUPPORTED")
|
||||
if err := b.v.Remove(ArtifactSeat, "novox.mesh-delivery-builder", RemoveRequest{Name: "m/p", Version: "0.1.7-feat-z.1", Reason: "branch merged"}); err == nil || !strings.Contains(err.Error(), "shares its digest") || b.cat.tags["m/p:0.1.7"] != part.Digest {
|
||||
t.Errorf("a store that cannot delete a tag alone: %v, tags %v; want refused and 0.1.7 kept", err, b.cat.tags)
|
||||
b.cat.tagErr = errors.New("the store answered 500")
|
||||
said := len(b.said)
|
||||
if err := b.v.Remove(ArtifactSeat, "novox.mesh-delivery-builder", RemoveRequest{Name: "m/p", Version: "0.1.7-feat-z.1", Reason: "branch merged"}); err == nil || !strings.Contains(err.Error(), "shares its digest") || b.cat.tags["m/p:0.1.7"] != part.Digest || len(b.said) != said {
|
||||
t.Errorf("a store failing otherwise: %v, tags %v; want refused, 0.1.7 kept, nothing said", err, b.cat.tags)
|
||||
}
|
||||
b.cat.tagErr = nil
|
||||
// The store's own answers, as registry 2.8.3 gives them: DELETE by tag removes nothing (ErrNoTagAlone); by
|
||||
// digest the manifest goes with every tag naming it.
|
||||
r283 := newFakeRegistry()
|
||||
sts := httptest.NewServer(r283)
|
||||
defer sts.Close()
|
||||
u, _ := url.Parse(sts.URL)
|
||||
o := &OCI{Base: u}
|
||||
manifest := []byte(`{"schemaVersion":2}`)
|
||||
r283.manifests["m/p:0.1.8-feat-w.1"], r283.manifests["m/p:0.1.8"] = manifest, manifest
|
||||
if err := o.RemoveTag("m/p", "0.1.8-feat-w.1"); !errors.Is(err, ErrNoTagAlone) || r283.manifests["m/p:0.1.8-feat-w.1"] == nil {
|
||||
t.Errorf("RemoveTag on 2.8.3 = %v; want ErrNoTagAlone and the tag kept", err)
|
||||
}
|
||||
if err := o.Remove("m/p", "0.1.8"); err != nil || len(r283.manifests) != 0 {
|
||||
t.Errorf("Remove by digest on 2.8.3 = %v, %d manifests left; want both tags gone", err, len(r283.manifests))
|
||||
}
|
||||
})
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user