mirror of
https://github.com/mudler/luet.git
synced 2025-09-03 00:06:36 +00:00
Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
bac9bac25f | ||
|
ce95b3ada4 | ||
|
49d8c2b972 | ||
|
2b3a1555f0 | ||
|
03e72653c7 | ||
|
264bf53fe7 | ||
|
edd2275bf5 | ||
|
d6ae727d79 | ||
|
fea872aba0 | ||
|
1006be9271 | ||
|
b5da2fa7b4 |
6
Makefile
6
Makefile
@@ -81,6 +81,12 @@ test-docker:
|
||||
--workdir /go/src/github.com/mudler/luet -ti golang:latest \
|
||||
bash -c "make test"
|
||||
|
||||
.PHONY: test-integration-docker
|
||||
test-integration-docker:
|
||||
docker run -v $(ROOT_DIR):/go/src/github.com/mudler/luet -v /var/run/docker.sock:/var/run/docker.sock \
|
||||
--workdir /go/src/github.com/mudler/luet -ti golang:latest \
|
||||
bash -c "apt-get update && apt-get install docker.io && make test-integration"
|
||||
|
||||
multiarch-build:
|
||||
goreleaser build --snapshot --rm-dist
|
||||
|
||||
|
@@ -30,7 +30,7 @@ var cfgFile string
|
||||
var Verbose bool
|
||||
|
||||
const (
|
||||
LuetCLIVersion = "0.32.0"
|
||||
LuetCLIVersion = "0.32.2"
|
||||
LuetEnvPrefix = "LUET"
|
||||
)
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
1
go.mod
1
go.mod
@@ -53,6 +53,7 @@ require (
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pterm/pterm v0.12.32-0.20211002183613-ada9ef6790c3
|
||||
github.com/rancher-sandbox/gofilecache v0.0.0-20210330135715-becdeff5df15
|
||||
github.com/scrohde/go-toposort v0.0.0-20170629203416-a9a902e065a3 // indirect
|
||||
github.com/spf13/cast v1.4.1 // indirect
|
||||
github.com/spf13/cobra v1.2.1
|
||||
github.com/spf13/viper v1.8.1
|
||||
|
2
go.sum
2
go.sum
@@ -1300,6 +1300,8 @@ github.com/sassoftware/go-rpmutils v0.0.0-20190420191620-a8f1baeba37b/go.mod h1:
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
|
||||
github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
|
||||
github.com/scrohde/go-toposort v0.0.0-20170629203416-a9a902e065a3 h1:i8yAzeysv+UgLJ0O8usGQHvtyM0AbPHlI03l5OnWD4A=
|
||||
github.com/scrohde/go-toposort v0.0.0-20170629203416-a9a902e065a3/go.mod h1:FzYqW619hxj8ogwgAr2ENoIELLwatZvaBnWdTquP99U=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
|
||||
github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
|
||||
|
@@ -30,7 +30,7 @@ import (
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
*logger.Logger
|
||||
types.Logger
|
||||
context.Context
|
||||
types.GarbageCollector
|
||||
Config *types.LuetConfig
|
||||
@@ -51,7 +51,7 @@ func (c *Context) GetAnnotation(s string) interface{} {
|
||||
type ContextOption func(c *Context) error
|
||||
|
||||
// WithLogger sets the logger
|
||||
func WithLogger(l *logger.Logger) ContextOption {
|
||||
func WithLogger(l types.Logger) ContextOption {
|
||||
return func(c *Context) error {
|
||||
c.Logger = l
|
||||
return nil
|
||||
@@ -122,13 +122,14 @@ func (c *Context) WithLoggingContext(name string) types.Context {
|
||||
ctxCopy.Config = &configCopy
|
||||
ctxCopy.annotations = ctx.annotations
|
||||
|
||||
ctxCopy.Logger, _ = c.Logger.Copy(logger.WithContext(name))
|
||||
ctxCopy.Logger, _ = c.Logger.Copy()
|
||||
ctxCopy.Logger.SetContext(name)
|
||||
|
||||
return ctxCopy
|
||||
}
|
||||
|
||||
// Copy returns a context copy with a reset logging context
|
||||
func (c *Context) Copy() types.Context {
|
||||
func (c *Context) Clone() types.Context {
|
||||
return c.WithLoggingContext("")
|
||||
}
|
||||
|
||||
|
@@ -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() {
|
||||
|
@@ -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")
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@ import (
|
||||
|
||||
log "github.com/ipfs/go-log/v2"
|
||||
"github.com/kyokomi/emoji"
|
||||
"github.com/mudler/luet/pkg/api/core/types"
|
||||
"github.com/pterm/pterm"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
@@ -114,18 +115,19 @@ func New(opts ...LoggerOptions) (*Logger, error) {
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (l *Logger) Copy(opts ...LoggerOptions) (*Logger, error) {
|
||||
// Copy returns a copy of the logger
|
||||
func (l *Logger) Copy() (types.Logger, error) {
|
||||
c := *l
|
||||
copy := &c
|
||||
for _, o := range opts {
|
||||
if err := o(copy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return copy, nil
|
||||
}
|
||||
|
||||
// SetContext sets the logger context, used to prefix log lines
|
||||
func (l *Logger) SetContext(name string) {
|
||||
l.context = name
|
||||
}
|
||||
|
||||
func joinMsg(args ...interface{}) (message string) {
|
||||
for _, m := range args {
|
||||
message += " " + fmt.Sprintf("%v", m)
|
||||
|
@@ -21,7 +21,6 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/gookit/color"
|
||||
"github.com/mudler/luet/pkg/api/core/logger"
|
||||
. "github.com/mudler/luet/pkg/api/core/logger"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
@@ -84,11 +83,12 @@ var _ = Describe("Context and logging", func() {
|
||||
|
||||
It("returns copies with logged context", func() {
|
||||
l, err := New(WithLevel("debug"))
|
||||
l, _ = l.Copy(logger.WithContext("bazzz"))
|
||||
l2, _ := l.Copy()
|
||||
l2.SetContext("bazzz")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(captureStdout(func(w io.Writer) {
|
||||
l.Debug("bar")
|
||||
l2.Debug("bar")
|
||||
})).To(ContainSubstring("(bazzz) bar"))
|
||||
})
|
||||
|
||||
|
@@ -6,9 +6,10 @@ import (
|
||||
"sort"
|
||||
"unicode"
|
||||
|
||||
"github.com/mudler/luet/pkg/helpers"
|
||||
"github.com/mudler/topsort"
|
||||
"github.com/philopon/go-toposort"
|
||||
"github.com/pkg/errors"
|
||||
toposort "github.com/scrohde/go-toposort"
|
||||
)
|
||||
|
||||
// PackageAssert represent a package assertion.
|
||||
@@ -30,51 +31,48 @@ func (a *PackageAssert) String() string {
|
||||
return fmt.Sprintf("%s/%s %s %s", a.Package.GetCategory(), a.Package.GetName(), a.Package.GetVersion(), msg)
|
||||
}
|
||||
|
||||
func (assertions PackagesAssertions) EnsureOrder() PackagesAssertions {
|
||||
func (assertions PackagesAssertions) EnsureOrder(definitiondb PackageDatabase) (PackagesAssertions, error) {
|
||||
|
||||
allAssertions := assertions
|
||||
orderedAssertions := PackagesAssertions{}
|
||||
unorderedAssertions := PackagesAssertions{}
|
||||
fingerprints := []string{}
|
||||
|
||||
tmpMap := map[string]PackageAssert{}
|
||||
|
||||
for _, a := range assertions {
|
||||
tmpMap[a.Package.GetFingerPrint()] = a
|
||||
fingerprints = append(fingerprints, a.Package.GetFingerPrint())
|
||||
unorderedAssertions = append(unorderedAssertions, a) // Build a list of the ones that must be ordered
|
||||
graph := toposort.NewGraph(len(allAssertions))
|
||||
|
||||
if a.Value {
|
||||
unorderedAssertions = append(unorderedAssertions, a) // Build a list of the ones that must be ordered
|
||||
} else {
|
||||
orderedAssertions = append(orderedAssertions, a) // Keep last the ones which are not meant to be installed
|
||||
}
|
||||
for _, a := range assertions {
|
||||
tmpMap[a.Package.GetPackageName()] = a
|
||||
graph.AddNode(a.Package.GetPackageName())
|
||||
}
|
||||
|
||||
sort.Sort(unorderedAssertions)
|
||||
edges := map[string]string{}
|
||||
|
||||
// Build a topological graph
|
||||
graph := toposort.NewGraph(len(unorderedAssertions))
|
||||
graph.AddNodes(fingerprints...)
|
||||
for _, a := range unorderedAssertions {
|
||||
for _, a := range allAssertions {
|
||||
for _, req := range a.Package.GetRequires() {
|
||||
graph.AddEdge(a.Package.GetFingerPrint(), req.GetFingerPrint())
|
||||
if def, err := definitiondb.FindPackage(req); err == nil { // Provides: Get a chance of being override here
|
||||
req = def
|
||||
}
|
||||
edges[a.Package.GetPackageName()] = req.GetPackageName()
|
||||
graph.AddNode(req.GetPackageName())
|
||||
}
|
||||
}
|
||||
result, ok := graph.Toposort()
|
||||
|
||||
for k, v := range edges {
|
||||
graph.AddEdge(k, v)
|
||||
}
|
||||
|
||||
result, ok := graph.ToposortStable()
|
||||
if !ok {
|
||||
panic("Cycle found")
|
||||
return nil, fmt.Errorf("cycle found")
|
||||
}
|
||||
for _, res := range result {
|
||||
a, ok := tmpMap[res]
|
||||
if !ok {
|
||||
panic("fail")
|
||||
// continue
|
||||
if ok {
|
||||
orderedAssertions = append(orderedAssertions, a)
|
||||
}
|
||||
orderedAssertions = append(orderedAssertions, a)
|
||||
// orderedAssertions = append(PackagesAssertions{a}, orderedAssertions...) // push upfront
|
||||
}
|
||||
//helpers.ReverseAny(orderedAssertions)
|
||||
return orderedAssertions
|
||||
helpers.ReverseAny(orderedAssertions)
|
||||
return orderedAssertions, nil
|
||||
}
|
||||
|
||||
// SearchByName searches a string matching a package in the assetion list
|
||||
|
86
pkg/api/core/types/assertion_test.go
Normal file
86
pkg/api/core/types/assertion_test.go
Normal file
@@ -0,0 +1,86 @@
|
||||
// Copyright © 2021 Ettore Di Giacinto <mudler@mocaccino.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/>.
|
||||
|
||||
package types_test
|
||||
|
||||
import (
|
||||
types "github.com/mudler/luet/pkg/api/core/types"
|
||||
"github.com/mudler/luet/pkg/database"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Assertions", func() {
|
||||
Context("Ordering", func() {
|
||||
It("orders them correctly", func() {
|
||||
foo := &types.Package{Name: "foo", PackageRequires: []*types.Package{{Name: "bar"}}}
|
||||
assertions := types.PackagesAssertions{
|
||||
{Package: foo},
|
||||
{Package: &types.Package{Name: "baz", PackageRequires: []*types.Package{{Name: "bar"}}}},
|
||||
{Package: &types.Package{Name: "bar", PackageRequires: []*types.Package{{}}}},
|
||||
}
|
||||
|
||||
ordered_old, err := assertions.Order(database.NewInMemoryDatabase(false), foo.GetFingerPrint())
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
Expect(ordered_old[0].Package.Name).To(Equal("bar"))
|
||||
|
||||
ordered, err := assertions.EnsureOrder(database.NewInMemoryDatabase(false))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(len(ordered)).To(Equal(3))
|
||||
|
||||
Expect(ordered[0].Package.Name).To(Equal("bar"))
|
||||
})
|
||||
|
||||
It("errors on cycles", func() {
|
||||
foo := &types.Package{Name: "foo", PackageRequires: []*types.Package{{Name: "bar"}}}
|
||||
assertions := types.PackagesAssertions{
|
||||
{Package: foo},
|
||||
{Package: &types.Package{Name: "baz", PackageRequires: []*types.Package{{Name: "bar"}}}},
|
||||
{Package: &types.Package{Name: "bar", PackageRequires: []*types.Package{{Name: "baz"}}}},
|
||||
}
|
||||
|
||||
_, err := assertions.Order(database.NewInMemoryDatabase(false), foo.GetFingerPrint())
|
||||
Expect(err).Should(HaveOccurred())
|
||||
|
||||
_, err = assertions.EnsureOrder(database.NewInMemoryDatabase(false))
|
||||
Expect(err).Should(HaveOccurred())
|
||||
})
|
||||
|
||||
It("orders them correctly", func() {
|
||||
foo := &types.Package{Name: "foo", PackageRequires: []*types.Package{{Name: "bar"}}}
|
||||
assertions := types.PackagesAssertions{
|
||||
{Package: foo},
|
||||
{Package: &types.Package{Name: "baz2", PackageRequires: []*types.Package{{Name: "foobaz"}}}},
|
||||
{Package: &types.Package{Name: "baz", PackageRequires: []*types.Package{{Name: "bar"}}}},
|
||||
{Package: &types.Package{Name: "bar", PackageRequires: []*types.Package{{}}}},
|
||||
{Package: &types.Package{Name: "foobaz", PackageRequires: []*types.Package{{}}}},
|
||||
}
|
||||
|
||||
ordered_old, err := assertions.Order(database.NewInMemoryDatabase(false), foo.GetFingerPrint())
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
|
||||
Expect(ordered_old[0].Package.Name).To(Equal("bar"))
|
||||
Expect(ordered_old[1].Package.Name).ToNot(Equal("foobaz"))
|
||||
|
||||
ordered, err := assertions.EnsureOrder(database.NewInMemoryDatabase(false))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(len(ordered)).To(Equal(5))
|
||||
|
||||
Expect(ordered[0].Package.Name).To(Equal("bar"))
|
||||
Expect(ordered[1].Package.Name).To(Equal("foobaz"))
|
||||
})
|
||||
})
|
||||
})
|
@@ -19,7 +19,7 @@ type Context interface {
|
||||
Logger
|
||||
GarbageCollector
|
||||
GetConfig() LuetConfig
|
||||
Copy() Context
|
||||
Clone() Context
|
||||
// SetAnnotation sets generic annotations to hold in a context
|
||||
SetAnnotation(s string, i interface{})
|
||||
|
||||
|
@@ -33,7 +33,8 @@ type Logger interface {
|
||||
Fatalf(string, ...interface{})
|
||||
Panicf(string, ...interface{})
|
||||
Tracef(string, ...interface{})
|
||||
|
||||
Copy() (Logger, error)
|
||||
SetContext(string)
|
||||
SpinnerStop()
|
||||
Spinner()
|
||||
Ask() bool
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -22,6 +22,8 @@ import (
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/mudler/luet/pkg/api/core/types"
|
||||
box "github.com/mudler/luet/pkg/box"
|
||||
fileHelper "github.com/mudler/luet/pkg/helpers/file"
|
||||
"github.com/mudler/luet/pkg/tree"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@@ -90,3 +92,47 @@ func NewLuetFinalizerFromYaml(data []byte) (*LuetFinalizer, error) {
|
||||
}
|
||||
return &p, err
|
||||
}
|
||||
|
||||
func OrderFinalizers(allRepos types.PackageDatabase, toInstall map[string]ArtifactMatch, solution types.PackagesAssertions) ([]*types.Package, error) {
|
||||
var toFinalize []*types.Package
|
||||
if len(toInstall) == 1 {
|
||||
for _, w := range toInstall {
|
||||
if fileHelper.Exists(w.Package.Rel(tree.FinalizerFile)) {
|
||||
// Finalizers needs to run in order and in sequence.
|
||||
ordered, err := solution.Order(allRepos, w.Package.GetFingerPrint())
|
||||
if err != nil {
|
||||
return toFinalize, errors.Wrap(err, "While order a solution for "+w.Package.HumanReadableString())
|
||||
}
|
||||
ORDER:
|
||||
for _, ass := range ordered {
|
||||
if ass.Value {
|
||||
installed, ok := toInstall[ass.Package.GetFingerPrint()]
|
||||
if !ok {
|
||||
// It was a dep already installed in the system, so we can skip it safely
|
||||
continue ORDER
|
||||
}
|
||||
treePackage, err := installed.Repository.GetTree().GetDatabase().FindPackage(ass.Package)
|
||||
if err != nil {
|
||||
return toFinalize, errors.Wrap(err, "Error getting package "+ass.Package.HumanReadableString())
|
||||
}
|
||||
|
||||
toFinalize = append(toFinalize, treePackage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assertions, err := solution.EnsureOrder(allRepos)
|
||||
if err != nil {
|
||||
return toFinalize, err
|
||||
}
|
||||
|
||||
for _, o := range assertions {
|
||||
if o.Value {
|
||||
toFinalize = append(toFinalize, o.Package)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return toFinalize, nil
|
||||
}
|
||||
|
@@ -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)
|
||||
|
||||
@@ -622,7 +638,7 @@ func (l *LuetInstaller) download(syncedRepos Repositories, toDownload map[string
|
||||
|
||||
var wg = new(sync.WaitGroup)
|
||||
|
||||
ctx := l.Options.Context.Copy()
|
||||
ctx := l.Options.Context.Clone()
|
||||
|
||||
// Check if the terminal is big enough to display a progress bar
|
||||
// https://github.com/pterm/pterm/blob/4c725e56bfd9eb38e1c7b9dec187b50b93baa8bd/progressbar_printer.go#L190
|
||||
@@ -800,33 +816,7 @@ func (l *LuetInstaller) computeInstall(o Option, syncedRepos Repositories, cp ty
|
||||
func (l *LuetInstaller) getFinalizers(allRepos types.PackageDatabase, solution types.PackagesAssertions, toInstall map[string]ArtifactMatch, nodeps bool) ([]*types.Package, error) {
|
||||
var toFinalize []*types.Package
|
||||
if !nodeps {
|
||||
// TODO: Lower those errors as l.Options.Context.Warning
|
||||
for _, w := range toInstall {
|
||||
if !fileHelper.Exists(w.Package.Rel(tree.FinalizerFile)) {
|
||||
continue
|
||||
}
|
||||
// Finalizers needs to run in order and in sequence.
|
||||
ordered, err := solution.Order(allRepos, w.Package.GetFingerPrint())
|
||||
if err != nil {
|
||||
return toFinalize, errors.Wrap(err, "While order a solution for "+w.Package.HumanReadableString())
|
||||
}
|
||||
ORDER:
|
||||
for _, ass := range ordered {
|
||||
if ass.Value {
|
||||
installed, ok := toInstall[ass.Package.GetFingerPrint()]
|
||||
if !ok {
|
||||
// It was a dep already installed in the system, so we can skip it safely
|
||||
continue ORDER
|
||||
}
|
||||
treePackage, err := installed.Repository.GetTree().GetDatabase().FindPackage(ass.Package)
|
||||
if err != nil {
|
||||
return toFinalize, errors.Wrap(err, "Error getting package "+ass.Package.HumanReadableString())
|
||||
}
|
||||
|
||||
toFinalize = append(toFinalize, treePackage)
|
||||
}
|
||||
}
|
||||
}
|
||||
return OrderFinalizers(allRepos, toInstall, solution)
|
||||
} else {
|
||||
for _, c := range toInstall {
|
||||
if !fileHelper.Exists(c.Package.Rel(tree.FinalizerFile)) {
|
||||
|
@@ -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())
|
||||
|
||||
|
@@ -457,6 +457,14 @@ func (r *LuetSystemRepository) SetVerify(p bool) {
|
||||
r.LuetRepository.Verify = p
|
||||
}
|
||||
|
||||
func (r *LuetSystemRepository) GetReferenceID() string {
|
||||
return r.LuetRepository.ReferenceID
|
||||
}
|
||||
|
||||
func (r *LuetSystemRepository) SetReferenceID(ref string) {
|
||||
r.LuetRepository.ReferenceID = ref
|
||||
}
|
||||
|
||||
func (r *LuetSystemRepository) GetBackend() compiler.CompilerBackend {
|
||||
return r.Backend
|
||||
}
|
||||
@@ -1051,6 +1059,7 @@ func (r *LuetSystemRepository) fill(r2 *LuetSystemRepository) {
|
||||
r2.SetPriority(r.GetPriority())
|
||||
r2.SetName(r.GetName())
|
||||
r2.SetVerify(r.GetVerify())
|
||||
r2.SetReferenceID(r.GetReferenceID())
|
||||
}
|
||||
|
||||
func (r *LuetSystemRepository) Serialize() (*LuetSystemRepositoryMetadata, LuetSystemRepository) {
|
||||
|
@@ -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 {
|
||||
|
@@ -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)
|
||||
|
75
pkg/tree/compiler_recipe_test.go
Normal file
75
pkg/tree/compiler_recipe_test.go
Normal 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"))
|
||||
})
|
||||
})
|
||||
})
|
7
tests/fixtures/template_requires/collection/build.yaml
vendored
Normal file
7
tests/fixtures/template_requires/collection/build.yaml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
requires:
|
||||
{{ if eq .Values.value "bar" }}
|
||||
- name: "bar"
|
||||
{{ end }}
|
||||
{{ if eq .Values.value "foobar" }}
|
||||
- name: "foobar"
|
||||
{{ end }}
|
5
tests/fixtures/template_requires/collection/collection.yaml
vendored
Normal file
5
tests/fixtures/template_requires/collection/collection.yaml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
packages:
|
||||
- name: "foo"
|
||||
value: "bar"
|
||||
- name: "baz"
|
||||
value: "foobar"
|
7
tests/fixtures/template_requires/collection_cat/build.yaml
vendored
Normal file
7
tests/fixtures/template_requires/collection_cat/build.yaml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
requires:
|
||||
{{ if eq .Values.value "bar" }}
|
||||
- name: "bar"
|
||||
{{ end }}
|
||||
{{ if eq .Values.value "foobar" }}
|
||||
- name: "foobar"
|
||||
{{ end }}
|
7
tests/fixtures/template_requires/collection_cat/collection.yaml
vendored
Normal file
7
tests/fixtures/template_requires/collection_cat/collection.yaml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
packages:
|
||||
- name: "foo"
|
||||
category: "test"
|
||||
value: "bar"
|
||||
- name: "baz"
|
||||
value: "foobar"
|
||||
category: "test"
|
7
tests/fixtures/template_requires/package/build.yaml
vendored
Normal file
7
tests/fixtures/template_requires/package/build.yaml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
requires:
|
||||
{{ if eq .Values.value "bar" }}
|
||||
- name: "bar"
|
||||
{{ end }}
|
||||
{{ if eq .Values.value "foobar" }}
|
||||
- name: "foobar"
|
||||
{{ end }}
|
2
tests/fixtures/template_requires/package/definition.yaml
vendored
Normal file
2
tests/fixtures/template_requires/package/definition.yaml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
name: "bazbaz"
|
||||
value: "foobar"
|
1
tests/fixtures/template_requires/references/build.yaml
vendored
Normal file
1
tests/fixtures/template_requires/references/build.yaml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
image: alpine
|
3
tests/fixtures/template_requires/references/collection.yaml
vendored
Normal file
3
tests/fixtures/template_requires/references/collection.yaml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
packages:
|
||||
- name: "foobar"
|
||||
- name: "bar"
|
@@ -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() {
|
||||
|
@@ -1,5 +1,7 @@
|
||||
package toposort
|
||||
|
||||
import "sort"
|
||||
|
||||
type Graph struct {
|
||||
nodes []string
|
||||
outputs map[string]map[string]int
|
||||
@@ -59,7 +61,7 @@ func (g *Graph) RemoveEdge(from, to string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (g *Graph) Toposort() ([]string, bool) {
|
||||
func (g *Graph) toposort(stable bool) ([]string, bool) {
|
||||
L := make([]string, 0, len(g.nodes))
|
||||
S := make([]string, 0, len(g.nodes))
|
||||
|
||||
@@ -69,6 +71,10 @@ func (g *Graph) Toposort() ([]string, bool) {
|
||||
}
|
||||
}
|
||||
|
||||
if stable {
|
||||
sort.Strings(S)
|
||||
}
|
||||
|
||||
for len(S) > 0 {
|
||||
var n string
|
||||
n, S = S[0], S[1:]
|
||||
@@ -79,6 +85,10 @@ func (g *Graph) Toposort() ([]string, bool) {
|
||||
ms[i-1] = m
|
||||
}
|
||||
|
||||
if stable {
|
||||
sort.Strings(ms)
|
||||
}
|
||||
|
||||
for _, m := range ms {
|
||||
g.unsafeRemoveEdge(n, m)
|
||||
|
||||
@@ -99,3 +109,11 @@ func (g *Graph) Toposort() ([]string, bool) {
|
||||
|
||||
return L, true
|
||||
}
|
||||
|
||||
func (g *Graph) Toposort() ([]string, bool) {
|
||||
return g.toposort(false)
|
||||
}
|
||||
|
||||
func (g *Graph) ToposortStable() ([]string, bool) {
|
||||
return g.toposort(true)
|
||||
}
|
4
vendor/modules.txt
vendored
4
vendor/modules.txt
vendored
@@ -370,7 +370,6 @@ github.com/pelletier/go-toml
|
||||
github.com/peterbourgon/diskv
|
||||
# github.com/philopon/go-toposort v0.0.0-20170620085441-9be86dbd762f
|
||||
## explicit
|
||||
github.com/philopon/go-toposort
|
||||
# github.com/pkg/errors v0.9.1
|
||||
## explicit
|
||||
github.com/pkg/errors
|
||||
@@ -402,6 +401,9 @@ github.com/rogpeppe/go-internal/internal/syscall/windows
|
||||
github.com/rogpeppe/go-internal/internal/syscall/windows/sysdll
|
||||
github.com/rogpeppe/go-internal/lockedfile
|
||||
github.com/rogpeppe/go-internal/lockedfile/internal/filelock
|
||||
# github.com/scrohde/go-toposort v0.0.0-20170629203416-a9a902e065a3
|
||||
## explicit
|
||||
github.com/scrohde/go-toposort
|
||||
# github.com/shopspring/decimal v1.2.0
|
||||
github.com/shopspring/decimal
|
||||
# github.com/sirupsen/logrus v1.8.1
|
||||
|
Reference in New Issue
Block a user