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"
2020-02-12 09:20:07 +00:00
"github.com/mudler/luet/pkg/config"
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"
)
2020-02-12 10:21:30 +00:00
type LuetInstallerOptions struct {
SolverOptions config . LuetSolverOptions
Concurrency int
2020-02-18 17:37:56 +00:00
NoDeps bool
OnlyDeps bool
Force bool
2020-02-12 10:21:30 +00:00
}
2019-11-22 20:01:38 +00:00
type LuetInstaller struct {
PackageRepositories Repositories
2020-02-12 10:21:30 +00:00
Options LuetInstallerOptions
2019-11-22 20:01:38 +00:00
}
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
}
2020-02-12 10:21:30 +00:00
func NewLuetInstaller ( opts LuetInstallerOptions ) Installer {
return & LuetInstaller { Options : opts }
2019-11-22 20:01:38 +00:00
}
2019-11-29 18:01:56 +00:00
func ( l * LuetInstaller ) Upgrade ( s * System ) error {
2020-01-02 17:31:25 +00:00
syncedRepos , err := l . SyncRepositories ( true )
if err != nil {
return err
}
2019-11-29 18:01:56 +00:00
// First match packages against repositories by priority
2019-12-31 11:48:12 +00:00
allRepos := pkg . NewInMemoryDatabase ( false )
2020-01-02 17:31:25 +00:00
syncedRepos . SyncDatabase ( allRepos )
2019-11-29 18:01:56 +00:00
// compute a "big" world
2020-02-12 10:21:30 +00:00
solv := solver . NewResolver ( s . Database , allRepos , pkg . NewInMemoryDatabase ( false ) , l . Options . SolverOptions . Resolver ( ) )
2020-02-27 17:26:48 +00:00
uninstall , solution , err := solv . Upgrade ( false )
2019-11-29 18:01:56 +00:00
if err != nil {
return errors . Wrap ( err , "Failed solving solution for upgrade" )
}
2020-02-27 17:26:48 +00:00
// We don't want any conflict with the installed to raise during the upgrade.
// In this way we both force uninstalls and we avoid to check with conflicts
// against the current system state which is pending to deletion
// E.g. you can't check for conflicts for an upgrade of a new version of A
// if the old A results installed in the system. This is due to the fact that
// now the solver enforces the constraints and explictly denies two packages
// of the same version installed.
forced := false
if l . Options . Force {
forced = true
}
l . Options . Force = true
2019-11-29 18:01:56 +00:00
for _ , u := range uninstall {
2020-02-27 17:26:48 +00:00
Info ( ":package: Marked for deletion" , u . HumanReadableString ( ) )
2019-11-29 18:01:56 +00:00
err := l . Uninstall ( u , s )
2020-02-18 17:37:56 +00:00
if err != nil && ! l . Options . Force {
Error ( "Failed uninstall for " , u . HumanReadableString ( ) )
return errors . Wrap ( err , "uninstalling " + u . HumanReadableString ( ) )
2019-11-29 18:01:56 +00:00
}
2020-02-27 17:26:48 +00:00
2019-11-29 18:01:56 +00:00
}
2020-02-27 17:26:48 +00:00
l . Options . Force = forced
2019-11-29 18:01:56 +00:00
toInstall := [ ] pkg . Package { }
for _ , assertion := range solution {
if assertion . Value {
toInstall = append ( toInstall , assertion . Package )
}
}
return l . Install ( toInstall , s )
}
2019-12-31 15:50:44 +00:00
func ( l * LuetInstaller ) SyncRepositories ( inMemory bool ) ( Repositories , error ) {
2019-12-31 11:48:12 +00:00
Spinner ( 32 )
defer SpinnerStop ( )
syncedRepos := Repositories { }
for _ , r := range l . PackageRepositories {
2020-01-12 22:31:43 +00:00
repo , err := r . Sync ( false )
2019-12-31 11:48:12 +00:00
if err != nil {
2019-12-31 15:50:44 +00:00
return nil , errors . Wrap ( err , "Failed syncing repository: " + r . GetName ( ) )
2019-12-31 11:48:12 +00:00
}
syncedRepos = append ( syncedRepos , repo )
}
2020-01-01 10:53:50 +00:00
2019-12-31 11:48:12 +00:00
// compute what to install and from where
sort . Sort ( syncedRepos )
2019-12-31 15:50:44 +00:00
if ! inMemory {
l . PackageRepositories = syncedRepos
}
2019-12-31 11:48:12 +00:00
2019-12-31 15:50:44 +00:00
return syncedRepos , nil
2019-12-31 11:48:12 +00:00
}
func ( l * LuetInstaller ) Install ( cp [ ] pkg . Package , s * System ) error {
2020-01-01 10:53:50 +00:00
var p [ ] pkg . Package
// Check if the package is installed first
for _ , pi := range cp {
vers , _ := s . Database . FindPackageVersions ( pi )
if len ( vers ) >= 1 {
2020-02-18 17:37:56 +00:00
Warning ( "Filtering out package " + pi . HumanReadableString ( ) + ", it has other versions already installed. Uninstall one of them first " )
2020-01-01 10:53:50 +00:00
continue
//return errors.New("Package " + pi.GetFingerPrint() + " has other versions already installed. Uninstall one of them first: " + strings.Join(vers, " "))
}
p = append ( p , pi )
}
if len ( p ) == 0 {
Warning ( "No package to install, bailing out with no errors" )
return nil
}
2019-11-22 20:01:38 +00:00
// First get metas from all repos (and decodes trees)
2019-12-31 15:50:44 +00:00
syncedRepos , err := l . SyncRepositories ( true )
if err != nil {
return err
}
2019-11-22 20:01:38 +00:00
// 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 )
2019-12-31 15:50:44 +00:00
syncedRepos . SyncDatabase ( allRepos )
2020-02-04 19:15:59 +00:00
p = syncedRepos . ResolveSelectors ( p )
2020-02-18 17:37:56 +00:00
toInstall := map [ string ] ArtifactMatch { }
var packagesToInstall [ ] pkg . Package
2020-02-12 10:21:30 +00:00
2020-02-18 17:37:56 +00:00
var solution solver . PackagesAssertions
if ! l . Options . NoDeps {
solv := solver . NewResolver ( s . Database , allRepos , pkg . NewInMemoryDatabase ( false ) , l . Options . SolverOptions . Resolver ( ) )
solution , err = solv . Install ( p )
if err != nil && ! l . Options . Force {
return errors . Wrap ( err , "Failed solving solution for package" )
}
// Gathers things to install
for _ , assertion := range solution {
if assertion . Value {
packagesToInstall = append ( packagesToInstall , assertion . Package )
}
}
} else if ! l . Options . OnlyDeps {
for _ , currentPack := range p {
packagesToInstall = append ( packagesToInstall , currentPack )
}
2019-11-22 20:01:38 +00:00
}
2019-12-31 15:50:44 +00:00
2019-11-22 20:01:38 +00:00
// Gathers things to install
2020-02-18 17:37:56 +00:00
for _ , currentPack := range packagesToInstall {
matches := syncedRepos . PackageMatches ( [ ] pkg . Package { currentPack } )
if len ( matches ) == 0 {
return errors . New ( "Failed matching solutions against repository for " + currentPack . HumanReadableString ( ) + " where are definitions coming from?!" )
}
A :
for _ , artefact := range matches [ 0 ] . Repo . GetIndex ( ) {
if artefact . GetCompileSpec ( ) . GetPackage ( ) == nil {
return errors . New ( "Package in compilespec empty" )
2019-11-23 14:42:05 +00:00
2020-02-18 17:37:56 +00:00
}
if matches [ 0 ] . Package . Matches ( artefact . GetCompileSpec ( ) . GetPackage ( ) ) {
// Filter out already installed
if _ , err := s . Database . FindPackage ( currentPack ) ; err != nil {
toInstall [ currentPack . GetFingerPrint ( ) ] = ArtifactMatch { Package : currentPack , Artifact : artefact , Repository : matches [ 0 ] . Repo }
2019-11-23 14:42:05 +00:00
}
2020-02-18 17:37:56 +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 )
2020-02-12 10:21:30 +00:00
for i := 0 ; i < l . Options . Concurrency ; i ++ {
2019-11-22 20:01:38 +00:00
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 ( )
2020-02-27 17:26:48 +00:00
for _ , c := range toInstall {
// Annotate to the system that the package was installed
_ , err := s . Database . CreatePackage ( c . Package )
if err != nil && ! l . Options . Force {
return errors . Wrap ( err , "Failed creating package" )
}
}
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 ( ) )
2020-02-27 17:26:48 +00:00
ORDER :
2019-11-22 21:23:05 +00:00
for _ , ass := range ordered {
2019-11-29 18:01:49 +00:00
if ass . Value {
2020-02-27 17:26:48 +00:00
2019-11-22 21:23:05 +00:00
installed , ok := toInstall [ ass . Package . GetFingerPrint ( ) ]
if ! ok {
2020-02-27 17:26:48 +00:00
// It was a dep already installed in the system, so we can skip it safely
continue ORDER
2019-11-22 21:23:05 +00:00
}
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 {
2020-02-18 17:37:56 +00:00
return errors . Wrap ( err , "Error getting package " + ass . Package . HumanReadableString ( ) )
2019-11-22 21:23:05 +00:00
}
2019-11-23 17:58:09 +00:00
if helpers . Exists ( treePackage . Rel ( tree . FinalizerFile ) ) {
2020-02-18 17:37:56 +00:00
Info ( "Executing finalizer for " + ass . Package . HumanReadableString ( ) )
2019-11-23 17:58:09 +00:00
finalizerRaw , err := ioutil . ReadFile ( treePackage . Rel ( tree . FinalizerFile ) )
2020-02-18 17:37:56 +00:00
if err != nil && ! l . Options . Force {
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 )
2020-02-18 17:37:56 +00:00
if err != nil && ! l . Options . Force {
2019-11-23 17:58:09 +00:00
return errors . Wrap ( err , "Error reading finalizer " + treePackage . Rel ( tree . FinalizerFile ) )
}
err = finalizer . RunInstall ( )
2020-02-18 17:37:56 +00:00
if err != nil && ! l . Options . Force {
2019-11-23 17:58:09 +00:00
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 )
2020-02-02 23:58:55 +00:00
if err != nil {
return errors . Wrap ( err , "Error on download artifact" )
}
2019-11-23 21:41:51 +00:00
2019-12-29 12:58:49 +00:00
err = artifact . Verify ( )
2020-02-18 17:37:56 +00:00
if err != nil && ! l . Options . Force {
2019-12-29 12:58:49 +00:00
return errors . Wrap ( err , "Artifact integrity check failure" )
}
2019-12-28 15:32:32 +00:00
files , err := artifact . FileList ( )
2020-02-18 17:37:56 +00:00
if err != nil && ! l . Options . Force {
2019-12-31 11:48:12 +00:00
return errors . Wrap ( err , "Could not open package archive" )
2019-11-23 21:41:51 +00:00
}
2019-12-28 15:32:32 +00:00
err = artifact . Unpack ( s . Target , true )
2020-02-18 17:37:56 +00:00
if err != nil && ! l . Options . Force {
2019-11-22 22:12:03 +00:00
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 )
2020-02-18 17:37:56 +00:00
if err != nil && ! l . Options . Force {
2019-11-22 20:01:38 +00:00
//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
}
2020-02-18 17:37:56 +00:00
if err == nil {
Info ( ":package: " , p . Package . HumanReadableString ( ) , "installed" )
} else if err != nil && l . Options . Force {
Info ( ":package: " , p . Package . HumanReadableString ( ) , "installed with failures (force install)" )
}
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 {
2020-02-27 17:26:48 +00:00
// compute uninstall from all world - remove packages in parallel - run uninstall finalizer (in order) TODO - mark the uninstallation in db
2019-11-23 23:16:12 +00:00
// Get installed definition
2020-02-18 17:37:56 +00:00
2020-02-27 17:26:48 +00:00
checkConflicts := true
if l . Options . Force == true {
checkConflicts = false
}
2020-02-18 17:37:56 +00:00
if ! l . Options . NoDeps {
solv := solver . NewResolver ( s . Database , s . Database , pkg . NewInMemoryDatabase ( false ) , l . Options . SolverOptions . Resolver ( ) )
2020-02-27 17:26:48 +00:00
solution , err := solv . Uninstall ( p , checkConflicts )
2020-02-18 17:37:56 +00:00
if err != nil && ! l . Options . Force {
return errors . Wrap ( err , "Could not solve the uninstall constraints. Tip: try with --solver-type qlearning or with --force, or by removing packages excluding their dependencies with --nodeps" )
}
for _ , p := range solution {
Info ( "Uninstalling" , p . HumanReadableString ( ) )
err := l . uninstall ( p , s )
if err != nil && ! l . Options . Force {
return errors . Wrap ( err , "Uninstall failed" )
}
}
} else {
Info ( "Uninstalling" , p . HumanReadableString ( ) , "without deps" )
2019-11-25 19:02:18 +00:00
err := l . uninstall ( p , s )
2020-02-18 17:37:56 +00:00
if err != nil && ! l . Options . Force {
2019-11-25 19:02:18 +00:00
return errors . Wrap ( err , "Uninstall failed" )
}
2020-02-18 17:37:56 +00:00
Info ( ":package: " , p . HumanReadableString ( ) , "uninstalled" )
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 }