From 95a78db9ee8ce5bbc8f6ae0e7bcc8d72e9301577 Mon Sep 17 00:00:00 2001 From: Cristian Girlea Date: Wed, 23 Sep 2026 11:03:51 +0300 Subject: [PATCH 1/4] fix: improve logs when Ubuntu Pro credentials are not available When a release defines Ubuntu Pro archives and no credentials are available, Chisel logged an error-like message for every Pro archive ("Cannot open credentials directory ...") followed by one "Archive ... ignored: credentials not found" line per archive. Users not using Pro archives at all found this confusing. A missing credentials directory is now handled silently (other errors opening it are still logged), and the archives ignored because of missing credentials are reported in a single informative message: Ubuntu Pro subscription not available, ignoring archives: ubuntu-esm-apps, ubuntu-esm-infra When valid credentials are available the logs are unchanged. Fixes #207 Assisted-by: AI tools --- cmd/chisel/cmd_cut.go | 16 ++++- .../cmd_debug_check_release_archives.go | 4 +- .../cmd_debug_check_release_archives_test.go | 65 +++++++++++++++++++ internal/archive/credentials.go | 12 ++-- internal/archive/credentials_test.go | 9 +++ 5 files changed, 99 insertions(+), 7 deletions(-) diff --git a/cmd/chisel/cmd_cut.go b/cmd/chisel/cmd_cut.go index 35c81a79a..7c7ac8466 100644 --- a/cmd/chisel/cmd_cut.go +++ b/cmd/chisel/cmd_cut.go @@ -3,6 +3,8 @@ package main import ( "fmt" "slices" + "sort" + "strings" "time" "github.com/jessevdk/go-flags" @@ -79,6 +81,7 @@ func (cmd *cmdCut) Execute(args []string) error { } archives := make(map[string]archive.Archive) + var ignoredArchives []string for archiveName, archiveInfo := range release.Archives { openArchive, err := archive.Open(&archive.Options{ Label: archiveName, @@ -94,13 +97,14 @@ func (cmd *cmdCut) Execute(args []string) error { }) if err != nil { if err == archive.ErrCredentialsNotFound { - logf("Archive %q ignored: credentials not found", archiveName) + ignoredArchives = append(ignoredArchives, archiveName) continue } return err } archives[archiveName] = openArchive } + logIgnoredProArchives(ignoredArchives) hasMaintainedArchive := false for _, archive := range archives { @@ -128,3 +132,13 @@ func (cmd *cmdCut) Execute(args []string) error { }) return err } + +// logIgnoredProArchives logs a single message listing the Ubuntu Pro archives +// that were ignored because no credentials were found for them. +func logIgnoredProArchives(archiveNames []string) { + if len(archiveNames) == 0 { + return + } + sort.Strings(archiveNames) + logf("Ubuntu Pro subscription not available, ignoring archives: %s", strings.Join(archiveNames, ", ")) +} diff --git a/cmd/chisel/cmd_debug_check_release_archives.go b/cmd/chisel/cmd_debug_check_release_archives.go index c9c6270e4..3602d8138 100644 --- a/cmd/chisel/cmd_debug_check_release_archives.go +++ b/cmd/chisel/cmd_debug_check_release_archives.go @@ -63,6 +63,7 @@ func (cmd *cmdDebugCheckReleaseArchives) Execute(args []string) error { } archives := make(map[string]archive.Archive) + var ignoredArchives []string for archiveName, archiveInfo := range release.Archives { openArchive, err := archiveOpen(&archive.Options{ Label: archiveName, @@ -77,13 +78,14 @@ func (cmd *cmdDebugCheckReleaseArchives) Execute(args []string) error { OldRelease: archiveInfo.OldRelease, }) if err == archive.ErrCredentialsNotFound { - logf("Archive %q ignored: credentials not found\n", archiveName) + ignoredArchives = append(ignoredArchives, archiveName) continue } else if err != nil { return err } archives[archiveName] = openArchive } + logIgnoredProArchives(ignoredArchives) pathObs, err := computePathObservations(release, archives) if err != nil { diff --git a/cmd/chisel/cmd_debug_check_release_archives_test.go b/cmd/chisel/cmd_debug_check_release_archives_test.go index 0ca5afb0b..33036de8b 100644 --- a/cmd/chisel/cmd_debug_check_release_archives_test.go +++ b/cmd/chisel/cmd_debug_check_release_archives_test.go @@ -1,6 +1,8 @@ package main_test import ( + "bytes" + "log" "os" "path/filepath" "slices" @@ -541,6 +543,69 @@ func (s *ChiselSuite) TestRun(c *C) { // makeChiselYaml returns a valid chisel.yaml that contains the archives // supplied. +func (s *ChiselSuite) TestRunIgnoresProArchivesWithoutCredentials(c *C) { + var logBuf bytes.Buffer + chisel.SetLogger(log.New(&logBuf, "", 0)) + defer chisel.SetLogger(nil) + + // Build a chisel.yaml with a regular archive and an Ubuntu Pro archive. + chiselYaml := map[string]any{} + err := yaml.Unmarshal([]byte(testutil.Reindent(testutil.DefaultChiselYaml)), chiselYaml) + c.Assert(err, IsNil) + archivesYaml := chiselYaml["archives"].(map[string]any) + ubuntuArchive := archivesYaml["ubuntu"].(map[string]any) + ubuntuArchive["priority"] = 1 + proArchive := deepCopyYAML(ubuntuArchive) + proArchive["priority"] = 2 + proArchive["pro"] = "esm-apps" + archivesYaml["ubuntu-pro"] = proArchive + bs, err := yaml.Marshal(chiselYaml) + c.Assert(err, IsNil) + + releaseDir := c.MkDir() + release := map[string]string{ + "chisel.yaml": strings.ReplaceAll(string(bs), "T00:00:00Z", ""), + "slices/mydir/pkg-a.yaml": ` + package: pkg-a + slices: + myslice: + contents: + `, + } + for path, data := range release { + fpath := filepath.Join(releaseDir, path) + err := os.MkdirAll(filepath.Dir(fpath), 0755) + c.Assert(err, IsNil) + err = os.WriteFile(fpath, testutil.Reindent(data), 0644) + c.Assert(err, IsNil) + } + + restore := chisel.FakeArchiveOpen(func(options *archive.Options) (archive.Archive, error) { + if options.Pro != "" { + return nil, archive.ErrCredentialsNotFound + } + return &testutil.TestArchive{ + Opts: *options, + Packages: map[string]*testutil.TestPackage{ + "pkg-a": { + Name: "pkg-a", + Data: testutil.MustMakeDeb([]testutil.TarEntry{ + testutil.Dir(0755, "./dir/"), + }), + }, + }, + }, nil + }) + defer restore() + + _, err = chisel.Parser().ParseArgs([]string{"debug", "check-release-archives", "--release", releaseDir}) + c.Assert(err, IsNil) + + logs := logBuf.String() + c.Assert(logs, Matches, "(?s).*Ubuntu Pro subscription not available, ignoring archives: ubuntu-pro\n.*") + c.Assert(strings.Contains(logs, "credentials not found"), Equals, false) +} + func makeChiselYaml(archives []string) string { rawChiselYaml := testutil.Reindent(testutil.DefaultChiselYaml) diff --git a/internal/archive/credentials.go b/internal/archive/credentials.go index 1f8c32894..6d398a8b1 100644 --- a/internal/archive/credentials.go +++ b/internal/archive/credentials.go @@ -89,17 +89,19 @@ func findCredentials(repoURL string) (*credentials, error) { } // findCredentialsInDir searches for credentials for repoURL in configuration -// files in credsDir directory. If the directory does not exist, empty -// credentials structure with nil err is returned. +// files in credsDir directory. If the directory does not exist, +// ErrCredentialsNotFound is returned. // Only files that do not begin with dot and have either no or ".conf" // extension are searched. The files are searched in ascending lexicographic // order. The first file that contains machine declaration matching repoURL -// ends the search. If no file contain matching machine declaration, empty -// credentials structure with nil err is returned. +// ends the search. If no file contain matching machine declaration, +// ErrCredentialsNotFound is returned. func findCredentialsInDir(repoURL string, credsDir string) (*credentials, error) { contents, err := os.ReadDir(credsDir) if err != nil { - logf("Cannot open credentials directory %q: %v", credsDir, err) + if !os.IsNotExist(err) { + logf("Cannot open credentials directory %q: %v", credsDir, err) + } return nil, ErrCredentialsNotFound } diff --git a/internal/archive/credentials_test.go b/internal/archive/credentials_test.go index 4cbbb8731..39cd46935 100644 --- a/internal/archive/credentials_test.go +++ b/internal/archive/credentials_test.go @@ -1,6 +1,8 @@ package archive_test import ( + "bytes" + "log" "os" "path/filepath" @@ -252,12 +254,19 @@ func (s *S) TestFindCredentialsInDirMissingDir(c *C) { var creds *archive.Credentials var err error + var logBuf bytes.Buffer + archive.SetLogger(log.New(&logBuf, "", 0)) + defer archive.SetLogger(c) + workDir := c.MkDir() credsDir := filepath.Join(workDir, "auth.conf.d") creds, err = archive.FindCredentialsInDir("https://example.com/foo/bar", credsDir) c.Assert(err, ErrorMatches, "^credentials not found$") c.Assert(creds, IsNil) + // A missing credentials directory is expected without Ubuntu Pro + // credentials and must not be logged as if it were an error. + c.Assert(logBuf.String(), Equals, "") err = os.Mkdir(credsDir, 0755) c.Assert(err, IsNil) From e43929e63ad42aee540df7ac13bbd99d27fbe3f7 Mon Sep 17 00:00:00 2001 From: Cristian Girlea Date: Mon, 12 Oct 2026 20:03:57 +0300 Subject: [PATCH 2/4] fix: let the archive package say when Pro credentials are missing Only the archive package knows that credentials were looked up for an Ubuntu Pro archive, so candidateArchiveURLs now returns ErrProCredentialsNotFound in that case. The error wraps ErrCredentialsNotFound, and cut and debug check-release-archives check both with errors.Is: Pro archives are listed in a single line saying what was ignored and why, without claiming anything about the subscription itself, since credentials may exist for other Pro archives. An archive ignored for missing credentials for any other reason keeps its own log line. Also puts the makeChiselYaml doc comment back on that function. --- cmd/chisel/cmd_cut.go | 19 ++++++--- .../cmd_debug_check_release_archives.go | 11 ++++-- .../cmd_debug_check_release_archives_test.go | 39 +++++++++++++++---- internal/archive/archive.go | 5 ++- internal/archive/archive_test.go | 22 +++++++++++ internal/archive/credentials.go | 5 +++ 6 files changed, 82 insertions(+), 19 deletions(-) diff --git a/cmd/chisel/cmd_cut.go b/cmd/chisel/cmd_cut.go index 7c7ac8466..9cc8937c3 100644 --- a/cmd/chisel/cmd_cut.go +++ b/cmd/chisel/cmd_cut.go @@ -1,6 +1,7 @@ package main import ( + "errors" "fmt" "slices" "sort" @@ -81,7 +82,7 @@ func (cmd *cmdCut) Execute(args []string) error { } archives := make(map[string]archive.Archive) - var ignoredArchives []string + var ignoredProArchives []string for archiveName, archiveInfo := range release.Archives { openArchive, err := archive.Open(&archive.Options{ Label: archiveName, @@ -96,15 +97,19 @@ func (cmd *cmdCut) Execute(args []string) error { OldRelease: archiveInfo.OldRelease, }) if err != nil { - if err == archive.ErrCredentialsNotFound { - ignoredArchives = append(ignoredArchives, archiveName) + if errors.Is(err, archive.ErrProCredentialsNotFound) { + ignoredProArchives = append(ignoredProArchives, archiveName) + continue + } + if errors.Is(err, archive.ErrCredentialsNotFound) { + logf("Archive %q ignored: credentials not found", archiveName) continue } return err } archives[archiveName] = openArchive } - logIgnoredProArchives(ignoredArchives) + logIgnoredProArchives(ignoredProArchives) hasMaintainedArchive := false for _, archive := range archives { @@ -134,11 +139,13 @@ func (cmd *cmdCut) Execute(args []string) error { } // logIgnoredProArchives logs a single message listing the Ubuntu Pro archives -// that were ignored because no credentials were found for them. +// that were ignored because no credentials were found for them. It says +// nothing about the subscription itself: credentials may well exist for +// other Pro archives. func logIgnoredProArchives(archiveNames []string) { if len(archiveNames) == 0 { return } sort.Strings(archiveNames) - logf("Ubuntu Pro subscription not available, ignoring archives: %s", strings.Join(archiveNames, ", ")) + logf("Ignored Ubuntu Pro archives without credentials: %s", strings.Join(archiveNames, ", ")) } diff --git a/cmd/chisel/cmd_debug_check_release_archives.go b/cmd/chisel/cmd_debug_check_release_archives.go index 3602d8138..6ba60d9d2 100644 --- a/cmd/chisel/cmd_debug_check_release_archives.go +++ b/cmd/chisel/cmd_debug_check_release_archives.go @@ -63,7 +63,7 @@ func (cmd *cmdDebugCheckReleaseArchives) Execute(args []string) error { } archives := make(map[string]archive.Archive) - var ignoredArchives []string + var ignoredProArchives []string for archiveName, archiveInfo := range release.Archives { openArchive, err := archiveOpen(&archive.Options{ Label: archiveName, @@ -77,15 +77,18 @@ func (cmd *cmdDebugCheckReleaseArchives) Execute(args []string) error { Maintained: archiveInfo.Maintained, OldRelease: archiveInfo.OldRelease, }) - if err == archive.ErrCredentialsNotFound { - ignoredArchives = append(ignoredArchives, archiveName) + if errors.Is(err, archive.ErrProCredentialsNotFound) { + ignoredProArchives = append(ignoredProArchives, archiveName) + continue + } else if errors.Is(err, archive.ErrCredentialsNotFound) { + logf("Archive %q ignored: credentials not found\n", archiveName) continue } else if err != nil { return err } archives[archiveName] = openArchive } - logIgnoredProArchives(ignoredArchives) + logIgnoredProArchives(ignoredProArchives) pathObs, err := computePathObservations(release, archives) if err != nil { diff --git a/cmd/chisel/cmd_debug_check_release_archives_test.go b/cmd/chisel/cmd_debug_check_release_archives_test.go index 33036de8b..3a7fce120 100644 --- a/cmd/chisel/cmd_debug_check_release_archives_test.go +++ b/cmd/chisel/cmd_debug_check_release_archives_test.go @@ -541,8 +541,6 @@ func (s *ChiselSuite) TestRun(c *C) { } } -// makeChiselYaml returns a valid chisel.yaml that contains the archives -// supplied. func (s *ChiselSuite) TestRunIgnoresProArchivesWithoutCredentials(c *C) { var logBuf bytes.Buffer chisel.SetLogger(log.New(&logBuf, "", 0)) @@ -580,10 +578,7 @@ func (s *ChiselSuite) TestRunIgnoresProArchivesWithoutCredentials(c *C) { c.Assert(err, IsNil) } - restore := chisel.FakeArchiveOpen(func(options *archive.Options) (archive.Archive, error) { - if options.Pro != "" { - return nil, archive.ErrCredentialsNotFound - } + openUbuntu := func(options *archive.Options) (archive.Archive, error) { return &testutil.TestArchive{ Opts: *options, Packages: map[string]*testutil.TestPackage{ @@ -595,17 +590,45 @@ func (s *ChiselSuite) TestRunIgnoresProArchivesWithoutCredentials(c *C) { }, }, }, nil + } + restore := chisel.FakeArchiveOpen(func(options *archive.Options) (archive.Archive, error) { + if options.Pro != "" { + return nil, archive.ErrProCredentialsNotFound + } + return openUbuntu(options) }) defer restore() _, err = chisel.Parser().ParseArgs([]string{"debug", "check-release-archives", "--release", releaseDir}) c.Assert(err, IsNil) + // The Pro archives are listed in one line, without any claim about the + // subscription itself. logs := logBuf.String() - c.Assert(logs, Matches, "(?s).*Ubuntu Pro subscription not available, ignoring archives: ubuntu-pro\n.*") - c.Assert(strings.Contains(logs, "credentials not found"), Equals, false) + c.Assert(logs, Matches, "(?s).*Ignored Ubuntu Pro archives without credentials: ubuntu-pro\n.*") + c.Assert(strings.Contains(logs, "subscription"), Equals, false) + + // An archive ignored for a reason the command does not know about is + // still reported on its own. + logBuf.Reset() + restore = chisel.FakeArchiveOpen(func(options *archive.Options) (archive.Archive, error) { + if options.Pro != "" { + return nil, archive.ErrCredentialsNotFound + } + return openUbuntu(options) + }) + defer restore() + + _, err = chisel.Parser().ParseArgs([]string{"debug", "check-release-archives", "--release", releaseDir}) + c.Assert(err, IsNil) + + logs = logBuf.String() + c.Assert(logs, Matches, `(?s).*Archive "ubuntu-pro" ignored: credentials not found\n.*`) + c.Assert(strings.Contains(logs, "Ubuntu Pro"), Equals, false) } +// makeChiselYaml returns a valid chisel.yaml that contains the archives +// supplied. func makeChiselYaml(archives []string) string { rawChiselYaml := testutil.Reindent(testutil.DefaultChiselYaml) diff --git a/internal/archive/archive.go b/internal/archive/archive.go index e2cfa5e76..f62a6a6ab 100644 --- a/internal/archive/archive.go +++ b/internal/archive/archive.go @@ -2,6 +2,7 @@ package archive import ( "compress/gzip" + "errors" "fmt" "io" "net/http" @@ -255,7 +256,9 @@ func candidateArchiveURLs(pro, arch string, oldRelease bool) ([]string, *credent } url := archiveInfo.BaseURL creds, err := findCredentials(url) - if err != nil { + if errors.Is(err, ErrCredentialsNotFound) { + return nil, nil, ErrProCredentialsNotFound + } else if err != nil { return nil, nil, err } return []string{url}, creds, nil diff --git a/internal/archive/archive_test.go b/internal/archive/archive_test.go index 8e699eac6..2961e00aa 100644 --- a/internal/archive/archive_test.go +++ b/internal/archive/archive_test.go @@ -582,6 +582,28 @@ func (s *httpSuite) TestProArchives(c *C) { } } +func (s *httpSuite) TestProArchiveWithoutCredentials(c *C) { + restore := fakeEnv("CHISEL_AUTH_DIR", c.MkDir()) + defer restore() + + options := archive.Options{ + Label: "ubuntu", + Version: "20.04", + Arch: "amd64", + Suites: []string{"focal"}, + Components: []string{"main"}, + CacheDir: c.MkDir(), + Pro: archive.ProApps, + PubKeys: []*packet.PublicKey{s.pubKey}, + } + + _, err := archive.Open(&options) + c.Assert(err, ErrorMatches, "credentials not found for Ubuntu Pro archive") + c.Assert(errors.Is(err, archive.ErrProCredentialsNotFound), Equals, true) + // Callers that only know about the generic error keep working. + c.Assert(errors.Is(err, archive.ErrCredentialsNotFound), Equals, true) +} + func (s *httpSuite) TestOpenUnmaintainedArchives(c *C) { s.base = "http://old-releases.ubuntu.com/ubuntu/" s.prepareArchive("jammy", "22.04", "amd64", []string{"main", "universe"}) diff --git a/internal/archive/credentials.go b/internal/archive/credentials.go index 6d398a8b1..9b73d83ee 100644 --- a/internal/archive/credentials.go +++ b/internal/archive/credentials.go @@ -77,6 +77,11 @@ const defaultCredsDir = "/etc/apt/auth.conf.d" var ErrCredentialsNotFound = errors.New("credentials not found") +// ErrProCredentialsNotFound is returned when an Ubuntu Pro archive is requested +// but no credentials for it are found. It matches ErrCredentialsNotFound in +// errors.Is comparisons. +var ErrProCredentialsNotFound = fmt.Errorf("%w for Ubuntu Pro archive", ErrCredentialsNotFound) + // findCredentials searches credentials for repoURL in configuration files in // directory specified by CHISEL_AUTH_DIR environment variable if it's // non-empty, otherwise /etc/apt/auth.conf.d. From 3ce1467a3569db57e4394c970b5646075ad8f37d Mon Sep 17 00:00:00 2001 From: Cristian Girlea Date: Mon, 12 Oct 2026 20:05:53 +0300 Subject: [PATCH 3/4] fix: only stay quiet about the default credentials directory The previous commit skipped the "Cannot open credentials directory" log for any missing directory, so a mistyped CHISEL_AUTH_DIR left no lead at all. Now only the default /etc/apt/auth.conf.d is allowed to be missing silently, since that is the normal state of a system without Ubuntu Pro credentials. A directory set via CHISEL_AUTH_DIR, or any other problem reading the default one, is logged as before. --- internal/archive/credentials.go | 16 +++++++----- internal/archive/credentials_test.go | 39 +++++++++++++++++++++++++--- internal/archive/export_test.go | 8 ++++++ 3 files changed, 53 insertions(+), 10 deletions(-) diff --git a/internal/archive/credentials.go b/internal/archive/credentials.go index 9b73d83ee..1a4775134 100644 --- a/internal/archive/credentials.go +++ b/internal/archive/credentials.go @@ -73,7 +73,7 @@ func parseRepoURL(repoURL string) (creds *credentials, query *credentialsQuery, return } -const defaultCredsDir = "/etc/apt/auth.conf.d" +var defaultCredsDir = "/etc/apt/auth.conf.d" var ErrCredentialsNotFound = errors.New("credentials not found") @@ -84,18 +84,22 @@ var ErrProCredentialsNotFound = fmt.Errorf("%w for Ubuntu Pro archive", ErrCrede // findCredentials searches credentials for repoURL in configuration files in // directory specified by CHISEL_AUTH_DIR environment variable if it's -// non-empty, otherwise /etc/apt/auth.conf.d. +// non-empty, otherwise /etc/apt/auth.conf.d. A missing default directory is +// the normal state of a system without Ubuntu Pro credentials and is not +// logged; a directory set via CHISEL_AUTH_DIR that cannot be read is. func findCredentials(repoURL string) (*credentials, error) { credsDir := defaultCredsDir if v := os.Getenv("CHISEL_AUTH_DIR"); v != "" { credsDir = v + } else if _, err := os.Stat(credsDir); errors.Is(err, os.ErrNotExist) { + return nil, ErrCredentialsNotFound } return findCredentialsInDir(repoURL, credsDir) } // findCredentialsInDir searches for credentials for repoURL in configuration -// files in credsDir directory. If the directory does not exist, -// ErrCredentialsNotFound is returned. +// files in credsDir directory. If the directory cannot be read, the error is +// logged and ErrCredentialsNotFound is returned. // Only files that do not begin with dot and have either no or ".conf" // extension are searched. The files are searched in ascending lexicographic // order. The first file that contains machine declaration matching repoURL @@ -104,9 +108,7 @@ func findCredentials(repoURL string) (*credentials, error) { func findCredentialsInDir(repoURL string, credsDir string) (*credentials, error) { contents, err := os.ReadDir(credsDir) if err != nil { - if !os.IsNotExist(err) { - logf("Cannot open credentials directory %q: %v", credsDir, err) - } + logf("Cannot open credentials directory %q: %v", credsDir, err) return nil, ErrCredentialsNotFound } diff --git a/internal/archive/credentials_test.go b/internal/archive/credentials_test.go index 39cd46935..fb2a4a43f 100644 --- a/internal/archive/credentials_test.go +++ b/internal/archive/credentials_test.go @@ -264,9 +264,9 @@ func (s *S) TestFindCredentialsInDirMissingDir(c *C) { creds, err = archive.FindCredentialsInDir("https://example.com/foo/bar", credsDir) c.Assert(err, ErrorMatches, "^credentials not found$") c.Assert(creds, IsNil) - // A missing credentials directory is expected without Ubuntu Pro - // credentials and must not be logged as if it were an error. - c.Assert(logBuf.String(), Equals, "") + // A directory given explicitly is reported when it cannot be read, so a + // mistyped CHISEL_AUTH_DIR leaves a lead in the logs. + c.Assert(logBuf.String(), Matches, `(?s)Cannot open credentials directory ".*auth.conf.d": .*`) err = os.Mkdir(credsDir, 0755) c.Assert(err, IsNil) @@ -286,6 +286,34 @@ func (s *S) TestFindCredentialsInDirMissingDir(c *C) { c.Assert(creds.Password, Equals, "swordfish") } +func (s *S) TestFindCredentialsDefaultDirMissing(c *C) { + var logBuf bytes.Buffer + archive.SetLogger(log.New(&logBuf, "", 0)) + defer archive.SetLogger(c) + + restore := fakeEnv("CHISEL_AUTH_DIR", "") + defer restore() + credsDir := filepath.Join(c.MkDir(), "auth.conf.d") + restoreDir := archive.FakeDefaultCredsDir(credsDir) + defer restoreDir() + + // Without Ubuntu Pro credentials the default directory usually does not + // exist at all, which is not worth a log line. + creds, err := archive.FindCredentials("http://example.com/my/site") + c.Assert(err, ErrorMatches, "^credentials not found$") + c.Assert(creds, IsNil) + c.Assert(logBuf.String(), Equals, "") + + // Any other problem with the default directory is still reported. + err = os.WriteFile(credsDir, []byte("not a directory"), 0600) + c.Assert(err, IsNil) + + creds, err = archive.FindCredentials("http://example.com/my/site") + c.Assert(err, ErrorMatches, "^credentials not found$") + c.Assert(creds, IsNil) + c.Assert(logBuf.String(), Matches, `(?s)Cannot open credentials directory ".*auth.conf.d": .*`) +} + func fakeEnv(name, value string) (restore func()) { origValue, origSet := os.LookupEnv(name) os.Setenv(name, value) @@ -308,9 +336,14 @@ func (s *S) TestFindCredentials(c *C) { restore := fakeEnv("CHISEL_AUTH_DIR", credsDir) defer restore() + var logBuf bytes.Buffer + archive.SetLogger(log.New(&logBuf, "", 0)) + defer archive.SetLogger(c) + creds, err = archive.FindCredentials("http://example.com/my/site") c.Assert(err, ErrorMatches, "^credentials not found$") c.Assert(creds, IsNil) + c.Assert(logBuf.String(), Matches, `(?s)Cannot open credentials directory ".*auth.conf.d": .*`) err = os.Mkdir(credsDir, 0755) c.Assert(err, IsNil) diff --git a/internal/archive/export_test.go b/internal/archive/export_test.go index 3569a6995..49d7350fe 100644 --- a/internal/archive/export_test.go +++ b/internal/archive/export_test.go @@ -20,4 +20,12 @@ type Credentials = credentials var FindCredentials = findCredentials var FindCredentialsInDir = findCredentialsInDir +func FakeDefaultCredsDir(dir string) (restore func()) { + _defaultCredsDir := defaultCredsDir + defaultCredsDir = dir + return func() { + defaultCredsDir = _defaultCredsDir + } +} + var ProArchiveInfo = proArchiveInfo From 6af211728febc2b1e7496c3c60e7ac8ea1563f09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cristian=20G=C3=AErlea?= Date: Thu, 15 Oct 2026 15:25:44 +0300 Subject: [PATCH 4/4] fix: tell Pro archives apart in the commands, not with a Pro error The commands already know which archives are Ubuntu Pro ones, so they can group the Pro archives whose credentials are not found without a Pro-specific credentials error. Drop ErrProCredentialsNotFound and its test, branch on the archive's Pro setting instead, and word the message like the one for other archives. Also correct a test comment that tied a missing /etc/apt/auth.conf.d to Ubuntu Pro: the directory comes with apt. --- cmd/chisel/cmd_cut.go | 8 ++----- .../cmd_debug_check_release_archives.go | 5 +++-- .../cmd_debug_check_release_archives_test.go | 15 +++++-------- internal/archive/archive.go | 5 +---- internal/archive/archive_test.go | 22 ------------------- internal/archive/credentials.go | 5 ----- internal/archive/credentials_test.go | 5 +---- 7 files changed, 13 insertions(+), 52 deletions(-) diff --git a/cmd/chisel/cmd_cut.go b/cmd/chisel/cmd_cut.go index 9cc8937c3..8643d7274 100644 --- a/cmd/chisel/cmd_cut.go +++ b/cmd/chisel/cmd_cut.go @@ -97,7 +97,7 @@ func (cmd *cmdCut) Execute(args []string) error { OldRelease: archiveInfo.OldRelease, }) if err != nil { - if errors.Is(err, archive.ErrProCredentialsNotFound) { + if archiveInfo.Pro != "" && errors.Is(err, archive.ErrCredentialsNotFound) { ignoredProArchives = append(ignoredProArchives, archiveName) continue } @@ -138,14 +138,10 @@ func (cmd *cmdCut) Execute(args []string) error { return err } -// logIgnoredProArchives logs a single message listing the Ubuntu Pro archives -// that were ignored because no credentials were found for them. It says -// nothing about the subscription itself: credentials may well exist for -// other Pro archives. func logIgnoredProArchives(archiveNames []string) { if len(archiveNames) == 0 { return } sort.Strings(archiveNames) - logf("Ignored Ubuntu Pro archives without credentials: %s", strings.Join(archiveNames, ", ")) + logf("Ubuntu Pro archives without credentials ignored: %s", strings.Join(archiveNames, ", ")) } diff --git a/cmd/chisel/cmd_debug_check_release_archives.go b/cmd/chisel/cmd_debug_check_release_archives.go index 6ba60d9d2..1cc8a820a 100644 --- a/cmd/chisel/cmd_debug_check_release_archives.go +++ b/cmd/chisel/cmd_debug_check_release_archives.go @@ -77,10 +77,11 @@ func (cmd *cmdDebugCheckReleaseArchives) Execute(args []string) error { Maintained: archiveInfo.Maintained, OldRelease: archiveInfo.OldRelease, }) - if errors.Is(err, archive.ErrProCredentialsNotFound) { + if archiveInfo.Pro != "" && errors.Is(err, archive.ErrCredentialsNotFound) { ignoredProArchives = append(ignoredProArchives, archiveName) continue - } else if errors.Is(err, archive.ErrCredentialsNotFound) { + } + if errors.Is(err, archive.ErrCredentialsNotFound) { logf("Archive %q ignored: credentials not found\n", archiveName) continue } else if err != nil { diff --git a/cmd/chisel/cmd_debug_check_release_archives_test.go b/cmd/chisel/cmd_debug_check_release_archives_test.go index 3a7fce120..e3dc0c12c 100644 --- a/cmd/chisel/cmd_debug_check_release_archives_test.go +++ b/cmd/chisel/cmd_debug_check_release_archives_test.go @@ -593,7 +593,7 @@ func (s *ChiselSuite) TestRunIgnoresProArchivesWithoutCredentials(c *C) { } restore := chisel.FakeArchiveOpen(func(options *archive.Options) (archive.Archive, error) { if options.Pro != "" { - return nil, archive.ErrProCredentialsNotFound + return nil, archive.ErrCredentialsNotFound } return openUbuntu(options) }) @@ -602,17 +602,14 @@ func (s *ChiselSuite) TestRunIgnoresProArchivesWithoutCredentials(c *C) { _, err = chisel.Parser().ParseArgs([]string{"debug", "check-release-archives", "--release", releaseDir}) c.Assert(err, IsNil) - // The Pro archives are listed in one line, without any claim about the - // subscription itself. logs := logBuf.String() - c.Assert(logs, Matches, "(?s).*Ignored Ubuntu Pro archives without credentials: ubuntu-pro\n.*") - c.Assert(strings.Contains(logs, "subscription"), Equals, false) + c.Assert(logs, Matches, "(?s).*Ubuntu Pro archives without credentials ignored: ubuntu-pro\n.*") + c.Assert(strings.Contains(logs, `Archive "ubuntu-pro" ignored`), Equals, false) - // An archive ignored for a reason the command does not know about is - // still reported on its own. + // A non-Pro archive without credentials keeps its own message. logBuf.Reset() restore = chisel.FakeArchiveOpen(func(options *archive.Options) (archive.Archive, error) { - if options.Pro != "" { + if options.Pro == "" { return nil, archive.ErrCredentialsNotFound } return openUbuntu(options) @@ -623,7 +620,7 @@ func (s *ChiselSuite) TestRunIgnoresProArchivesWithoutCredentials(c *C) { c.Assert(err, IsNil) logs = logBuf.String() - c.Assert(logs, Matches, `(?s).*Archive "ubuntu-pro" ignored: credentials not found\n.*`) + c.Assert(logs, Matches, `(?s).*Archive "ubuntu" ignored: credentials not found\n.*`) c.Assert(strings.Contains(logs, "Ubuntu Pro"), Equals, false) } diff --git a/internal/archive/archive.go b/internal/archive/archive.go index f62a6a6ab..e2cfa5e76 100644 --- a/internal/archive/archive.go +++ b/internal/archive/archive.go @@ -2,7 +2,6 @@ package archive import ( "compress/gzip" - "errors" "fmt" "io" "net/http" @@ -256,9 +255,7 @@ func candidateArchiveURLs(pro, arch string, oldRelease bool) ([]string, *credent } url := archiveInfo.BaseURL creds, err := findCredentials(url) - if errors.Is(err, ErrCredentialsNotFound) { - return nil, nil, ErrProCredentialsNotFound - } else if err != nil { + if err != nil { return nil, nil, err } return []string{url}, creds, nil diff --git a/internal/archive/archive_test.go b/internal/archive/archive_test.go index 2961e00aa..8e699eac6 100644 --- a/internal/archive/archive_test.go +++ b/internal/archive/archive_test.go @@ -582,28 +582,6 @@ func (s *httpSuite) TestProArchives(c *C) { } } -func (s *httpSuite) TestProArchiveWithoutCredentials(c *C) { - restore := fakeEnv("CHISEL_AUTH_DIR", c.MkDir()) - defer restore() - - options := archive.Options{ - Label: "ubuntu", - Version: "20.04", - Arch: "amd64", - Suites: []string{"focal"}, - Components: []string{"main"}, - CacheDir: c.MkDir(), - Pro: archive.ProApps, - PubKeys: []*packet.PublicKey{s.pubKey}, - } - - _, err := archive.Open(&options) - c.Assert(err, ErrorMatches, "credentials not found for Ubuntu Pro archive") - c.Assert(errors.Is(err, archive.ErrProCredentialsNotFound), Equals, true) - // Callers that only know about the generic error keep working. - c.Assert(errors.Is(err, archive.ErrCredentialsNotFound), Equals, true) -} - func (s *httpSuite) TestOpenUnmaintainedArchives(c *C) { s.base = "http://old-releases.ubuntu.com/ubuntu/" s.prepareArchive("jammy", "22.04", "amd64", []string{"main", "universe"}) diff --git a/internal/archive/credentials.go b/internal/archive/credentials.go index 1a4775134..a3200ccf3 100644 --- a/internal/archive/credentials.go +++ b/internal/archive/credentials.go @@ -77,11 +77,6 @@ var defaultCredsDir = "/etc/apt/auth.conf.d" var ErrCredentialsNotFound = errors.New("credentials not found") -// ErrProCredentialsNotFound is returned when an Ubuntu Pro archive is requested -// but no credentials for it are found. It matches ErrCredentialsNotFound in -// errors.Is comparisons. -var ErrProCredentialsNotFound = fmt.Errorf("%w for Ubuntu Pro archive", ErrCredentialsNotFound) - // findCredentials searches credentials for repoURL in configuration files in // directory specified by CHISEL_AUTH_DIR environment variable if it's // non-empty, otherwise /etc/apt/auth.conf.d. A missing default directory is diff --git a/internal/archive/credentials_test.go b/internal/archive/credentials_test.go index fb2a4a43f..f0ba30edd 100644 --- a/internal/archive/credentials_test.go +++ b/internal/archive/credentials_test.go @@ -264,8 +264,6 @@ func (s *S) TestFindCredentialsInDirMissingDir(c *C) { creds, err = archive.FindCredentialsInDir("https://example.com/foo/bar", credsDir) c.Assert(err, ErrorMatches, "^credentials not found$") c.Assert(creds, IsNil) - // A directory given explicitly is reported when it cannot be read, so a - // mistyped CHISEL_AUTH_DIR leaves a lead in the logs. c.Assert(logBuf.String(), Matches, `(?s)Cannot open credentials directory ".*auth.conf.d": .*`) err = os.Mkdir(credsDir, 0755) @@ -297,8 +295,7 @@ func (s *S) TestFindCredentialsDefaultDirMissing(c *C) { restoreDir := archive.FakeDefaultCredsDir(credsDir) defer restoreDir() - // Without Ubuntu Pro credentials the default directory usually does not - // exist at all, which is not worth a log line. + // The default directory comes with apt, so it not existing is not logged. creds, err := archive.FindCredentials("http://example.com/my/site") c.Assert(err, ErrorMatches, "^credentials not found$") c.Assert(creds, IsNil)