diff --git a/cmd/build.go b/cmd/build.go index f696ef6c..2d6d4f31 100644 --- a/cmd/build.go +++ b/cmd/build.go @@ -36,8 +36,30 @@ import ( var buildCmd = &cobra.Command{ Use: "build ...", Short: "build a package or a tree", - Long: `build packages or trees from luet tree definitions. Packages are in [category]/[name]-[version] form`, - PreRun: func(cmd *cobra.Command, args []string) { + Long: `Builds one or more packages from a tree (current directory is implied): + + $ luet build utils/busybox utils/yq ... + +Builds all packages + + $ luet build --all + +Builds only the leaf packages: + + $ luet build --full + +Build package revdeps: + + $ luet build --revdeps utils/yq + +Build package without dependencies (needs the images already in the host, or either need to be available online): + + $ luet build --nodeps utils/yq ... + +Build packages specifying multiple definition trees: + + $ luet build --tree overlay/path --tree overlay/path2 utils/yq ... +`, PreRun: func(cmd *cobra.Command, args []string) { viper.BindPFlag("clean", cmd.Flags().Lookup("clean")) viper.BindPFlag("tree", cmd.Flags().Lookup("tree")) viper.BindPFlag("destination", cmd.Flags().Lookup("destination")) diff --git a/cmd/install.go b/cmd/install.go index df212982..6139d3a1 100644 --- a/cmd/install.go +++ b/cmd/install.go @@ -30,8 +30,24 @@ import ( ) var installCmd = &cobra.Command{ - Use: "install ...", - Short: "Install a package", + Use: "install ...", + Short: "Install a package", + Long: `Installs one or more packages without asking questions: + + $ luet install -y utils/busybox utils/yq ... + +To install only deps of a package: + + $ luet install --onlydeps utils/busybox ... + +To not install deps of a package: + + $ luet install --nodeps utils/busybox ... + +To force install a package: + + $ luet install --force utils/busybox ... +`, Aliases: []string{"i"}, PreRun: func(cmd *cobra.Command, args []string) { LuetCfg.Viper.BindPFlag("system.database_path", cmd.Flags().Lookup("system-dbpath")) @@ -45,7 +61,6 @@ var installCmd = &cobra.Command{ LuetCfg.Viper.BindPFlag("force", cmd.Flags().Lookup("force")) LuetCfg.Viper.BindPFlag("yes", cmd.Flags().Lookup("yes")) }, - Long: `Install packages in parallel`, Run: func(cmd *cobra.Command, args []string) { var toInstall pkg.Packages var systemDB pkg.PackageDatabase diff --git a/cmd/replace.go b/cmd/replace.go new file mode 100644 index 00000000..c8e51e25 --- /dev/null +++ b/cmd/replace.go @@ -0,0 +1,156 @@ +// Copyright © 2020 Ettore Di Giacinto +// +// 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 . +package cmd + +import ( + "os" + "path/filepath" + + installer "github.com/mudler/luet/pkg/installer" + "github.com/mudler/luet/pkg/solver" + + helpers "github.com/mudler/luet/cmd/helpers" + . "github.com/mudler/luet/pkg/config" + . "github.com/mudler/luet/pkg/logger" + pkg "github.com/mudler/luet/pkg/package" + + "github.com/spf13/cobra" +) + +var replaceCmd = &cobra.Command{ + Use: "replace --for --for ...", + Short: "replace a set of packages", + Aliases: []string{"r"}, + Long: `Replaces one or a group of packages without asking questions: + + $ luet replace -y system/busybox ... --for shells/bash --for system/coreutils ... +`, + PreRun: func(cmd *cobra.Command, args []string) { + LuetCfg.Viper.BindPFlag("system.database_path", cmd.Flags().Lookup("system-dbpath")) + LuetCfg.Viper.BindPFlag("system.rootfs", cmd.Flags().Lookup("system-target")) + LuetCfg.Viper.BindPFlag("solver.type", cmd.Flags().Lookup("solver-type")) + LuetCfg.Viper.BindPFlag("solver.discount", cmd.Flags().Lookup("solver-discount")) + LuetCfg.Viper.BindPFlag("solver.rate", cmd.Flags().Lookup("solver-rate")) + LuetCfg.Viper.BindPFlag("solver.max_attempts", cmd.Flags().Lookup("solver-attempts")) + LuetCfg.Viper.BindPFlag("onlydeps", cmd.Flags().Lookup("onlydeps")) + LuetCfg.Viper.BindPFlag("nodeps", cmd.Flags().Lookup("nodeps")) + LuetCfg.Viper.BindPFlag("force", cmd.Flags().Lookup("force")) + LuetCfg.Viper.BindPFlag("for", cmd.Flags().Lookup("for")) + + LuetCfg.Viper.BindPFlag("yes", cmd.Flags().Lookup("yes")) + }, + Run: func(cmd *cobra.Command, args []string) { + var toUninstall pkg.Packages + var toAdd pkg.Packages + var systemDB pkg.PackageDatabase + + f := LuetCfg.Viper.GetStringSlice("for") + stype := LuetCfg.Viper.GetString("solver.type") + discount := LuetCfg.Viper.GetFloat64("solver.discount") + rate := LuetCfg.Viper.GetFloat64("solver.rate") + attempts := LuetCfg.Viper.GetInt("solver.max_attempts") + force := LuetCfg.Viper.GetBool("force") + nodeps := LuetCfg.Viper.GetBool("nodeps") + onlydeps := LuetCfg.Viper.GetBool("onlydeps") + concurrent, _ := cmd.Flags().GetBool("solver-concurrent") + yes := LuetCfg.Viper.GetBool("yes") + + for _, a := range args { + pack, err := helpers.ParsePackageStr(a) + if err != nil { + Fatal("Invalid package string ", a, ": ", err.Error()) + } + toUninstall = append(toUninstall, pack) + } + + for _, a := range f { + pack, err := helpers.ParsePackageStr(a) + if err != nil { + Fatal("Invalid package string ", a, ": ", err.Error()) + } + toAdd = append(toAdd, pack) + } + + // This shouldn't be necessary, but we need to unmarshal the repositories to a concrete struct, thus we need to port them back to the Repositories type + repos := installer.Repositories{} + for _, repo := range LuetCfg.SystemRepositories { + if !repo.Enable { + continue + } + r := installer.NewSystemRepository(repo) + repos = append(repos, r) + } + + LuetCfg.GetSolverOptions().Type = stype + LuetCfg.GetSolverOptions().LearnRate = float32(rate) + LuetCfg.GetSolverOptions().Discount = float32(discount) + LuetCfg.GetSolverOptions().MaxAttempts = attempts + + if concurrent { + LuetCfg.GetSolverOptions().Implementation = solver.ParallelSimple + } else { + LuetCfg.GetSolverOptions().Implementation = solver.SingleCoreSimple + } + + Debug("Solver", LuetCfg.GetSolverOptions().CompactString()) + + // Load config protect configs + installer.LoadConfigProtectConfs(LuetCfg) + + inst := installer.NewLuetInstaller(installer.LuetInstallerOptions{ + Concurrency: LuetCfg.GetGeneral().Concurrency, + SolverOptions: *LuetCfg.GetSolverOptions(), + NoDeps: nodeps, + Force: force, + OnlyDeps: onlydeps, + PreserveSystemEssentialData: true, + Ask: !yes, + }) + inst.Repositories(repos) + + if LuetCfg.GetSystem().DatabaseEngine == "boltdb" { + systemDB = pkg.NewBoltDatabase( + filepath.Join(LuetCfg.GetSystem().GetSystemRepoDatabaseDirPath(), "luet.db")) + } else { + systemDB = pkg.NewInMemoryDatabase(true) + } + system := &installer.System{Database: systemDB, Target: LuetCfg.GetSystem().Rootfs} + err := inst.Swap(toUninstall, toAdd, system) + if err != nil { + Fatal("Error: " + err.Error()) + } + }, +} + +func init() { + path, err := os.Getwd() + if err != nil { + Fatal(err) + } + replaceCmd.Flags().String("system-dbpath", path, "System db path") + replaceCmd.Flags().String("system-target", path, "System rootpath") + replaceCmd.Flags().String("solver-type", "", "Solver strategy ( Defaults none, available: "+AvailableResolvers+" )") + replaceCmd.Flags().Float32("solver-rate", 0.7, "Solver learning rate") + replaceCmd.Flags().Float32("solver-discount", 1.0, "Solver discount rate") + replaceCmd.Flags().Int("solver-attempts", 9000, "Solver maximum attempts") + replaceCmd.Flags().Bool("nodeps", false, "Don't consider package dependencies (harmful!)") + replaceCmd.Flags().Bool("onlydeps", false, "Consider **only** package dependencies") + replaceCmd.Flags().Bool("force", false, "Skip errors and keep going (potentially harmful)") + replaceCmd.Flags().Bool("solver-concurrent", false, "Use concurrent solver (experimental)") + replaceCmd.Flags().BoolP("yes", "y", false, "Don't ask questions") + replaceCmd.Flags().StringSlice("for", []string{}, "Packages that has to be installed in place of others") + + RootCmd.AddCommand(replaceCmd) +} diff --git a/cmd/root.go b/cmd/root.go index b33988af..d300424f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -55,12 +55,12 @@ var ( // RootCmd represents the base command when called without any subcommands var RootCmd = &cobra.Command{ Use: "luet", - Short: "Package manager for the XXth century!", + Short: "Container based package manager", Long: `Luet is a single-binary package manager based on containers to build packages. To install a package: -$ luet install package + $ luet install package To search for a package in the repositories: @@ -68,15 +68,15 @@ $ luet search package To list all packages installed in the system: -$ luet search --installed . + $ luet search --installed . To show hidden packages: -$ luet search --hidden package + $ luet search --hidden package To build a package, from a tree definition: -$ luet build --tree tree/path package + $ luet build --tree tree/path package `, Version: fmt.Sprintf("%s-g%s %s", LuetCLIVersion, BuildCommit, BuildTime), diff --git a/pkg/bus/events.go b/pkg/bus/events.go index dffcaeb0..a72e3643 100644 --- a/pkg/bus/events.go +++ b/pkg/bus/events.go @@ -23,6 +23,21 @@ var ( // EventPackagePostBuild is the event fired after a package was built EventPackagePostBuild pluggable.EventType = "package.post.build" + // Image build + + // EventImagePreBuild is the event fired before a image is being built + EventImagePreBuild pluggable.EventType = "image.pre.build" + // EventImagePrePull is the event fired before a image is being pulled + EventImagePrePull pluggable.EventType = "image.pre.pull" + // EventImagePrePush is the event fired before a image is being pushed + EventImagePrePush pluggable.EventType = "image.pre.push" + // EventImagePostBuild is the event fired after an image is being built + EventImagePostBuild pluggable.EventType = "image.post.build" + // EventImagePostPull is the event fired after an image is being pulled + EventImagePostPull pluggable.EventType = "image.post.pull" + // EventImagePostPush is the event fired after an image is being pushed + EventImagePostPush pluggable.EventType = "image.post.push" + // Repository events // EventRepositoryPreBuild is the event fired before a repository is being built @@ -42,5 +57,11 @@ var Manager *pluggable.Manager = pluggable.NewManager( EventPackagePostBuild, EventRepositoryPreBuild, EventRepositoryPostBuild, + EventImagePreBuild, + EventImagePrePull, + EventImagePrePush, + EventImagePostBuild, + EventImagePostPull, + EventImagePostPush, }, ) diff --git a/pkg/compiler/compiler.go b/pkg/compiler/compiler.go index a1023fe5..8ff2cd5d 100644 --- a/pkg/compiler/compiler.go +++ b/pkg/compiler/compiler.go @@ -340,8 +340,6 @@ func (cs *LuetCompiler) buildPackageImage(image, buildertaggedImage, packageImag } } - Info(pkgTag, ":whale: Generating 'builder' image definition from", image) - // First we create the builder image if err := p.WriteBuildImageDefinition(filepath.Join(buildDir, p.GetPackage().GetFingerPrint()+"-builder.dockerfile")); err != nil { return builderOpts, runnerOpts, errors.Wrap(err, "Could not generate image definition") @@ -368,31 +366,38 @@ func (cs *LuetCompiler) buildPackageImage(image, buildertaggedImage, packageImag buildAndPush := func(opts CompilerBackendOptions) error { buildImage := true if cs.Options.PullFirst { + bus.Manager.Publish(bus.EventImagePrePull, opts) err := cs.Backend.DownloadImage(opts) if err == nil { buildImage = false } else { - Warning("Failed to download image. Will keep going and build the image unless you use --fatal") + Warning("Failed to download '" + opts.ImageName + "'. Will keep going and build the image unless you use --fatal") Warning(err.Error()) } + bus.Manager.Publish(bus.EventImagePostPull, opts) } if buildImage { + bus.Manager.Publish(bus.EventImagePreBuild, opts) if err := cs.Backend.BuildImage(opts); err != nil { return errors.Wrap(err, "Could not build image: "+image+" "+opts.DockerFileName) } + bus.Manager.Publish(bus.EventImagePostBuild, opts) if cs.Options.Push { + bus.Manager.Publish(bus.EventImagePrePush, opts) if err = cs.Backend.Push(opts); err != nil { return errors.Wrap(err, "Could not push image: "+image+" "+opts.DockerFileName) } + bus.Manager.Publish(bus.EventImagePostPush, opts) } } return nil } + Info(pkgTag, ":whale: Generating 'builder' image from", image, "as", buildertaggedImage, "with prelude steps") if err := buildAndPush(builderOpts); err != nil { return builderOpts, runnerOpts, errors.Wrap(err, "Could not push image: "+image+" "+builderOpts.DockerFileName) } - + Info(pkgTag, ":whale: Generating 'package' image from", buildertaggedImage, "as", packageImage, "with build steps") if err := buildAndPush(runnerOpts); err != nil { return builderOpts, runnerOpts, errors.Wrap(err, "Could not push image: "+image+" "+builderOpts.DockerFileName) } diff --git a/pkg/installer/installer.go b/pkg/installer/installer.go index 49fbd64e..e0e8c8d7 100644 --- a/pkg/installer/installer.go +++ b/pkg/installer/installer.go @@ -153,7 +153,7 @@ func (l *LuetInstaller) Upgrade(s *System) error { } if len(toInstall) > 0 { - Info(":zap: Packages that are going to be installed in the system:\n ", Green(packsToList(toInstall)).BgBlack().String()) + Info(":zap:Packages that are going to be installed in the system:\n ", Green(packsToList(toInstall)).BgBlack().String()) } if len(toInstall) == 0 && len(uninstall) == 0 { @@ -203,6 +203,25 @@ func (l *LuetInstaller) Swap(toRemove pkg.Packages, toInstall pkg.Packages, s *S if err != nil { return err } + + 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 + return l.swap(syncedRepos, toRemove, toInstall, s) + } else { + return errors.New("Aborted by user") + } + } + return l.swap(syncedRepos, toRemove, toInstall, s) } @@ -363,7 +382,7 @@ func (l *LuetInstaller) Reclaim(s *System) error { return errors.Wrap(err, "Failed creating package") } s.Database.SetPackageFiles(&pkg.PackageFile{PackageFingerprint: pack.GetFingerPrint(), Files: match.Artifact.GetFiles()}) - Info(":zap: Reclaimed package:", pack.HumanReadableString()) + Info(":zap:Reclaimed package:", pack.HumanReadableString()) } Info("Done!") @@ -702,7 +721,7 @@ func (l *LuetInstaller) uninstall(p pkg.Package, s *System) error { bus.Manager.Publish(bus.EventPackageUnInstall, p) - Info(":recycle:", p.GetFingerPrint(), "Removed :heavy_check_mark:") + Info(":recycle: ", p.GetFingerPrint(), "Removed :heavy_check_mark:") return nil } diff --git a/pkg/installer/repository.go b/pkg/installer/repository.go index 392a03c9..b8f4ef9a 100644 --- a/pkg/installer/repository.go +++ b/pkg/installer/repository.go @@ -711,9 +711,12 @@ func (r *LuetSystemRepository) Sync(force bool) (Repository, error) { repo.SetPriority(r.GetPriority()) repo.SetName(r.GetName()) InfoC( - aurora.Bold( - aurora.Yellow(":information_source: Repository "+repo.GetName()+" priority: ")).String() + - aurora.Bold(aurora.Green(repo.GetPriority())).String() + " - type " + + aurora.Yellow(":information_source:").String() + + aurora.Magenta("Repository: ").String() + + aurora.Green(aurora.Bold(repo.GetName()).String()).String() + + aurora.Magenta(" Priority: ").String() + + aurora.Bold(aurora.Green(repo.GetPriority())).String() + + aurora.Magenta(" Type: ").String() + aurora.Bold(aurora.Green(repo.GetType())).String(), ) return repo, nil diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index f6bf572b..6c48ebac 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -202,7 +202,7 @@ func msg(level string, withoutColor bool, msg ...interface{}) { case "info": levelMsg = message case "error": - levelMsg = Bold(Red(":bomb: " + message + ":fire:")).BgBlack().String() + levelMsg = Red(message).String() } } @@ -248,6 +248,6 @@ func Error(mess ...interface{}) { } func Fatal(mess ...interface{}) { - Error(mess) + Error(mess...) os.Exit(1) }