diff --git a/cmd/chisel/cmd_cut.go b/cmd/chisel/cmd_cut.go index 35c81a79..8643d727 100644 --- a/cmd/chisel/cmd_cut.go +++ b/cmd/chisel/cmd_cut.go @@ -1,8 +1,11 @@ package main import ( + "errors" "fmt" "slices" + "sort" + "strings" "time" "github.com/jessevdk/go-flags" @@ -79,6 +82,7 @@ func (cmd *cmdCut) Execute(args []string) error { } archives := make(map[string]archive.Archive) + var ignoredProArchives []string for archiveName, archiveInfo := range release.Archives { openArchive, err := archive.Open(&archive.Options{ Label: archiveName, @@ -93,7 +97,11 @@ func (cmd *cmdCut) Execute(args []string) error { OldRelease: archiveInfo.OldRelease, }) if err != nil { - if err == archive.ErrCredentialsNotFound { + if archiveInfo.Pro != "" && errors.Is(err, archive.ErrCredentialsNotFound) { + ignoredProArchives = append(ignoredProArchives, archiveName) + continue + } + if errors.Is(err, archive.ErrCredentialsNotFound) { logf("Archive %q ignored: credentials not found", archiveName) continue } @@ -101,6 +109,7 @@ func (cmd *cmdCut) Execute(args []string) error { } archives[archiveName] = openArchive } + logIgnoredProArchives(ignoredProArchives) hasMaintainedArchive := false for _, archive := range archives { @@ -128,3 +137,11 @@ func (cmd *cmdCut) Execute(args []string) error { }) return err } + +func logIgnoredProArchives(archiveNames []string) { + if len(archiveNames) == 0 { + return + } + sort.Strings(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 c9c6270e..1cc8a820 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 ignoredProArchives []string for archiveName, archiveInfo := range release.Archives { openArchive, err := archiveOpen(&archive.Options{ Label: archiveName, @@ -76,7 +77,11 @@ func (cmd *cmdDebugCheckReleaseArchives) Execute(args []string) error { Maintained: archiveInfo.Maintained, OldRelease: archiveInfo.OldRelease, }) - if err == archive.ErrCredentialsNotFound { + if archiveInfo.Pro != "" && errors.Is(err, archive.ErrCredentialsNotFound) { + ignoredProArchives = append(ignoredProArchives, archiveName) + continue + } + if errors.Is(err, archive.ErrCredentialsNotFound) { logf("Archive %q ignored: credentials not found\n", archiveName) continue } else if err != nil { @@ -84,6 +89,7 @@ func (cmd *cmdDebugCheckReleaseArchives) Execute(args []string) error { } archives[archiveName] = openArchive } + 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 0ca5afb0..e3dc0c12 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" @@ -539,6 +541,89 @@ func (s *ChiselSuite) TestRun(c *C) { } } +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) + } + + openUbuntu := func(options *archive.Options) (archive.Archive, error) { + 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 + } + 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).*Ubuntu Pro archives without credentials ignored: ubuntu-pro\n.*") + c.Assert(strings.Contains(logs, `Archive "ubuntu-pro" ignored`), Equals, false) + + // 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 == "" { + 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" 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 { diff --git a/internal/archive/credentials.go b/internal/archive/credentials.go index 1f8c3289..a3200ccf 100644 --- a/internal/archive/credentials.go +++ b/internal/archive/credentials.go @@ -73,29 +73,33 @@ 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") // 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, empty -// credentials structure with nil err 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 -// 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 { diff --git a/internal/archive/credentials_test.go b/internal/archive/credentials_test.go index 4cbbb873..f0ba30ed 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,17 @@ 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) + c.Assert(logBuf.String(), Matches, `(?s)Cannot open credentials directory ".*auth.conf.d": .*`) err = os.Mkdir(credsDir, 0755) c.Assert(err, IsNil) @@ -277,6 +284,33 @@ 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() + + // 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) + 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) @@ -299,9 +333,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 3569a699..49d7350f 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