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 (
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"
2020-11-22 19:16:04 +00:00
. "github.com/logrusorgru/aurora"
2020-11-13 17:25:44 +00:00
"github.com/mudler/luet/pkg/bus"
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"
"github.com/pkg/errors"
)
2020-02-12 10:21:30 +00:00
type LuetInstallerOptions struct {
2020-07-12 13:27:50 +00:00
SolverOptions config . LuetSolverOptions
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
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
}
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
}
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
2020-11-23 17:20:30 +00:00
func ( l * LuetInstaller ) computeUpgrade ( syncedRepos Repositories , s * System ) ( pkg . Packages , pkg . Packages , error ) {
2020-11-22 19:16:04 +00:00
toInstall := pkg . Packages { }
var uninstall pkg . 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
2020-10-25 17:43:35 +00:00
solv := solver . NewResolver ( solver . Options { Type : l . Options . SolverOptions . Implementation , Concurrency : l . Options . Concurrency } , s . Database , allRepos , pkg . NewInMemoryDatabase ( false ) , l . Options . SolverOptions . Resolver ( ) )
2020-05-22 18:45:28 +00:00
var solution solver . PackagesAssertions
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 ( ) {
matches := syncedRepos . PackageMatches ( pkg . Packages { p } )
if len ( matches ) == 0 {
// Package missing. the user should run luet upgrade --universe
continue
}
for _ , artefact := range matches [ 0 ] . Repo . GetIndex ( ) {
if artefact . GetCompileSpec ( ) . 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
}
if artefact . GetCompileSpec ( ) . GetPackage ( ) . Matches ( p ) && artefact . GetCompileSpec ( ) . GetPackage ( ) . GetBuildTimestamp ( ) != p . GetBuildTimestamp ( ) {
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
}
func packsToList ( p pkg . Packages ) string {
var packs [ ] string
for _ , pp := range p {
packs = append ( packs , pp . HumanReadableString ( ) )
}
return strings . Join ( packs , " " )
}
2020-12-03 16:25:29 +00:00
func matchesToList ( artefacts map [ string ] ArtifactMatch ) string {
var packs [ ] string
for fingerprint , match := range artefacts {
packs = append ( packs , fmt . Sprintf ( "%s (%s)" , fingerprint , match . Repository . GetName ( ) ) )
}
return strings . Join ( packs , " " )
}
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 {
2020-11-23 17:20:30 +00:00
syncedRepos , err := l . SyncRepositories ( true )
if err != nil {
return err
}
2020-11-22 19:16:04 +00:00
Info ( ":thinking: Computing upgrade, please hang tight... :zzz:" )
if l . Options . UpgradeNewRevisions {
Info ( ":memo: note: will consider new build revisions while upgrading" )
}
2020-11-23 17:20:30 +00:00
Spinner ( 32 )
uninstall , toInstall , err := l . computeUpgrade ( syncedRepos , s )
2020-11-22 19:16:04 +00:00
if err != nil {
return errors . Wrap ( err , "failed computing upgrade" )
}
2020-11-23 17:20:30 +00:00
SpinnerStop ( )
2020-11-22 19:16:04 +00:00
if len ( uninstall ) > 0 {
Info ( ":recycle: Packages that are going to be removed from the system:\n " , Yellow ( packsToList ( uninstall ) ) . BgBlack ( ) . String ( ) )
}
if len ( toInstall ) > 0 {
2020-12-02 17:24:32 +00:00
Info ( ":zap:Packages that are going to be installed in the system:\n " , Green ( packsToList ( toInstall ) ) . BgBlack ( ) . String ( ) )
2020-11-22 19:16:04 +00:00
}
2020-11-23 17:20:30 +00:00
if len ( toInstall ) == 0 && len ( uninstall ) == 0 {
Info ( "Nothing to do" )
return nil
}
2020-11-22 19:16:04 +00:00
if l . Options . Ask {
Info ( "By going forward, you are also accepting the licenses of the packages that you are going to install in your system." )
if 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 l . swap ( syncedRepos , uninstall , toInstall , s )
} else {
return errors . New ( "Aborted by user" )
}
}
2020-11-23 17:20:30 +00:00
Spinner ( 32 )
defer SpinnerStop ( )
2020-02-27 22:48:14 +00:00
return l . swap ( syncedRepos , uninstall , toInstall , s )
}
func ( l * LuetInstaller ) SyncRepositories ( inMemory bool ) ( Repositories , error ) {
Spinner ( 32 )
defer SpinnerStop ( )
syncedRepos := Repositories { }
for _ , r := range l . PackageRepositories {
repo , err := r . Sync ( false )
if err != nil {
return nil , errors . Wrap ( err , "Failed syncing repository: " + r . GetName ( ) )
}
syncedRepos = append ( syncedRepos , repo )
}
// compute what to install and from where
sort . Sort ( syncedRepos )
if ! inMemory {
l . PackageRepositories = syncedRepos
}
return syncedRepos , nil
}
2020-04-04 12:29:08 +00:00
func ( l * LuetInstaller ) Swap ( toRemove pkg . Packages , toInstall pkg . Packages , s * System ) error {
2020-02-27 22:48:14 +00:00
syncedRepos , err := l . SyncRepositories ( true )
if err != nil {
return err
}
2020-12-02 17:24:32 +00:00
2020-12-17 23:49:51 +00:00
toRemoveFinal := pkg . Packages { }
toInstallFinal := pkg . Packages { }
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 )
}
}
match , _ , _ , _ , err := l . computeInstall ( syncedRepos , toInstall , s )
if err != nil {
return err
}
for _ , m := range match {
toInstallFinal = append ( toInstallFinal , m . Package )
}
2020-12-02 17:24:32 +00:00
if len ( toRemove ) > 0 {
Info ( ":recycle: Packages that are going to be removed from the system:\n " , Yellow ( packsToList ( toRemove ) ) . BgBlack ( ) . String ( ) )
}
if len ( toInstall ) > 0 {
Info ( ":zap:Packages that are going to be installed in the system:\n " , Green ( packsToList ( toInstall ) ) . BgBlack ( ) . String ( ) )
}
if l . Options . Ask {
Info ( "By going forward, you are also accepting the licenses of the packages that you are going to install in your system." )
if Ask ( ) {
l . Options . Ask = false // Don't prompt anymore
2020-12-17 23:49:51 +00:00
return l . swap ( syncedRepos , toRemoveFinal , toInstallFinal , s )
2020-12-02 17:24:32 +00:00
} else {
return errors . New ( "Aborted by user" )
}
}
2020-12-17 23:49:51 +00:00
return l . swap ( syncedRepos , toRemoveFinal , toInstallFinal , s )
2020-02-27 22:48:14 +00:00
}
2020-04-04 12:29:08 +00:00
func ( l * LuetInstaller ) swap ( syncedRepos Repositories , toRemove pkg . Packages , toInstall pkg . Packages , s * System ) error {
2020-02-27 22:48:14 +00:00
// First match packages against repositories by priority
allRepos := pkg . NewInMemoryDatabase ( false )
syncedRepos . SyncDatabase ( allRepos )
toInstall = syncedRepos . ResolveSelectors ( toInstall )
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.
2020-05-03 10:16:45 +00:00
forced := l . Options . Force
2020-12-06 21:35:30 +00:00
nodeps := l . Options . NoDeps
2020-02-27 17:26:48 +00:00
l . Options . Force = true
2020-12-06 21:35:30 +00:00
l . Options . NoDeps = true
2020-02-27 17:26:48 +00:00
2020-12-06 21:11:17 +00:00
// First check what would have been done
installedtmp := pkg . NewInMemoryDatabase ( false )
for _ , i := range s . Database . World ( ) {
_ , err := installedtmp . CreatePackage ( i )
if err != nil {
return errors . Wrap ( err , "Failed create temporary in-memory db" )
}
}
systemAfterChanges := & System { Database : installedtmp }
2020-02-27 22:48:14 +00:00
for _ , u := range toRemove {
2020-12-06 21:11:17 +00:00
packs , err := l . computeUninstall ( u , systemAfterChanges )
2020-02-18 17:37:56 +00:00
if err != nil && ! l . Options . Force {
2020-12-06 21:11:17 +00:00
Error ( "Failed computing uninstall for " , u . HumanReadableString ( ) )
return errors . Wrap ( err , "computing uninstall " + u . HumanReadableString ( ) )
}
for _ , p := range packs {
err = systemAfterChanges . Database . RemovePackage ( p )
if err != nil {
return errors . Wrap ( err , "Failed removing package from database" )
}
2019-11-29 18:01:56 +00:00
}
}
2020-12-06 21:11:17 +00:00
match , packages , assertions , allRepos , err := l . computeInstall ( syncedRepos , toInstall , systemAfterChanges )
2020-11-22 19:16:04 +00:00
if err != nil {
return errors . Wrap ( err , "computing installation" )
}
2020-12-06 21:11:17 +00:00
if err := l . download ( syncedRepos , match ) ; err != nil {
return errors . Wrap ( err , "Pre-downloading packages" )
}
for _ , u := range toRemove {
err := l . Uninstall ( u , s )
if err != nil && ! l . Options . Force {
Error ( "Failed uninstall for " , u . HumanReadableString ( ) )
return errors . Wrap ( err , "uninstalling " + u . HumanReadableString ( ) )
}
}
2020-12-06 21:35:30 +00:00
l . Options . Force = forced
l . Options . NoDeps = nodeps
2020-11-22 19:16:04 +00:00
return l . install ( syncedRepos , match , packages , assertions , allRepos , s )
2019-11-29 18:01:56 +00:00
}
2020-04-13 16:49:05 +00:00
func ( l * LuetInstaller ) Install ( cp pkg . Packages , s * System ) error {
2020-02-27 22:25:29 +00:00
syncedRepos , err := l . SyncRepositories ( true )
if err != nil {
return err
}
2020-11-22 19:16:04 +00:00
2020-11-23 17:20:30 +00:00
match , packages , assertions , allRepos , err := l . computeInstall ( 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 {
Info ( "No packages to install" )
return nil
}
2020-12-03 17:53:57 +00:00
// Resolvers might decide to remove some packages from being installed
if ! l . Options . SolverOptions . ResolverIsSet ( ) {
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
}
}
2020-12-03 19:03:37 +00:00
2020-12-03 17:53:57 +00:00
if ! found {
return fmt . Errorf ( "Package '%s' not found" , p . HumanReadableString ( ) )
2020-12-03 16:25:29 +00:00
}
}
}
2020-12-03 17:32:24 +00:00
Info ( "Packages that are going to be installed in the system: \n " , Green ( matchesToList ( match ) ) . BgBlack ( ) . String ( ) )
2020-11-22 19:16:04 +00:00
if l . Options . Ask {
Info ( "By going forward, you are also accepting the licenses of the packages that you are going to install in your system." )
if 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 l . install ( syncedRepos , match , packages , assertions , allRepos , s )
} else {
return errors . New ( "Aborted by user" )
}
}
return l . install ( 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
// Download packages into cache in parallel.
all := make ( chan ArtifactMatch )
var wg = new ( sync . WaitGroup )
// Download
for i := 0 ; i < l . Options . Concurrency ; i ++ {
wg . Add ( 1 )
go l . downloadWorker ( i , wg , all )
}
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 {
syncedRepos , err := l . SyncRepositories ( true )
if err != nil {
return err
}
var toMerge [ ] ArtifactMatch = [ ] ArtifactMatch { }
for _ , repo := range syncedRepos {
for _ , artefact := range repo . GetIndex ( ) {
2020-05-02 10:17:54 +00:00
Debug ( "Checking if" ,
artefact . GetCompileSpec ( ) . GetPackage ( ) . HumanReadableString ( ) ,
"from" , repo . GetName ( ) , "is installed" )
2020-04-13 17:30:40 +00:00
FILES :
for _ , f := range artefact . GetFiles ( ) {
if helpers . Exists ( filepath . Join ( s . Target , f ) ) {
2020-04-30 16:56:50 +00:00
p , err := repo . GetTree ( ) . GetDatabase ( ) . FindPackage ( artefact . GetCompileSpec ( ) . GetPackage ( ) )
if err != nil {
return err
}
2020-10-30 18:15:04 +00:00
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 {
Warning ( "Filtering out package " + pack . HumanReadableString ( ) + ", already reclaimed" )
continue
}
_ , err := s . Database . CreatePackage ( pack )
if err != nil && ! l . Options . Force {
return errors . Wrap ( err , "Failed creating package" )
}
s . Database . SetPackageFiles ( & pkg . PackageFile { PackageFingerprint : pack . GetFingerPrint ( ) , Files : match . Artifact . GetFiles ( ) } )
2020-12-02 17:24:32 +00:00
Info ( ":zap:Reclaimed package:" , pack . HumanReadableString ( ) )
2020-04-13 17:30:40 +00:00
}
2020-05-02 10:17:54 +00:00
Info ( "Done!" )
2020-04-13 17:30:40 +00:00
return nil
}
2020-11-23 17:20:30 +00:00
func ( l * LuetInstaller ) computeInstall ( syncedRepos Repositories , cp pkg . Packages , s * System ) ( map [ string ] ArtifactMatch , pkg . Packages , solver . PackagesAssertions , pkg . PackageDatabase , error ) {
2020-04-04 12:29:08 +00:00
var p pkg . Packages
2020-11-22 19:16:04 +00:00
toInstall := map [ string ] ArtifactMatch { }
allRepos := pkg . NewInMemoryDatabase ( false )
var solution solver . 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 {
2020-11-22 19:16:04 +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 {
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 )
2020-04-04 12:29:08 +00:00
var packagesToInstall pkg . Packages
2020-02-27 22:25:29 +00:00
var err error
2020-02-18 17:37:56 +00:00
if ! l . Options . NoDeps {
2020-10-25 17:43:35 +00:00
solv := solver . NewResolver ( solver . Options { Type : l . Options . SolverOptions . Implementation , Concurrency : l . Options . Concurrency } , s . Database , allRepos , pkg . NewInMemoryDatabase ( false ) , l . Options . SolverOptions . Resolver ( ) )
2020-02-18 17:37:56 +00:00
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)
2020-02-18 17:37:56 +00:00
if err != nil && ! l . Options . 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 )
}
}
} else if ! l . Options . OnlyDeps {
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
2020-04-04 12:29:08 +00:00
matches := syncedRepos . PackageMatches ( pkg . 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 ( ) {
if artefact . GetCompileSpec ( ) . 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
}
if matches [ 0 ] . Package . Matches ( artefact . GetCompileSpec ( ) . GetPackage ( ) ) {
2020-07-12 13:27:50 +00:00
currentPack . SetBuildTimestamp ( artefact . GetCompileSpec ( ) . 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
}
func ( l * LuetInstaller ) install ( syncedRepos Repositories , toInstall map [ string ] ArtifactMatch , p pkg . Packages , solution solver . PackagesAssertions , allRepos pkg . PackageDatabase , s * System ) error {
2019-11-22 21:23:05 +00:00
// Install packages into rootfs in parallel.
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
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 )
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 )
go l . installerWorker ( i , wg , 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 )
if err != nil && ! l . Options . Force {
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-11-08 20:14:19 +00:00
var toFinalize [ ] pkg . Package
2020-03-13 18:31:44 +00:00
if ! l . Options . NoDeps {
// TODO: Lower those errors as warning
for _ , w := range p {
// Finalizers needs to run in order and in sequence.
2020-04-09 15:07:57 +00:00
ordered , err := solution . Order ( allRepos , w . GetFingerPrint ( ) )
if err != nil {
return errors . Wrap ( err , "While order a solution for " + w . HumanReadableString ( ) )
}
2020-03-13 18:31:44 +00:00
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 errors . Wrap ( err , "Error getting package " + ass . Package . HumanReadableString ( ) )
2019-11-22 21:23:05 +00:00
}
2020-03-28 15:57:40 +00:00
2020-11-08 20:14:19 +00:00
toFinalize = append ( toFinalize , treePackage )
2019-11-22 21:23:05 +00:00
}
}
2020-03-13 18:31:44 +00:00
}
} else {
for _ , c := range toInstall {
treePackage , err := c . Repository . GetTree ( ) . GetDatabase ( ) . FindPackage ( c . Package )
if err != nil {
return errors . Wrap ( err , "Error getting package " + c . Package . HumanReadableString ( ) )
}
2020-11-08 20:14:19 +00:00
toFinalize = append ( toFinalize , treePackage )
2020-03-13 18:31:44 +00:00
}
2019-11-22 21:23:05 +00:00
}
2020-03-28 15:57:40 +00:00
2020-11-08 20:14:19 +00:00
return s . ExecuteFinalizers ( toFinalize , l . Options . Force )
2019-11-22 20:01:38 +00:00
}
2020-03-24 17:13:10 +00:00
func ( l * LuetInstaller ) downloadPackage ( a ArtifactMatch ) ( compiler . Artifact , error ) {
2019-11-22 20:01:38 +00:00
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 {
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
}
func ( l * LuetInstaller ) installPackage ( a ArtifactMatch , s * System ) error {
artifact , err := l . downloadPackage ( a )
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
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
}
2020-03-24 17:13:10 +00:00
func ( l * LuetInstaller ) downloadWorker ( i int , wg * sync . WaitGroup , c <- chan ArtifactMatch ) 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
2020-03-24 17:13:10 +00:00
_ , err := l . downloadPackage ( p )
2020-12-09 21:56:55 +00:00
if err != nil {
Fatal ( "Failed downloading package " + p . Package . GetName ( ) , err . Error ( ) )
return errors . Wrap ( err , "Failed downloading package " + p . Package . GetName ( ) )
2020-12-09 21:58:33 +00:00
} else {
2020-12-09 21:56:55 +00:00
Info ( ":package: Package " , p . Package . HumanReadableString ( ) , "downloaded" )
2020-03-24 17:13:10 +00:00
}
}
return nil
}
func ( l * LuetInstaller ) installerWorker ( i int , wg * sync . WaitGroup , c <- chan ArtifactMatch , s * System ) error {
defer wg . Done ( )
for p := range c {
// TODO: Keep trace of what was added from the tar, and save it into system
err := l . installPackage ( p , s )
if err != nil && ! l . Options . Force {
//TODO: Uninstall, rollback.
Fatal ( "Failed installing package " + p . Package . GetName ( ) , err . Error ( ) )
return errors . Wrap ( err , "Failed installing package " + p . Package . GetName ( ) )
}
if err == nil {
2020-11-22 19:16:04 +00:00
Info ( ":package: Package " , p . Package . HumanReadableString ( ) , "installed" )
2020-02-18 17:37:56 +00:00
} else if err != nil && l . Options . Force {
2020-11-22 19:16:04 +00:00
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
func ( l * LuetInstaller ) uninstall ( p pkg . Package , s * System ) error {
2020-11-06 19:14:25 +00:00
var cp * config . ConfigProtect
annotationDir := ""
2019-11-23 23:16:12 +00:00
files , err := s . Database . GetPackageFiles ( p )
if err != nil {
return errors . Wrap ( err , "Failed getting installed files" )
}
2020-11-06 19:14:25 +00:00
if ! config . LuetCfg . ConfigProtectSkip {
if p . HasAnnotation ( string ( pkg . ConfigProtectAnnnotation ) ) {
dir , ok := p . GetAnnotations ( ) [ string ( pkg . ConfigProtectAnnnotation ) ]
if ok {
annotationDir = dir
}
}
cp = config . NewConfigProtect ( annotationDir )
cp . Map ( files )
}
2020-11-08 11:36:41 +00:00
toRemove , notPresent := helpers . OrderFiles ( s . Target , files )
2019-11-23 23:16:12 +00:00
// Remove from target
2020-11-08 11:36:41 +00:00
for _ , f := range toRemove {
2019-11-23 23:16:12 +00:00
target := filepath . Join ( s . Target , f )
2020-02-27 22:14:36 +00:00
2020-11-06 19:14:25 +00:00
if ! config . LuetCfg . ConfigProtectSkip && cp . Protected ( f ) {
2020-11-07 11:27:18 +00:00
Debug ( "Preserving protected file:" , f )
2020-11-06 19:14:25 +00:00
continue
}
Debug ( "Removing" , target )
2020-02-27 22:14:36 +00:00
if l . Options . PreserveSystemEssentialData &&
strings . HasPrefix ( f , config . LuetCfg . GetSystem ( ) . GetSystemPkgsCacheDirPath ( ) ) ||
strings . HasPrefix ( f , config . LuetCfg . GetSystem ( ) . GetSystemRepoDatabaseDirPath ( ) ) {
Warning ( "Preserve " , f , " which is required by luet ( you have to delete it manually if you really need to)" )
continue
}
2020-11-06 20:34:56 +00:00
fi , err := os . Lstat ( target )
2019-11-23 23:16:12 +00:00
if err != nil {
2020-11-08 11:36:41 +00:00
Warning ( "File not found (it was before?) " , err . Error ( ) )
2020-10-31 10:56:03 +00:00
continue
}
switch mode := fi . Mode ( ) ; {
case mode . IsDir ( ) :
files , err := ioutil . ReadDir ( target )
if err != nil {
2020-11-06 20:34:56 +00:00
Warning ( "Failed reading folder" , target , err . Error ( ) )
2020-10-31 10:56:03 +00:00
}
if len ( files ) != 0 {
2020-11-08 11:36:41 +00:00
Debug ( "Preserving not-empty folder" , target )
2020-10-31 10:56:03 +00:00
continue
}
}
if err = os . Remove ( target ) ; err != nil {
2020-11-08 11:36:41 +00:00
Warning ( "Failed removing file (maybe not present in the system target anymore ?)" , target , err . Error ( ) )
}
}
for _ , f := range notPresent {
target := filepath . Join ( s . Target , f )
if ! config . LuetCfg . ConfigProtectSkip && cp . Protected ( f ) {
Debug ( "Preserving protected file:" , f )
continue
}
if err = os . Remove ( target ) ; err != nil {
Debug ( "Failed removing file (not present in the system target)" , target , err . Error ( ) )
2019-11-23 23:16:12 +00:00
}
}
2020-11-08 11:36:41 +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" )
}
2020-11-13 17:25:44 +00:00
bus . Manager . Publish ( bus . EventPackageUnInstall , p )
2020-12-02 17:24:32 +00:00
Info ( ":recycle: " , p . GetFingerPrint ( ) , "Removed :heavy_check_mark:" )
2019-11-23 23:16:12 +00:00
return nil
}
2020-11-23 17:20:30 +00:00
func ( l * LuetInstaller ) computeUninstall ( p pkg . Package , s * System ) ( pkg . Packages , error ) {
2020-05-03 11:16:52 +00:00
2020-11-22 19:16:04 +00:00
var toUninstall pkg . 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
2020-05-03 10:16:45 +00:00
checkConflicts := l . Options . CheckConflicts
full := l . Options . FullUninstall
if l . Options . Force == true { // IF forced, we want to remove the package and all its requires
2020-02-27 17:26:48 +00:00
checkConflicts = false
2020-05-03 10:16:45 +00:00
full = false
2020-02-27 17:26:48 +00:00
}
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
installedtmp := pkg . NewInMemoryDatabase ( false )
for _ , i := range s . Database . World ( ) {
_ , err := installedtmp . CreatePackage ( i )
if err != nil {
2020-11-22 19:16:04 +00:00
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
2020-02-18 17:37:56 +00:00
if ! l . Options . NoDeps {
2020-10-25 17:43:35 +00:00
solv := solver . NewResolver ( solver . Options { Type : l . Options . SolverOptions . Implementation , Concurrency : l . Options . Concurrency } , installedtmp , installedtmp , pkg . NewInMemoryDatabase ( false ) , l . Options . SolverOptions . Resolver ( ) )
2020-05-22 18:45:28 +00:00
var solution pkg . Packages
var err error
if l . Options . FullCleanUninstall {
solution , err = solv . UninstallUniverse ( pkg . Packages { p } )
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-11-19 15:25:51 +00:00
solution , err = solv . Uninstall ( checkConflicts , full , p )
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
2020-02-18 17:37:56 +00:00
for _ , p := range solution {
2020-11-22 19:16:04 +00:00
toUninstall = append ( toUninstall , p )
}
} else {
toUninstall = append ( toUninstall , p )
}
return toUninstall , nil
}
func ( l * LuetInstaller ) Uninstall ( p pkg . Package , s * System ) error {
2020-12-17 23:50:20 +00:00
if packs , _ := s . Database . FindPackages ( p ) ; len ( packs ) == 0 {
return errors . New ( "Package not found in the system" )
2020-12-03 16:25:29 +00:00
}
2020-11-22 19:16:04 +00:00
Spinner ( 32 )
2020-11-23 17:20:30 +00:00
toUninstall , err := l . computeUninstall ( p , s )
2020-11-22 19:16:04 +00:00
if err != nil {
return errors . Wrap ( err , "while computing uninstall" )
}
2020-11-24 17:27:49 +00:00
SpinnerStop ( )
2020-11-22 19:16:04 +00:00
uninstall := func ( ) error {
for _ , p := range toUninstall {
2020-02-18 17:37:56 +00:00
err := l . uninstall ( p , s )
if err != nil && ! l . Options . Force {
return errors . Wrap ( err , "Uninstall failed" )
}
}
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
2020-11-23 17:20:30 +00:00
if len ( toUninstall ) == 0 {
Info ( "Nothing to do" )
return nil
}
2020-11-22 19:16:04 +00:00
Info ( ":recycle: Packages that are going to be removed from the system:\n " , Yellow ( packsToList ( toUninstall ) ) . BgBlack ( ) . String ( ) )
if l . Options . Ask {
Info ( "By going forward, you are also accepting the licenses of the packages that you are going to install in your system." )
if 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
}
func ( l * LuetInstaller ) Repositories ( r [ ] Repository ) { l . PackageRepositories = r }