Compare commits

..

4 Commits

Author SHA1 Message Date
Ettore Di Giacinto
d6ae727d79 🐛 Fix handlelock panic
Somehow this slipped in, as we should check the lock only if we have enough args.

Fixes #297
2022-05-25 10:07:25 +00:00
Ettore Di Giacinto
fea872aba0 ⬆️ Tag 0.32.1 2022-05-24 23:05:35 +02:00
Ettore Di Giacinto
1006be9271 🐛 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
2022-05-24 23:01:56 +02:00
Ettore Di Giacinto
b5da2fa7b4 ⚙️ Fixup corner case when templating requires
Adds also specific tests to cover that area
2022-04-28 12:57:36 +02:00
20 changed files with 223 additions and 29 deletions

View File

@@ -30,7 +30,7 @@ var cfgFile string
var Verbose bool
const (
LuetCLIVersion = "0.32.0"
LuetCLIVersion = "0.32.1"
LuetEnvPrefix = "LUET"
)

View File

@@ -55,11 +55,7 @@ func TemplateFolders(ctx *context.Context, i installer.BuildTreeResult, treePath
}
func HandleLock() {
if os.Getenv("LUET_NOLOCK") == "true" {
return
}
if len(os.Args) == 0 {
if os.Getenv("LUET_NOLOCK") == "true" || len(os.Args) < 2 {
return
}

View File

@@ -53,17 +53,18 @@ var _ = Describe("Delta", func() {
var img, img2 v1.Image
var err error
ref, _ = name.ParseReference("alpine")
ref2, _ = name.ParseReference("golang:alpine")
img, _ = daemon.Image(ref)
img2, _ = daemon.Image(ref2)
BeforeEach(func() {
ctx = context.NewContext()
ctx.Config.General.Debug = true
tmpfile, err = ioutil.TempFile("", "delta")
Expect(err).ToNot(HaveOccurred())
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() {

View File

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

View File

@@ -146,9 +146,12 @@ func PackageFromYaml(yml []byte) (Package, error) {
type rawPackages []map[string]interface{}
func (r rawPackages) Find(name, category, version string) map[string]interface{} {
func (r rawPackages) Find(wanted Package) map[string]interface{} {
for _, v := range r {
if v["name"] == name && v["category"] == category && v["version"] == version {
p := &Package{}
dat, _ := json.Marshal(v)
json.Unmarshal(dat, p)
if wanted.Matches(p) {
return v
}
}
@@ -288,6 +291,11 @@ func (p *Package) String() string {
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.
// FIXME: this needs to be unique, now just name is generalized
func (p *Package) GetFingerPrint() string {

View File

@@ -1461,7 +1461,7 @@ func (cs *LuetCompiler) templatePackage(vals []map[string]interface{}, pack *typ
return nil, errors.Wrap(err, "getting raw packages")
}
raw := packsRaw.Find(pack.GetName(), pack.GetCategory(), pack.GetVersion())
raw := packsRaw.Find(*pack)
td := templatedata{}
if len(vals) > 0 {
for _, bv := range vals {

View File

@@ -552,21 +552,28 @@ func (l *LuetInstaller) Install(cp types.Packages, s *System) error {
return err
}
// 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")
return nil
}
allInstalled := true
// 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 {
found := false
vers, _ := s.Database.FindPackageVersions(p) // If was installed, it is found, as it was filtered
if len(vers) >= 1 {
found = true
continue
if p.HasVersionDefined() {
f, err := s.Database.FindPackage(p)
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 {
if m.Package.GetName() == p.GetName() {
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: \n ", Green(matchesToList(match)).BgBlack().String())
printMatches(match)

View File

@@ -26,6 +26,7 @@ import (
compiler "github.com/mudler/luet/pkg/compiler"
backend "github.com/mudler/luet/pkg/compiler/backend"
fileHelper "github.com/mudler/luet/pkg/helpers/file"
"github.com/mudler/luet/pkg/solver"
pkg "github.com/mudler/luet/pkg/database"
. "github.com/mudler/luet/pkg/installer"
@@ -1161,6 +1162,7 @@ urls:
Expect(err).ToNot(HaveOccurred())
inst := NewLuetInstaller(LuetInstallerOptions{
Relaxed: true,
Concurrency: 1, Context: ctx,
PackageRepositories: types.LuetRepositories{*repo2.LuetRepository},
})
@@ -1174,6 +1176,28 @@ urls:
systemDB := pkg.NewBoltDatabase(filepath.Join(bolt, "db.db"))
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)
Expect(err).ToNot(HaveOccurred())

View File

@@ -40,6 +40,13 @@ type Solver struct {
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,
// the second represent all the known packages.
func NewSolver(t types.SolverOptions, installed types.PackageDatabase, definitiondb types.PackageDatabase, solverdb types.PackageDatabase) types.PackageSolver {

View File

@@ -54,7 +54,7 @@ func BuildCollectionParser(srcDir, currentpath, name string, templates []string,
compileDefPath := pack.Rel(CompilerDefinitionFile)
if fileHelper.Exists(compileDefPath) {
raw := packsRaw.Find(pack.GetName(), pack.GetCategory(), pack.GetVersion())
raw := packsRaw.Find(pack)
buildyaml, err := ioutil.ReadFile(compileDefPath)
if err != nil {
return errors.Wrap(err, "Error reading file "+currentpath)
@@ -113,7 +113,7 @@ func RuntimeCollectionParser(srcDir, currentpath, name string, templates []strin
compileDefPath := p.Rel(CompilerDefinitionFile)
if fileHelper.Exists(compileDefPath) {
raw := packsRaw.Find(p.GetName(), p.GetCategory(), p.GetVersion())
raw := packsRaw.Find(p)
buildyaml, err := ioutil.ReadFile(compileDefPath)
if err != nil {
return errors.Wrap(err, "Error reading file "+currentpath)

View File

@@ -0,0 +1,75 @@
// Copyright © 2019 Ettore Di Giacinto <mudler@gentoo.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
// Recipe is a builder imeplementation.
// It reads a Tree and spit it in human readable form (YAML), called recipe,
// It also loads a tree (recipe) from a YAML (to a db, e.g. BoltDB), allowing to query it
// with the solver, using the package object.
package tree_test
import (
"io/ioutil"
"os"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/mudler/luet/pkg/api/core/types"
pkg "github.com/mudler/luet/pkg/database"
. "github.com/mudler/luet/pkg/tree"
)
var _ = Describe("Templated tree", func() {
Context("Resolves correctly dependencies", func() {
It("interpolates correctly templated requires", func() {
db := pkg.NewInMemoryDatabase(false)
generalRecipe := NewCompilerRecipe(db)
tmpdir, err := ioutil.TempDir("", "package")
Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(tmpdir) // clean up
err = generalRecipe.Load("../../tests/fixtures/template_requires")
Expect(err).ToNot(HaveOccurred())
Expect(len(generalRecipe.GetDatabase().World())).To(Equal(7))
foo, err := generalRecipe.GetDatabase().FindPackage(&types.Package{Name: "foo"})
Expect(err).ToNot(HaveOccurred())
Expect(len(foo.GetRequires())).To(Equal(1))
Expect(foo.GetRequires()[0].Name).To(Equal("bar"))
baz, err := generalRecipe.GetDatabase().FindPackage(&types.Package{Name: "baz"})
Expect(err).ToNot(HaveOccurred())
Expect(len(baz.GetRequires())).To(Equal(1))
Expect(baz.GetRequires()[0].Name).To(Equal("foobar"))
bazbaz, err := generalRecipe.GetDatabase().FindPackage(&types.Package{Name: "bazbaz"})
Expect(err).ToNot(HaveOccurred())
Expect(len(bazbaz.GetRequires())).To(Equal(1))
Expect(bazbaz.GetRequires()[0].Name).To(Equal("foobar"))
foo, err = generalRecipe.GetDatabase().FindPackage(&types.Package{Name: "foo", Category: "test"})
Expect(err).ToNot(HaveOccurred())
Expect(len(foo.GetRequires())).To(Equal(1))
Expect(foo.GetRequires()[0].Name).To(Equal("bar"))
baz, err = generalRecipe.GetDatabase().FindPackage(&types.Package{Name: "baz", Category: "test"})
Expect(err).ToNot(HaveOccurred())
Expect(len(baz.GetRequires())).To(Equal(1))
Expect(baz.GetRequires()[0].Name).To(Equal("foobar"))
})
})
})

View File

@@ -0,0 +1,7 @@
requires:
{{ if eq .Values.value "bar" }}
- name: "bar"
{{ end }}
{{ if eq .Values.value "foobar" }}
- name: "foobar"
{{ end }}

View File

@@ -0,0 +1,5 @@
packages:
- name: "foo"
value: "bar"
- name: "baz"
value: "foobar"

View File

@@ -0,0 +1,7 @@
requires:
{{ if eq .Values.value "bar" }}
- name: "bar"
{{ end }}
{{ if eq .Values.value "foobar" }}
- name: "foobar"
{{ end }}

View File

@@ -0,0 +1,7 @@
packages:
- name: "foo"
category: "test"
value: "bar"
- name: "baz"
value: "foobar"
category: "test"

View File

@@ -0,0 +1,7 @@
requires:
{{ if eq .Values.value "bar" }}
- name: "bar"
{{ end }}
{{ if eq .Values.value "foobar" }}
- name: "foobar"
{{ end }}

View File

@@ -0,0 +1,2 @@
name: "bazbaz"
value: "foobar"

View File

@@ -0,0 +1 @@
image: alpine

View File

@@ -0,0 +1,3 @@
packages:
- name: "foobar"
- name: "bar"

View File

@@ -57,11 +57,39 @@ EOF
}
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@1.0 > /dev/null
installst=$?
assertEquals 'install test successfully' "$installst" "0"
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() {