2019-11-22 20:01:38 +00:00
|
|
|
// 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/>.
|
|
|
|
|
|
|
|
package installer
|
|
|
|
|
|
|
|
import (
|
2019-11-22 21:23:05 +00:00
|
|
|
"io/ioutil"
|
2019-11-22 22:12:03 +00:00
|
|
|
"os"
|
2019-11-22 21:23:05 +00:00
|
|
|
"os/exec"
|
2019-11-23 23:16:12 +00:00
|
|
|
"path/filepath"
|
2019-11-22 20:01:38 +00:00
|
|
|
"sort"
|
|
|
|
"sync"
|
|
|
|
|
2019-11-22 21:23:05 +00:00
|
|
|
"github.com/ghodss/yaml"
|
2019-11-22 20:01:38 +00:00
|
|
|
compiler "github.com/mudler/luet/pkg/compiler"
|
2019-11-22 22:12:03 +00:00
|
|
|
"github.com/mudler/luet/pkg/helpers"
|
2019-11-22 20:01:38 +00:00
|
|
|
. "github.com/mudler/luet/pkg/logger"
|
|
|
|
pkg "github.com/mudler/luet/pkg/package"
|
|
|
|
"github.com/mudler/luet/pkg/solver"
|
2019-11-22 22:24:22 +00:00
|
|
|
"github.com/mudler/luet/pkg/tree"
|
2019-11-22 20:01:38 +00:00
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
|
|
|
type LuetInstaller struct {
|
|
|
|
PackageRepositories Repositories
|
|
|
|
Concurrency int
|
|
|
|
}
|
|
|
|
|
|
|
|
type ArtifactMatch struct {
|
|
|
|
Package pkg.Package
|
|
|
|
Artifact compiler.Artifact
|
|
|
|
Repository Repository
|
|
|
|
}
|
|
|
|
|
2019-11-22 21:23:05 +00:00
|
|
|
type LuetFinalizer struct {
|
|
|
|
Install []string `json:"install"`
|
|
|
|
Uninstall []string `json:"uninstall"` // TODO: Where to store?
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *LuetFinalizer) RunInstall() error {
|
|
|
|
for _, c := range f.Install {
|
|
|
|
Debug("finalizer:", "sh", "-c", c)
|
|
|
|
cmd := exec.Command("sh", "-c", c)
|
|
|
|
stdoutStderr, err := cmd.CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "Failed running command: "+string(stdoutStderr))
|
|
|
|
}
|
2019-12-12 22:48:29 +00:00
|
|
|
Info(string(stdoutStderr))
|
2019-11-22 21:23:05 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: We don't store uninstall finalizers ?!
|
|
|
|
func (f *LuetFinalizer) RunUnInstall() error {
|
|
|
|
for _, c := range f.Install {
|
|
|
|
Debug("finalizer:", "sh", "-c", c)
|
|
|
|
cmd := exec.Command("sh", "-c", c)
|
|
|
|
stdoutStderr, err := cmd.CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "Failed running command: "+string(stdoutStderr))
|
|
|
|
}
|
2019-12-12 22:48:29 +00:00
|
|
|
Info(string(stdoutStderr))
|
2019-11-22 21:23:05 +00:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewLuetFinalizerFromYaml(data []byte) (*LuetFinalizer, error) {
|
|
|
|
var p LuetFinalizer
|
|
|
|
err := yaml.Unmarshal(data, &p)
|
|
|
|
if err != nil {
|
|
|
|
return &p, err
|
|
|
|
}
|
|
|
|
return &p, err
|
|
|
|
}
|
|
|
|
|
2019-11-22 20:01:38 +00:00
|
|
|
func NewLuetInstaller(concurrency int) Installer {
|
|
|
|
return &LuetInstaller{Concurrency: concurrency}
|
|
|
|
}
|
|
|
|
|
2019-11-29 18:01:56 +00:00
|
|
|
func (l *LuetInstaller) Upgrade(s *System) error {
|
|
|
|
Spinner(32)
|
|
|
|
defer SpinnerStop()
|
|
|
|
syncedRepos := Repositories{}
|
|
|
|
for _, r := range l.PackageRepositories {
|
|
|
|
repo, err := r.Sync()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "Failed syncing repository: "+r.GetName())
|
|
|
|
}
|
|
|
|
syncedRepos = append(syncedRepos, repo)
|
|
|
|
}
|
|
|
|
|
|
|
|
// compute what to install and from where
|
|
|
|
sort.Sort(syncedRepos)
|
|
|
|
|
|
|
|
// First match packages against repositories by priority
|
|
|
|
// matches := syncedRepos.PackageMatches(p)
|
|
|
|
|
|
|
|
// compute a "big" world
|
|
|
|
allRepos := pkg.NewInMemoryDatabase(false)
|
|
|
|
syncedRepos.SyncDatabase(allRepos)
|
|
|
|
solv := solver.NewSolver(s.Database, allRepos, pkg.NewInMemoryDatabase(false))
|
|
|
|
uninstall, solution, err := solv.Upgrade()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "Failed solving solution for upgrade")
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, u := range uninstall {
|
|
|
|
err := l.Uninstall(u, s)
|
|
|
|
if err != nil {
|
|
|
|
Warning("Failed uninstall for ", u.GetFingerPrint())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
toInstall := []pkg.Package{}
|
|
|
|
for _, assertion := range solution {
|
|
|
|
if assertion.Value {
|
|
|
|
toInstall = append(toInstall, assertion.Package)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return l.Install(toInstall, s)
|
|
|
|
}
|
|
|
|
|
2019-11-22 20:01:38 +00:00
|
|
|
func (l *LuetInstaller) Install(p []pkg.Package, s *System) error {
|
|
|
|
// First get metas from all repos (and decodes trees)
|
|
|
|
|
|
|
|
Spinner(32)
|
|
|
|
defer SpinnerStop()
|
|
|
|
syncedRepos := Repositories{}
|
|
|
|
for _, r := range l.PackageRepositories {
|
2019-11-22 22:12:03 +00:00
|
|
|
repo, err := r.Sync()
|
2019-11-22 20:01:38 +00:00
|
|
|
if err != nil {
|
2019-11-23 14:42:05 +00:00
|
|
|
return errors.Wrap(err, "Failed syncing repository: "+r.GetName())
|
2019-11-22 20:01:38 +00:00
|
|
|
}
|
|
|
|
syncedRepos = append(syncedRepos, repo)
|
|
|
|
}
|
|
|
|
|
|
|
|
// compute what to install and from where
|
|
|
|
sort.Sort(syncedRepos)
|
|
|
|
|
|
|
|
// First match packages against repositories by priority
|
2019-11-22 21:23:05 +00:00
|
|
|
// matches := syncedRepos.PackageMatches(p)
|
2019-11-22 20:01:38 +00:00
|
|
|
|
|
|
|
// compute a "big" world
|
2019-11-29 18:01:52 +00:00
|
|
|
allRepos := pkg.NewInMemoryDatabase(false)
|
|
|
|
syncedRepos.SyncDatabase(allRepos)
|
2019-11-22 20:01:38 +00:00
|
|
|
|
2019-11-29 18:01:52 +00:00
|
|
|
solv := solver.NewSolver(s.Database, allRepos, pkg.NewInMemoryDatabase(false))
|
|
|
|
solution, err := solv.Install(p)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "Failed solving solution for package")
|
2019-11-22 20:01:38 +00:00
|
|
|
}
|
|
|
|
// Gathers things to install
|
2019-11-22 21:23:05 +00:00
|
|
|
toInstall := map[string]ArtifactMatch{}
|
2019-11-22 20:01:38 +00:00
|
|
|
for _, assertion := range solution {
|
2019-11-29 18:01:49 +00:00
|
|
|
if assertion.Value {
|
2019-11-22 20:01:38 +00:00
|
|
|
matches := syncedRepos.PackageMatches([]pkg.Package{assertion.Package})
|
|
|
|
if len(matches) != 1 {
|
|
|
|
return errors.New("Failed matching solutions against repository - where are definitions coming from?!")
|
|
|
|
}
|
2019-11-22 21:23:05 +00:00
|
|
|
A:
|
2019-11-22 20:01:38 +00:00
|
|
|
for _, artefact := range matches[0].Repo.GetIndex() {
|
2019-11-23 14:42:05 +00:00
|
|
|
if artefact.GetCompileSpec().GetPackage() == nil {
|
|
|
|
return errors.New("Package in compilespec empty")
|
|
|
|
|
|
|
|
}
|
2019-11-22 20:01:38 +00:00
|
|
|
if matches[0].Package.Matches(artefact.GetCompileSpec().GetPackage()) {
|
2019-11-29 18:01:56 +00:00
|
|
|
// Filter out already installed
|
|
|
|
if _, err := s.Database.FindPackage(assertion.Package); err != nil {
|
|
|
|
toInstall[assertion.Package.GetFingerPrint()] = ArtifactMatch{Package: assertion.Package, Artifact: artefact, Repository: matches[0].Repo}
|
|
|
|
}
|
2019-11-22 21:23:05 +00:00
|
|
|
break A
|
2019-11-22 20:01:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-22 21:23:05 +00:00
|
|
|
// Install packages into rootfs in parallel.
|
2019-11-22 20:01:38 +00:00
|
|
|
all := make(chan ArtifactMatch)
|
|
|
|
|
|
|
|
var wg = new(sync.WaitGroup)
|
|
|
|
for i := 0; i < l.Concurrency; i++ {
|
|
|
|
wg.Add(1)
|
2019-11-22 21:23:05 +00:00
|
|
|
go l.installerWorker(i, wg, all, s)
|
2019-11-22 20:01:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, c := range toInstall {
|
|
|
|
all <- c
|
|
|
|
}
|
|
|
|
close(all)
|
|
|
|
wg.Wait()
|
|
|
|
|
2019-11-22 21:23:05 +00:00
|
|
|
executedFinalizer := map[string]bool{}
|
|
|
|
|
|
|
|
// TODO: Lower those errors as warning
|
2019-11-29 18:01:52 +00:00
|
|
|
for _, w := range p {
|
2019-11-22 21:23:05 +00:00
|
|
|
// Finalizers needs to run in order and in sequence.
|
2019-11-29 18:01:52 +00:00
|
|
|
ordered := solution.Order(allRepos, w.GetFingerPrint())
|
2019-11-22 21:23:05 +00:00
|
|
|
for _, ass := range ordered {
|
2019-11-29 18:01:49 +00:00
|
|
|
if ass.Value {
|
2019-11-22 21:23:05 +00:00
|
|
|
// Annotate to the system that the package was installed
|
|
|
|
// TODO: Annotate also files that belong to the package, somewhere to uninstall
|
|
|
|
if _, err := s.Database.FindPackage(ass.Package); err == nil {
|
2019-11-25 19:02:18 +00:00
|
|
|
err := s.Database.UpdatePackage(ass.Package)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "Failed updating package")
|
|
|
|
}
|
2019-11-22 21:23:05 +00:00
|
|
|
} else {
|
2019-11-25 19:02:18 +00:00
|
|
|
_, err := s.Database.CreatePackage(ass.Package)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "Failed creating package")
|
|
|
|
}
|
2019-11-22 21:23:05 +00:00
|
|
|
}
|
|
|
|
installed, ok := toInstall[ass.Package.GetFingerPrint()]
|
|
|
|
if !ok {
|
|
|
|
return errors.New("Couldn't find ArtifactMatch for " + ass.Package.GetFingerPrint())
|
|
|
|
}
|
|
|
|
|
2019-11-29 18:01:52 +00:00
|
|
|
treePackage, err := installed.Repository.GetTree().GetDatabase().FindPackage(ass.Package)
|
2019-11-22 21:23:05 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "Error getting package "+ass.Package.GetFingerPrint())
|
|
|
|
}
|
2019-11-23 17:58:09 +00:00
|
|
|
if helpers.Exists(treePackage.Rel(tree.FinalizerFile)) {
|
|
|
|
Info("Executing finalizer for " + ass.Package.GetName())
|
|
|
|
finalizerRaw, err := ioutil.ReadFile(treePackage.Rel(tree.FinalizerFile))
|
2019-11-22 21:23:05 +00:00
|
|
|
if err != nil {
|
2019-11-23 17:58:09 +00:00
|
|
|
return errors.Wrap(err, "Error reading file "+treePackage.Rel(tree.FinalizerFile))
|
2019-11-22 21:23:05 +00:00
|
|
|
}
|
2019-11-23 17:58:09 +00:00
|
|
|
if _, exists := executedFinalizer[ass.Package.GetFingerPrint()]; !exists {
|
|
|
|
finalizer, err := NewLuetFinalizerFromYaml(finalizerRaw)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "Error reading finalizer "+treePackage.Rel(tree.FinalizerFile))
|
|
|
|
}
|
|
|
|
err = finalizer.RunInstall()
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "Error executing install finalizer "+treePackage.Rel(tree.FinalizerFile))
|
|
|
|
}
|
|
|
|
executedFinalizer[ass.Package.GetFingerPrint()] = true
|
2019-11-22 21:23:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2019-11-22 20:01:38 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *LuetInstaller) installPackage(a ArtifactMatch, s *System) error {
|
|
|
|
|
2019-11-22 22:12:03 +00:00
|
|
|
artifact, err := a.Repository.Client().DownloadArtifact(a.Artifact)
|
|
|
|
defer os.Remove(artifact.GetPath())
|
2019-11-23 21:41:51 +00:00
|
|
|
|
2019-12-28 15:32:32 +00:00
|
|
|
files, err := artifact.FileList()
|
2019-11-23 21:41:51 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "Could not open package archive")
|
|
|
|
}
|
|
|
|
|
2019-12-28 15:32:32 +00:00
|
|
|
err = artifact.Unpack(s.Target, true)
|
2019-11-22 22:12:03 +00:00
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "Error met while unpacking rootfs")
|
|
|
|
}
|
2019-11-23 21:41:51 +00:00
|
|
|
|
2019-11-22 22:12:03 +00:00
|
|
|
// First create client and download
|
|
|
|
// Then unpack to system
|
2019-11-25 19:02:18 +00:00
|
|
|
return s.Database.SetPackageFiles(&pkg.PackageFile{PackageFingerprint: a.Package.GetFingerPrint(), Files: files})
|
2019-11-22 20:01:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (l *LuetInstaller) installerWorker(i int, wg *sync.WaitGroup, c <-chan ArtifactMatch, s *System) error {
|
|
|
|
defer wg.Done()
|
|
|
|
|
|
|
|
for p := range c {
|
2019-11-23 15:28:50 +00:00
|
|
|
// TODO: Keep trace of what was added from the tar, and save it into system
|
2019-11-22 20:01:38 +00:00
|
|
|
err := l.installPackage(p, s)
|
|
|
|
if err != nil {
|
|
|
|
//TODO: Uninstall, rollback.
|
2019-11-25 19:02:18 +00:00
|
|
|
Fatal("Failed installing package "+p.Package.GetName(), err.Error())
|
|
|
|
return errors.Wrap(err, "Failed installing package "+p.Package.GetName())
|
2019-11-22 20:01:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2019-11-23 23:16:12 +00:00
|
|
|
|
|
|
|
func (l *LuetInstaller) uninstall(p pkg.Package, s *System) error {
|
|
|
|
files, err := s.Database.GetPackageFiles(p)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "Failed getting installed files")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove from target
|
|
|
|
for _, f := range files {
|
|
|
|
target := filepath.Join(s.Target, f)
|
|
|
|
Info("Removing", target)
|
|
|
|
err := os.Remove(target)
|
|
|
|
if err != nil {
|
2019-11-25 19:02:18 +00:00
|
|
|
Warning("Failed removing file (not present in the system target ?)", target)
|
2019-11-23 23:16:12 +00:00
|
|
|
}
|
|
|
|
}
|
2019-11-25 19:02:18 +00:00
|
|
|
err = s.Database.RemovePackageFiles(p)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "Failed removing package files from database")
|
|
|
|
}
|
2019-11-23 23:16:12 +00:00
|
|
|
err = s.Database.RemovePackage(p)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "Failed removing package from database")
|
|
|
|
}
|
|
|
|
|
|
|
|
Info(p.GetFingerPrint(), "Removed")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *LuetInstaller) Uninstall(p pkg.Package, s *System) error {
|
2019-11-22 20:01:38 +00:00
|
|
|
// compute uninstall from all world - remove packages in parallel - run uninstall finalizer (in order) - mark the uninstallation in db
|
2019-11-23 23:16:12 +00:00
|
|
|
// Get installed definition
|
|
|
|
|
2019-11-29 18:01:52 +00:00
|
|
|
solv := solver.NewSolver(s.Database, s.Database, pkg.NewInMemoryDatabase(false))
|
|
|
|
solution, err := solv.Uninstall(p)
|
2019-11-25 19:02:18 +00:00
|
|
|
if err != nil {
|
2019-11-29 18:01:52 +00:00
|
|
|
return errors.Wrap(err, "Uninstall failed")
|
2019-11-23 23:16:12 +00:00
|
|
|
}
|
|
|
|
for _, p := range solution {
|
|
|
|
Info("Uninstalling", p.GetFingerPrint())
|
2019-11-25 19:02:18 +00:00
|
|
|
err := l.uninstall(p, s)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "Uninstall failed")
|
|
|
|
}
|
2019-11-23 23:16:12 +00:00
|
|
|
}
|
2019-11-22 20:01:38 +00:00
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *LuetInstaller) Repositories(r []Repository) { l.PackageRepositories = r }
|