package release import ( "context" "errors" "fmt" "path" "strings" "sync/atomic" "testing" "time" ) func manifestDigestOf(t *testing.T, s Store, acct, app, ver string) string { t.Helper() raw, _, err := s.Get(context.Background(), ManifestKey(acct, app, ver)) if err != nil { t.Fatal(err) } return ManifestDigest(raw) } func readPtr(t *testing.T, s Store, pub []byte, ch string) *ChannelPointer { t.Helper() p, err := Channel(context.Background(), s, pub, "bab", "km", ch) if err != nil { t.Fatalf("Channel: %v", err) } return p } func TestWritersEmitNothingWithoutTheMarker(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() priv, pub := testKeys(t) shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) shipRelease(t, s, "bab", "km", "v2", adoptPlatform, []byte("two")) for _, v := range []string{"v1", "v2"} { if err := Promote(ctx, s, priv, "bab", "km", "stable", v); err != nil { t.Fatal(err) } } if p := readPtr(t, s, pub, "stable"); p.Digest != "" || p.HistoryDigests != nil { t.Fatalf("no marker, yet digests: %+v", p) } raw, _, _ := s.Get(ctx, ChannelKey("bab", "km", "stable")) if strings.Contains(string(raw), "digest") { t.Fatal("the wrapper mentions digests with the marker off") } } func TestPromoteSignsTheShippedDigestAndInheritsNothing(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() priv, pub := testKeys(t) shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) shipRelease(t, s, "bab", "km", "v2", adoptPlatform, []byte("two")) shipRelease(t, s, "bab", "km", "v3", adoptPlatform, []byte("three")) if err := Promote(ctx, s, priv, "bab", "km", "stable", "v1"); err != nil { // a legacy pointer t.Fatal(err) } markerOn(t, s, priv, "bab") if err := Promote(ctx, s, priv, "bab", "km", "stable", "v2"); err != nil { t.Fatal(err) } p := readPtr(t, s, pub, "stable") if p.Digest != manifestDigestOf(t, s, "bab", "km", "v2") { t.Fatalf("head digest %q is not v2's shipped manifest", p.Digest) } // Spec ยง8: promoting over a legacy pointer leaves inherited entries // without digests, never computed. if len(p.HistoryDigests) != 1 || p.HistoryDigests[0] != "" { t.Fatalf("v1 was inherited from a legacy pointer, yet history_digests = %q", p.HistoryDigests) } if err := Promote(ctx, s, priv, "bab", "km", "stable", "v3"); err != nil { t.Fatal(err) } p = readPtr(t, s, pub, "stable") want := []string{manifestDigestOf(t, s, "bab", "km", "v2"), ""} if p.History[0] != "v2" || p.HistoryDigests[0] != want[0] || p.HistoryDigests[1] != "" { t.Fatalf("history %q / %q, want v2's signed digest copied and v1 still empty", p.History, p.HistoryDigests) } } func TestHistoryCapTrimsDigestsWithVersions(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() priv, pub := testKeys(t) markerOn(t, s, priv, "bab") for i := 0; i < historyMax+3; i++ { v := "v" + string(rune('a'+i)) shipRelease(t, s, "bab", "km", v, adoptPlatform, []byte(v)) if err := Promote(ctx, s, priv, "bab", "km", "canary/x", v); err != nil { t.Fatal(err) } } p := readPtr(t, s, pub, "canary/x") if len(p.History) != historyMax || len(p.HistoryDigests) != historyMax { t.Fatalf("history %d, digests %d; want %d each", len(p.History), len(p.HistoryDigests), historyMax) } for i, v := range p.History { if p.HistoryDigests[i] != manifestDigestOf(t, s, "bab", "km", v) { t.Fatalf("history[%d] %s carries another build's digest", i, v) } } } // Review Focus 2. func TestPromoteRefusesAnUnverifiableMarker(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() priv, pub := testKeys(t) shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) other, _ := testKeys(t) markerOn(t, s, other, "bab") err := Promote(ctx, s, priv, "bab", "km", "stable", "v1") if err == nil || !strings.Contains(err.Error(), "marker") { t.Fatalf("Promote with a foreign marker = %v; want a refusal naming the marker", err) } if _, err := Channel(ctx, s, pub, "bab", "km", "stable"); err == nil { t.Fatal("the refused promotion still wrote a pointer") } } func TestRetryCopiesTheHeadDigest(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() priv, pub := testKeys(t) shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) if err := Promote(ctx, s, priv, "bab", "km", "stable", "v1"); err != nil { // legacy head t.Fatal(err) } markerOn(t, s, priv, "bab") time.Sleep(1100 * time.Millisecond) if _, err := PromoteOnLadder(ctx, s, priv, "bab", "km", "stable", "v1", PromoteOptions{Retry: true, Hotfix: true}); err != nil { t.Fatal(err) } if p := readPtr(t, s, pub, "stable"); p.Digest != "" { t.Fatalf("a retry computed a digest for the head it renewed: %q", p.Digest) } } func TestScopedPromoteCarriesDigests(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() root, pub := testKeys(t) markerOn(t, s, root, "bab") seed, grant, err := MintKeyGrant(root, "ci", []string{"canary"}, time.Hour) if err != nil { t.Fatal(err) } key, _ := ParseSigningKey(seed, "test") sg, err := ScopedSigner(key, grant, pub) if err != nil { t.Fatal(err) } shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) if err := sg.Promote(ctx, s, "bab", "km", "canary/x", "v1"); err != nil { t.Fatal(err) } if p := readPtr(t, s, pub, "canary/x"); p.Digest != manifestDigestOf(t, s, "bab", "km", "v1") { t.Fatalf("a scoped promotion signed digest %q", p.Digest) } } func TestRollbackSwapsDigestsWithVersions(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() priv, pub := testKeys(t) markerOn(t, s, priv, "bab") shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) shipRelease(t, s, "bab", "km", "v2", adoptPlatform, []byte("two")) for _, v := range []string{"v1", "v2"} { if err := Promote(ctx, s, priv, "bab", "km", "stable", v); err != nil { t.Fatal(err) } } if err := Rollback(ctx, s, priv, "bab", "km", "stable"); err != nil { t.Fatal(err) } p := readPtr(t, s, pub, "stable") if p.Version != "v1" || p.Digest != manifestDigestOf(t, s, "bab", "km", "v1") || p.History[0] != "v2" || p.HistoryDigests[0] != manifestDigestOf(t, s, "bab", "km", "v2") { t.Fatalf("after rollback: %+v", p) } } func TestRollbackOntoAnEntryWithoutADigestIsRefused(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() priv, pub := testKeys(t) shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) shipRelease(t, s, "bab", "km", "v2", adoptPlatform, []byte("two")) if err := Promote(ctx, s, priv, "bab", "km", "stable", "v1"); err != nil { t.Fatal(err) } markerOn(t, s, priv, "bab") if err := Promote(ctx, s, priv, "bab", "km", "stable", "v2"); err != nil { t.Fatal(err) } err := Rollback(ctx, s, priv, "bab", "km", "stable") var rn *ResignNeededError if !errors.As(err, &rn) || rn.Version != "v1" || err.Error() != fmt.Sprintf(TextRollbackResign, "km", "stable", "v1", "km", "stable") { t.Fatalf("rollback onto a digest-less v1 = %v; want ResignNeededError naming v1", err) } if p := readPtr(t, s, pub, "stable"); p.Version != "v2" { t.Fatalf("the refused rollback moved the channel to %s", p.Version) } } // manifestCountingStore counts manifest reads, so a test can prove a writer never // fetched one. type manifestCountingStore struct { Store manifests atomic.Int32 } func (c *manifestCountingStore) Get(ctx context.Context, key string) ([]byte, string, error) { if strings.HasSuffix(key, "/manifest.json") { c.manifests.Add(1) } return c.Store.Get(ctx, key) } func TestFastTrackKeepsTheSignedDigestsVerbatim(t *testing.T) { base := NewDirStore(t.TempDir()) ctx := context.Background() priv, pub := testKeys(t) shipRelease(t, base, "bab", "km", "v1", adoptPlatform, []byte("one")) shipRelease(t, base, "bab", "km", "v2", adoptPlatform, []byte("two")) if err := Promote(ctx, base, priv, "bab", "km", "stable", "v1"); err != nil { t.Fatal(err) } markerOn(t, base, priv, "bab") if err := Promote(ctx, base, priv, "bab", "km", "stable", "v2"); err != nil { t.Fatal(err) } before := readPtr(t, base, pub, "stable") s := &manifestCountingStore{Store: base} if _, err := FastTrack(ctx, s, priv, "bab", "km", "stable", "ops@example.com"); err != nil { t.Fatal(err) } if n := s.manifests.Load(); n != 0 { t.Fatalf("FastTrack read %d manifests; it must copy digests, never fetch them", n) } after := readPtr(t, base, pub, "stable") if !after.FastTrack || after.Digest != before.Digest || len(after.HistoryDigests) != 1 || after.HistoryDigests[0] != before.HistoryDigests[0] { t.Fatalf("FastTrack changed the digests: before %+v, after %+v", before, after) } } func TestRebindKeepsDigests(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() priv, pub := testKeys(t) markerOn(t, s, priv, "bab") shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) if err := Promote(ctx, s, priv, "bab", "km", "stable", "v1"); err != nil { t.Fatal(err) } raw, _, _ := s.Get(ctx, ChannelKey("bab", "km", "stable")) out, err := RebindPointer(priv, raw, "bab", "cdf", "km", "stable") if err != nil { t.Fatal(err) } p, err := verifyPointer(pub, out, "cdf", "km", "stable") if err != nil || p.Digest != manifestDigestOf(t, s, "bab", "km", "v1") { t.Fatalf("rebound pointer = %+v, %v; want the digest carried verbatim", p, err) } } // reship deletes every object of a shipped version and ships the name again // with other bytes: the reused-name replay of #253. func reship(t *testing.T, s Store, acct, app, ver string, body []byte) { t.Helper() ctx := context.Background() keys, err := s.List(ctx, path.Dir(ManifestKey(acct, app, ver))+"/") if err != nil || len(keys) == 0 { t.Fatalf("List %s: %v %v", ver, keys, err) } for _, k := range keys { if err := s.Delete(ctx, k); err != nil { t.Fatal(err) } } shipRelease(t, s, acct, app, ver, adoptPlatform, body) } // Ruling P7: a promote onto a version in the predecessor's history is bound // to that entry's signed digest. func TestPromoteOfAHistoryEntryWithReshippedBytesIsRefused(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() priv, pub := testKeys(t) markerOn(t, s, priv, "bab") shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) shipRelease(t, s, "bab", "km", "v2", adoptPlatform, []byte("two")) for _, v := range []string{"v1", "v2"} { if err := Promote(ctx, s, priv, "bab", "km", "stable", v); err != nil { t.Fatal(err) } } before := readPtr(t, s, pub, "stable") reship(t, s, "bab", "km", "v1", []byte("not what was promoted")) err := Promote(ctx, s, priv, "bab", "km", "stable", "v1") if !errors.Is(err, ErrManifestDigest) { t.Fatalf("promote of a re-shipped history entry = %v; want ErrManifestDigest", err) } after := readPtr(t, s, pub, "stable") if after.Version != before.Version || after.Digest != before.Digest || !after.PromotedAt.Equal(before.PromotedAt) { t.Fatalf("the refused promote changed the pointer: %+v -> %+v", before, after) } } func TestPromoteOfADigestlessHistoryEntryIsRefused(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() priv, pub := testKeys(t) shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) shipRelease(t, s, "bab", "km", "v2", adoptPlatform, []byte("two")) if err := Promote(ctx, s, priv, "bab", "km", "stable", "v1"); err != nil { t.Fatal(err) } markerOn(t, s, priv, "bab") if err := Promote(ctx, s, priv, "bab", "km", "stable", "v2"); err != nil { t.Fatal(err) } before := readPtr(t, s, pub, "stable") err := Promote(ctx, s, priv, "bab", "km", "stable", "v1") var rn *ResignNeededError if !errors.As(err, &rn) || rn.Version != "v1" || rn.App != "km" || rn.Channel != "stable" { t.Fatalf("promote of a digest-less history entry = %v; want ResignNeededError naming v1", err) } after := readPtr(t, s, pub, "stable") if after.Version != before.Version || !after.PromotedAt.Equal(before.PromotedAt) { t.Fatalf("the refused promote changed the pointer: %+v -> %+v", before, after) } } func TestPromoteOfAnUnchangedHistoryEntrySignsItsDigest(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() priv, pub := testKeys(t) markerOn(t, s, priv, "bab") shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) shipRelease(t, s, "bab", "km", "v2", adoptPlatform, []byte("two")) for _, v := range []string{"v1", "v2"} { if err := Promote(ctx, s, priv, "bab", "km", "stable", v); err != nil { t.Fatal(err) } } signed := readPtr(t, s, pub, "stable").HistoryDigests[0] if err := Promote(ctx, s, priv, "bab", "km", "stable", "v1"); err != nil { t.Fatal(err) } p := readPtr(t, s, pub, "stable") if p.Version != "v1" || p.Digest != signed || p.Digest != manifestDigestOf(t, s, "bab", "km", "v1") { t.Fatalf("promote back to v1: %+v; want the entry's signed digest %s", p, signed) } } // digestChannelThenMarkerOff is stable with v1 promoted before the marker // (no digest), v2 promoted under it (digests), and then the marker removed: // emission suspended on a channel that already carries digests. func digestChannelThenMarkerOff(t *testing.T) (Store, []byte, func() *ChannelPointer) { t.Helper() s := NewDirStore(t.TempDir()) ctx := context.Background() priv, pub := testKeys(t) shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) shipRelease(t, s, "bab", "km", "v2", adoptPlatform, []byte("two")) if err := Promote(ctx, s, priv, "bab", "km", "stable", "v1"); err != nil { t.Fatal(err) } markerOn(t, s, priv, "bab") if err := Promote(ctx, s, priv, "bab", "km", "stable", "v2"); err != nil { t.Fatal(err) } if err := RemoveDigestMarker(ctx, s, "bab"); err != nil { t.Fatal(err) } if m, err := ReadDigestMarker(ctx, s, pub, "bab"); m != nil || err != nil { t.Fatalf("marker after removal = %+v, %v", m, err) } return s, priv, func() *ChannelPointer { return readPtr(t, s, pub, "stable") } } // Astra r1 #3, ruling P9 reversed: suspending emission does not make a // digest-less history entry a rollback target on a channel with digests. func TestRollbackOntoADigestlessEntryIsRefusedWithTheMarkerOff(t *testing.T) { s, priv, cur := digestChannelThenMarkerOff(t) before := cur() err := Rollback(context.Background(), s, priv, "bab", "km", "stable") var rn *ResignNeededError if !errors.As(err, &rn) || rn.Version != "v1" { t.Fatalf("marker off, rollback onto a digest-less v1 = %v; want ResignNeededError naming v1", err) } if after := cur(); after.Version != before.Version || !after.PromotedAt.Equal(before.PromotedAt) { t.Fatalf("the refused rollback changed the pointer: %+v -> %+v", before, after) } } // The approval-gated rollback runs as a hotfix promote of History[0]; with // the marker off it is refused the same way. func TestApprovalGatedRollbackOntoADigestlessEntryIsRefusedWithTheMarkerOff(t *testing.T) { s, priv, cur := digestChannelThenMarkerOff(t) before := cur() _, err := PromoteOnLadder(context.Background(), s, priv, "bab", "km", "stable", "v1", PromoteOptions{Hotfix: true}) var rn *ResignNeededError if !errors.As(err, &rn) || rn.Version != "v1" { t.Fatalf("marker off, hotfix promote of a digest-less v1 = %v; want ResignNeededError naming v1", err) } if after := cur(); after.Version != before.Version || !after.PromotedAt.Equal(before.PromotedAt) { t.Fatalf("the refused promote changed the pointer: %+v -> %+v", before, after) } } // With the marker off, a promote back onto a history entry whose signed // digest the bucket's bytes no longer match is still refused. func TestPromoteOfAReshippedHistoryEntryIsRefusedWithTheMarkerOff(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() priv, pub := testKeys(t) markerOn(t, s, priv, "bab") shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) shipRelease(t, s, "bab", "km", "v2", adoptPlatform, []byte("two")) for _, v := range []string{"v1", "v2"} { if err := Promote(ctx, s, priv, "bab", "km", "stable", v); err != nil { t.Fatal(err) } } if err := RemoveDigestMarker(ctx, s, "bab"); err != nil { t.Fatal(err) } reship(t, s, "bab", "km", "v1", []byte("not what was promoted")) if _, err := PromoteOnLadder(ctx, s, priv, "bab", "km", "stable", "v1", PromoteOptions{Hotfix: true}); !errors.Is(err, ErrManifestDigest) { t.Fatalf("marker off, promote of a re-shipped history entry = %v; want ErrManifestDigest", err) } if p := readPtr(t, s, pub, "stable"); p.Version != "v2" { t.Fatalf("the refused promote moved stable to %s", p.Version) } } // Only emission depends on the marker: with it off, a rollback onto an // entry that carries a digest succeeds and writes no digest fields at all. func TestRollbackOntoADigestEntryWithTheMarkerOffWritesNoDigests(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() priv, pub := testKeys(t) markerOn(t, s, priv, "bab") shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) shipRelease(t, s, "bab", "km", "v2", adoptPlatform, []byte("two")) for _, v := range []string{"v1", "v2"} { if err := Promote(ctx, s, priv, "bab", "km", "stable", v); err != nil { t.Fatal(err) } } if err := RemoveDigestMarker(ctx, s, "bab"); err != nil { t.Fatal(err) } if err := Rollback(ctx, s, priv, "bab", "km", "stable"); err != nil { t.Fatalf("marker off, rollback onto a digest-bearing v1: %v", err) } p := readPtr(t, s, pub, "stable") if p.Version != "v1" || p.Digest != "" || p.HistoryDigests != nil { t.Fatalf("after a marker-off rollback: %+v; want v1 with no digest fields", p) } raw, _, _ := s.Get(ctx, ChannelKey("bab", "km", "stable")) if strings.Contains(string(raw), "digest") { t.Fatal("the marker-off rollback's wrapper mentions digests") } } // Task 5's deferred minor: an unverifiable marker refuses rollback and // fast-track as it refuses promote, and the pointer is unchanged. func TestRollbackAndFastTrackRefuseAnUnverifiableMarker(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() priv, pub := testKeys(t) shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) shipRelease(t, s, "bab", "km", "v2", adoptPlatform, []byte("two")) for _, v := range []string{"v1", "v2"} { if err := Promote(ctx, s, priv, "bab", "km", "stable", v); err != nil { t.Fatal(err) } } other, _ := testKeys(t) markerOn(t, s, other, "bab") before := readPtr(t, s, pub, "stable") _, merr := ReadDigestMarker(ctx, s, pub, "bab") if merr == nil { t.Fatal("control: a foreign-key marker verified") } if err := Rollback(ctx, s, priv, "bab", "km", "stable"); err == nil || err.Error() != fmt.Sprintf(TextMarkerUnreadable, "rollback", "bab", merr) { t.Fatalf("Rollback with a foreign marker = %v; want the marker refusal", err) } if _, err := FastTrack(ctx, s, priv, "bab", "km", "stable", "ops@example.com"); err == nil || err.Error() != fmt.Sprintf(TextMarkerUnreadable, "fast-track", "bab", merr) { t.Fatalf("FastTrack with a foreign marker = %v; want the marker refusal", err) } if after := readPtr(t, s, pub, "stable"); after.Version != before.Version || after.FastTrack || !after.PromotedAt.Equal(before.PromotedAt) { t.Fatalf("a refused write changed the pointer: %+v -> %+v", before, after) } }