From 4c58bd51b85bd8f8f81ac62580c7feca06e1065c Mon Sep 17 00:00:00 2001 From: Paul Mars Date: Wed, 22 Jul 2026 11:18:47 +0200 Subject: [PATCH] fix: fallbacks on current archive if old release not moved yet Signed-off-by: Paul Mars --- internal/archive/archive.go | 47 +++++++++++++++++++++---------- internal/archive/archive_test.go | 48 ++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 14 deletions(-) diff --git a/internal/archive/archive.go b/internal/archive/archive.go index 7c90a41dd..2fa755115 100644 --- a/internal/archive/archive.go +++ b/internal/archive/archive.go @@ -189,28 +189,31 @@ var proArchiveInfo = map[string]struct { }, } -func archiveURL(pro, arch string, oldRelease bool) (string, *credentials, error) { +func archiveURL(pro, arch string, oldRelease bool) (string, string, *credentials, error) { if pro != "" { archiveInfo, ok := proArchiveInfo[pro] if !ok { - return "", nil, fmt.Errorf("invalid pro value: %q", pro) + return "", "", nil, fmt.Errorf("invalid pro value: %q", pro) } url := archiveInfo.BaseURL creds, err := findCredentials(url) if err != nil { - return "", nil, err + return "", "", nil, err } - return url, creds, nil + return url, "", creds, nil } - if oldRelease { - return ubuntuOldReleasesURL, nil, nil + current := ubuntuURL + if arch != "amd64" && arch != "i386" { + current = ubuntuPortsURL } - - if arch == "amd64" || arch == "i386" { - return ubuntuURL, nil, nil + if oldRelease { + // The old release may not have moved from archive.ubuntu.com + // to old-releases.ubuntu.com yet. So return the current + // archive as a fallback. + return ubuntuOldReleasesURL, current, nil, nil } - return ubuntuPortsURL, nil, nil + return current, "", nil, nil } func openUbuntu(options *Options) (Archive, error) { @@ -224,21 +227,37 @@ func openUbuntu(options *Options) (Archive, error) { return nil, fmt.Errorf("archive options missing version") } - baseURL, creds, err := archiveURL(options.Pro, options.Arch, options.OldRelease) + baseURL, fallbackURL, creds, err := archiveURL(options.Pro, options.Arch, options.OldRelease) if err != nil { return nil, err } archive := &ubuntuArchive{ options: *options, - cache: &cache.Cache{ - Dir: options.CacheDir, - }, + cache: &cache.Cache{Dir: options.CacheDir}, pubKeys: options.PubKeys, baseURL: baseURL, creds: creds, } + if fallbackURL != "" { + // Check if the release is in the expected archive, use the + // fallback one if not. + probe := &ubuntuIndex{ + label: options.Label, + version: options.Version, + arch: options.Arch, + suite: options.Suites[0], + archive: archive, + } + _, err := probe.fetch(probe.distPath("InRelease"), "", fetchDefault) + if err == errNotFound { + archive.baseURL = fallbackURL + } else if err != nil { + return nil, err + } + } + for _, suite := range options.Suites { var release control.Section for _, component := range options.Components { diff --git a/internal/archive/archive_test.go b/internal/archive/archive_test.go index 90e3f0047..a59825da0 100644 --- a/internal/archive/archive_test.go +++ b/internal/archive/archive_test.go @@ -536,6 +536,54 @@ func (s *httpSuite) TestOpenUnmaintainedArchives(c *C) { c.Assert(err, IsNil) } +// TestOpenOldReleaseFallback verifies that when OldRelease is set and the +// old-releases mirror returns 404 (because the release has not yet been +// physically moved), Chisel uses the current archive. +func (s *httpSuite) TestOpenOldReleaseFallback(c *C) { + s.prepareArchive("plucky", "25.04", "amd64", []string{"main"}) + + // Override Do: return 404 for old-releases, delegate to s.Do (which + // serves the prepared content) for the current archive. + s.restore() + s.restore = archive.FakeDo(func(req *http.Request) (*http.Response, error) { + if strings.HasPrefix(req.URL.String(), "http://old-releases.ubuntu.com/ubuntu/") { + s.requestResults = append(s.requestResults, requestResult{path: req.URL.Path, status: 404}) + return &http.Response{ + Body: io.NopCloser(strings.NewReader("")), + StatusCode: 404, + }, nil + } + return s.Do(req) + }) + + options := archive.Options{ + Label: "ubuntu", + Version: "25.04", + Arch: "amd64", + Suites: []string{"plucky"}, + Components: []string{"main"}, + CacheDir: c.MkDir(), + PubKeys: []*packet.PublicKey{s.pubKey}, + OldRelease: true, + } + + testArchive, err := archive.Open(&options) + c.Assert(err, IsNil) + + _, _, err = testArchive.Fetch("mypkg1") + c.Assert(err, IsNil) + + // Exactly one 404 (the probe to old-releases); all subsequent + // requests must be served by the current archive. + oldReleasesHits := 0 + for _, r := range s.requestResults { + if r.status == 404 { + oldReleasesHits++ + } + } + c.Assert(oldReleasesHits, Equals, 1) +} + type verifyArchiveReleaseTest struct { summary string pubKeys []*packet.PublicKey