diff --git a/internal/archive/archive.go b/internal/archive/archive.go index 57f1529d..48777606 100644 --- a/internal/archive/archive.go +++ b/internal/archive/archive.go @@ -13,9 +13,17 @@ import ( "github.com/canonical/chisel/internal/deb" ) +type PackageInfo interface { + Name() string + Version() string + Arch() string + SHA256() string +} + type Archive interface { Options() *Options Fetch(pkg string) (io.ReadCloser, error) + Info(pkg string) PackageInfo Exists(pkg string) bool } @@ -79,6 +87,23 @@ func (a *ubuntuArchive) Exists(pkg string) bool { return err == nil } +type pkgInfo struct{ control.Section } + +var _ PackageInfo = pkgInfo{} + +func (info pkgInfo) Name() string { return info.Get("Package") } +func (info pkgInfo) Version() string { return info.Get("Version") } +func (info pkgInfo) Arch() string { return info.Get("Architecture") } +func (info pkgInfo) SHA256() string { return info.Get("SHA256") } + +func (a *ubuntuArchive) Info(pkg string) (info PackageInfo) { + section, _, _ := a.selectPackage(pkg) + if section != nil { + info = &pkgInfo{section} + } + return +} + func (a *ubuntuArchive) selectPackage(pkg string) (control.Section, *ubuntuIndex, error) { var selectedVersion string var selectedSection control.Section diff --git a/internal/archive/archive_test.go b/internal/archive/archive_test.go index 108bec2c..46e46f34 100644 --- a/internal/archive/archive_test.go +++ b/internal/archive/archive_test.go @@ -260,6 +260,39 @@ func (s *httpSuite) TestFetchSecurityPackage(c *C) { c.Assert(read(pkg), Equals, "mypkg2 1.2 data") } +func (s *httpSuite) TestPackageInfo(c *C) { + s.prepareArchive("jammy", "22.04", "amd64", []string{"main", "universe"}) + + options := archive.Options{ + Label: "ubuntu", + Version: "22.04", + Arch: "amd64", + Suites: []string{"jammy"}, + Components: []string{"main", "universe"}, + CacheDir: c.MkDir(), + } + + archive, err := archive.Open(&options) + c.Assert(err, IsNil) + + info1 := archive.Info("mypkg1") + c.Assert(info1, NotNil) + c.Assert(info1.Name(), Equals, "mypkg1") + c.Assert(info1.Version(), Equals, "1.1") + c.Assert(info1.Arch(), Equals, "amd64") + c.Assert(info1.SHA256(), Equals, "1f08ef04cfe7a8087ee38a1ea35fa1810246648136c3c42d5a61ad6503d85e05") + + info3 := archive.Info("mypkg3") + c.Assert(info3, NotNil) + c.Assert(info3.Name(), Equals, "mypkg3") + c.Assert(info3.Version(), Equals, "1.3") + c.Assert(info3.Arch(), Equals, "amd64") + c.Assert(info3.SHA256(), Equals, "fe377bf13ba1a5cb287cb4e037e6e7321281c929405ae39a72358ef0f5d179aa") + + info99 := archive.Info("mypkg99") + c.Assert(info99, IsNil) +} + func read(r io.Reader) string { data, err := ioutil.ReadAll(r) if err != nil { diff --git a/internal/slicer/slicer_test.go b/internal/slicer/slicer_test.go index 5082313c..ab338f8f 100644 --- a/internal/slicer/slicer_test.go +++ b/internal/slicer/slicer_test.go @@ -17,9 +17,21 @@ import ( "github.com/canonical/chisel/internal/testutil" ) +var ( + REG = testutil.REG + DIR = testutil.DIR + LNK = testutil.LNK +) + +type testPackage struct { + info map[string]string + content []byte +} + type slicerTest struct { summary string arch string + pkgs map[string]map[string]testPackage release map[string]string slices []setup.SliceKey hackopt func(c *C, opts *slicer.RunOptions) @@ -487,6 +499,106 @@ var slicerTests = []slicerTest{{ `, }, error: `slice base-files_myslice: content is not a file: /x/y`, +}, { + summary: "Non-default archive", + slices: []setup.SliceKey{{"base-files", "myslice"}}, + release: map[string]string{ + "chisel.yaml": ` + format: chisel-v1 + archives: + foo: + version: 22.04 + components: [main, universe] + default: true + bar: + version: 22.04 + components: [main] + `, + "slices/mydir/base-files.yaml": ` + package: base-files + archive: bar + slices: + myslice: + contents: + /usr/bin/hello: + `, + }, + result: map[string]string{ + "/usr/": "dir 0755", + "/usr/bin/": "dir 0755", + "/usr/bin/hello": "file 0775 eaf29575", + }, +}, { + summary: "Custom archives with custom packages", + pkgs: map[string]map[string]testPackage{ + "leptons": { + "electron": testPackage{ + content: testutil.MakeTestDeb([]testutil.TarEntry{ + DIR(0755, "./"), + DIR(0755, "./mass/"), + REG(0644, "./mass/electron", "9.1093837015E−31 kg\n"), + DIR(0755, "./usr/"), + DIR(0755, "./usr/share/"), + DIR(0755, "./usr/share/doc/"), + DIR(0755, "./usr/share/doc/electron/"), + REG(0644, "./usr/share/doc/electron/copyright", ""), + }), + }, + }, + "hadrons": { + "proton": testPackage{ + content: testutil.MakeTestDeb([]testutil.TarEntry{ + DIR(0755, "./"), + DIR(0755, "./mass/"), + REG(0644, "./mass/proton", "1.67262192369E−27 kg\n"), + }), + }, + }, + }, + release: map[string]string{ + "chisel.yaml": ` + format: chisel-v1 + archives: + leptons: + version: 1 + suites: [main] + components: [main, universe] + default: true + hadrons: + version: 1 + suites: [main] + components: [main] + `, + "slices/mydir/electron.yaml": ` + package: electron + slices: + mass: + contents: + /mass/electron: + `, + "slices/mydir/proton.yaml": ` + package: proton + archive: hadrons + slices: + mass: + contents: + /mass/proton: + `, + }, + slices: []setup.SliceKey{ + {"electron", "mass"}, + {"proton", "mass"}, + }, + result: map[string]string{ + "/mass/": "dir 0755", + "/mass/electron": "file 0644 a1258e30", + "/mass/proton": "file 0644 a2390d10", + "/usr/": "dir 0755", + "/usr/share/": "dir 0755", + "/usr/share/doc/": "dir 0755", + "/usr/share/doc/electron/": "dir 0755", + "/usr/share/doc/electron/copyright": "file 0644 empty", + }, }} const defaultChiselYaml = ` @@ -497,18 +609,34 @@ const defaultChiselYaml = ` components: [main, universe] ` +type testPackageInfo map[string]string + +var _ archive.PackageInfo = (testPackageInfo)(nil) + +func (info testPackageInfo) Name() string { return info["Package"] } +func (info testPackageInfo) Version() string { return info["Version"] } +func (info testPackageInfo) Arch() string { return info["Architecture"] } +func (info testPackageInfo) SHA256() string { return info["SHA256"] } + +func (s testPackageInfo) Get(key string) (value string) { + if s != nil { + value = s[key] + } + return +} + type testArchive struct { - arch string - pkgs map[string][]byte + options archive.Options + pkgs map[string]testPackage } func (a *testArchive) Options() *archive.Options { - return &archive.Options{Arch: a.arch} + return &a.options } func (a *testArchive) Fetch(pkg string) (io.ReadCloser, error) { if data, ok := a.pkgs[pkg]; ok { - return ioutil.NopCloser(bytes.NewBuffer(data)), nil + return ioutil.NopCloser(bytes.NewBuffer(data.content)), nil } return nil, fmt.Errorf("attempted to open %q package", pkg) } @@ -518,6 +646,18 @@ func (a *testArchive) Exists(pkg string) bool { return ok } +func (a *testArchive) Info(pkg string) archive.PackageInfo { + var info map[string]string + if pkgData, ok := a.pkgs[pkg]; ok { + if info = pkgData.info; info == nil { + info = map[string]string{ + "Version": "1.0", + } + } + } + return testPackageInfo(info) +} + func (s *S) TestRun(c *C) { for _, test := range slicerTests { c.Logf("Summary: %s", test.summary) @@ -541,19 +681,34 @@ func (s *S) TestRun(c *C) { selection, err := setup.Select(release, test.slices) c.Assert(err, IsNil) - pkgs := map[string][]byte{ - "base-files": testutil.PackageData["base-files"], + pkgs := map[string]testPackage{ + "base-files": testPackage{content: testutil.PackageData["base-files"]}, } for name, entries := range packageEntries { deb, err := testutil.MakeDeb(entries) c.Assert(err, IsNil) - pkgs[name] = deb + pkgs[name] = testPackage{content: deb} } - archives := map[string]archive.Archive{ - "ubuntu": &testArchive{ - arch: test.arch, - pkgs: pkgs, - }, + archives := map[string]archive.Archive{} + for name, setupArchive := range release.Archives { + var archivePkgs map[string]testPackage + if test.pkgs != nil { + archivePkgs = test.pkgs[name] + } + if archivePkgs == nil { + archivePkgs = pkgs + } + archive := &testArchive{ + options: archive.Options{ + Label: setupArchive.Name, + Version: setupArchive.Version, + Suites: setupArchive.Suites, + Components: setupArchive.Components, + Arch: test.arch, + }, + pkgs: archivePkgs, + } + archives[name] = archive } targetDir := c.MkDir() @@ -575,8 +730,15 @@ func (s *S) TestRun(c *C) { if test.result != nil { result := make(map[string]string, len(copyrightEntries)+len(test.result)) - for k, v := range copyrightEntries { - result[k] = v + if test.pkgs == nil { + // This was added in order to not specify copyright entries for each + // existing test. These tests use only the base-files embedded + // package. Custom packages may not include copyright entries + // though. So if a test defines any custom packages, it must include + // copyright entries explicitly in the results. + for k, v := range copyrightEntries { + result[k] = v + } } for k, v := range test.result { result[k] = v diff --git a/internal/testutil/pkgdata.go b/internal/testutil/pkgdata.go index 2b7f0221..0561a0c9 100644 --- a/internal/testutil/pkgdata.go +++ b/internal/testutil/pkgdata.go @@ -4,6 +4,7 @@ import ( "archive/tar" "bytes" "encoding/base64" + "strings" "time" "github.com/blakesmith/ar" @@ -174,7 +175,7 @@ func fixupTarEntry(entry *TarEntry) { if hdr.Typeflag == 0 { if hdr.Linkname != "" { hdr.Typeflag = tar.TypeSymlink - } else if hdr.Name[len(hdr.Name)-1] == '/' { + } else if strings.HasSuffix(hdr.Name, "/") { hdr.Typeflag = tar.TypeDir } else { hdr.Typeflag = tar.TypeReg @@ -265,3 +266,43 @@ func MakeDeb(entries []TarEntry) ([]byte, error) { } return buf.Bytes(), nil } + +func MakeTestDeb(entries []TarEntry) []byte { + data, err := MakeDeb(entries) + if err != nil { + panic(err) + } + return data +} + +func REG(mode int64, path, content string) TarEntry { + return TarEntry{ + Header: tar.Header{ + Typeflag: tar.TypeReg, + Name: path, + Mode: mode, + }, + Content: []byte(content), + } +} + +func DIR(mode int64, path string) TarEntry { + return TarEntry{ + Header: tar.Header{ + Typeflag: tar.TypeDir, + Name: path, + Mode: mode, + }, + } +} + +func LNK(mode int64, path, target string) TarEntry { + return TarEntry{ + Header: tar.Header{ + Typeflag: tar.TypeSymlink, + Name: path, + Mode: mode, + Linkname: target, + }, + } +} diff --git a/internal/testutil/pkgdata_test.go b/internal/testutil/pkgdata_test.go index 53283b3a..ab398cc5 100644 --- a/internal/testutil/pkgdata_test.go +++ b/internal/testutil/pkgdata_test.go @@ -315,6 +315,21 @@ var pkgdataCheckEntries = []checkTarEntry{{ ModTime: epochStartTime, Format: tar.FormatGNU, }, +}, { + testutil.TarEntry{ + Header: tar.Header{ + Name: "", + }, + }, + tar.Header{ + Typeflag: tar.TypeReg, + Name: "", + Mode: 00644, + Uname: "root", + Gname: "root", + ModTime: epochStartTime, + Format: tar.FormatGNU, + }, }} func (s *pkgdataSuite) TestMakeDeb(c *C) { @@ -370,3 +385,54 @@ func (s *pkgdataSuite) TestMakeDeb(c *C) { _, err = arReader.Next() c.Assert(err, Equals, io.EOF) } + +func (s *S) TestMakeTestDeb(c *C) { + defer func() { + err := recover() + c.Assert(err, ErrorMatches, `.*: cannot encode header: invalid PAX record: "path = \\x00./foo.*`) + }() + testutil.MakeTestDeb([]testutil.TarEntry{{ + Header: tar.Header{ + Name: "\000./foo", + }, + }}) +} + +func (s *S) TestTarEntryShortHands(c *C) { + var testCases = []struct { + shorthand testutil.TarEntry + result testutil.TarEntry + }{{ + testutil.REG(0600, "./document.txt", "cats are best"), + testutil.TarEntry{ + Header: tar.Header{ + Typeflag: tar.TypeReg, + Name: "./document.txt", + Mode: 0600, + }, + Content: []byte("cats are best"), + }, + }, { + testutil.DIR(0755, "./home/user"), + testutil.TarEntry{ + Header: tar.Header{ + Typeflag: tar.TypeDir, + Name: "./home/user", + Mode: 0755, + }, + }, + }, { + testutil.LNK(0755, "./lib", "./usr/lib/"), + testutil.TarEntry{ + Header: tar.Header{ + Typeflag: tar.TypeSymlink, + Name: "./lib", + Mode: 0755, + Linkname: "./usr/lib/", + }, + }, + }} + for _, test := range testCases { + c.Assert(test.shorthand, DeepEquals, test.result) + } +}