diff --git a/internal/slicer/fetch.go b/internal/slicer/fetch.go new file mode 100644 index 00000000..e18286b9 --- /dev/null +++ b/internal/slicer/fetch.go @@ -0,0 +1,115 @@ +package slicer + +import ( + "fmt" + "io" + "slices" + + "github.com/canonical/chisel/internal/archive" + "github.com/canonical/chisel/internal/manifestutil" + "github.com/canonical/chisel/internal/setup" +) + +type Fetcher interface { + Arch() string + Fetch() (io.ReadSeekCloser, manifestutil.PackageInfo, error) +} + +var ( + _ Fetcher = (*debFetcher)(nil) + _ Fetcher = (*binFetcher)(nil) +) + +// debFetcher fetches deb packages from an archive. +type debFetcher struct { + archive archive.Archive + name string +} + +func (d *debFetcher) Arch() string { + return d.archive.Options().Arch +} + +func (d *debFetcher) Fetch() (io.ReadSeekCloser, manifestutil.PackageInfo, error) { + return d.archive.Fetch(d.name) +} + +// binFetcher fetches bin packages from a store. +type binFetcher struct { + arch string + name string + store string +} + +func (b *binFetcher) Arch() string { + return b.arch +} + +func (b *binFetcher) Fetch() (io.ReadSeekCloser, manifestutil.PackageInfo, error) { + return nil, nil, fmt.Errorf("cannot fetch package %q from store %q: not implemented", b.name, b.store) +} + +// selectPkgFetchers determines the fetcher for each package in the selection. +// For packages from an archive it selects the highest priority archive +// containing the package unless a particular archive is pinned within the +// package slices file. For packages from a store it selects the store +// named in the package slices file. It returns a map of Fetcher indexed +// by package names. +func selectPkgFetchers(archives map[string]archive.Archive, selection *setup.Selection) (map[string]Fetcher, error) { + sortedArchives := make([]*setup.Archive, 0, len(selection.Release.Archives)) + for _, archive := range selection.Release.Archives { + if archive.Priority < 0 { + // Ignore negative priority archives unless a package specifically + // asks for it with the "archive" field. + continue + } + sortedArchives = append(sortedArchives, archive) + } + slices.SortFunc(sortedArchives, func(a, b *setup.Archive) int { + return b.Priority - a.Priority + }) + + fetchers := make(map[string]Fetcher) + for _, s := range selection.Slices { + if _, ok := fetchers[s.Package]; ok { + continue + } + pkg := selection.Release.Packages[s.Package] + if pkg.Store != "" { + fetchers[pkg.Name] = &binFetcher{ + name: pkg.Name, + store: pkg.Store, + // TODO: populate arch, track and risk when implementing + // fetching from the store. + } + continue + } + + var candidates []*setup.Archive + if pkg.Archive == "" { + // If the package has not pinned any archive, choose the highest + // priority archive in which the package exists. + candidates = sortedArchives + } else { + candidates = []*setup.Archive{selection.Release.Archives[pkg.Archive]} + } + + var chosen archive.Archive + for _, archiveInfo := range candidates { + archive := archives[archiveInfo.Name] + if archive != nil && archive.Exists(pkg.RealName) { + chosen = archive + break + } + } + if chosen == nil { + return nil, fmt.Errorf("cannot find package %q in archive(s)", pkg.RealName) + } + fetchers[pkg.Name] = &debFetcher{ + archive: chosen, + name: pkg.RealName, + } + } + + return fetchers, nil +} diff --git a/internal/slicer/slicer.go b/internal/slicer/slicer.go index ce791162..7405fcdb 100644 --- a/internal/slicer/slicer.go +++ b/internal/slicer/slicer.go @@ -90,7 +90,7 @@ func Run(options *RunOptions) error { targetDir = filepath.Join(dir, targetDir) } - pkgArchive, err := selectPkgArchives(options.Archives, options.Selection) + pkgFetchers, err := selectPkgFetchers(options.Archives, options.Selection) if err != nil { return err } @@ -108,7 +108,7 @@ func Run(options *RunOptions) error { extractPackage = make(map[string][]tarball.ExtractInfo) extract[slice.Package] = extractPackage } - arch := pkgArchive[slice.Package].Options().Arch + arch := pkgFetchers[slice.Package].Arch() for targetPath, pathInfo := range slice.Contents { if targetPath == "" { continue @@ -153,7 +153,7 @@ func Run(options *RunOptions) error { continue } pkg := options.Selection.Release.Packages[slice.Package] - reader, info, err := pkgArchive[slice.Package].Fetch(pkg.RealName) + reader, info, err := pkgFetchers[pkg.Name].Fetch() if err != nil { return err } @@ -270,7 +270,7 @@ func Run(options *RunOptions) error { // them to the appropriate slices. relPaths := map[string][]*setup.Slice{} for _, slice := range options.Selection.Slices { - arch := pkgArchive[slice.Package].Options().Arch + arch := pkgFetchers[slice.Package].Arch() for relPath, pathInfo := range slice.Contents { if len(pathInfo.Arch) > 0 && !slices.Contains(pathInfo.Arch, arch) { continue @@ -489,56 +489,3 @@ func createFile(targetDir, relPath string, pathInfo setup.PathInfo) (*fsutil.Ent MakeParents: true, }) } - -// selectPkgArchives selects the highest priority archive containing the package -// unless a particular archive is pinned within the slice definition file. It -// returns a map of archives indexed by package names. -func selectPkgArchives(archives map[string]archive.Archive, selection *setup.Selection) (map[string]archive.Archive, error) { - sortedArchives := make([]*setup.Archive, 0, len(selection.Release.Archives)) - for _, archive := range selection.Release.Archives { - if archive.Priority < 0 { - // Ignore negative priority archives unless a package specifically - // asks for it with the "archive" field. - continue - } - sortedArchives = append(sortedArchives, archive) - } - slices.SortFunc(sortedArchives, func(a, b *setup.Archive) int { - return b.Priority - a.Priority - }) - - pkgArchive := make(map[string]archive.Archive) - for _, s := range selection.Slices { - if _, ok := pkgArchive[s.Package]; ok { - continue - } - pkg := selection.Release.Packages[s.Package] - - if pkg.Store != "" { - return nil, fmt.Errorf("cannot fetch package %q from store %q: not implemented", pkg.Name, pkg.Store) - } - - var candidates []*setup.Archive - if pkg.Archive == "" { - // If the package has not pinned any archive, choose the highest - // priority archive in which the package exists. - candidates = sortedArchives - } else { - candidates = []*setup.Archive{selection.Release.Archives[pkg.Archive]} - } - - var chosen archive.Archive - for _, archiveInfo := range candidates { - archive := archives[archiveInfo.Name] - if archive != nil && archive.Exists(pkg.RealName) { - chosen = archive - break - } - } - if chosen == nil { - return nil, fmt.Errorf("cannot find package %q in archive(s)", pkg.RealName) - } - pkgArchive[pkg.Name] = chosen - } - return pkgArchive, nil -} diff --git a/internal/slicer/slicer_test.go b/internal/slicer/slicer_test.go index d6ef9ca0..954602de 100644 --- a/internal/slicer/slicer_test.go +++ b/internal/slicer/slicer_test.go @@ -1979,21 +1979,29 @@ var slicerTests = []slicerTest{{ "/dir/file": "file 0644 cc55e2ec {test-package_third}", }, }, { - summary: "Store package is not yet implemented", - slices: []setup.SliceKey{{"bin-curl", "bin"}}, + summary: "Store package fetching not yet implemented", + slices: []setup.SliceKey{{"test-package", "myslice"}, {"bin-store-pkg", "myslice"}}, + arch: "amd64", release: map[string]string{ "chisel.yaml": testutil.DefaultChiselYamlWithStores, - "slices/curl.yaml": ` - package: curl + "slices/mydir/test-package.yaml": ` + package: test-package + slices: + myslice: + contents: + /dir/file: + `, + "slices/mydir/store-pkg.yaml": ` + package: store-pkg store: bin - default-track: latest + default-track: 3.1 slices: - bin: + myslice: contents: - /usr/bin/curl: + /dir/store-file: `, }, - error: `cannot fetch package "bin-curl" from store "bin": not implemented`, + error: `cannot fetch package "bin-store-pkg" from store "bin": not implemented`, }} func (s *S) TestRun(c *C) {