From 0a98d9fdc3dbe3c25b29e147d88279d2eff4453f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Roth?= Date: Mon, 8 Jun 2026 13:44:03 +0200 Subject: [PATCH] snapshots: fix snapshot of snapshot MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### api/snapshot.go — fix apiSnapshotsCreate The function was building the new snapshot's ref list only from b.PackageRefs, completely ignoring SourceSnapshots package contents (they were stored as provenance metadata only). The fix mirrors the approach from apiSnapshotsMerge: 1. Start with source snapshots: merge all freshSources[i].RefList() together using Merge(overrideMatching=true) 2. Layer explicit PackageRefs on top: only enter the package-loading loop if b.PackageRefs is non-empty, then merge the result into refList 3. Pass the combined refList to NewSnapshotFromRefList This means an empty snapshot (SourceSnapshots: [], PackageRefs: []) still correctly produces an empty ref list, single-source and multi-source snapshot-of-snapshot cases are now handled, and PackageRefs can still augment or override on top of the merged sources. ### system/t12_api/publish.py — fix PublishSwitchAPITestSnapshot - Removed the # FIXME comment - Changed check_not_exists → check_exists for pyspi-0.6.1-1.3.stripped.dsc after the publish-switch to snapshot2, which is now the correct expectation since snapshot2 inherits all packages from snapshot1 --- api/snapshot.go | 43 +++++++++++++++++++++++++-------------- system/t12_api/publish.py | 5 ++--- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/api/snapshot.go b/api/snapshot.go index a9061eb1..3256242d 100644 --- a/api/snapshot.go +++ b/api/snapshot.go @@ -209,24 +209,37 @@ func apiSnapshotsCreate(c *gin.Context) { } } - list := deb.NewPackageList() - - // verify package refs and build package list using fresh factory - for _, ref := range b.PackageRefs { - p, err := taskPackageCollection.ByKey([]byte(ref)) - if err != nil { - if err == database.ErrNotFound { - return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("package %s: %s", ref, err) - } - return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err - } - err = list.Add(p) - if err != nil { - return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err + // Merge packages from all source snapshots + var refList *deb.PackageRefList + if len(freshSources) > 0 { + refList = freshSources[0].RefList() + for i := 1; i < len(freshSources); i++ { + refList = refList.Merge(freshSources[i].RefList(), true, false) } + } else { + refList = deb.NewPackageRefList() } - snapshot = deb.NewSnapshotFromRefList(b.Name, freshSources, deb.NewPackageRefListFromPackageList(list), b.Description) + // Add any explicitly specified package refs on top + if len(b.PackageRefs) > 0 { + list := deb.NewPackageList() + for _, ref := range b.PackageRefs { + p, err := taskPackageCollection.ByKey([]byte(ref)) + if err != nil { + if err == database.ErrNotFound { + return &task.ProcessReturnValue{Code: http.StatusNotFound, Value: nil}, fmt.Errorf("package %s: %s", ref, err) + } + return &task.ProcessReturnValue{Code: http.StatusInternalServerError, Value: nil}, err + } + err = list.Add(p) + if err != nil { + return &task.ProcessReturnValue{Code: http.StatusBadRequest, Value: nil}, err + } + } + refList = refList.Merge(deb.NewPackageRefListFromPackageList(list), true, false) + } + + snapshot = deb.NewSnapshotFromRefList(b.Name, freshSources, refList, b.Description) err = taskSnapshotCollection.Add(snapshot) if err != nil { diff --git a/system/t12_api/publish.py b/system/t12_api/publish.py index a9d5cf0d..cd92f775 100644 --- a/system/t12_api/publish.py +++ b/system/t12_api/publish.py @@ -1207,11 +1207,10 @@ class PublishSwitchAPITestSnapshot(APITest): self.check_equal(all_repos.status_code, 200) self.check_in(repo_expected, all_repos.json()) - # FIXME: what should exist here ? publish snapshot of snapshot self.check_not_exists( "public/" + prefix + "/pool/main/b/boost-defaults/libboost-program-options-dev_1.49.0.1_i386.deb") - self.check_not_exists("public/" + prefix + - "/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc") + self.check_exists("public/" + prefix + + "/pool/main/p/pyspi/pyspi-0.6.1-1.3.stripped.dsc") task = self.delete_task("/api/publish/" + prefix + "/wheezy") self.check_task(task)