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
115 changes: 115 additions & 0 deletions internal/slicer/fetch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package slicer

import (
"fmt"
"io"
"slices"

"github.com/canonical/chisel/internal/archive"
"github.com/canonical/chisel/internal/manifestutil"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Note to reviewer]: This is a smell that having PackageInfo in manifestutil shows its limit, as already discussed.

I considered moving it to the slicer pkg but that would be wrong because manifestutil (a leaf package) would need to import it.
I also considered creating a standalone package with fetch.go and PackageInfo but that would couple manifestutil with this fetch package and it looks wrong too.

So the next best solution looks to be a small pkginfo leaf package, holding the interface. Let me know what you think about it and I will proceed with it in a follow-up PR (to not mix refactor and feature in this one) if we agree on a refactor.

"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) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Note to reviewer]: I think it makes sense to have this function here, but it is a refactor (and rename) of selectPkgArchives so moving it right now make it a bit difficult to see the diff. The actual changes in this function are very local but if it is too hard to read I can move it back to slicer.go in this PR and do the move to this file in a follow-up.

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
}
61 changes: 4 additions & 57 deletions internal/slicer/slicer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
24 changes: 16 additions & 8 deletions internal/slicer/slicer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Loading