Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion cmd/chisel/cmd_cut.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package main

import (
"errors"
"fmt"
"slices"
"sort"
"strings"
"time"

"github.com/jessevdk/go-flags"
Expand Down Expand Up @@ -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,
Expand All @@ -93,14 +97,19 @@ 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
}
return err
}
archives[archiveName] = openArchive
}
logIgnoredProArchives(ignoredProArchives)

hasMaintainedArchive := false
for _, archive := range archives {
Expand Down Expand Up @@ -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, ", "))
}
8 changes: 7 additions & 1 deletion cmd/chisel/cmd_debug_check_release_archives.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -76,14 +77,19 @@ 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 {
return err
}
archives[archiveName] = openArchive
}
logIgnoredProArchives(ignoredProArchives)

pathObs, err := computePathObservations(release, archives)
if err != nil {
Expand Down
85 changes: 85 additions & 0 deletions cmd/chisel/cmd_debug_check_release_archives_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package main_test

import (
"bytes"
"log"
"os"
"path/filepath"
"slices"
Expand Down Expand Up @@ -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 {
Expand Down
16 changes: 10 additions & 6 deletions internal/archive/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
39 changes: 39 additions & 0 deletions internal/archive/credentials_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package archive_test

import (
"bytes"
"log"
"os"
"path/filepath"

Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions internal/archive/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading