🐛 Bail out when no packages are found with default solvers (#296)

* 🐛 Bail out when no packages are found with default solvers

Checking packages is more tricky when a resolver is set. Resolvers
are capable of mutating the user request and remove part of the
constraints in order to resolve a specific solution.

This had the countereffect on a normal solver to not detect correctly
packages when missing from the wanted set and not proposed during
installation.

This should fix all the cases above taking into consideration of
resolvers and adding specific test-cases for it.

* ⚙️ Pin to tag for test image
This commit is contained in:
Ettore Di Giacinto
2022-05-24 23:01:56 +02:00
committed by GitHub
parent b5da2fa7b4
commit 1006be9271
7 changed files with 99 additions and 18 deletions

View File

@@ -53,17 +53,18 @@ var _ = Describe("Delta", func() {
var img, img2 v1.Image var img, img2 v1.Image
var err error var err error
ref, _ = name.ParseReference("alpine")
ref2, _ = name.ParseReference("golang:alpine")
img, _ = daemon.Image(ref)
img2, _ = daemon.Image(ref2)
BeforeEach(func() { BeforeEach(func() {
ctx = context.NewContext() ctx = context.NewContext()
ctx.Config.General.Debug = true
tmpfile, err = ioutil.TempFile("", "delta") tmpfile, err = ioutil.TempFile("", "delta")
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(tmpfile.Name()) // clean up defer os.RemoveAll(tmpfile.Name()) // clean up
ref, _ = name.ParseReference("alpine")
ref2, _ = name.ParseReference("golang:1.16-alpine3.14")
img, _ = daemon.Image(ref)
img2, _ = daemon.Image(ref2)
}) })
It("Extract all deltas", func() { It("Extract all deltas", func() {

View File

@@ -29,6 +29,6 @@ func TestImageApi(t *testing.T) {
b := backend.NewSimpleDockerBackend(context.NewContext()) b := backend.NewSimpleDockerBackend(context.NewContext())
b.DownloadImage(backend.Options{ImageName: "alpine"}) b.DownloadImage(backend.Options{ImageName: "alpine"})
b.DownloadImage(backend.Options{ImageName: "golang:alpine"}) b.DownloadImage(backend.Options{ImageName: "golang:alpine"})
b.DownloadImage(backend.Options{ImageName: "golang:1.16-alpine3.14"})
RunSpecs(t, "Image API Suite") RunSpecs(t, "Image API Suite")
} }

View File

@@ -291,6 +291,11 @@ func (p *Package) String() string {
return fmt.Sprintf("%s", string(b)) return fmt.Sprintf("%s", string(b))
} }
// HasVersionDefined returns true when a specific version of a package is implied
func (p *Package) HasVersionDefined() bool {
return p.Version != ">=0"
}
// GetFingerPrint returns a UUID of the package. // GetFingerPrint returns a UUID of the package.
// FIXME: this needs to be unique, now just name is generalized // FIXME: this needs to be unique, now just name is generalized
func (p *Package) GetFingerPrint() string { func (p *Package) GetFingerPrint() string {

View File

@@ -552,21 +552,28 @@ func (l *LuetInstaller) Install(cp types.Packages, s *System) error {
return err return err
} }
// Check if we have to process something, or return to the user an error allInstalled := true
if len(match) == 0 {
l.Options.Context.Info("No packages to install")
return nil
}
// Resolvers might decide to remove some packages from being installed // Resolvers might decide to remove some packages from being installed
if l.Options.SolverOptions.Type != solver.QLearningResolverType { if !solver.IsRelaxedResolver(l.Options.SolverOptions) {
for _, p := range cp { for _, p := range cp {
found := false found := false
vers, _ := s.Database.FindPackageVersions(p) // If was installed, it is found, as it was filtered
if len(vers) >= 1 { if p.HasVersionDefined() {
found = true f, err := s.Database.FindPackage(p)
continue if f != nil && err == nil {
found = true
continue
}
} else {
vers, _ := s.Database.FindPackageVersions(p) // If was installed, it is found, as it was filtered
if len(vers) >= 1 {
found = true
continue
}
} }
allInstalled = false
for _, m := range match { for _, m := range match {
if m.Package.GetName() == p.GetName() { if m.Package.GetName() == p.GetName() {
found = true found = true
@@ -583,8 +590,17 @@ func (l *LuetInstaller) Install(cp types.Packages, s *System) error {
} }
} }
} }
// Check if we have to process something, or return to the user an error
if len(match) == 0 {
l.Options.Context.Info("No packages to install")
if !solver.IsRelaxedResolver(l.Options.SolverOptions) && !allInstalled {
return fmt.Errorf("could not find packages to install from the repositories in the system")
}
return nil
}
l.Options.Context.Info("Packages that are going to be installed in the system:") l.Options.Context.Info("Packages that are going to be installed in the system:")
//l.Options.Context.Info("Packages that are going to be installed in the system: \n ", Green(matchesToList(match)).BgBlack().String())
printMatches(match) printMatches(match)

View File

@@ -26,6 +26,7 @@ import (
compiler "github.com/mudler/luet/pkg/compiler" compiler "github.com/mudler/luet/pkg/compiler"
backend "github.com/mudler/luet/pkg/compiler/backend" backend "github.com/mudler/luet/pkg/compiler/backend"
fileHelper "github.com/mudler/luet/pkg/helpers/file" fileHelper "github.com/mudler/luet/pkg/helpers/file"
"github.com/mudler/luet/pkg/solver"
pkg "github.com/mudler/luet/pkg/database" pkg "github.com/mudler/luet/pkg/database"
. "github.com/mudler/luet/pkg/installer" . "github.com/mudler/luet/pkg/installer"
@@ -1161,6 +1162,7 @@ urls:
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
inst := NewLuetInstaller(LuetInstallerOptions{ inst := NewLuetInstaller(LuetInstallerOptions{
Relaxed: true,
Concurrency: 1, Context: ctx, Concurrency: 1, Context: ctx,
PackageRepositories: types.LuetRepositories{*repo2.LuetRepository}, PackageRepositories: types.LuetRepositories{*repo2.LuetRepository},
}) })
@@ -1174,6 +1176,28 @@ urls:
systemDB := pkg.NewBoltDatabase(filepath.Join(bolt, "db.db")) systemDB := pkg.NewBoltDatabase(filepath.Join(bolt, "db.db"))
system := &System{Database: systemDB, Target: fakeroot} system := &System{Database: systemDB, Target: fakeroot}
err = inst.Install([]*types.Package{&types.Package{Name: "b", Category: "test", Version: "1.0"}}, system)
Expect(err).ToNot(HaveOccurred())
err = inst.Install([]*types.Package{&types.Package{Name: "b", Category: "foo", Version: "1.0"}}, system)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("where are definitions coming from"))
err = inst.Install([]*types.Package{&types.Package{Name: "b", Category: "foo", Version: ">=0"}}, system)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("package 'foo/b->=0' not found"))
inst2 := NewLuetInstaller(LuetInstallerOptions{
SolverOptions: types.LuetSolverOptions{Type: solver.QLearningResolverType},
Relaxed: true,
Concurrency: 1, Context: ctx,
PackageRepositories: types.LuetRepositories{*repo2.LuetRepository},
})
err = inst2.Install([]*types.Package{&types.Package{Name: "b"}, &types.Package{Name: "b", Category: "test", Version: ">=0"}}, system)
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("where are definitions coming from"))
err = inst.Install([]*types.Package{&types.Package{Name: "b", Category: "test", Version: "1.0"}}, system) err = inst.Install([]*types.Package{&types.Package{Name: "b", Category: "test", Version: "1.0"}}, system)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())

View File

@@ -40,6 +40,13 @@ type Solver struct {
Resolver types.PackageResolver Resolver types.PackageResolver
} }
// IsRelaxedResolver returns true wether a solver might
// take action on user side, by removing some installation constraints
// or taking automated actions (e.g. qlearning)
func IsRelaxedResolver(t types.LuetSolverOptions) bool {
return t.Type == QLearningResolverType
}
// NewSolver accepts as argument two lists of packages, the first is the initial set, // NewSolver accepts as argument two lists of packages, the first is the initial set,
// the second represent all the known packages. // the second represent all the known packages.
func NewSolver(t types.SolverOptions, installed types.PackageDatabase, definitiondb types.PackageDatabase, solverdb types.PackageDatabase) types.PackageSolver { func NewSolver(t types.SolverOptions, installed types.PackageDatabase, definitiondb types.PackageDatabase, solverdb types.PackageDatabase) types.PackageSolver {

View File

@@ -57,11 +57,39 @@ EOF
} }
testInstall() { testInstall() {
luet install -y --config $tmpdir/luet.yaml test/foobar
installst=$?
assertEquals 'install test fails' "$installst" "2"
luet install -y --config $tmpdir/luet.yaml test/foobar test/c
installst=$?
assertEquals 'install test fails' "$installst" "2"
luet install -y --config $tmpdir/luet.yaml test/foobar@1.0
installst=$?
assertEquals 'install test fails' "$installst" "2"
luet install -y --config $tmpdir/luet.yaml test/foobar@1.0 test/c@1.0
installst=$?
assertEquals 'install test fails' "$installst" "2"
luet install -y --config $tmpdir/luet.yaml test/foobar@1.0 test/c
installst=$?
assertEquals 'install test fails' "$installst" "2"
luet install -y --config $tmpdir/luet.yaml test/c luet install -y --config $tmpdir/luet.yaml test/c
#luet install -y --config $tmpdir/luet.yaml test/c@1.0 > /dev/null
installst=$? installst=$?
assertEquals 'install test successfully' "$installst" "0" assertEquals 'install test successfully' "$installst" "0"
assertTrue 'package installed' "[ -e '$tmpdir/testrootfs/c' ]" assertTrue 'package installed' "[ -e '$tmpdir/testrootfs/c' ]"
luet install -y --config $tmpdir/luet.yaml test/foobar test/c
installst=$?
assertEquals 'install test fails' "$installst" "2"
# Already installed
luet install -y --config $tmpdir/luet.yaml test/c@1.0
installst=$?
assertEquals 'install test fails' "$installst" "0"
} }
testReInstall() { testReInstall() {