2021-10-20 22:13:02 +00:00
// Copyright © 2019-2021 Ettore Di Giacinto <mudler@gentoo.org>
2019-11-22 20:01:38 +00:00
//
// 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 (
2020-12-03 16:25:29 +00:00
"fmt"
2019-11-22 21:23:05 +00:00
"io/ioutil"
2019-11-22 22:12:03 +00:00
"os"
2019-11-23 23:16:12 +00:00
"path/filepath"
2019-11-22 20:01:38 +00:00
"sort"
2020-02-27 22:14:36 +00:00
"strings"
2019-11-22 20:01:38 +00:00
"sync"
2022-04-22 09:41:41 +00:00
"github.com/hashicorp/go-multierror"
2021-10-20 22:13:02 +00:00
"github.com/mudler/luet/pkg/api/core/config"
2021-12-17 14:21:03 +00:00
"github.com/mudler/luet/pkg/api/core/logger"
2022-01-06 22:57:56 +00:00
"github.com/mudler/luet/pkg/helpers"
2021-10-20 22:13:02 +00:00
2021-10-24 15:43:33 +00:00
"github.com/mudler/luet/pkg/api/core/bus"
2021-10-19 11:05:25 +00:00
"github.com/mudler/luet/pkg/api/core/types"
2021-10-19 15:06:48 +00:00
artifact "github.com/mudler/luet/pkg/api/core/types/artifact"
2022-01-06 22:57:56 +00:00
pkg "github.com/mudler/luet/pkg/database"
2021-06-01 14:43:31 +00:00
fileHelper "github.com/mudler/luet/pkg/helpers/file"
2019-11-22 20:01:38 +00:00
"github.com/mudler/luet/pkg/solver"
2021-10-19 20:26:23 +00:00
"github.com/pterm/pterm"
2019-11-22 20:01:38 +00:00
"github.com/pkg/errors"
)
2020-02-12 10:21:30 +00:00
type LuetInstallerOptions struct {
2021-10-20 22:13:02 +00:00
SolverOptions types . LuetSolverOptions
2020-07-12 13:27:50 +00:00
Concurrency int
NoDeps bool
OnlyDeps bool
Force bool
PreserveSystemEssentialData bool
FullUninstall , FullCleanUninstall bool
CheckConflicts bool
SolverUpgrade , RemoveUnavailableOnUpgrade , UpgradeNewRevisions bool
2020-11-22 19:16:04 +00:00
Ask bool
2021-03-07 10:39:19 +00:00
DownloadOnly bool
2021-10-09 15:36:13 +00:00
Relaxed bool
2021-10-19 11:05:25 +00:00
PackageRepositories types . LuetRepositories
2021-12-12 09:42:51 +00:00
AutoOSCheck bool
2021-10-20 22:13:02 +00:00
2021-12-17 14:21:03 +00:00
Context types . Context
2020-02-12 10:21:30 +00:00
}
2019-11-22 20:01:38 +00:00
type LuetInstaller struct {
2020-02-12 10:21:30 +00:00
Options LuetInstallerOptions
2019-11-22 20:01:38 +00:00
}
type ArtifactMatch struct {
2022-01-06 22:57:56 +00:00
Package * types . Package
2021-04-12 17:00:36 +00:00
Artifact * artifact . PackageArtifact
2021-12-24 10:44:50 +00:00
Repository Repository
2019-11-22 20:01:38 +00:00
}
2021-04-12 17:00:36 +00:00
func NewLuetInstaller ( opts LuetInstallerOptions ) * LuetInstaller {
2020-02-12 10:21:30 +00:00
return & LuetInstaller { Options : opts }
2019-11-22 20:01:38 +00:00
}
2020-11-23 17:20:30 +00:00
// computeUpgrade returns the packages to be uninstalled and installed in a system to perform an upgrade
2020-11-22 19:16:04 +00:00
// based on the system repositories
2022-01-06 22:57:56 +00:00
func ( l * LuetInstaller ) computeUpgrade ( syncedRepos Repositories , s * System ) ( types . Packages , types . Packages , error ) {
toInstall := types . Packages { }
var uninstall types . Packages
2020-11-23 17:20:30 +00:00
var err error
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
2022-01-06 22:57:56 +00:00
solv := solver . NewResolver (
types . SolverOptions {
Type : l . Options . SolverOptions . Implementation ,
Concurrency : l . Options . Concurrency } ,
s . Database , allRepos , pkg . NewInMemoryDatabase ( false ) ,
solver . NewSolverFromOptions ( l . Options . SolverOptions ) )
var solution types . PackagesAssertions
2020-05-22 18:45:28 +00:00
if l . Options . SolverUpgrade {
uninstall , solution , err = solv . UpgradeUniverse ( l . Options . RemoveUnavailableOnUpgrade )
if err != nil {
2020-11-23 17:20:30 +00:00
return uninstall , toInstall , errors . Wrap ( err , "Failed solving solution for upgrade" )
2020-05-22 18:45:28 +00:00
}
} else {
2020-12-07 16:20:55 +00:00
uninstall , solution , err = solv . Upgrade ( l . Options . FullUninstall , true )
2020-05-22 18:45:28 +00:00
if err != nil {
2020-11-23 17:20:30 +00:00
return uninstall , toInstall , errors . Wrap ( err , "Failed solving solution for upgrade" )
2020-05-22 18:45:28 +00:00
}
2020-07-12 13:27:50 +00:00
}
2020-02-27 22:14:53 +00:00
for _ , assertion := range solution {
2020-03-24 19:30:32 +00:00
// Be sure to filter from solutions packages already installed in the system
if _ , err := s . Database . FindPackage ( assertion . Package ) ; err != nil && assertion . Value {
2020-02-27 22:14:53 +00:00
toInstall = append ( toInstall , assertion . Package )
}
}
2020-07-12 13:27:50 +00:00
if l . Options . UpgradeNewRevisions {
for _ , p := range s . Database . World ( ) {
2022-01-06 22:57:56 +00:00
matches := syncedRepos . PackageMatches ( types . Packages { p } )
2020-07-12 13:27:50 +00:00
if len ( matches ) == 0 {
// Package missing. the user should run luet upgrade --universe
continue
}
for _ , artefact := range matches [ 0 ] . Repo . GetIndex ( ) {
2021-04-12 17:00:36 +00:00
if artefact . CompileSpec . GetPackage ( ) == nil {
2020-11-23 17:20:30 +00:00
return uninstall , toInstall , errors . New ( "Package in compilespec empty" )
2020-07-12 13:27:50 +00:00
}
2021-04-12 17:00:36 +00:00
if artefact . CompileSpec . GetPackage ( ) . Matches ( p ) && artefact . CompileSpec . GetPackage ( ) . GetBuildTimestamp ( ) != p . GetBuildTimestamp ( ) {
2020-07-12 13:27:50 +00:00
toInstall = append ( toInstall , matches [ 0 ] . Package ) . Unique ( )
uninstall = append ( uninstall , p ) . Unique ( )
}
}
}
}
2020-11-23 17:20:30 +00:00
return uninstall , toInstall , nil
2020-11-22 19:16:04 +00:00
}
// Upgrade upgrades a System based on the Installer options. Returns error in case of failure
func ( l * LuetInstaller ) Upgrade ( s * System ) error {
2021-10-20 22:13:02 +00:00
l . Options . Context . Screen ( "Upgrade" )
2021-10-19 11:05:25 +00:00
syncedRepos , err := l . SyncRepositories ( )
2020-11-23 17:20:30 +00:00
if err != nil {
return err
}
2021-10-20 22:13:02 +00:00
l . Options . Context . Info ( ":thinking: Computing upgrade, please hang tight... :zzz:" )
2020-11-22 19:16:04 +00:00
if l . Options . UpgradeNewRevisions {
2021-10-20 22:13:02 +00:00
l . Options . Context . Info ( ":memo: note: will consider new build revisions while upgrading" )
2020-11-22 19:16:04 +00:00
}
2021-04-16 21:38:10 +00:00
return l . checkAndUpgrade ( syncedRepos , s )
2020-02-27 22:48:14 +00:00
}
2021-10-19 11:05:25 +00:00
func ( l * LuetInstaller ) SyncRepositories ( ) ( Repositories , error ) {
2021-10-20 22:13:02 +00:00
l . Options . Context . Spinner ( )
defer l . Options . Context . SpinnerStop ( )
2022-04-22 09:41:41 +00:00
var errs error
2020-02-27 22:48:14 +00:00
syncedRepos := Repositories { }
2022-04-22 09:41:41 +00:00
2021-10-19 11:05:25 +00:00
for _ , r := range SystemRepositories ( l . Options . PackageRepositories ) {
2021-10-20 22:13:02 +00:00
repo , err := r . Sync ( l . Options . Context , false )
2022-04-22 09:41:41 +00:00
if err == nil {
syncedRepos = append ( syncedRepos , repo )
} else {
multierror . Append ( errs , fmt . Errorf ( "failed syncing '%s': %w" , r . Name , err ) )
2020-02-27 22:48:14 +00:00
}
}
// compute what to install and from where
sort . Sort ( syncedRepos )
2022-04-22 09:41:41 +00:00
return syncedRepos , errs
2020-02-27 22:48:14 +00:00
}
2022-01-06 22:57:56 +00:00
func ( l * LuetInstaller ) Swap ( toRemove types . Packages , toInstall types . Packages , s * System ) error {
2021-10-19 11:05:25 +00:00
syncedRepos , err := l . SyncRepositories ( )
2020-02-27 22:48:14 +00:00
if err != nil {
return err
}
2020-12-02 17:24:32 +00:00
2022-01-06 22:57:56 +00:00
toRemoveFinal := types . Packages { }
2020-12-17 23:49:51 +00:00
for _ , p := range toRemove {
packs , _ := s . Database . FindPackages ( p )
if len ( packs ) == 0 {
return errors . New ( "Package " + p . HumanReadableString ( ) + " not found in the system" )
}
for _ , pp := range packs {
toRemoveFinal = append ( toRemoveFinal , pp )
}
}
2021-04-24 17:29:53 +00:00
o := Option {
FullUninstall : false ,
Force : true ,
CheckConflicts : false ,
FullCleanUninstall : false ,
NoDeps : l . Options . NoDeps ,
OnlyDeps : false ,
}
2020-12-17 23:49:51 +00:00
2021-04-24 17:29:53 +00:00
return l . swap ( o , syncedRepos , toRemoveFinal , toInstall , s )
2020-02-27 22:48:14 +00:00
}
2022-01-06 22:57:56 +00:00
func ( l * LuetInstaller ) computeSwap ( o Option , syncedRepos Repositories , toRemove types . Packages , toInstall types . Packages , s * System ) ( map [ string ] ArtifactMatch , types . Packages , types . PackagesAssertions , types . PackageDatabase , error ) {
2020-12-19 13:55:59 +00:00
2020-02-27 22:48:14 +00:00
allRepos := pkg . NewInMemoryDatabase ( false )
syncedRepos . SyncDatabase ( allRepos )
2020-12-19 13:55:59 +00:00
2020-02-27 22:48:14 +00:00
toInstall = syncedRepos . ResolveSelectors ( toInstall )
2020-12-06 21:11:17 +00:00
// First check what would have been done
2020-12-19 16:45:50 +00:00
installedtmp , err := s . Database . Copy ( )
if err != nil {
return nil , nil , nil , nil , errors . Wrap ( err , "Failed create temporary in-memory db" )
2020-12-06 21:11:17 +00:00
}
2020-12-19 16:45:50 +00:00
2020-12-06 21:11:17 +00:00
systemAfterChanges := & System { Database : installedtmp }
2021-04-24 12:26:26 +00:00
packs , err := l . computeUninstall ( o , systemAfterChanges , toRemove ... )
2021-04-24 16:15:47 +00:00
if err != nil && ! o . Force {
2021-10-20 22:13:02 +00:00
l . Options . Context . Error ( "Failed computing uninstall for " , packsToList ( toRemove ) )
2020-12-19 16:16:53 +00:00
return nil , nil , nil , nil , errors . Wrap ( err , "computing uninstall " + packsToList ( toRemove ) )
}
for _ , p := range packs {
err = systemAfterChanges . Database . RemovePackage ( p )
if err != nil {
return nil , nil , nil , nil , errors . Wrap ( err , "Failed removing package from database" )
2019-11-29 18:01:56 +00:00
}
}
2021-04-24 16:15:47 +00:00
match , packages , assertions , allRepos , err := l . computeInstall ( o , syncedRepos , toInstall , systemAfterChanges )
2021-01-02 20:28:54 +00:00
for _ , p := range toInstall {
2022-01-06 22:57:56 +00:00
assertions = append ( assertions , types . PackageAssert { Package : p , Value : true } )
2021-01-02 20:28:54 +00:00
}
return match , packages , assertions , allRepos , err
2020-12-19 13:55:59 +00:00
}
2022-01-06 22:57:56 +00:00
func ( l * LuetInstaller ) swap ( o Option , syncedRepos Repositories , toRemove types . Packages , toInstall types . Packages , s * System ) error {
2020-12-19 14:26:18 +00:00
2021-04-24 12:26:26 +00:00
match , packages , assertions , allRepos , err := l . computeSwap ( o , syncedRepos , toRemove , toInstall , s )
2020-11-22 19:16:04 +00:00
if err != nil {
2020-12-19 13:55:59 +00:00
return errors . Wrap ( err , "failed computing package replacement" )
}
if l . Options . Ask {
2021-10-19 20:26:23 +00:00
// if len(toRemove) > 0 {
2021-10-20 22:13:02 +00:00
// l.Options.Context.Info(":recycle: Packages that are going to be removed from the system:\n ", Yellow(packsToList(toRemove)).BgBlack().String())
2021-10-19 20:26:23 +00:00
// }
// if len(match) > 0 {
2021-10-20 22:13:02 +00:00
// 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())
2021-10-19 20:26:23 +00:00
// printMatches(match)
// }
2021-10-20 22:13:02 +00:00
l . Options . Context . Info ( ":zap: Proposed version changes to the system:\n " )
2021-10-19 20:26:23 +00:00
printMatchUpgrade ( match , toRemove )
2020-12-19 13:55:59 +00:00
2021-10-20 22:13:02 +00:00
l . Options . Context . Info ( "By going forward, you are also accepting the licenses of the packages that you are going to install in your system." )
if l . Options . Context . Ask ( ) {
2020-12-19 13:55:59 +00:00
l . Options . Ask = false // Don't prompt anymore
} else {
return errors . New ( "Aborted by user" )
}
2020-11-22 19:16:04 +00:00
}
2020-12-19 13:55:59 +00:00
// First match packages against repositories by priority
2020-12-06 21:11:17 +00:00
if err := l . download ( syncedRepos , match ) ; err != nil {
return errors . Wrap ( err , "Pre-downloading packages" )
}
2021-08-03 19:35:21 +00:00
if err := l . checkFileconflicts ( match , false , s ) ; err != nil {
if ! l . Options . Force {
return errors . Wrap ( err , "file conflict found" )
} else {
2021-10-20 22:13:02 +00:00
l . Options . Context . Warning ( "file conflict found" , err . Error ( ) )
2021-08-03 19:35:21 +00:00
}
}
2021-03-07 10:39:19 +00:00
if l . Options . DownloadOnly {
return nil
}
2020-12-06 21:11:17 +00:00
2021-12-24 10:44:50 +00:00
ops , err := l . generateRunOps ( toRemove , match , Option {
2021-08-11 08:31:09 +00:00
Force : o . Force ,
NoDeps : false ,
OnlyDeps : o . OnlyDeps ,
RunFinalizers : false ,
CheckFileConflicts : false ,
2021-12-15 16:51:17 +00:00
} , o , syncedRepos , packages , assertions , allRepos , s )
if err != nil {
return errors . Wrap ( err , "failed computing installer options" )
}
2021-04-24 16:15:47 +00:00
err = l . runOps ( ops , s )
if err != nil {
return errors . Wrap ( err , "failed running installer options" )
2021-04-24 12:26:26 +00:00
}
2021-04-24 16:15:47 +00:00
toFinalize , err := l . getFinalizers ( allRepos , assertions , match , o . NoDeps )
if err != nil {
return errors . Wrap ( err , "failed getting package to finalize" )
2021-04-24 12:26:26 +00:00
}
2021-10-20 22:13:02 +00:00
return s . ExecuteFinalizers ( l . Options . Context , toFinalize )
2021-04-24 12:26:26 +00:00
}
type Option struct {
Force bool
NoDeps bool
CheckConflicts bool
FullUninstall bool
FullCleanUninstall bool
2021-04-24 16:15:47 +00:00
OnlyDeps bool
RunFinalizers bool
2021-08-11 08:31:09 +00:00
CheckFileConflicts bool
2021-04-24 12:26:26 +00:00
}
type operation struct {
Option Option
2022-01-06 22:57:56 +00:00
Package * types . Package
2021-04-24 12:26:26 +00:00
}
2021-04-24 16:15:47 +00:00
type installOperation struct {
operation
Reposiories Repositories
2022-01-06 22:57:56 +00:00
Packages types . Packages
Assertions types . PackagesAssertions
Database types . PackageDatabase
2021-04-24 16:15:47 +00:00
Matches map [ string ] ArtifactMatch
}
2021-04-24 12:26:26 +00:00
// installerOp is the operation that is sent to the
// upgradeWorker's channel (todo)
type installerOp struct {
2021-12-15 16:51:17 +00:00
Uninstall [ ] operation
Install [ ] installOperation
2021-04-24 12:26:26 +00:00
}
2021-04-24 16:15:47 +00:00
func ( l * LuetInstaller ) runOps ( ops [ ] installerOp , s * System ) error {
2021-04-24 12:26:26 +00:00
all := make ( chan installerOp )
wg := new ( sync . WaitGroup )
2021-10-24 07:20:26 +00:00
systemLock := & sync . Mutex { }
2021-04-24 12:26:26 +00:00
// Do the real install
for i := 0 ; i < l . Options . Concurrency ; i ++ {
wg . Add ( 1 )
2021-10-24 07:20:26 +00:00
go l . installerOpWorker ( i , wg , systemLock , all , s )
2020-12-06 21:11:17 +00:00
}
2021-04-24 12:26:26 +00:00
for _ , c := range ops {
all <- c
}
close ( all )
wg . Wait ( )
return nil
}
2021-04-24 17:29:53 +00:00
// TODO: use installerOpWorker in place of all the other workers.
// This one is general enough to read a list of operations and execute them.
2021-10-24 07:20:26 +00:00
func ( l * LuetInstaller ) installerOpWorker ( i int , wg * sync . WaitGroup , systemLock * sync . Mutex , c <- chan installerOp , s * System ) error {
2021-04-24 12:26:26 +00:00
defer wg . Done ( )
for p := range c {
2021-10-24 10:09:08 +00:00
2021-12-24 10:44:50 +00:00
installedFiles := map [ string ] interface { } { }
for _ , pp := range p . Install {
artMatch := pp . Matches [ pp . Package . GetFingerPrint ( ) ]
art , err := l . getPackage ( artMatch , l . Options . Context )
if err != nil {
installedFiles = map [ string ] interface { } { }
break
}
l , err := art . FileList ( )
if err != nil {
installedFiles = map [ string ] interface { } { }
break
}
for _ , f := range l {
installedFiles [ f ] = nil
}
}
2021-12-15 16:51:17 +00:00
for _ , pp := range p . Uninstall {
2021-12-24 10:44:50 +00:00
2021-10-20 22:13:02 +00:00
l . Options . Context . Debug ( "Replacing package inplace" )
2021-12-24 10:44:50 +00:00
toUninstall , uninstall , err := l . generateUninstallFn ( pp . Option , s , installedFiles , pp . Package )
2021-04-24 16:15:47 +00:00
if err != nil {
2021-12-15 17:48:34 +00:00
l . Options . Context . Debug ( "Skipping uninstall, fail to generate uninstall function, error: " + err . Error ( ) )
2021-04-24 16:15:47 +00:00
continue
2021-04-24 12:26:26 +00:00
}
2021-10-24 10:09:08 +00:00
systemLock . Lock ( )
2021-04-24 12:26:26 +00:00
err = uninstall ( )
2021-10-24 10:09:08 +00:00
systemLock . Unlock ( )
2021-04-24 16:15:47 +00:00
if err != nil {
2021-10-20 22:13:02 +00:00
l . Options . Context . Error ( "Failed uninstall for " , packsToList ( toUninstall ) )
2021-04-24 16:15:47 +00:00
continue
}
}
2021-12-15 16:51:17 +00:00
for _ , pp := range p . Install {
artMatch := pp . Matches [ pp . Package . GetFingerPrint ( ) ]
ass := pp . Assertions . Search ( pp . Package . GetFingerPrint ( ) )
packageToInstall , _ := pp . Packages . Find ( pp . Package . GetPackageName ( ) )
2021-04-24 16:15:47 +00:00
2021-10-24 07:20:26 +00:00
systemLock . Lock ( )
2021-04-24 16:15:47 +00:00
err := l . install (
2021-12-15 16:51:17 +00:00
pp . Option ,
pp . Reposiories ,
map [ string ] ArtifactMatch { pp . Package . GetFingerPrint ( ) : artMatch } ,
2022-01-06 22:57:56 +00:00
types . Packages { packageToInstall } ,
types . PackagesAssertions { * ass } ,
2021-12-15 16:51:17 +00:00
pp . Database ,
2021-04-24 16:15:47 +00:00
s ,
)
2021-10-24 07:20:26 +00:00
systemLock . Unlock ( )
2021-04-24 16:15:47 +00:00
if err != nil {
2021-10-20 22:13:02 +00:00
l . Options . Context . Error ( err )
2021-04-24 12:26:26 +00:00
}
}
}
return nil
}
// checks wheter we can uninstall and install in place and compose installer worker ops
2021-12-24 10:44:50 +00:00
func ( l * LuetInstaller ) generateRunOps (
2022-01-06 22:57:56 +00:00
toUninstall types . Packages , installMatch map [ string ] ArtifactMatch , installOpt , uninstallOpt Option ,
syncedRepos Repositories , toInstall types . Packages , solution types . PackagesAssertions , allRepos types . PackageDatabase , s * System ) ( resOps [ ] installerOp , err error ) {
2021-12-24 10:44:50 +00:00
uOpts := [ ] operation { }
for _ , u := range toUninstall {
uOpts = append ( uOpts , operation { Package : u , Option : uninstallOpt } )
}
iOpts := [ ] installOperation { }
for _ , u := range installMatch {
iOpts = append ( iOpts , installOperation {
operation : operation {
Package : u . Package ,
Option : installOpt ,
} ,
Matches : installMatch ,
Packages : toInstall ,
Reposiories : syncedRepos ,
Assertions : solution ,
Database : allRepos ,
2021-12-15 16:51:17 +00:00
} )
}
2021-12-24 10:44:50 +00:00
resOps = append ( resOps , installerOp {
Uninstall : uOpts ,
Install : iOpts ,
} )
2021-12-15 16:51:17 +00:00
return resOps , nil
2019-11-29 18:01:56 +00:00
}
2021-04-16 21:38:10 +00:00
func ( l * LuetInstaller ) checkAndUpgrade ( r Repositories , s * System ) error {
uninstall , toInstall , err := l . computeUpgrade ( r , s )
if err != nil {
return errors . Wrap ( err , "failed computing upgrade" )
}
if len ( toInstall ) == 0 && len ( uninstall ) == 0 {
2021-10-20 22:13:02 +00:00
l . Options . Context . Info ( "Nothing to upgrade" )
2021-04-16 21:38:10 +00:00
return nil
2021-10-19 20:26:23 +00:00
} else {
2021-10-20 22:13:02 +00:00
l . Options . Context . Info ( ":zap: Proposed version changes to the system:\n " )
2021-10-19 20:26:23 +00:00
printUpgradeList ( toInstall , uninstall )
2021-04-16 21:38:10 +00:00
}
2021-04-24 17:29:53 +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.
o := Option {
FullUninstall : false ,
Force : true ,
CheckConflicts : false ,
FullCleanUninstall : false ,
NoDeps : true ,
OnlyDeps : false ,
}
2021-04-16 21:38:10 +00:00
if l . Options . Ask {
2021-10-20 22:13:02 +00:00
l . Options . Context . Info ( "By going forward, you are also accepting the licenses of the packages that you are going to install in your system." )
if l . Options . Context . Ask ( ) {
2021-04-16 21:38:10 +00:00
l . Options . Ask = false // Don't prompt anymore
2021-04-24 17:29:53 +00:00
return l . swap ( o , r , uninstall , toInstall , s )
2021-04-16 21:38:10 +00:00
} else {
return errors . New ( "Aborted by user" )
}
}
2022-01-06 22:57:56 +00:00
bus . Manager . Publish ( bus . EventPreUpgrade , struct { Uninstall , Install types . Packages } { Uninstall : uninstall , Install : toInstall } )
2021-12-12 09:31:03 +00:00
err = l . swap ( o , r , uninstall , toInstall , s )
bus . Manager . Publish ( bus . EventPostUpgrade , struct {
Error error
2022-01-06 22:57:56 +00:00
Uninstall , Install types . Packages
2021-12-12 09:31:03 +00:00
} { Uninstall : uninstall , Install : toInstall , Error : err } )
2021-12-12 09:42:51 +00:00
if err != nil {
return err
}
if l . Options . AutoOSCheck {
2021-12-15 17:48:34 +00:00
l . Options . Context . Info ( "Performing automatic oscheck" )
2021-12-24 11:07:08 +00:00
packs := s . OSCheck ( l . Options . Context )
2021-12-12 09:42:51 +00:00
if len ( packs ) > 0 {
2021-12-15 17:48:34 +00:00
p := ""
for _ , r := range packs {
p += " " + r . HumanReadableString ( )
}
l . Options . Context . Info ( "Following packages requires reinstallation: " + p )
2021-12-12 09:42:51 +00:00
return l . swap ( o , r , packs , packs , s )
}
2021-12-15 17:48:34 +00:00
l . Options . Context . Info ( "OSCheck done" )
2021-12-12 09:42:51 +00:00
}
2021-12-12 09:31:03 +00:00
return err
2021-04-16 21:38:10 +00:00
}
2022-01-06 22:57:56 +00:00
func ( l * LuetInstaller ) Install ( cp types . Packages , s * System ) error {
2021-10-20 22:13:02 +00:00
l . Options . Context . Screen ( "Install" )
2021-10-19 11:05:25 +00:00
syncedRepos , err := l . SyncRepositories ( )
2020-02-27 22:25:29 +00:00
if err != nil {
return err
}
2020-11-22 19:16:04 +00:00
2021-10-09 15:36:13 +00:00
if len ( s . Database . World ( ) ) > 0 && ! l . Options . Relaxed {
2021-10-20 22:13:02 +00:00
l . Options . Context . Info ( ":thinking: Checking for available upgrades" )
2021-04-16 21:38:10 +00:00
if err := l . checkAndUpgrade ( syncedRepos , s ) ; err != nil {
return errors . Wrap ( err , "while checking upgrades before install" )
2021-04-16 20:28:30 +00:00
}
}
2021-04-24 16:15:47 +00:00
o := Option {
2021-08-11 08:31:09 +00:00
NoDeps : l . Options . NoDeps ,
Force : l . Options . Force ,
OnlyDeps : l . Options . OnlyDeps ,
CheckFileConflicts : true ,
RunFinalizers : true ,
2021-04-24 16:15:47 +00:00
}
match , packages , assertions , allRepos , err := l . computeInstall ( o , syncedRepos , cp , s )
2020-11-22 19:16:04 +00:00
if err != nil {
return err
}
2020-12-03 19:03:37 +00:00
// Check if we have to process something, or return to the user an error
2020-12-03 17:32:24 +00:00
if len ( match ) == 0 {
2021-10-20 22:13:02 +00:00
l . Options . Context . Info ( "No packages to install" )
2020-12-03 17:32:24 +00:00
return nil
}
2020-12-03 17:53:57 +00:00
// Resolvers might decide to remove some packages from being installed
2022-01-06 22:57:56 +00:00
if l . Options . SolverOptions . Type != solver . QLearningResolverType {
2020-12-03 17:53:57 +00:00
for _ , p := range cp {
found := false
2020-12-03 19:03:37 +00:00
vers , _ := s . Database . FindPackageVersions ( p ) // If was installed, it is found, as it was filtered
if len ( vers ) >= 1 {
found = true
continue
}
2020-12-03 17:53:57 +00:00
for _ , m := range match {
if m . Package . GetName ( ) == p . GetName ( ) {
found = true
}
2021-09-06 12:58:50 +00:00
for _ , pack := range m . Package . GetProvides ( ) {
if pack . GetName ( ) == p . GetName ( ) {
found = true
}
}
2020-12-03 17:53:57 +00:00
}
2020-12-03 19:03:37 +00:00
2020-12-03 17:53:57 +00:00
if ! found {
2021-10-20 22:13:02 +00:00
return fmt . Errorf ( "package '%s' not found" , p . HumanReadableString ( ) )
2020-12-03 16:25:29 +00:00
}
}
}
2021-10-20 22:13:02 +00:00
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())
2021-10-19 20:26:23 +00:00
printMatches ( match )
2021-04-24 16:15:47 +00:00
2020-11-22 19:16:04 +00:00
if l . Options . Ask {
2021-10-20 22:13:02 +00:00
l . Options . Context . Info ( "By going forward, you are also accepting the licenses of the packages that you are going to install in your system." )
if l . Options . Context . Ask ( ) {
2020-11-23 17:20:30 +00:00
l . Options . Ask = false // Don't prompt anymore
2021-04-24 12:26:26 +00:00
return l . install ( o , syncedRepos , match , packages , assertions , allRepos , s )
2020-11-22 19:16:04 +00:00
} else {
return errors . New ( "Aborted by user" )
}
}
2021-04-24 12:26:26 +00:00
return l . install ( o , syncedRepos , match , packages , assertions , allRepos , s )
2020-02-27 22:25:29 +00:00
}
2020-12-06 21:11:17 +00:00
func ( l * LuetInstaller ) download ( syncedRepos Repositories , toDownload map [ string ] ArtifactMatch ) error {
2020-03-24 17:13:10 +00:00
2021-12-15 16:51:17 +00:00
// Don't attempt to download stuff that is already in cache
missArtifacts := false
for _ , m := range toDownload {
c := m . Repository . Client ( l . Options . Context )
_ , err := c . CacheGet ( m . Artifact )
if err != nil {
missArtifacts = true
}
}
if ! missArtifacts {
l . Options . Context . Debug ( "Packages already in cache, skipping download" )
return nil
}
2020-03-24 17:13:10 +00:00
// Download packages into cache in parallel.
all := make ( chan ArtifactMatch )
var wg = new ( sync . WaitGroup )
2021-10-22 10:35:03 +00:00
ctx := l . Options . Context . Copy ( )
2021-10-19 20:26:23 +00:00
2021-10-22 10:35:03 +00:00
// Check if the terminal is big enough to display a progress bar
// https://github.com/pterm/pterm/blob/4c725e56bfd9eb38e1c7b9dec187b50b93baa8bd/progressbar_printer.go#L190
2021-12-17 14:21:03 +00:00
w , _ , err := logger . GetTerminalSize ( )
2021-10-31 19:06:59 +00:00
var pb * pterm . ProgressbarPrinter
2021-12-17 14:21:03 +00:00
if logger . IsTerminal ( ) && err == nil && w > 100 {
2021-10-22 10:35:03 +00:00
area , _ := pterm . DefaultArea . Start ( )
2021-12-17 14:21:03 +00:00
pb = pterm . DefaultProgressbar . WithPrintTogether ( area ) . WithTotal ( len ( toDownload ) ) . WithTitle ( "Downloading packages" )
pb , _ = pb . Start ( )
ctx . SetAnnotation ( "progressbar" , pb )
2021-10-22 10:35:03 +00:00
defer area . Stop ( )
}
2021-10-19 20:26:23 +00:00
2020-03-24 17:13:10 +00:00
// Download
for i := 0 ; i < l . Options . Concurrency ; i ++ {
wg . Add ( 1 )
2021-10-31 19:06:59 +00:00
go l . downloadWorker ( i , wg , pb , all , ctx )
2020-03-24 17:13:10 +00:00
}
for _ , c := range toDownload {
all <- c
}
close ( all )
wg . Wait ( )
return nil
}
2020-04-13 17:30:40 +00:00
// Reclaim adds packages to the system database
// if files from artifacts in the repositories are found
// in the system target
func ( l * LuetInstaller ) Reclaim ( s * System ) error {
2021-10-19 11:05:25 +00:00
syncedRepos , err := l . SyncRepositories ( )
2020-04-13 17:30:40 +00:00
if err != nil {
return err
}
var toMerge [ ] ArtifactMatch = [ ] ArtifactMatch { }
for _ , repo := range syncedRepos {
for _ , artefact := range repo . GetIndex ( ) {
2021-10-20 22:13:02 +00:00
l . Options . Context . Debug ( "Checking if" ,
2021-04-12 17:00:36 +00:00
artefact . CompileSpec . GetPackage ( ) . HumanReadableString ( ) ,
2020-05-02 10:17:54 +00:00
"from" , repo . GetName ( ) , "is installed" )
2020-04-13 17:30:40 +00:00
FILES :
2021-04-12 17:00:36 +00:00
for _ , f := range artefact . Files {
2021-06-01 14:43:31 +00:00
if fileHelper . Exists ( filepath . Join ( s . Target , f ) ) {
2021-04-12 17:00:36 +00:00
p , err := repo . GetTree ( ) . GetDatabase ( ) . FindPackage ( artefact . CompileSpec . GetPackage ( ) )
2020-04-30 16:56:50 +00:00
if err != nil {
return err
}
2021-10-20 22:13:02 +00:00
l . Options . Context . Info ( ":mag: Found package:" , p . HumanReadableString ( ) )
2020-04-30 16:56:50 +00:00
toMerge = append ( toMerge , ArtifactMatch { Artifact : artefact , Package : p } )
2020-04-13 17:30:40 +00:00
break FILES
}
}
}
}
for _ , match := range toMerge {
pack := match . Package
vers , _ := s . Database . FindPackageVersions ( pack )
if len ( vers ) >= 1 {
2021-10-20 22:13:02 +00:00
l . Options . Context . Warning ( "Filtering out package " + pack . HumanReadableString ( ) + ", already reclaimed" )
2020-04-13 17:30:40 +00:00
continue
}
_ , err := s . Database . CreatePackage ( pack )
if err != nil && ! l . Options . Force {
return errors . Wrap ( err , "Failed creating package" )
}
2022-01-06 22:57:56 +00:00
s . Database . SetPackageFiles ( & types . PackageFile { PackageFingerprint : pack . GetFingerPrint ( ) , Files : match . Artifact . Files } )
2021-10-20 22:13:02 +00:00
l . Options . Context . Info ( ":zap:Reclaimed package:" , pack . HumanReadableString ( ) )
2020-04-13 17:30:40 +00:00
}
2021-10-20 22:13:02 +00:00
l . Options . Context . Info ( "Done!" )
2020-05-02 10:17:54 +00:00
2020-04-13 17:30:40 +00:00
return nil
}
2022-01-06 22:57:56 +00:00
func ( l * LuetInstaller ) computeInstall ( o Option , syncedRepos Repositories , cp types . Packages , s * System ) ( map [ string ] ArtifactMatch , types . Packages , types . PackagesAssertions , types . PackageDatabase , error ) {
var p types . Packages
2020-11-22 19:16:04 +00:00
toInstall := map [ string ] ArtifactMatch { }
allRepos := pkg . NewInMemoryDatabase ( false )
2022-01-06 22:57:56 +00:00
var solution types . PackagesAssertions
2020-01-01 10:53:50 +00:00
// Check if the package is installed first
for _ , pi := range cp {
vers , _ := s . Database . FindPackageVersions ( pi )
if len ( vers ) >= 1 {
2021-10-20 22:13:02 +00:00
// l.Options.Context.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 {
2020-11-22 19:16:04 +00:00
return toInstall , p , solution , allRepos , nil
2020-01-01 10:53:50 +00:00
}
2019-11-22 20:01:38 +00:00
// First get metas from all repos (and decodes trees)
// 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-12-31 15:50:44 +00:00
syncedRepos . SyncDatabase ( allRepos )
2020-02-04 19:15:59 +00:00
p = syncedRepos . ResolveSelectors ( p )
2022-01-06 22:57:56 +00:00
var packagesToInstall types . Packages
2020-02-27 22:25:29 +00:00
var err error
2020-02-18 17:37:56 +00:00
2021-04-24 16:15:47 +00:00
if ! o . NoDeps {
2022-01-06 22:57:56 +00:00
solv := solver . NewResolver ( types . SolverOptions {
Type : l . Options . SolverOptions . Implementation ,
Concurrency : l . Options . Concurrency } ,
s . Database , allRepos , pkg . NewInMemoryDatabase ( false ) ,
solver . NewSolverFromOptions ( l . Options . SolverOptions ) ,
)
2021-10-09 15:36:13 +00:00
if l . Options . Relaxed {
solution , err = solv . RelaxedInstall ( p )
} else {
solution , err = solv . Install ( p )
}
2020-10-31 00:22:30 +00:00
/// TODO: PackageAssertions needs to be a map[fingerprint]pack so lookup is in O(1)
2021-04-24 16:15:47 +00:00
if err != nil && ! o . Force {
2020-11-22 19:16:04 +00:00
return toInstall , p , solution , allRepos , errors . Wrap ( err , "Failed solving solution for package" )
2020-02-18 17:37:56 +00:00
}
// Gathers things to install
for _ , assertion := range solution {
if assertion . Value {
2020-10-31 00:22:30 +00:00
if _ , err := s . Database . FindPackage ( assertion . Package ) ; err == nil {
// skip matching if it is installed already
continue
}
2020-02-18 17:37:56 +00:00
packagesToInstall = append ( packagesToInstall , assertion . Package )
}
}
2021-04-24 16:15:47 +00:00
} else if ! o . OnlyDeps {
2020-02-18 17:37:56 +00:00
for _ , currentPack := range p {
2020-10-31 00:22:30 +00:00
if _ , err := s . Database . FindPackage ( currentPack ) ; err == nil {
// skip matching if it is installed already
continue
}
2020-02-18 17:37:56 +00:00
packagesToInstall = append ( packagesToInstall , currentPack )
}
2019-11-22 20:01:38 +00:00
}
// Gathers things to install
2020-02-18 17:37:56 +00:00
for _ , currentPack := range packagesToInstall {
2020-05-02 11:24:46 +00:00
// Check if package is already installed.
2020-10-31 00:22:30 +00:00
2022-01-06 22:57:56 +00:00
matches := syncedRepos . PackageMatches ( types . Packages { currentPack } )
2020-02-18 17:37:56 +00:00
if len ( matches ) == 0 {
2020-11-22 19:16:04 +00:00
return toInstall , p , solution , allRepos , errors . New ( "Failed matching solutions against repository for " + currentPack . HumanReadableString ( ) + " where are definitions coming from?!" )
2020-02-18 17:37:56 +00:00
}
A :
for _ , artefact := range matches [ 0 ] . Repo . GetIndex ( ) {
2021-04-12 17:00:36 +00:00
if artefact . CompileSpec . GetPackage ( ) == nil {
2020-11-22 19:16:04 +00:00
return toInstall , p , solution , allRepos , errors . New ( "Package in compilespec empty" )
2020-02-18 17:37:56 +00:00
}
2021-04-12 17:00:36 +00:00
if matches [ 0 ] . Package . Matches ( artefact . CompileSpec . GetPackage ( ) ) {
currentPack . SetBuildTimestamp ( artefact . CompileSpec . GetPackage ( ) . GetBuildTimestamp ( ) )
2020-02-18 17:37:56 +00:00
// 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
}
}
}
2020-11-22 19:16:04 +00:00
return toInstall , p , solution , allRepos , nil
}
2022-01-06 22:57:56 +00:00
func ( l * LuetInstaller ) getFinalizers ( allRepos types . PackageDatabase , solution types . PackagesAssertions , toInstall map [ string ] ArtifactMatch , nodeps bool ) ( [ ] * types . Package , error ) {
var toFinalize [ ] * types . Package
2021-04-24 16:15:47 +00:00
if ! nodeps {
2021-10-20 22:13:02 +00:00
// TODO: Lower those errors as l.Options.Context.Warning
2021-04-24 16:15:47 +00:00
for _ , w := range toInstall {
// 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 {
for _ , c := range toInstall {
treePackage , err := c . Repository . GetTree ( ) . GetDatabase ( ) . FindPackage ( c . Package )
if err != nil {
return toFinalize , errors . Wrap ( err , "Error getting package " + c . Package . HumanReadableString ( ) )
}
toFinalize = append ( toFinalize , treePackage )
}
}
return toFinalize , nil
}
2021-08-03 19:35:21 +00:00
func ( l * LuetInstaller ) checkFileconflicts ( toInstall map [ string ] ArtifactMatch , checkSystem bool , s * System ) error {
2021-10-20 22:13:02 +00:00
l . Options . Context . Info ( "Checking for file conflicts.." )
2021-07-30 09:38:49 +00:00
defer s . Clean ( ) // Release memory
2021-12-17 22:13:28 +00:00
filesToInstall := map [ string ] interface { } { }
2021-07-29 08:14:05 +00:00
for _ , m := range toInstall {
2021-12-17 22:13:28 +00:00
l . Options . Context . Debug ( "Checking file conflicts for" , m . Package . HumanReadableString ( ) )
2021-12-15 16:51:17 +00:00
a , err := l . getPackage ( m , l . Options . Context )
2021-07-29 08:14:05 +00:00
if err != nil && ! l . Options . Force {
return errors . Wrap ( err , "Failed downloading package" )
}
files , err := a . FileList ( )
if err != nil && ! l . Options . Force {
return errors . Wrapf ( err , "Could not get filelist for %s" , a . CompileSpec . Package . HumanReadableString ( ) )
}
for _ , f := range files {
2021-12-17 22:13:28 +00:00
if _ , ok := filesToInstall [ f ] ; ok {
2021-07-29 08:14:05 +00:00
return fmt . Errorf (
"file conflict between packages to be installed" ,
)
}
2021-08-03 19:35:21 +00:00
if checkSystem {
exists , p , err := s . ExistsPackageFile ( f )
if err != nil {
return errors . Wrap ( err , "failed checking into system db" )
}
if exists {
return fmt . Errorf (
"file conflict between '%s' and '%s' ( file: %s )" ,
p . HumanReadableString ( ) ,
m . Package . HumanReadableString ( ) ,
f ,
)
}
2021-07-29 08:14:05 +00:00
}
2021-12-17 22:13:28 +00:00
filesToInstall [ f ] = nil
2021-07-29 08:14:05 +00:00
}
}
2021-12-17 22:13:28 +00:00
l . Options . Context . Info ( "Done checking for file conflicts.." )
2021-07-29 08:14:05 +00:00
return nil
}
2022-01-06 22:57:56 +00:00
func ( l * LuetInstaller ) install ( o Option , syncedRepos Repositories , toInstall map [ string ] ArtifactMatch , p types . Packages , solution types . PackagesAssertions , allRepos types . PackageDatabase , s * System ) error {
2021-07-29 08:14:05 +00:00
// Download packages in parallel first
2020-12-06 21:11:17 +00:00
if err := l . download ( syncedRepos , toInstall ) ; err != nil {
return errors . Wrap ( err , "Downloading packages" )
2020-03-24 17:13:10 +00:00
}
2020-02-27 22:14:53 +00:00
2021-08-11 08:31:09 +00:00
if o . CheckFileConflicts {
// Check file conflicts
if err := l . checkFileconflicts ( toInstall , true , s ) ; err != nil {
if ! l . Options . Force {
return errors . Wrap ( err , "file conflict found" )
} else {
2021-10-20 22:13:02 +00:00
l . Options . Context . Warning ( "file conflict found" , err . Error ( ) )
2021-08-11 08:31:09 +00:00
}
2021-07-29 08:14:05 +00:00
}
}
2021-03-07 10:39:19 +00:00
if l . Options . DownloadOnly {
return nil
}
2020-12-06 21:11:17 +00:00
all := make ( chan ArtifactMatch )
2020-02-27 22:14:53 +00:00
2020-12-06 21:11:17 +00:00
wg := new ( sync . WaitGroup )
2021-10-24 07:20:26 +00:00
installLock := & sync . Mutex { }
2020-02-27 22:14:53 +00:00
2020-03-24 17:13:10 +00:00
// Do the real install
for i := 0 ; i < l . Options . Concurrency ; i ++ {
wg . Add ( 1 )
2021-10-24 07:20:26 +00:00
go l . installerWorker ( i , wg , installLock , all , s )
2019-11-22 20:01:38 +00:00
}
2020-03-24 17:13:10 +00:00
for _ , c := range toInstall {
all <- c
2019-11-22 20:01:38 +00:00
}
2020-03-24 17:13:10 +00:00
close ( all )
wg . Wait ( )
2019-11-22 20:01:38 +00:00
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 )
2021-04-24 12:26:26 +00:00
if err != nil && ! o . Force {
2020-02-27 17:26:48 +00:00
return errors . Wrap ( err , "Failed creating package" )
}
2020-11-13 17:25:44 +00:00
bus . Manager . Publish ( bus . EventPackageInstall , c )
2020-02-27 17:26:48 +00:00
}
2020-03-28 15:57:40 +00:00
2021-04-24 16:15:47 +00:00
if ! o . RunFinalizers {
return nil
}
2019-11-22 21:23:05 +00:00
2021-04-24 16:15:47 +00:00
toFinalize , err := l . getFinalizers ( allRepos , solution , toInstall , o . NoDeps )
if err != nil {
return errors . Wrap ( err , "failed getting package to finalize" )
2019-11-22 21:23:05 +00:00
}
2020-03-28 15:57:40 +00:00
2021-10-20 22:13:02 +00:00
return s . ExecuteFinalizers ( l . Options . Context , toFinalize )
2019-11-22 20:01:38 +00:00
}
2021-12-17 14:21:03 +00:00
func ( l * LuetInstaller ) getPackage ( a ArtifactMatch , ctx types . Context ) ( artifact * artifact . PackageArtifact , err error ) {
2021-10-22 10:35:03 +00:00
cli := a . Repository . Client ( ctx )
2021-10-19 20:26:23 +00:00
2021-12-15 16:51:17 +00:00
artifact , err = cli . DownloadArtifact ( a . Artifact )
2020-02-02 23:58:55 +00:00
if err != nil {
2020-03-24 17:13:10 +00:00
return nil , errors . Wrap ( err , "Error on download artifact" )
2020-02-02 23:58:55 +00:00
}
2019-11-23 21:41:51 +00:00
2019-12-29 12:58:49 +00:00
err = artifact . Verify ( )
2020-12-09 20:31:07 +00:00
if err != nil {
2020-03-24 17:13:10 +00:00
return nil , errors . Wrap ( err , "Artifact integrity check failure" )
2019-12-29 12:58:49 +00:00
}
2020-03-24 17:13:10 +00:00
return artifact , nil
}
2021-04-12 17:00:36 +00:00
func ( l * LuetInstaller ) installPackage ( m ArtifactMatch , s * System ) error {
2020-03-24 17:13:10 +00:00
2021-12-15 16:51:17 +00:00
a , err := l . getPackage ( m , l . Options . Context )
2020-03-24 17:13:10 +00:00
if err != nil && ! l . Options . Force {
return errors . Wrap ( err , "Failed downloading package" )
2020-02-27 22:14:53 +00:00
}
2019-12-29 12:58:49 +00:00
2021-04-12 17:00:36 +00:00
files , err := a . 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
}
2021-10-20 22:13:02 +00:00
err = a . Unpack ( l . Options . Context , s . Target , true )
2020-02-18 17:37:56 +00:00
if err != nil && ! l . Options . Force {
2021-10-21 19:26:48 +00:00
return errors . Wrap ( err , "error met while unpacking package " + a . Path )
2019-11-22 22:12:03 +00:00
}
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
2022-01-06 22:57:56 +00:00
return s . Database . SetPackageFiles ( & types . PackageFile { PackageFingerprint : m . Package . GetFingerPrint ( ) , Files : files } )
2019-11-22 20:01:38 +00:00
}
2021-12-17 14:21:03 +00:00
func ( l * LuetInstaller ) downloadWorker ( i int , wg * sync . WaitGroup , pb * pterm . ProgressbarPrinter , c <- chan ArtifactMatch , ctx types . Context ) error {
2019-11-22 20:01:38 +00:00
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
2021-12-15 16:51:17 +00:00
_ , err := l . getPackage ( p , ctx )
2020-12-09 21:56:55 +00:00
if err != nil {
2021-10-20 22:13:02 +00:00
l . Options . Context . Error ( "Failed downloading package " + p . Package . GetName ( ) , err . Error ( ) )
2020-12-09 21:56:55 +00:00
return errors . Wrap ( err , "Failed downloading package " + p . Package . GetName ( ) )
2020-12-09 21:58:33 +00:00
} else {
2021-10-20 22:13:02 +00:00
l . Options . Context . Success ( ":package: Package " , p . Package . HumanReadableString ( ) , "downloaded" )
2020-03-24 17:13:10 +00:00
}
2021-10-31 19:06:59 +00:00
if pb != nil {
pb . Increment ( )
2021-10-22 10:35:03 +00:00
}
2020-03-24 17:13:10 +00:00
}
return nil
}
2021-10-24 07:20:26 +00:00
func ( l * LuetInstaller ) installerWorker ( i int , wg * sync . WaitGroup , installLock * sync . Mutex , c <- chan ArtifactMatch , s * System ) error {
2020-03-24 17:13:10 +00:00
defer wg . Done ( )
for p := range c {
// TODO: Keep trace of what was added from the tar, and save it into system
2021-10-24 07:20:26 +00:00
installLock . Lock ( )
2020-03-24 17:13:10 +00:00
err := l . installPackage ( p , s )
2021-10-24 07:20:26 +00:00
installLock . Unlock ( )
2020-03-24 17:13:10 +00:00
if err != nil && ! l . Options . Force {
//TODO: Uninstall, rollback.
2021-12-17 14:21:03 +00:00
l . Options . Context . Error ( "Failed installing package " + p . Package . GetName ( ) , err . Error ( ) )
2020-03-24 17:13:10 +00:00
return errors . Wrap ( err , "Failed installing package " + p . Package . GetName ( ) )
}
if err == nil {
2021-10-20 22:13:02 +00:00
l . Options . Context . Info ( ":package: Package " , p . Package . HumanReadableString ( ) , "installed" )
2020-02-18 17:37:56 +00:00
} else if err != nil && l . Options . Force {
2021-10-20 22:13:02 +00:00
l . Options . Context . Info ( ":package: Package " , p . Package . HumanReadableString ( ) , "installed with failures (forced install)" )
2020-02-18 17:37:56 +00:00
}
2019-11-22 20:01:38 +00:00
}
return nil
}
2019-11-23 23:16:12 +00:00
2021-12-17 14:21:03 +00:00
func checkAndPrunePath ( ctx types . Context , target , path string ) {
2021-04-24 17:21:15 +00:00
// check if now the target path is empty
targetPath := filepath . Dir ( path )
2021-10-21 19:26:48 +00:00
if target == targetPath {
return
}
2021-04-24 17:21:15 +00:00
fi , err := os . Lstat ( targetPath )
if err != nil {
2021-10-20 22:13:02 +00:00
// l.Options.Context.Warning("Dir not found (it was before?) ", err.Error())
2021-04-24 17:21:15 +00:00
return
}
switch mode := fi . Mode ( ) ; {
case mode . IsDir ( ) :
files , err := ioutil . ReadDir ( targetPath )
if err != nil {
2021-10-20 22:13:02 +00:00
ctx . Warning ( "Failed reading folder" , targetPath , err . Error ( ) )
return
2021-04-24 17:21:15 +00:00
}
if len ( files ) != 0 {
2021-10-20 22:13:02 +00:00
ctx . Debug ( "Preserving not-empty folder" , targetPath )
2021-04-24 17:21:15 +00:00
return
}
}
if err = os . Remove ( targetPath ) ; err != nil {
2021-10-20 22:13:02 +00:00
ctx . Warning ( "Failed removing file (maybe not present in the system target anymore ?)" , targetPath , err . Error ( ) )
2021-04-24 17:21:15 +00:00
}
}
// We will try to cleanup every path from the file, if the folders left behind are empty
2021-12-17 14:21:03 +00:00
func pruneEmptyFilePath ( ctx types . Context , target string , path string ) {
2021-10-21 19:26:48 +00:00
checkAndPrunePath ( ctx , target , path )
2021-04-24 17:21:15 +00:00
// A path is for e.g. /usr/bin/bar
2021-10-20 22:13:02 +00:00
// we want to create an array
// as "/usr", "/usr/bin", "/usr/bin/bar",
// excluding the target (in the case above was /)
2021-04-24 17:21:15 +00:00
paths := strings . Split ( path , string ( os . PathSeparator ) )
currentPath := filepath . Join ( string ( os . PathSeparator ) , paths [ 0 ] )
2021-10-21 19:26:48 +00:00
allPaths := [ ] string { }
if strings . HasPrefix ( currentPath , target ) && target != currentPath {
allPaths = append ( allPaths , currentPath )
}
2021-04-24 17:21:15 +00:00
for _ , p := range paths [ 1 : ] {
currentPath = filepath . Join ( currentPath , p )
2021-10-21 19:26:48 +00:00
if strings . HasPrefix ( currentPath , target ) && target != currentPath {
allPaths = append ( allPaths , currentPath )
}
2021-04-24 17:21:15 +00:00
}
2022-01-06 22:57:56 +00:00
helpers . ReverseAny ( allPaths )
2021-04-24 17:21:15 +00:00
for _ , p := range allPaths {
2021-10-21 19:26:48 +00:00
checkAndPrunePath ( ctx , target , p )
2021-04-24 17:21:15 +00:00
}
}
2021-12-24 10:44:50 +00:00
func ( l * LuetInstaller ) pruneFile ( f string , s * System , cp * config . ConfigProtect ) {
target := filepath . Join ( s . Target , f )
2020-11-06 19:14:25 +00:00
2021-12-24 10:44:50 +00:00
if ! l . Options . Context . GetConfig ( ) . ConfigProtectSkip && cp . Protected ( f ) {
l . Options . Context . Debug ( "Preserving protected file:" , f )
return
}
l . Options . Context . Debug ( "Removing" , target )
if l . Options . PreserveSystemEssentialData &&
strings . HasPrefix ( f , l . Options . Context . GetConfig ( ) . System . PkgsCachePath ) ||
strings . HasPrefix ( f , l . Options . Context . GetConfig ( ) . System . DatabasePath ) {
l . Options . Context . Warning ( "Preserve " , f , " which is required by luet ( you have to delete it manually if you really need to)" )
return
}
fi , err := os . Lstat ( target )
2019-11-23 23:16:12 +00:00
if err != nil {
2021-12-24 10:44:50 +00:00
l . Options . Context . Debug ( "File not found (it was before?) " , err . Error ( ) )
return
2019-11-23 23:16:12 +00:00
}
2021-12-24 10:44:50 +00:00
switch mode := fi . Mode ( ) ; {
case mode . IsDir ( ) :
files , err := ioutil . ReadDir ( target )
if err != nil {
l . Options . Context . Debug ( "Failed reading folder" , target , err . Error ( ) )
}
if len ( files ) != 0 {
l . Options . Context . Debug ( "Preserving not-empty folder" , target )
return
}
}
if err = os . Remove ( target ) ; err != nil {
l . Options . Context . Debug ( "Failed removing file (maybe not present in the system target anymore ?)" , target , err . Error ( ) )
} else {
l . Options . Context . Debug ( "Removed" , target )
}
pruneEmptyFilePath ( l . Options . Context , s . Target , target )
}
2022-01-06 22:57:56 +00:00
func ( l * LuetInstaller ) configProtectForPackage ( p * types . Package , s * System , files [ ] string ) * config . ConfigProtect {
2021-12-24 10:44:50 +00:00
var cp * config . ConfigProtect
2019-11-23 23:16:12 +00:00
2021-12-17 14:21:03 +00:00
if ! l . Options . Context . GetConfig ( ) . ConfigProtectSkip {
2022-01-06 22:57:56 +00:00
annotationDir , _ := p . Annotations [ types . ConfigProtectAnnotation ]
2020-11-06 19:14:25 +00:00
cp = config . NewConfigProtect ( annotationDir )
2021-12-17 14:21:03 +00:00
cp . Map ( files , l . Options . Context . GetConfig ( ) . ConfigProtectConfFiles )
2020-11-06 19:14:25 +00:00
}
2021-12-24 10:44:50 +00:00
return cp
}
2020-11-06 19:14:25 +00:00
2021-12-24 10:44:50 +00:00
func ( l * LuetInstaller ) pruneFiles ( files [ ] string , cp * config . ConfigProtect , s * System ) {
2020-02-27 22:14:36 +00:00
2021-12-24 10:44:50 +00:00
toRemove , notPresent := fileHelper . OrderFiles ( s . Target , files )
2021-04-24 17:21:15 +00:00
2021-12-24 10:44:50 +00:00
// Remove from target
for _ , f := range append ( toRemove , notPresent ... ) {
l . pruneFile ( f , s , cp )
2020-11-08 11:36:41 +00:00
}
2021-12-24 10:44:50 +00:00
}
2020-11-08 11:36:41 +00:00
2022-01-06 22:57:56 +00:00
func ( l * LuetInstaller ) uninstall ( p * types . Package , s * System ) error {
2021-12-24 10:44:50 +00:00
files , err := s . Database . GetPackageFiles ( p )
if err != nil {
return errors . Wrap ( err , "Failed getting installed files" )
}
2020-11-08 11:36:41 +00:00
2021-12-24 10:44:50 +00:00
cp := l . configProtectForPackage ( p , s , files )
2020-11-08 11:36:41 +00:00
2021-12-24 10:44:50 +00:00
l . pruneFiles ( files , cp , s )
2021-04-24 17:21:15 +00:00
2021-12-24 10:44:50 +00:00
err = l . removePackage ( p , s )
if err != nil {
return errors . Wrap ( err , "Failed removing package files from database" )
2019-11-23 23:16:12 +00:00
}
2020-11-08 11:36:41 +00:00
2021-12-24 10:44:50 +00:00
l . Options . Context . Info ( ":recycle: " , p . HumanReadableString ( ) , "Removed :heavy_check_mark:" )
return nil
}
2022-01-06 22:57:56 +00:00
func ( l * LuetInstaller ) removePackage ( p * types . Package , s * System ) error {
2021-12-24 10:44:50 +00:00
err := s . Database . RemovePackageFiles ( p )
2019-11-25 19:02:18 +00:00
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" )
}
2020-11-13 17:25:44 +00:00
bus . Manager . Publish ( bus . EventPackageUnInstall , p )
2019-11-23 23:16:12 +00:00
return nil
}
2022-01-06 22:57:56 +00:00
func ( l * LuetInstaller ) computeUninstall ( o Option , s * System , packs ... * types . Package ) ( types . Packages , error ) {
2020-05-03 11:16:52 +00:00
2022-01-06 22:57:56 +00:00
var toUninstall types . Packages
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
2021-04-24 12:26:26 +00:00
checkConflicts := o . CheckConflicts
full := o . FullUninstall
2021-04-24 16:15:47 +00:00
// if o.Force == true { // IF forced, we want to remove the package and all its requires
// checkConflicts = false
// full = false
// }
2020-05-03 10:16:45 +00:00
2020-05-02 13:43:57 +00:00
// Create a temporary DB with the installed packages
// so the solver is much faster finding the deptree
2020-12-19 16:45:50 +00:00
// First check what would have been done
installedtmp , err := s . Database . Copy ( )
if err != nil {
return toUninstall , errors . Wrap ( err , "Failed create temporary in-memory db" )
2020-05-02 13:43:57 +00:00
}
2020-02-27 17:26:48 +00:00
2021-04-24 16:15:47 +00:00
if ! o . NoDeps {
2022-01-06 22:57:56 +00:00
solv := solver . NewResolver (
types . SolverOptions {
Type : l . Options . SolverOptions . Implementation ,
Concurrency : l . Options . Concurrency ,
} ,
installedtmp ,
installedtmp ,
pkg . NewInMemoryDatabase ( false ) ,
solver . NewSolverFromOptions ( l . Options . SolverOptions ) )
var solution types . Packages
2020-05-22 18:45:28 +00:00
var err error
2021-04-24 12:26:26 +00:00
if o . FullCleanUninstall {
2020-12-19 16:16:53 +00:00
solution , err = solv . UninstallUniverse ( packs )
2020-05-22 18:45:28 +00:00
if err != nil {
2020-11-22 19:16:04 +00:00
return toUninstall , 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" )
2020-05-22 18:45:28 +00:00
}
} else {
2020-12-19 16:16:53 +00:00
solution , err = solv . Uninstall ( checkConflicts , full , packs ... )
2020-05-22 18:45:28 +00:00
if err != nil && ! l . Options . Force {
2020-11-22 19:16:04 +00:00
return toUninstall , 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" )
2020-05-22 18:45:28 +00:00
}
2020-02-18 17:37:56 +00:00
}
2020-05-22 18:45:28 +00:00
2021-10-20 22:13:02 +00:00
toUninstall = append ( toUninstall , solution ... )
2020-11-22 19:16:04 +00:00
} else {
2020-12-19 16:16:53 +00:00
toUninstall = append ( toUninstall , packs ... )
2020-11-22 19:16:04 +00:00
}
return toUninstall , nil
}
2020-12-19 16:16:53 +00:00
2022-01-06 22:57:56 +00:00
func ( l * LuetInstaller ) generateUninstallFn ( o Option , s * System , filesToInstall map [ string ] interface { } , packs ... * types . Package ) ( types . Packages , func ( ) error , error ) {
2020-12-19 16:16:53 +00:00
for _ , p := range packs {
if packs , _ := s . Database . FindPackages ( p ) ; len ( packs ) == 0 {
2021-08-26 12:45:19 +00:00
return nil , nil , errors . New ( fmt . Sprintf ( "Package %s not found in the system" , p . HumanReadableString ( ) ) )
2020-12-19 16:16:53 +00:00
}
2020-12-03 16:25:29 +00:00
}
2021-04-24 12:26:26 +00:00
toUninstall , err := l . computeUninstall ( o , s , packs ... )
2020-11-22 19:16:04 +00:00
if err != nil {
2021-04-24 12:26:26 +00:00
return nil , nil , errors . Wrap ( err , "while computing uninstall" )
2020-11-22 19:16:04 +00:00
}
uninstall := func ( ) error {
for _ , p := range toUninstall {
2021-12-24 10:44:50 +00:00
if len ( filesToInstall ) == 0 {
err := l . uninstall ( p , s )
if err != nil && ! o . Force {
return errors . Wrap ( err , "Uninstall failed" )
}
} else {
files , err := s . Database . GetPackageFiles ( p )
if err != nil && ! o . Force {
return errors . Wrap ( err , "Failed getting installed files" )
}
cp := l . configProtectForPackage ( p , s , files )
toPrune := [ ] string { }
for _ , f := range files {
if _ , exists := filesToInstall [ f ] ; ! exists {
toPrune = append ( toPrune , f )
}
}
l . Options . Context . Debug ( "calculated files for removal" , toPrune )
l . pruneFiles ( toPrune , cp , s )
err = l . removePackage ( p , s )
if err != nil && ! o . Force {
return errors . Wrap ( err , "Failed removing package" )
}
2020-02-18 17:37:56 +00:00
}
}
2020-11-22 19:16:04 +00:00
return nil
2019-11-23 23:16:12 +00:00
}
2019-11-22 20:01:38 +00:00
2021-04-24 12:26:26 +00:00
return toUninstall , uninstall , nil
}
2022-01-06 22:57:56 +00:00
func ( l * LuetInstaller ) Uninstall ( s * System , packs ... * types . Package ) error {
2021-10-20 22:13:02 +00:00
l . Options . Context . Screen ( "Uninstall" )
2021-04-24 12:26:26 +00:00
2021-10-20 22:13:02 +00:00
l . Options . Context . Spinner ( )
2021-04-24 12:26:26 +00:00
o := Option {
FullUninstall : l . Options . FullUninstall ,
Force : l . Options . Force ,
CheckConflicts : l . Options . CheckConflicts ,
FullCleanUninstall : l . Options . FullCleanUninstall ,
}
2021-12-24 10:44:50 +00:00
toUninstall , uninstall , err := l . generateUninstallFn ( o , s , map [ string ] interface { } { } , packs ... )
2021-04-24 12:26:26 +00:00
if err != nil {
return errors . Wrap ( err , "while computing uninstall" )
}
2021-10-20 22:13:02 +00:00
l . Options . Context . SpinnerStop ( )
2021-04-24 12:26:26 +00:00
2020-11-23 17:20:30 +00:00
if len ( toUninstall ) == 0 {
2021-10-20 22:13:02 +00:00
l . Options . Context . Info ( "Nothing to do" )
2020-11-23 17:20:30 +00:00
return nil
}
2020-11-22 19:16:04 +00:00
if l . Options . Ask {
2021-10-20 22:13:02 +00:00
l . Options . Context . Info ( ":recycle: Packages that are going to be removed from the system:" )
2021-10-19 20:26:23 +00:00
printList ( toUninstall )
2021-10-20 22:13:02 +00:00
if l . Options . Context . Ask ( ) {
2020-11-23 17:20:30 +00:00
l . Options . Ask = false // Don't prompt anymore
2020-11-22 19:16:04 +00:00
return uninstall ( )
} else {
return errors . New ( "Aborted by user" )
}
}
return uninstall ( )
2019-11-22 20:01:38 +00:00
}